From 1d0808018be37a5336d3b11f00889e33fe84aa13 Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:01:32 +0300 Subject: [PATCH 01/12] feat(MED-122): don't use math.random on ssr --- packages/ui/src/shadcn/sidebar.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ui/src/shadcn/sidebar.tsx b/packages/ui/src/shadcn/sidebar.tsx index 7391317..bb50d83 100644 --- a/packages/ui/src/shadcn/sidebar.tsx +++ b/packages/ui/src/shadcn/sidebar.tsx @@ -674,11 +674,6 @@ const SidebarMenuSkeleton: React.FC< showIcon?: boolean; } > = ({ className, showIcon = false, ...props }) => { - // Random width between 50 to 90%. - const width = React.useMemo(() => { - return `${Math.floor(Math.random() * 40) + 50}%`; - }, []); - return (
From 00b079e1702997977a270483037958161d87f397 Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:05:55 +0300 Subject: [PATCH 02/12] feat(MED-122): fix error in case of cart without shipping --- .../src/modules/order/components/shipping-details/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/features/medusa-storefront/src/modules/order/components/shipping-details/index.tsx b/packages/features/medusa-storefront/src/modules/order/components/shipping-details/index.tsx index e83564c..dc8ad5b 100644 --- a/packages/features/medusa-storefront/src/modules/order/components/shipping-details/index.tsx +++ b/packages/features/medusa-storefront/src/modules/order/components/shipping-details/index.tsx @@ -9,6 +9,10 @@ type ShippingDetailsProps = { } const ShippingDetails = ({ order }: ShippingDetailsProps) => { + if (!order.shipping_methods || order.shipping_methods.length === 0) { + return null; + } + return (
@@ -58,7 +62,7 @@ const ShippingDetails = ({ order }: ShippingDetailsProps) => { {(order as any).shipping_methods[0]?.name} ( {convertToLocale({ - amount: order.shipping_methods?.[0].total ?? 0, + amount: order.shipping_methods?.[0]?.total ?? 0, currency_code: order.currency_code, }) .replace(/,/g, "") From 02bb9f7d342db9d858fdacacf728a2562c981e5a Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:08:52 +0300 Subject: [PATCH 03/12] feat(MED-99): use montonio api and webhook --- .env.example | 6 +- package.json | 2 + .../billing/core/src/create-billing-schema.ts | 1 + .../billing-webhook-handler.service.ts | 64 +- packages/billing/gateway/package.json | 1 + .../billing-event-handler-factory.service.ts | 7 + packages/billing/montonio/README.md | 4 + packages/billing/montonio/eslint.config.mjs | 3 + packages/billing/montonio/package.json | 35 + packages/billing/montonio/src/index.ts | 2 + .../src/schema/montonio-client-env.schema.ts | 6 + .../src/schema/montonio-server-env.schema.ts | 15 + .../montonio-order-handler.service.ts | 63 ++ .../montonio-webhook-handler.service.ts | 111 ++++ packages/billing/montonio/tsconfig.json | 8 + packages/database-webhooks/package.json | 1 + packages/supabase/src/database.types.ts | 4 +- pnpm-lock.yaml | 622 +++++++++++++++--- .../20250717075135_montonio_type.sql | 1 + 19 files changed, 813 insertions(+), 143 deletions(-) create mode 100644 packages/billing/montonio/README.md create mode 100644 packages/billing/montonio/eslint.config.mjs create mode 100644 packages/billing/montonio/package.json create mode 100644 packages/billing/montonio/src/index.ts create mode 100644 packages/billing/montonio/src/schema/montonio-client-env.schema.ts create mode 100644 packages/billing/montonio/src/schema/montonio-server-env.schema.ts create mode 100644 packages/billing/montonio/src/services/montonio-order-handler.service.ts create mode 100644 packages/billing/montonio/src/services/montonio-webhook-handler.service.ts create mode 100644 packages/billing/montonio/tsconfig.json create mode 100644 supabase/migrations/20250717075135_montonio_type.sql diff --git a/.env.example b/.env.example index 1b78f4e..92af783 100644 --- a/.env.example +++ b/.env.example @@ -18,4 +18,8 @@ EMAIL_HOST= # refer to your email provider's documentation EMAIL_PORT= # or 465 for SSL EMAIL_TLS= # or false for SSL (see provider documentation) -NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY= \ No newline at end of file +NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY= + +NEXT_PUBLIC_MONTONIO_ACCESS_KEY=7da5d7fa-3383-4997-9435-46aa818f4ead +MONTONIO_SECRET_KEY=rNZkzwxOiH93mzkdV53AvhSsbGidrgO2Kl5lE/IT7cvo +MONTONIO_API_URL=https://sandbox-stargate.montonio.com diff --git a/package.json b/package.json index ce946e8..0f29eed 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "clsx": "^2.1.1", "date-fns": "^4.1.0", "fast-xml-parser": "^5.2.5", + "jsonwebtoken": "9.0.2", "lodash": "^4.17.21", "lucide-react": "^0.510.0", "next": "15.3.2", @@ -92,6 +93,7 @@ "@medusajs/ui-preset": "latest", "@next/bundle-analyzer": "15.3.2", "@tailwindcss/postcss": "^4.1.10", + "@types/jsonwebtoken": "9.0.10", "@types/lodash": "^4.17.17", "@types/node": "^22.15.32", "@types/react": "19.1.4", diff --git a/packages/billing/core/src/create-billing-schema.ts b/packages/billing/core/src/create-billing-schema.ts index 8f33c1f..deeb15e 100644 --- a/packages/billing/core/src/create-billing-schema.ts +++ b/packages/billing/core/src/create-billing-schema.ts @@ -13,6 +13,7 @@ export const BillingProviderSchema = z.enum([ 'stripe', 'paddle', 'lemon-squeezy', + 'montonio', ]); export const PaymentTypeSchema = z.enum(['one-time', 'recurring']); diff --git a/packages/billing/core/src/services/billing-webhook-handler.service.ts b/packages/billing/core/src/services/billing-webhook-handler.service.ts index 5e4a88f..0a047b9 100644 --- a/packages/billing/core/src/services/billing-webhook-handler.service.ts +++ b/packages/billing/core/src/services/billing-webhook-handler.service.ts @@ -1,5 +1,37 @@ import { UpsertOrderParams, UpsertSubscriptionParams } from '../types'; +export interface IHandleWebhookEventParams { + // this method is called when a checkout session is completed + onCheckoutSessionCompleted: ( + subscription: UpsertSubscriptionParams | UpsertOrderParams, + ) => Promise; + + // this method is called when a subscription is updated + onSubscriptionUpdated: ( + subscription: UpsertSubscriptionParams, + ) => Promise; + + // this method is called when a subscription is deleted + onSubscriptionDeleted: (subscriptionId: string) => Promise; + + // this method is called when a payment is succeeded. This is used for + // one-time payments + onPaymentSucceeded: (sessionId: string) => Promise; + + // this method is called when a payment is failed. This is used for + // one-time payments + onPaymentFailed: (sessionId: string) => Promise; + + // this method is called when an invoice is paid. We don't have a specific use case for this + // but it's extremely common for credit-based systems + onInvoicePaid: ( + subscription: UpsertSubscriptionParams, + ) => Promise; + + // generic handler for any event + onEvent?: (data: unknown) => Promise; +} + /** * @name BillingWebhookHandlerService * @description Represents an abstract class for handling billing webhook events. @@ -20,36 +52,6 @@ export abstract class BillingWebhookHandlerService { */ abstract handleWebhookEvent( event: unknown, - params: { - // this method is called when a checkout session is completed - onCheckoutSessionCompleted: ( - subscription: UpsertSubscriptionParams | UpsertOrderParams, - ) => Promise; - - // this method is called when a subscription is updated - onSubscriptionUpdated: ( - subscription: UpsertSubscriptionParams, - ) => Promise; - - // this method is called when a subscription is deleted - onSubscriptionDeleted: (subscriptionId: string) => Promise; - - // this method is called when a payment is succeeded. This is used for - // one-time payments - onPaymentSucceeded: (sessionId: string) => Promise; - - // this method is called when a payment is failed. This is used for - // one-time payments - onPaymentFailed: (sessionId: string) => Promise; - - // this method is called when an invoice is paid. We don't have a specific use case for this - // but it's extremely common for credit-based systems - onInvoicePaid: ( - subscription: UpsertSubscriptionParams, - ) => Promise; - - // generic handler for any event - onEvent?: (data: unknown) => Promise; - }, + params: IHandleWebhookEventParams, ): Promise; } diff --git a/packages/billing/gateway/package.json b/packages/billing/gateway/package.json index 94446f3..57d05cc 100644 --- a/packages/billing/gateway/package.json +++ b/packages/billing/gateway/package.json @@ -23,6 +23,7 @@ "@kit/prettier-config": "workspace:*", "@kit/shared": "workspace:*", "@kit/stripe": "workspace:*", + "@kit/montonio": "workspace:*", "@kit/supabase": "workspace:*", "@kit/tsconfig": "workspace:*", "@kit/ui": "workspace:*", diff --git a/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler-factory.service.ts b/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler-factory.service.ts index 213c53f..4779600 100644 --- a/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler-factory.service.ts +++ b/packages/billing/gateway/src/server/services/billing-event-handler/billing-event-handler-factory.service.ts @@ -30,6 +30,13 @@ export function createBillingEventHandlerFactoryService( return new StripeWebhookHandlerService(planTypesMap); }); + // Register the Montonio webhook handler + billingWebhookHandlerRegistry.register('montonio', async () => { + const { MontonioWebhookHandlerService } = await import('@kit/montonio'); + + return new MontonioWebhookHandlerService(); + }); + // Register the Lemon Squeezy webhook handler billingWebhookHandlerRegistry.register('lemon-squeezy', async () => { const { LemonSqueezyWebhookHandlerService } = await import( diff --git a/packages/billing/montonio/README.md b/packages/billing/montonio/README.md new file mode 100644 index 0000000..608cb7b --- /dev/null +++ b/packages/billing/montonio/README.md @@ -0,0 +1,4 @@ +# Billing / Montonio - @kit/montonio + +This package is responsible for handling all billing related operations using Montonio. + diff --git a/packages/billing/montonio/eslint.config.mjs b/packages/billing/montonio/eslint.config.mjs new file mode 100644 index 0000000..97563ae --- /dev/null +++ b/packages/billing/montonio/eslint.config.mjs @@ -0,0 +1,3 @@ +import eslintConfigBase from '@kit/eslint-config/base.js'; + +export default eslintConfigBase; diff --git a/packages/billing/montonio/package.json b/packages/billing/montonio/package.json new file mode 100644 index 0000000..8904515 --- /dev/null +++ b/packages/billing/montonio/package.json @@ -0,0 +1,35 @@ +{ + "name": "@kit/montonio", + "private": true, + "version": "0.1.0", + "scripts": { + "clean": "git clean -xdf .turbo node_modules", + "format": "prettier --check \"**/*.{ts,tsx}\"", + "lint": "eslint .", + "typecheck": "tsc --noEmit" + }, + "prettier": "@kit/prettier-config", + "exports": { + ".": "./src/index.ts" + }, + "devDependencies": { + "@kit/billing": "workspace:*", + "@kit/eslint-config": "workspace:*", + "@kit/prettier-config": "workspace:*", + "@kit/shared": "workspace:*", + "@kit/supabase": "workspace:*", + "@kit/tsconfig": "workspace:*", + "@kit/ui": "workspace:*", + "@types/react": "19.1.4", + "date-fns": "^4.1.0", + "next": "15.3.2", + "react": "19.1.0" + }, + "typesVersions": { + "*": { + "*": [ + "src/*" + ] + } + } +} diff --git a/packages/billing/montonio/src/index.ts b/packages/billing/montonio/src/index.ts new file mode 100644 index 0000000..8e534a9 --- /dev/null +++ b/packages/billing/montonio/src/index.ts @@ -0,0 +1,2 @@ +export { MontonioWebhookHandlerService } from './services/montonio-webhook-handler.service'; +export { MontonioOrderHandlerService } from './services/montonio-order-handler.service'; diff --git a/packages/billing/montonio/src/schema/montonio-client-env.schema.ts b/packages/billing/montonio/src/schema/montonio-client-env.schema.ts new file mode 100644 index 0000000..eadb056 --- /dev/null +++ b/packages/billing/montonio/src/schema/montonio-client-env.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const MontonioClientEnvSchema = z + .object({ + accessKey: z.string().min(1), + }); diff --git a/packages/billing/montonio/src/schema/montonio-server-env.schema.ts b/packages/billing/montonio/src/schema/montonio-server-env.schema.ts new file mode 100644 index 0000000..e867cae --- /dev/null +++ b/packages/billing/montonio/src/schema/montonio-server-env.schema.ts @@ -0,0 +1,15 @@ +import { z } from 'zod'; + +export const MontonioServerEnvSchema = z + .object({ + secretKey: z + .string({ + required_error: `Please provide the variable MONTONIO_SECRET_KEY`, + }) + .min(1), + apiUrl: z + .string({ + required_error: `Please provide the variable MONTONIO_API_URL`, + }) + .min(1), + }); diff --git a/packages/billing/montonio/src/services/montonio-order-handler.service.ts b/packages/billing/montonio/src/services/montonio-order-handler.service.ts new file mode 100644 index 0000000..03aff3d --- /dev/null +++ b/packages/billing/montonio/src/services/montonio-order-handler.service.ts @@ -0,0 +1,63 @@ +import jwt from 'jsonwebtoken'; +import axios, { AxiosError } from 'axios'; +import { MontonioClientEnvSchema } from '../schema/montonio-client-env.schema'; +import { MontonioServerEnvSchema } from '../schema/montonio-server-env.schema'; + +const { accessKey } = MontonioClientEnvSchema.parse({ + accessKey: process.env.NEXT_PUBLIC_MONTONIO_ACCESS_KEY, +}); + +const { apiUrl, secretKey } = MontonioServerEnvSchema.parse({ + apiUrl: process.env.MONTONIO_API_URL, + secretKey: process.env.MONTONIO_SECRET_KEY, +}); + +export class MontonioOrderHandlerService { + public async getMontonioPaymentLink({ + notificationUrl, + returnUrl, + amount, + currency, + description, + locale, + merchantReference, + }: { + notificationUrl: string; + returnUrl: string; + amount: number; + currency: string; + description: string; + locale: string; + merchantReference: string; + }) { + const token = jwt.sign({ + accessKey, + description, + currency, + amount, + locale, + // 15 minutes + expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(), + notificationUrl, + returnUrl, + askAdditionalInfo: false, + merchantReference, + type: "one_time", + }, secretKey, { + algorithm: "HS256", + expiresIn: "10m", + }); + + try { + const { data } = await axios.post(`${apiUrl}/api/payment-links`, { data: token }); + return data.url; + } catch (error) { + if (error instanceof AxiosError) { + console.error(error.response?.data); + } + console.error(error); + throw new Error("Failed to create payment link"); + } + } + +} diff --git a/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts b/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts new file mode 100644 index 0000000..20fa308 --- /dev/null +++ b/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts @@ -0,0 +1,111 @@ +import type { BillingWebhookHandlerService, IHandleWebhookEventParams } from '@kit/billing'; +import { getLogger } from '@kit/shared/logger'; +import { Database, Enums } from '@kit/supabase/database'; +import jwt from 'jsonwebtoken'; +import { MontonioServerEnvSchema } from '../schema/montonio-server-env.schema'; + +type UpsertOrderParams = + Database['medreport']['Functions']['upsert_order']['Args']; + +type BillingProvider = Enums<{ schema: 'medreport' }, 'billing_provider'>; + +interface MontonioOrderToken { + uuid: string; + accessKey: string; + merchantReference: string; + merchantReferenceDisplay: string; + paymentStatus: 'PAID' | 'FAILED' | 'CANCELLED' | 'PENDING' | 'EXPIRED' | 'REFUNDED'; + paymentMethod: string; + grandTotal: number; + currency: string; + senderIban?: string; + senderName?: string; + paymentProviderName?: string; + paymentLinkUuid: string; + iat: number; + exp: number; +} + +const { secretKey } = MontonioServerEnvSchema.parse({ + apiUrl: process.env.MONTONIO_API_URL, + secretKey: process.env.MONTONIO_SECRET_KEY, +}); + +export class MontonioWebhookHandlerService + implements BillingWebhookHandlerService +{ + private readonly provider: BillingProvider = 'montonio'; + private readonly namespace = 'billing.montonio'; + + async verifyWebhookSignature(request: Request) { + const logger = await getLogger(); + + let token: string; + try { + const url = new URL(request.url); + const searchParams = url.searchParams; + console.info("searchParams", searchParams, url); + const tokenParam = searchParams.get('order-token') as string | null; + if (!tokenParam) { + throw new Error('Missing order-token'); + } + token = tokenParam; + } catch (error) { + logger.error({ + error, + name: this.namespace, + }, `Failed to parse Montonio webhook request`); + throw new Error('Invalid request'); + } + + try { + const decoded = jwt.verify(token, secretKey, { + algorithms: ['HS256'], + }); + return decoded as MontonioOrderToken; + } catch (error) { + logger.error({ + error, + name: this.namespace, + }, `Failed to verify Montonio webhook signature`); + throw new Error('Invalid signature'); + } + } + + async handleWebhookEvent( + event: MontonioOrderToken, + params: IHandleWebhookEventParams + ) { + const logger = await getLogger(); + + logger.info({ + name: this.namespace, + event, + }, `Received Montonio webhook event`); + + if (event.paymentStatus === 'PAID') { + const accountId = event.merchantReferenceDisplay.split(':')[0]; + if (!accountId) { + throw new Error('Invalid merchant reference'); + } + const order: UpsertOrderParams = { + target_account_id: accountId, + target_customer_id: '', + target_order_id: event.uuid, + status: 'succeeded', + billing_provider: this.provider, + total_amount: event.grandTotal, + currency: event.currency, + line_items: [], + }; + return params.onCheckoutSessionCompleted(order); + } + + if (event.paymentStatus === 'FAILED' || event.paymentStatus === 'CANCELLED') { + return params.onPaymentFailed(event.uuid); + } + + return; + } +} + \ No newline at end of file diff --git a/packages/billing/montonio/tsconfig.json b/packages/billing/montonio/tsconfig.json new file mode 100644 index 0000000..c4697e9 --- /dev/null +++ b/packages/billing/montonio/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@kit/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["*.ts", "src"], + "exclude": ["node_modules"] +} diff --git a/packages/database-webhooks/package.json b/packages/database-webhooks/package.json index 1c85411..2a032b0 100644 --- a/packages/database-webhooks/package.json +++ b/packages/database-webhooks/package.json @@ -19,6 +19,7 @@ "@kit/prettier-config": "workspace:*", "@kit/shared": "workspace:*", "@kit/stripe": "workspace:*", + "@kit/montonio": "workspace:*", "@kit/supabase": "workspace:*", "@kit/team-accounts": "workspace:*", "@kit/tsconfig": "workspace:*", diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 053c4fc..3f1b629 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -1736,7 +1736,7 @@ export type Database = { | "settings.manage" | "members.manage" | "invites.manage" - billing_provider: "stripe" | "lemon-squeezy" | "paddle" + billing_provider: "stripe" | "lemon-squeezy" | "paddle" | "montonio" notification_channel: "in_app" | "email" notification_type: "info" | "warning" | "error" payment_status: "pending" | "succeeded" | "failed" @@ -1938,7 +1938,7 @@ export const Constants = { "members.manage", "invites.manage", ], - billing_provider: ["stripe", "lemon-squeezy", "paddle"], + billing_provider: ["stripe", "lemon-squeezy", "paddle", "montonio"], notification_channel: ["in_app", "email"], notification_type: ["info", "warning", "error"], payment_status: ["pending", "succeeded", "failed"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e5c761..c58f49e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@edge-csrf/nextjs': specifier: 2.5.3-cloudflare-rc1 - version: 2.5.3-cloudflare-rc1(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 2.5.3-cloudflare-rc1(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@hookform/resolvers': specifier: ^5.1.1 version: 5.1.1(react-hook-form@7.58.0(react@19.1.0)) @@ -73,7 +73,7 @@ importers: version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.5 - version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) '@marsidev/react-turnstile': specifier: ^1.1.0 version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -82,13 +82,13 @@ importers: version: 2.8.6(react@19.1.0) '@medusajs/js-sdk': specifier: latest - version: 2.8.6(awilix@8.0.1) + version: 2.8.7(awilix@8.0.1) '@medusajs/ui': specifier: latest - version: 4.0.16(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + version: 4.0.17(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) '@nosecone/next': specifier: 1.0.0-beta.7 - version: 1.0.0-beta.7(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.0.0-beta.7(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.1.0) @@ -119,6 +119,9 @@ importers: fast-xml-parser: specifier: ^5.2.5 version: 5.2.5 + jsonwebtoken: + specifier: 9.0.2 + version: 9.0.2 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -126,11 +129,11 @@ importers: specifier: ^0.510.0 version: 0.510.0(react@19.1.0) next: - specifier: 15.3.2 - version: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.3.5 + version: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 4.2.3(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -173,16 +176,19 @@ importers: version: link:tooling/typescript '@medusajs/types': specifier: latest - version: 2.8.6(awilix@8.0.1) + version: 2.8.7(awilix@8.0.1) '@medusajs/ui-preset': specifier: latest - version: 2.8.6(tailwindcss@4.1.7) + version: 2.8.7(tailwindcss@4.1.7) '@next/bundle-analyzer': specifier: 15.3.2 version: 15.3.2 '@tailwindcss/postcss': specifier: ^4.1.10 version: 4.1.10 + '@types/jsonwebtoken': + specifier: 9.0.10 + version: 9.0.10 '@types/lodash': specifier: ^4.17.17 version: 4.17.17 @@ -273,6 +279,9 @@ importers: '@kit/lemon-squeezy': specifier: workspace:* version: link:../lemon-squeezy + '@kit/montonio': + specifier: workspace:* + version: link:../montonio '@kit/prettier-config': specifier: workspace:* version: link:../../../tooling/prettier @@ -350,6 +359,42 @@ importers: specifier: 19.1.0 version: 19.1.0 + packages/billing/montonio: + devDependencies: + '@kit/billing': + specifier: workspace:* + version: link:../core + '@kit/eslint-config': + specifier: workspace:* + version: link:../../../tooling/eslint + '@kit/prettier-config': + specifier: workspace:* + version: link:../../../tooling/prettier + '@kit/shared': + specifier: workspace:* + version: link:../../shared + '@kit/supabase': + specifier: workspace:* + version: link:../../supabase + '@kit/tsconfig': + specifier: workspace:* + version: link:../../../tooling/typescript + '@kit/ui': + specifier: workspace:* + version: link:../../ui + '@types/react': + specifier: 19.1.4 + version: 19.1.4 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + next: + specifier: 15.3.2 + version: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: + specifier: 19.1.0 + version: 19.1.0 + packages/billing/stripe: dependencies: '@stripe/react-stripe-js': @@ -427,10 +472,10 @@ importers: dependencies: '@keystatic/core': specifier: 0.5.47 - version: 0.5.47(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 0.5.47(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@keystatic/next': specifier: ^5.0.4 - version: 5.0.4(@keystatic/core@0.5.47(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 5.0.4(@keystatic/core@0.5.47(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@markdoc/markdoc': specifier: ^0.5.1 version: 0.5.2(@types/react@19.1.4)(react@19.1.0) @@ -510,6 +555,9 @@ importers: '@kit/eslint-config': specifier: workspace:* version: link:../../tooling/eslint + '@kit/montonio': + specifier: workspace:* + version: link:../billing/montonio '@kit/prettier-config': specifier: workspace:* version: link:../../tooling/prettier @@ -751,10 +799,10 @@ importers: version: 2.2.4(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@medusajs/js-sdk': specifier: latest - version: 2.8.6(awilix@8.0.1) + version: 2.8.7(awilix@8.0.1) '@medusajs/ui': specifier: latest - version: 4.0.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3) + version: 4.0.17(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3) '@radix-ui/react-accordion': specifier: ^1.2.1 version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -800,10 +848,10 @@ importers: version: 7.27.4 '@medusajs/types': specifier: latest - version: 2.8.6(awilix@8.0.1) + version: 2.8.7(awilix@8.0.1) '@medusajs/ui-preset': specifier: latest - version: 2.8.6(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))) + version: 2.8.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))) '@types/lodash': specifier: ^4.14.195 version: 4.17.17 @@ -1173,7 +1221,7 @@ importers: dependencies: '@sentry/nextjs': specifier: ^9.19.0 - version: 9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9) + version: 9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9) import-in-the-middle: specifier: 1.13.2 version: 1.13.2 @@ -2131,12 +2179,17 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - '@medusajs/js-sdk@2.8.6': - resolution: {integrity: sha512-nrecLAeo+uHfL8u7WhUleysSRPjcgPLSgWbiRIq1oFeJU91M8xVlvNZgz+MSgwiDWCJsMYBr/8bgEu7+PxSy0w==} + '@medusajs/icons@2.8.7': + resolution: {integrity: sha512-zGkAokqWBNJ1PcTktCPSMT5spIIjv8Pba88BXvfcbblG5cUbMSvvJ2v/BRODMFejQ9NqlboIeP0fo/9RzLpPHg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + '@medusajs/js-sdk@2.8.7': + resolution: {integrity: sha512-ZGYMQOM7GHuKtxOvJ+wgKyC/fzLlyMu5nij4hIWIf2osZy7d6dpvEglcV6w9B0UgSEADJh1SZ7a22HOJdjjJ9A==} engines: {node: '>=20'} - '@medusajs/types@2.8.6': - resolution: {integrity: sha512-SAkRVASQL+Rd+snaq2kEXJCfJBUjB6U4PBKvS+yHWdTVnvGUxkD725I4W+hWORjCK85po0lNdhv5Hx5YVe2BoQ==} + '@medusajs/types@2.8.7': + resolution: {integrity: sha512-8m/H9KkDUQz4YD+XkD/C63RfE/2elcdWf5G/KOK2QViTK0Jsd/Iw8Yy+T60pm0Lq/QQ925AfGH/Ji8UYNXjT8g==} engines: {node: '>=20'} peerDependencies: awilix: ^8.0.1 @@ -2148,13 +2201,13 @@ packages: vite: optional: true - '@medusajs/ui-preset@2.8.6': - resolution: {integrity: sha512-t5LKe7rOZeP8D87d1nzc3wjDSzVypMzasM3CbUlQVZ78xgfnewjqkbVlFacSQy4X36sIKHV4eHW8/E6cxdGbng==} + '@medusajs/ui-preset@2.8.7': + resolution: {integrity: sha512-ro8BrYlqHh7iZvYKrxmJtLweJYYet+wYQQv0R3pyfxkkP0aQ09KDPo8yTwls11iuMC4cQHljekdaOyXtSR6ZiQ==} peerDependencies: tailwindcss: '>=3.0.0' - '@medusajs/ui@4.0.16': - resolution: {integrity: sha512-eAziJhmVM4mQPbAE1qXFKF3oTk+j32XFjXU8LEjuw5TN8gXuWHEu+5/aNxycV8N01ooO41Bms5rcGzPuj8kcGw==} + '@medusajs/ui@4.0.17': + resolution: {integrity: sha512-N5KtZXvns13jDiCE3ZgZLINQnlECYLf4Q4GFdbRhCjAFKFBRGyyeNKX+Zo2wBUZA2Oi4kockdxFfsZfBHh/ZhA==} peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc @@ -2171,6 +2224,12 @@ packages: '@next/env@15.3.2': resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + '@next/env@15.3.5': + resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} + + '@next/env@15.4.0-canary.128': + resolution: {integrity: sha512-o1L2iI/6zHvYQo4hwwf7a3eu5lEOLBChl79Apreub/EwUFHAoRHfmaMYlVfk4uWo3XUNvMRxOG+dRAWmxn4mJQ==} + '@next/eslint-plugin-next@15.0.3': resolution: {integrity: sha512-3Ln/nHq2V+v8uIaxCR6YfYo7ceRgZNXfTd3yW1ukTaFbO+/I8jNakrjYWODvG9BuR2v5kgVtH/C8r0i11quOgw==} @@ -2183,48 +2242,144 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.3.5': + resolution: {integrity: sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-arm64@15.4.0-canary.128': + resolution: {integrity: sha512-3W+dQHTO4baj4iMfWCcXDrrpYsQmuLE1rqLjx3UjMswrJx5DSOPre2haAH5W6CtLvTVEB8x/xCCzJ8ZlqfF5CA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@15.3.2': resolution: {integrity: sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.3.5': + resolution: {integrity: sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-darwin-x64@15.4.0-canary.128': + resolution: {integrity: sha512-BlXLOSoc9Xubx/ZRB1k76Akd7ildo98Ypn+IglZiapheAfA//euQZUHZXD66ZVyWHYqdCEw7vg4EzpnXA8wlpg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@15.3.2': resolution: {integrity: sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.3.5': + resolution: {integrity: sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-gnu@15.4.0-canary.128': + resolution: {integrity: sha512-ZUnD74X5yTa/De0s1x/SG6Rv+MGEx6nuz+Th3qmKOUCmwzlxE4UBF7+Vwti4Wg8aEEzxebJUR8WRrmWwvmui8g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@15.3.2': resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.3.5': + resolution: {integrity: sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.4.0-canary.128': + resolution: {integrity: sha512-pMJ79xdufMeLuOyHY6F0LE/GgHRZZiLVch7YntB7dNZ8HZbV+br4UzssAgr3JfAGyuRnNY0dWgreKwGyGDNSfw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@15.3.2': resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.3.5': + resolution: {integrity: sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.4.0-canary.128': + resolution: {integrity: sha512-33xXJqrbQO/qNO1n9tLbz6o38j7bs4VnOMmWVHPZH9IAHA99gHFThUZZ4TXgVa8Yj6lgIOLb7MuymaN55FZeZA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@15.3.2': resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.3.5': + resolution: {integrity: sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.4.0-canary.128': + resolution: {integrity: sha512-WJv6LPLKJXqvHTaX6WgliI9enlgm4tpwDE6pL619zgGSI5HXHrDsr4LzlDCD7rmuG+n937umVFESmUrnwTTrGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@15.3.2': resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.3.5': + resolution: {integrity: sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-arm64-msvc@15.4.0-canary.128': + resolution: {integrity: sha512-97Wrx1M3MJSGbiNNbscb/W6avnRCp/9NX9vNTp58caYxk7U7S2e3HJoWS8yeHW9193ci5kHAIBfNDr/GvFws7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-x64-msvc@15.3.2': resolution: {integrity: sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.3.5': + resolution: {integrity: sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.4.0-canary.128': + resolution: {integrity: sha512-NbpGc9eZjQkjwDst5WsAlv+GFKayojuy7sz+01FJ6og8LXRJvzMCTh5uFTdaHIZr47RpZvewIlJW7mjWyvAHjg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -5362,6 +5517,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/linkify-it@3.0.5': resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} @@ -5952,6 +6110,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -6375,6 +6536,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + electron-to-chromium@1.5.168: resolution: {integrity: sha512-RUNQmFLNIWVW6+z32EJQ5+qx8ci6RGvdtDC0Ls+F89wz6I2AthpXF0w0DIrn2jpLX0/PU9ZCo+Qp7bg/EckJmA==} @@ -7232,10 +7396,20 @@ packages: engines: {node: '>=6'} hasBin: true + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -7351,12 +7525,33 @@ packages: lodash.deburr@4.1.0: resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -7678,6 +7873,48 @@ packages: sass: optional: true + next@15.3.5: + resolution: {integrity: sha512-RkazLBMMDJSJ4XZQ81kolSpwiCt907l0xcgcpF4xC2Vml6QVcPNXW0NQRwQ80FFtSn7UM52XN0anaw8TEJXaiw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + next@15.4.0-canary.128: + resolution: {integrity: sha512-Oo1GjM7ToTkus3mEMnKI93NpFt3KgtTnVDyINHrvX/rjdtEHiabNQhgowOqv84h8uLfatV+vsy7gMkYR+UsV/A==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -9565,9 +9802,9 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': dependencies: - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@emnapi/core@1.4.3': dependencies: @@ -9970,7 +10207,7 @@ snapshots: '@juggle/resize-observer@3.4.0': {} - '@keystar/ui@0.7.19(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@keystar/ui@0.7.19(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 '@emotion/css': 11.13.5 @@ -10063,18 +10300,18 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - supports-color - '@keystatic/core@0.5.47(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@keystatic/core@0.5.47(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 '@braintree/sanitize-url': 6.0.4 '@emotion/weak-memoize': 0.3.1 '@floating-ui/react': 0.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@internationalized/string': 3.2.7 - '@keystar/ui': 0.7.19(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@keystar/ui': 0.7.19(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@markdoc/markdoc': 0.4.0(@types/react@19.1.4)(react@19.1.0) '@react-aria/focus': 3.20.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@react-aria/i18n': 3.12.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -10145,13 +10382,13 @@ snapshots: - next - supports-color - '@keystatic/next@5.0.4(@keystatic/core@0.5.47(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@keystatic/next@5.0.4(@keystatic/core@0.5.47(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.27.6 - '@keystatic/core': 0.5.47(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@keystatic/core': 0.5.47(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/react': 19.1.4 chokidar: 3.6.0 - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) server-only: 0.0.1 @@ -10174,6 +10411,16 @@ snapshots: transitivePeerDependencies: - '@supabase/postgrest-js' + '@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + dependencies: + '@makerkit/data-loader-supabase-core': 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4) + '@supabase/supabase-js': 2.49.4 + '@tanstack/react-query': 5.76.1(react@19.1.0) + next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + transitivePeerDependencies: + - '@supabase/postgrest-js' + '@markdoc/markdoc@0.4.0(@types/react@19.1.4)(react@19.1.0)': optionalDependencies: '@types/markdown-it': 12.2.3 @@ -10192,17 +10439,21 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@medusajs/icons@2.8.6(react@19.0.0-rc-66855b96-20241106)': - dependencies: - react: 19.0.0-rc-66855b96-20241106 - '@medusajs/icons@2.8.6(react@19.1.0)': dependencies: react: 19.1.0 - '@medusajs/js-sdk@2.8.6(awilix@8.0.1)': + '@medusajs/icons@2.8.7(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@medusajs/types': 2.8.6(awilix@8.0.1) + react: 19.0.0-rc-66855b96-20241106 + + '@medusajs/icons@2.8.7(react@19.1.0)': + dependencies: + react: 19.1.0 + + '@medusajs/js-sdk@2.8.7(awilix@8.0.1)': + dependencies: + '@medusajs/types': 2.8.7(awilix@8.0.1) fetch-event-stream: 0.1.5 qs: 6.14.0 transitivePeerDependencies: @@ -10210,26 +10461,26 @@ snapshots: - ioredis - vite - '@medusajs/types@2.8.6(awilix@8.0.1)': + '@medusajs/types@2.8.7(awilix@8.0.1)': dependencies: awilix: 8.0.1 bignumber.js: 9.3.0 - '@medusajs/ui-preset@2.8.6(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))': + '@medusajs/ui-preset@2.8.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)))': dependencies: '@tailwindcss/forms': 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))) tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3)) tailwindcss-animate: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.8.3))) - '@medusajs/ui-preset@2.8.6(tailwindcss@4.1.7)': + '@medusajs/ui-preset@2.8.7(tailwindcss@4.1.7)': dependencies: '@tailwindcss/forms': 0.5.10(tailwindcss@4.1.7) tailwindcss: 4.1.7 tailwindcss-animate: 1.0.7(tailwindcss@4.1.7) - '@medusajs/ui@4.0.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)': + '@medusajs/ui@4.0.17(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.8.3)': dependencies: - '@medusajs/icons': 2.8.6(react@19.0.0-rc-66855b96-20241106) + '@medusajs/icons': 2.8.7(react@19.0.0-rc-66855b96-20241106) '@tanstack/react-table': 8.20.5(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) clsx: 1.2.1 copy-to-clipboard: 3.3.3 @@ -10249,9 +10500,9 @@ snapshots: - '@types/react-dom' - typescript - '@medusajs/ui@4.0.16(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)': + '@medusajs/ui@4.0.17(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)': dependencies: - '@medusajs/icons': 2.8.6(react@19.1.0) + '@medusajs/icons': 2.8.7(react@19.1.0) '@tanstack/react-table': 8.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) clsx: 1.2.1 copy-to-clipboard: 3.3.3 @@ -10289,6 +10540,10 @@ snapshots: '@next/env@15.3.2': {} + '@next/env@15.3.5': {} + + '@next/env@15.4.0-canary.128': {} + '@next/eslint-plugin-next@15.0.3': dependencies: fast-glob: 3.3.1 @@ -10300,27 +10555,75 @@ snapshots: '@next/swc-darwin-arm64@15.3.2': optional: true + '@next/swc-darwin-arm64@15.3.5': + optional: true + + '@next/swc-darwin-arm64@15.4.0-canary.128': + optional: true + '@next/swc-darwin-x64@15.3.2': optional: true + '@next/swc-darwin-x64@15.3.5': + optional: true + + '@next/swc-darwin-x64@15.4.0-canary.128': + optional: true + '@next/swc-linux-arm64-gnu@15.3.2': optional: true + '@next/swc-linux-arm64-gnu@15.3.5': + optional: true + + '@next/swc-linux-arm64-gnu@15.4.0-canary.128': + optional: true + '@next/swc-linux-arm64-musl@15.3.2': optional: true + '@next/swc-linux-arm64-musl@15.3.5': + optional: true + + '@next/swc-linux-arm64-musl@15.4.0-canary.128': + optional: true + '@next/swc-linux-x64-gnu@15.3.2': optional: true + '@next/swc-linux-x64-gnu@15.3.5': + optional: true + + '@next/swc-linux-x64-gnu@15.4.0-canary.128': + optional: true + '@next/swc-linux-x64-musl@15.3.2': optional: true + '@next/swc-linux-x64-musl@15.3.5': + optional: true + + '@next/swc-linux-x64-musl@15.4.0-canary.128': + optional: true + '@next/swc-win32-arm64-msvc@15.3.2': optional: true + '@next/swc-win32-arm64-msvc@15.3.5': + optional: true + + '@next/swc-win32-arm64-msvc@15.4.0-canary.128': + optional: true + '@next/swc-win32-x64-msvc@15.3.2': optional: true + '@next/swc-win32-x64-msvc@15.3.5': + optional: true + + '@next/swc-win32-x64-msvc@15.4.0-canary.128': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -10335,9 +10638,9 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@nosecone/next@1.0.0-beta.7(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + '@nosecone/next@1.0.0-beta.7(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': dependencies: - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nosecone: 1.0.0-beta.7 '@opentelemetry/api-logs@0.50.0': @@ -15707,7 +16010,7 @@ snapshots: '@sentry/core@9.27.0': {} - '@sentry/nextjs@9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)': + '@sentry/nextjs@9.27.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.34.0 @@ -15720,7 +16023,7 @@ snapshots: '@sentry/vercel-edge': 9.27.0 '@sentry/webpack-plugin': 3.5.0(webpack@5.99.9) chalk: 3.0.0 - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) resolve: 1.22.8 rollup: 4.35.0 stacktrace-parser: 0.1.11 @@ -16117,6 +16420,11 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.15.32 + '@types/linkify-it@3.0.5': optional: true @@ -16878,6 +17186,8 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.0) + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} busboy@1.6.0: @@ -17300,6 +17610,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + electron-to-chromium@1.5.168: {} emery@1.4.4: {} @@ -17444,8 +17758,8 @@ snapshots: '@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3) eslint: 8.10.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.10.0) eslint-plugin-react: 7.37.5(eslint@8.10.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.10.0) @@ -17464,8 +17778,8 @@ snapshots: '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.28.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.28.0(jiti@2.4.2)) @@ -17490,22 +17804,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1 - eslint: 8.10.0 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.14 - unrs-resolver: 1.7.11 - optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0) - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -17516,62 +17815,48 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.7.11 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1 + eslint: 8.10.0 + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.14 + unrs-resolver: 1.7.11 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.33.1(eslint@8.10.0)(typescript@5.8.3) eslint: 8.10.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.10.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.28.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.28.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 9.28.0(jiti@2.4.2) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -17582,7 +17867,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.10.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -17600,6 +17885,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.28.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@8.10.0): dependencies: aria-query: 5.3.2 @@ -18430,6 +18744,19 @@ snapshots: json5@2.2.3: {} + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.2 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -18437,6 +18764,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -18527,10 +18865,24 @@ snapshots: lodash.deburr@4.1.0: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.uniq@4.5.0: {} lodash@4.17.21: {} @@ -19004,13 +19356,13 @@ snapshots: neo-async@2.6.2: {} - next-sitemap@4.2.3(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): + next-sitemap@4.2.3(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.11 fast-glob: 3.3.3 minimist: 1.2.8 - next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: @@ -19071,6 +19423,58 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.3.5 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001723 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.5 + '@next/swc-darwin-x64': 15.3.5 + '@next/swc-linux-arm64-gnu': 15.3.5 + '@next/swc-linux-arm64-musl': 15.3.5 + '@next/swc-linux-x64-gnu': 15.3.5 + '@next/swc-linux-x64-musl': 15.3.5 + '@next/swc-win32-arm64-msvc': 15.3.5 + '@next/swc-win32-x64-msvc': 15.3.5 + '@opentelemetry/api': 1.9.0 + babel-plugin-react-compiler: 19.1.0-rc.2 + sharp: 0.34.2 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.4.0-canary.128 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001723 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.0-canary.128 + '@next/swc-darwin-x64': 15.4.0-canary.128 + '@next/swc-linux-arm64-gnu': 15.4.0-canary.128 + '@next/swc-linux-arm64-musl': 15.4.0-canary.128 + '@next/swc-linux-x64-gnu': 15.4.0-canary.128 + '@next/swc-linux-x64-musl': 15.4.0-canary.128 + '@next/swc-win32-arm64-msvc': 15.4.0-canary.128 + '@next/swc-win32-x64-msvc': 15.4.0-canary.128 + '@opentelemetry/api': 1.9.0 + babel-plugin-react-compiler: 19.1.0-rc.2 + sharp: 0.34.2 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + no-case@3.0.4: dependencies: lower-case: 2.0.2 diff --git a/supabase/migrations/20250717075135_montonio_type.sql b/supabase/migrations/20250717075135_montonio_type.sql new file mode 100644 index 0000000..5cc840f --- /dev/null +++ b/supabase/migrations/20250717075135_montonio_type.sql @@ -0,0 +1 @@ +alter type public.billing_provider add value 'montonio'; From ea3fb22f1d4b4339cbe4322beb473d78041b0312 Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:10:08 +0300 Subject: [PATCH 04/12] feat(MED-122): delete unused translations --- public/locales/en/product.json | 12 ------------ public/locales/et/product.json | 12 ------------ public/locales/ru/product.json | 12 ------------ 3 files changed, 36 deletions(-) diff --git a/public/locales/en/product.json b/public/locales/en/product.json index e2aaf81..3a5c425 100644 --- a/public/locales/en/product.json +++ b/public/locales/en/product.json @@ -1,16 +1,4 @@ { - "standard": { - "label": "Standard", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "standardPlus": { - "label": "Standard +", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "premium": { - "label": "Premium", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, "nrOfAnalyses": "{{nr}} analyses", "clinicalBloodDraw": { "label": "Kliiniline vereanalüüs", diff --git a/public/locales/et/product.json b/public/locales/et/product.json index 01876f9..8fd8bc2 100644 --- a/public/locales/et/product.json +++ b/public/locales/et/product.json @@ -1,16 +1,4 @@ { - "standard": { - "label": "Standard", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "standardPlus": { - "label": "Standard +", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "premium": { - "label": "Premium", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, "nrOfAnalyses": "{{nr}} analüüsi", "clinicalBloodDraw": { "label": "Kliiniline vereanalüüs", diff --git a/public/locales/ru/product.json b/public/locales/ru/product.json index e2aaf81..3a5c425 100644 --- a/public/locales/ru/product.json +++ b/public/locales/ru/product.json @@ -1,16 +1,4 @@ { - "standard": { - "label": "Standard", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "standardPlus": { - "label": "Standard +", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, - "premium": { - "label": "Premium", - "description": "Sobib, kui soovid lisaks peamistele tervisenäitajatele ülevaadet, kas organismis on olulisemaid vitamiine ja mineraalaineid piisavalt." - }, "nrOfAnalyses": "{{nr}} analyses", "clinicalBloodDraw": { "label": "Kliiniline vereanalüüs", From 6426e2a79b055a1e0905c9e956d15ade40fddf2d Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:16:52 +0300 Subject: [PATCH 05/12] feat(MED-100): update cart checkout flow and views --- app/api/montonio/verify-token/route.ts | 97 ++++++++++ app/home/(user)/(dashboard)/cart/loading.tsx | 5 + .../montonio-callback/[montonioId]/page.tsx | 23 +++ app/home/(user)/(dashboard)/cart/page.tsx | 47 +++++ app/home/(user)/(dashboard)/layout.tsx | 4 +- .../order-analysis-package/page.tsx | 6 +- .../order/[orderId]/confirmed/page.tsx | 28 +++ .../(user)/_components/cart/cart-item.tsx | 57 ++++++ .../(user)/_components/cart/cart-items.tsx | 54 ++++++ .../(user)/_components/cart/cart-timer.tsx | 91 ++++++++++ .../(user)/_components/cart/discount-code.tsx | 166 ++++++++++++++++++ app/home/(user)/_components/cart/index.tsx | 111 ++++++++++++ .../cart/montonio-checkout-callback.tsx | 87 +++++++++ .../_components/compare-packages-modal.tsx | 37 ++-- .../_components/home-menu-navigation.tsx | 41 +++-- .../(user)/_components/order/cart-totals.tsx | 82 +++++++++ .../_components/order/order-completed.tsx | 24 +++ .../_components/order/order-details.tsx | 47 +++++ .../(user)/_components/order/order-item.tsx | 52 ++++++ .../(user)/_components/order/order-items.tsx | 41 +++++ .../_lib/server/load-analysis-packages.ts | 36 ++++ app/select-package/page.tsx | 6 +- components/select-analysis-package.tsx | 99 +++++++++++ components/select-analysis-packages.tsx | 101 +---------- lib/i18n/i18n.settings.ts | 1 + lib/services/medusaCart.service.ts | 127 ++++++++++++++ .../medusa-storefront/src/lib/data/cart.ts | 13 +- .../src/lib/data/products.ts | 2 +- .../medusa-storefront/src/lib/data/regions.ts | 2 +- .../common/components/delete-button/index.tsx | 16 +- public/locales/en/cart.json | 56 ++++++ public/locales/et/cart.json | 57 ++++++ .../migrations/20250717075136_audit_cart.sql | 27 +++ 33 files changed, 1505 insertions(+), 138 deletions(-) create mode 100644 app/api/montonio/verify-token/route.ts create mode 100644 app/home/(user)/(dashboard)/cart/loading.tsx create mode 100644 app/home/(user)/(dashboard)/cart/montonio-callback/[montonioId]/page.tsx create mode 100644 app/home/(user)/(dashboard)/cart/page.tsx create mode 100644 app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx create mode 100644 app/home/(user)/_components/cart/cart-item.tsx create mode 100644 app/home/(user)/_components/cart/cart-items.tsx create mode 100644 app/home/(user)/_components/cart/cart-timer.tsx create mode 100644 app/home/(user)/_components/cart/discount-code.tsx create mode 100644 app/home/(user)/_components/cart/index.tsx create mode 100644 app/home/(user)/_components/cart/montonio-checkout-callback.tsx create mode 100644 app/home/(user)/_components/order/cart-totals.tsx create mode 100644 app/home/(user)/_components/order/order-completed.tsx create mode 100644 app/home/(user)/_components/order/order-details.tsx create mode 100644 app/home/(user)/_components/order/order-item.tsx create mode 100644 app/home/(user)/_components/order/order-items.tsx create mode 100644 app/home/(user)/_lib/server/load-analysis-packages.ts create mode 100644 components/select-analysis-package.tsx create mode 100644 lib/services/medusaCart.service.ts create mode 100644 public/locales/en/cart.json create mode 100644 public/locales/et/cart.json create mode 100644 supabase/migrations/20250717075136_audit_cart.sql diff --git a/app/api/montonio/verify-token/route.ts b/app/api/montonio/verify-token/route.ts new file mode 100644 index 0000000..6898ac6 --- /dev/null +++ b/app/api/montonio/verify-token/route.ts @@ -0,0 +1,97 @@ +import { NextResponse } from 'next/server'; +import jwt from 'jsonwebtoken'; +import { z } from 'zod'; + +import { enhanceRouteHandler } from '@kit/next/routes'; +import { getLogger } from '@kit/shared/logger'; + +interface MontonioOrderToken { + uuid: string; + accessKey: string; + merchantReference: string; + merchantReferenceDisplay: string; + paymentStatus: + | 'PAID' + | 'FAILED' + | 'CANCELLED' + | 'PENDING' + | 'EXPIRED' + | 'REFUNDED'; + paymentMethod: string; + grandTotal: number; + currency: string; + senderIban?: string; + senderName?: string; + paymentProviderName?: string; + paymentLinkUuid: string; + iat: number; + exp: number; +} + +const BodySchema = z.object({ + token: z.string(), +}); + +export const POST = enhanceRouteHandler( + async ({ request }) => { + const logger = await getLogger(); + const body = await request.json(); + const namespace = 'montonio.verify-token'; + + try { + const { token } = BodySchema.parse(body); + + const secretKey = process.env.MONTONIO_SECRET_KEY as string; + + if (!secretKey) { + logger.error( + { + name: namespace, + }, + `Missing MONTONIO_SECRET_KEY`, + ); + + throw new Error('Server misconfiguration.'); + } + + const decoded = jwt.verify(token, secretKey, { + algorithms: ['HS256'], + }) as MontonioOrderToken; + + logger.info( + { + name: namespace, + status: decoded.paymentStatus, + orderId: decoded.uuid, + }, + `Successfully verified Montonio token.`, + ); + + return NextResponse.json({ + status: decoded.paymentStatus, + }); + } catch (error) { + logger.error( + { + name: namespace, + error, + }, + `Failed to verify Montonio token`, + ); + + const message = error instanceof Error ? error.message : 'Invalid token'; + + return NextResponse.json( + { + error: message, + }, + { + status: 400, + }, + ); + } + }, + { + auth: false, + }, +); \ No newline at end of file diff --git a/app/home/(user)/(dashboard)/cart/loading.tsx b/app/home/(user)/(dashboard)/cart/loading.tsx new file mode 100644 index 0000000..f093295 --- /dev/null +++ b/app/home/(user)/(dashboard)/cart/loading.tsx @@ -0,0 +1,5 @@ +import SkeletonCartPage from '~/medusa/modules/skeletons/templates/skeleton-cart-page'; + +export default function Loading() { + return ; +} diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/[montonioId]/page.tsx b/app/home/(user)/(dashboard)/cart/montonio-callback/[montonioId]/page.tsx new file mode 100644 index 0000000..1c42dcb --- /dev/null +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/[montonioId]/page.tsx @@ -0,0 +1,23 @@ +import { PageBody, PageHeader } from '@/packages/ui/src/makerkit/page'; +import { MontonioCheckoutCallback } from '../../../../_components/cart/montonio-checkout-callback'; +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { Trans } from '@kit/ui/trans'; + +export async function generateMetadata() { + const { t } = await createI18nServerInstance(); + + return { + title: t('cart:montonioCallback.title'), + }; +} + +export default async function MontonioCheckoutCallbackPage() { + return ( +
+ } /> + + + +
+ ); +} diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx new file mode 100644 index 0000000..cdb0918 --- /dev/null +++ b/app/home/(user)/(dashboard)/cart/page.tsx @@ -0,0 +1,47 @@ +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { PageBody, PageHeader } from '@/packages/ui/src/makerkit/page'; + +import { notFound } from 'next/navigation'; + +import { retrieveCart } from '~/medusa/lib/data/cart'; +import Cart from '../../_components/cart'; +import { listCollections } from '@lib/data'; +import CartTimer from '../../_components/cart/cart-timer'; +import { Trans } from '@kit/ui/trans'; + +export async function generateMetadata() { + const { t } = await createI18nServerInstance(); + + return { + title: t('cart:title'), + }; +} + +export default async function CartPage() { + const cart = await retrieveCart().catch((error) => { + console.error(error); + return notFound(); + }); + + const { collections } = await listCollections({ + limit: "100", + }); + + const analysisPackagesCollection = collections.find(({ handle }) => handle === 'analysis-packages'); + const analysisPackages = analysisPackagesCollection && cart?.items + ? cart.items.filter((item) => item.product?.collection_id === analysisPackagesCollection.id) + : []; + const otherItems = cart?.items?.filter((item) => item.product?.collection_id !== analysisPackagesCollection?.id) ?? []; + + const otherItemsSorted = otherItems.sort((a, b) => (a.updated_at ?? "") > (b.updated_at ?? "") ? -1 : 1); + const item = otherItemsSorted[0]; + + return ( + + }> + {item && item.updated_at && } + + + + ); +} diff --git a/app/home/(user)/(dashboard)/layout.tsx b/app/home/(user)/(dashboard)/layout.tsx index 281c34c..5e08ec6 100644 --- a/app/home/(user)/(dashboard)/layout.tsx +++ b/app/home/(user)/(dashboard)/layout.tsx @@ -17,6 +17,7 @@ import { HomeMenuNavigation } from '../_components/home-menu-navigation'; import { HomeMobileNavigation } from '../_components/home-mobile-navigation'; import { HomeSidebar } from '../_components/home-sidebar'; import { loadUserWorkspace } from '../_lib/server/load-user-workspace'; +import { retrieveCart } from '@lib/data'; function UserHomeLayout({ children }: React.PropsWithChildren) { const state = use(getLayoutState()); @@ -55,12 +56,13 @@ function SidebarLayout({ children }: React.PropsWithChildren) { function HeaderLayout({ children }: React.PropsWithChildren) { const workspace = use(loadUserWorkspace()); + const cart = use(retrieveCart()); return ( - + diff --git a/app/home/(user)/(dashboard)/order-analysis-package/page.tsx b/app/home/(user)/(dashboard)/order-analysis-package/page.tsx index 7ac0ed4..81ef7e8 100644 --- a/app/home/(user)/(dashboard)/order-analysis-package/page.tsx +++ b/app/home/(user)/(dashboard)/order-analysis-package/page.tsx @@ -8,6 +8,7 @@ import { PageBody } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; import ComparePackagesModal from '../../_components/compare-packages-modal'; +import { loadAnalysisPackages } from '../../_lib/server/load-analysis-packages'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); @@ -19,6 +20,8 @@ export const generateMetadata = async () => { }; async function OrderAnalysisPackagePage() { + const { analysisPackages, countryCode } = await loadAnalysisPackages(); + return (
@@ -26,6 +29,7 @@ async function OrderAnalysisPackagePage() { @@ -34,7 +38,7 @@ async function OrderAnalysisPackagePage() { } />
- +
); } diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx new file mode 100644 index 0000000..95d9e81 --- /dev/null +++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx @@ -0,0 +1,28 @@ +import { notFound } from 'next/navigation'; + +import { retrieveOrder } from '~/medusa/lib/data/orders'; +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import OrderCompleted from '@/app/home/(user)/_components/order/order-completed'; + +type Props = { + params: Promise<{ orderId: string }>; +}; + +export async function generateMetadata() { + const { t } = await createI18nServerInstance(); + + return { + title: t('cart:orderConfirmed.title'), + }; +} + +export default async function OrderConfirmedPage(props: Props) { + const params = await props.params; + const order = await retrieveOrder(params.orderId).catch(() => null); + + if (!order) { + return notFound(); + } + + return ; +} diff --git a/app/home/(user)/_components/cart/cart-item.tsx b/app/home/(user)/_components/cart/cart-item.tsx new file mode 100644 index 0000000..a25e535 --- /dev/null +++ b/app/home/(user)/_components/cart/cart-item.tsx @@ -0,0 +1,57 @@ +"use client" + +import { HttpTypes } from "@medusajs/types" +import DeleteButton from "@modules/common/components/delete-button" +import { useTranslation } from "react-i18next" +import { + TableCell, + TableRow, +} from '@kit/ui/table'; +import { formatCurrency } from "@/packages/shared/src/utils" +import { Trash } from "lucide-react" + +export default function CartItem({ item, currencyCode }: { + item: HttpTypes.StoreCartLineItem + currencyCode: string +}) { + const { i18n: { language } } = useTranslation(); + + return ( + + +

+ {item.product_title} +

+
+ + + {item.quantity} + + + + {formatCurrency({ + value: item.unit_price, + currencyCode, + locale: language, + })} + + + + {formatCurrency({ + value: item.total, + currencyCode, + locale: language, + })} + + + + + } /> + + +
+ ) +} diff --git a/app/home/(user)/_components/cart/cart-items.tsx b/app/home/(user)/_components/cart/cart-items.tsx new file mode 100644 index 0000000..7ca24a9 --- /dev/null +++ b/app/home/(user)/_components/cart/cart-items.tsx @@ -0,0 +1,54 @@ +import { StoreCart, StoreCartLineItem } from "@medusajs/types" +import { Trans } from '@kit/ui/trans'; +import CartItem from "./cart-item"; +import { + Table, + TableBody, + TableHead, + TableRow, + TableHeader, +} from '@kit/ui/table'; + +export default function CartItems({ cart, items, productColumnLabelKey }: { + cart: StoreCart; + items: StoreCartLineItem[]; + productColumnLabelKey: string; +}) { + if (!items || items.length === 0) { + return null; + } + + return ( + + + + + + + + + + + + + + + + + + + + + {items + .sort((a, b) => (a.created_at ?? "") > (b.created_at ?? "") ? -1 : 1) + .map((item) => ( + + ))} + +
+ ) +} diff --git a/app/home/(user)/_components/cart/cart-timer.tsx b/app/home/(user)/_components/cart/cart-timer.tsx new file mode 100644 index 0000000..22ad5e9 --- /dev/null +++ b/app/home/(user)/_components/cart/cart-timer.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { Button } from '@kit/ui/button'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle +} from "@kit/ui/alert-dialog"; + +import { Timer } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { StoreCartLineItem } from '@medusajs/types'; +import { handleLineItemTimeout } from '@/lib/services/medusaCart.service'; + +const TIMEOUT_MINUTES = 15; + +export default function CartTimer({ cartItem }: { cartItem: StoreCartLineItem }) { + const { t } = useTranslation(); + const [timeLeft, setTimeLeft] = useState(null); + const [isDialogOpen, setDialogOpen] = useState(false); + + const updatedAt = cartItem.updated_at!; + + useEffect(() => { + const date = new Date(updatedAt); + date.setMinutes(date.getMinutes() + TIMEOUT_MINUTES); + + const interval = setInterval(() => { + const now = new Date(); + const diff = date.getTime() - now.getTime(); + setTimeLeft(diff); + }, 1000); + + return () => clearInterval(interval); + }, [updatedAt]); + + const minutes = timeLeft ? Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)) : 0; + const seconds = timeLeft ? Math.floor((timeLeft % (1000 * 60)) / 1000) : 0; + + const isTimeLeftPositive = timeLeft === null || timeLeft > 0; + useEffect(() => { + if (!isTimeLeftPositive) { + void handleLineItemTimeout({ + lineItem: cartItem, + }); + setDialogOpen(true); + } + }, [isTimeLeftPositive, cartItem.id]); + + if (timeLeft === null) { + return
; + } + + return ( + <> +
+ +
+ + + + + + {t('cart:checkout.timeoutTitle')} + + + {t('cart:checkout.timeoutDescription', { productTitle: cartItem.product?.title })} + + + + setDialogOpen(false)}> + {t('cart:checkout.timeoutAction')} + + + + + + ) +} diff --git a/app/home/(user)/_components/cart/discount-code.tsx b/app/home/(user)/_components/cart/discount-code.tsx new file mode 100644 index 0000000..0a5cbaf --- /dev/null +++ b/app/home/(user)/_components/cart/discount-code.tsx @@ -0,0 +1,166 @@ +"use client" + +import { Badge, Heading, Text } from "@medusajs/ui" +import React, { useActionState } from "react"; + +import { applyPromotions, submitPromotionForm } from "@lib/data/cart" +import { convertToLocale } from "@lib/util/money" +import { StoreCart, StorePromotion } from "@medusajs/types" +import Trash from "@modules/common/icons/trash" +import { Button } from '@kit/ui/button'; +import { Form, FormControl, FormField, FormItem } from "@kit/ui/form"; +import { Trans } from '@kit/ui/trans'; +import { Input } from "@kit/ui/input"; +import { useTranslation } from "react-i18next"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +const DiscountCodeSchema = z.object({ + code: z.string().min(1), +}) + +export default function DiscountCode({ cart }: { + cart: StoreCart & { + promotions: StorePromotion[] + } +}) { + const { t } = useTranslation('cart'); + + const { promotions = [] } = cart; + + const removePromotionCode = async (code: string) => { + const validPromotions = promotions.filter( + (promotion) => promotion.code !== code + ) + + await applyPromotions( + validPromotions.filter((p) => p.code === undefined).map((p) => p.code!) + ) + } + + const addPromotionCode = async (code: string) => { + const codes = promotions + .filter((p) => p.code === undefined) + .map((p) => p.code!) + codes.push(code.toString()) + + await applyPromotions(codes) + + form.reset() + } + + const [message, formAction] = useActionState(submitPromotionForm, null) + + const form = useForm>({ + defaultValues: { + code: '', + }, + resolver: zodResolver(DiscountCodeSchema), + }); + + return ( +
+
+ addPromotionCode(data.code))} + className="w-full mb-2 flex gap-x-2" + > + ( + + + + + + )} + /> + + + + + +

+ +

+ + {promotions.length > 0 && ( +
+
+ + Promotion(s) applied: + + + {promotions.map((promotion) => { + return ( +
+ + + + {promotion.code} + {" "} + ( + {promotion.application_method?.value !== undefined && + promotion.application_method.currency_code !== + undefined && ( + <> + {promotion.application_method.type === + "percentage" + ? `${promotion.application_method.value}%` + : convertToLocale({ + amount: promotion.application_method.value, + currency_code: + promotion.application_method + .currency_code, + })} + + )} + ) + {/* {promotion.is_automatic && ( + + + + )} */} + + + {!promotion.is_automatic && ( + + )} +
+ ) + })} +
+
+ )} +
+ ) +} diff --git a/app/home/(user)/_components/cart/index.tsx b/app/home/(user)/_components/cart/index.tsx new file mode 100644 index 0000000..90f7c7d --- /dev/null +++ b/app/home/(user)/_components/cart/index.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { StoreCart, StoreCartLineItem } from "@medusajs/types" +import CartItems from "./cart-items" +import { Trans } from '@kit/ui/trans'; +import { Button } from '@kit/ui/button'; +import { + Card, + CardContent, + CardHeader, +} from '@kit/ui/card'; +import DiscountCode from "./discount-code"; +import { useRouter } from "next/navigation"; +import { initiatePaymentSession } from "@lib/data/cart"; +import { formatCurrency } from "@/packages/shared/src/utils"; +import { useTranslation } from "react-i18next"; +import { handleNavigateToPayment } from "@/lib/services/medusaCart.service"; + +const IS_DISCOUNT_SHOWN = false as boolean; + +export default function Cart({ + cart, + analysisPackages, + otherItems, +}: { + cart: StoreCart | null + analysisPackages: StoreCartLineItem[]; + otherItems: StoreCartLineItem[]; +}) { + const router = useRouter(); + const { i18n: { language } } = useTranslation(); + + const items = cart?.items ?? []; + + if (!cart || items.length === 0) { + return ( +
+
+
+

+ +

+

+ +

+
+
+
+ ); + } + + async function handlePayment() { + const response = await initiatePaymentSession(cart!, { + provider_id: 'pp_system_default', + }); + if (response.payment_collection) { + const url = await handleNavigateToPayment({ language }); + router.push(url); + } + } + + return ( +
+
+ + +
+ {Array.isArray(cart.items) && cart.items.length > 0 && ( +
+
+

+ +

+
+
+

+ {formatCurrency({ + value: cart.total, + currencyCode: cart.currency_code, + locale: language, + })} +

+
+
+ )} + +
+ {IS_DISCOUNT_SHOWN && ( + + +
+ +
+
+ + + +
+ )} +
+ +
+ +
+
+ ); +} diff --git a/app/home/(user)/_components/cart/montonio-checkout-callback.tsx b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx new file mode 100644 index 0000000..7c29c59 --- /dev/null +++ b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { useSearchParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; + +import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; +import { Button } from '@kit/ui/button'; +import { Trans } from '@kit/ui/trans'; +import { placeOrder } from "@lib/data/cart" +import Link from 'next/link'; + +enum Status { + LOADING = 'LOADING', + ERROR = 'ERROR', +} + +export function MontonioCheckoutCallback() { + const [status, setStatus] = useState(Status.LOADING); + const searchParams = useSearchParams(); + + useEffect(() => { + const token = searchParams.get('order-token'); + if (!token) { + return; + } + + async function verifyToken() { + setStatus(Status.LOADING); + + try { + const response = await fetch('/api/montonio/verify-token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token }), + }); + + if (!response.ok) { + const body = await response.json(); + throw new Error(body.error ?? 'Failed to verify payment status.'); + } + + const body = await response.json(); + const paymentStatus = body.status as string; + if (paymentStatus === 'PAID') { + await placeOrder(); + } else { + setStatus(Status.ERROR); + } + } catch (e) { + console.error("Error verifying token", e); + setStatus(Status.ERROR); + } + } + + void verifyToken(); + }, [searchParams]); + + if (status === Status.ERROR) { + return ( +
+ + + + + + +

+ +

+
+
+ +
+ +
+
+ ); + } + + return null; +} diff --git a/app/home/(user)/_components/compare-packages-modal.tsx b/app/home/(user)/_components/compare-packages-modal.tsx index 5f581e7..ca2e2db 100644 --- a/app/home/(user)/_components/compare-packages-modal.tsx +++ b/app/home/(user)/_components/compare-packages-modal.tsx @@ -22,6 +22,7 @@ import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; import { PackageHeader } from '@/components/package-header'; import { InfoTooltip } from '@/components/ui/info-tooltip'; +import { StoreProduct } from '@medusajs/types'; const dummyCards = [ { @@ -105,8 +106,10 @@ const CheckWithBackground = () => { }; const ComparePackagesModal = async ({ + analysisPackages, triggerElement, }: { + analysisPackages: StoreProduct[]; triggerElement: JSX.Element; }) => { const { t, language } = await createI18nServerInstance(); @@ -140,21 +143,25 @@ const ComparePackagesModal = async ({ - {dummyCards.map( - ({ titleKey, price, nrOfAnalyses, tagColor }) => ( - - - - ), - )} + {analysisPackages.map( + (product) => { + const variant = product.variants?.[0]; + const titleKey = product.title; + const price = variant?.calculated_price?.calculated_amount ?? 0; + return ( + + + + ) + })} diff --git a/app/home/(user)/_components/home-menu-navigation.tsx b/app/home/(user)/_components/home-menu-navigation.tsx index cc77954..761be60 100644 --- a/app/home/(user)/_components/home-menu-navigation.tsx +++ b/app/home/(user)/_components/home-menu-navigation.tsx @@ -1,19 +1,30 @@ +import Link from 'next/link'; +import { ShoppingCart } from 'lucide-react'; import { Trans } from '@kit/ui/trans'; - import { AppLogo } from '~/components/app-logo'; import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container'; import { Search } from '~/components/ui/search'; +import { SIDEBAR_WIDTH_PROPERTY } from '@/packages/ui/src/shadcn/constants'; +import { Button } from '@kit/ui/button'; -import { SIDEBAR_WIDTH_PROPERTY } from '../../../../packages/ui/src/shadcn/constants'; -// home imports import { UserNotifications } from '../_components/user-notifications'; import { type UserWorkspace } from '../_lib/server/load-user-workspace'; -import { Button } from '@kit/ui/button'; -import { ShoppingCart } from 'lucide-react'; +import { StoreCart } from '@medusajs/types'; +import { formatCurrency } from '@/packages/shared/src/utils'; +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; -export function HomeMenuNavigation(props: { workspace: UserWorkspace }) { +export async function HomeMenuNavigation(props: { workspace: UserWorkspace, cart: StoreCart | null }) { + const { language } = await createI18nServerInstance(); const { workspace, user, accounts } = props.workspace; + const totalValue = props.cart?.total ? formatCurrency({ + currencyCode: props.cart.currency_code, + locale: language, + value: props.cart.total, + }) : 0; + + const cartItemsCount = props.cart?.items?.length ?? 0; + const hasCartItems = cartItemsCount > 0; return (
@@ -27,13 +38,17 @@ export function HomeMenuNavigation(props: { workspace: UserWorkspace }) { />
- - + {hasCartItems && ( + + )} + + +
diff --git a/app/home/(user)/_components/order/cart-totals.tsx b/app/home/(user)/_components/order/cart-totals.tsx new file mode 100644 index 0000000..69dadde --- /dev/null +++ b/app/home/(user)/_components/order/cart-totals.tsx @@ -0,0 +1,82 @@ +"use client" + +import { formatCurrency } from "@/packages/shared/src/utils" +import { StoreOrder } from "@medusajs/types" +import React from "react" +import { useTranslation } from "react-i18next" +import { Trans } from '@kit/ui/trans'; + +export default function CartTotals({ order }: { + order: StoreOrder +}) { + const { i18n: { language } } = useTranslation() + const { + currency_code, + total, + subtotal, + tax_total, + discount_total, + gift_card_total, + } = order + + return ( +
+
+
+ + + + + {formatCurrency({ value: subtotal ?? 0, currencyCode: currency_code, locale: language })} + +
+ {!!discount_total && ( +
+ + + -{" "} + {formatCurrency({ value: discount_total ?? 0, currencyCode: currency_code, locale: language })} + +
+ )} +
+ + + + + {formatCurrency({ value: tax_total ?? 0, currencyCode: currency_code, locale: language })} + +
+ {!!gift_card_total && ( +
+ + + -{" "} + {formatCurrency({ value: gift_card_total ?? 0, currencyCode: currency_code, locale: language })} + +
+ )} +
+
+
+ + + {formatCurrency({ value: total ?? 0, currencyCode: currency_code, locale: language })} + +
+
+
+ ) +} diff --git a/app/home/(user)/_components/order/order-completed.tsx b/app/home/(user)/_components/order/order-completed.tsx new file mode 100644 index 0000000..cf2cfa6 --- /dev/null +++ b/app/home/(user)/_components/order/order-completed.tsx @@ -0,0 +1,24 @@ +import { Trans } from '@kit/ui/trans'; +import { PageBody, PageHeader } from '@kit/ui/page'; +import { StoreOrder } from "@medusajs/types" + +import CartTotals from "./cart-totals" +import OrderDetails from "./order-details" +import OrderItems from "./order-items" + +export default async function OrderCompleted({ + order, +}: { + order: StoreOrder, +}) { + return ( + + } /> +
+ + + +
+
+ ) +} diff --git a/app/home/(user)/_components/order/order-details.tsx b/app/home/(user)/_components/order/order-details.tsx new file mode 100644 index 0000000..e6bc6cf --- /dev/null +++ b/app/home/(user)/_components/order/order-details.tsx @@ -0,0 +1,47 @@ +import { StoreOrder } from "@medusajs/types" +import { Trans } from '@kit/ui/trans'; + +export default function OrderDetails({ order, showStatus }: { + order: StoreOrder + showStatus?: boolean +}) { + const formatStatus = (str: string) => { + const formatted = str.split("_").join(" ") + + return formatted.slice(0, 1).toUpperCase() + formatted.slice(1) + } + + return ( +
+ + :{" "} + + {new Date(order.created_at).toLocaleDateString()} + + + + : {order.display_id} + + + {showStatus && ( + <> + + :{" "} + + {formatStatus(order.fulfillment_status)} + + + + :{" "} + + {formatStatus(order.payment_status)} + + + + )} +
+ ) +} diff --git a/app/home/(user)/_components/order/order-item.tsx b/app/home/(user)/_components/order/order-item.tsx new file mode 100644 index 0000000..658f91b --- /dev/null +++ b/app/home/(user)/_components/order/order-item.tsx @@ -0,0 +1,52 @@ +import { StoreCartLineItem, StoreOrderLineItem } from "@medusajs/types" +import { TableCell, TableRow } from "@kit/ui/table" + +import LineItemOptions from "@modules/common/components/line-item-options" +import LineItemPrice from "@modules/common/components/line-item-price" +import LineItemUnitPrice from "@modules/common/components/line-item-unit-price" + +export default function OrderItem({ item, currencyCode }: { + item: StoreCartLineItem | StoreOrderLineItem + currencyCode: string +}) { + return ( + + {/* +
+ +
+
*/} + + + + {item.product_title} + + + + + + + + + {item.quantity}x{" "} + + + + + + + +
+ ) +} diff --git a/app/home/(user)/_components/order/order-items.tsx b/app/home/(user)/_components/order/order-items.tsx new file mode 100644 index 0000000..b08db72 --- /dev/null +++ b/app/home/(user)/_components/order/order-items.tsx @@ -0,0 +1,41 @@ +import repeat from "@lib/util/repeat" +import { StoreOrder } from "@medusajs/types" +import { Table, TableBody } from "@kit/ui/table" + +import Divider from "@modules/common/components/divider" +import SkeletonLineItem from "@modules/skeletons/components/skeleton-line-item" +import OrderItem from "./order-item" +import { Heading } from "@kit/ui/heading" +import { Trans } from '@kit/ui/trans'; + +export default function OrderItems({ order }: { + order: StoreOrder +}) { + const items = order.items + + return ( +
+ + + +
+ + + + {items?.length + ? items + .sort((a, b) => (a.created_at ?? "") > (b.created_at ?? "") ? -1 : 1) + .map((item) => ( + + )) + : repeat(5).map((i) => )} + +
+
+
+ ) +} diff --git a/app/home/(user)/_lib/server/load-analysis-packages.ts b/app/home/(user)/_lib/server/load-analysis-packages.ts new file mode 100644 index 0000000..3dc758d --- /dev/null +++ b/app/home/(user)/_lib/server/load-analysis-packages.ts @@ -0,0 +1,36 @@ +import { cache } from 'react'; + +import { listCollections, listProducts, listRegions } from "@lib/data"; + +async function countryCodesLoader() { + const countryCodes = await listRegions().then((regions) => + regions?.map((r) => r.countries?.map((c) => c.iso_2)).flat(), + ); + return countryCodes ?? []; +} +export const loadCountryCodes = cache(countryCodesLoader); + +async function collectionsLoader() { + const { collections } = await listCollections({ + fields: 'id, handle', + }); + return collections ?? []; +} +export const loadCollections = cache(collectionsLoader); + +async function analysisPackagesLoader() { + const [countryCodes, collections] = await Promise.all([loadCountryCodes(), loadCollections()]); + const countryCode = countryCodes[0]!; + + const collection = collections.find(({ handle }) => handle === 'analysis-packages'); + if (!collection) { + return { analysisPackages: [], countryCode }; + } + + const { response } = await listProducts({ + countryCode, + queryParams: { limit: 100, collection_id: collection?.id }, + }); + return { analysisPackages: response.products, countryCode }; +} +export const loadAnalysisPackages = cache(analysisPackagesLoader); diff --git a/app/select-package/page.tsx b/app/select-package/page.tsx index e4de4bb..a446b97 100644 --- a/app/select-package/page.tsx +++ b/app/select-package/page.tsx @@ -13,6 +13,7 @@ import SelectAnalysisPackages from '@/components/select-analysis-packages'; import { MedReportLogo } from '../../components/med-report-logo'; import pathsConfig from '../../config/paths.config'; import ComparePackagesModal from '../home/(user)/_components/compare-packages-modal'; +import { loadAnalysisPackages } from '../home/(user)/_lib/server/load-analysis-packages'; export const generateMetadata = async () => { const { t } = await createI18nServerInstance(); @@ -23,6 +24,8 @@ export const generateMetadata = async () => { }; async function SelectPackagePage() { + const { analysisPackages, countryCode } = await loadAnalysisPackages(); + return (
@@ -31,6 +34,7 @@ async function SelectPackagePage() { @@ -39,7 +43,7 @@ async function SelectPackagePage() { } />
- + + + + ); +} diff --git a/components/select-analysis-packages.tsx b/components/select-analysis-packages.tsx index b5149fe..2f1cfff 100644 --- a/components/select-analysis-packages.tsx +++ b/components/select-analysis-packages.tsx @@ -1,104 +1,15 @@ -'use client'; - -import Image from 'next/image'; -import { useTranslation } from 'react-i18next'; - -import { Button } from '@kit/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, -} from '@kit/ui/card'; import { Trans } from '@kit/ui/trans'; +import { StoreProduct } from '@medusajs/types'; -import { PackageHeader } from './package-header'; -import { ButtonTooltip } from './ui/button-tooltip'; - -export interface IAnalysisPackage { - titleKey: string; - price: number; - nrOfAnalyses: number | string; - tagColor: string; - descriptionKey: string; -} - -const analysisPackages = [ - { - titleKey: 'product:standard.label', - price: 40, - nrOfAnalyses: 4, - tagColor: 'bg-cyan', - descriptionKey: 'marketing:standard.description', - }, - { - titleKey: 'product:standardPlus.label', - price: 85, - nrOfAnalyses: 10, - - tagColor: 'bg-warning', - descriptionKey: 'product:standardPlus.description', - }, - { - titleKey: 'product:premium.label', - price: 140, - nrOfAnalyses: '12+', - - tagColor: 'bg-purple', - descriptionKey: 'product:premium.description', - }, -] satisfies IAnalysisPackage[]; - -export default function SelectAnalysisPackages() { - const { - t, - i18n: { language }, - } = useTranslation(); +import SelectAnalysisPackage from './select-analysis-package'; +export default function SelectAnalysisPackages({ analysisPackages, countryCode }: { analysisPackages: StoreProduct[], countryCode: string }) { return (
{analysisPackages.length > 0 ? analysisPackages.map( - ( - { titleKey, price, nrOfAnalyses, tagColor, descriptionKey }, - index, - ) => { - return ( - - - - background - - - - - - - - - - - - ); - }, - ) : ( + (product) => ( + + )) : (

diff --git a/lib/i18n/i18n.settings.ts b/lib/i18n/i18n.settings.ts index 22b4768..77e22fc 100644 --- a/lib/i18n/i18n.settings.ts +++ b/lib/i18n/i18n.settings.ts @@ -36,6 +36,7 @@ export const defaultI18nNamespaces = [ 'product', 'booking', 'order-analysis-package', + 'cart', ]; /** diff --git a/lib/services/medusaCart.service.ts b/lib/services/medusaCart.service.ts new file mode 100644 index 0000000..2813846 --- /dev/null +++ b/lib/services/medusaCart.service.ts @@ -0,0 +1,127 @@ +'use server'; + +import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; +import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart'; +import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types'; +import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src'; +import { headers } from 'next/headers'; +import { requireUserInServerComponent } from '../server/require-user-in-server-component'; + +export async function handleAddToCart({ + selectedVariant, + countryCode, +}: { + selectedVariant: StoreProductVariant + countryCode: string +}) { + const supabase = getSupabaseServerClient(); + const user = await requireUserInServerComponent(); + const account = await loadCurrentUserAccount() + if (!account) { + throw new Error('Account not found'); + } + + const quantity = 1; + const cart = await addToCart({ + variantId: selectedVariant.id, + quantity, + countryCode, + }); + + const { error } = await supabase + .schema('audit') + .from('cart_entries') + .insert({ + variant_id: selectedVariant.id, + operation: 'ADD_TO_CART', + account_id: account.id, + cart_id: cart.id, + changed_by: user.id, + }); + if (error) { + throw new Error('Error logging cart entry: ' + error.message); + } + + return cart; +} + +export async function handleNavigateToPayment({ language }: { language: string }) { + const supabase = getSupabaseServerClient(); + const user = await requireUserInServerComponent(); + const account = await loadCurrentUserAccount() + if (!account) { + throw new Error('Account not found'); + } + + const cart = await retrieveCart(); + if (!cart) { + throw new Error("No cart found"); + } + + const headersList = await headers(); + const host = "webhook.site:3000"; + const proto = "http"; + // const host = headersList.get('host'); + // const proto = headersList.get('x-forwarded-proto') ?? 'http'; + const publicUrl = `${proto}://${host}`; + + const paymentLink = await new MontonioOrderHandlerService().getMontonioPaymentLink({ + notificationUrl: `${publicUrl}/api/billing/webhook`, + returnUrl: `${publicUrl}/home/cart/montonio-callback`, + amount: cart.total, + currency: cart.currency_code.toUpperCase(), + description: `Order from Medreport`, + locale: language, + merchantReference: `${account.id}:${Date.now()}`, + }); + + const { error } = await supabase + .schema('audit') + .from('cart_entries') + .insert({ + operation: 'NAVIGATE_TO_PAYMENT', + account_id: account.id, + cart_id: cart.id, + changed_by: user.id, + }); + if (error) { + throw new Error('Error logging cart entry: ' + error.message); + } + + return paymentLink; +} + +export async function handleLineItemTimeout({ + lineItem, +}: { + lineItem: StoreCartLineItem +}) { + const supabase = getSupabaseServerClient(); + const user = await requireUserInServerComponent(); + const account = await loadCurrentUserAccount() + if (!account) { + throw new Error('Account not found'); + } + + if (lineItem.updated_at) { + const updatedAt = new Date(lineItem.updated_at); + const now = new Date(); + const diff = now.getTime() - updatedAt.getTime(); + } + + await deleteLineItem(lineItem.id); + + const { error } = await supabase + .schema('audit') + .from('cart_entries') + .insert({ + operation: 'LINE_ITEM_TIMEOUT', + account_id: account.id, + cart_id: lineItem.cart_id, + changed_by: user.id, + }); + if (error) { + throw new Error('Error logging cart entry: ' + error.message); + } +} diff --git a/packages/features/medusa-storefront/src/lib/data/cart.ts b/packages/features/medusa-storefront/src/lib/data/cart.ts index 26a9b2b..3ae27c8 100644 --- a/packages/features/medusa-storefront/src/lib/data/cart.ts +++ b/packages/features/medusa-storefront/src/lib/data/cart.ts @@ -154,6 +154,8 @@ export async function addToCart({ revalidateTag(fulfillmentCacheTag); }) .catch(medusaError); + + return cart; } export async function updateLineItem({ @@ -394,7 +396,7 @@ export async function placeOrder(cartId?: string) { const id = cartId || (await getCartId()); if (!id) { - throw new Error("No existing cart found when placing an order"); + return; } const headers = { @@ -411,17 +413,14 @@ export async function placeOrder(cartId?: string) { .catch(medusaError); if (cartRes?.type === "order") { - const countryCode = - cartRes.order.shipping_address?.country_code?.toLowerCase(); - const orderCacheTag = await getCacheTag("orders"); revalidateTag(orderCacheTag); removeCartId(); - redirect(`/${countryCode}/order/${cartRes?.order.id}/confirmed`); + redirect(`/home/order/${cartRes?.order.id}/confirmed`); + } else { + throw new Error("Cart is not an order"); } - - return cartRes.cart; } /** diff --git a/packages/features/medusa-storefront/src/lib/data/products.ts b/packages/features/medusa-storefront/src/lib/data/products.ts index 6a505b1..2ef33ab 100644 --- a/packages/features/medusa-storefront/src/lib/data/products.ts +++ b/packages/features/medusa-storefront/src/lib/data/products.ts @@ -14,7 +14,7 @@ export const listProducts = async ({ regionId, }: { pageParam?: number - queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams + queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & { collection_id?: string } countryCode?: string regionId?: string }): Promise<{ diff --git a/packages/features/medusa-storefront/src/lib/data/regions.ts b/packages/features/medusa-storefront/src/lib/data/regions.ts index 4489db5..d724328 100644 --- a/packages/features/medusa-storefront/src/lib/data/regions.ts +++ b/packages/features/medusa-storefront/src/lib/data/regions.ts @@ -57,7 +57,7 @@ export const getRegion = async (countryCode: string) => { const region = countryCode ? regionMap.get(countryCode) - : regionMap.get("us") + : regionMap.get("et") return region } catch (e: any) { diff --git a/packages/features/medusa-storefront/src/modules/common/components/delete-button/index.tsx b/packages/features/medusa-storefront/src/modules/common/components/delete-button/index.tsx index 161e0d6..1d08857 100644 --- a/packages/features/medusa-storefront/src/modules/common/components/delete-button/index.tsx +++ b/packages/features/medusa-storefront/src/modules/common/components/delete-button/index.tsx @@ -3,24 +3,34 @@ import { deleteLineItem } from "@lib/data/cart"; import { Spinner, Trash } from "@medusajs/icons"; import { clx } from "@medusajs/ui"; +import { useRouter } from "next/navigation"; import { useState } from "react"; const DeleteButton = ({ id, children, className, + Icon, }: { id: string; children?: React.ReactNode; className?: string; + Icon?: React.ReactNode; }) => { const [isDeleting, setIsDeleting] = useState(false); + const router = useRouter(); const handleDelete = async (id: string) => { setIsDeleting(true); - await deleteLineItem(id).catch((err) => { + + try { + await deleteLineItem(id); + + router.refresh(); + } catch (err) { + // TODO: display a toast notification with the error setIsDeleting(false); - }); + } }; return ( @@ -34,7 +44,7 @@ const DeleteButton = ({ className="flex gap-x-1 text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer" onClick={() => handleDelete(id)} > - {isDeleting ? : } + {isDeleting ? : (Icon ?? )} {children}
diff --git a/public/locales/en/cart.json b/public/locales/en/cart.json new file mode 100644 index 0000000..50d34d1 --- /dev/null +++ b/public/locales/en/cart.json @@ -0,0 +1,56 @@ +{ + "title": "Cart", + "description": "View your cart", + "emptyCartMessage": "Your cart is empty", + "emptyCartMessageDescription": "Add items to your cart to continue.", + "subtotal": "Subtotal", + "total": "Total", + "table": { + "item": "Item", + "quantity": "Quantity", + "price": "Price", + "total": "Total" + }, + "checkout": { + "goToCheckout": "Go to checkout", + "goToDashboard": "Continue", + "error": { + "title": "Something went wrong", + "description": "Please try again later." + }, + "timeLeft": "Time left {{timeLeft}}", + "timeoutTitle": "Reservation expired", + "timeoutDescription": "Reservation for {{productTitle}} in shopping cart has expired.", + "timeoutAction": "Continue" + }, + "discountCode": { + "label": "Add Promotion Code(s)", + "apply": "Apply", + "subtitle": "If you wish, you can add a promotion code", + "placeholder": "Enter promotion code" + }, + "items": { + "analysisPackages": { + "productColumnLabel": "Package name" + }, + "services": { + "productColumnLabel": "Service name" + } + }, + "orderConfirmed": { + "title": "Order confirmed", + "summary": "Summary", + "subtotal": "Subtotal", + "taxes": "Taxes", + "giftCard": "Gift card", + "total": "Total", + "orderDate": "Order date", + "orderNumber": "Order number", + "orderStatus": "Order status", + "paymentStatus": "Payment status" + }, + "montonioCallback": { + "title": "Montonio checkout", + "description": "Please wait while we process your payment." + } +} \ No newline at end of file diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json new file mode 100644 index 0000000..42f21a8 --- /dev/null +++ b/public/locales/et/cart.json @@ -0,0 +1,57 @@ +{ + "title": "Ostukorv", + "description": "Vaata oma ostukorvi", + "emptyCartMessage": "Sinu ostukorv on tühi", + "emptyCartMessageDescription": "Lisa tooteid ostukorvi, et jätkata.", + "subtotal": "Vahesumma", + "total": "Summa", + "table": { + "item": "Toode", + "quantity": "Kogus", + "price": "Hind", + "total": "Summa" + }, + "checkout": { + "goToCheckout": "Vormista ost", + "goToDashboard": "Jätkan", + "error": { + "title": "Midagi läks valesti", + "description": "Palun proovi hiljem uuesti." + }, + "timeLeft": "Aega jäänud {{timeLeft}}", + "timeoutTitle": "Broneering aegus", + "timeoutDescription": "Toote {{productTitle}} broneering ostukorvis on aegunud.", + "timeoutAction": "Jätkan" + }, + "discountCode": { + "title": "Kinkekaart või sooduskood", + "label": "Lisa promo kood", + "apply": "Rakenda", + "subtitle": "Kui soovid, võid lisada promo koodi", + "placeholder": "Sisesta promo kood" + }, + "items": { + "analysisPackages": { + "productColumnLabel": "Paketi nimi" + }, + "services": { + "productColumnLabel": "Teenuse nimi" + } + }, + "orderConfirmed": { + "title": "Tellimus on edukalt esitatud", + "summary": "Summa", + "subtotal": "Vahesumma", + "taxes": "Maksud", + "giftCard": "Kinkekaart", + "total": "Summa", + "orderDate": "Tellimuse kuupäev", + "orderNumber": "Tellimuse number", + "orderStatus": "Tellimuse olek", + "paymentStatus": "Makse olek" + }, + "montonioCallback": { + "title": "Montonio makseprotsess", + "description": "Palun oodake, kuni me töötleme sinu makseprotsessi lõpuni." + } +} \ No newline at end of file diff --git a/supabase/migrations/20250717075136_audit_cart.sql b/supabase/migrations/20250717075136_audit_cart.sql new file mode 100644 index 0000000..f467ad7 --- /dev/null +++ b/supabase/migrations/20250717075136_audit_cart.sql @@ -0,0 +1,27 @@ +create table "audit"."cart_entries" ( + "id" bigint generated by default as identity not null, + "account_id" text not null, + "cart_id" text not null, + "operation" text not null, + "variant_id" text, + "comment" text, + "created_at" timestamp with time zone not null default now(), + "changed_by" uuid not null +); + +grant usage on schema audit to authenticated; +grant select, insert, update, delete on table audit.cart_entries to authenticated; + +alter table "audit"."cart_entries" enable row level security; + +create policy "insert_own" +on "audit"."cart_entries" +as permissive +for insert +to authenticated +with check (auth.uid() = changed_by); + +create policy "service_role_select" on "audit"."cart_entries" for select to service_role using (true); +create policy "service_role_insert" on "audit"."cart_entries" for insert to service_role with check (true); +create policy "service_role_update" on "audit"."cart_entries" for update to service_role using (true); +create policy "service_role_delete" on "audit"."cart_entries" for delete to service_role using (true); From 736194bb0bfd3ff495cda2579454b0b93463565b Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:17:50 +0300 Subject: [PATCH 06/12] feat(MED-100): updateaccountform should be prefilled --- .../features/auth/src/components/update-account-form.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/features/auth/src/components/update-account-form.tsx b/packages/features/auth/src/components/update-account-form.tsx index 2cad0f0..403ef6d 100644 --- a/packages/features/auth/src/components/update-account-form.tsx +++ b/packages/features/auth/src/components/update-account-form.tsx @@ -31,15 +31,16 @@ export function UpdateAccountForm({ user }: { user: User }) { defaultValues: { firstName: '', lastName: '', - personalCode: '', + personalCode: user.user_metadata.personalCode ?? '', email: user.email, phone: '', city: '', - weight: 0, - height: 0, + weight: user.user_metadata.weight ?? undefined, + height: user.user_metadata.height ?? undefined, userConsent: false, }, }); + return (
Date: Thu, 17 Jul 2025 10:19:27 +0300 Subject: [PATCH 07/12] feat(MED-100): create medusa store account for user --- .../server/actions/update-account-actions.ts | 8 ++++ .../medusa-storefront/src/lib/data/cookies.ts | 27 +++++++++++ .../src/lib/data/customer.ts | 48 +++++++++++++++++++ packages/shared/src/utils.ts | 2 +- .../hooks/use-sign-in-with-email-password.ts | 14 +++++- .../hooks/use-sign-up-with-email-password.ts | 13 +++++ 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/packages/features/auth/src/server/actions/update-account-actions.ts b/packages/features/auth/src/server/actions/update-account-actions.ts index 0efdab8..abb847f 100644 --- a/packages/features/auth/src/server/actions/update-account-actions.ts +++ b/packages/features/auth/src/server/actions/update-account-actions.ts @@ -6,6 +6,7 @@ import { enhanceAction } from '@kit/next/actions'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import pathsConfig from '~/config/paths.config'; +import { updateCustomer } from '@lib/data/customer'; import { UpdateAccountSchema } from '../../schemas/update-account.schema'; import { createAuthApi } from '../api'; @@ -36,6 +37,13 @@ export const onUpdateAccount = enhanceAction( } console.warn('On update account error: ', err); } + + await updateCustomer({ + first_name: params.firstName, + last_name: params.lastName, + phone: params.phone, + }); + const hasUnseenMembershipConfirmation = await api.hasUnseenMembershipConfirmation(); diff --git a/packages/features/medusa-storefront/src/lib/data/cookies.ts b/packages/features/medusa-storefront/src/lib/data/cookies.ts index 1e21f78..7694904 100644 --- a/packages/features/medusa-storefront/src/lib/data/cookies.ts +++ b/packages/features/medusa-storefront/src/lib/data/cookies.ts @@ -18,6 +18,23 @@ export const getAuthHeaders = async (): Promise< } } +export const getMedusaCustomerId = async (): Promise< + { customerId: string | null } +> => { + try { + const cookies = await nextCookies() + const customerId = cookies.get("_medusa_customer_id")?.value + + if (!customerId) { + return { customerId: null } + } + + return { customerId } + } catch { + return { customerId: null} + } +} + export const getCacheTag = async (tag: string): Promise => { try { const cookies = await nextCookies() @@ -59,6 +76,16 @@ export const setAuthToken = async (token: string) => { }) } +export const setMedusaCustomerId = async (customerId: string) => { + const cookies = await nextCookies() + cookies.set("_medusa_customer_id", customerId, { + maxAge: 60 * 60 * 24 * 7, + httpOnly: true, + sameSite: "strict", + secure: process.env.NODE_ENV === "production", + }) +} + export const removeAuthToken = async () => { const cookies = await nextCookies() cookies.set("_medusa_jwt", "", { diff --git a/packages/features/medusa-storefront/src/lib/data/customer.ts b/packages/features/medusa-storefront/src/lib/data/customer.ts index 309d774..9b22479 100644 --- a/packages/features/medusa-storefront/src/lib/data/customer.ts +++ b/packages/features/medusa-storefront/src/lib/data/customer.ts @@ -259,3 +259,51 @@ export const updateCustomerAddress = async ( return { success: false, error: err.toString() } }) } + +export async function medusaLoginOrRegister(credentials: { + email: string + password?: string +}) { + const { email, password } = credentials; + + try { + const token = await sdk.auth.login("customer", "emailpass", { + email, + password, + }); + await setAuthToken(token as string); + await transferCart(); + + const customerCacheTag = await getCacheTag("customers"); + revalidateTag(customerCacheTag); + } catch (error) { + console.error("Failed to login customer, attempting to register", error); + try { + const registerToken = await sdk.auth.register("customer", "emailpass", { + email: email, + password: password, + }) + + await setAuthToken(registerToken as string); + + const headers = { + ...(await getAuthHeaders()), + }; + + await sdk.store.customer.create({ email }, {}, headers); + + const loginToken = await sdk.auth.login("customer", "emailpass", { + email, + password, + }); + + await setAuthToken(loginToken as string); + + const customerCacheTag = await getCacheTag("customers"); + revalidateTag(customerCacheTag); + await transferCart(); + } catch (registerError) { + throw medusaError(registerError); + } + } +} diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 4ec9ae7..1b8d1af 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -14,7 +14,7 @@ export function formatCurrency(params: { locale: string; value: string | number; }) { - const [lang, region] = params.locale.split('-'); + const [lang, region] = (params.locale ?? 'et-ET').split('-'); return new Intl.NumberFormat(region ?? lang, { style: 'currency', diff --git a/packages/supabase/src/hooks/use-sign-in-with-email-password.ts b/packages/supabase/src/hooks/use-sign-in-with-email-password.ts index d6ba8e7..2e11a95 100644 --- a/packages/supabase/src/hooks/use-sign-in-with-email-password.ts +++ b/packages/supabase/src/hooks/use-sign-in-with-email-password.ts @@ -2,6 +2,7 @@ import type { SignInWithPasswordCredentials } from '@supabase/supabase-js'; import { useMutation } from '@tanstack/react-query'; +import { medusaLoginOrRegister } from '../../../features/medusa-storefront/src/lib/data/customer'; import { useSupabase } from './use-supabase'; export function useSignInWithEmailPassword() { @@ -18,11 +19,22 @@ export function useSignInWithEmailPassword() { const user = response.data?.user; const identities = user?.identities ?? []; - // if the user has no identities, it means that the email is taken if (identities.length === 0) { throw new Error('User already registered'); } + if ('email' in credentials) { + try { + await medusaLoginOrRegister({ + email: credentials.email, + password: credentials.password, + }); + } catch (error) { + await client.auth.signOut(); + throw error; + } + } + return response.data; }; diff --git a/packages/supabase/src/hooks/use-sign-up-with-email-password.ts b/packages/supabase/src/hooks/use-sign-up-with-email-password.ts index a387d0e..01a247f 100644 --- a/packages/supabase/src/hooks/use-sign-up-with-email-password.ts +++ b/packages/supabase/src/hooks/use-sign-up-with-email-password.ts @@ -1,6 +1,7 @@ import { useMutation } from '@tanstack/react-query'; import { useSupabase } from './use-supabase'; +import { medusaLoginOrRegister } from '../../../features/medusa-storefront/src/lib/data/customer'; interface Credentials { personalCode: string; @@ -41,6 +42,18 @@ export function useSignUpWithEmailAndPassword() { throw new Error('User already registered'); } + if ('email' in credentials) { + try { + await medusaLoginOrRegister({ + email: credentials.email, + password: credentials.password, + }); + } catch (error) { + await client.auth.signOut(); + throw error; + } + } + return response.data; }; From 25b4e06b89d6c99ca49677e3c6b6a350a7a125bd Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:19:36 +0300 Subject: [PATCH 08/12] feat(MED-100): improve styles --- packages/ui/src/makerkit/page.tsx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/makerkit/page.tsx b/packages/ui/src/makerkit/page.tsx index f134f57..4035d45 100644 --- a/packages/ui/src/makerkit/page.tsx +++ b/packages/ui/src/makerkit/page.tsx @@ -171,22 +171,24 @@ export function PageHeader({ {title} -
- {displaySidebarTrigger ? ( - - ) : null} + +
+ {displaySidebarTrigger ? ( + + ) : null} - - - + + + + + + {description} - - {description} - -
+
+
{children} From 7ccc45ce7767775babc0bcf25984ce37f1df9939 Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 10:44:05 +0300 Subject: [PATCH 09/12] feat(MED-100): show toast on delete --- .../_components/cart/cart-item-delete.tsx | 48 +++++++++++++++++++ .../(user)/_components/cart/cart-item.tsx | 5 +- .../cart/montonio-checkout-callback.tsx | 16 +++++-- components/select-analysis-package.tsx | 5 +- .../medusa-storefront/src/lib/data/cart.ts | 4 +- public/locales/en/cart.json | 5 ++ public/locales/et/cart.json | 5 ++ 7 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 app/home/(user)/_components/cart/cart-item-delete.tsx diff --git a/app/home/(user)/_components/cart/cart-item-delete.tsx b/app/home/(user)/_components/cart/cart-item-delete.tsx new file mode 100644 index 0000000..4c359cf --- /dev/null +++ b/app/home/(user)/_components/cart/cart-item-delete.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { Trash } from "lucide-react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from 'sonner'; + +import { deleteLineItem } from "@lib/data/cart"; +import { Spinner } from "@medusajs/icons"; + +const CartItemDelete = ({ + id, + children, +}: { + id: string; + children?: React.ReactNode; +}) => { + const [isDeleting, setIsDeleting] = useState(false); + const { t } = useTranslation(); + + const handleDelete = async () => { + setIsDeleting(true); + + const promise = async () => { + await deleteLineItem(id); + }; + + toast.promise(promise, { + success: t(`cart:items.delete.success`), + loading: t(`cart:items.delete.loading`), + error: t(`cart:items.delete.error`), + }); + }; + + return ( +
+ +
+ ); +}; + +export default CartItemDelete; diff --git a/app/home/(user)/_components/cart/cart-item.tsx b/app/home/(user)/_components/cart/cart-item.tsx index a25e535..db6a4ec 100644 --- a/app/home/(user)/_components/cart/cart-item.tsx +++ b/app/home/(user)/_components/cart/cart-item.tsx @@ -1,14 +1,13 @@ "use client" import { HttpTypes } from "@medusajs/types" -import DeleteButton from "@modules/common/components/delete-button" import { useTranslation } from "react-i18next" import { TableCell, TableRow, } from '@kit/ui/table'; import { formatCurrency } from "@/packages/shared/src/utils" -import { Trash } from "lucide-react" +import CartItemDelete from "./cart-item-delete"; export default function CartItem({ item, currencyCode }: { item: HttpTypes.StoreCartLineItem @@ -49,7 +48,7 @@ export default function CartItem({ item, currencyCode }: { - } /> + diff --git a/app/home/(user)/_components/cart/montonio-checkout-callback.tsx b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx index 7c29c59..9f2c1eb 100644 --- a/app/home/(user)/_components/cart/montonio-checkout-callback.tsx +++ b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; @@ -8,6 +8,7 @@ import { Button } from '@kit/ui/button'; import { Trans } from '@kit/ui/trans'; import { placeOrder } from "@lib/data/cart" import Link from 'next/link'; +import Loading from '@/app/home/loading'; enum Status { LOADING = 'LOADING', @@ -15,12 +16,14 @@ enum Status { } export function MontonioCheckoutCallback() { + const router = useRouter(); const [status, setStatus] = useState(Status.LOADING); const searchParams = useSearchParams(); - + useEffect(() => { const token = searchParams.get('order-token'); if (!token) { + router.push('/home/cart'); return; } @@ -44,7 +47,12 @@ export function MontonioCheckoutCallback() { const body = await response.json(); const paymentStatus = body.status as string; if (paymentStatus === 'PAID') { - await placeOrder(); + try { + await placeOrder(); + } catch (e) { + console.error("Error placing order", e); + router.push('/home/cart'); + } } else { setStatus(Status.ERROR); } @@ -83,5 +91,5 @@ export function MontonioCheckoutCallback() { ); } - return null; + return (); } diff --git a/components/select-analysis-package.tsx b/components/select-analysis-package.tsx index 20afe6c..2b55ac2 100644 --- a/components/select-analysis-package.tsx +++ b/components/select-analysis-package.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import Image from 'next/image'; +import { useRouter } from 'next/navigation'; import { Card, @@ -34,8 +35,9 @@ export default function SelectAnalysisPackage({ analysisPackage: StoreProduct countryCode: string, }) { + const router = useRouter(); const { t, i18n: { language } } = useTranslation(); - + const [isAddingToCart, setIsAddingToCart] = useState(false); const handleSelect = async (selectedVariant: StoreProductVariant) => { if (!selectedVariant?.id) return null @@ -46,6 +48,7 @@ export default function SelectAnalysisPackage({ countryCode, }); setIsAddingToCart(false); + router.push('/home/cart'); } const titleKey = analysisPackage.title; diff --git a/packages/features/medusa-storefront/src/lib/data/cart.ts b/packages/features/medusa-storefront/src/lib/data/cart.ts index 3ae27c8..d739abf 100644 --- a/packages/features/medusa-storefront/src/lib/data/cart.ts +++ b/packages/features/medusa-storefront/src/lib/data/cart.ts @@ -44,7 +44,7 @@ export async function retrieveCart(cartId?: string) { }, headers, next, - cache: "force-cache", + //cache: "force-cache", }) .then(({ cart }) => cart) .catch(() => null); @@ -396,7 +396,7 @@ export async function placeOrder(cartId?: string) { const id = cartId || (await getCartId()); if (!id) { - return; + throw new Error("No existing cart found when placing an order"); } const headers = { diff --git a/public/locales/en/cart.json b/public/locales/en/cart.json index 50d34d1..8f83aa1 100644 --- a/public/locales/en/cart.json +++ b/public/locales/en/cart.json @@ -35,6 +35,11 @@ }, "services": { "productColumnLabel": "Service name" + }, + "delete": { + "success": "Item removed from cart", + "loading": "Removing item from cart", + "error": "Failed to remove item from cart" } }, "orderConfirmed": { diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json index 42f21a8..8db2419 100644 --- a/public/locales/et/cart.json +++ b/public/locales/et/cart.json @@ -36,6 +36,11 @@ }, "services": { "productColumnLabel": "Teenuse nimi" + }, + "delete": { + "success": "Toode eemaldatud ostukorvist", + "loading": "Toote eemaldamine ostukorvist", + "error": "Toote eemaldamine ostukorvist ebaõnnestus" } }, "orderConfirmed": { From 5487242bbe51884626625be70ef234041a7dc5f3 Mon Sep 17 00:00:00 2001 From: k4rli Date: Thu, 17 Jul 2025 12:55:59 +0300 Subject: [PATCH 10/12] feat(MED-100): db types --- packages/supabase/src/database.types.ts | 24 +++- pnpm-lock.yaml | 171 +++--------------------- 2 files changed, 41 insertions(+), 154 deletions(-) diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 3f1b629..17ac293 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -116,7 +116,29 @@ export type Database = { status?: string } Relationships: [] - } + }, + cart_entries: { + Row: { + id: number + account_id: string + cart_id: string + operation: string + variant_id: string + comment: string | null + created_at: string + changed_by: string | null + } + Insert: { + id: number + account_id: string + cart_id: string + operation: string + variant_id: string + comment: string | null + created_at: string + changed_by: string | null + } + }, } Views: { [_ in never]: never diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c58f49e..5367ea9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@edge-csrf/nextjs': specifier: 2.5.3-cloudflare-rc1 - version: 2.5.3-cloudflare-rc1(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 2.5.3-cloudflare-rc1(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@hookform/resolvers': specifier: ^5.1.1 version: 5.1.1(react-hook-form@7.58.0(react@19.1.0)) @@ -73,7 +73,7 @@ importers: version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4) '@makerkit/data-loader-supabase-nextjs': specifier: ^1.2.5 - version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) '@marsidev/react-turnstile': specifier: ^1.1.0 version: 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -88,7 +88,7 @@ importers: version: 4.0.17(@types/react-dom@19.1.5(@types/react@19.1.4))(@types/react@19.1.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3) '@nosecone/next': specifier: 1.0.0-beta.7 - version: 1.0.0-beta.7(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.0.0-beta.7(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.1.0) @@ -129,11 +129,11 @@ importers: specifier: ^0.510.0 version: 0.510.0(react@19.1.0) next: - specifier: 15.3.5 - version: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.3.2 + version: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-sitemap: specifier: ^4.2.3 - version: 4.2.3(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 4.2.3(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) next-themes: specifier: 0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2224,9 +2224,6 @@ packages: '@next/env@15.3.2': resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} - '@next/env@15.3.5': - resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} - '@next/env@15.4.0-canary.128': resolution: {integrity: sha512-o1L2iI/6zHvYQo4hwwf7a3eu5lEOLBChl79Apreub/EwUFHAoRHfmaMYlVfk4uWo3XUNvMRxOG+dRAWmxn4mJQ==} @@ -2242,12 +2239,6 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@15.3.5': - resolution: {integrity: sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - '@next/swc-darwin-arm64@15.4.0-canary.128': resolution: {integrity: sha512-3W+dQHTO4baj4iMfWCcXDrrpYsQmuLE1rqLjx3UjMswrJx5DSOPre2haAH5W6CtLvTVEB8x/xCCzJ8ZlqfF5CA==} engines: {node: '>= 10'} @@ -2260,12 +2251,6 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@15.3.5': - resolution: {integrity: sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - '@next/swc-darwin-x64@15.4.0-canary.128': resolution: {integrity: sha512-BlXLOSoc9Xubx/ZRB1k76Akd7ildo98Ypn+IglZiapheAfA//euQZUHZXD66ZVyWHYqdCEw7vg4EzpnXA8wlpg==} engines: {node: '>= 10'} @@ -2278,12 +2263,6 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@15.3.5': - resolution: {integrity: sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - '@next/swc-linux-arm64-gnu@15.4.0-canary.128': resolution: {integrity: sha512-ZUnD74X5yTa/De0s1x/SG6Rv+MGEx6nuz+Th3qmKOUCmwzlxE4UBF7+Vwti4Wg8aEEzxebJUR8WRrmWwvmui8g==} engines: {node: '>= 10'} @@ -2296,12 +2275,6 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.5': - resolution: {integrity: sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - '@next/swc-linux-arm64-musl@15.4.0-canary.128': resolution: {integrity: sha512-pMJ79xdufMeLuOyHY6F0LE/GgHRZZiLVch7YntB7dNZ8HZbV+br4UzssAgr3JfAGyuRnNY0dWgreKwGyGDNSfw==} engines: {node: '>= 10'} @@ -2314,12 +2287,6 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.5': - resolution: {integrity: sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - '@next/swc-linux-x64-gnu@15.4.0-canary.128': resolution: {integrity: sha512-33xXJqrbQO/qNO1n9tLbz6o38j7bs4VnOMmWVHPZH9IAHA99gHFThUZZ4TXgVa8Yj6lgIOLb7MuymaN55FZeZA==} engines: {node: '>= 10'} @@ -2332,12 +2299,6 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.5': - resolution: {integrity: sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - '@next/swc-linux-x64-musl@15.4.0-canary.128': resolution: {integrity: sha512-WJv6LPLKJXqvHTaX6WgliI9enlgm4tpwDE6pL619zgGSI5HXHrDsr4LzlDCD7rmuG+n937umVFESmUrnwTTrGw==} engines: {node: '>= 10'} @@ -2350,12 +2311,6 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@15.3.5': - resolution: {integrity: sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - '@next/swc-win32-arm64-msvc@15.4.0-canary.128': resolution: {integrity: sha512-97Wrx1M3MJSGbiNNbscb/W6avnRCp/9NX9vNTp58caYxk7U7S2e3HJoWS8yeHW9193ci5kHAIBfNDr/GvFws7A==} engines: {node: '>= 10'} @@ -2368,12 +2323,6 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.5': - resolution: {integrity: sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - '@next/swc-win32-x64-msvc@15.4.0-canary.128': resolution: {integrity: sha512-NbpGc9eZjQkjwDst5WsAlv+GFKayojuy7sz+01FJ6og8LXRJvzMCTh5uFTdaHIZr47RpZvewIlJW7mjWyvAHjg==} engines: {node: '>= 10'} @@ -7873,27 +7822,6 @@ packages: sass: optional: true - next@15.3.5: - resolution: {integrity: sha512-RkazLBMMDJSJ4XZQ81kolSpwiCt907l0xcgcpF4xC2Vml6QVcPNXW0NQRwQ80FFtSn7UM52XN0anaw8TEJXaiw==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - next@15.4.0-canary.128: resolution: {integrity: sha512-Oo1GjM7ToTkus3mEMnKI93NpFt3KgtTnVDyINHrvX/rjdtEHiabNQhgowOqv84h8uLfatV+vsy7gMkYR+UsV/A==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -9802,9 +9730,9 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + '@edge-csrf/nextjs@2.5.3-cloudflare-rc1(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': dependencies: - next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@emnapi/core@1.4.3': dependencies: @@ -10411,16 +10339,6 @@ snapshots: transitivePeerDependencies: - '@supabase/postgrest-js' - '@makerkit/data-loader-supabase-nextjs@1.2.5(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)(@tanstack/react-query@5.76.1(react@19.1.0))(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': - dependencies: - '@makerkit/data-loader-supabase-core': 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4) - '@supabase/supabase-js': 2.49.4 - '@tanstack/react-query': 5.76.1(react@19.1.0) - next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - transitivePeerDependencies: - - '@supabase/postgrest-js' - '@markdoc/markdoc@0.4.0(@types/react@19.1.4)(react@19.1.0)': optionalDependencies: '@types/markdown-it': 12.2.3 @@ -10540,8 +10458,6 @@ snapshots: '@next/env@15.3.2': {} - '@next/env@15.3.5': {} - '@next/env@15.4.0-canary.128': {} '@next/eslint-plugin-next@15.0.3': @@ -10555,72 +10471,48 @@ snapshots: '@next/swc-darwin-arm64@15.3.2': optional: true - '@next/swc-darwin-arm64@15.3.5': - optional: true - '@next/swc-darwin-arm64@15.4.0-canary.128': optional: true '@next/swc-darwin-x64@15.3.2': optional: true - '@next/swc-darwin-x64@15.3.5': - optional: true - '@next/swc-darwin-x64@15.4.0-canary.128': optional: true '@next/swc-linux-arm64-gnu@15.3.2': optional: true - '@next/swc-linux-arm64-gnu@15.3.5': - optional: true - '@next/swc-linux-arm64-gnu@15.4.0-canary.128': optional: true '@next/swc-linux-arm64-musl@15.3.2': optional: true - '@next/swc-linux-arm64-musl@15.3.5': - optional: true - '@next/swc-linux-arm64-musl@15.4.0-canary.128': optional: true '@next/swc-linux-x64-gnu@15.3.2': optional: true - '@next/swc-linux-x64-gnu@15.3.5': - optional: true - '@next/swc-linux-x64-gnu@15.4.0-canary.128': optional: true '@next/swc-linux-x64-musl@15.3.2': optional: true - '@next/swc-linux-x64-musl@15.3.5': - optional: true - '@next/swc-linux-x64-musl@15.4.0-canary.128': optional: true '@next/swc-win32-arm64-msvc@15.3.2': optional: true - '@next/swc-win32-arm64-msvc@15.3.5': - optional: true - '@next/swc-win32-arm64-msvc@15.4.0-canary.128': optional: true '@next/swc-win32-x64-msvc@15.3.2': optional: true - '@next/swc-win32-x64-msvc@15.3.5': - optional: true - '@next/swc-win32-x64-msvc@15.4.0-canary.128': optional: true @@ -10638,9 +10530,9 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@nosecone/next@1.0.0-beta.7(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + '@nosecone/next@1.0.0-beta.7(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': dependencies: - next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nosecone: 1.0.0-beta.7 '@opentelemetry/api-logs@0.50.0': @@ -17759,7 +17651,7 @@ snapshots: eslint: 8.10.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.10.0) eslint-plugin-react: 7.37.5(eslint@8.10.0) eslint-plugin-react-hooks: 5.2.0(eslint@8.10.0) @@ -17779,7 +17671,7 @@ snapshots: eslint: 9.28.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-react: 7.37.5(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.28.0(jiti@2.4.2)) @@ -17815,7 +17707,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.7.11 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -17830,7 +17722,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.7.11 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0) transitivePeerDependencies: - supports-color @@ -17856,7 +17748,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.10.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@8.10.0)(typescript@5.8.3))(eslint@8.10.0))(eslint@8.10.0))(eslint@8.10.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -17885,7 +17777,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -19356,13 +19248,13 @@ snapshots: neo-async@2.6.2: {} - next-sitemap@4.2.3(next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): + next-sitemap@4.2.3(next@15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: '@corex/deepmerge': 4.0.43 '@next/env': 13.5.11 fast-glob: 3.3.3 minimist: 1.2.8 - next: 15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.2(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: @@ -19423,33 +19315,6 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.3.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@next/env': 15.3.5 - '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.15 - busboy: 1.6.0 - caniuse-lite: 1.0.30001723 - postcss: 8.4.31 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(react@19.1.0) - optionalDependencies: - '@next/swc-darwin-arm64': 15.3.5 - '@next/swc-darwin-x64': 15.3.5 - '@next/swc-linux-arm64-gnu': 15.3.5 - '@next/swc-linux-arm64-musl': 15.3.5 - '@next/swc-linux-x64-gnu': 15.3.5 - '@next/swc-linux-x64-musl': 15.3.5 - '@next/swc-win32-arm64-msvc': 15.3.5 - '@next/swc-win32-x64-msvc': 15.3.5 - '@opentelemetry/api': 1.9.0 - babel-plugin-react-compiler: 19.1.0-rc.2 - sharp: 0.34.2 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - next@15.4.0-canary.128(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.4.0-canary.128 From 5d53e53ba8dc9ea2101e03d2c5c95a2d71fe36b1 Mon Sep 17 00:00:00 2001 From: k4rli Date: Mon, 21 Jul 2025 11:54:07 +0300 Subject: [PATCH 11/12] feat(MED-100): small improvements --- app/api/montonio/verify-token/route.ts | 6 ++++++ app/home/(user)/_components/cart/cart-items.tsx | 2 +- .../cart/montonio-checkout-callback.tsx | 14 ++++++++++---- app/home/(user)/_components/order/cart-totals.tsx | 2 +- .../(user)/_components/order/order-completed.tsx | 3 +++ app/home/(user)/_components/order/order-item.tsx | 8 ++++---- app/home/(user)/_components/order/order-items.tsx | 4 +--- components/select-analysis-package.tsx | 2 +- lib/services/medusaCart.service.ts | 2 +- .../services/montonio-webhook-handler.service.ts | 2 +- public/locales/et/cart.json | 2 +- 11 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/api/montonio/verify-token/route.ts b/app/api/montonio/verify-token/route.ts index 6898ac6..36f305f 100644 --- a/app/api/montonio/verify-token/route.ts +++ b/app/api/montonio/verify-token/route.ts @@ -58,6 +58,12 @@ export const POST = enhanceRouteHandler( algorithms: ['HS256'], }) as MontonioOrderToken; + const activeCartId = request.cookies.get('_medusa_cart_id')?.value; + const [, cartId] = decoded.merchantReferenceDisplay.split(':'); + if (cartId !== activeCartId) { + throw new Error('Invalid cart id'); + } + logger.info( { name: namespace, diff --git a/app/home/(user)/_components/cart/cart-items.tsx b/app/home/(user)/_components/cart/cart-items.tsx index 7ca24a9..5e7243b 100644 --- a/app/home/(user)/_components/cart/cart-items.tsx +++ b/app/home/(user)/_components/cart/cart-items.tsx @@ -21,7 +21,7 @@ export default function CartItems({ cart, items, productColumnLabelKey }: { return ( - + diff --git a/app/home/(user)/_components/cart/montonio-checkout-callback.tsx b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx index 9f2c1eb..d6214c7 100644 --- a/app/home/(user)/_components/cart/montonio-checkout-callback.tsx +++ b/app/home/(user)/_components/cart/montonio-checkout-callback.tsx @@ -8,7 +8,7 @@ import { Button } from '@kit/ui/button'; import { Trans } from '@kit/ui/trans'; import { placeOrder } from "@lib/data/cart" import Link from 'next/link'; -import Loading from '@/app/home/loading'; +import GlobalLoader from '../../loading'; enum Status { LOADING = 'LOADING', @@ -18,9 +18,14 @@ enum Status { export function MontonioCheckoutCallback() { const router = useRouter(); const [status, setStatus] = useState(Status.LOADING); + const [isFinalized, setIsFinalized] = useState(false); const searchParams = useSearchParams(); useEffect(() => { + if (isFinalized) { + return; + } + const token = searchParams.get('order-token'); if (!token) { router.push('/home/cart'); @@ -38,6 +43,7 @@ export function MontonioCheckoutCallback() { }, body: JSON.stringify({ token }), }); + setIsFinalized(true); if (!response.ok) { const body = await response.json(); @@ -54,7 +60,7 @@ export function MontonioCheckoutCallback() { router.push('/home/cart'); } } else { - setStatus(Status.ERROR); + throw new Error('Payment failed or pending'); } } catch (e) { console.error("Error verifying token", e); @@ -63,7 +69,7 @@ export function MontonioCheckoutCallback() { } void verifyToken(); - }, [searchParams]); + }, [searchParams, isFinalized]); if (status === Status.ERROR) { return ( @@ -91,5 +97,5 @@ export function MontonioCheckoutCallback() { ); } - return (); + return ; } diff --git a/app/home/(user)/_components/order/cart-totals.tsx b/app/home/(user)/_components/order/cart-totals.tsx index 69dadde..dc25aad 100644 --- a/app/home/(user)/_components/order/cart-totals.tsx +++ b/app/home/(user)/_components/order/cart-totals.tsx @@ -67,7 +67,7 @@ export default function CartTotals({ order }: {
- + } /> +
+
diff --git a/app/home/(user)/_components/order/order-item.tsx b/app/home/(user)/_components/order/order-item.tsx index 658f91b..f938360 100644 --- a/app/home/(user)/_components/order/order-item.tsx +++ b/app/home/(user)/_components/order/order-item.tsx @@ -11,13 +11,13 @@ export default function OrderItem({ item, currencyCode }: { }) { return ( - {/* + {/*
*/} - + - - + + {item.quantity}x{" "} diff --git a/app/home/(user)/_components/order/order-items.tsx b/app/home/(user)/_components/order/order-items.tsx index b08db72..25dbe31 100644 --- a/app/home/(user)/_components/order/order-items.tsx +++ b/app/home/(user)/_components/order/order-items.tsx @@ -2,7 +2,6 @@ import repeat from "@lib/util/repeat" import { StoreOrder } from "@medusajs/types" import { Table, TableBody } from "@kit/ui/table" -import Divider from "@modules/common/components/divider" import SkeletonLineItem from "@modules/skeletons/components/skeleton-line-item" import OrderItem from "./order-item" import { Heading } from "@kit/ui/heading" @@ -19,8 +18,7 @@ export default function OrderItems({ order }: {
- -
+
{items?.length ? items diff --git a/components/select-analysis-package.tsx b/components/select-analysis-package.tsx index 2b55ac2..950da9b 100644 --- a/components/select-analysis-package.tsx +++ b/components/select-analysis-package.tsx @@ -93,7 +93,7 @@ export default function SelectAnalysisPackage({ - diff --git a/lib/services/medusaCart.service.ts b/lib/services/medusaCart.service.ts index 2813846..c3a59a6 100644 --- a/lib/services/medusaCart.service.ts +++ b/lib/services/medusaCart.service.ts @@ -73,7 +73,7 @@ export async function handleNavigateToPayment({ language }: { language: string } currency: cart.currency_code.toUpperCase(), description: `Order from Medreport`, locale: language, - merchantReference: `${account.id}:${Date.now()}`, + merchantReference: `${account.id}:${cart.id}:${Date.now()}`, }); const { error } = await supabase diff --git a/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts b/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts index 20fa308..792d103 100644 --- a/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts +++ b/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts @@ -84,7 +84,7 @@ export class MontonioWebhookHandlerService }, `Received Montonio webhook event`); if (event.paymentStatus === 'PAID') { - const accountId = event.merchantReferenceDisplay.split(':')[0]; + const [accountId] = event.merchantReferenceDisplay.split(':'); if (!accountId) { throw new Error('Invalid merchant reference'); } diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json index 8db2419..f5bcc75 100644 --- a/public/locales/et/cart.json +++ b/public/locales/et/cart.json @@ -45,7 +45,7 @@ }, "orderConfirmed": { "title": "Tellimus on edukalt esitatud", - "summary": "Summa", + "summary": "Teenused", "subtotal": "Vahesumma", "taxes": "Maksud", "giftCard": "Kinkekaart", From 185a8a8293b8eb0cc6ecbfb62d7f79a1b4826a09 Mon Sep 17 00:00:00 2001 From: k4rli Date: Mon, 21 Jul 2025 11:54:27 +0300 Subject: [PATCH 12/12] feat(MED-100): update env --- .env | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.env b/.env index f279691..38867a6 100644 --- a/.env +++ b/.env @@ -51,4 +51,10 @@ LOGGER=pino NEXT_PUBLIC_DEFAULT_LOCALE=et NEXT_PUBLIC_TEAM_NAVIGATION_STYLE=custom -NEXT_PUBLIC_USER_NAVIGATION_STYLE=custom \ No newline at end of file +NEXT_PUBLIC_USER_NAVIGATION_STYLE=custom + +NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY= + +NEXT_PUBLIC_MONTONIO_ACCESS_KEY=7da5d7fa-3383-4997-9435-46aa818f4ead +MONTONIO_SECRET_KEY=rNZkzwxOiH93mzkdV53AvhSsbGidrgO2Kl5lE/IT7cvo +MONTONIO_API_URL=https://sandbox-stargate.montonio.com