From 07237dece6fae3f993552c50230ff882849157bc Mon Sep 17 00:00:00 2001 From: Karli Date: Wed, 24 Sep 2025 14:57:52 +0300 Subject: [PATCH 01/36] feat(MED-97): clean up --- .../20250924145253_fix_upsert_and_rls.sql | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/supabase/migrations/20250924145253_fix_upsert_and_rls.sql b/supabase/migrations/20250924145253_fix_upsert_and_rls.sql index d6d7589..b03c9c0 100644 --- a/supabase/migrations/20250924145253_fix_upsert_and_rls.sql +++ b/supabase/migrations/20250924145253_fix_upsert_and_rls.sql @@ -52,49 +52,3 @@ $$; -- 2. Grant permissions to authenticated users grant select, insert, update, delete on table "medreport"."benefit_distribution_schedule" to authenticated; - --- 3. Grant execute permissions to all functions -grant execute on function medreport.get_account_balance(uuid) to authenticated; -grant execute on function medreport.distribute_health_benefits(uuid, numeric, text) to authenticated; -grant execute on function medreport.consume_account_balance(uuid, numeric, text, text) to authenticated; -grant execute on function medreport.upsert_benefit_distribution_schedule(uuid, numeric, text) to authenticated; -grant execute on function medreport.calculate_next_distribution_date(text, timestamp with time zone) to authenticated; -grant execute on function medreport.trigger_benefit_distribution(uuid) to authenticated; -grant execute on function medreport.trigger_distribute_benefits() to authenticated; -grant execute on function medreport.process_periodic_benefit_distributions() to authenticated; - --- 4. Ensure trigger function has security definer -create or replace function medreport.trigger_distribute_benefits() -returns trigger -language plpgsql -security definer -as $$ -begin - -- Only distribute if benefit_amount is set and greater than 0 - if new.benefit_amount is not null and new.benefit_amount > 0 then - -- Distribute benefits to all company members immediately - perform medreport.distribute_health_benefits( - new.account_id, - new.benefit_amount, - coalesce(new.benefit_occurance, 'yearly') - ); - - -- Create or update the distribution schedule for future distributions - perform medreport.upsert_benefit_distribution_schedule( - new.account_id, - new.benefit_amount, - coalesce(new.benefit_occurance, 'yearly') - ); - else - -- If benefit_amount is 0 or null, deactivate the schedule - update medreport.benefit_distribution_schedule - set is_active = false, updated_at = now() - where company_id = new.account_id; - end if; - - return new; -end; -$$; - --- 5. Grant execute permission to the updated trigger function -grant execute on function medreport.trigger_distribute_benefits() to authenticated, service_role; From 2e2498577f498a4c0a639cdb3b5be723a23e8958 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 15:40:37 +0300 Subject: [PATCH 02/36] MED-103: create job --- app/api/job/handler/sync-connected-online.ts | 4 +++- ...500925153100_sync_connected_online_cron_job.sql | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 supabase/migrations/202500925153100_sync_connected_online_cron_job.sql diff --git a/app/api/job/handler/sync-connected-online.ts b/app/api/job/handler/sync-connected-online.ts index 07b7435..41528ba 100644 --- a/app/api/job/handler/sync-connected-online.ts +++ b/app/api/job/handler/sync-connected-online.ts @@ -38,7 +38,9 @@ function getSpokenLanguages(spokenLanguages?: string) { } export default async function syncConnectedOnline() { - const isProd = process.env.NODE_ENV === 'production'; + const isProd = !['test', 'localhost'].some((pathString) => + process.env.NEXT_PUBLIC_SITE_URL?.includes(pathString), + ); const baseUrl = process.env.CONNECTED_ONLINE_URL; diff --git a/supabase/migrations/202500925153100_sync_connected_online_cron_job.sql b/supabase/migrations/202500925153100_sync_connected_online_cron_job.sql new file mode 100644 index 0000000..f3fde10 --- /dev/null +++ b/supabase/migrations/202500925153100_sync_connected_online_cron_job.sql @@ -0,0 +1,14 @@ +select + cron.schedule( + 'sync-connected-online-every-night', -- Unique job name + '0 1 * * *', -- Cron schedule: every night at 04:00 (GMT +3) + $$ + select + net.http_post( + url := 'https://test.medreport.ee/api/job/sync-connected-online', + headers := jsonb_build_object( + 'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84' + ) + ) as request_id; + $$ + ); From 5d88121e78106e5973e6eb7bf751b97f2c59b465 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 15:51:43 +0300 Subject: [PATCH 03/36] refactor demo clinic condition --- app/api/job/handler/sync-connected-online.ts | 40 +++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/app/api/job/handler/sync-connected-online.ts b/app/api/job/handler/sync-connected-online.ts index 41528ba..80e89c9 100644 --- a/app/api/job/handler/sync-connected-online.ts +++ b/app/api/job/handler/sync-connected-online.ts @@ -86,34 +86,20 @@ export default async function syncConnectedOnline() { let serviceProviders; let jobTitleTranslations; // Filter out "Dentas Demo OÜ" in prod or only sync "Dentas Demo OÜ" in any other environment - const isDemoClinic = (clinicId: number) => clinicId === 2; - if (isProd) { - clinics = responseData.Data.T_Lic.filter(({ ID }) => !isDemoClinic(ID)); - services = responseData.Data.T_Service.filter( - ({ ClinicID }) => !isDemoClinic(ClinicID), - ); - serviceProviders = responseData.Data.T_Doctor.filter( - ({ ClinicID }) => !isDemoClinic(ClinicID), - ); - jobTitleTranslations = createTranslationMap( - responseData.Data.P_JobTitleTranslations.filter( - ({ ClinicID }) => !isDemoClinic(ClinicID), - ), - ); - } else { - clinics = responseData.Data.T_Lic.filter(({ ID }) => isDemoClinic(ID)); - services = responseData.Data.T_Service.filter(({ ClinicID }) => + const isDemoClinic = (clinicId: number) => + isProd ? clinicId !== 2 : clinicId === 2; + clinics = responseData.Data.T_Lic.filter(({ ID }) => isDemoClinic(ID)); + services = responseData.Data.T_Service.filter(({ ClinicID }) => + isDemoClinic(ClinicID), + ); + serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) => + isDemoClinic(ClinicID), + ); + jobTitleTranslations = createTranslationMap( + responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) => isDemoClinic(ClinicID), - ); - serviceProviders = responseData.Data.T_Doctor.filter(({ ClinicID }) => - isDemoClinic(ClinicID), - ); - jobTitleTranslations = createTranslationMap( - responseData.Data.P_JobTitleTranslations.filter(({ ClinicID }) => - isDemoClinic(ClinicID), - ), - ); - } + ), + ); const mappedClinics = clinics.map((clinic) => { return { From c69a1af0940c0d3a4f5915e31b4fd1bd9e009bbb Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 16:06:34 +0300 Subject: [PATCH 04/36] added CONNECTED_ONLINE_URL env From 85c6621b7a38598c35e3942a2ec736d505f203d4 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 16:11:08 +0300 Subject: [PATCH 05/36] use env --- lib/services/medusaCart.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/medusaCart.service.ts b/lib/services/medusaCart.service.ts index e91ef65..87efe47 100644 --- a/lib/services/medusaCart.service.ts +++ b/lib/services/medusaCart.service.ts @@ -31,8 +31,8 @@ const env = () => .min(1), }) .parse({ - medusaBackendPublicUrl: 'http://webhook.site:3000', - siteUrl: 'http://webhook.site:3000', + medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!, + siteUrl: process.env.NEXT_PUBLIC_SITE_URL!, }); export async function handleAddToCart({ From 2188b73f5433880cb1cc18f2041e4ba5285d771d Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 17:11:00 +0300 Subject: [PATCH 06/36] recreated montonio env From 0f2bfb74b456f87e02a5fd4ce270f8df33695139 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 17:24:44 +0300 Subject: [PATCH 07/36] disable revalidatePath for home/cart --- lib/services/reservation.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/reservation.service.ts b/lib/services/reservation.service.ts index 330a8b2..223cacf 100644 --- a/lib/services/reservation.service.ts +++ b/lib/services/reservation.service.ts @@ -312,7 +312,7 @@ export async function updateReservationTime( cartId: cartId, comment: `${reservationData}`, }); - revalidatePath('/home/cart', 'layout'); + // revalidatePath('/home/cart', 'layout'); } catch (e) { logger.error(`Failed to update reservation ${reservationData}`); await createCartEntriesLog({ From 1acde486f27e707d7b0cbd0623ac21924f2c28a1 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 17:42:53 +0300 Subject: [PATCH 08/36] add dynamic export --- app/home/(user)/(dashboard)/cart/page.tsx | 2 ++ lib/services/reservation.service.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx index 4a2af1f..cef7d8b 100644 --- a/app/home/(user)/(dashboard)/cart/page.tsx +++ b/app/home/(user)/(dashboard)/cart/page.tsx @@ -15,6 +15,8 @@ import Cart from '../../_components/cart'; import CartTimer from '../../_components/cart/cart-timer'; import { EnrichedCartItem } from '../../_components/cart/types'; +export const dynamic = 'force-dynamic'; + export async function generateMetadata() { const { t } = await createI18nServerInstance(); diff --git a/lib/services/reservation.service.ts b/lib/services/reservation.service.ts index 223cacf..330a8b2 100644 --- a/lib/services/reservation.service.ts +++ b/lib/services/reservation.service.ts @@ -312,7 +312,7 @@ export async function updateReservationTime( cartId: cartId, comment: `${reservationData}`, }); - // revalidatePath('/home/cart', 'layout'); + revalidatePath('/home/cart', 'layout'); } catch (e) { logger.error(`Failed to update reservation ${reservationData}`); await createCartEntriesLog({ From 0a6137127116cb555d5dbffeb2b0f20fdade451b Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 25 Sep 2025 18:07:10 +0300 Subject: [PATCH 09/36] refactor env --- app/home/(user)/(dashboard)/cart/page.tsx | 2 -- .../src/services/montonio-webhook-handler.service.ts | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx index cef7d8b..4a2af1f 100644 --- a/app/home/(user)/(dashboard)/cart/page.tsx +++ b/app/home/(user)/(dashboard)/cart/page.tsx @@ -15,8 +15,6 @@ import Cart from '../../_components/cart'; import CartTimer from '../../_components/cart/cart-timer'; import { EnrichedCartItem } from '../../_components/cart/types'; -export const dynamic = 'force-dynamic'; - export async function generateMetadata() { const { t } = await createI18nServerInstance(); 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 97a2fd0..2df2be2 100644 --- a/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts +++ b/packages/billing/montonio/src/services/montonio-webhook-handler.service.ts @@ -37,10 +37,11 @@ interface MontonioOrderToken { exp: number; } -const { secretKey } = MontonioServerEnvSchema.parse({ - apiUrl: process.env.MONTONIO_API_URL, - secretKey: process.env.MONTONIO_SECRET_KEY, -}); +const env = () => + MontonioServerEnvSchema.parse({ + apiUrl: process.env.MONTONIO_API_URL, + secretKey: process.env.MONTONIO_SECRET_KEY, + }); export class MontonioWebhookHandlerService implements BillingWebhookHandlerService @@ -50,6 +51,7 @@ export class MontonioWebhookHandlerService async verifyWebhookSignature(request: Request) { const logger = await getLogger(); + const { secretKey } = env(); let token: string; try { From fc63b9e7b798085387c41cb3ea2dba83f7a88ace Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 09:43:49 +0300 Subject: [PATCH 10/36] feat(MED-97): move order status updating to user-analyses feature pkg --- .../medipostPrivateMessage.service.ts | 26 ++++++++++------- packages/features/doctor/package.json | 1 + .../services/doctor-analysis.service.ts | 27 +++++++++--------- .../features/user-analyses/src/server/api.ts | 28 ++++++++++++++++++- .../src/types/analysis-orders.ts | 1 + pnpm-lock.yaml | 3 ++ 6 files changed, 61 insertions(+), 25 deletions(-) diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index 055acd6..efe0e74 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -28,7 +28,7 @@ import { upsertAnalysisResponseElement, } from '../analysis-order.service'; import { logMedipostDispatch } from '../audit.service'; -import { getAnalysisOrder, updateAnalysisOrderStatus } from '../order.service'; +import { getAnalysisOrder } from '../order.service'; import { parseXML } from '../util/xml.service'; import { MedipostValidationError } from './MedipostValidationError'; import { @@ -430,17 +430,19 @@ export async function readPrivateMessageResponse({ medipostExternalOrderId, }); if (status.isPartial) { - await updateAnalysisOrderStatus({ - medusaOrderId, - orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', - }); + await createUserAnalysesApi(getSupabaseServerAdminClient()) + .updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', + }); hasAnalysisResponse = true; hasPartialAnalysisResponse = true; } else if (status.isCompleted) { - await updateAnalysisOrderStatus({ - medusaOrderId, - orderStatus: 'FULL_ANALYSIS_RESPONSE', - }); + await createUserAnalysesApi(getSupabaseServerAdminClient()) + .updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'FULL_ANALYSIS_RESPONSE', + }); if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) { await deletePrivateMessage(privateMessageId); } @@ -622,5 +624,9 @@ export async function sendOrderToMedipost({ hasAnalysisResults: false, medusaOrderId, }); - await updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' }); + await createUserAnalysesApi(getSupabaseServerAdminClient()) + .updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'PROCESSING', + }); } diff --git a/packages/features/doctor/package.json b/packages/features/doctor/package.json index df1c635..fdf8e74 100644 --- a/packages/features/doctor/package.json +++ b/packages/features/doctor/package.json @@ -13,6 +13,7 @@ "@kit/supabase": "workspace:*", "@kit/tsconfig": "workspace:*", "@kit/ui": "workspace:*", + "@kit/user-analyses": "workspace:*", "@makerkit/data-loader-supabase-core": "^0.0.10", "@makerkit/data-loader-supabase-nextjs": "^1.2.5", "@supabase/supabase-js": "2.49.4", diff --git a/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts index c835c76..7033567 100644 --- a/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts +++ b/packages/features/doctor/src/lib/server/services/doctor-analysis.service.ts @@ -5,6 +5,7 @@ import { isBefore } from 'date-fns'; import { renderDoctorSummaryReceivedEmail } from '@kit/email-templates'; import { getFullName } from '@kit/shared/utils'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; +import { createUserAnalysesApi } from '@kit/user-analyses/api'; import { sendEmailFromTemplate } from '../../../../../../../lib/services/mailer.service'; import { AnalysisResultDetails } from '../schema/doctor-analysis-detail-view.schema'; @@ -641,7 +642,14 @@ export async function submitFeedback( } if (status === 'COMPLETED') { - const [{ data: recipient }, { data: analysisOrder }] = await Promise.all([ + const { data: analysisOrder } = await supabase + .schema('medreport') + .from('analysis_orders') + .select('medusa_order_id, id') + .eq('id', analysisOrderId) + .limit(1) + .throwOnError(); + const [{ data: recipient }] = await Promise.all([ supabase .schema('medreport') .from('accounts') @@ -649,19 +657,10 @@ export async function submitFeedback( .eq('is_personal_account', true) .eq('primary_owner_user_id', userId) .throwOnError(), - supabase - .schema('medreport') - .from('analysis_orders') - .select('medusa_order_id, id') - .eq('id', analysisOrderId) - .limit(1) - .throwOnError(), - supabase - .schema('medreport') - .from('analysis_orders') - .update({ status: 'COMPLETED' }) - .eq('id', analysisOrderId) - .throwOnError(), + createUserAnalysesApi(supabase).updateAnalysisOrderStatus({ + orderId: analysisOrderId, + orderStatus: 'COMPLETED', + }), ]); if (!recipient?.[0]?.email) { diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index 0532886..ec01587 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -4,7 +4,7 @@ import type { UuringuVastus } from '@kit/shared/types/medipost-analysis'; import { toArray } from '@kit/shared/utils'; import { Database } from '@kit/supabase/database'; -import type { AnalysisOrder } from '../types/analysis-orders'; +import type { AnalysisOrder, AnalysisOrderStatus } from '../types/analysis-orders'; import type { AnalysisResultDetailsElement, AnalysisResultDetailsMapped, @@ -450,6 +450,32 @@ class UserAnalysesApi { return data; } + + async updateAnalysisOrderStatus({ + orderId, + medusaOrderId, + orderStatus, + }: { + orderId?: number; + medusaOrderId?: string; + orderStatus: AnalysisOrderStatus; + }) { + const orderIdParam = orderId; + const medusaOrderIdParam = medusaOrderId; + + console.info(`Updating order id=${orderId} medusaOrderId=${medusaOrderId} status=${orderStatus}`); + if (!orderIdParam && !medusaOrderIdParam) { + throw new Error('Either orderId or medusaOrderId must be provided'); + } + await this.client + .schema('medreport') + .rpc('update_analysis_order_status', { + order_id: orderIdParam ?? -1, + status_param: orderStatus, + medusa_order_id_param: medusaOrderIdParam ?? '', + }) + .throwOnError(); + } } export function createUserAnalysesApi(client: SupabaseClient) { diff --git a/packages/features/user-analyses/src/types/analysis-orders.ts b/packages/features/user-analyses/src/types/analysis-orders.ts index 4ef4027..1eac2f0 100644 --- a/packages/features/user-analyses/src/types/analysis-orders.ts +++ b/packages/features/user-analyses/src/types/analysis-orders.ts @@ -1,3 +1,4 @@ import { Tables } from '@kit/supabase/database'; export type AnalysisOrder = Tables<{ schema: 'medreport' }, 'analysis_orders'>; +export type AnalysisOrderStatus = AnalysisOrder['status']; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ec48dd..2c396f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -755,6 +755,9 @@ importers: '@kit/ui': specifier: workspace:* version: link:../../ui + '@kit/user-analyses': + specifier: workspace:* + version: link:../user-analyses '@makerkit/data-loader-supabase-core': specifier: ^0.0.10 version: 0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4) From f091ed5b49b9eebd4493c6f4d60a9a149aed6279 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 09:45:18 +0300 Subject: [PATCH 11/36] feat(MED-97): update team account navigation links --- .../config/team-account-navigation.config.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/shared/src/config/team-account-navigation.config.tsx b/packages/shared/src/config/team-account-navigation.config.tsx index 6f2c924..09bfdcd 100644 --- a/packages/shared/src/config/team-account-navigation.config.tsx +++ b/packages/shared/src/config/team-account-navigation.config.tsx @@ -1,4 +1,4 @@ -import { CreditCard, LayoutDashboard, Settings, Users } from 'lucide-react'; +import { Euro, LayoutDashboard, Settings, Users } from 'lucide-react'; import { featureFlagsConfig, pathsConfig } from '@kit/shared/config'; import { NavigationConfigSchema } from '@kit/ui/navigation-schema'; @@ -9,28 +9,28 @@ const getRoutes = (account: string) => [ { children: [ { - label: 'common:routes.dashboard', + label: 'common:routes.companyDashboard', path: pathsConfig.app.accountHome.replace('[account]', account), Icon: , end: true, }, { - label: 'common:routes.settings', - path: createPath(pathsConfig.app.accountSettings, account), - Icon: , - }, - { - label: 'common:routes.members', + label: 'common:routes.companyMembers', path: createPath(pathsConfig.app.accountMembers, account), Icon: , }, featureFlagsConfig.enableTeamAccountBilling ? { - label: 'common:routes.billing', - path: createPath(pathsConfig.app.accountBilling, account), - Icon: , - } + label: 'common:routes.billing', + path: createPath(pathsConfig.app.accountBilling, account), + Icon: , + } : undefined, + { + label: 'common:routes.companySettings', + path: createPath(pathsConfig.app.accountSettings, account), + Icon: , + }, ].filter(Boolean), }, ]; From 579ec7547ef144af28d2d07beb656cad487a59fd Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 09:45:35 +0300 Subject: [PATCH 12/36] feat(MED-97): fix client type --- packages/supabase/src/clients/server-client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/supabase/src/clients/server-client.ts b/packages/supabase/src/clients/server-client.ts index c9b8d7e..4c20471 100644 --- a/packages/supabase/src/clients/server-client.ts +++ b/packages/supabase/src/clients/server-client.ts @@ -11,10 +11,10 @@ import { getSupabaseClientKeys } from '../get-supabase-client-keys'; * @name getSupabaseServerClient * @description Creates a Supabase client for use in the Server. */ -export function getSupabaseServerClient() { +export function getSupabaseServerClient() { const keys = getSupabaseClientKeys(); - return createServerClient(keys.url, keys.anonKey, { + return createServerClient(keys.url, keys.anonKey, { auth: { flowType: 'pkce', autoRefreshToken: true, From 56f84a003cb22438b45aabea0e891e88035ba081 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 09:47:28 +0300 Subject: [PATCH 13/36] feat(MED-97): move shared order placing logic to cart-actions --- .../cart/montonio-callback/actions.ts | 218 +------------ app/home/(user)/_lib/server/cart-actions.ts | 308 ++++++++++++++++++ 2 files changed, 312 insertions(+), 214 deletions(-) create mode 100644 app/home/(user)/_lib/server/cart-actions.ts diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts index fd3c894..a23660c 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts @@ -2,100 +2,13 @@ import { MontonioOrderToken } from '@/app/home/(user)/_components/cart/types'; import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; -import { placeOrder, retrieveCart } from '@lib/data/cart'; -import { listProductTypes } from '@lib/data/products'; -import type { StoreOrder } from '@medusajs/types'; +import { retrieveCart } from '@lib/data/cart'; import jwt from 'jsonwebtoken'; -import { z } from 'zod'; -import type { AccountWithParams } from '@kit/accounts/types/accounts'; -import { createNotificationsApi } from '@kit/notifications/api'; -import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; +import { handlePlaceOrder } from '../../../_lib/server/cart-actions'; -import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; -import { sendOrderToMedipost } from '~/lib/services/medipost/medipostPrivateMessage.service'; -import { getOrderedAnalysisIds } from '~/lib/services/medusaOrder.service'; -import { - createAnalysisOrder, - getAnalysisOrder, -} from '~/lib/services/order.service'; - -const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages'; -const ANALYSIS_TYPE_HANDLE = 'synlab-analysis'; const MONTONIO_PAID_STATUS = 'PAID'; -const env = () => - z - .object({ - emailSender: z - .string({ - error: 'EMAIL_SENDER is required', - }) - .min(1), - siteUrl: z - .string({ - error: 'NEXT_PUBLIC_SITE_URL is required', - }) - .min(1), - isEnabledDispatchOnMontonioCallback: z.boolean({ - error: 'MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK is required', - }), - }) - .parse({ - emailSender: process.env.EMAIL_SENDER, - siteUrl: process.env.NEXT_PUBLIC_SITE_URL!, - isEnabledDispatchOnMontonioCallback: - process.env.MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK === 'true', - }); - -const sendEmail = async ({ - account, - email, - analysisPackageName, - partnerLocationName, - language, -}: { - account: Pick; - email: string; - analysisPackageName: string; - partnerLocationName: string; - language: string; -}) => { - const client = getSupabaseServerAdminClient(); - try { - const { renderSynlabAnalysisPackageEmail } = await import( - '@kit/email-templates' - ); - const { getMailer } = await import('@kit/mailers'); - - const mailer = await getMailer(); - - const { html, subject } = await renderSynlabAnalysisPackageEmail({ - analysisPackageName, - personName: account.name, - partnerLocationName, - language, - }); - - await mailer - .sendEmail({ - from: env().emailSender, - to: email, - subject, - html, - }) - .catch((error) => { - throw new Error(`Failed to send email, message=${error}`); - }); - await createNotificationsApi(client).createNotification({ - account_id: account.id, - body: html, - }); - } catch (error) { - throw new Error(`Failed to send email, message=${error}`); - } -}; - async function decodeOrderToken(orderToken: string) { const secretKey = process.env.MONTONIO_SECRET_KEY as string; @@ -122,74 +35,6 @@ async function getCartByOrderToken(decoded: MontonioOrderToken) { return cart; } -async function getOrderResultParameters(medusaOrder: StoreOrder) { - const { productTypes } = await listProductTypes(); - const analysisPackagesType = productTypes.find( - ({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE, - ); - const analysisType = productTypes.find( - ({ metadata }) => metadata?.handle === ANALYSIS_TYPE_HANDLE, - ); - - const analysisPackageOrderItem = medusaOrder.items?.find( - ({ product_type_id }) => product_type_id === analysisPackagesType?.id, - ); - const analysisItems = medusaOrder.items?.filter( - ({ product_type_id }) => product_type_id === analysisType?.id, - ); - - return { - medusaOrderId: medusaOrder.id, - email: medusaOrder.email, - analysisPackageOrder: analysisPackageOrderItem - ? { - partnerLocationName: - (analysisPackageOrderItem?.metadata - ?.partner_location_name as string) ?? '', - analysisPackageName: analysisPackageOrderItem?.title ?? '', - } - : null, - analysisItemsOrder: - Array.isArray(analysisItems) && analysisItems.length > 0 - ? analysisItems.map(({ product }) => ({ - analysisName: product?.title ?? '', - analysisId: (product?.metadata?.analysisIdOriginal as string) ?? '', - })) - : null, - }; -} - -async function sendAnalysisPackageOrderEmail({ - account, - email, - analysisPackageOrder, -}: { - account: AccountWithParams; - email: string; - analysisPackageOrder: { - partnerLocationName: string; - analysisPackageName: string; - }; -}) { - const { language } = await createI18nServerInstance(); - const { analysisPackageName, partnerLocationName } = analysisPackageOrder; - try { - await sendEmail({ - account: { id: account.id, name: account.name }, - email, - analysisPackageName, - partnerLocationName, - language, - }); - console.info(`Successfully sent analysis package order email to ${email}`); - } catch (error) { - console.error( - `Failed to send analysis package order email to ${email}`, - error, - ); - } -} - export async function processMontonioCallback(orderToken: string) { const { account } = await loadCurrentUserAccount(); if (!account) { @@ -199,63 +44,8 @@ export async function processMontonioCallback(orderToken: string) { try { const decoded = await decodeOrderToken(orderToken); const cart = await getCartByOrderToken(decoded); - - const medusaOrder = await placeOrder(cart.id, { - revalidateCacheTags: false, - }); - const orderedAnalysisElements = await getOrderedAnalysisIds({ - medusaOrder, - }); - - try { - const existingAnalysisOrder = await getAnalysisOrder({ - medusaOrderId: medusaOrder.id, - }); - console.info( - `Analysis order already exists for medusaOrderId=${medusaOrder.id}, orderId=${existingAnalysisOrder.id}`, - ); - return { success: true, orderId: existingAnalysisOrder.id }; - } catch { - // ignored - } - - const orderId = await createAnalysisOrder({ - medusaOrder, - orderedAnalysisElements, - }); - const orderResult = await getOrderResultParameters(medusaOrder); - - const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } = - orderResult; - - if (email) { - if (analysisPackageOrder) { - await sendAnalysisPackageOrderEmail({ - account, - email, - analysisPackageOrder, - }); - } else { - console.info(`Order has no analysis package, skipping email.`); - } - - if (analysisItemsOrder) { - // @TODO send email for separate analyses - console.warn( - `Order has analysis items, but no email template exists yet`, - ); - } else { - console.info(`Order has no analysis items, skipping email.`); - } - } else { - console.error('Missing email to send order result email', orderResult); - } - - if (env().isEnabledDispatchOnMontonioCallback) { - await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements }); - } - - return { success: true, orderId }; + const result = await handlePlaceOrder({ cart }); + return result; } catch (error) { console.error('Failed to place order', error); throw new Error(`Failed to place order, message=${error}`); diff --git a/app/home/(user)/_lib/server/cart-actions.ts b/app/home/(user)/_lib/server/cart-actions.ts new file mode 100644 index 0000000..fb95d49 --- /dev/null +++ b/app/home/(user)/_lib/server/cart-actions.ts @@ -0,0 +1,308 @@ +'use server'; + +import { z } from 'zod'; +import jwt from 'jsonwebtoken'; + +import type { StoreCart, StoreOrder } from "@medusajs/types"; + +import { initiateMultiPaymentSession, placeOrder } from "@lib/data/cart"; +import type { AccountBalanceSummary } from "~/lib/services/accountBalance.service"; +import { handleNavigateToPayment } from "~/lib/services/medusaCart.service"; +import { loadCurrentUserAccount } from "./load-user-account"; +import { getOrderedAnalysisIds } from "~/lib/services/medusaOrder.service"; +import { createAnalysisOrder, getAnalysisOrder } from "~/lib/services/order.service"; +import { listProductTypes } from "@lib/data"; +import { sendOrderToMedipost } from "~/lib/services/medipost/medipostPrivateMessage.service"; +import { AccountWithParams } from "@/packages/features/accounts/src/types/accounts"; +import { createI18nServerInstance } from "~/lib/i18n/i18n.server"; +import { getSupabaseServerAdminClient } from "@/packages/supabase/src/clients/server-admin-client"; +import { createNotificationsApi } from "@/packages/features/notifications/src/server/api"; + +const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages'; +const ANALYSIS_TYPE_HANDLE = 'synlab-analysis'; + +const env = () => + z + .object({ + emailSender: z + .string({ + error: 'EMAIL_SENDER is required', + }) + .min(1), + siteUrl: z + .string({ + error: 'NEXT_PUBLIC_SITE_URL is required', + }) + .min(1), + isEnabledDispatchOnMontonioCallback: z.boolean({ + error: 'MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK is required', + }), + medusaBackendPublicUrl: z.string({ + error: 'MEDUSA_BACKEND_PUBLIC_URL is required', + }).min(1), + companyBenefitsPaymentSecretKey: z.string({ + error: 'COMPANY_BENEFITS_PAYMENT_SECRET_KEY is required', + }).min(1), + }) + .parse({ + emailSender: process.env.EMAIL_SENDER, + siteUrl: process.env.NEXT_PUBLIC_SITE_URL!, + isEnabledDispatchOnMontonioCallback: + process.env.MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK === 'true', + medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!, + companyBenefitsPaymentSecretKey: process.env.COMPANY_BENEFITS_PAYMENT_SECRET_KEY!, + }); + +export const initiatePayment = async ({ + accountId, + balanceSummary, + cart, + language, +}: { + accountId: string; + balanceSummary: AccountBalanceSummary; + cart: StoreCart; + language: string; +}) => { + try { + const { + montonioPaymentSessionId, + companyBenefitsPaymentSessionId, + totalByMontonio, + totalByBenefits, + isFullyPaidByBenefits, + } = await initiateMultiPaymentSession(cart, balanceSummary.totalBalance); + + if (!isFullyPaidByBenefits) { + if (!montonioPaymentSessionId) { + throw new Error('Montonio payment session ID is missing'); + } + const url = await handleNavigateToPayment({ + language, + paymentSessionId: montonioPaymentSessionId, + amount: totalByMontonio, + currencyCode: cart.currency_code, + cartId: cart.id, + }); + return { url }; + } else { + // place order if all paid already + const { orderId } = await handlePlaceOrder({ cart }); + + const companyBenefitsOrderToken = jwt.sign({ + accountId, + companyBenefitsPaymentSessionId, + orderId, + totalByBenefits, + }, env().companyBenefitsPaymentSecretKey, { + algorithm: 'HS256', + }); + const webhookResponse = await fetch(`${env().medusaBackendPublicUrl}/hooks/payment/company-benefits_company-benefits`, { + method: 'POST', + body: JSON.stringify({ + orderToken: companyBenefitsOrderToken, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + if (!webhookResponse.ok) { + throw new Error('Failed to send company benefits webhook'); + } + return { isFullyPaidByBenefits, orderId }; + } + } catch (error) { + console.error('Error initiating payment', error); + } + + return { url: null } +} + +export async function handlePlaceOrder({ + cart, +}: { + cart: StoreCart; +}) { + const { account } = await loadCurrentUserAccount(); + if (!account) { + throw new Error('Account not found in context'); + } + + try { + const medusaOrder = await placeOrder(cart.id, { + revalidateCacheTags: false, + }); + const orderedAnalysisElements = await getOrderedAnalysisIds({ + medusaOrder, + }); + + try { + const existingAnalysisOrder = await getAnalysisOrder({ + medusaOrderId: medusaOrder.id, + }); + console.info( + `Analysis order already exists for medusaOrderId=${medusaOrder.id}, orderId=${existingAnalysisOrder.id}`, + ); + return { success: true, orderId: existingAnalysisOrder.id }; + } catch { + // ignored + } + + const orderId = await createAnalysisOrder({ + medusaOrder, + orderedAnalysisElements, + }); + const orderResult = await getOrderResultParameters(medusaOrder); + + const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } = + orderResult; + + if (email) { + if (analysisPackageOrder) { + await sendAnalysisPackageOrderEmail({ + account, + email, + analysisPackageOrder, + }); + } else { + console.info(`Order has no analysis package, skipping email.`); + } + + if (analysisItemsOrder) { + // @TODO send email for separate analyses + console.warn( + `Order has analysis items, but no email template exists yet`, + ); + } else { + console.info(`Order has no analysis items, skipping email.`); + } + } else { + console.error('Missing email to send order result email', orderResult); + } + + if (env().isEnabledDispatchOnMontonioCallback) { + await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements }); + } + + return { success: true, orderId }; + } catch (error) { + console.error('Failed to place order', error); + throw new Error(`Failed to place order, message=${error}`); + } +} + +async function sendAnalysisPackageOrderEmail({ + account, + email, + analysisPackageOrder, +}: { + account: AccountWithParams; + email: string; + analysisPackageOrder: { + partnerLocationName: string; + analysisPackageName: string; + }; +}) { + const { language } = await createI18nServerInstance(); + const { analysisPackageName, partnerLocationName } = analysisPackageOrder; + try { + await sendEmail({ + account: { id: account.id, name: account.name }, + email, + analysisPackageName, + partnerLocationName, + language, + }); + console.info(`Successfully sent analysis package order email to ${email}`); + } catch (error) { + console.error( + `Failed to send analysis package order email to ${email}`, + error, + ); + } +} + +async function getOrderResultParameters(medusaOrder: StoreOrder) { + const { productTypes } = await listProductTypes(); + const analysisPackagesType = productTypes.find( + ({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE, + ); + const analysisType = productTypes.find( + ({ metadata }) => metadata?.handle === ANALYSIS_TYPE_HANDLE, + ); + + const analysisPackageOrderItem = medusaOrder.items?.find( + ({ product_type_id }) => product_type_id === analysisPackagesType?.id, + ); + const analysisItems = medusaOrder.items?.filter( + ({ product_type_id }) => product_type_id === analysisType?.id, + ); + + return { + medusaOrderId: medusaOrder.id, + email: medusaOrder.email, + analysisPackageOrder: analysisPackageOrderItem + ? { + partnerLocationName: + (analysisPackageOrderItem?.metadata + ?.partner_location_name as string) ?? '', + analysisPackageName: analysisPackageOrderItem?.title ?? '', + } + : null, + analysisItemsOrder: + Array.isArray(analysisItems) && analysisItems.length > 0 + ? analysisItems.map(({ product }) => ({ + analysisName: product?.title ?? '', + analysisId: (product?.metadata?.analysisIdOriginal as string) ?? '', + })) + : null, + }; +} + +const sendEmail = async ({ + account, + email, + analysisPackageName, + partnerLocationName, + language, +}: { + account: Pick; + email: string; + analysisPackageName: string; + partnerLocationName: string; + language: string; +}) => { + const client = getSupabaseServerAdminClient(); + try { + const { renderSynlabAnalysisPackageEmail } = await import( + '@kit/email-templates' + ); + const { getMailer } = await import('@kit/mailers'); + + const mailer = await getMailer(); + + const { html, subject } = await renderSynlabAnalysisPackageEmail({ + analysisPackageName, + personName: account.name, + partnerLocationName, + language, + }); + + await mailer + .sendEmail({ + from: env().emailSender, + to: email, + subject, + html, + }) + .catch((error) => { + throw new Error(`Failed to send email, message=${error}`); + }); + await createNotificationsApi(client).createNotification({ + account_id: account.id, + body: html, + }); + } catch (error) { + throw new Error(`Failed to send email, message=${error}`); + } +}; From db38e602aad8805a17e53c161ad642b17e53caf2 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 13:24:09 +0300 Subject: [PATCH 14/36] feat(MED-97): update cart flow for using benefits --- app/home/(user)/(dashboard)/cart/page.tsx | 26 +++- app/home/(user)/_components/cart/index.tsx | 71 +++++++--- .../(user)/_components/order/cart-totals.tsx | 52 +++++++- .../_components/order/order-details.tsx | 2 +- .../(user)/_lib/server/balance-actions.ts | 13 ++ app/home/(user)/_lib/server/cart-actions.ts | 2 +- lib/services/medusaCart.service.ts | 21 +-- lib/services/order.service.ts | 42 ------ lib/types/account-balance-entry.ts | 3 + packages/features/accounts/package.json | 1 + .../services/account-balance.service.ts | 125 ++++++++++++++++++ .../src/types/account-balance-entry.ts | 3 + .../medusa-storefront/src/lib/data/cart.ts | 31 +++++ packages/supabase/src/database.types.ts | 49 +++++++ .../20250926040043_update_consume_balance.sql | 59 +++++++++ 15 files changed, 419 insertions(+), 81 deletions(-) create mode 100644 app/home/(user)/_lib/server/balance-actions.ts create mode 100644 lib/types/account-balance-entry.ts create mode 100644 packages/features/accounts/src/server/services/account-balance.service.ts create mode 100644 packages/features/accounts/src/types/account-balance-entry.ts create mode 100644 supabase/migrations/20250926040043_update_consume_balance.sql diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx index 41dca03..a514cad 100644 --- a/app/home/(user)/(dashboard)/cart/page.tsx +++ b/app/home/(user)/(dashboard)/cart/page.tsx @@ -1,5 +1,3 @@ -import { notFound } from 'next/navigation'; - import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { PageBody, PageHeader } from '@/packages/ui/src/makerkit/page'; import { retrieveCart } from '@lib/data/cart'; @@ -11,6 +9,8 @@ import { withI18n } from '~/lib/i18n/with-i18n'; import Cart from '../../_components/cart'; import CartTimer from '../../_components/cart/cart-timer'; +import { loadCurrentUserAccount } from '../../_lib/server/load-user-account'; +import { AccountBalanceService } from '~/lib/services/accountBalance.service'; export async function generateMetadata() { const { t } = await createI18nServerInstance(); @@ -21,12 +21,22 @@ export async function generateMetadata() { } async function CartPage() { - const cart = await retrieveCart().catch((error) => { - console.error('Failed to retrieve cart', error); - return notFound(); - }); + const [ + cart, + { productTypes }, + { account }, + ] = await Promise.all([ + retrieveCart(), + listProductTypes(), + loadCurrentUserAccount(), + ]); + + if (!account) { + return null; + } + + const balanceSummary = await new AccountBalanceService().getBalanceSummary(account.id); - const { productTypes } = await listProductTypes(); const analysisPackagesType = productTypes.find( ({ metadata }) => metadata?.handle === 'analysis-packages', ); @@ -63,9 +73,11 @@ async function CartPage() { {isTimerShown && } ); diff --git a/app/home/(user)/_components/cart/index.tsx b/app/home/(user)/_components/cart/index.tsx index 7887040..328dbdf 100644 --- a/app/home/(user)/_components/cart/index.tsx +++ b/app/home/(user)/_components/cart/index.tsx @@ -2,9 +2,7 @@ import { useState } from 'react'; -import { handleNavigateToPayment } from '@/lib/services/medusaCart.service'; import { formatCurrency } from '@/packages/shared/src/utils'; -import { initiatePaymentSession } from '@lib/data/cart'; import { StoreCart, StoreCartLineItem } from '@medusajs/types'; import { Loader2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -16,27 +14,35 @@ import { Trans } from '@kit/ui/trans'; import AnalysisLocation from './analysis-location'; import CartItems from './cart-items'; import DiscountCode from './discount-code'; +import { initiatePayment } from '../../_lib/server/cart-actions'; +import { useRouter } from 'next/navigation'; +import { AccountBalanceSummary } from '@kit/accounts/services/account-balance.service'; const IS_DISCOUNT_SHOWN = true as boolean; export default function Cart({ + accountId, cart, synlabAnalyses, ttoServiceItems, + balanceSummary, }: { + accountId: string; cart: StoreCart | null; synlabAnalyses: StoreCartLineItem[]; ttoServiceItems: StoreCartLineItem[]; + balanceSummary: AccountBalanceSummary | null; }) { const { i18n: { language }, } = useTranslation(); const [isInitiatingSession, setIsInitiatingSession] = useState(false); - + const router = useRouter(); const items = cart?.items ?? []; + const hasCartItems = cart && Array.isArray(items) && items.length > 0; - if (!cart || items.length === 0) { + if (!hasCartItems) { return (
@@ -56,24 +62,35 @@ export default function Cart({ ); } - async function initiatePayment() { + async function initiateSession() { setIsInitiatingSession(true); - const response = await initiatePaymentSession(cart!, { - provider_id: 'pp_montonio_montonio', - }); - if (response.payment_collection) { - const { payment_sessions } = response.payment_collection; - const paymentSessionId = payment_sessions![0]!.id; - const url = await handleNavigateToPayment({ language, paymentSessionId }); - window.location.href = url; - } else { + + try { + const { url, isFullyPaidByBenefits, orderId } = await initiatePayment({ + accountId, + balanceSummary: balanceSummary!, + cart: cart!, + language, + }); + if (url) { + window.location.href = url; + } else if (isFullyPaidByBenefits) { + if (typeof orderId !== 'number') { + throw new Error('Order ID is missing'); + } + router.push(`/home/order/${orderId}/confirmed`); + } + } catch (error) { + console.error('Failed to initiate payment', error); setIsInitiatingSession(false); } } - const hasCartItems = Array.isArray(cart.items) && cart.items.length > 0; const isLocationsShown = synlabAnalyses.length > 0; + const companyBenefitsTotal = balanceSummary?.totalBalance ?? 0; + const montonioTotal = cart && companyBenefitsTotal > 0 ? cart.total - companyBenefitsTotal : cart.total; + return (
@@ -106,7 +123,7 @@ export default function Cart({

-
+

@@ -122,6 +139,24 @@ export default function Cart({

+ {companyBenefitsTotal > 0 && ( +
+
+

+ +

+
+
+

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

+
+
+ )}

@@ -131,7 +166,7 @@ export default function Cart({

{formatCurrency({ - value: cart.total, + value: montonioTotal < 0 ? 0 : montonioTotal, currencyCode: cart.currency_code, locale: language, })} @@ -175,7 +210,7 @@ export default function Cart({

From 1aeee0bc30960f8208fd32beb6768c975d2e3bc6 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 13:47:32 +0300 Subject: [PATCH 18/36] feat(MED-97): update benefit stats view in dashboards --- .../(user)/_components/dashboard-cards.tsx | 55 +++++- app/home/(user)/_components/dashboard.tsx | 8 +- .../team-account-benefit-statistics.tsx | 133 ++++---------- .../team-account-health-details.tsx | 6 +- .../_components/team-account-statistics.tsx | 70 +++----- ...-team-account-benefit-expenses-overview.ts | 75 ++++++++ .../load-team-account-benefit-statistics.ts | 96 ++++++++++ .../load-team-account-health-details.ts | 8 +- .../health-benefit-form-client.tsx | 97 ++++++++++ .../_components/health-benefit-form.tsx | 166 ++++++------------ .../_components/yearly-expenses-overview.tsx | 96 +++++----- app/home/[account]/billing/page.tsx | 10 +- app/home/[account]/members/page.tsx | 2 +- app/home/[account]/page.tsx | 7 +- lib/types/account-balance-entry.ts | 3 - lib/utils.ts | 7 +- .../features/accounts/src/types/accounts.ts | 32 ++-- packages/features/admin/package.json | 1 + .../src/components/admin-accounts-table.tsx | 4 +- .../lib/server/schema/admin-actions.schema.ts | 6 +- .../server/services/admin-accounts.service.ts | 3 +- .../webhooks/account-webhooks.service.ts | 4 +- pnpm-lock.yaml | 3 + 23 files changed, 518 insertions(+), 374 deletions(-) create mode 100644 app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts create mode 100644 app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts create mode 100644 app/home/[account]/billing/_components/health-benefit-form-client.tsx delete mode 100644 lib/types/account-balance-entry.ts diff --git a/app/home/(user)/_components/dashboard-cards.tsx b/app/home/(user)/_components/dashboard-cards.tsx index 686afe9..f8adcb0 100644 --- a/app/home/(user)/_components/dashboard-cards.tsx +++ b/app/home/(user)/_components/dashboard-cards.tsx @@ -3,12 +3,33 @@ import Link from 'next/link'; import { ChevronRight, HeartPulse } from 'lucide-react'; import { Button } from '@kit/ui/button'; -import { Card, CardDescription, CardFooter, CardHeader } from '@kit/ui/card'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from '@kit/ui/card'; import { Trans } from '@kit/ui/trans'; +import { formatCurrency } from '@/packages/shared/src/utils'; +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { cn } from '@kit/ui/lib/utils'; +import { loadCurrentUserAccount } from '../_lib/server/load-user-account'; +import { getAccountBalanceSummary } from '../_lib/server/balance-actions'; + +export default async function DashboardCards() { + const { language } = await createI18nServerInstance(); + + const { account } = await loadCurrentUserAccount(); + const balanceSummary = account ? await getAccountBalanceSummary(account.id) : null; -export default function DashboardCards() { return ( -
+
+ + + + + + + + + +
+ {formatCurrency({ + value: balanceSummary?.totalBalance || 0, + locale: language, + currencyCode: 'EUR', + })} +
+ + + +
+
); } + +function Figure(props: React.PropsWithChildren) { + return
{props.children}
; +} diff --git a/app/home/(user)/_components/dashboard.tsx b/app/home/(user)/_components/dashboard.tsx index 53e93ad..e0fbf7e 100644 --- a/app/home/(user)/_components/dashboard.tsx +++ b/app/home/(user)/_components/dashboard.tsx @@ -2,7 +2,6 @@ import Link from 'next/link'; -import { Database } from '@/packages/supabase/src/database.types'; import { BlendingModeIcon, RulerHorizontalIcon } from '@radix-ui/react-icons'; import { isNil } from 'lodash'; import { @@ -15,7 +14,7 @@ import { User, } from 'lucide-react'; -import type { AccountWithParams } from '@kit/accounts/types/accounts'; +import type { AccountWithParams, BmiThresholds } from '@kit/accounts/types/accounts'; import { pathsConfig } from '@kit/shared/config'; import { Button } from '@kit/ui/button'; import { @@ -138,10 +137,7 @@ export default function Dashboard({ bmiThresholds, }: { account: AccountWithParams; - bmiThresholds: Omit< - Database['medreport']['Tables']['bmi_thresholds']['Row'], - 'id' - >[]; + bmiThresholds: Omit[]; }) { const height = account.accountParams?.height || 0; const weight = account.accountParams?.weight || 0; diff --git a/app/home/[account]/_components/team-account-benefit-statistics.tsx b/app/home/[account]/_components/team-account-benefit-statistics.tsx index dd4d983..035ff9d 100644 --- a/app/home/[account]/_components/team-account-benefit-statistics.tsx +++ b/app/home/[account]/_components/team-account-benefit-statistics.tsx @@ -1,23 +1,14 @@ import React from 'react'; -import { redirect } from 'next/navigation'; - import { formatCurrency } from '@/packages/shared/src/utils'; -import { Database } from '@/packages/supabase/src/database.types'; -import { PiggyBankIcon, Settings } from 'lucide-react'; +import { PiggyBankIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { createPath, pathsConfig } from '@kit/shared/config'; import { Card, CardTitle } from '@kit/ui/card'; import { cn } from '@kit/ui/lib/utils'; -import { Button } from '@kit/ui/shadcn/button'; import { Trans } from '@kit/ui/trans'; -interface TeamAccountBenefitStatisticsProps { - employeeCount: number; - accountSlug: string; - companyParams: Database['medreport']['Tables']['company_params']['Row']; -} +import { AccountBenefitStatistics } from '../_lib/server/load-team-account-benefit-statistics'; const StatisticsCard = ({ children }: { children: React.ReactNode }) => { return {children}; @@ -46,10 +37,10 @@ const StatisticsValue = ({ children }: { children: React.ReactNode }) => { }; const TeamAccountBenefitStatistics = ({ - employeeCount, - accountSlug, - companyParams, -}: TeamAccountBenefitStatisticsProps) => { + accountBenefitStatistics, +}: { + accountBenefitStatistics: AccountBenefitStatistics; +}) => { const { i18n: { language }, } = useTranslation(); @@ -58,114 +49,64 @@ const TeamAccountBenefitStatistics = ({
-
-

- -

-

- -

- - - + + + + + {formatCurrency({ + value: accountBenefitStatistics.periodTotal, + locale: language, + currencyCode: 'EUR', + })} +
- + - 1800 € + + {formatCurrency({ + value: accountBenefitStatistics.orders.totalSum, + locale: language, + currencyCode: 'EUR', + })} + + - 200 € + {accountBenefitStatistics.orders.analysesSum} € - - - - - - - 200 € - - - - - - - - - 200 € - - - - - - - - - 200 € - - - + - 200 € + + {formatCurrency({ + value: accountBenefitStatistics.orders.analysisPackagesSum, + locale: language, + currencyCode: 'EUR', + })} + diff --git a/app/home/[account]/_components/team-account-health-details.tsx b/app/home/[account]/_components/team-account-health-details.tsx index 666a029..547a8a8 100644 --- a/app/home/[account]/_components/team-account-health-details.tsx +++ b/app/home/[account]/_components/team-account-health-details.tsx @@ -5,6 +5,7 @@ import { Database } from '@/packages/supabase/src/database.types'; import { Card } from '@kit/ui/card'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; +import type { BmiThresholds } from '@kit/accounts/types/accounts'; import { getAccountHealthDetailsFields } from '../_lib/server/load-team-account-health-details'; import { TeamAccountStatisticsProps } from './team-account-statistics'; @@ -15,10 +16,7 @@ const TeamAccountHealthDetails = ({ members, }: { memberParams: TeamAccountStatisticsProps['memberParams']; - bmiThresholds: Omit< - Database['medreport']['Tables']['bmi_thresholds']['Row'], - 'id' - >[]; + bmiThresholds: Omit[]; members: Database['medreport']['Functions']['get_account_members']['Returns']; }) => { const accountHealthDetailsFields = getAccountHealthDetailsFields( diff --git a/app/home/[account]/_components/team-account-statistics.tsx b/app/home/[account]/_components/team-account-statistics.tsx index fe8af56..bae53a4 100644 --- a/app/home/[account]/_components/team-account-statistics.tsx +++ b/app/home/[account]/_components/team-account-statistics.tsx @@ -14,28 +14,19 @@ import { createPath, pathsConfig } from '@kit/shared/config'; import { Card } from '@kit/ui/card'; import { Trans } from '@kit/ui/makerkit/trans'; import { Button } from '@kit/ui/shadcn/button'; -import { Calendar, DateRange } from '@kit/ui/shadcn/calendar'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@kit/ui/shadcn/popover'; +import { DateRange } from '@kit/ui/shadcn/calendar'; +import { AccountBenefitStatistics } from '../_lib/server/load-team-account-benefit-statistics'; import TeamAccountBenefitStatistics from './team-account-benefit-statistics'; import TeamAccountHealthDetails from './team-account-health-details'; +import type { Account, AccountParams, BmiThresholds } from '@/packages/features/accounts/src/types/accounts'; export interface TeamAccountStatisticsProps { - teamAccount: Database['medreport']['Tables']['accounts']['Row']; - memberParams: Pick< - Database['medreport']['Tables']['account_params']['Row'], - 'weight' | 'height' - >[]; - bmiThresholds: Omit< - Database['medreport']['Tables']['bmi_thresholds']['Row'], - 'id' - >[]; + teamAccount: Account; + memberParams: Pick[]; + bmiThresholds: Omit[]; members: Database['medreport']['Functions']['get_account_members']['Returns']; - companyParams: Database['medreport']['Tables']['company_params']['Row']; + accountBenefitStatistics: AccountBenefitStatistics; } export default function TeamAccountStatistics({ @@ -43,11 +34,12 @@ export default function TeamAccountStatistics({ memberParams, bmiThresholds, members, - companyParams, + accountBenefitStatistics, }: TeamAccountStatisticsProps) { + const currentDate = new Date(); const [date, setDate] = useState({ - from: new Date(), - to: new Date(), + from: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1), + to: new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0), }); const { i18n: { language }, @@ -66,28 +58,16 @@ export default function TeamAccountStatistics({ /> - - - - - - - - +
- +
@@ -148,7 +124,7 @@ export default function TeamAccountStatistics({ redirect( createPath( pathsConfig.app.accountBilling, - teamAccount.slug || '', + teamAccount.slug!, ), ) } diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts new file mode 100644 index 0000000..93548d7 --- /dev/null +++ b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts @@ -0,0 +1,75 @@ +import { getSupabaseServerClient } from "@/packages/supabase/src/clients/server-client"; +import { loadAccountBenefitStatistics, loadCompanyPersonalAccountsBalanceEntries } from "./load-team-account-benefit-statistics"; + +export interface TeamAccountBenefitExpensesOverview { + benefitAmount: number | null; + benefitOccurrence: 'yearly' | 'monthly' | 'quarterly' | null; + currentMonthUsageTotal: number; + managementFee: number; + managementFeeTotal: number; + total: number; +} + +const MANAGEMENT_FEE = 5.50; + +const MONTHS = 12; +const QUARTERS = 4; + +export async function loadTeamAccountBenefitExpensesOverview({ + companyId, + employeeCount, +}: { + companyId: string; + employeeCount: number; +}): Promise { + const supabase = getSupabaseServerClient(); + const { data, error } = await supabase + .schema('medreport') + .from('benefit_distribution_schedule') + .select('*') + .eq('company_id', companyId) + .eq('is_active', true) + .single(); + + let benefitAmount: TeamAccountBenefitExpensesOverview['benefitAmount'] = null; + let benefitOccurrence: TeamAccountBenefitExpensesOverview['benefitOccurrence'] = null; + if (error) { + console.warn('Failed to load team account benefit expenses overview'); + } else { + benefitAmount = data.benefit_amount as TeamAccountBenefitExpensesOverview['benefitAmount']; + benefitOccurrence = data.benefit_occurrence as TeamAccountBenefitExpensesOverview['benefitOccurrence']; + } + + const { purchaseEntriesTotal } = await loadCompanyPersonalAccountsBalanceEntries({ accountId: companyId }); + + return { + benefitAmount, + benefitOccurrence, + currentMonthUsageTotal: purchaseEntriesTotal, + managementFee: MANAGEMENT_FEE, + managementFeeTotal: MANAGEMENT_FEE * employeeCount, + total: (() => { + if (typeof benefitAmount !== 'number') { + return 0; + } + + const currentDate = new Date(); + const createdAt = new Date(data.created_at); + const isCreatedThisYear = createdAt.getFullYear() === currentDate.getFullYear(); + if (benefitOccurrence === 'yearly') { + return benefitAmount * employeeCount; + } else if (benefitOccurrence === 'monthly') { + const monthsLeft = isCreatedThisYear + ? MONTHS - createdAt.getMonth() + : MONTHS; + return benefitAmount * employeeCount * monthsLeft; + } else if (benefitOccurrence === 'quarterly') { + const quartersLeft = isCreatedThisYear + ? QUARTERS - (createdAt.getMonth() / 3) + : QUARTERS; + return benefitAmount * employeeCount * quartersLeft; + } + return 0; + })(), + } +} diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts new file mode 100644 index 0000000..05f51f0 --- /dev/null +++ b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts @@ -0,0 +1,96 @@ +'use server'; + +import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; + +export interface AccountBenefitStatistics { + benefitDistributionSchedule: { + amount: number; + }; + companyAccountsCount: number; + periodTotal: number; + orders: { + totalSum: number; + + analysesCount: number; + analysesSum: number; + + analysisPackagesCount: number; + analysisPackagesSum: number; + } +} + +export const loadCompanyPersonalAccountsBalanceEntries = async ({ + accountId, +}: { + accountId: string; +}) => { + const supabase = getSupabaseServerAdminClient(); + + const { count, data: accountMemberships } = await supabase + .schema('medreport') + .from('accounts_memberships') + .select('user_id') + .eq('account_id', accountId) + .throwOnError(); + + const { data: accountBalanceEntries } = await supabase + .schema('medreport') + .from('account_balance_entries') + .select('*') + .eq('is_active', true) + .in('account_id', accountMemberships.map(({ user_id }) => user_id)) + .throwOnError(); + + const purchaseEntries = accountBalanceEntries.filter(({ entry_type }) => entry_type === 'purchase'); + const analysesEntries = purchaseEntries.filter(({ is_analysis_order }) => is_analysis_order); + const analysisPackagesEntries = purchaseEntries.filter(({ is_analysis_package_order }) => is_analysis_package_order); + + return { + accountBalanceEntries, + analysesEntries, + analysisPackagesEntries, + companyAccountsCount: count || 0, + purchaseEntries, + purchaseEntriesTotal: purchaseEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + }; +} + +export const loadAccountBenefitStatistics = async ( + accountId: string, +): Promise => { + const supabase = getSupabaseServerAdminClient(); + + const { + analysesEntries, + analysisPackagesEntries, + companyAccountsCount, + purchaseEntriesTotal, + } = await loadCompanyPersonalAccountsBalanceEntries({ accountId }); + + const { data: benefitDistributionSchedule } = await supabase + .schema('medreport') + .from('benefit_distribution_schedule') + .select('*') + .eq('company_id', accountId) + .eq('is_active', true) + .single() + .throwOnError(); + + const scheduleAmount = benefitDistributionSchedule?.benefit_amount || 0; + return { + companyAccountsCount, + benefitDistributionSchedule: { + amount: benefitDistributionSchedule?.benefit_amount || 0, + }, + periodTotal: scheduleAmount * companyAccountsCount, + orders: { + totalSum: purchaseEntriesTotal, + + analysesCount: analysesEntries.length, + analysesSum: analysesEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + + analysisPackagesCount: analysisPackagesEntries.length, + analysisPackagesSum: analysisPackagesEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + }, + }; +}; diff --git a/app/home/[account]/_lib/server/load-team-account-health-details.ts b/app/home/[account]/_lib/server/load-team-account-health-details.ts index 1705770..9568dda 100644 --- a/app/home/[account]/_lib/server/load-team-account-health-details.ts +++ b/app/home/[account]/_lib/server/load-team-account-health-details.ts @@ -11,6 +11,7 @@ import { } from '~/lib/utils'; import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics'; +import type { BmiThresholds } from '@kit/accounts/types/accounts'; interface AccountHealthDetailsField { title: string; @@ -25,10 +26,7 @@ interface AccountHealthDetailsField { export const getAccountHealthDetailsFields = ( memberParams: TeamAccountStatisticsProps['memberParams'], - bmiThresholds: Omit< - Database['medreport']['Tables']['bmi_thresholds']['Row'], - 'id' - >[], + bmiThresholds: Omit[], members: Database['medreport']['Functions']['get_account_members']['Returns'], ): AccountHealthDetailsField[] => { const averageWeight = @@ -82,7 +80,7 @@ export const getAccountHealthDetailsFields = ( }, { title: 'teams:healthDetails.bmi', - value: averageBMI, + value: averageBMI!, Icon: TrendingUp, iconBg: getBmiBackgroundColor(bmiStatus), }, diff --git a/app/home/[account]/billing/_components/health-benefit-form-client.tsx b/app/home/[account]/billing/_components/health-benefit-form-client.tsx new file mode 100644 index 0000000..8812683 --- /dev/null +++ b/app/home/[account]/billing/_components/health-benefit-form-client.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { useState } from 'react'; + +import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; + +import { Button } from '@kit/ui/button'; +import { Form } from '@kit/ui/form'; +import { Spinner } from '@kit/ui/makerkit/spinner'; +import { toast } from '@kit/ui/shadcn/sonner'; +import { Trans } from '@kit/ui/trans'; + +import { cn } from '~/lib/utils'; + +import { updateHealthBenefit } from '../_lib/server/server-actions'; +import HealthBenefitFields from './health-benefit-fields'; +import { Account, CompanyParams } from '@/packages/features/accounts/src/types/accounts'; +import { useTranslation } from 'react-i18next'; + +const HealthBenefitFormClient = ({ + account, + companyParams, +}: { + account: Account; + companyParams: CompanyParams; +}) => { + const { t } = useTranslation('account'); + + const [currentCompanyParams, setCurrentCompanyParams] = + useState(companyParams); + const [isLoading, setIsLoading] = useState(false); + + const form = useForm({ + resolver: zodResolver(UpdateHealthBenefitSchema), + mode: 'onChange', + defaultValues: { + occurrence: currentCompanyParams.benefit_occurance || 'yearly', + amount: currentCompanyParams.benefit_amount || 0, + }, + }); + + const isDirty = form.formState.isDirty; + + const onSubmit = (data: { occurrence: string; amount: number }) => { + const promise = async () => { + setIsLoading(true); + try { + await updateHealthBenefit({ ...data, accountId: account.id }); + setCurrentCompanyParams((prev) => ({ + ...prev, + benefit_amount: data.amount, + benefit_occurance: data.occurrence, + })); + } finally { + form.reset(data); + setIsLoading(false); + } + }; + + toast.promise(promise, { + success: t('account:healthBenefitForm.updateSuccess'), + error: 'error', + }); + }; + + return ( +
+ + + + + + + ); +}; + +export default HealthBenefitFormClient; + + diff --git a/app/home/[account]/billing/_components/health-benefit-form.tsx b/app/home/[account]/billing/_components/health-benefit-form.tsx index a21a72e..d49ccd2 100644 --- a/app/home/[account]/billing/_components/health-benefit-form.tsx +++ b/app/home/[account]/billing/_components/health-benefit-form.tsx @@ -1,138 +1,70 @@ -'use client'; - -import { useState } from 'react'; - -import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema'; -import { Database } from '@/packages/supabase/src/database.types'; -import { zodResolver } from '@hookform/resolvers/zod'; import { PiggyBankIcon } from 'lucide-react'; -import { useForm } from 'react-hook-form'; -import { Button } from '@kit/ui/button'; -import { Form } from '@kit/ui/form'; -import { Spinner } from '@kit/ui/makerkit/spinner'; import { Separator } from '@kit/ui/shadcn/separator'; -import { toast } from '@kit/ui/shadcn/sonner'; import { Trans } from '@kit/ui/trans'; -import { cn } from '~/lib/utils'; - -import { updateHealthBenefit } from '../_lib/server/server-actions'; -import HealthBenefitFields from './health-benefit-fields'; +import HealthBenefitFormClient from './health-benefit-form-client'; import YearlyExpensesOverview from './yearly-expenses-overview'; +import { TeamAccountBenefitExpensesOverview } from '../../_lib/server/load-team-account-benefit-expenses-overview'; +import { Account, CompanyParams } from '@/packages/features/accounts/src/types/accounts'; -const HealthBenefitForm = ({ +const HealthBenefitForm = async ({ account, companyParams, employeeCount, + expensesOverview, }: { - account: Database['medreport']['Tables']['accounts']['Row']; - companyParams: Database['medreport']['Tables']['company_params']['Row']; + account: Account; + companyParams: CompanyParams; employeeCount: number; + expensesOverview: TeamAccountBenefitExpensesOverview; }) => { - const [currentCompanyParams, setCurrentCompanyParams] = - useState( - companyParams, - ); - const [isLoading, setIsLoading] = useState(false); - const form = useForm({ - resolver: zodResolver(UpdateHealthBenefitSchema), - mode: 'onChange', - defaultValues: { - occurrence: currentCompanyParams.benefit_occurance || 'yearly', - amount: currentCompanyParams.benefit_amount || 0, - }, - }); - const isDirty = form.formState.isDirty; - - const onSubmit = (data: { occurrence: string; amount: number }) => { - const promise = async () => { - setIsLoading(true); - try { - await updateHealthBenefit({ ...data, accountId: account.id }); - setCurrentCompanyParams((prev) => ({ - ...prev, - benefit_amount: data.amount, - benefit_occurance: data.occurrence, - })); - } finally { - form.reset(data); - setIsLoading(false); - } - }; - - toast.promise(promise, { - success: 'Andmed uuendatud', - error: 'error', - }); - }; - return ( -
- -
-
-

- -

-

- -

-
- -
-
-
-
-
- -
-

- -

-

- {currentCompanyParams.benefit_amount || 0} € -

-
- - - -
- -
-
- -
- +
+
+

+ -

- +

+
+
+ +
+
+
+
+ +
+

+

+ + + +
+ +
- - + +
+ + +

+ +

+
+
+
); }; diff --git a/app/home/[account]/billing/_components/yearly-expenses-overview.tsx b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx index 36a42be..afd3d21 100644 --- a/app/home/[account]/billing/_components/yearly-expenses-overview.tsx +++ b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx @@ -1,50 +1,19 @@ -import { useMemo } from 'react'; - -import { Database } from '@/packages/supabase/src/database.types'; +'use client'; import { Trans } from '@kit/ui/makerkit/trans'; import { Separator } from '@kit/ui/separator'; +import { formatCurrency } from '@/packages/shared/src/utils'; +import { useTranslation } from 'react-i18next'; +import { TeamAccountBenefitExpensesOverview } from '../../_lib/server/load-team-account-benefit-expenses-overview'; const YearlyExpensesOverview = ({ employeeCount = 0, - companyParams, + expensesOverview, }: { employeeCount?: number; - companyParams: Database['medreport']['Tables']['company_params']['Row']; + expensesOverview: TeamAccountBenefitExpensesOverview; }) => { - const monthlyExpensePerEmployee = useMemo(() => { - if (!companyParams.benefit_amount) { - return '0.00'; - } - - switch (companyParams.benefit_occurance) { - case 'yearly': - return (companyParams.benefit_amount / 12).toFixed(2); - case 'quarterly': - return (companyParams.benefit_amount / 3).toFixed(2); - case 'monthly': - return companyParams.benefit_amount.toFixed(2); - default: - return '0.00'; - } - }, [companyParams]); - - const maxYearlyExpensePerEmployee = useMemo(() => { - if (!companyParams.benefit_amount) { - return '0.00'; - } - - switch (companyParams.benefit_occurance) { - case 'yearly': - return companyParams.benefit_amount.toFixed(2); - case 'quarterly': - return (companyParams.benefit_amount * 3).toFixed(2); - case 'monthly': - return (companyParams.benefit_amount * 12).toFixed(2); - default: - return '0.00'; - } - }, [companyParams]); + const { i18n: { language } } = useTranslation(); return (
@@ -53,41 +22,56 @@ const YearlyExpensesOverview = ({

- +

- {monthlyExpensePerEmployee} € + {employeeCount}
-

- -

- - {maxYearlyExpensePerEmployee} € - -
-

- {(Number(maxYearlyExpensePerEmployee) * employeeCount).toFixed(2)} € + {formatCurrency({ + value: expensesOverview.managementFeeTotal, + locale: language, + currencyCode: 'EUR', + })} + +
+
+

+ +

+ + {formatCurrency({ + value: expensesOverview.currentMonthUsageTotal, + locale: language, + currencyCode: 'EUR', + })}

- +

- {companyParams.benefit_amount - ? companyParams.benefit_amount * employeeCount - : 0}{' '} - € + {formatCurrency({ + value: expensesOverview.total, + locale: language, + currencyCode: 'EUR', + })}
diff --git a/app/home/[account]/billing/page.tsx b/app/home/[account]/billing/page.tsx index bc8288b..ed06890 100644 --- a/app/home/[account]/billing/page.tsx +++ b/app/home/[account]/billing/page.tsx @@ -7,6 +7,7 @@ import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; import HealthBenefitForm from './_components/health-benefit-form'; +import { loadTeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; interface TeamAccountBillingPageProps { params: Promise<{ account: string }>; @@ -27,8 +28,14 @@ async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) { const api = createTeamAccountsApi(client); const account = await api.getTeamAccount(accountSlug); - const companyParams = await api.getTeamAccountParams(account.id); const { members } = await api.getMembers(accountSlug); + const [expensesOverview, companyParams] = await Promise.all([ + loadTeamAccountBenefitExpensesOverview({ + companyId: account.id, + employeeCount: members.length, + }), + api.getTeamAccountParams(account.id), + ]); return ( @@ -36,6 +43,7 @@ async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) { account={account} companyParams={companyParams} employeeCount={members.length} + expensesOverview={expensesOverview} /> ); diff --git a/app/home/[account]/members/page.tsx b/app/home/[account]/members/page.tsx index 324e6b0..fc11cea 100644 --- a/app/home/[account]/members/page.tsx +++ b/app/home/[account]/members/page.tsx @@ -54,7 +54,7 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) { return ( <> } + title={} description={} /> diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx index 7283798..5cff5d3 100644 --- a/app/home/[account]/page.tsx +++ b/app/home/[account]/page.tsx @@ -17,6 +17,7 @@ import { } from '~/lib/services/audit/pageView.service'; import { Dashboard } from './_components/dashboard'; +import { loadAccountBenefitStatistics } from './_lib/server/load-team-account-benefit-statistics'; interface TeamAccountHomePageProps { params: Promise<{ account: string }>; @@ -39,9 +40,7 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { const teamAccount = use(teamAccountsApi.getTeamAccount(account)); const { memberParams, members } = use(teamAccountsApi.getMembers(account)); const bmiThresholds = use(userAnalysesApi.fetchBmiThresholds()); - const companyParams = use( - teamAccountsApi.getTeamAccountParams(teamAccount.id), - ); + const accountBenefitStatistics = use(loadAccountBenefitStatistics(teamAccount.id)); use( createPageViewLog({ @@ -57,7 +56,7 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { memberParams={memberParams} bmiThresholds={bmiThresholds} members={members} - companyParams={companyParams} + accountBenefitStatistics={accountBenefitStatistics} /> ); diff --git a/lib/types/account-balance-entry.ts b/lib/types/account-balance-entry.ts deleted file mode 100644 index 434e5e6..0000000 --- a/lib/types/account-balance-entry.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { Database } from "@/packages/supabase/src/database.types"; - -export type AccountBalanceEntry = Database['medreport']['Tables']['account_balance_entries']['Row']; diff --git a/lib/utils.ts b/lib/utils.ts index d9d0f96..7d2f9ad 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,9 +1,9 @@ -import { Database } from '@/packages/supabase/src/database.types'; import { type ClassValue, clsx } from 'clsx'; import Isikukood, { Gender } from 'isikukood'; import { twMerge } from 'tailwind-merge'; import { BmiCategory } from './types/bmi'; +import type { BmiThresholds } from '@kit/accounts/types/accounts'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -45,10 +45,7 @@ export const bmiFromMetric = (kg: number, cm: number) => { }; export function getBmiStatus( - thresholds: Omit< - Database['medreport']['Tables']['bmi_thresholds']['Row'], - 'id' - >[], + thresholds: Omit[], params: { age: number; height: number; weight: number }, ): BmiCategory | null { const age = params.age; diff --git a/packages/features/accounts/src/types/accounts.ts b/packages/features/accounts/src/types/accounts.ts index bc305c0..430c898 100644 --- a/packages/features/accounts/src/types/accounts.ts +++ b/packages/features/accounts/src/types/accounts.ts @@ -1,23 +1,25 @@ import { Database } from '@kit/supabase/database'; -export type ApplicationRole = - Database['medreport']['Tables']['accounts']['Row']['application_role']; +export type ApplicationRole = Account['application_role']; export enum ApplicationRoleEnum { User = 'user', Doctor = 'doctor', SuperAdmin = 'super_admin', } -export type AccountWithParams = - Database['medreport']['Tables']['accounts']['Row'] & { - accountParams: - | (Pick< - Database['medreport']['Tables']['account_params']['Row'], - 'weight' | 'height' - > & { - isSmoker: - | Database['medreport']['Tables']['account_params']['Row']['is_smoker'] - | null; - }) - | null; - }; +export type AccountParams = + Database['medreport']['Tables']['account_params']['Row']; + +export type Account = Database['medreport']['Tables']['accounts']['Row']; +export type AccountWithParams = Account & { + accountParams: + | (Pick & { + isSmoker: AccountParams['is_smoker'] | null; + }) + | null; +}; + +export type CompanyParams = + Database['medreport']['Tables']['company_params']['Row']; + +export type BmiThresholds = Database['medreport']['Tables']['bmi_thresholds']['Row']; diff --git a/packages/features/admin/package.json b/packages/features/admin/package.json index 72da143..d07673d 100644 --- a/packages/features/admin/package.json +++ b/packages/features/admin/package.json @@ -9,6 +9,7 @@ "devDependencies": { "@hookform/resolvers": "^5.0.1", "@kit/next": "workspace:*", + "@kit/accounts": "workspace:*", "@kit/shared": "workspace:*", "@kit/supabase": "workspace:*", "@kit/tsconfig": "workspace:*", diff --git a/packages/features/admin/src/components/admin-accounts-table.tsx b/packages/features/admin/src/components/admin-accounts-table.tsx index ab7f0c1..d993ab7 100644 --- a/packages/features/admin/src/components/admin-accounts-table.tsx +++ b/packages/features/admin/src/components/admin-accounts-table.tsx @@ -11,7 +11,7 @@ import { EllipsisVertical } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; -import { Database } from '@kit/supabase/database'; +import type { Account } from '@kit/accounts/types/accounts'; import { Button } from '@kit/ui/button'; import { Checkbox } from '@kit/ui/checkbox'; import { @@ -44,8 +44,6 @@ import { AdminDeleteUserDialog } from './admin-delete-user-dialog'; import { AdminImpersonateUserDialog } from './admin-impersonate-user-dialog'; import { AdminResetPasswordDialog } from './admin-reset-password-dialog'; -type Account = Database['medreport']['Tables']['accounts']['Row']; - const FiltersSchema = z.object({ type: z.enum(['all', 'team', 'personal']), query: z.string().optional(), diff --git a/packages/features/admin/src/lib/server/schema/admin-actions.schema.ts b/packages/features/admin/src/lib/server/schema/admin-actions.schema.ts index 8edd356..aa5d86d 100644 --- a/packages/features/admin/src/lib/server/schema/admin-actions.schema.ts +++ b/packages/features/admin/src/lib/server/schema/admin-actions.schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { Database } from '@kit/supabase/database'; +import { ApplicationRole } from '@kit/accounts/types/accounts'; const ConfirmationSchema = z.object({ confirmation: z.custom((value) => value === 'CONFIRM'), @@ -19,9 +19,7 @@ export const DeleteAccountSchema = ConfirmationSchema.extend({ accountId: z.string().uuid(), }); -type ApplicationRoleType = - Database['medreport']['Tables']['accounts']['Row']['application_role']; export const UpdateAccountRoleSchema = z.object({ accountId: z.string().uuid(), - role: z.string() as z.ZodType, + role: z.string() as z.ZodType, }); diff --git a/packages/features/admin/src/lib/server/services/admin-accounts.service.ts b/packages/features/admin/src/lib/server/services/admin-accounts.service.ts index c46bc03..6f26b58 100644 --- a/packages/features/admin/src/lib/server/services/admin-accounts.service.ts +++ b/packages/features/admin/src/lib/server/services/admin-accounts.service.ts @@ -3,6 +3,7 @@ import 'server-only'; import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@kit/supabase/database'; +import type { ApplicationRole } from '@kit/accounts/types/accounts'; export function createAdminAccountsService(client: SupabaseClient) { return new AdminAccountsService(client); @@ -25,7 +26,7 @@ class AdminAccountsService { async updateRole( accountId: string, - role: Database['medreport']['Tables']['accounts']['Row']['application_role'], + role: ApplicationRole, ) { const { error } = await this.adminClient .schema('medreport') diff --git a/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts b/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts index 9bdc5a3..d1d6aa4 100644 --- a/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts +++ b/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts @@ -1,9 +1,7 @@ import { z } from 'zod'; import { getLogger } from '@kit/shared/logger'; -import { Database } from '@kit/supabase/database'; - -type Account = Database['medreport']['Tables']['accounts']['Row']; +import type { Account } from '@kit/accounts/types/accounts'; export function createAccountWebhooksService() { return new AccountWebhooksService(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c396f6..b000d26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -647,6 +647,9 @@ importers: '@hookform/resolvers': specifier: ^5.0.1 version: 5.2.1(react-hook-form@7.62.0(react@19.1.0)) + '@kit/accounts': + specifier: workspace:* + version: link:../accounts '@kit/next': specifier: workspace:* version: link:../../next From 0aa16c457a23beee248846f71f3d593022b9764f Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 13:50:33 +0300 Subject: [PATCH 19/36] feat(MED-97): make sure new company employee accounts get benefits balance --- .../src/server/actions/team-invitations-server-actions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts index 4f11f93..69b8810 100644 --- a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts +++ b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts @@ -18,6 +18,7 @@ import { RenewInvitationSchema } from '../../schema/renew-invitation.schema'; import { UpdateInvitationSchema } from '../../schema/update-invitation.schema'; import { createAccountInvitationsService } from '../services/account-invitations.service'; import { createAccountPerSeatBillingService } from '../services/account-per-seat-billing.service'; +import { AccountBalanceService } from '@kit/accounts/services/account-balance.service'; /** * @name createInvitationsAction @@ -171,6 +172,9 @@ export const acceptInvitationAction = enhanceAction( throw new Error('Failed to accept invitation'); } + // Make sure new account gets company benefits added to balance + await new AccountBalanceService().processPeriodicBenefitDistributions(); + // Increase the seats for the account await perSeatBillingService.increaseSeats(accountId); From 428cbd9477e4d30a1347eac9b45dcd701b2f149d Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 15:37:47 +0300 Subject: [PATCH 20/36] feat(MED-97): small fixes --- .../server/load-team-account-benefit-expenses-overview.ts | 4 ++-- .../_lib/server/load-team-account-benefit-statistics.ts | 3 +-- .../billing/_components/health-benefit-form-client.tsx | 5 ++++- .../[account]/billing/_components/health-benefit-form.tsx | 4 ++-- app/join/page.tsx | 2 +- .../accounts/src/server/services/account-balance.service.ts | 4 +++- .../src/server/actions/team-invitations-server-actions.ts | 3 ++- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts index 93548d7..0f260ea 100644 --- a/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts +++ b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts @@ -1,5 +1,5 @@ import { getSupabaseServerClient } from "@/packages/supabase/src/clients/server-client"; -import { loadAccountBenefitStatistics, loadCompanyPersonalAccountsBalanceEntries } from "./load-team-account-benefit-statistics"; +import { loadCompanyPersonalAccountsBalanceEntries } from "./load-team-account-benefit-statistics"; export interface TeamAccountBenefitExpensesOverview { benefitAmount: number | null; @@ -65,7 +65,7 @@ export async function loadTeamAccountBenefitExpensesOverview({ return benefitAmount * employeeCount * monthsLeft; } else if (benefitOccurrence === 'quarterly') { const quartersLeft = isCreatedThisYear - ? QUARTERS - (createdAt.getMonth() / 3) + ? QUARTERS - Math.ceil(createdAt.getMonth() / 3) : QUARTERS; return benefitAmount * employeeCount * quartersLeft; } diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts index 05f51f0..4de4be5 100644 --- a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts +++ b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts @@ -73,8 +73,7 @@ export const loadAccountBenefitStatistics = async ( .select('*') .eq('company_id', accountId) .eq('is_active', true) - .single() - .throwOnError(); + .single(); const scheduleAmount = benefitDistributionSchedule?.benefit_amount || 0; return { diff --git a/app/home/[account]/billing/_components/health-benefit-form-client.tsx b/app/home/[account]/billing/_components/health-benefit-form-client.tsx index 8812683..6ca2ff2 100644 --- a/app/home/[account]/billing/_components/health-benefit-form-client.tsx +++ b/app/home/[account]/billing/_components/health-benefit-form-client.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState } from 'react'; +import { useRouter } from 'next/navigation'; import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -27,7 +28,8 @@ const HealthBenefitFormClient = ({ companyParams: CompanyParams; }) => { const { t } = useTranslation('account'); - + const router = useRouter(); + const [currentCompanyParams, setCurrentCompanyParams] = useState(companyParams); const [isLoading, setIsLoading] = useState(false); @@ -56,6 +58,7 @@ const HealthBenefitFormClient = ({ } finally { form.reset(data); setIsLoading(false); + router.refresh(); } }; diff --git a/app/home/[account]/billing/_components/health-benefit-form.tsx b/app/home/[account]/billing/_components/health-benefit-form.tsx index d49ccd2..4decbe2 100644 --- a/app/home/[account]/billing/_components/health-benefit-form.tsx +++ b/app/home/[account]/billing/_components/health-benefit-form.tsx @@ -32,8 +32,8 @@ const HealthBenefitForm = async ({
-
-
+
+
diff --git a/app/join/page.tsx b/app/join/page.tsx index 72b6e2f..cabd06b 100644 --- a/app/join/page.tsx +++ b/app/join/page.tsx @@ -116,7 +116,7 @@ async function JoinTeamAccountPage(props: JoinTeamAccountPageProps) { return ( { - const { error } = await this.supabase.schema('medreport').rpc('process_periodic_benefit_distributions') + console.info('Processing periodic benefit distributions...'); + const { error } = await this.supabase.schema('medreport').rpc('process_periodic_benefit_distributions'); if (error) { console.error('Error processing periodic benefit distributions:', error); throw new Error('Failed to process periodic benefit distributions'); } + console.info('Periodic benefit distributions processed successfully'); } } diff --git a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts index 69b8810..ccb89e2 100644 --- a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts +++ b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts @@ -149,6 +149,7 @@ export const updateInvitationAction = enhanceAction( export const acceptInvitationAction = enhanceAction( async (data: FormData, user) => { const client = getSupabaseServerClient(); + const accountBalanceService = new AccountBalanceService(); const { inviteToken, nextPath } = AcceptInvitationSchema.parse( Object.fromEntries(data), @@ -173,7 +174,7 @@ export const acceptInvitationAction = enhanceAction( } // Make sure new account gets company benefits added to balance - await new AccountBalanceService().processPeriodicBenefitDistributions(); + await accountBalanceService.processPeriodicBenefitDistributions(); // Increase the seats for the account await perSeatBillingService.increaseSeats(accountId); From eb6ef2abe1c711516f2fc1c8e5259b70a8991296 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 15:39:33 +0300 Subject: [PATCH 21/36] feat(MED-97): show benefits amount for each member --- .../_lib/server/members-page.loader.ts | 26 ++++++++++++++- app/home/[account]/members/page.tsx | 3 +- .../members/account-members-table.tsx | 32 +++++++++++++++++-- public/locales/en/teams.json | 3 +- public/locales/et/teams.json | 3 +- public/locales/ru/teams.json | 3 +- 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/app/home/[account]/members/_lib/server/members-page.loader.ts b/app/home/[account]/members/_lib/server/members-page.loader.ts index 6025aba..db902bc 100644 --- a/app/home/[account]/members/_lib/server/members-page.loader.ts +++ b/app/home/[account]/members/_lib/server/members-page.loader.ts @@ -5,6 +5,7 @@ import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@/packages/supabase/src/database.types'; import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader'; +import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; /** * Load data for the members page @@ -15,11 +16,13 @@ export async function loadMembersPageData( client: SupabaseClient, slug: string, ) { + const workspace = await loadTeamWorkspace(slug); return Promise.all([ loadAccountMembers(client, slug), loadInvitations(client, slug), canAddMember, - loadTeamWorkspace(slug), + workspace, + loadAccountMembersBenefitsUsage(getSupabaseServerAdminClient(), workspace.account.id), ]); } @@ -60,6 +63,27 @@ async function loadAccountMembers( return data ?? []; } +export async function loadAccountMembersBenefitsUsage( + client: SupabaseClient, + accountId: string, +): Promise<{ + personal_account_id: string; + benefit_amount: number; +}[]> { + const { data, error } = await client + .schema('medreport') + .rpc('get_benefits_usages_for_company_members', { + p_account_id: accountId, + }); + + if (error) { + console.error('Failed to load account members benefits usage', error); + return []; + } + + return data ?? []; +} + /** * Load account invitations * @param client diff --git a/app/home/[account]/members/page.tsx b/app/home/[account]/members/page.tsx index fc11cea..0b4f4cd 100644 --- a/app/home/[account]/members/page.tsx +++ b/app/home/[account]/members/page.tsx @@ -42,7 +42,7 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) { const client = getSupabaseServerClient(); const slug = (await params).account; - const [members, invitations, canAddMember, { user, account }] = + const [members, invitations, canAddMember, { user, account }, membersBenefitsUsage] = await loadMembersPageData(client, slug); const canManageRoles = account.permissions.includes('roles.manage'); @@ -96,6 +96,7 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) { members={members} isPrimaryOwner={isPrimaryOwner} canManageRoles={canManageRoles} + membersBenefitsUsage={membersBenefitsUsage} /> diff --git a/packages/features/team-accounts/src/components/members/account-members-table.tsx b/packages/features/team-accounts/src/components/members/account-members-table.tsx index 0e461f9..5003f40 100644 --- a/packages/features/team-accounts/src/components/members/account-members-table.tsx +++ b/packages/features/team-accounts/src/components/members/account-members-table.tsx @@ -20,6 +20,7 @@ import { If } from '@kit/ui/if'; import { Input } from '@kit/ui/input'; import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { Trans } from '@kit/ui/trans'; +import { formatCurrency } from '@kit/shared/utils'; import { RemoveMemberDialog } from './remove-member-dialog'; import { RoleBadge } from './role-badge'; @@ -42,6 +43,10 @@ type AccountMembersTableProps = { userRoleHierarchy: number; isPrimaryOwner: boolean; canManageRoles: boolean; + membersBenefitsUsage: { + personal_account_id: string; + benefit_amount: number; + }[]; }; export function AccountMembersTable({ @@ -51,6 +56,7 @@ export function AccountMembersTable({ isPrimaryOwner, userRoleHierarchy, canManageRoles, + membersBenefitsUsage, }: AccountMembersTableProps) { const [search, setSearch] = useState(''); const { t } = useTranslation('teams'); @@ -73,6 +79,7 @@ export function AccountMembersTable({ currentUserId, currentAccountId, currentRoleHierarchy: userRoleHierarchy, + membersBenefitsUsage, }); const filteredMembers = members @@ -122,9 +129,13 @@ function useGetColumns( currentUserId: string; currentAccountId: string; currentRoleHierarchy: number; + membersBenefitsUsage: { + personal_account_id: string; + benefit_amount: number; + }[]; }, ): ColumnDef[] { - const { t } = useTranslation('teams'); + const { t, i18n: { language } } = useTranslation('teams'); return useMemo( () => [ @@ -168,6 +179,23 @@ function useGetColumns( return row.original.personal_code ?? '-'; }, }, + { + header: t('distributedBenefitsAmount'), + cell: ({ row }) => { + const benefitAmount = params.membersBenefitsUsage.find( + (usage) => usage.personal_account_id === row.original.id + )?.benefit_amount; + if (typeof benefitAmount !== 'number') { + return '-'; + } + + return formatCurrency({ + currencyCode: 'EUR', + locale: language, + value: benefitAmount, + }); + }, + }, { header: t('roleLabel'), cell: ({ row }) => { @@ -175,7 +203,7 @@ function useGetColumns( const isPrimaryOwner = primary_owner_user_id === user_id; return ( - + diff --git a/public/locales/en/teams.json b/public/locales/en/teams.json index b015945..7bc7f6e 100644 --- a/public/locales/en/teams.json +++ b/public/locales/en/teams.json @@ -160,5 +160,6 @@ "leaveTeamInputDescription": "By leaving the company, you will no longer have access to it.", "reservedNameError": "This name is reserved. Please choose a different one.", "specialCharactersError": "This name cannot contain special characters. Please choose a different one.", - "personalCode": "Personal Code" + "personalCode": "Personal Code", + "distributedBenefitsAmount": "Assigned benefits" } diff --git a/public/locales/et/teams.json b/public/locales/et/teams.json index 6b85800..74376f5 100644 --- a/public/locales/et/teams.json +++ b/public/locales/et/teams.json @@ -193,5 +193,6 @@ "reservedNameError": "See nimi on reserveeritud. Palun vali mõni teine.", "specialCharactersError": "Nimi ei tohi sisaldada erimärke. Palun vali mõni teine.", "personalCode": "Isikukood", - "teamOwnerPersonalCodeLabel": "Omaniku isikukood" + "teamOwnerPersonalCodeLabel": "Omaniku isikukood", + "distributedBenefitsAmount": "Väljastatud toetus" } diff --git a/public/locales/ru/teams.json b/public/locales/ru/teams.json index 9eaa712..9f4125f 100644 --- a/public/locales/ru/teams.json +++ b/public/locales/ru/teams.json @@ -193,5 +193,6 @@ "reservedNameError": "Это имя зарезервировано. Пожалуйста, выберите другое.", "specialCharactersError": "Это имя не может содержать специальные символы. Пожалуйста, выберите другое.", "personalCode": "Идентификационный код", - "teamOwnerPersonalCodeLabel": "Идентификационный код владельца" + "teamOwnerPersonalCodeLabel": "Идентификационный код владельца", + "distributedBenefitsAmount": "Распределенные выплаты" } From 92dd79212195d233fe908b05c25a10813046eb64 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 15:46:01 +0300 Subject: [PATCH 22/36] feat(MED-97): update translations in each language --- public/locales/en/account.json | 5 +++- public/locales/en/billing.json | 11 ++++----- public/locales/en/cart.json | 6 ++++- public/locales/en/common.json | 6 ++--- public/locales/en/dashboard.json | 6 ++++- public/locales/en/teams.json | 39 +++++++++++++++++++++++++++++--- public/locales/et/account.json | 5 +++- public/locales/et/billing.json | 13 +++++------ public/locales/et/cart.json | 6 ++++- public/locales/et/common.json | 8 +++---- public/locales/et/dashboard.json | 7 +++++- public/locales/et/teams.json | 14 ++++++------ public/locales/ru/account.json | 5 +++- public/locales/ru/billing.json | 9 ++++---- public/locales/ru/cart.json | 6 ++++- public/locales/ru/common.json | 6 ++--- public/locales/ru/dashboard.json | 7 +++++- public/locales/ru/teams.json | 10 ++++---- 18 files changed, 117 insertions(+), 52 deletions(-) diff --git a/public/locales/en/account.json b/public/locales/en/account.json index e101985..a17036c 100644 --- a/public/locales/en/account.json +++ b/public/locales/en/account.json @@ -169,5 +169,8 @@ "updateAccountError": "Updating account details failed", "updateAccountPreferencesSuccess": "Account preferences updated", "updateAccountPreferencesError": "Updating account preferences failed", - "consents": "Consents" + "consents": "Consents", + "healthBenefitForm": { + "updateSuccess": "Health benefit updated" + } } diff --git a/public/locales/en/billing.json b/public/locales/en/billing.json index 88b2bbe..f8d12bc 100644 --- a/public/locales/en/billing.json +++ b/public/locales/en/billing.json @@ -121,7 +121,6 @@ "label": "Cart ({{ items }})" }, "pageTitle": "{{companyName}} budget", - "description": "Configure company budget..", "saveChanges": "Save changes", "healthBenefitForm": { "title": "Health benefit form", @@ -134,10 +133,10 @@ "monthly": "Monthly" }, "expensesOverview": { - "title": "Expenses overview 2025", - "monthly": "Expense per employee per month *", - "yearly": "Maximum expense per employee per year *", - "total": "Maximum expense per {{employeeCount}} employee(s) per year *", - "sum": "Total" + "title": "Health account budget overview 2025", + "employeeCount": "Health account users", + "managementFeeTotal": "Health account management fee {{managementFee}} per employee per month *", + "currentMonthUsageTotal": "Health account current month usage", + "total": "Health account budget total" } } diff --git a/public/locales/en/cart.json b/public/locales/en/cart.json index 53f83ef..ad6225a 100644 --- a/public/locales/en/cart.json +++ b/public/locales/en/cart.json @@ -57,7 +57,10 @@ "order": { "title": "Order", "promotionsTotal": "Promotions total", + "companyBenefitsTotal": "Company benefits total", "subtotal": "Subtotal", + "benefitsTotal": "Paid with benefits", + "montonioTotal": "Paid with Montonio", "total": "Total", "giftCard": "Gift card" }, @@ -72,7 +75,8 @@ "orderNumber": "Order number", "orderStatus": "Order status", "paymentStatus": "Payment status", - "discount": "Discount" + "discount": "Discount", + "paymentConfirmationLoading": "Payment confirmation..." }, "montonioCallback": { "title": "Montonio checkout", diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f2dd9df..0fd868e 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -75,10 +75,10 @@ "orderAnalysis": "Order analysis", "orderHealthAnalysis": "Order health check", "account": "Account", - "members": "Members", + "companyMembers": "Manage employees", "billing": "Billing", - "dashboard": "Dashboard", - "settings": "Settings", + "companyDashboard": "Dashboard", + "companySettings": "Settings", "profile": "Profile", "pickTime": "Pick time", "preferences": "Preferences", diff --git a/public/locales/en/dashboard.json b/public/locales/en/dashboard.json index c92f438..03079b6 100644 --- a/public/locales/en/dashboard.json +++ b/public/locales/en/dashboard.json @@ -17,9 +17,13 @@ "orderAnalysis": { "title": "Order analysis", "description": "Select an analysis to get started" + }, + "benefits": { + "title": "Your company benefits" } }, "recommendations": { - "title": "Medreport recommends" + "title": "Medreport recommends", + "validUntil": "Valid until {{date}}" } } diff --git a/public/locales/en/teams.json b/public/locales/en/teams.json index 7bc7f6e..f0a728b 100644 --- a/public/locales/en/teams.json +++ b/public/locales/en/teams.json @@ -1,6 +1,12 @@ { "home": { - "pageTitle": "Company Dashboard" + "pageTitle": "Company Dashboard", + "headerTitle": "{{companyName}} Health Dashboard", + "healthDetails": "Company Health Details", + "membersSettingsButtonTitle": "Manage Members", + "membersSettingsButtonDescription": "Add, edit, or remove members.", + "membersBillingButtonTitle": "Manage Billing", + "membersBillingButtonDescription": "Select how you want to distribute the budget between members." }, "settings": { "pageTitle": "Company Settings", @@ -18,6 +24,32 @@ "billing": { "pageTitle": "Company Billing" }, + "benefitStatistics": { + "budget": { + "title": "Company Health Account Balance", + "balance": "Budget Balance {{balance}}", + "volume": "Budget Volume" + }, + "data": { + "reservations": "{{value}} services", + "analysis": "Analyses", + "doctorsAndSpecialists": "Doctors and Specialists", + "researches": "Researches", + "analysisPackages": "Health Analysis Packages", + "analysisPackagesCount": "{{value}} service usage", + "totalSum": "Total Sum", + "eclinic": "E-Clinic" + } + }, + "healthDetails": { + "women": "Women", + "men": "Men", + "avgAge": "Average Age", + "bmi": "BMI", + "cholesterol": "Cholesterol", + "vitaminD": "Vitamin D", + "smokers": "Smokers" + }, "yourTeams": "Your Companies ({{teamsCount}})", "createTeam": "Create a Company", "creatingTeam": "Creating Company...", @@ -28,7 +60,7 @@ "youLabel": "You", "emailLabel": "Email", "roleLabel": "Role", - "primaryOwnerLabel": "Primary Admin", + "primaryOwnerLabel": "Manager", "joinedAtLabel": "Joined at", "invitedAtLabel": "Invited at", "inviteMembersPageSubheading": "Invite members to your Company", @@ -153,7 +185,7 @@ "signInWithDifferentAccountDescription": "If you wish to accept the invitation with a different account, please sign out and back in with the account you wish to use.", "acceptInvitationHeading": "Accept Invitation to join {{accountName}}", "acceptInvitationDescription": "You have been invited to join the company {{accountName}}. If you wish to accept the invitation, please click the button below.", - "continueAs": "Continue as {{email}}", + "continueAs": "Continue as {{fullName}}", "joinTeamAccount": "Join Company", "joiningTeam": "Joining company...", "leaveTeamInputLabel": "Please type LEAVE to confirm leaving the company.", @@ -161,5 +193,6 @@ "reservedNameError": "This name is reserved. Please choose a different one.", "specialCharactersError": "This name cannot contain special characters. Please choose a different one.", "personalCode": "Personal Code", + "teamOwnerPersonalCodeLabel": "Owner's Personal Code", "distributedBenefitsAmount": "Assigned benefits" } diff --git a/public/locales/et/account.json b/public/locales/et/account.json index bb89f86..4dee3e5 100644 --- a/public/locales/et/account.json +++ b/public/locales/et/account.json @@ -169,5 +169,8 @@ "updateAccountError": "Konto andmete uuendamine ebaõnnestus", "updateAccountPreferencesSuccess": "Konto eelistused uuendatud", "updateAccountPreferencesError": "Konto eelistused uuendamine ebaõnnestus", - "consents": "Nõusolekud" + "consents": "Nõusolekud", + "healthBenefitForm": { + "updateSuccess": "Tervisekonto andmed uuendatud" + } } diff --git a/public/locales/et/billing.json b/public/locales/et/billing.json index 14734c6..1abb884 100644 --- a/public/locales/et/billing.json +++ b/public/locales/et/billing.json @@ -121,11 +121,10 @@ "label": "Cart ({{ items }})" }, "pageTitle": "{{companyName}} eelarve", - "description": "Muuda ettevõtte eelarve seadistusi.", "saveChanges": "Salvesta muudatused", "healthBenefitForm": { "title": "Tervisetoetuse vorm", - "description": "Ettevõtte Tervisekassa toetus töötajale", + "description": "Ettevõtte tervisekonto seadistamine", "info": "* Hindadele lisanduvad riigipoolsed maksud" }, "occurrence": { @@ -134,10 +133,10 @@ "monthly": "Kord kuus" }, "expensesOverview": { - "title": "Kulude ülevaade 2025 aasta raames", - "monthly": "Kulu töötaja kohta kuus *", - "yearly": "Maksimaalne kulu inimese kohta kokku aastas *", - "total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *", - "sum": "Kokku" + "title": "Tervisekonto eelarve ülevaade 2025", + "employeeCount": "Tervisekonto kasutajate arv", + "managementFeeTotal": "Tervisekonto haldustasu {{managementFee}} kuus töötaja kohta*", + "currentMonthUsageTotal": "Tervisekonto jooksva kuu kasutus", + "total": "Tervisekonto eelarve maht kokku" } } diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json index 7f38792..06c4039 100644 --- a/public/locales/et/cart.json +++ b/public/locales/et/cart.json @@ -57,7 +57,10 @@ "order": { "title": "Tellimus", "promotionsTotal": "Soodustuse summa", + "companyBenefitsTotal": "Toetuse summa", "subtotal": "Vahesumma", + "benefitsTotal": "Tasutud tervisetoetusest", + "montonioTotal": "Tasutud Montonio'ga", "total": "Summa", "giftCard": "Kinkekaart" }, @@ -72,7 +75,8 @@ "orderNumber": "Tellimuse number", "orderStatus": "Tellimuse olek", "paymentStatus": "Makse olek", - "discount": "Soodus" + "discount": "Soodus", + "paymentConfirmationLoading": "Makse kinnitamine..." }, "montonioCallback": { "title": "Montonio makseprotsess", diff --git a/public/locales/et/common.json b/public/locales/et/common.json index f0d2c3d..cfaba55 100644 --- a/public/locales/et/common.json +++ b/public/locales/et/common.json @@ -75,10 +75,10 @@ "orderAnalysis": "Telli analüüs", "orderHealthAnalysis": "Telli terviseuuring", "account": "Konto", - "members": "Liikmed", - "billing": "Arveldamine", - "dashboard": "Ülevaade", - "settings": "Seaded", + "companyMembers": "Töötajate haldamine", + "billing": "Eelarve", + "companyDashboard": "Ülevaade", + "companySettings": "Seaded", "profile": "Profiil", "pickTime": "Vali aeg", "preferences": "Eelistused", diff --git a/public/locales/et/dashboard.json b/public/locales/et/dashboard.json index d18c230..1b518f0 100644 --- a/public/locales/et/dashboard.json +++ b/public/locales/et/dashboard.json @@ -17,9 +17,14 @@ "orderAnalysis": { "title": "Telli analüüs", "description": "Telli endale sobiv analüüs" + }, + "benefits": { + "title": "Sinu Medreport konto seis", + "validUntil": "Kehtiv kuni {{date}}" } }, "recommendations": { - "title": "Medreport soovitab teile" + "title": "Medreport soovitab teile", + "validUntil": "Kehtiv kuni {{date}}" } } diff --git a/public/locales/et/teams.json b/public/locales/et/teams.json index 74376f5..f506595 100644 --- a/public/locales/et/teams.json +++ b/public/locales/et/teams.json @@ -1,7 +1,7 @@ { "home": { "pageTitle": "Ettevõtte ülevaade", - "headerTitle": "{{companyName}} Tervisekassa kokkuvõte", + "headerTitle": "{{companyName}} tervise ülevaade", "healthDetails": "Ettevõtte terviseandmed", "membersSettingsButtonTitle": "Halda töötajaid", "membersSettingsButtonDescription": "Lisa, muuda või eemalda töötajaid.", @@ -28,16 +28,16 @@ "budget": { "title": "Ettevõtte Tervisekassa seis", "balance": "Eelarve jääk {{balance}}", - "volume": "Eelarve maht {{volume}}" + "volume": "Eelarve maht" }, "data": { "reservations": "{{value}} teenust", "analysis": "Analüüsid", "doctorsAndSpecialists": "Eriarstid ja spetsialistid", "researches": "Uuringud", - "healthResearchPlans": "Terviseuuringute paketid", - "serviceUsage": "{{value}} teenuse kasutust", - "serviceSum": "Teenuste summa", + "analysisPackages": "Terviseuuringute paketid", + "analysisPackagesCount": "{{value}} teenuse kasutust", + "totalSum": "Tellitud teenuste summa", "eclinic": "Digikliinik" } }, @@ -60,7 +60,7 @@ "youLabel": "Sina", "emailLabel": "E-post", "roleLabel": "Roll", - "primaryOwnerLabel": "Peaadministraator", + "primaryOwnerLabel": "Haldur", "joinedAtLabel": "Liitus", "invitedAtLabel": "Kutsutud", "inviteMembersPageSubheading": "Kutsu töötajaid oma ettevõttesse", @@ -185,7 +185,7 @@ "signInWithDifferentAccountDescription": "Kui soovid kutse vastu võtta teise kontoga, logi välja ja tagasi sisse soovitud kontoga.", "acceptInvitationHeading": "Võta kutse vastu, et liituda ettevõttega {{accountName}}", "acceptInvitationDescription": "Sind on kutsutud liituma ettevõttega {{accountName}}. Kui soovid kutse vastu võtta, vajuta allolevat nuppu.", - "continueAs": "Jätka kui {{email}}", + "continueAs": "Jätka kui {{fullName}}", "joinTeamAccount": "Liitu ettevõttega", "joiningTeam": "Ettevõttega liitumine...", "leaveTeamInputLabel": "Palun kirjuta LEAVE kinnituseks, et ettevõttest lahkuda.", diff --git a/public/locales/ru/account.json b/public/locales/ru/account.json index 9eca7c0..fe66124 100644 --- a/public/locales/ru/account.json +++ b/public/locales/ru/account.json @@ -169,5 +169,8 @@ "updateAccountError": "Не удалось обновить данные аккаунта", "updateAccountPreferencesSuccess": "Предпочтения аккаунта обновлены", "updateAccountPreferencesError": "Не удалось обновить предпочтения аккаунта", - "consents": "Согласия" + "consents": "Согласия", + "healthBenefitForm": { + "updateSuccess": "Данные о выгоде обновлены" + } } diff --git a/public/locales/ru/billing.json b/public/locales/ru/billing.json index 90cdbfe..399a75c 100644 --- a/public/locales/ru/billing.json +++ b/public/locales/ru/billing.json @@ -121,7 +121,6 @@ "label": "Корзина ({{ items }})" }, "pageTitle": "Бюджет {{companyName}}", - "description": "Измените настройки бюджета компании.", "saveChanges": "Сохранить изменения", "healthBenefitForm": { "title": "Форма здоровья", @@ -135,9 +134,9 @@ }, "expensesOverview": { "title": "Обзор расходов за 2025 год", - "monthly": "Расход на одного сотрудника в месяц *", - "yearly": "Максимальный расход на одного человека в год *", - "total": "Максимальный расход на {{employeeCount}} сотрудников в год *", - "sum": "Итого" + "employeeCount": "Сотрудники корпоративного фонда здоровья", + "managementFeeTotal": "Расходы на управление корпоративным фондом здоровья {{managementFee}} в месяц на одного сотрудника *", + "currentMonthUsageTotal": "Расходы на корпоративный фонд здоровья в текущем месяце", + "total": "Общая сумма расходов на корпоративный фонд здоровья" } } diff --git a/public/locales/ru/cart.json b/public/locales/ru/cart.json index fb1a4d5..56decf2 100644 --- a/public/locales/ru/cart.json +++ b/public/locales/ru/cart.json @@ -57,7 +57,10 @@ "order": { "title": "Заказ", "promotionsTotal": "Скидка", + "companyBenefitsTotal": "Скидка компании", "subtotal": "Промежуточный итог", + "benefitsTotal": "Оплачено за счет выгод", + "montonioTotal": "Оплачено за счет Montonio", "total": "Сумма", "giftCard": "Подарочная карта" }, @@ -72,7 +75,8 @@ "orderNumber": "Номер заказа", "orderStatus": "Статус заказа", "paymentStatus": "Статус оплаты", - "discount": "Скидка" + "discount": "Скидка", + "paymentConfirmationLoading": "Ожидание подтверждения оплаты..." }, "montonioCallback": { "title": "Процесс оплаты Montonio", diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index ca97132..510b4a1 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -75,10 +75,10 @@ "orderAnalysis": "Заказать анализ", "orderHealthAnalysis": "Заказать обследование", "account": "Аккаунт", - "members": "Участники", + "companyMembers": "Участники", "billing": "Оплата", - "dashboard": "Обзор", - "settings": "Настройки", + "companyDashboard": "Обзор", + "companySettings": "Настройки", "profile": "Профиль", "pickTime": "Выбрать время", "preferences": "Предпочтения", diff --git a/public/locales/ru/dashboard.json b/public/locales/ru/dashboard.json index 9c0a7d6..523e70c 100644 --- a/public/locales/ru/dashboard.json +++ b/public/locales/ru/dashboard.json @@ -17,9 +17,14 @@ "orderAnalysis": { "title": "Заказать анализ", "description": "Закажите подходящий для вас анализ" + }, + "benefits": { + "title": "Ваш счет Medreport", + "validUntil": "Действителен до {{date}}" } }, "recommendations": { - "title": "Medreport recommends" + "title": "Medreport рекомендует", + "validUntil": "Действителен до {{date}}" } } diff --git a/public/locales/ru/teams.json b/public/locales/ru/teams.json index 9f4125f..aa61b70 100644 --- a/public/locales/ru/teams.json +++ b/public/locales/ru/teams.json @@ -28,16 +28,16 @@ "budget": { "title": "Баланс Tervisekassa компании", "balance": "Остаток бюджета {{balance}}", - "volume": "Объем бюджета {{volume}}" + "volume": "Объем бюджета" }, "data": { "reservations": "{{value}} услуги", "analysis": "Анализы", "doctorsAndSpecialists": "Врачи и специалисты", "researches": "Исследования", - "healthResearchPlans": "Пакеты медицинских исследований", - "serviceUsage": "{{value}} использование услуг", - "serviceSum": "Сумма услуг", + "analysisPackages": "Пакеты медицинских исследований", + "analysisPackagesCount": "{{value}} использование услуг", + "totalSum": "Сумма услуг", "eclinic": "Дигиклиника" } }, @@ -185,7 +185,7 @@ "signInWithDifferentAccountDescription": "Если вы хотите принять приглашение с другим аккаунтом, выйдите из системы и войдите с нужным аккаунтом.", "acceptInvitationHeading": "Принять приглашение для присоединения к {{accountName}}", "acceptInvitationDescription": "Вас пригласили присоединиться к компании {{accountName}}. Чтобы принять приглашение, нажмите кнопку ниже.", - "continueAs": "Продолжить как {{email}}", + "continueAs": "Продолжить как {{fullName}}", "joinTeamAccount": "Присоединиться к компании", "joiningTeam": "Присоединение к компании...", "leaveTeamInputLabel": "Пожалуйста, введите LEAVE для подтверждения выхода из компании.", From a68f7c7ab5fda779aaccbc4379a8379f016c5981 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 15:46:26 +0300 Subject: [PATCH 23/36] feat(MED-97): save `benefit_distribution_schedule_id` to `account_balance_entries` --- packages/supabase/src/database.types.ts | 9 + ...250926135946_include_benefit_config_id.sql | 219 ++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 supabase/migrations/20250926135946_include_benefit_config_id.sql diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 58ffac2..4653429 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -2066,6 +2066,15 @@ export type Database = { user_id: string }[] } + get_benefits_usages_for_company_members: { + Args: { + p_account_id: string + } + Returns: { + personal_account_id: string + benefit_amount: number + } + } get_config: { Args: Record Returns: Json diff --git a/supabase/migrations/20250926135946_include_benefit_config_id.sql b/supabase/migrations/20250926135946_include_benefit_config_id.sql new file mode 100644 index 0000000..cd6b448 --- /dev/null +++ b/supabase/migrations/20250926135946_include_benefit_config_id.sql @@ -0,0 +1,219 @@ +ALTER TABLE medreport.account_balance_entries ADD COLUMN benefit_distribution_schedule_id uuid; + +-- Also setting `benefit_distribution_schedule_id` value now +drop function if exists medreport.distribute_health_benefits(uuid, numeric, text); +create or replace function medreport.distribute_health_benefits( + p_benefit_distribution_schedule_id uuid +) +returns void +language plpgsql +security definer +as $$ +declare + member_record record; + expires_date timestamp with time zone; + v_company_id uuid; + v_benefit_amount numeric; +begin + -- Expires on first day of next year. + expires_date := date_trunc('year', now() + interval '1 year'); + + -- Get company_id and benefit_amount from benefit_distribution_schedule + select company_id, benefit_amount into v_company_id, v_benefit_amount + from medreport.benefit_distribution_schedule + where id = p_benefit_distribution_schedule_id; + + -- Get all personal accounts that are members of this company + for member_record in + select distinct a.id as personal_account_id + from medreport.accounts a + join medreport.accounts_memberships am on a.id = am.user_id + where am.account_id = v_company_id + and a.is_personal_account = true + loop + -- Check if there is already a balance entry for this personal account from the same company in same month + if exists ( + select 1 + from medreport.account_balance_entries + where entry_type = 'benefit' + and account_id = member_record.personal_account_id + and source_company_id = v_company_id + and date_trunc('month', created_at) = date_trunc('month', now()) + ) then + continue; + end if; + + -- Insert balance entry for each personal account + insert into medreport.account_balance_entries ( + account_id, + amount, + entry_type, + description, + source_company_id, + created_by, + expires_at, + benefit_distribution_schedule_id + ) values ( + member_record.personal_account_id, + v_benefit_amount, + 'benefit', + 'Health benefit from company', + v_company_id, + auth.uid(), + expires_date, + p_benefit_distribution_schedule_id + ); + end loop; +end; +$$; + +grant execute on function medreport.distribute_health_benefits(uuid) to authenticated, service_role; + +create or replace function medreport.process_periodic_benefit_distributions() +returns void +language plpgsql +as $$ +declare + schedule_record record; + next_distribution_date timestamp with time zone; +begin + -- Get all active schedules that are due for distribution + for schedule_record in + select * + from medreport.benefit_distribution_schedule + where is_active = true + and next_distribution_at <= now() + loop + -- Distribute benefits + perform medreport.distribute_health_benefits( + schedule_record.id + ); + + -- Calculate next distribution date + next_distribution_date := medreport.calculate_next_distribution_date( + schedule_record.benefit_occurrence, + now() + ); + + -- Update the schedule + update medreport.benefit_distribution_schedule + set + last_distributed_at = now(), + next_distribution_at = next_distribution_date, + updated_at = now() + where id = schedule_record.id; + end loop; +end; +$$; + +DROP FUNCTION IF EXISTS medreport.upsert_benefit_distribution_schedule(uuid,numeric,text); +create or replace function medreport.upsert_benefit_distribution_schedule( + p_company_id uuid, + p_benefit_amount numeric, + p_benefit_occurrence text +) +-- Return schedule row id +returns uuid +language plpgsql +as $$ +declare + next_distribution_date timestamp with time zone; + record_id uuid; +begin + -- Calculate next distribution date + next_distribution_date := medreport.calculate_next_distribution_date(p_benefit_occurrence); + + -- Check if there's an existing record for this company + select id into record_id + from medreport.benefit_distribution_schedule + where company_id = p_company_id + limit 1; + + if record_id is not null then + -- Update existing record + update medreport.benefit_distribution_schedule + set + benefit_amount = p_benefit_amount, + benefit_occurrence = p_benefit_occurrence, + next_distribution_at = next_distribution_date, + is_active = true, + updated_at = now() + where id = record_id; + else + record_id := gen_random_uuid(); + + -- Insert new record + insert into medreport.benefit_distribution_schedule ( + id, + company_id, + benefit_amount, + benefit_occurrence, + next_distribution_at + ) values ( + record_id, + p_company_id, + p_benefit_amount, + p_benefit_occurrence, + next_distribution_date + ); + end if; + + return record_id; +end; +$$; + +grant execute on function medreport.upsert_benefit_distribution_schedule(uuid, numeric, text) to authenticated, service_role; + +create or replace function medreport.trigger_distribute_benefits() +returns trigger +language plpgsql +security definer +as $$ +declare + v_benefit_distribution_schedule_id uuid; +begin + -- Only distribute if benefit_amount is set and greater than 0 + if new.benefit_amount is not null and new.benefit_amount > 0 then + -- Create or update the distribution schedule for future distributions + v_benefit_distribution_schedule_id := medreport.upsert_benefit_distribution_schedule( + new.account_id, + new.benefit_amount, + coalesce(new.benefit_occurance, 'yearly') + ); + + -- Distribute benefits to all company members immediately + perform medreport.distribute_health_benefits( + v_benefit_distribution_schedule_id + ); + else + -- If benefit_amount is 0 or null, deactivate the schedule + update medreport.benefit_distribution_schedule + set is_active = false, updated_at = now() + where company_id = new.account_id; + end if; + + return new; +end; +$$; + + +CREATE OR REPLACE FUNCTION medreport.get_benefits_usages_for_company_members(p_account_id uuid) +returns table ( + personal_account_id uuid, + benefit_amount numeric +) +language plpgsql +as $$ +begin + return query + select + abe.account_id as personal_account_id, + sum(abe.amount) as benefit_amount + from medreport.account_balance_entries abe + where abe.source_company_id = p_account_id + and abe.entry_type = 'benefit' + group by abe.account_id; +end; +$$; + +grant execute on function medreport.get_benefits_usages_for_company_members(uuid) to authenticated, service_role; From 27689dbbffc649b43cbea9f6da9b454910d87fb6 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 15:46:52 +0300 Subject: [PATCH 24/36] feat(MED-97): hide company logo upload if it doesn't work --- .../team-account-settings-container.tsx | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/features/team-accounts/src/components/settings/team-account-settings-container.tsx b/packages/features/team-accounts/src/components/settings/team-account-settings-container.tsx index c49ae1e..cfa629c 100644 --- a/packages/features/team-accounts/src/components/settings/team-account-settings-container.tsx +++ b/packages/features/team-accounts/src/components/settings/team-account-settings-container.tsx @@ -13,6 +13,8 @@ import { TeamAccountDangerZone } from './team-account-danger-zone'; import { UpdateTeamAccountImage } from './update-team-account-image-container'; import { UpdateTeamAccountNameForm } from './update-team-account-name-form'; +const SHOW_TEAM_LOGO = false as boolean; + export function TeamAccountSettingsContainer(props: { account: { name: string; @@ -32,21 +34,23 @@ export function TeamAccountSettingsContainer(props: { }) { return (
- - - - - + {SHOW_TEAM_LOGO && ( + + + + + - - - - + + + + - - - - + + + + + )} From c99beea46a3499277cd0265da2143beb4a683239 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Fri, 26 Sep 2025 16:18:50 +0300 Subject: [PATCH 25/36] add openai validation --- app/home/(user)/(dashboard)/page.tsx | 22 +++++++++---------- .../_lib/server/is-valid-open-ai-env.ts | 13 +++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 app/home/(user)/_lib/server/is-valid-open-ai-env.ts diff --git a/app/home/(user)/(dashboard)/page.tsx b/app/home/(user)/(dashboard)/page.tsx index 22a671c..cdb7bcf 100644 --- a/app/home/(user)/(dashboard)/page.tsx +++ b/app/home/(user)/(dashboard)/page.tsx @@ -16,6 +16,7 @@ import Dashboard from '../_components/dashboard'; import DashboardCards from '../_components/dashboard-cards'; import Recommendations from '../_components/recommendations'; import RecommendationsSkeleton from '../_components/recommendations-skeleton'; +import { isValidOpenAiEnv } from '../_lib/server/is-valid-open-ai-env'; import { loadCurrentUserAccount } from '../_lib/server/load-user-account'; export const generateMetadata = async () => { @@ -52,17 +53,16 @@ async function UserHomePage() { /> - {process.env.OPENAI_API_KEY && - process.env.PROMPT_ID_ANALYSIS_RECOMMENDATIONS && ( - <> -

- -

- }> - - - - )} + {(await isValidOpenAiEnv()) && ( + <> +

+ +

+ }> + + + + )}
); diff --git a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts new file mode 100644 index 0000000..d6dbfae --- /dev/null +++ b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts @@ -0,0 +1,13 @@ +import OpenAI from 'openai'; + +export const isValidOpenAiEnv = async () => { + const client = new OpenAI(); + + try { + await client.models.list(); + return true; + } catch (e) { + console.log('No openAI env'); + return false; + } +}; From 2d9e6f8df3dcca866557b46e417b736de2f6de00 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 16:34:10 +0300 Subject: [PATCH 26/36] feat(MED-97): display accounts count, usage total --- .../team-account-benefit-statistics.tsx | 50 ++++++++++++------- .../_components/team-account-statistics.tsx | 7 ++- .../load-team-account-benefit-statistics.ts | 2 +- app/home/[account]/page.tsx | 6 +++ public/locales/en/teams.json | 6 ++- public/locales/et/teams.json | 10 ++-- public/locales/ru/teams.json | 6 ++- 7 files changed, 58 insertions(+), 29 deletions(-) diff --git a/app/home/[account]/_components/team-account-benefit-statistics.tsx b/app/home/[account]/_components/team-account-benefit-statistics.tsx index 035ff9d..81f232d 100644 --- a/app/home/[account]/_components/team-account-benefit-statistics.tsx +++ b/app/home/[account]/_components/team-account-benefit-statistics.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { formatCurrency } from '@/packages/shared/src/utils'; -import { PiggyBankIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Card, CardTitle } from '@kit/ui/card'; import { cn } from '@kit/ui/lib/utils'; import { Trans } from '@kit/ui/trans'; +import { TeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; import { AccountBenefitStatistics } from '../_lib/server/load-team-account-benefit-statistics'; const StatisticsCard = ({ children }: { children: React.ReactNode }) => { @@ -38,8 +38,10 @@ const StatisticsValue = ({ children }: { children: React.ReactNode }) => { const TeamAccountBenefitStatistics = ({ accountBenefitStatistics, + expensesOverview, }: { accountBenefitStatistics: AccountBenefitStatistics; + expensesOverview: TeamAccountBenefitExpensesOverview; }) => { const { i18n: { language }, @@ -47,25 +49,16 @@ const TeamAccountBenefitStatistics = ({ return (
- -
-
- -
- - +
+ + + - {formatCurrency({ - value: accountBenefitStatistics.periodTotal, - locale: language, - currencyCode: 'EUR', - })} + {accountBenefitStatistics.companyAccountsCount} -
- + -
@@ -79,11 +72,30 @@ const TeamAccountBenefitStatistics = ({ + + + + + + {formatCurrency({ + value: expensesOverview.currentMonthUsageTotal, + locale: language, + currencyCode: 'EUR', + })} + + + - {accountBenefitStatistics.orders.analysesSum} € + + {formatCurrency({ + value: accountBenefitStatistics.orders.analysesSum, + locale: language, + currencyCode: 'EUR', + })} + diff --git a/app/home/[account]/_components/team-account-statistics.tsx b/app/home/[account]/_components/team-account-statistics.tsx index bae53a4..17a7466 100644 --- a/app/home/[account]/_components/team-account-statistics.tsx +++ b/app/home/[account]/_components/team-account-statistics.tsx @@ -20,6 +20,7 @@ import { AccountBenefitStatistics } from '../_lib/server/load-team-account-benef import TeamAccountBenefitStatistics from './team-account-benefit-statistics'; import TeamAccountHealthDetails from './team-account-health-details'; import type { Account, AccountParams, BmiThresholds } from '@/packages/features/accounts/src/types/accounts'; +import { TeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; export interface TeamAccountStatisticsProps { teamAccount: Account; @@ -27,6 +28,7 @@ export interface TeamAccountStatisticsProps { bmiThresholds: Omit[]; members: Database['medreport']['Functions']['get_account_members']['Returns']; accountBenefitStatistics: AccountBenefitStatistics; + expensesOverview: TeamAccountBenefitExpensesOverview; } export default function TeamAccountStatistics({ @@ -35,6 +37,7 @@ export default function TeamAccountStatistics({ bmiThresholds, members, accountBenefitStatistics, + expensesOverview, }: TeamAccountStatisticsProps) { const currentDate = new Date(); const [date, setDate] = useState({ @@ -50,7 +53,7 @@ export default function TeamAccountStatistics({ return ( <> -
+

- +
diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts index 4de4be5..f61f350 100644 --- a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts +++ b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts @@ -29,7 +29,7 @@ export const loadCompanyPersonalAccountsBalanceEntries = async ({ const { count, data: accountMemberships } = await supabase .schema('medreport') .from('accounts_memberships') - .select('user_id') + .select('user_id', { count: 'exact' }) .eq('account_id', accountId) .throwOnError(); diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx index 5cff5d3..e8eb7ba 100644 --- a/app/home/[account]/page.tsx +++ b/app/home/[account]/page.tsx @@ -18,6 +18,7 @@ import { import { Dashboard } from './_components/dashboard'; import { loadAccountBenefitStatistics } from './_lib/server/load-team-account-benefit-statistics'; +import { loadTeamAccountBenefitExpensesOverview } from './_lib/server/load-team-account-benefit-expenses-overview'; interface TeamAccountHomePageProps { params: Promise<{ account: string }>; @@ -41,6 +42,10 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { const { memberParams, members } = use(teamAccountsApi.getMembers(account)); const bmiThresholds = use(userAnalysesApi.fetchBmiThresholds()); const accountBenefitStatistics = use(loadAccountBenefitStatistics(teamAccount.id)); + const expensesOverview = use(loadTeamAccountBenefitExpensesOverview({ + companyId: teamAccount.id, + employeeCount: members.length, + })); use( createPageViewLog({ @@ -57,6 +62,7 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { bmiThresholds={bmiThresholds} members={members} accountBenefitStatistics={accountBenefitStatistics} + expensesOverview={expensesOverview} /> ); diff --git a/public/locales/en/teams.json b/public/locales/en/teams.json index f0a728b..8af264f 100644 --- a/public/locales/en/teams.json +++ b/public/locales/en/teams.json @@ -28,7 +28,8 @@ "budget": { "title": "Company Health Account Balance", "balance": "Budget Balance {{balance}}", - "volume": "Budget Volume" + "volume": "Budget Volume", + "membersCount": "Members Count" }, "data": { "reservations": "{{value}} services", @@ -38,7 +39,8 @@ "analysisPackages": "Health Analysis Packages", "analysisPackagesCount": "{{value}} service usage", "totalSum": "Total Sum", - "eclinic": "E-Clinic" + "eclinic": "E-Clinic", + "currentMonthUsageTotal": "Current Month Usage" } }, "healthDetails": { diff --git a/public/locales/et/teams.json b/public/locales/et/teams.json index f506595..884923d 100644 --- a/public/locales/et/teams.json +++ b/public/locales/et/teams.json @@ -28,17 +28,19 @@ "budget": { "title": "Ettevõtte Tervisekassa seis", "balance": "Eelarve jääk {{balance}}", - "volume": "Eelarve maht" + "volume": "Eelarve maht", + "membersCount": "Töötajate arv" }, "data": { - "reservations": "{{value}} teenust", + "reservations": "{{value}} tellimus(t)", "analysis": "Analüüsid", "doctorsAndSpecialists": "Eriarstid ja spetsialistid", "researches": "Uuringud", "analysisPackages": "Terviseuuringute paketid", - "analysisPackagesCount": "{{value}} teenuse kasutust", + "analysisPackagesCount": "{{value}} tellimus(t)", "totalSum": "Tellitud teenuste summa", - "eclinic": "Digikliinik" + "eclinic": "Digikliinik", + "currentMonthUsageTotal": "Kasutatud eelarve" } }, "healthDetails": { diff --git a/public/locales/ru/teams.json b/public/locales/ru/teams.json index aa61b70..74111f1 100644 --- a/public/locales/ru/teams.json +++ b/public/locales/ru/teams.json @@ -28,7 +28,8 @@ "budget": { "title": "Баланс Tervisekassa компании", "balance": "Остаток бюджета {{balance}}", - "volume": "Объем бюджета" + "volume": "Объем бюджета", + "membersCount": "Количество сотрудников" }, "data": { "reservations": "{{value}} услуги", @@ -38,7 +39,8 @@ "analysisPackages": "Пакеты медицинских исследований", "analysisPackagesCount": "{{value}} использование услуг", "totalSum": "Сумма услуг", - "eclinic": "Дигиклиника" + "eclinic": "Дигиклиника", + "currentMonthUsageTotal": "Текущее использование бюджета" } }, "healthDetails": { From 6bdf5fbf12e4aa6c7e4ff58cabf3ca16f7b3350a Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 17:01:35 +0300 Subject: [PATCH 27/36] feat(MED-97): fix duplicate element --- app/home/(user)/_components/cart/index.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/home/(user)/_components/cart/index.tsx b/app/home/(user)/_components/cart/index.tsx index c289b7b..5f884a5 100644 --- a/app/home/(user)/_components/cart/index.tsx +++ b/app/home/(user)/_components/cart/index.tsx @@ -211,10 +211,6 @@ export default function Cart({ cart={{ ...cart }} synlabAnalyses={synlabAnalyses} /> - )} From f794a66147b0e90efcc09443e79d4516e8091d93 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 17:01:56 +0300 Subject: [PATCH 28/36] feat(MED-97): fix `new OpenAI()` throws error when key is missing in env --- app/home/(user)/_lib/server/is-valid-open-ai-env.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts index d6dbfae..183a8f9 100644 --- a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts +++ b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts @@ -1,9 +1,8 @@ import OpenAI from 'openai'; export const isValidOpenAiEnv = async () => { - const client = new OpenAI(); - try { + const client = new OpenAI(); await client.models.list(); return true; } catch (e) { From b674640bd80dea78b074cbaa7094ad30b357a289 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Fri, 26 Sep 2025 17:20:50 +0300 Subject: [PATCH 29/36] refactor time slots --- .../_components/booking/booking-container.tsx | 11 +- .../booking/booking-pagination.tsx | 113 ++++++++++++++ .../(user)/_components/booking/time-slots.tsx | 141 ++++-------------- lib/services/medusaCart.service.ts | 11 +- packages/ui/src/shadcn/skeleton.tsx | 16 +- 5 files changed, 173 insertions(+), 119 deletions(-) create mode 100644 app/home/(user)/_components/booking/booking-pagination.tsx diff --git a/app/home/(user)/_components/booking/booking-container.tsx b/app/home/(user)/_components/booking/booking-container.tsx index 9d3b6e8..67a9400 100644 --- a/app/home/(user)/_components/booking/booking-container.tsx +++ b/app/home/(user)/_components/booking/booking-container.tsx @@ -32,7 +32,16 @@ const BookingContainer = ({
- + { + if (product.metadata?.serviceIds) { + return Array.isArray( + JSON.parse(product.metadata.serviceIds as string), + ); + } + return false; + })} + />
diff --git a/app/home/(user)/_components/booking/booking-pagination.tsx b/app/home/(user)/_components/booking/booking-pagination.tsx new file mode 100644 index 0000000..f0c4631 --- /dev/null +++ b/app/home/(user)/_components/booking/booking-pagination.tsx @@ -0,0 +1,113 @@ +import React from 'react'; + +import { useTranslation } from 'react-i18next'; + +import { Trans } from '@kit/ui/makerkit/trans'; +import { cn } from '@kit/ui/shadcn'; +import { Button } from '@kit/ui/shadcn/button'; + +const BookingPagination = ({ + totalPages, + setCurrentPage, + currentPage, +}: { + totalPages: number; + setCurrentPage: (page: number) => void; + currentPage: number; +}) => { + const { t } = useTranslation(); + + const generatePageNumbers = () => { + const pages = []; + const maxVisiblePages = 5; + + if (totalPages <= maxVisiblePages) { + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + if (currentPage <= 3) { + for (let i = 1; i <= 4; i++) { + pages.push(i); + } + pages.push('...'); + pages.push(totalPages); + } else if (currentPage >= totalPages - 2) { + pages.push(1); + pages.push('...'); + for (let i = totalPages - 3; i <= totalPages; i++) { + pages.push(i); + } + } else { + pages.push(1); + pages.push('...'); + for (let i = currentPage - 1; i <= currentPage + 1; i++) { + pages.push(i); + } + pages.push('...'); + pages.push(totalPages); + } + } + + return pages; + }; + + if (totalPages === 0) { + return ( +
+

{t('booking:noResults')}

+
+ ); + } + + return ( + totalPages > 1 && ( +
+
+ {t('common:pageOfPages', { + page: currentPage, + total: totalPages, + })} +
+ +
+ + + {generatePageNumbers().map((page, index) => ( + + ))} + + +
+
+ ) + ); +}; + +export default BookingPagination; diff --git a/app/home/(user)/_components/booking/time-slots.tsx b/app/home/(user)/_components/booking/time-slots.tsx index 220a79f..a88815c 100644 --- a/app/home/(user)/_components/booking/time-slots.tsx +++ b/app/home/(user)/_components/booking/time-slots.tsx @@ -11,6 +11,7 @@ import { pathsConfig } from '@kit/shared/config'; import { formatDateAndTime } from '@kit/shared/utils'; import { Button } from '@kit/ui/shadcn/button'; import { Card } from '@kit/ui/shadcn/card'; +import { Skeleton } from '@kit/ui/shadcn/skeleton'; import { toast } from '@kit/ui/sonner'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; @@ -19,6 +20,7 @@ import { updateReservationTime } from '~/lib/services/reservation.service'; import { createInitialReservationAction } from '../../_lib/server/actions'; import { EnrichedCartItem } from '../cart/types'; +import BookingPagination from './booking-pagination'; import { ServiceProvider, TimeSlot } from './booking.context'; import { useBooking } from './booking.provider'; @@ -68,57 +70,16 @@ const TimeSlots = ({ }) ?? [], 'StartTime', 'asc', - ), + ).filter(({ StartTime }) => isSameDay(StartTime, selectedDate)), [booking.timeSlots, selectedDate], ); - const totalPages = Math.ceil(filteredBookings.length / PAGE_SIZE); - const paginatedBookings = useMemo(() => { const startIndex = (currentPage - 1) * PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE; return filteredBookings.slice(startIndex, endIndex); }, [filteredBookings, currentPage, PAGE_SIZE]); - const handlePageChange = (page: number) => { - setCurrentPage(page); - }; - - const generatePageNumbers = () => { - const pages = []; - const maxVisiblePages = 5; - - if (totalPages <= maxVisiblePages) { - for (let i = 1; i <= totalPages; i++) { - pages.push(i); - } - } else { - if (currentPage <= 3) { - for (let i = 1; i <= 4; i++) { - pages.push(i); - } - pages.push('...'); - pages.push(totalPages); - } else if (currentPage >= totalPages - 2) { - pages.push(1); - pages.push('...'); - for (let i = totalPages - 3; i <= totalPages; i++) { - pages.push(i); - } - } else { - pages.push(1); - pages.push('...'); - for (let i = currentPage - 1; i <= currentPage + 1; i++) { - pages.push(i); - } - pages.push('...'); - pages.push(totalPages); - } - } - - return pages; - }; - if (!booking?.timeSlots?.length) { return null; } @@ -143,12 +104,17 @@ const TimeSlots = ({ timeSlot.StartTime, booking.selectedLocationId ? booking.selectedLocationId : null, comments, - ).then(() => { - if (onComplete) { - onComplete(); - } - router.push(pathsConfig.app.cart); - }); + ) + .then(() => { + if (onComplete) { + onComplete(); + } + router.push(pathsConfig.app.cart); + }) + .catch((error) => { + console.error('Booking error: ', error); + throw error; + }); toast.promise(() => bookTimePromise, { success: , @@ -199,12 +165,15 @@ const TimeSlots = ({ return handleBookTime(timeSlot); }; - + console.log('paginatedBookings', booking.isLoadingTimeSlots); return ( -
+
{paginatedBookings.map((timeSlot, index) => { - const isEHIF = timeSlot.HKServiceID > 0; + const isHaigeKassa = timeSlot.HKServiceID > 0; const serviceProviderTitle = getServiceProviderTitle( currentLocale, timeSlot.serviceProvider, @@ -212,6 +181,7 @@ const TimeSlots = ({ const price = booking.selectedService?.variants?.[0]?.calculated_price ?.calculated_amount ?? cartItem?.unit_price; + return (
@@ -230,12 +200,14 @@ const TimeSlots = ({
{serviceProviderTitle && ( {serviceProviderTitle} )} - {isEHIF && {t('booking:ehifBooking')}} + {isHaigeKassa && {t('booking:ehifBooking')}}
{timeSlot.location?.address}
@@ -254,63 +226,14 @@ const TimeSlots = ({ ); })} - - {!paginatedBookings.length && ( -
-

{t('booking:noResults')}

-
- )}
- {totalPages > 1 && ( -
-
- {t('common:pageOfPages', { - page: currentPage, - total: totalPages, - })} -
- -
- - - {generatePageNumbers().map((page, index) => ( - - ))} - - -
-
- )} -

+ + ); }; diff --git a/lib/services/medusaCart.service.ts b/lib/services/medusaCart.service.ts index 87efe47..a94b5c8 100644 --- a/lib/services/medusaCart.service.ts +++ b/lib/services/medusaCart.service.ts @@ -31,8 +31,11 @@ const env = () => .min(1), }) .parse({ - medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!, - siteUrl: process.env.NEXT_PUBLIC_SITE_URL!, + // Use for local testing + medusaBackendPublicUrl: 'http://webhook.site:3000', + siteUrl: 'http://webhook.site:3000', + // medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!, + // siteUrl: process.env.NEXT_PUBLIC_SITE_URL!, }); export async function handleAddToCart({ @@ -42,6 +45,10 @@ export async function handleAddToCart({ selectedVariant: Pick; countryCode: string; }) { + try { + } catch (e) { + console.error('medusa card error: ', e); + } const { account } = await loadCurrentUserAccount(); if (!account) { throw new Error('Account not found'); diff --git a/packages/ui/src/shadcn/skeleton.tsx b/packages/ui/src/shadcn/skeleton.tsx index 5b0ac1e..2f3ba22 100644 --- a/packages/ui/src/shadcn/skeleton.tsx +++ b/packages/ui/src/shadcn/skeleton.tsx @@ -3,21 +3,23 @@ import { cn } from '../lib/utils'; function Skeleton({ className, children, + isLoading = true, ...props -}: React.HTMLAttributes) { +}: React.HTMLAttributes & { isLoading?: boolean }) { return (
-
+
{children ?? }
- -
+ {isLoading && ( +
+ )}
); } From e4fcafa57c6cd9421a0448f10f1b056ff7033a70 Mon Sep 17 00:00:00 2001 From: Karli Date: Fri, 26 Sep 2025 17:23:23 +0300 Subject: [PATCH 30/36] feat(MED-97): add dev key for medusa benefits payment --- .env.development | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.development b/.env.development index 2cc0b56..4fdb0cc 100644 --- a/.env.development +++ b/.env.development @@ -43,6 +43,7 @@ MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK=true #MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK=false # MEDUSA +COMPANY_BENEFITS_PAYMENT_SECRET_KEY=NzcwMzE2NmEtOThiMS0xMWYwLWI4NjYtMDMwZDQzMjFhMjExCg== MEDUSA_BACKEND_URL=http://localhost:9000 MEDUSA_BACKEND_PUBLIC_URL=http://localhost:9000 From 02645aa59887d648969d0514fffe1ea93558bf57 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Mon, 29 Sep 2025 11:11:33 +0300 Subject: [PATCH 31/36] include credentials --- packages/features/medusa-storefront/src/lib/data/orders.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/features/medusa-storefront/src/lib/data/orders.ts b/packages/features/medusa-storefront/src/lib/data/orders.ts index 8a655cb..464a747 100644 --- a/packages/features/medusa-storefront/src/lib/data/orders.ts +++ b/packages/features/medusa-storefront/src/lib/data/orders.ts @@ -25,6 +25,7 @@ export const retrieveOrder = async (id: string, allowCache = true) => { headers, next, ...(allowCache ? { cache: 'force-cache' } : {}), + credentials: 'include', }) .then(({ order }) => order) .catch((err) => medusaError(err)); @@ -55,6 +56,7 @@ export const listOrders = async ( }, headers, next, + credentials: 'include', }) .then(({ orders }) => orders) .catch((err) => medusaError(err)); From 1e19e0ab8ab08d1749563bc0d609a99b1ce41a0e Mon Sep 17 00:00:00 2001 From: Karli Date: Mon, 29 Sep 2025 23:09:28 +0300 Subject: [PATCH 32/36] feat: delete analysis responses in medipost in test/prod --- .env.development | 1 + lib/services/medipost/medipostPrivateMessage.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.env.development b/.env.development index 4fdb0cc..ac3067a 100644 --- a/.env.development +++ b/.env.development @@ -34,6 +34,7 @@ MEDIPOST_PASSWORD=SRB48HZMV MEDIPOST_RECIPIENT=trvurgtst MEDIPOST_MESSAGE_SENDER=trvurgtst MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK=true +MEDIPOST_ENABLE_DELETE_RESPONSE_PRIVATE_MESSAGE_ON_READ=false #MEDIPOST_URL=https://medipost2.medisoft.ee:8443/Medipost/MedipostServlet #MEDIPOST_USER=medreport diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index 57380a4..9027079 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -43,7 +43,7 @@ const USER = process.env.MEDIPOST_USER!; const PASSWORD = process.env.MEDIPOST_PASSWORD!; const RECIPIENT = process.env.MEDIPOST_RECIPIENT!; -const IS_ENABLED_DELETE_PRIVATE_MESSAGE = false as boolean; +const IS_ENABLED_DELETE_PRIVATE_MESSAGE = process.env.MEDIPOST_ENABLE_DELETE_RESPONSE_PRIVATE_MESSAGE_ON_READ === 'true'; export async function getLatestPrivateMessageListItem({ excludedMessageIds, From bf10741786b033fa54fb319bff90f329d5615d18 Mon Sep 17 00:00:00 2001 From: Karli Date: Tue, 30 Sep 2025 14:40:57 +0300 Subject: [PATCH 33/36] feat: fix incorrect component imports --- .../(user)/settings/_components/account-preferences-form.tsx | 2 +- app/home/(user)/settings/_components/account-settings-form.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/home/(user)/settings/_components/account-preferences-form.tsx b/app/home/(user)/settings/_components/account-preferences-form.tsx index 54aa472..7fbd6f5 100644 --- a/app/home/(user)/settings/_components/account-preferences-form.tsx +++ b/app/home/(user)/settings/_components/account-preferences-form.tsx @@ -2,8 +2,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; -import { Trans } from 'react-i18next'; +import { Trans } from '@kit/ui/trans'; import { useRevalidatePersonalAccountDataQuery } from '@kit/accounts/hooks/use-personal-account-data'; import type { AccountWithParams } from '@kit/accounts/types/accounts'; import { Button } from '@kit/ui/button'; diff --git a/app/home/(user)/settings/_components/account-settings-form.tsx b/app/home/(user)/settings/_components/account-settings-form.tsx index 2aaa91e..0ab1bdc 100644 --- a/app/home/(user)/settings/_components/account-settings-form.tsx +++ b/app/home/(user)/settings/_components/account-settings-form.tsx @@ -2,8 +2,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; -import { Trans } from 'react-i18next'; +import { Trans } from '@kit/ui/trans'; import { useRevalidatePersonalAccountDataQuery } from '@kit/accounts/hooks/use-personal-account-data'; import type { AccountWithParams } from '@kit/accounts/types/accounts'; import { Button } from '@kit/ui/button'; From 2868875044da9ab9e0c4774e11c7cf5e6334dedf Mon Sep 17 00:00:00 2001 From: Karli Date: Tue, 30 Sep 2025 14:44:28 +0300 Subject: [PATCH 34/36] feat: fix % should be rounded, not 33.333... --- .../[account]/_lib/server/load-team-account-health-details.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/home/[account]/_lib/server/load-team-account-health-details.ts b/app/home/[account]/_lib/server/load-team-account-health-details.ts index 9568dda..094072a 100644 --- a/app/home/[account]/_lib/server/load-team-account-health-details.ts +++ b/app/home/[account]/_lib/server/load-team-account-health-details.ts @@ -62,13 +62,13 @@ export const getAccountHealthDetailsFields = ( return [ { title: 'teams:healthDetails.women', - value: `${femalePercentage}% (${numberOfFemaleMembers})`, + value: `${femalePercentage.toFixed(0)}% (${numberOfFemaleMembers})`, Icon: User, iconBg: 'bg-success', }, { title: 'teams:healthDetails.men', - value: `${malePercentage}% (${numberOfMaleMembers})`, + value: `${malePercentage.toFixed(0)}% (${numberOfMaleMembers})`, Icon: User, iconBg: 'bg-success', }, From f477bfaa135708e0c6ebfa0f7d37e18713d153df Mon Sep 17 00:00:00 2001 From: Karli Date: Tue, 30 Sep 2025 15:52:50 +0300 Subject: [PATCH 35/36] feat: delete partial analysis responses in medipost after sync --- lib/services/medipost/medipostPrivateMessage.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index 9027079..0ff60b2 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -435,6 +435,9 @@ export async function readPrivateMessageResponse({ medusaOrderId, orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', }); + if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) { + await deletePrivateMessage(privateMessageId); + } hasAnalysisResponse = true; hasPartialAnalysisResponse = true; } else if (status.isCompleted) { From 72f6f2b716417704db174e75cc0caf57bacc5415 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Tue, 30 Sep 2025 16:05:43 +0300 Subject: [PATCH 36/36] feat: create email template for TTO reservation confirmation feat: implement order notifications service with TTO reservation confirmation handling feat: create migration for TTO booking email webhook trigger --- .../_lib/server/update-account.ts | 1 - app/home/(user)/(dashboard)/cart/page.tsx | 14 +- .../order-confirmed-loading-wrapper.tsx | 14 +- .../order/[orderId]/confirmed/page.tsx | 5 +- app/home/(user)/(dashboard)/order/page.tsx | 13 +- .../_components/booking/booking.provider.tsx | 1 - .../(user)/_components/booking/time-slots.tsx | 2 +- app/home/(user)/_components/cart/index.tsx | 30 +- app/home/(user)/_components/cart/types.ts | 7 +- .../(user)/_components/dashboard-cards.tsx | 20 +- app/home/(user)/_components/dashboard.tsx | 5 +- .../(user)/_components/order/cart-totals.tsx | 25 +- .../(user)/_lib/server/balance-actions.ts | 9 +- app/home/(user)/_lib/server/cart-actions.ts | 110 +- .../_lib/server/is-valid-open-ai-env.ts | 2 +- .../team-account-health-details.tsx | 2 +- .../_components/team-account-statistics.tsx | 20 +- ...-team-account-benefit-expenses-overview.ts | 24 +- .../load-team-account-benefit-statistics.ts | 36 +- .../load-team-account-health-details.ts | 3 +- .../health-benefit-form-client.tsx | 16 +- .../_components/health-benefit-form.tsx | 17 +- .../_components/yearly-expenses-overview.tsx | 14 +- app/home/[account]/billing/page.tsx | 2 +- .../_lib/server/members-page.loader.ts | 17 +- app/home/[account]/members/page.tsx | 9 +- app/home/[account]/page.tsx | 16 +- .../audit/notificationEntries.service.ts | 1 + .../medipostPrivateMessage.service.ts | 33 +- lib/utils.ts | 3 +- .../database-webhook-router.service.ts | 26 +- packages/email-templates/package.json | 9 +- .../src/components/common-footer.tsx | 34 +- .../email-templates/src/components/footer.tsx | 6 +- packages/email-templates/src/emails/email.tsx | 8 + .../tto-reservation-confirmation.email.tsx | 137 + packages/email-templates/src/index.ts | 1 + .../tto-reservation-confirmation-email.json | 8 + .../src/locales/et/common.json | 10 +- .../tto-reservation-confirmation-email.json | 8 + .../tto-reservation-confirmation-email.json | 8 + .../services/account-balance.service.ts | 17 +- .../src/types/account-balance-entry.ts | 3 +- .../features/accounts/src/types/accounts.ts | 3 +- .../server/services/admin-accounts.service.ts | 7 +- .../medusa-storefront/src/lib/data/cart.ts | 21 +- .../src/lib/data/collections.ts | 2 +- ...vice.ts => order-notifications.service.ts} | 135 +- .../members/account-members-table.tsx | 15 +- .../team-invitations-server-actions.ts | 2 +- .../webhooks/account-webhooks.service.ts | 2 +- .../features/user-analyses/src/server/api.ts | 11 +- .../config/team-account-navigation.config.tsx | 10 +- pnpm-lock.yaml | 3021 ++++++++++++++++- ...153100_sync_connected_online_cron_job.sql} | 0 .../20250929144800_tto_booking_email_hook.sql | 11 + 56 files changed, 3692 insertions(+), 294 deletions(-) create mode 100644 packages/email-templates/src/emails/email.tsx create mode 100644 packages/email-templates/src/emails/tto-reservation-confirmation.email.tsx create mode 100644 packages/email-templates/src/locales/en/tto-reservation-confirmation-email.json create mode 100644 packages/email-templates/src/locales/et/tto-reservation-confirmation-email.json create mode 100644 packages/email-templates/src/locales/ru/tto-reservation-confirmation-email.json rename packages/features/notifications/src/server/services/webhooks/{analysis-order-notifications.service.ts => order-notifications.service.ts} (64%) rename supabase/migrations/{202500925153100_sync_connected_online_cron_job.sql => 20250925153100_sync_connected_online_cron_job.sql} (100%) create mode 100644 supabase/migrations/20250929144800_tto_booking_email_hook.sql diff --git a/app/auth/update-account/_lib/server/update-account.ts b/app/auth/update-account/_lib/server/update-account.ts index 8bca7bb..622787d 100644 --- a/app/auth/update-account/_lib/server/update-account.ts +++ b/app/auth/update-account/_lib/server/update-account.ts @@ -16,7 +16,6 @@ export const onUpdateAccount = enhanceAction( try { await api.updateAccount(params); - console.log('SUCCESS', pathsConfig.auth.updateAccountSuccess); } catch (err: unknown) { if (err instanceof Error) { console.warn('On update account error: ' + err.message); diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx index 38967f1..389ee2d 100644 --- a/app/home/(user)/(dashboard)/cart/page.tsx +++ b/app/home/(user)/(dashboard)/cart/page.tsx @@ -3,6 +3,7 @@ import { PageBody, PageHeader } from '@/packages/ui/src/makerkit/page'; import { retrieveCart } from '@lib/data/cart'; import { listProductTypes } from '@lib/data/products'; +import { AccountBalanceService } from '@kit/accounts/services/account-balance.service'; import { Trans } from '@kit/ui/trans'; import { withI18n } from '~/lib/i18n/with-i18n'; @@ -11,9 +12,8 @@ import { findProductTypeIdByHandle } from '~/lib/utils'; import Cart from '../../_components/cart'; import CartTimer from '../../_components/cart/cart-timer'; -import { loadCurrentUserAccount } from '../../_lib/server/load-user-account'; -import { AccountBalanceService } from '@kit/accounts/services/account-balance.service'; import { EnrichedCartItem } from '../../_components/cart/types'; +import { loadCurrentUserAccount } from '../../_lib/server/load-user-account'; export async function generateMetadata() { const { t } = await createI18nServerInstance(); @@ -24,11 +24,7 @@ export async function generateMetadata() { } async function CartPage() { - const [ - cart, - { productTypes }, - { account }, - ] = await Promise.all([ + const [cart, { productTypes }, { account }] = await Promise.all([ retrieveCart(), listProductTypes(), loadCurrentUserAccount(), @@ -38,7 +34,9 @@ async function CartPage() { return null; } - const balanceSummary = await new AccountBalanceService().getBalanceSummary(account.id); + const balanceSummary = await new AccountBalanceService().getBalanceSummary( + account.id, + ); const synlabAnalysisTypeId = findProductTypeIdByHandle( productTypes, diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx index 939962d..7e99300 100644 --- a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx +++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/order-confirmed-loading-wrapper.tsx @@ -1,18 +1,19 @@ 'use client'; +import { useEffect, useRef, useState } from 'react'; + import CartTotals from '@/app/home/(user)/_components/order/cart-totals'; import OrderDetails from '@/app/home/(user)/_components/order/order-details'; import OrderItems from '@/app/home/(user)/_components/order/order-items'; +import { retrieveOrder } from '@lib/data/orders'; +import { StoreOrder } from '@medusajs/types'; import Divider from '@modules/common/components/divider'; +import { GlobalLoader } from '@kit/ui/makerkit/global-loader'; import { PageBody, PageHeader } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; -import { StoreOrder } from '@medusajs/types'; import { AnalysisOrder } from '~/lib/types/analysis-order'; -import { useEffect, useRef, useState } from 'react'; -import { retrieveOrder } from '@lib/data/orders'; -import { GlobalLoader } from '@kit/ui/makerkit/global-loader'; function OrderConfirmedLoadingWrapper({ medusaOrder: initialMedusaOrder, @@ -21,7 +22,8 @@ function OrderConfirmedLoadingWrapper({ medusaOrder: StoreOrder; order: AnalysisOrder; }) { - const [medusaOrder, setMedusaOrder] = useState(initialMedusaOrder); + const [medusaOrder, setMedusaOrder] = + useState(initialMedusaOrder); const fetchingRef = useRef(false); const paymentStatus = medusaOrder.payment_status; @@ -52,7 +54,7 @@ function OrderConfirmedLoadingWrapper({ if (!isPaid) { return ( -
+
diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx index eaffa1c..4477532 100644 --- a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx +++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx @@ -7,6 +7,7 @@ import { pathsConfig } from '@kit/shared/config'; import { withI18n } from '~/lib/i18n/with-i18n'; import { getAnalysisOrder } from '~/lib/services/order.service'; + import OrderConfirmedLoadingWrapper from './order-confirmed-loading-wrapper'; export async function generateMetadata() { @@ -36,7 +37,9 @@ async function OrderConfirmedPage(props: { redirect(pathsConfig.app.myOrders); } - return ; + return ( + + ); } export default withI18n(OrderConfirmedPage); diff --git a/app/home/(user)/(dashboard)/order/page.tsx b/app/home/(user)/(dashboard)/order/page.tsx index c50b7d1..beb0634 100644 --- a/app/home/(user)/(dashboard)/order/page.tsx +++ b/app/home/(user)/(dashboard)/order/page.tsx @@ -29,12 +29,13 @@ export async function generateMetadata() { } async function OrdersPage() { - const [medusaOrders, analysisOrders, ttoOrders, { productTypes }] = await Promise.all([ - listOrders(ORDERS_LIMIT), - getAnalysisOrders(), - getTtoOrders(), - listProductTypes(), - ]); + const [medusaOrders, analysisOrders, ttoOrders, { productTypes }] = + await Promise.all([ + listOrders(ORDERS_LIMIT), + getAnalysisOrders(), + getTtoOrders(), + listProductTypes(), + ]); if (!medusaOrders || !productTypes || !ttoOrders) { redirect(pathsConfig.auth.signIn); diff --git a/app/home/(user)/_components/booking/booking.provider.tsx b/app/home/(user)/_components/booking/booking.provider.tsx index b0c21e1..3ae2f1c 100644 --- a/app/home/(user)/_components/booking/booking.provider.tsx +++ b/app/home/(user)/_components/booking/booking.provider.tsx @@ -45,7 +45,6 @@ export const BookingProvider: React.FC<{ const updateTimeSlots = async (serviceIds: number[]) => { setIsLoadingTimeSlots(true); try { - console.log('serviceIds', serviceIds, selectedLocationId); const response = await getAvailableTimeSlotsForDisplay( serviceIds, selectedLocationId, diff --git a/app/home/(user)/_components/booking/time-slots.tsx b/app/home/(user)/_components/booking/time-slots.tsx index 3dbb7f5..58190b1 100644 --- a/app/home/(user)/_components/booking/time-slots.tsx +++ b/app/home/(user)/_components/booking/time-slots.tsx @@ -167,7 +167,7 @@ const TimeSlots = ({ return handleBookTime(timeSlot); }; - console.log('paginatedBookings', booking.isLoadingTimeSlots); + return ( 0; const companyBenefitsTotal = balanceSummary?.totalBalance ?? 0; - const montonioTotal = cart && companyBenefitsTotal > 0 ? cart.total - companyBenefitsTotal : cart.total; + const montonioTotal = + cart && companyBenefitsTotal > 0 + ? cart.total - companyBenefitsTotal + : cart.total; return (
@@ -158,7 +163,10 @@ export default function Cart({

{formatCurrency({ - value: (companyBenefitsTotal > cart.total ? cart.total : companyBenefitsTotal), + value: + companyBenefitsTotal > cart.total + ? cart.total + : companyBenefitsTotal, currencyCode: cart.currency_code, locale: language, })} diff --git a/app/home/(user)/_components/cart/types.ts b/app/home/(user)/_components/cart/types.ts index aa5cc6d..b7de7ce 100644 --- a/app/home/(user)/_components/cart/types.ts +++ b/app/home/(user)/_components/cart/types.ts @@ -1,5 +1,6 @@ -import { StoreCartLineItem } from "@medusajs/types"; -import { Reservation } from "~/lib/types/reservation"; +import { StoreCartLineItem } from '@medusajs/types'; + +import { Reservation } from '~/lib/types/reservation'; export interface MontonioOrderToken { uuid: string; @@ -12,7 +13,7 @@ export interface MontonioOrderToken { | 'CANCELLED' | 'PENDING' | 'EXPIRED' - | 'REFUNDED'; + | 'REFUNDED' | 'PAID' | 'FAILED' | 'CANCELLED' diff --git a/app/home/(user)/_components/dashboard-cards.tsx b/app/home/(user)/_components/dashboard-cards.tsx index f8adcb0..b5ca430 100644 --- a/app/home/(user)/_components/dashboard-cards.tsx +++ b/app/home/(user)/_components/dashboard-cards.tsx @@ -1,5 +1,7 @@ import Link from 'next/link'; +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { formatCurrency } from '@/packages/shared/src/utils'; import { ChevronRight, HeartPulse } from 'lucide-react'; import { Button } from '@kit/ui/button'; @@ -11,25 +13,27 @@ import { CardHeader, CardTitle, } from '@kit/ui/card'; -import { Trans } from '@kit/ui/trans'; -import { formatCurrency } from '@/packages/shared/src/utils'; -import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { cn } from '@kit/ui/lib/utils'; -import { loadCurrentUserAccount } from '../_lib/server/load-user-account'; +import { Trans } from '@kit/ui/trans'; + import { getAccountBalanceSummary } from '../_lib/server/balance-actions'; +import { loadCurrentUserAccount } from '../_lib/server/load-user-account'; export default async function DashboardCards() { const { language } = await createI18nServerInstance(); - + const { account } = await loadCurrentUserAccount(); - const balanceSummary = account ? await getAccountBalanceSummary(account.id) : null; + const balanceSummary = account + ? await getAccountBalanceSummary(account.id) + : null; return (

+ )} + > + /> diff --git a/app/home/(user)/_components/dashboard.tsx b/app/home/(user)/_components/dashboard.tsx index e0fbf7e..670c69f 100644 --- a/app/home/(user)/_components/dashboard.tsx +++ b/app/home/(user)/_components/dashboard.tsx @@ -14,7 +14,10 @@ import { User, } from 'lucide-react'; -import type { AccountWithParams, BmiThresholds } from '@kit/accounts/types/accounts'; +import type { + AccountWithParams, + BmiThresholds, +} from '@kit/accounts/types/accounts'; import { pathsConfig } from '@kit/shared/config'; import { Button } from '@kit/ui/button'; import { diff --git a/app/home/(user)/_components/order/cart-totals.tsx b/app/home/(user)/_components/order/cart-totals.tsx index c5ce5e0..470c44b 100644 --- a/app/home/(user)/_components/order/cart-totals.tsx +++ b/app/home/(user)/_components/order/cart-totals.tsx @@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next'; import { Trans } from '@kit/ui/trans'; const PaymentProviderIds = { - COMPANY_BENEFITS: "pp_company-benefits_company-benefits", - MONTONIO: "pp_montonio_montonio", + COMPANY_BENEFITS: 'pp_company-benefits_company-benefits', + MONTONIO: 'pp_montonio_montonio', }; export default function CartTotals({ @@ -30,10 +30,12 @@ export default function CartTotals({ payment_collections, } = medusaOrder; - const montonioPayment = payment_collections?.[0]?.payments - ?.find(({ provider_id }) => provider_id === PaymentProviderIds.MONTONIO); - const companyBenefitsPayment = payment_collections?.[0]?.payments - ?.find(({ provider_id }) => provider_id === PaymentProviderIds.COMPANY_BENEFITS); + const montonioPayment = payment_collections?.[0]?.payments?.find( + ({ provider_id }) => provider_id === PaymentProviderIds.MONTONIO, + ); + const companyBenefitsPayment = payment_collections?.[0]?.payments?.find( + ({ provider_id }) => provider_id === PaymentProviderIds.COMPANY_BENEFITS, + ); return (
@@ -96,7 +98,6 @@ export default function CartTotals({
)} -
@@ -126,7 +127,10 @@ export default function CartTotals({ - + -{' '} {formatCurrency({ value: companyBenefitsPayment.amount ?? 0, @@ -142,7 +146,10 @@ export default function CartTotals({ - + -{' '} {formatCurrency({ value: montonioPayment.amount ?? 0, diff --git a/app/home/(user)/_lib/server/balance-actions.ts b/app/home/(user)/_lib/server/balance-actions.ts index 425af58..cfff4b0 100644 --- a/app/home/(user)/_lib/server/balance-actions.ts +++ b/app/home/(user)/_lib/server/balance-actions.ts @@ -1,8 +1,13 @@ 'use server'; -import { AccountBalanceService, AccountBalanceSummary } from '@kit/accounts/services/account-balance.service'; +import { + AccountBalanceService, + AccountBalanceSummary, +} from '@kit/accounts/services/account-balance.service'; -export async function getAccountBalanceSummary(accountId: string): Promise { +export async function getAccountBalanceSummary( + accountId: string, +): Promise { try { const service = new AccountBalanceService(); return await service.getBalanceSummary(accountId); diff --git a/app/home/(user)/_lib/server/cart-actions.ts b/app/home/(user)/_lib/server/cart-actions.ts index b6c95f8..bfd1081 100644 --- a/app/home/(user)/_lib/server/cart-actions.ts +++ b/app/home/(user)/_lib/server/cart-actions.ts @@ -1,25 +1,29 @@ 'use server'; -import { z } from 'zod'; +import { AccountWithParams } from '@/packages/features/accounts/src/types/accounts'; +import { createNotificationsApi } from '@/packages/features/notifications/src/server/api'; +import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; +import { listProductTypes } from '@lib/data'; +import { initiateMultiPaymentSession, placeOrder } from '@lib/data/cart'; +import type { StoreCart, StoreOrder } from '@medusajs/types'; import jwt from 'jsonwebtoken'; +import { z } from 'zod'; -import type { StoreCart, StoreOrder } from "@medusajs/types"; +import type { AccountBalanceSummary } from '@kit/accounts/services/account-balance.service'; -import { initiateMultiPaymentSession, placeOrder } from "@lib/data/cart"; -import type { AccountBalanceSummary } from "@kit/accounts/services/account-balance.service"; -import { handleNavigateToPayment } from "~/lib/services/medusaCart.service"; -import { loadCurrentUserAccount } from "./load-user-account"; -import { getOrderedAnalysisIds } from "~/lib/services/medusaOrder.service"; -import { createAnalysisOrder, getAnalysisOrder } from "~/lib/services/order.service"; -import { listProductTypes } from "@lib/data"; -import { sendOrderToMedipost } from "~/lib/services/medipost/medipostPrivateMessage.service"; -import { AccountWithParams } from "@/packages/features/accounts/src/types/accounts"; -import { createI18nServerInstance } from "~/lib/i18n/i18n.server"; -import { getSupabaseServerAdminClient } from "@/packages/supabase/src/clients/server-admin-client"; -import { createNotificationsApi } from "@/packages/features/notifications/src/server/api"; -import { FailureReason } from '~/lib/types/connected-online'; -import { getOrderedTtoServices } from '~/lib/services/reservation.service'; +import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { bookAppointment } from '~/lib/services/connected-online.service'; +import { sendOrderToMedipost } from '~/lib/services/medipost/medipostPrivateMessage.service'; +import { handleNavigateToPayment } from '~/lib/services/medusaCart.service'; +import { getOrderedAnalysisIds } from '~/lib/services/medusaOrder.service'; +import { + createAnalysisOrder, + getAnalysisOrder, +} from '~/lib/services/order.service'; +import { getOrderedTtoServices } from '~/lib/services/reservation.service'; +import { FailureReason } from '~/lib/types/connected-online'; + +import { loadCurrentUserAccount } from './load-user-account'; const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages'; const ANALYSIS_TYPE_HANDLE = 'synlab-analysis'; @@ -40,12 +44,16 @@ const env = () => isEnabledDispatchOnMontonioCallback: z.boolean({ error: 'MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK is required', }), - medusaBackendPublicUrl: z.string({ - error: 'MEDUSA_BACKEND_PUBLIC_URL is required', - }).min(1), - companyBenefitsPaymentSecretKey: z.string({ - error: 'COMPANY_BENEFITS_PAYMENT_SECRET_KEY is required', - }).min(1), + medusaBackendPublicUrl: z + .string({ + error: 'MEDUSA_BACKEND_PUBLIC_URL is required', + }) + .min(1), + companyBenefitsPaymentSecretKey: z + .string({ + error: 'COMPANY_BENEFITS_PAYMENT_SECRET_KEY is required', + }) + .min(1), }) .parse({ emailSender: process.env.EMAIL_SENDER, @@ -53,7 +61,8 @@ const env = () => isEnabledDispatchOnMontonioCallback: process.env.MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK === 'true', medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!, - companyBenefitsPaymentSecretKey: process.env.COMPANY_BENEFITS_PAYMENT_SECRET_KEY!, + companyBenefitsPaymentSecretKey: + process.env.COMPANY_BENEFITS_PAYMENT_SECRET_KEY!, }); export const initiatePayment = async ({ @@ -92,23 +101,30 @@ export const initiatePayment = async ({ // place order if all paid already const { orderId } = await handlePlaceOrder({ cart }); - const companyBenefitsOrderToken = jwt.sign({ - accountId, - companyBenefitsPaymentSessionId, - orderId, - totalByBenefits, - }, env().companyBenefitsPaymentSecretKey, { - algorithm: 'HS256', - }); - const webhookResponse = await fetch(`${env().medusaBackendPublicUrl}/hooks/payment/company-benefits_company-benefits`, { - method: 'POST', - body: JSON.stringify({ - orderToken: companyBenefitsOrderToken, - }), - headers: { - 'Content-Type': 'application/json', + const companyBenefitsOrderToken = jwt.sign( + { + accountId, + companyBenefitsPaymentSessionId, + orderId, + totalByBenefits, }, - }); + env().companyBenefitsPaymentSecretKey, + { + algorithm: 'HS256', + }, + ); + const webhookResponse = await fetch( + `${env().medusaBackendPublicUrl}/hooks/payment/company-benefits_company-benefits`, + { + method: 'POST', + body: JSON.stringify({ + orderToken: companyBenefitsOrderToken, + }), + headers: { + 'Content-Type': 'application/json', + }, + }, + ); if (!webhookResponse.ok) { throw new Error('Failed to send company benefits webhook'); } @@ -118,14 +134,15 @@ export const initiatePayment = async ({ console.error('Error initiating payment', error); } - return { url: null, isFullyPaidByBenefits: false, orderId: null, unavailableLineItemIds: [] }; -} + return { + url: null, + isFullyPaidByBenefits: false, + orderId: null, + unavailableLineItemIds: [], + }; +}; -export async function handlePlaceOrder({ - cart, -}: { - cart: StoreCart; -}) { +export async function handlePlaceOrder({ cart }: { cart: StoreCart }) { const { account } = await loadCurrentUserAccount(); if (!account) { throw new Error('Account not found in context'); @@ -184,6 +201,7 @@ export async function handlePlaceOrder({ ); bookServiceResults = await Promise.all(bookingPromises); } + // TODO: SEND EMAIL if (email) { if (analysisPackageOrder) { diff --git a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts index 183a8f9..12c8ba9 100644 --- a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts +++ b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts @@ -6,7 +6,7 @@ export const isValidOpenAiEnv = async () => { await client.models.list(); return true; } catch (e) { - console.log('No openAI env'); + console.log('AI not enabled'); return false; } }; diff --git a/app/home/[account]/_components/team-account-health-details.tsx b/app/home/[account]/_components/team-account-health-details.tsx index 547a8a8..6ee3a0d 100644 --- a/app/home/[account]/_components/team-account-health-details.tsx +++ b/app/home/[account]/_components/team-account-health-details.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { Database } from '@/packages/supabase/src/database.types'; +import type { BmiThresholds } from '@kit/accounts/types/accounts'; import { Card } from '@kit/ui/card'; import { Trans } from '@kit/ui/trans'; import { cn } from '@kit/ui/utils'; -import type { BmiThresholds } from '@kit/accounts/types/accounts'; import { getAccountHealthDetailsFields } from '../_lib/server/load-team-account-health-details'; import { TeamAccountStatisticsProps } from './team-account-statistics'; diff --git a/app/home/[account]/_components/team-account-statistics.tsx b/app/home/[account]/_components/team-account-statistics.tsx index 17a7466..d921b5a 100644 --- a/app/home/[account]/_components/team-account-statistics.tsx +++ b/app/home/[account]/_components/team-account-statistics.tsx @@ -4,6 +4,11 @@ import { useState } from 'react'; import { redirect } from 'next/navigation'; +import type { + Account, + AccountParams, + BmiThresholds, +} from '@/packages/features/accounts/src/types/accounts'; import { Database } from '@/packages/supabase/src/database.types'; import { format } from 'date-fns'; import { enGB, et } from 'date-fns/locale'; @@ -16,11 +21,10 @@ import { Trans } from '@kit/ui/makerkit/trans'; import { Button } from '@kit/ui/shadcn/button'; import { DateRange } from '@kit/ui/shadcn/calendar'; +import { TeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; import { AccountBenefitStatistics } from '../_lib/server/load-team-account-benefit-statistics'; import TeamAccountBenefitStatistics from './team-account-benefit-statistics'; import TeamAccountHealthDetails from './team-account-health-details'; -import type { Account, AccountParams, BmiThresholds } from '@/packages/features/accounts/src/types/accounts'; -import { TeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; export interface TeamAccountStatisticsProps { teamAccount: Account; @@ -53,7 +57,7 @@ export default function TeamAccountStatistics({ return ( <> -
+

- +
@@ -125,10 +132,7 @@ export default function TeamAccountStatistics({ className="border-warning/40 hover:bg-warning/20 relative flex h-full cursor-pointer flex-col justify-center px-6 py-4 transition-colors" onClick={() => redirect( - createPath( - pathsConfig.app.accountBilling, - teamAccount.slug!, - ), + createPath(pathsConfig.app.accountBilling, teamAccount.slug!), ) } > diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts index 0f260ea..061d8b0 100644 --- a/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts +++ b/app/home/[account]/_lib/server/load-team-account-benefit-expenses-overview.ts @@ -1,5 +1,6 @@ -import { getSupabaseServerClient } from "@/packages/supabase/src/clients/server-client"; -import { loadCompanyPersonalAccountsBalanceEntries } from "./load-team-account-benefit-statistics"; +import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; + +import { loadCompanyPersonalAccountsBalanceEntries } from './load-team-account-benefit-statistics'; export interface TeamAccountBenefitExpensesOverview { benefitAmount: number | null; @@ -10,7 +11,7 @@ export interface TeamAccountBenefitExpensesOverview { total: number; } -const MANAGEMENT_FEE = 5.50; +const MANAGEMENT_FEE = 5.5; const MONTHS = 12; const QUARTERS = 4; @@ -32,15 +33,19 @@ export async function loadTeamAccountBenefitExpensesOverview({ .single(); let benefitAmount: TeamAccountBenefitExpensesOverview['benefitAmount'] = null; - let benefitOccurrence: TeamAccountBenefitExpensesOverview['benefitOccurrence'] = null; + let benefitOccurrence: TeamAccountBenefitExpensesOverview['benefitOccurrence'] = + null; if (error) { console.warn('Failed to load team account benefit expenses overview'); } else { - benefitAmount = data.benefit_amount as TeamAccountBenefitExpensesOverview['benefitAmount']; - benefitOccurrence = data.benefit_occurrence as TeamAccountBenefitExpensesOverview['benefitOccurrence']; + benefitAmount = + data.benefit_amount as TeamAccountBenefitExpensesOverview['benefitAmount']; + benefitOccurrence = + data.benefit_occurrence as TeamAccountBenefitExpensesOverview['benefitOccurrence']; } - const { purchaseEntriesTotal } = await loadCompanyPersonalAccountsBalanceEntries({ accountId: companyId }); + const { purchaseEntriesTotal } = + await loadCompanyPersonalAccountsBalanceEntries({ accountId: companyId }); return { benefitAmount, @@ -55,7 +60,8 @@ export async function loadTeamAccountBenefitExpensesOverview({ const currentDate = new Date(); const createdAt = new Date(data.created_at); - const isCreatedThisYear = createdAt.getFullYear() === currentDate.getFullYear(); + const isCreatedThisYear = + createdAt.getFullYear() === currentDate.getFullYear(); if (benefitOccurrence === 'yearly') { return benefitAmount * employeeCount; } else if (benefitOccurrence === 'monthly') { @@ -71,5 +77,5 @@ export async function loadTeamAccountBenefitExpensesOverview({ } return 0; })(), - } + }; } diff --git a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts index f61f350..2daa29e 100644 --- a/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts +++ b/app/home/[account]/_lib/server/load-team-account-benefit-statistics.ts @@ -16,7 +16,7 @@ export interface AccountBenefitStatistics { analysisPackagesCount: number; analysisPackagesSum: number; - } + }; } export const loadCompanyPersonalAccountsBalanceEntries = async ({ @@ -38,12 +38,21 @@ export const loadCompanyPersonalAccountsBalanceEntries = async ({ .from('account_balance_entries') .select('*') .eq('is_active', true) - .in('account_id', accountMemberships.map(({ user_id }) => user_id)) + .in( + 'account_id', + accountMemberships.map(({ user_id }) => user_id), + ) .throwOnError(); - const purchaseEntries = accountBalanceEntries.filter(({ entry_type }) => entry_type === 'purchase'); - const analysesEntries = purchaseEntries.filter(({ is_analysis_order }) => is_analysis_order); - const analysisPackagesEntries = purchaseEntries.filter(({ is_analysis_package_order }) => is_analysis_package_order); + const purchaseEntries = accountBalanceEntries.filter( + ({ entry_type }) => entry_type === 'purchase', + ); + const analysesEntries = purchaseEntries.filter( + ({ is_analysis_order }) => is_analysis_order, + ); + const analysisPackagesEntries = purchaseEntries.filter( + ({ is_analysis_package_order }) => is_analysis_package_order, + ); return { accountBalanceEntries, @@ -51,9 +60,12 @@ export const loadCompanyPersonalAccountsBalanceEntries = async ({ analysisPackagesEntries, companyAccountsCount: count || 0, purchaseEntries, - purchaseEntriesTotal: purchaseEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + purchaseEntriesTotal: purchaseEntries.reduce( + (acc, { amount }) => acc + Math.abs(amount || 0), + 0, + ), }; -} +}; export const loadAccountBenefitStatistics = async ( accountId: string, @@ -86,10 +98,16 @@ export const loadAccountBenefitStatistics = async ( totalSum: purchaseEntriesTotal, analysesCount: analysesEntries.length, - analysesSum: analysesEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + analysesSum: analysesEntries.reduce( + (acc, { amount }) => acc + Math.abs(amount || 0), + 0, + ), analysisPackagesCount: analysisPackagesEntries.length, - analysisPackagesSum: analysisPackagesEntries.reduce((acc, { amount }) => acc + Math.abs(amount || 0), 0), + analysisPackagesSum: analysisPackagesEntries.reduce( + (acc, { amount }) => acc + Math.abs(amount || 0), + 0, + ), }, }; }; diff --git a/app/home/[account]/_lib/server/load-team-account-health-details.ts b/app/home/[account]/_lib/server/load-team-account-health-details.ts index 9568dda..0d9136d 100644 --- a/app/home/[account]/_lib/server/load-team-account-health-details.ts +++ b/app/home/[account]/_lib/server/load-team-account-health-details.ts @@ -4,6 +4,8 @@ import { Database } from '@/packages/supabase/src/database.types'; import Isikukood from 'isikukood'; import { Clock, TrendingUp, User } from 'lucide-react'; +import type { BmiThresholds } from '@kit/accounts/types/accounts'; + import { bmiFromMetric, getBmiBackgroundColor, @@ -11,7 +13,6 @@ import { } from '~/lib/utils'; import { TeamAccountStatisticsProps } from '../../_components/team-account-statistics'; -import type { BmiThresholds } from '@kit/accounts/types/accounts'; interface AccountHealthDetailsField { title: string; diff --git a/app/home/[account]/billing/_components/health-benefit-form-client.tsx b/app/home/[account]/billing/_components/health-benefit-form-client.tsx index 6ca2ff2..a644e3b 100644 --- a/app/home/[account]/billing/_components/health-benefit-form-client.tsx +++ b/app/home/[account]/billing/_components/health-benefit-form-client.tsx @@ -1,11 +1,17 @@ 'use client'; import { useState } from 'react'; + import { useRouter } from 'next/navigation'; import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema'; +import { + Account, + CompanyParams, +} from '@/packages/features/accounts/src/types/accounts'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { Button } from '@kit/ui/button'; import { Form } from '@kit/ui/form'; @@ -17,8 +23,6 @@ import { cn } from '~/lib/utils'; import { updateHealthBenefit } from '../_lib/server/server-actions'; import HealthBenefitFields from './health-benefit-fields'; -import { Account, CompanyParams } from '@/packages/features/accounts/src/types/accounts'; -import { useTranslation } from 'react-i18next'; const HealthBenefitFormClient = ({ account, @@ -29,7 +33,7 @@ const HealthBenefitFormClient = ({ }) => { const { t } = useTranslation('account'); const router = useRouter(); - + const [currentCompanyParams, setCurrentCompanyParams] = useState(companyParams); const [isLoading, setIsLoading] = useState(false); @@ -42,7 +46,7 @@ const HealthBenefitFormClient = ({ amount: currentCompanyParams.benefit_amount || 0, }, }); - + const isDirty = form.formState.isDirty; const onSubmit = (data: { occurrence: string; amount: number }) => { @@ -75,7 +79,7 @@ const HealthBenefitFormClient = ({ onSubmit={form.handleSubmit(onSubmit)} > - +

- -
-
+ +
+
@@ -46,7 +49,7 @@ const HealthBenefitForm = async ({
- @@ -58,7 +61,7 @@ const HealthBenefitForm = async ({ employeeCount={employeeCount} expensesOverview={expensesOverview} /> - +

diff --git a/app/home/[account]/billing/_components/yearly-expenses-overview.tsx b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx index afd3d21..232bf57 100644 --- a/app/home/[account]/billing/_components/yearly-expenses-overview.tsx +++ b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx @@ -1,9 +1,11 @@ 'use client'; +import { formatCurrency } from '@/packages/shared/src/utils'; +import { useTranslation } from 'react-i18next'; + import { Trans } from '@kit/ui/makerkit/trans'; import { Separator } from '@kit/ui/separator'; -import { formatCurrency } from '@/packages/shared/src/utils'; -import { useTranslation } from 'react-i18next'; + import { TeamAccountBenefitExpensesOverview } from '../../_lib/server/load-team-account-benefit-expenses-overview'; const YearlyExpensesOverview = ({ @@ -13,7 +15,9 @@ const YearlyExpensesOverview = ({ employeeCount?: number; expensesOverview: TeamAccountBenefitExpensesOverview; }) => { - const { i18n: { language } } = useTranslation(); + const { + i18n: { language }, + } = useTranslation(); return (
@@ -24,9 +28,7 @@ const YearlyExpensesOverview = ({

- - {employeeCount} - + {employeeCount}

diff --git a/app/home/[account]/billing/page.tsx b/app/home/[account]/billing/page.tsx index ed06890..e3ca767 100644 --- a/app/home/[account]/billing/page.tsx +++ b/app/home/[account]/billing/page.tsx @@ -6,8 +6,8 @@ import { PageBody } from '@kit/ui/page'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; -import HealthBenefitForm from './_components/health-benefit-form'; import { loadTeamAccountBenefitExpensesOverview } from '../_lib/server/load-team-account-benefit-expenses-overview'; +import HealthBenefitForm from './_components/health-benefit-form'; interface TeamAccountBillingPageProps { params: Promise<{ account: string }>; diff --git a/app/home/[account]/members/_lib/server/members-page.loader.ts b/app/home/[account]/members/_lib/server/members-page.loader.ts index db902bc..8df60c8 100644 --- a/app/home/[account]/members/_lib/server/members-page.loader.ts +++ b/app/home/[account]/members/_lib/server/members-page.loader.ts @@ -2,10 +2,10 @@ import 'server-only'; import { SupabaseClient } from '@supabase/supabase-js'; +import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; import { Database } from '@/packages/supabase/src/database.types'; import { loadTeamWorkspace } from '~/home/[account]/_lib/server/team-account-workspace.loader'; -import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; /** * Load data for the members page @@ -22,7 +22,10 @@ export async function loadMembersPageData( loadInvitations(client, slug), canAddMember, workspace, - loadAccountMembersBenefitsUsage(getSupabaseServerAdminClient(), workspace.account.id), + loadAccountMembersBenefitsUsage( + getSupabaseServerAdminClient(), + workspace.account.id, + ), ]); } @@ -66,10 +69,12 @@ async function loadAccountMembers( export async function loadAccountMembersBenefitsUsage( client: SupabaseClient, accountId: string, -): Promise<{ - personal_account_id: string; - benefit_amount: number; -}[]> { +): Promise< + { + personal_account_id: string; + benefit_amount: number; + }[] +> { const { data, error } = await client .schema('medreport') .rpc('get_benefits_usages_for_company_members', { diff --git a/app/home/[account]/members/page.tsx b/app/home/[account]/members/page.tsx index fc124ea..eeb012e 100644 --- a/app/home/[account]/members/page.tsx +++ b/app/home/[account]/members/page.tsx @@ -42,8 +42,13 @@ async function TeamAccountMembersPage({ params }: TeamAccountMembersPageProps) { const client = getSupabaseServerClient(); const slug = (await params).account; - const [members, invitations, canAddMember, { user, account }, membersBenefitsUsage] = - await loadMembersPageData(client, slug); + const [ + members, + invitations, + canAddMember, + { user, account }, + membersBenefitsUsage, + ] = await loadMembersPageData(client, slug); const canManageRoles = account.permissions.includes('roles.manage'); const canManageInvitations = account.permissions.includes('invites.manage'); diff --git a/app/home/[account]/page.tsx b/app/home/[account]/page.tsx index e8eb7ba..f53a1a8 100644 --- a/app/home/[account]/page.tsx +++ b/app/home/[account]/page.tsx @@ -17,8 +17,8 @@ import { } from '~/lib/services/audit/pageView.service'; import { Dashboard } from './_components/dashboard'; -import { loadAccountBenefitStatistics } from './_lib/server/load-team-account-benefit-statistics'; import { loadTeamAccountBenefitExpensesOverview } from './_lib/server/load-team-account-benefit-expenses-overview'; +import { loadAccountBenefitStatistics } from './_lib/server/load-team-account-benefit-statistics'; interface TeamAccountHomePageProps { params: Promise<{ account: string }>; @@ -41,11 +41,15 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { const teamAccount = use(teamAccountsApi.getTeamAccount(account)); const { memberParams, members } = use(teamAccountsApi.getMembers(account)); const bmiThresholds = use(userAnalysesApi.fetchBmiThresholds()); - const accountBenefitStatistics = use(loadAccountBenefitStatistics(teamAccount.id)); - const expensesOverview = use(loadTeamAccountBenefitExpensesOverview({ - companyId: teamAccount.id, - employeeCount: members.length, - })); + const accountBenefitStatistics = use( + loadAccountBenefitStatistics(teamAccount.id), + ); + const expensesOverview = use( + loadTeamAccountBenefitExpensesOverview({ + companyId: teamAccount.id, + employeeCount: members.length, + }), + ); use( createPageViewLog({ diff --git a/lib/services/audit/notificationEntries.service.ts b/lib/services/audit/notificationEntries.service.ts index 980de2e..cdf3680 100644 --- a/lib/services/audit/notificationEntries.service.ts +++ b/lib/services/audit/notificationEntries.service.ts @@ -8,6 +8,7 @@ export enum NotificationAction { PATIENT_ORDER_PROCESSING = 'PATIENT_ORDER_PROCESSING', PATIENT_FIRST_RESULTS_RECEIVED = 'PATIENT_FIRST_RESULTS_RECEIVED', PATIENT_FULL_RESULTS_RECEIVED = 'PATIENT_FULL_RESULTS_RECEIVED', + TTO_ORDER_CONFIRMATION = 'TTO_ORDER_CONFIRMATION', } export const createNotificationLog = async ({ diff --git a/lib/services/medipost/medipostPrivateMessage.service.ts b/lib/services/medipost/medipostPrivateMessage.service.ts index 57380a4..8c27f84 100644 --- a/lib/services/medipost/medipostPrivateMessage.service.ts +++ b/lib/services/medipost/medipostPrivateMessage.service.ts @@ -430,19 +430,21 @@ export async function readPrivateMessageResponse({ medipostExternalOrderId, }); if (status.isPartial) { - await createUserAnalysesApi(getSupabaseServerAdminClient()) - .updateAnalysisOrderStatus({ - medusaOrderId, - orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', - }); + await createUserAnalysesApi( + getSupabaseServerAdminClient(), + ).updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', + }); hasAnalysisResponse = true; hasPartialAnalysisResponse = true; } else if (status.isCompleted) { - await createUserAnalysesApi(getSupabaseServerAdminClient()) - .updateAnalysisOrderStatus({ - medusaOrderId, - orderStatus: 'FULL_ANALYSIS_RESPONSE', - }); + await createUserAnalysesApi( + getSupabaseServerAdminClient(), + ).updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'FULL_ANALYSIS_RESPONSE', + }); if (IS_ENABLED_DELETE_PRIVATE_MESSAGE) { await deletePrivateMessage(privateMessageId); } @@ -624,9 +626,10 @@ export async function sendOrderToMedipost({ hasAnalysisResults: false, medusaOrderId, }); - await createUserAnalysesApi(getSupabaseServerAdminClient()) - .updateAnalysisOrderStatus({ - medusaOrderId, - orderStatus: 'PROCESSING', - }); + await createUserAnalysesApi( + getSupabaseServerAdminClient(), + ).updateAnalysisOrderStatus({ + medusaOrderId, + orderStatus: 'PROCESSING', + }); } diff --git a/lib/utils.ts b/lib/utils.ts index da20b36..841f57b 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,9 +2,10 @@ import { type ClassValue, clsx } from 'clsx'; import Isikukood, { Gender } from 'isikukood'; import { twMerge } from 'tailwind-merge'; -import { BmiCategory } from './types/bmi'; import type { BmiThresholds } from '@kit/accounts/types/accounts'; +import { BmiCategory } from './types/bmi'; + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } diff --git a/packages/database-webhooks/src/server/services/database-webhook-router.service.ts b/packages/database-webhooks/src/server/services/database-webhook-router.service.ts index 9299ae1..0cf8590 100644 --- a/packages/database-webhooks/src/server/services/database-webhook-router.service.ts +++ b/packages/database-webhooks/src/server/services/database-webhook-router.service.ts @@ -48,6 +48,12 @@ class DatabaseWebhookRouterService { return this.handleAnalysisOrdersWebhook(payload); } + case 'connected_online_reservation': { + const payload = body as RecordChange; + + return this.handleTtoOrdersWebhook(payload); + } + default: { return; } @@ -100,13 +106,27 @@ class DatabaseWebhookRouterService { return; } - const { createAnalysisOrderWebhooksService } = await import( - '@kit/notifications/webhooks/analysis-order-notifications.service' + const { createOrderWebhooksService } = await import( + '@kit/notifications/webhooks/order-notifications.service' ); - const service = createAnalysisOrderWebhooksService(); + const service = createOrderWebhooksService(); return service.handleStatusChangeWebhook(record); } } + + private async handleTtoOrdersWebhook( + body: RecordChange<'connected_online_reservation'>, + ) { + if (body.type === 'UPDATE' && body.record) { + const { createOrderWebhooksService } = await import( + '@kit/notifications/webhooks/order-notifications.service' + ); + + const service = createOrderWebhooksService(); + + return service.handleTtoReservationConfirmationWebhook(body.record); + } + } } diff --git a/packages/email-templates/package.json b/packages/email-templates/package.json index 9628ddb..f350452 100644 --- a/packages/email-templates/package.json +++ b/packages/email-templates/package.json @@ -4,17 +4,20 @@ "version": "0.1.0", "scripts": { "clean": "git clean -xdf .turbo node_modules", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "email:dev": "email dev --dir src/emails --port 3001" }, "exports": { ".": "./src/index.ts" }, "dependencies": { - "@react-email/components": "0.0.41" + "@react-email/components": "0.0.41", + "react-email": "4.2.12" }, "devDependencies": { "@kit/i18n": "workspace:*", - "@kit/tsconfig": "workspace:*" + "@kit/tsconfig": "workspace:*", + "@react-email/preview-server": "4.2.12" }, "typesVersions": { "*": { diff --git a/packages/email-templates/src/components/common-footer.tsx b/packages/email-templates/src/components/common-footer.tsx index 32480ee..8214f1b 100644 --- a/packages/email-templates/src/components/common-footer.tsx +++ b/packages/email-templates/src/components/common-footer.tsx @@ -6,22 +6,28 @@ import { EmailFooter } from './footer'; export default function CommonFooter({ t }: { t: TFunction }) { const namespace = 'common'; - const lines = [ - t(`${namespace}:footer.lines1`), - t(`${namespace}:footer.lines2`), - t(`${namespace}:footer.lines3`), - t(`${namespace}:footer.lines4`), - ]; - return ( - {lines.map((line, index) => ( - - ))} + + {t(`${namespace}:footer.title`)} + + + {t(`${namespace}:footer.emailField`)}{' '} + + {t(`${namespace}:footer.email`)} + + + + {t(`${namespace}:footer.phoneField`)}{' '} + + {t(`${namespace}:footer.phone`)} + + + + + {t(`${namespace}:footer.website`)} + + ); } diff --git a/packages/email-templates/src/components/footer.tsx b/packages/email-templates/src/components/footer.tsx index c201795..5c97e09 100644 --- a/packages/email-templates/src/components/footer.tsx +++ b/packages/email-templates/src/components/footer.tsx @@ -1,11 +1,11 @@ -import { Container, Text } from '@react-email/components'; +import { Container, Section } from '@react-email/components'; export function EmailFooter(props: React.PropsWithChildren) { return ( - +

{props.children} - +
); } diff --git a/packages/email-templates/src/emails/email.tsx b/packages/email-templates/src/emails/email.tsx new file mode 100644 index 0000000..84e2cd4 --- /dev/null +++ b/packages/email-templates/src/emails/email.tsx @@ -0,0 +1,8 @@ +import React from 'react'; + +const Email = () => { + return
Email
; +}; + +export default Email; +Email.PreviewProps = {}; diff --git a/packages/email-templates/src/emails/tto-reservation-confirmation.email.tsx b/packages/email-templates/src/emails/tto-reservation-confirmation.email.tsx new file mode 100644 index 0000000..e8dadc3 --- /dev/null +++ b/packages/email-templates/src/emails/tto-reservation-confirmation.email.tsx @@ -0,0 +1,137 @@ +import { + Body, + Head, + Html, + Link, + Preview, + Tailwind, + Text, + render, +} from '@react-email/components'; + +import { BodyStyle } from '../components/body-style'; +import CommonFooter from '../components/common-footer'; +import { EmailContent } from '../components/content'; +import { EmailHeader } from '../components/header'; +import { EmailHeading } from '../components/heading'; +import { EmailWrapper } from '../components/wrapper'; +import { initializeEmailI18n } from '../lib/i18n'; + +export async function renderTtoReservationConfirmationEmail({ + language, + recipientName, + startTime, + orderName, + locationName, + locationAddress, + orderId, + serviceProviderName, + serviceProviderEmail, + serviceProviderPhone, +}: { + language: string; + recipientName: string; + startTime: string; + orderName: string; + locationName?: string; + locationAddress?: string | null; + orderId: string; + serviceProviderName?: string; + serviceProviderEmail?: string | null; + serviceProviderPhone?: string | null; +}) { + const namespace = 'tto-reservation-confirmation-email'; + + const { t } = await initializeEmailI18n({ + language, + namespace: [namespace, 'common'], + }); + + const previewText = t(`${namespace}:previewText`, { + reservation: orderName, + }); + + const subject = t(`${namespace}:subject`, { + reservation: orderName, + }); + + const email = ( + + + + + + {previewText} + + + + + + + {previewText} + + + {t(`${namespace}:helloName`, { name: recipientName })} + + + {t(`${namespace}:thankYou`)} + + + {orderName}, {new Date(startTime).toLocaleString()},{' '} + {locationAddress}, {locationName} + + + + {t(`${namespace}:viewOrder`)} + + + + {serviceProviderName} + + + {t(`${namespace}:customerSupport`)} + + {serviceProviderPhone} + + + + {serviceProviderEmail} + + + + + + + + + ); + + const html = await render(email); + + return { + html, + subject, + email, + }; +} + +const PreviewEmail = async () => { + const { email } = await renderTtoReservationConfirmationEmail({ + language: 'et', + recipientName: 'John Doe', + startTime: '2025-09-27 05:45:00+00', + orderName: 'Hambaarst', + locationName: 'Tallinn', + locationAddress: 'Põhja puiestee, 2/3A', + orderId: '123123', + serviceProviderName: 'Dentas OÜ', + serviceProviderEmail: 'email@example.ee', + serviceProviderPhone: '+372111111', + }); + return email; +}; + +export default PreviewEmail; +PreviewEmail.PreviewProps = {}; diff --git a/packages/email-templates/src/index.ts b/packages/email-templates/src/index.ts index 669a84b..059235e 100644 --- a/packages/email-templates/src/index.ts +++ b/packages/email-templates/src/index.ts @@ -11,3 +11,4 @@ export * from './emails/order-processing.email'; export * from './emails/patient-first-results-received.email'; export * from './emails/patient-full-results-received.email'; export * from './emails/book-time-failed.email'; +export * from './emails/tto-reservation-confirmation.email'; diff --git a/packages/email-templates/src/locales/en/tto-reservation-confirmation-email.json b/packages/email-templates/src/locales/en/tto-reservation-confirmation-email.json new file mode 100644 index 0000000..830822f --- /dev/null +++ b/packages/email-templates/src/locales/en/tto-reservation-confirmation-email.json @@ -0,0 +1,8 @@ +{ + "previewText": "Your booking is confirmed! - {{reservation}}", + "subject": "Your booking is confirmed! - {{reservation}}", + "helloName": "Hello, {{name}}!", + "thankYou": "Thank you for your order!", + "viewOrder": "See booking", + "customerSupport": "Customer support: " +} diff --git a/packages/email-templates/src/locales/et/common.json b/packages/email-templates/src/locales/et/common.json index c663ccf..38349b2 100644 --- a/packages/email-templates/src/locales/et/common.json +++ b/packages/email-templates/src/locales/et/common.json @@ -1,9 +1,11 @@ { "footer": { - "lines1": "MedReport", - "lines2": "E-mail: info@medreport.ee", - "lines3": "Klienditugi: +372 5887 1517", - "lines4": "www.medreport.ee" + "title": "MedReport", + "emailField": "E-mail:", + "email": "info@medreport.ee", + "phoneField": "Klienditugi:", + "phone": "+372 5887 1517", + "website": "www.medreport.ee" }, "helloName": "Tere, {{name}}", "hello": "Tere" diff --git a/packages/email-templates/src/locales/et/tto-reservation-confirmation-email.json b/packages/email-templates/src/locales/et/tto-reservation-confirmation-email.json new file mode 100644 index 0000000..fad1006 --- /dev/null +++ b/packages/email-templates/src/locales/et/tto-reservation-confirmation-email.json @@ -0,0 +1,8 @@ +{ + "previewText": "Teie broneering on kinnitatud! - {{reservation}}", + "subject": "Teie broneering on kinnitatud! - {{reservation}}", + "helloName": "Tere, {{name}}!", + "thankYou": "Täname tellimuse eest!", + "viewOrder": "Vaata broneeringut", + "customerSupport": "Klienditugi: " +} diff --git a/packages/email-templates/src/locales/ru/tto-reservation-confirmation-email.json b/packages/email-templates/src/locales/ru/tto-reservation-confirmation-email.json new file mode 100644 index 0000000..830822f --- /dev/null +++ b/packages/email-templates/src/locales/ru/tto-reservation-confirmation-email.json @@ -0,0 +1,8 @@ +{ + "previewText": "Your booking is confirmed! - {{reservation}}", + "subject": "Your booking is confirmed! - {{reservation}}", + "helloName": "Hello, {{name}}!", + "thankYou": "Thank you for your order!", + "viewOrder": "See booking", + "customerSupport": "Customer support: " +} diff --git a/packages/features/accounts/src/server/services/account-balance.service.ts b/packages/features/accounts/src/server/services/account-balance.service.ts index 434bbed..8a7f1c5 100644 --- a/packages/features/accounts/src/server/services/account-balance.service.ts +++ b/packages/features/accounts/src/server/services/account-balance.service.ts @@ -43,12 +43,17 @@ export class AccountBalanceService { offset?: number; entryType?: string; includeInactive?: boolean; - } = {} + } = {}, ): Promise<{ entries: AccountBalanceEntry[]; total: number; }> { - const { limit = 50, offset = 0, entryType, includeInactive = false } = options; + const { + limit = 50, + offset = 0, + entryType, + includeInactive = false, + } = options; let query = this.supabase .schema('medreport') @@ -105,7 +110,8 @@ export class AccountBalanceService { console.error('Error getting expiring balance:', expiringError); } - const expiringSoon = expiringData?.reduce((sum, entry) => sum + (entry.amount || 0), 0) || 0; + const expiringSoon = + expiringData?.reduce((sum, entry) => sum + (entry.amount || 0), 0) || 0; return { totalBalance: balance, @@ -116,12 +122,13 @@ export class AccountBalanceService { async processPeriodicBenefitDistributions(): Promise { console.info('Processing periodic benefit distributions...'); - const { error } = await this.supabase.schema('medreport').rpc('process_periodic_benefit_distributions'); + const { error } = await this.supabase + .schema('medreport') + .rpc('process_periodic_benefit_distributions'); if (error) { console.error('Error processing periodic benefit distributions:', error); throw new Error('Failed to process periodic benefit distributions'); } console.info('Periodic benefit distributions processed successfully'); } - } diff --git a/packages/features/accounts/src/types/account-balance-entry.ts b/packages/features/accounts/src/types/account-balance-entry.ts index e76cca1..e58f853 100644 --- a/packages/features/accounts/src/types/account-balance-entry.ts +++ b/packages/features/accounts/src/types/account-balance-entry.ts @@ -1,3 +1,4 @@ import type { Database } from '@kit/supabase/database'; -export type AccountBalanceEntry = Database['medreport']['Tables']['account_balance_entries']['Row']; +export type AccountBalanceEntry = + Database['medreport']['Tables']['account_balance_entries']['Row']; diff --git a/packages/features/accounts/src/types/accounts.ts b/packages/features/accounts/src/types/accounts.ts index 430c898..67866c1 100644 --- a/packages/features/accounts/src/types/accounts.ts +++ b/packages/features/accounts/src/types/accounts.ts @@ -22,4 +22,5 @@ export type AccountWithParams = Account & { export type CompanyParams = Database['medreport']['Tables']['company_params']['Row']; -export type BmiThresholds = Database['medreport']['Tables']['bmi_thresholds']['Row']; +export type BmiThresholds = + Database['medreport']['Tables']['bmi_thresholds']['Row']; diff --git a/packages/features/admin/src/lib/server/services/admin-accounts.service.ts b/packages/features/admin/src/lib/server/services/admin-accounts.service.ts index 6f26b58..3732bd4 100644 --- a/packages/features/admin/src/lib/server/services/admin-accounts.service.ts +++ b/packages/features/admin/src/lib/server/services/admin-accounts.service.ts @@ -2,8 +2,8 @@ import 'server-only'; import { SupabaseClient } from '@supabase/supabase-js'; -import { Database } from '@kit/supabase/database'; import type { ApplicationRole } from '@kit/accounts/types/accounts'; +import { Database } from '@kit/supabase/database'; export function createAdminAccountsService(client: SupabaseClient) { return new AdminAccountsService(client); @@ -24,10 +24,7 @@ class AdminAccountsService { } } - async updateRole( - accountId: string, - role: ApplicationRole, - ) { + async updateRole(accountId: string, role: ApplicationRole) { const { error } = await this.adminClient .schema('medreport') .from('accounts') diff --git a/packages/features/medusa-storefront/src/lib/data/cart.ts b/packages/features/medusa-storefront/src/lib/data/cart.ts index a910889..ddb9350 100644 --- a/packages/features/medusa-storefront/src/lib/data/cart.ts +++ b/packages/features/medusa-storefront/src/lib/data/cart.ts @@ -5,7 +5,7 @@ import { redirect } from 'next/navigation'; import { sdk } from '@lib/config'; import medusaError from '@lib/util/medusa-error'; -import { HttpTypes } from '@medusajs/types'; +import { HttpTypes, StoreCart } from '@medusajs/types'; import { getAuthHeaders, @@ -259,18 +259,19 @@ export async function setShippingMethod({ } export async function initiateMultiPaymentSession( - cart: HttpTypes.StoreCart, + cart: StoreCart, benefitsAmount: number, ) { const headers = { ...(await getAuthHeaders()), }; - return sdk.client.fetch(`/store/multi-payment`, { - method: 'POST', - body: { cartId: cart.id, benefitsAmount }, - headers, - }) + return sdk.client + .fetch(`/store/multi-payment`, { + method: 'POST', + body: { cartId: cart.id, benefitsAmount }, + headers, + }) .then(async (response) => { console.info('Payment session initiated:', response); const cartCacheTag = await getCacheTag('carts'); @@ -284,7 +285,11 @@ export async function initiateMultiPaymentSession( }; }) .catch((e) => { - console.error('Error initiating payment session:', e, JSON.stringify(Object.keys(e))); + console.error( + 'Error initiating payment session:', + e, + JSON.stringify(Object.keys(e)), + ); return medusaError(e); }); } diff --git a/packages/features/medusa-storefront/src/lib/data/collections.ts b/packages/features/medusa-storefront/src/lib/data/collections.ts index 540e2f5..62cb856 100644 --- a/packages/features/medusa-storefront/src/lib/data/collections.ts +++ b/packages/features/medusa-storefront/src/lib/data/collections.ts @@ -30,7 +30,7 @@ export const listCollections = async ( queryParams.limit = queryParams.limit || '100'; queryParams.offset = queryParams.offset || '0'; - console.log('SDK_CONFIG: ', SDK_CONFIG.baseUrl); + return sdk.client .fetch<{ collections: HttpTypes.StoreCollection[]; count: number }>( '/store/collections', diff --git a/packages/features/notifications/src/server/services/webhooks/analysis-order-notifications.service.ts b/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts similarity index 64% rename from packages/features/notifications/src/server/services/webhooks/analysis-order-notifications.service.ts rename to packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts index 643b1b7..3adc4bb 100644 --- a/packages/features/notifications/src/server/services/webhooks/analysis-order-notifications.service.ts +++ b/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts @@ -1,9 +1,12 @@ +import { sdk } from '@lib/config'; + import { renderAllResultsReceivedEmail, renderFirstResultsReceivedEmail, renderOrderProcessingEmail, renderPatientFirstResultsReceivedEmail, renderPatientFullResultsReceivedEmail, + renderTtoReservationConfirmationEmail, } from '@kit/email-templates'; import { getLogger } from '@kit/shared/logger'; import { getFullName } from '@kit/shared/utils'; @@ -25,13 +28,15 @@ import { } from '~/lib/services/mailer.service'; type AnalysisOrder = Database['medreport']['Tables']['analysis_orders']['Row']; +type TtoReservation = + Database['medreport']['Tables']['connected_online_reservation']['Row']; -export function createAnalysisOrderWebhooksService() { - return new AnalysisOrderWebhooksService(); +export function createOrderWebhooksService() { + return new OrderWebhooksService(); } -class AnalysisOrderWebhooksService { - private readonly namespace = 'analysis_orders.webhooks'; +class OrderWebhooksService { + private readonly namespace = 'orders.webhooks'; async handleStatusChangeWebhook(analysisOrder: AnalysisOrder) { const logger = await getLogger(); @@ -90,6 +95,128 @@ class AnalysisOrderWebhooksService { } } + async handleTtoReservationConfirmationWebhook( + ttoReservation: TtoReservation, + ) { + const logger = await getLogger(); + const ctx = { + ttoReservationId: ttoReservation.id, + namespace: this.namespace, + }; + + try { + const userContact = await getUserContactAdmin(ttoReservation.user_id); + if (!userContact.email) { + await createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'FAIL', + comment: 'No email found for ' + ttoReservation.user_id, + relatedRecordId: ttoReservation.id, + }); + logger.warn(ctx, 'No email found '); + return; + } + const supabaseClient = getSupabaseServerAdminClient(); + if (!ttoReservation.medusa_cart_line_item_id) { + await createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'FAIL', + comment: 'No cart item id found for ' + ttoReservation.user_id, + relatedRecordId: ttoReservation.id, + }); + logger.warn(ctx, 'No cart item id found '); + return; + } + const [ + { data: cartItem }, + { data: location }, + { data: serviceProvider }, + ] = await Promise.all([ + supabaseClient + .from('cart_line_item') + .select('cart_id,title') + .eq('id', ttoReservation.medusa_cart_line_item_id) + .single(), + supabaseClient + .schema('medreport') + .from('connected_online_locations') + .select('name,address') + .eq('sync_id', ttoReservation.location_sync_id || 0) + .single(), + supabaseClient + .schema('medreport') + .from('connected_online_providers') + .select('email,phone_number,name') + .eq('id', ttoReservation.clinic_id) + .single(), + ]); + + if (!cartItem) { + await createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'FAIL', + comment: 'No medusa cart item found for ' + ttoReservation.user_id, + relatedRecordId: ttoReservation.medusa_cart_line_item_id, + }); + logger.warn(ctx, 'No cart item found '); + return; + } + + const [{ data: orderCart }] = await Promise.all([ + supabaseClient + .from('order_cart') + .select('order_id') + .eq('cart_id', cartItem.cart_id) + .single(), + ]); + + if (!orderCart) { + await createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'FAIL', + comment: 'No medusa order cart found for ' + ttoReservation.user_id, + relatedRecordId: cartItem.cart_id, + }); + logger.warn(ctx, 'No order cart found '); + return; + } + + await sendEmailFromTemplate( + renderTtoReservationConfirmationEmail, + { + language: userContact.preferred_locale ?? 'et', + recipientName: getFullName(userContact.name, userContact.last_name), + startTime: ttoReservation.start_time, + orderName: cartItem.title, + locationName: location?.name, + locationAddress: location?.address, + orderId: orderCart.order_id, + serviceProviderName: serviceProvider?.name, + serviceProviderEmail: serviceProvider?.email, + serviceProviderPhone: serviceProvider?.phone_number, + }, + userContact.email, + ); + + return createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'SUCCESS', + relatedRecordId: orderCart.order_id, + }); + } catch (e: any) { + createNotificationLog({ + action: NotificationAction.TTO_ORDER_CONFIRMATION, + status: 'FAIL', + comment: e?.message, + relatedRecordId: ttoReservation.id, + }); + logger.error( + ctx, + `Error while processing tto reservation: ${JSON.stringify(e)}`, + ); + } + } + async sendProcessingNotification(analysisOrder: AnalysisOrder) { const logger = await getLogger(); const supabase = getSupabaseServerAdminClient(); diff --git a/packages/features/team-accounts/src/components/members/account-members-table.tsx b/packages/features/team-accounts/src/components/members/account-members-table.tsx index 5003f40..d1fbcac 100644 --- a/packages/features/team-accounts/src/components/members/account-members-table.tsx +++ b/packages/features/team-accounts/src/components/members/account-members-table.tsx @@ -6,6 +6,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { Ellipsis } from 'lucide-react'; import { useTranslation } from 'react-i18next'; +import { formatCurrency } from '@kit/shared/utils'; import { Database } from '@kit/supabase/database'; import { Badge } from '@kit/ui/badge'; import { Button } from '@kit/ui/button'; @@ -20,7 +21,6 @@ import { If } from '@kit/ui/if'; import { Input } from '@kit/ui/input'; import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { Trans } from '@kit/ui/trans'; -import { formatCurrency } from '@kit/shared/utils'; import { RemoveMemberDialog } from './remove-member-dialog'; import { RoleBadge } from './role-badge'; @@ -135,7 +135,10 @@ function useGetColumns( }[]; }, ): ColumnDef[] { - const { t, i18n: { language } } = useTranslation('teams'); + const { + t, + i18n: { language }, + } = useTranslation('teams'); return useMemo( () => [ @@ -183,7 +186,7 @@ function useGetColumns( header: t('distributedBenefitsAmount'), cell: ({ row }) => { const benefitAmount = params.membersBenefitsUsage.find( - (usage) => usage.personal_account_id === row.original.id + (usage) => usage.personal_account_id === row.original.id, )?.benefit_amount; if (typeof benefitAmount !== 'number') { return '-'; @@ -203,7 +206,11 @@ function useGetColumns( const isPrimaryOwner = primary_owner_user_id === user_id; return ( - + diff --git a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts index ccb89e2..bc438a8 100644 --- a/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts +++ b/packages/features/team-accounts/src/server/actions/team-invitations-server-actions.ts @@ -5,6 +5,7 @@ import { redirect } from 'next/navigation'; import { z } from 'zod'; +import { AccountBalanceService } from '@kit/accounts/services/account-balance.service'; import { enhanceAction } from '@kit/next/actions'; import { createNotificationsApi } from '@kit/notifications/api'; import { getLogger } from '@kit/shared/logger'; @@ -18,7 +19,6 @@ import { RenewInvitationSchema } from '../../schema/renew-invitation.schema'; import { UpdateInvitationSchema } from '../../schema/update-invitation.schema'; import { createAccountInvitationsService } from '../services/account-invitations.service'; import { createAccountPerSeatBillingService } from '../services/account-per-seat-billing.service'; -import { AccountBalanceService } from '@kit/accounts/services/account-balance.service'; /** * @name createInvitationsAction diff --git a/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts b/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts index d1d6aa4..c7c7fb0 100644 --- a/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts +++ b/packages/features/team-accounts/src/server/services/webhooks/account-webhooks.service.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; -import { getLogger } from '@kit/shared/logger'; import type { Account } from '@kit/accounts/types/accounts'; +import { getLogger } from '@kit/shared/logger'; export function createAccountWebhooksService() { return new AccountWebhooksService(); diff --git a/packages/features/user-analyses/src/server/api.ts b/packages/features/user-analyses/src/server/api.ts index ec01587..93222ce 100644 --- a/packages/features/user-analyses/src/server/api.ts +++ b/packages/features/user-analyses/src/server/api.ts @@ -4,7 +4,10 @@ import type { UuringuVastus } from '@kit/shared/types/medipost-analysis'; import { toArray } from '@kit/shared/utils'; import { Database } from '@kit/supabase/database'; -import type { AnalysisOrder, AnalysisOrderStatus } from '../types/analysis-orders'; +import type { + AnalysisOrder, + AnalysisOrderStatus, +} from '../types/analysis-orders'; import type { AnalysisResultDetailsElement, AnalysisResultDetailsMapped, @@ -462,8 +465,10 @@ class UserAnalysesApi { }) { const orderIdParam = orderId; const medusaOrderIdParam = medusaOrderId; - - console.info(`Updating order id=${orderId} medusaOrderId=${medusaOrderId} status=${orderStatus}`); + + console.info( + `Updating order id=${orderId} medusaOrderId=${medusaOrderId} status=${orderStatus}`, + ); if (!orderIdParam && !medusaOrderIdParam) { throw new Error('Either orderId or medusaOrderId must be provided'); } diff --git a/packages/shared/src/config/team-account-navigation.config.tsx b/packages/shared/src/config/team-account-navigation.config.tsx index ee15275..45d0867 100644 --- a/packages/shared/src/config/team-account-navigation.config.tsx +++ b/packages/shared/src/config/team-account-navigation.config.tsx @@ -2,8 +2,8 @@ import { Euro, LayoutDashboard, Settings, Users } from 'lucide-react'; import { NavigationConfigSchema } from '@kit/ui/navigation-schema'; -import pathsConfig from './paths.config'; import featureFlagsConfig from './feature-flags.config'; +import pathsConfig from './paths.config'; const iconClasses = 'w-4'; @@ -23,10 +23,10 @@ const getRoutes = (account: string) => [ }, featureFlagsConfig.enableTeamAccountBilling ? { - label: 'common:routes.billing', - path: createPath(pathsConfig.app.accountBilling, account), - Icon: , - } + label: 'common:routes.billing', + path: createPath(pathsConfig.app.accountBilling, account), + Icon: , + } : undefined, { label: 'common:routes.companySettings', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff117b9..3506ae7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,10 +87,10 @@ importers: version: 2.10.1(react@19.1.0) '@medusajs/js-sdk': specifier: latest - version: 2.10.2(awilix@8.0.1) + version: 2.10.3(awilix@8.0.1) '@medusajs/ui': specifier: latest - version: 4.0.22(@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.9.2) + version: 4.0.23(@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.9.2) '@nosecone/next': specifier: 1.0.0-beta.7 version: 1.0.0-beta.7(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) @@ -196,10 +196,10 @@ importers: version: link:tooling/typescript '@medusajs/types': specifier: latest - version: 2.10.2(awilix@8.0.1) + version: 2.10.3(awilix@8.0.1) '@medusajs/ui-preset': specifier: latest - version: 2.10.2(tailwindcss@4.1.7) + version: 2.10.3(tailwindcss@4.1.7) '@next/bundle-analyzer': specifier: 15.3.2 version: 15.3.2 @@ -560,6 +560,9 @@ importers: '@react-email/components': specifier: 0.0.41 version: 0.0.41(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-email: + specifier: 4.2.12 + version: 4.2.12 devDependencies: '@kit/i18n': specifier: workspace:* @@ -567,6 +570,9 @@ importers: '@kit/tsconfig': specifier: workspace:* version: link:../../tooling/typescript + '@react-email/preview-server': + specifier: 4.2.12 + version: 4.2.12(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2)) packages/features/accounts: dependencies: @@ -798,10 +804,10 @@ importers: version: 2.2.7(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.10.2(awilix@8.0.1) + version: 2.10.3(awilix@8.0.1) '@medusajs/ui': specifier: latest - version: 4.0.22(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.9.2) + version: 4.0.23(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.9.2) '@radix-ui/react-accordion': specifier: ^1.2.1 version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -847,10 +853,10 @@ importers: version: 7.28.3 '@medusajs/types': specifier: latest - version: 2.10.2(awilix@8.0.1) + version: 2.10.3(awilix@8.0.1) '@medusajs/ui-preset': specifier: latest - version: 2.10.2(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2))) + version: 2.10.3(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2))) '@types/lodash': specifier: ^4.14.195 version: 4.17.20 @@ -1618,6 +1624,13 @@ packages: } engines: { node: '>=6.9.0' } + '@babel/core@7.26.10': + resolution: + { + integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==, + } + engines: { node: '>=6.9.0' } + '@babel/core@7.28.3': resolution: { @@ -1704,6 +1717,14 @@ packages: } engines: { node: '>=6.9.0' } + '@babel/parser@7.27.0': + resolution: + { + integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==, + } + engines: { node: '>=6.0.0' } + hasBin: true + '@babel/parser@7.27.5': resolution: { @@ -1876,6 +1897,13 @@ packages: } engines: { node: '>=6.9.0' } + '@babel/traverse@7.27.0': + resolution: + { + integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==, + } + engines: { node: '>=6.9.0' } + '@babel/traverse@7.27.4': resolution: { @@ -2085,6 +2113,465 @@ packages: integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==, } + '@esbuild/aix-ppc64@0.25.0': + resolution: + { + integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.10': + resolution: + { + integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.0': + resolution: + { + integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.10': + resolution: + { + integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: + { + integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: + { + integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: + { + integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: + { + integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.0': + resolution: + { + integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.10': + resolution: + { + integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: + { + integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: + { + integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: + { + integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: + { + integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: + { + integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: + { + integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.0': + resolution: + { + integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.10': + resolution: + { + integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: + { + integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: + { + integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: + { + integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: + { + integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: + { + integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==, + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: + { + integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==, + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: + { + integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==, + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: + { + integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==, + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: + { + integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: + { + integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: + { + integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==, + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: + { + integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==, + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: + { + integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==, + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: + { + integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==, + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: + { + integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: + { + integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: + { + integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: + { + integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: + { + integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: + { + integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: + { + integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.0': + resolution: + { + integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: + { + integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: + { + integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.0': + resolution: + { + integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.10': + resolution: + { + integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.0': + resolution: + { + integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.10': + resolution: + { + integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: + { + integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: + { + integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.0': + resolution: + { + integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: + { + integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.7.0': resolution: { @@ -2344,6 +2831,15 @@ packages: } engines: { node: '>=18.18' } + '@img/sharp-darwin-arm64@0.34.1': + resolution: + { + integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-arm64@0.34.2': resolution: { @@ -2362,6 +2858,15 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-x64@0.34.1': + resolution: + { + integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + '@img/sharp-darwin-x64@0.34.2': resolution: { @@ -2524,6 +3029,15 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-linux-arm64@0.34.1': + resolution: + { + integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + '@img/sharp-linux-arm64@0.34.2': resolution: { @@ -2542,6 +3056,15 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-linux-arm@0.34.1': + resolution: + { + integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + '@img/sharp-linux-arm@0.34.2': resolution: { @@ -2569,6 +3092,15 @@ packages: cpu: [ppc64] os: [linux] + '@img/sharp-linux-s390x@0.34.1': + resolution: + { + integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + '@img/sharp-linux-s390x@0.34.2': resolution: { @@ -2587,6 +3119,15 @@ packages: cpu: [s390x] os: [linux] + '@img/sharp-linux-x64@0.34.1': + resolution: + { + integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + '@img/sharp-linux-x64@0.34.2': resolution: { @@ -2605,6 +3146,15 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: + { + integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + '@img/sharp-linuxmusl-arm64@0.34.2': resolution: { @@ -2623,6 +3173,15 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: + { + integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + '@img/sharp-linuxmusl-x64@0.34.2': resolution: { @@ -2641,6 +3200,14 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-wasm32@0.34.1': + resolution: + { + integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + '@img/sharp-wasm32@0.34.2': resolution: { @@ -2675,6 +3242,15 @@ packages: cpu: [arm64] os: [win32] + '@img/sharp-win32-ia32@0.34.1': + resolution: + { + integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + '@img/sharp-win32-ia32@0.34.2': resolution: { @@ -2693,6 +3269,15 @@ packages: cpu: [ia32] os: [win32] + '@img/sharp-win32-x64@0.34.1': + resolution: + { + integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + '@img/sharp-win32-x64@0.34.2': resolution: { @@ -2735,6 +3320,20 @@ packages: integrity: sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==, } + '@isaacs/balanced-match@4.0.1': + resolution: + { + integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==, + } + engines: { node: 20 || >=22 } + + '@isaacs/brace-expansion@5.0.0': + resolution: + { + integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==, + } + engines: { node: 20 || >=22 } + '@isaacs/cliui@8.0.2': resolution: { @@ -3128,6 +3727,20 @@ packages: } engines: { node: '>=20' } + '@lottiefiles/dotlottie-react@0.13.3': + resolution: + { + integrity: sha512-V4FfdYlqzjBUX7f0KV6vfQOOI0Cp+3XeG/ZqSDFSEVg5P7fpROpDv5/I9aTM8sOCESK1SWT96Fem+QVUnBV1wQ==, + } + peerDependencies: + react: ^17 || ^18 || ^19 + + '@lottiefiles/dotlottie-web@0.42.0': + resolution: + { + integrity: sha512-Zr2LCaOAoPCsdAQgeLyCSiQ1+xrAJtRCyuEYDj0qR5heUwpc+Pxbb88JyTVumcXFfKOBMOMmrlsTScLz2mrvQQ==, + } + '@makerkit/data-loader-supabase-core@0.0.10': resolution: { @@ -3195,25 +3808,25 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - '@medusajs/icons@2.10.2': + '@medusajs/icons@2.10.3': resolution: { - integrity: sha512-ZZFEWTGdQGvsRPs5ANV9GlFUFyba852cqWtRu7aO3QlPhk+ECbWqJYkhL3h/HZ1twN5nEG51QAncUkjm5TVBRw==, + integrity: sha512-w1NQzNY/cTsfjuEQ8AOpEI49mxA6Oa6tJlg4HMfpOjDl3/R2qZtQXa8DIchSr3Sx+/RTsIGYUxv4Vbd+VTszJg==, } peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - '@medusajs/js-sdk@2.10.2': + '@medusajs/js-sdk@2.10.3': resolution: { - integrity: sha512-P6A9E5LQkG/g9YiHlvUbH/k777XTfnY47neI1EmA56B66XBVIwzTqUglluREVZ+akjlI0eOxpwk2PDFPVaK1ZQ==, + integrity: sha512-S2B6ViUFrZeCQnshJCjuhzLJmcb3eIVvdl/W4u/TcQGSF+cMXQGEsB9T7QUC6pyloY2+g5tTYxO+ND8s0enTCg==, } engines: { node: '>=20' } - '@medusajs/types@2.10.2': + '@medusajs/types@2.10.3': resolution: { - integrity: sha512-yonCLLnO2FFDB+HCo6nAGz+I7u+7ux+HDJCT8KUPlZJ5/6AYZcgdsuNWMwaa441yjEXDDdZmGZtbSM37lBFoxA==, + integrity: sha512-Rx1H4LZ17AsOspaFAPpMwpi2rxj14ZWSip+1FA28GZourviWCLR2i3Er0b6SqJdh861fH6RhSyNaYVEKX6rk/w==, } engines: { node: '>=20' } peerDependencies: @@ -3226,18 +3839,18 @@ packages: vite: optional: true - '@medusajs/ui-preset@2.10.2': + '@medusajs/ui-preset@2.10.3': resolution: { - integrity: sha512-t3VmloSbeZOjfyYL+iQTU94wutX0/B+uv9wz8L1MK6CErHK4T4ox246QDsNAR2QrNE0EUEEy+SX0qiW+xHFsMQ==, + integrity: sha512-rpWJtg4pLOxKYWA9sqhWkOgG2B0npyGO4nMqqAOh4Xl/LNCkeyeQ3Hmwj0v2mH8jhum65E/mxDVpjQ/lWFVpqQ==, } peerDependencies: tailwindcss: '>=3.0.0' - '@medusajs/ui@4.0.22': + '@medusajs/ui@4.0.23': resolution: { - integrity: sha512-p3Nl6OTyxe583VVmvGAYbizFFPzr9GsZzbO54Ku9jZqf+MYNqdDjKFP28K5QHkpLpbvkGU05BQZ5d5H49vGxiQ==, + integrity: sha512-jXQEIv6nluO+P5KxPrS9QYfQ2U/HO3Vl2cmZZVMdaNB1ol3y7pcQq5FEs00IgyjQv4DsK209bj8cicqnvVvRWA==, } peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc @@ -4066,6 +4679,12 @@ packages: integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==, } + '@radix-ui/colors@3.0.0': + resolution: + { + integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==, + } + '@radix-ui/number@1.1.0': resolution: { @@ -4208,6 +4827,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.4': + resolution: + { + integrity: sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.6': resolution: { @@ -4368,6 +5003,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collapsible@1.1.7': + resolution: + { + integrity: sha512-zGFsPcFJNdQa/UNd6MOgF40BS054FIGj32oOWBllixz42f+AkQg3QJ1YT9pw7vs+Ai+EgWkh839h69GEK8oH2A==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.1': resolution: { @@ -4384,6 +5035,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.4': + resolution: + { + integrity: sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.6': resolution: { @@ -4600,6 +5267,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.7': + resolution: + { + integrity: sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dismissable-layer@1.1.9': resolution: { @@ -4616,6 +5299,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.10': + resolution: + { + integrity: sha512-8qnILty92BmXbxKugWX3jgEeFeMoxtdggeCCxb/aB7l34QFAKB23IhJfnwyVMbRnAUJiT5LOay4kUS22+AWuRg==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-dropdown-menu@2.1.16': resolution: { @@ -4660,6 +5359,18 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-guards@1.1.2': + resolution: + { + integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-focus-guards@1.1.3': resolution: { @@ -4688,6 +5399,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-focus-scope@1.1.4': + resolution: + { + integrity: sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-scope@1.1.7': resolution: { @@ -4800,6 +5527,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.10': + resolution: + { + integrity: sha512-OupA+1PrVf2H0K4jIwkDyA+rsJ7vF1y/VxLEO43dmZ68GtCjvx9K1/B/QscPZM3jIeFNK/wPd0HmiLjT36hVcA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.16': resolution: { @@ -4880,6 +5623,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.10': + resolution: + { + integrity: sha512-IZN7b3sXqajiPsOzKuNJBSP9obF4MX5/5UhTgWNofw4r1H+eATWb0SyMlaxPD/kzA4vadFgy1s7Z1AEJ6WMyHQ==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.15': resolution: { @@ -4928,6 +5687,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.2.4': + resolution: + { + integrity: sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.6': resolution: { @@ -4976,6 +5751,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.6': + resolution: + { + integrity: sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.8': resolution: { @@ -5024,6 +5815,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.3': + resolution: + { + integrity: sha512-IrVLIhskYhH3nLvtcBLQFZr61tBG7wx7O3kEmdzcYwRGAEBmBicGGL7ATzNgruYJ3xBTbuzEEq9OXJM3PAX3tA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.4': resolution: { @@ -5072,6 +5879,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.0': + resolution: + { + integrity: sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.2': resolution: { @@ -5200,6 +6023,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.6': + resolution: + { + integrity: sha512-D2ReXCuIueKf5L2f1ks/wTj3bWck1SvK1pjLmEHPbwksS1nOHBsvgY0b9Hypt81FczqBqSyLHQxn/vbsQ0gDHw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-scroll-area@1.2.10': resolution: { @@ -5324,6 +6163,18 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.0': + resolution: + { + integrity: sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-slot@1.2.2': resolution: { @@ -5412,6 +6263,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tabs@1.1.7': + resolution: + { + integrity: sha512-sawt4HkD+6haVGjYOC3BMIiCumBpqTK6o407n6zN/6yReed2EN7bXyykNrpqg+xCfudpBUZg7Y2cJBd/x/iybA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toast@1.2.15': resolution: { @@ -5460,6 +6327,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle-group@1.1.6': + resolution: + { + integrity: sha512-XOBq9VqC+mIn5hzjGdJLhQbvQeiOpV5ExNE6qMQQPvFsCT44QUcxFzYytTWVoyWg9XKfgrleKmTeEyu6aoTPhg==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle@1.1.1': resolution: { @@ -5476,6 +6359,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle@1.1.6': + resolution: + { + integrity: sha512-3SeJxKeO3TO1zVw1Nl++Cp0krYk6zHDHMCUXXVkosIzl6Nxcvb07EerQpyD2wXQSJ5RZajrYAmPaydU8Hk1IyQ==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toolbar@1.1.1': resolution: { @@ -5508,6 +6407,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.2.3': + resolution: + { + integrity: sha512-0KX7jUYFA02np01Y11NWkk6Ip6TqMNmD4ijLelYAzeIndl2aVeltjJFJ2gwjNa1P8U/dgjQ+8cr9Y3Ni+ZNoRA==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.6': resolution: { @@ -5732,6 +6647,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.0': + resolution: + { + integrity: sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-visually-hidden@1.2.2': resolution: { @@ -6626,6 +7557,12 @@ packages: peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/preview-server@4.2.12': + resolution: + { + integrity: sha512-FVM3h6vJQdjk5E3P8ts8zAPeZSyefcIDWGmy/Tnwl9zVlwiGcQSjGVPmAHlw1DdJnkK79OeC/VYisMlq0w6W8Q==, + } + '@react-email/preview@0.0.13': resolution: { @@ -7586,6 +8523,12 @@ packages: integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==, } + '@socket.io/component-emitter@3.1.2': + resolution: + { + integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==, + } + '@standard-schema/utils@0.3.0': resolution: { @@ -8007,6 +8950,12 @@ packages: integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==, } + '@types/cors@2.8.19': + resolution: + { + integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==, + } + '@types/d3-array@3.2.1': resolution: { @@ -8205,6 +9154,12 @@ packages: integrity: sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==, } + '@types/node@22.14.1': + resolution: + { + integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==, + } + '@types/node@22.18.0': resolution: { @@ -8223,6 +9178,12 @@ packages: integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==, } + '@types/normalize-path@3.0.2': + resolution: + { + integrity: sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==, + } + '@types/parse-json@4.0.2': resolution: { @@ -8273,6 +9234,14 @@ packages: peerDependencies: '@types/react': ^18.0.0 + '@types/react-dom@19.0.4': + resolution: + { + integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==, + } + peerDependencies: + '@types/react': ^19.0.0 + '@types/react-dom@19.1.5': resolution: { @@ -8299,6 +9268,12 @@ packages: integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==, } + '@types/react@19.0.10': + resolution: + { + integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==, + } + '@types/react@19.1.4': resolution: { @@ -8335,6 +9310,12 @@ packages: integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, } + '@types/webpack@5.28.5': + resolution: + { + integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==, + } + '@types/ws@8.18.1': resolution: { @@ -8787,6 +9768,13 @@ packages: integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==, } + accepts@1.3.8: + resolution: + { + integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, + } + engines: { node: '>= 0.6' } + acorn-import-assertions@1.9.0: resolution: { @@ -9239,6 +10227,19 @@ packages: integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, } + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + base64id@2.0.0: + resolution: + { + integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==, + } + engines: { node: ^4.5.0 || >= 5.9 } + big.js@5.2.2: resolution: { @@ -9265,6 +10266,12 @@ packages: } engines: { node: '>=8' } + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + boolbase@1.0.0: resolution: { @@ -9331,6 +10338,12 @@ packages: integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, } + buffer@5.7.1: + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, + } + busboy@1.6.0: resolution: { @@ -9431,6 +10444,13 @@ packages: } engines: { node: '>=10' } + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + char-regex@1.0.2: resolution: { @@ -9469,6 +10489,13 @@ packages: } engines: { node: '>= 8.10.0' } + chokidar@4.0.3: + resolution: + { + integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, + } + engines: { node: '>= 14.16.0' } + chownr@3.0.0: resolution: { @@ -9497,6 +10524,12 @@ packages: } engines: { node: '>=8' } + citty@0.1.6: + resolution: + { + integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==, + } + cjs-module-lexer@1.4.3: resolution: { @@ -9515,6 +10548,27 @@ packages: integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, } + cli-cursor@3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, + } + engines: { node: '>=8' } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, + } + engines: { node: '>=18' } + + cli-spinners@2.9.2: + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==, + } + engines: { node: '>=6' } + client-only@0.0.1: resolution: { @@ -9528,6 +10582,13 @@ packages: } engines: { node: '>=12' } + clone@1.0.4: + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, + } + engines: { node: '>=0.8' } + clsx@1.2.1: resolution: { @@ -9630,6 +10691,13 @@ packages: } engines: { node: '>=16' } + commander@13.1.0: + resolution: + { + integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, + } + engines: { node: '>=18' } + commander@2.20.3: resolution: { @@ -9674,6 +10742,19 @@ packages: integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, } + confbox@0.2.2: + resolution: + { + integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==, + } + + consola@3.4.2: + resolution: + { + integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, + } + engines: { node: ^14.18.0 || >=16.10.0 } + convert-source-map@1.9.0: resolution: { @@ -9686,6 +10767,13 @@ packages: integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, } + cookie@0.7.2: + resolution: + { + integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==, + } + engines: { node: '>= 0.6' } + cookie@1.0.2: resolution: { @@ -9699,6 +10787,13 @@ packages: integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==, } + cors@2.8.5: + resolution: + { + integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==, + } + engines: { node: '>= 0.10' } + cosmiconfig@7.1.0: resolution: { @@ -9951,6 +11046,13 @@ packages: integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==, } + debounce@2.2.0: + resolution: + { + integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==, + } + engines: { node: '>=18' } + debug@3.2.7: resolution: { @@ -9962,6 +11064,18 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: + { + integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==, + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.1: resolution: { @@ -10016,6 +11130,12 @@ packages: } engines: { node: '>=0.10.0' } + defaults@1.0.4: + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, + } + define-data-property@1.1.4: resolution: { @@ -10205,6 +11325,12 @@ packages: } engines: { node: '>=12' } + emoji-regex@10.5.0: + resolution: + { + integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==, + } + emoji-regex@8.0.0: resolution: { @@ -10230,6 +11356,26 @@ packages: integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, } + engine.io-client@6.6.3: + resolution: + { + integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==, + } + + engine.io-parser@5.2.3: + resolution: + { + integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==, + } + engines: { node: '>=10.0.0' } + + engine.io@6.6.4: + resolution: + { + integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==, + } + engines: { node: '>=10.2.0' } + enhanced-resolve@5.18.1: resolution: { @@ -10319,6 +11465,22 @@ packages: } engines: { node: '>= 0.4' } + esbuild@0.25.0: + resolution: + { + integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==, + } + engines: { node: '>=18' } + hasBin: true + + esbuild@0.25.10: + resolution: + { + integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==, + } + engines: { node: '>=18' } + hasBin: true + escalade@3.2.0: resolution: { @@ -10640,6 +11802,12 @@ packages: } engines: { node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0 } + exsolve@1.0.7: + resolution: + { + integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==, + } + facepaint@1.2.1: resolution: { @@ -10873,6 +12041,23 @@ packages: integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, } + framer-motion@12.23.12: + resolution: + { + integrity: sha512-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg==, + } + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fs.realpath@1.0.0: resolution: { @@ -10920,6 +12105,13 @@ packages: } engines: { node: 6.* || 8.* || >= 10.* } + get-east-asian-width@1.4.0: + resolution: + { + integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==, + } + engines: { node: '>=18' } + get-intrinsic@1.3.0: resolution: { @@ -10995,6 +12187,14 @@ packages: } hasBin: true + glob@11.0.3: + resolution: + { + integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==, + } + engines: { node: 20 || >=22 } + hasBin: true + glob@7.2.3: resolution: { @@ -11135,6 +12335,13 @@ packages: } engines: { node: '>= 0.4' } + he@1.2.0: + resolution: + { + integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==, + } + hasBin: true + help-me@5.0.0: resolution: { @@ -11228,6 +12435,12 @@ packages: integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==, } + ieee754@1.2.1: + resolution: + { + integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, + } + ignore@5.3.2: resolution: { @@ -11489,6 +12702,20 @@ packages: integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==, } + is-interactive@1.0.0: + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, + } + engines: { node: '>=8' } + + is-interactive@2.0.0: + resolution: + { + integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==, + } + engines: { node: '>=12' } + is-map@2.0.3: resolution: { @@ -11579,6 +12806,27 @@ packages: } engines: { node: '>= 0.4' } + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: '>=10' } + + is-unicode-supported@1.3.0: + resolution: + { + integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==, + } + engines: { node: '>=12' } + + is-unicode-supported@2.1.0: + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, + } + engines: { node: '>=18' } + is-weakmap@2.0.2: resolution: { @@ -11686,6 +12934,13 @@ packages: integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, } + jackspeak@4.1.1: + resolution: + { + integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==, + } + engines: { node: 20 || >=22 } + javascript-natural-sort@0.7.1: resolution: { @@ -12104,6 +13359,13 @@ packages: } hasBin: true + jiti@2.4.2: + resolution: + { + integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==, + } + hasBin: true + jiti@2.5.1: resolution: { @@ -12381,6 +13643,13 @@ packages: } engines: { node: '>= 12.0.0' } + lilconfig@2.1.0: + resolution: + { + integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==, + } + engines: { node: '>=10' } + lilconfig@3.1.3: resolution: { @@ -12500,6 +13769,27 @@ packages: integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, } + log-symbols@4.1.0: + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: '>=10' } + + log-symbols@6.0.0: + resolution: + { + integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==, + } + engines: { node: '>=18' } + + log-symbols@7.0.1: + resolution: + { + integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==, + } + engines: { node: '>=18' } + long@5.3.2: resolution: { @@ -12531,6 +13821,13 @@ packages: integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, } + lru-cache@11.2.2: + resolution: + { + integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==, + } + engines: { node: 20 || >=22 } + lru-cache@5.1.1: resolution: { @@ -12916,6 +14213,13 @@ packages: } engines: { node: '>= 0.6' } + mime-db@1.54.0: + resolution: + { + integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==, + } + engines: { node: '>= 0.6' } + mime-types@2.1.35: resolution: { @@ -12923,6 +14227,13 @@ packages: } engines: { node: '>= 0.6' } + mime-types@3.0.1: + resolution: + { + integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==, + } + engines: { node: '>= 0.6' } + mimic-fn@2.1.0: resolution: { @@ -12930,6 +14241,13 @@ packages: } engines: { node: '>=6' } + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, + } + engines: { node: '>=18' } + mini-svg-data-uri@1.4.4: resolution: { @@ -12937,6 +14255,13 @@ packages: } hasBin: true + minimatch@10.0.3: + resolution: + { + integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==, + } + engines: { node: 20 || >=22 } + minimatch@3.1.2: resolution: { @@ -12998,6 +14323,18 @@ packages: integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==, } + motion-dom@12.23.21: + resolution: + { + integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==, + } + + motion-utils@12.23.6: + resolution: + { + integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==, + } + mrmime@2.0.1: resolution: { @@ -13047,6 +14384,13 @@ packages: integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, } + negotiator@0.6.3: + resolution: + { + integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==, + } + engines: { node: '>= 0.6' } + neo-async@2.6.2: resolution: { @@ -13153,6 +14497,12 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + node-html-parser@7.0.1: + resolution: + { + integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==, + } + node-int64@0.4.0: resolution: { @@ -13213,6 +14563,14 @@ packages: integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, } + nypm@0.6.0: + resolution: + { + integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==, + } + engines: { node: ^14.16.0 || >=16.10.0 } + hasBin: true + object-assign@4.1.1: resolution: { @@ -13296,6 +14654,13 @@ packages: } engines: { node: '>=6' } + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, + } + engines: { node: '>=18' } + openai@5.20.3: resolution: { @@ -13325,6 +14690,20 @@ packages: } engines: { node: '>= 0.8.0' } + ora@5.4.1: + resolution: + { + integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==, + } + engines: { node: '>=10' } + + ora@8.2.0: + resolution: + { + integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==, + } + engines: { node: '>=18' } + orderedmap@2.1.1: resolution: { @@ -13451,6 +14830,13 @@ packages: } engines: { node: '>=16 || 14 >=14.18' } + path-scurry@2.0.0: + resolution: + { + integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==, + } + engines: { node: 20 || >=22 } + path-type@4.0.0: resolution: { @@ -13458,6 +14844,12 @@ packages: } engines: { node: '>=8' } + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + peberminta@0.9.0: resolution: { @@ -13595,6 +14987,12 @@ packages: } engines: { node: '>=8' } + pkg-types@2.3.0: + resolution: + { + integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==, + } + possible-typed-array-names@1.1.0: resolution: { @@ -14028,6 +15426,13 @@ packages: engines: { node: '>=14' } hasBin: true + pretty-bytes@6.1.1: + resolution: + { + integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==, + } + engines: { node: ^14.13.1 || >=16.0.0 } + pretty-format@29.7.0: resolution: { @@ -14258,6 +15663,14 @@ packages: date-fns: ^2.28.0 || ^3.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom@19.0.0: + resolution: + { + integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==, + } + peerDependencies: + react: ^19.0.0 + react-dom@19.0.0-rc-66855b96-20241106: resolution: { @@ -14274,6 +15687,14 @@ packages: peerDependencies: react: ^19.1.0 + react-email@4.2.12: + resolution: + { + integrity: sha512-Cp6nYAG9uL4//X/96wDSKxGqVTXsaRMuD1ub0G0iBcfTqLel4VVre3kicq7RraplDuj+ATlqSZu47d7P2ULP5w==, + } + engines: { node: '>=18.0.0' } + hasBin: true + react-error-boundary@4.1.2: resolution: { @@ -14402,6 +15823,13 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' + react@19.0.0: + resolution: + { + integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==, + } + engines: { node: '>=0.10.0' } + react@19.0.0-rc-66855b96-20241106: resolution: { @@ -14429,6 +15857,13 @@ packages: } engines: { node: ^18.17.0 || >=20.5.0 } + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: '>= 6' } + readdirp@3.6.0: resolution: { @@ -14436,6 +15871,13 @@ packages: } engines: { node: '>=8.10.0' } + readdirp@4.1.2: + resolution: + { + integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, + } + engines: { node: '>= 14.18.0' } + real-require@0.2.0: resolution: { @@ -14556,6 +15998,20 @@ packages: } hasBin: true + restore-cursor@3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, + } + engines: { node: '>=8' } + + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, + } + engines: { node: '>=18' } + reusify@1.1.0: resolution: { @@ -14623,6 +16079,12 @@ packages: integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==, } + scheduler@0.25.0: + resolution: + { + integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==, + } + scheduler@0.25.0-rc-66855b96-20241106: resolution: { @@ -14721,6 +16183,13 @@ packages: } engines: { node: '>= 0.4' } + sharp@0.34.1: + resolution: + { + integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==, + } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + sharp@0.34.2: resolution: { @@ -14846,6 +16315,33 @@ packages: integrity: sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==, } + socket.io-adapter@2.5.5: + resolution: + { + integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==, + } + + socket.io-client@4.8.1: + resolution: + { + integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==, + } + engines: { node: '>=10.0.0' } + + socket.io-parser@4.2.4: + resolution: + { + integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==, + } + engines: { node: '>=10.0.0' } + + socket.io@4.8.1: + resolution: + { + integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==, + } + engines: { node: '>=10.2.0' } + sonic-boom@4.2.0: resolution: { @@ -14861,6 +16357,15 @@ packages: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + sonner@2.0.3: + resolution: + { + integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==, + } + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + sonner@2.0.7: resolution: { @@ -14903,6 +16408,12 @@ packages: } engines: { node: '>=0.10.0' } + spamc@0.0.5: + resolution: + { + integrity: sha512-jYXItuZuiWZyG9fIdvgTUbp2MNRuyhuSwvvhhpPJd4JK/9oSZxkD7zAj53GJtowSlXwCJzLg6sCKAoE9wXsKgg==, + } + split2@4.2.0: resolution: { @@ -14936,6 +16447,13 @@ packages: } engines: { node: '>=6' } + stdin-discarder@0.2.2: + resolution: + { + integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==, + } + engines: { node: '>=18' } + stop-iteration-iterator@1.1.0: resolution: { @@ -14971,6 +16489,13 @@ packages: } engines: { node: '>=12' } + string-width@7.2.0: + resolution: + { + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, + } + engines: { node: '>=18' } + string.prototype.includes@2.0.1: resolution: { @@ -15012,6 +16537,12 @@ packages: } engines: { node: '>= 0.4' } + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + stringify-entities@4.0.4: resolution: { @@ -15180,6 +16711,12 @@ packages: integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==, } + tailwind-merge@3.2.0: + resolution: + { + integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==, + } + tailwind-merge@3.3.1: resolution: { @@ -15200,6 +16737,14 @@ packages: integrity: sha512-N49SnciSeRgLC+VK+Fu5VULNJIvJUvN7tUKF1kEHPXrS76WAlwrSVthCbJ9NUw0Cj/ptxs73pdVEdosomAN5Lg==, } + tailwindcss@3.4.0: + resolution: + { + integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==, + } + engines: { node: '>=14.0.0' } + hasBin: true + tailwindcss@3.4.17: resolution: { @@ -15318,6 +16863,12 @@ packages: integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==, } + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + tinyglobby@0.2.14: resolution: { @@ -15437,6 +16988,13 @@ packages: integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==, } + tsconfig-paths@4.2.0: + resolution: + { + integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==, + } + engines: { node: '>=6' } + tslib@2.8.1: resolution: { @@ -15699,6 +17257,15 @@ packages: '@types/react': optional: true + use-debounce@10.0.4: + resolution: + { + integrity: sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==, + } + engines: { node: '>= 16.0.0' } + peerDependencies: + react: '*' + use-sidecar@1.1.3: resolution: { @@ -15746,6 +17313,13 @@ packages: } engines: { node: '>=10.12.0' } + vary@1.1.2: + resolution: + { + integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, + } + engines: { node: '>= 0.8' } + vfile-message@4.0.2: resolution: { @@ -15784,6 +17358,12 @@ packages: } engines: { node: '>=10.13.0' } + wcwidth@1.0.1: + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, + } + web-streams-polyfill@3.3.3: resolution: { @@ -15960,6 +17540,21 @@ packages: utf-8-validate: optional: true + ws@8.17.1: + resolution: + { + integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==, + } + engines: { node: '>=10.0.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.2: resolution: { @@ -15975,6 +17570,13 @@ packages: utf-8-validate: optional: true + xmlhttprequest-ssl@2.1.2: + resolution: + { + integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==, + } + engines: { node: '>=0.4.0' } + xtend@4.0.2: resolution: { @@ -16082,12 +17684,25 @@ packages: } engines: { node: '>=10' } + yoctocolors@2.1.2: + resolution: + { + integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==, + } + engines: { node: '>=18' } + yup@1.7.0: resolution: { integrity: sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==, } + zod@3.24.3: + resolution: + { + integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==, + } + zod@4.1.5: resolution: { @@ -16199,6 +17814,26 @@ snapshots: '@babel/compat-data@7.27.5': {} + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.26.10) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.28.3': dependencies: '@ampproject/remapping': 2.3.0 @@ -16252,6 +17887,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -16274,6 +17918,10 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.2 + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.28.2 + '@babel/parser@7.27.5': dependencies: '@babel/types': 7.27.6 @@ -16375,6 +18023,18 @@ snapshots: '@babel/parser': 7.28.3 '@babel/types': 7.28.2 + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/traverse@7.27.4': dependencies: '@babel/code-frame': 7.27.1 @@ -16473,9 +18133,9 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tslib: 2.8.1 - '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.0.0-rc-66855b96-20241106)': dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@dnd-kit/utilities': 3.2.2(react@19.0.0-rc-66855b96-20241106) react: 19.0.0-rc-66855b96-20241106 tslib: 2.8.1 @@ -16578,6 +18238,159 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} + '@esbuild/aix-ppc64@0.25.0': + optional: true + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.0': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.0': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.0': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.0': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.0': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@2.5.1))': dependencies: eslint: 9.34.0(jiti@2.5.1) @@ -16664,6 +18477,12 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@floating-ui/react-dom@2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@floating-ui/react-dom@2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/dom': 1.7.4 @@ -16760,6 +18579,11 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/sharp-darwin-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.1.0 + optional: true + '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.1.0 @@ -16770,6 +18594,11 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.0 optional: true + '@img/sharp-darwin-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.1.0 + optional: true + '@img/sharp-darwin-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.1.0 @@ -16834,6 +18663,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64@1.2.0': optional: true + '@img/sharp-linux-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.1.0 + optional: true + '@img/sharp-linux-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.1.0 @@ -16844,6 +18678,11 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.0 optional: true + '@img/sharp-linux-arm@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.1.0 + optional: true + '@img/sharp-linux-arm@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.1.0 @@ -16859,6 +18698,11 @@ snapshots: '@img/sharp-libvips-linux-ppc64': 1.2.0 optional: true + '@img/sharp-linux-s390x@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.1.0 + optional: true + '@img/sharp-linux-s390x@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.1.0 @@ -16869,6 +18713,11 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.0 optional: true + '@img/sharp-linux-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.1.0 + optional: true + '@img/sharp-linux-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.1.0 @@ -16879,6 +18728,11 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.0 optional: true + '@img/sharp-linuxmusl-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + optional: true + '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 @@ -16889,6 +18743,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 optional: true + '@img/sharp-linuxmusl-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + optional: true + '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.1.0 @@ -16899,6 +18758,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.0 optional: true + '@img/sharp-wasm32@0.34.1': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + '@img/sharp-wasm32@0.34.2': dependencies: '@emnapi/runtime': 1.4.3 @@ -16915,12 +18779,18 @@ snapshots: '@img/sharp-win32-arm64@0.34.3': optional: true + '@img/sharp-win32-ia32@0.34.1': + optional: true + '@img/sharp-win32-ia32@0.34.2': optional: true '@img/sharp-win32-ia32@0.34.3': optional: true + '@img/sharp-win32-x64@0.34.1': + optional: true + '@img/sharp-win32-x64@0.34.2': optional: true @@ -16944,6 +18814,12 @@ snapshots: dependencies: '@swc/helpers': 0.5.17 + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -17544,6 +19420,13 @@ snapshots: '@lemonsqueezy/lemonsqueezy.js@4.0.0': {} + '@lottiefiles/dotlottie-react@0.13.3(react@19.0.0)': + dependencies: + '@lottiefiles/dotlottie-web': 0.42.0 + react: 19.0.0 + + '@lottiefiles/dotlottie-web@0.42.0': {} + '@makerkit/data-loader-supabase-core@0.0.10(@supabase/postgrest-js@1.19.4)(@supabase/supabase-js@2.49.4)': dependencies: '@supabase/postgrest-js': 1.19.4 @@ -17582,17 +19465,17 @@ snapshots: dependencies: react: 19.1.0 - '@medusajs/icons@2.10.2(react@19.0.0-rc-66855b96-20241106)': + '@medusajs/icons@2.10.3(react@19.0.0-rc-66855b96-20241106)': dependencies: react: 19.0.0-rc-66855b96-20241106 - '@medusajs/icons@2.10.2(react@19.1.0)': + '@medusajs/icons@2.10.3(react@19.1.0)': dependencies: react: 19.1.0 - '@medusajs/js-sdk@2.10.2(awilix@8.0.1)': + '@medusajs/js-sdk@2.10.3(awilix@8.0.1)': dependencies: - '@medusajs/types': 2.10.2(awilix@8.0.1) + '@medusajs/types': 2.10.3(awilix@8.0.1) fetch-event-stream: 0.1.5 qs: 6.14.0 transitivePeerDependencies: @@ -17600,29 +19483,29 @@ snapshots: - ioredis - vite - '@medusajs/types@2.10.2(awilix@8.0.1)': + '@medusajs/types@2.10.3(awilix@8.0.1)': dependencies: awilix: 8.0.1 bignumber.js: 9.3.0 - '@medusajs/ui-preset@2.10.2(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2)))': + '@medusajs/ui-preset@2.10.3(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2)))': dependencies: '@tailwindcss/forms': 0.5.10(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2))) tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2)) tailwindcss-animate: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2))) - '@medusajs/ui-preset@2.10.2(tailwindcss@4.1.7)': + '@medusajs/ui-preset@2.10.3(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.22(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.9.2)': + '@medusajs/ui@4.0.23(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)(typescript@5.9.2)': dependencies: '@dnd-kit/core': 6.3.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) - '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.0.0-rc-66855b96-20241106) '@dnd-kit/utilities': 3.2.2(react@19.0.0-rc-66855b96-20241106) - '@medusajs/icons': 2.10.2(react@19.0.0-rc-66855b96-20241106) + '@medusajs/icons': 2.10.3(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-dialog': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(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) @@ -17644,12 +19527,12 @@ snapshots: - '@types/react-dom' - typescript - '@medusajs/ui@4.0.22(@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.9.2)': + '@medusajs/ui@4.0.23(@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.9.2)': dependencies: '@dnd-kit/core': 6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) '@dnd-kit/utilities': 3.2.2(react@19.1.0) - '@medusajs/icons': 2.10.2(react@19.1.0) + '@medusajs/icons': 2.10.3(react@19.1.0) '@radix-ui/react-dialog': 1.1.4(@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) '@radix-ui/react-dismissable-layer': 1.1.4(@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) '@tanstack/react-table': 8.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -18238,6 +20121,8 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@radix-ui/colors@3.0.0': {} + '@radix-ui/number@1.1.0': {} '@radix-ui/number@1.1.1': {} @@ -18394,6 +20279,15 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-arrow@1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-arrow@1.1.6(@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)': dependencies: '@radix-ui/react-primitive': 2.1.2(@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) @@ -18579,6 +20473,22 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-collapsible@1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-collection@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.24)(react@19.0.0-rc-66855b96-20241106) @@ -18603,6 +20513,18 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-collection@1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-collection@1.1.6(@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)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) @@ -18657,6 +20579,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -18709,6 +20637,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-context@1.1.2(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-context@1.1.2(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -18843,6 +20777,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-direction@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-direction@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -18914,6 +20854,19 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-dismissable-layer@1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-dismissable-layer@1.1.9(@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)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -18927,6 +20880,21 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-dropdown-menu@2.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-menu': 2.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-dropdown-menu@2.1.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)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -18984,6 +20952,12 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -19012,6 +20986,17 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-focus-scope@1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-focus-scope@1.1.7(@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)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) @@ -19110,6 +21095,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-id@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-id@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) @@ -19144,6 +21136,32 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-menu@2.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + aria-hidden: 1.2.6 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.7.1(@types/react@19.0.10)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-menu@2.1.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)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -19324,6 +21342,29 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-popover@1.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + aria-hidden: 1.2.6 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.7.1(@types/react@19.0.10)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-popover@1.1.15(@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)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -19429,6 +21470,24 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-popper@1.2.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-arrow': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/rect': 1.1.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-popper@1.2.6(@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)': dependencies: '@floating-ui/react-dom': 2.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -19485,6 +21544,16 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-portal@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-portal@1.1.8(@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)': dependencies: '@radix-ui/react-primitive': 2.1.2(@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) @@ -19525,6 +21594,16 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-presence@1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-presence@1.1.4(@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)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) @@ -19573,6 +21652,15 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-primitive@2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-primitive@2.1.2(@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)': dependencies: '@radix-ui/react-slot': 1.2.2(@types/react@19.1.4)(react@19.1.0) @@ -19735,6 +21823,23 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-roving-focus@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-scroll-area@1.2.10(@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)': dependencies: '@radix-ui/number': 1.1.1 @@ -19952,6 +22057,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-slot@1.2.0(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-slot@1.2.2(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.4)(react@19.1.0) @@ -20066,6 +22178,22 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-tabs@1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-toast@1.2.15(@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)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -20156,6 +22284,21 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-toggle-group@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-toggle@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -20178,6 +22321,17 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-toggle@1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-toolbar@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/primitive': 1.1.1 @@ -20248,6 +22402,26 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-tooltip@1.2.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-visually-hidden': 1.2.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-tooltip@1.2.6(@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)': dependencies: '@radix-ui/primitive': 1.1.2 @@ -20280,6 +22454,12 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -20308,6 +22488,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.4)(react@19.1.0) @@ -20323,6 +22511,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) @@ -20344,6 +22539,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.4)(react@19.1.0) @@ -20376,6 +22578,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: react: 19.1.0 @@ -20414,6 +22622,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-use-rect@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/rect': 1.1.1 @@ -20435,6 +22650,13 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + '@radix-ui/react-use-size@1.1.1(@types/react@19.0.10)(react@19.0.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.0.10)(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.10 + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.4)(react@19.1.0)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.4)(react@19.1.0) @@ -20460,6 +22682,15 @@ snapshots: '@types/react': 19.1.4 '@types/react-dom': 19.1.5(@types/react@19.1.4) + '@radix-ui/react-visually-hidden@1.2.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@radix-ui/react-visually-hidden@1.2.2(@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)': dependencies: '@radix-ui/react-primitive': 2.1.2(@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) @@ -22239,6 +24470,65 @@ snapshots: md-to-react-email: 5.0.5(react@19.1.0) react: 19.1.0 + '@react-email/preview-server@4.2.12(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2))': + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/traverse': 7.27.0 + '@lottiefiles/dotlottie-react': 0.13.3(react@19.0.0) + '@radix-ui/colors': 3.0.0 + '@radix-ui/react-collapsible': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dropdown-menu': 2.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-popover': 1.1.10(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.0.10)(react@19.0.0) + '@radix-ui/react-tabs': 1.1.7(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-toggle-group': 1.1.6(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-tooltip': 1.2.3(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/node': 22.14.1 + '@types/normalize-path': 3.0.2 + '@types/react': 19.0.10 + '@types/react-dom': 19.0.4(@types/react@19.0.10) + '@types/webpack': 5.28.5(esbuild@0.25.0) + autoprefixer: 10.4.21(postcss@8.5.6) + clsx: 2.1.1 + esbuild: 0.25.0 + framer-motion: 12.23.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + json5: 2.2.3 + log-symbols: 4.1.0 + module-punycode: punycode@2.3.1 + next: 15.5.2(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + node-html-parser: 7.0.1 + ora: 5.4.1 + pretty-bytes: 6.1.1 + prism-react-renderer: 2.4.1(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + sharp: 0.34.1 + socket.io-client: 4.8.1 + sonner: 2.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + source-map-js: 1.2.1 + spamc: 0.0.5 + stacktrace-parser: 0.1.11 + tailwind-merge: 3.2.0 + tailwindcss: 3.4.0(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2)) + use-debounce: 10.0.4(react@19.0.0) + zod: 3.24.3 + transitivePeerDependencies: + - '@emotion/is-prop-valid' + - '@opentelemetry/api' + - '@playwright/test' + - '@swc/core' + - babel-plugin-macros + - babel-plugin-react-compiler + - bufferutil + - postcss + - sass + - supports-color + - ts-node + - uglify-js + - utf-8-validate + - webpack-cli + '@react-email/preview@0.0.13(react@19.1.0)': dependencies: react: 19.1.0 @@ -23356,6 +25646,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/utils@0.3.0': {} '@stripe/react-stripe-js@1.16.5(@stripe/stripe-js@1.54.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': @@ -23621,6 +25913,10 @@ snapshots: dependencies: '@types/node': 22.18.0 + '@types/cors@2.8.19': + dependencies: + '@types/node': 22.18.0 + '@types/d3-array@3.2.1': {} '@types/d3-color@3.1.3': {} @@ -23727,6 +26023,10 @@ snapshots: '@types/node@17.0.21': {} + '@types/node@22.14.1': + dependencies: + undici-types: 6.21.0 + '@types/node@22.18.0': dependencies: undici-types: 6.21.0 @@ -23739,6 +26039,8 @@ snapshots: dependencies: '@types/node': 24.3.0 + '@types/normalize-path@3.0.2': {} + '@types/parse-json@4.0.2': {} '@types/pg-pool@2.0.6': @@ -23767,6 +26069,10 @@ snapshots: dependencies: '@types/react': 18.3.24 + '@types/react-dom@19.0.4(@types/react@19.0.10)': + dependencies: + '@types/react': 19.0.10 + '@types/react-dom@19.1.5(@types/react@19.1.4)': dependencies: '@types/react': 19.1.4 @@ -23787,6 +26093,10 @@ snapshots: '@types/prop-types': 15.7.15 csstype: 3.1.3 + '@types/react@19.0.10': + dependencies: + csstype: 3.1.3 + '@types/react@19.1.4': dependencies: csstype: 3.1.3 @@ -23803,6 +26113,17 @@ snapshots: '@types/unist@3.0.3': {} + '@types/webpack@5.28.5(esbuild@0.25.0)': + dependencies: + '@types/node': 22.18.0 + tapable: 2.2.3 + webpack: 5.101.3(esbuild@0.25.0) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + '@types/ws@8.18.1': dependencies: '@types/node': 22.18.0 @@ -24193,6 +26514,11 @@ snapshots: '@xtuc/long@4.2.2': {} + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + acorn-import-assertions@1.9.0(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -24535,6 +26861,10 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + + base64id@2.0.0: {} + big.js@5.2.2: {} bignumber.js@9.3.0: {} @@ -24549,6 +26879,12 @@ snapshots: binary-extensions@2.3.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + boolbase@1.0.0: {} brace-expansion@1.1.11: @@ -24590,6 +26926,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -24647,6 +26988,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -24669,6 +27012,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@3.0.0: {} chrome-trace-event@1.0.4: {} @@ -24677,6 +27024,10 @@ snapshots: ci-info@4.3.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + cjs-module-lexer@1.4.3: {} cjs-module-lexer@2.1.0: {} @@ -24685,6 +27036,16 @@ snapshots: dependencies: clsx: 2.1.1 + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + client-only@0.0.1: {} cliui@8.0.1: @@ -24693,6 +27054,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone@1.0.4: {} + clsx@1.2.1: {} clsx@2.0.0: {} @@ -24727,13 +27090,11 @@ snapshots: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - optional: true color@4.2.3: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - optional: true colord@2.9.3: {} @@ -24745,6 +27106,8 @@ snapshots: commander@11.1.0: {} + commander@13.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -24759,16 +27122,27 @@ snapshots: concat-map@0.0.1: {} + confbox@0.2.2: {} + + consola@3.4.2: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} + cookie@0.7.2: {} + cookie@1.0.2: {} copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -24948,10 +27322,16 @@ snapshots: debounce@1.2.1: {} + debounce@2.2.0: {} + debug@3.2.7: dependencies: ms: 2.1.3 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.1: dependencies: ms: 2.1.3 @@ -24972,6 +27352,10 @@ snapshots: deepmerge@4.3.1: {} + defaults@1.0.4: + dependencies: + clone: 1.0.4 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -25061,6 +27445,8 @@ snapshots: emittery@0.13.1: {} + emoji-regex@10.5.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -25071,6 +27457,36 @@ snapshots: dependencies: once: 1.4.0 + engine.io-client@6.6.3: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + + engine.io@6.6.4: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 22.18.0 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -25190,6 +27606,63 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + escalade@3.2.0: {} escape-string-regexp@2.0.0: {} @@ -25207,7 +27680,7 @@ snapshots: eslint: 9.34.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.34.0(jiti@2.5.1)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.34.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.34.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.34.0(jiti@2.5.1)) @@ -25243,21 +27716,22 @@ 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.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.34.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.34.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.34.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -25268,7 +27742,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.34.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.34.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.34.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -25280,7 +27754,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.32.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.33.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -25527,6 +28001,8 @@ snapshots: jest-mock: 30.0.5 jest-util: 30.0.5 + exsolve@1.0.7: {} + facepaint@1.2.1: {} fast-copy@3.0.2: {} @@ -25648,6 +28124,15 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@12.23.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + motion-dom: 12.23.21 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -25670,6 +28155,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.4.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -25723,6 +28210,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -25798,6 +28294,8 @@ snapshots: dependencies: function-bind: 1.1.2 + he@1.2.0: {} + help-me@5.0.0: {} hoist-non-react-statics@3.3.2: @@ -25859,6 +28357,8 @@ snapshots: idb@7.1.1: {} + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -25940,8 +28440,7 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: - optional: true + is-arrayish@0.3.2: {} is-async-function@2.1.1: dependencies: @@ -26014,6 +28513,10 @@ snapshots: is-hotkey@0.2.0: {} + is-interactive@1.0.0: {} + + is-interactive@2.0.0: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -26061,6 +28564,12 @@ snapshots: dependencies: which-typed-array: 1.1.19 + is-unicode-supported@0.1.0: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -26154,6 +28663,10 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + javascript-natural-sort@0.7.1: {} jest-changed-files@29.7.0: @@ -26785,6 +29298,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.4.2: {} + jiti@2.5.1: {} joycon@3.1.1: {} @@ -26923,6 +29438,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + lilconfig@2.1.0: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -26969,6 +29486,21 @@ snapshots: lodash@4.17.21: {} + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + long@5.3.2: {} longest-streak@3.1.0: {} @@ -26983,6 +29515,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.2.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -27394,14 +29928,26 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + mini-svg-data-uri@1.4.4: {} + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -27428,6 +29974,12 @@ snapshots: module-details-from-path@1.0.4: {} + motion-dom@12.23.21: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -27446,6 +29998,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@0.6.3: {} + neo-async@2.6.2: {} next-sitemap@4.2.3(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): @@ -27488,6 +30042,31 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.5.2(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@next/env': 15.5.2 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001723 + postcss: 8.4.31 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.0.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.2 + '@next/swc-darwin-x64': 15.5.2 + '@next/swc-linux-arm64-gnu': 15.5.2 + '@next/swc-linux-arm64-musl': 15.5.2 + '@next/swc-linux-x64-gnu': 15.5.2 + '@next/swc-linux-x64-musl': 15.5.2 + '@next/swc-win32-arm64-msvc': 15.5.2 + '@next/swc-win32-x64-msvc': 15.5.2 + '@opentelemetry/api': 1.9.0 + babel-plugin-react-compiler: 19.1.0-rc.2 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106): dependencies: '@next/env': 15.5.2 @@ -27530,6 +30109,11 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-html-parser@7.0.1: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + node-int64@0.4.0: {} node-releases@2.0.19: {} @@ -27552,6 +30136,14 @@ snapshots: dependencies: boolbase: 1.0.0 + nypm@0.6.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 0.3.2 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -27606,6 +30198,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + openai@5.20.3(ws@8.18.2)(zod@4.1.5): optionalDependencies: ws: 8.18.2 @@ -27622,6 +30218,30 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + orderedmap@2.1.1: {} own-keys@1.0.1: @@ -27698,8 +30318,15 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.0: + dependencies: + lru-cache: 11.2.2 + minipass: 7.1.2 + path-type@4.0.0: {} + pathe@2.0.3: {} + peberminta@0.9.0: {} pg-cloudflare@1.2.7: @@ -27789,6 +30416,12 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + possible-typed-array-names@1.1.0: {} postcss-calc@10.1.1(postcss@8.5.6): @@ -27848,6 +30481,14 @@ snapshots: postcss: 8.5.6 ts-node: 10.9.2(@types/node@17.0.21)(typescript@5.9.2) + postcss-load-config@4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2)): + dependencies: + lilconfig: 3.1.3 + yaml: 2.8.0 + optionalDependencies: + postcss: 8.5.6 + ts-node: 10.9.2(@types/node@22.18.0)(typescript@5.9.2) + postcss-merge-longhand@7.0.5(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -28009,6 +30650,8 @@ snapshots: prettier@3.6.2: {} + pretty-bytes@6.1.1: {} + pretty-format@29.7.0: dependencies: '@jest/schemas': 29.6.3 @@ -28021,6 +30664,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + prism-react-renderer@2.4.1(react@19.0.0): + dependencies: + '@types/prismjs': 1.26.5 + clsx: 2.1.1 + react: 19.0.0 + prism-react-renderer@2.4.1(react@19.0.0-rc-66855b96-20241106): dependencies: '@types/prismjs': 1.26.5 @@ -28367,6 +31016,11 @@ snapshots: date-fns: 4.1.0 react: 19.1.0 + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106): dependencies: react: 19.0.0-rc-66855b96-20241106 @@ -28377,6 +31031,29 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 + react-email@4.2.12: + dependencies: + '@babel/parser': 7.28.3 + '@babel/traverse': 7.28.3 + chokidar: 4.0.3 + commander: 13.1.0 + debounce: 2.2.0 + esbuild: 0.25.10 + glob: 11.0.3 + jiti: 2.4.2 + log-symbols: 7.0.1 + mime-types: 3.0.1 + normalize-path: 3.0.0 + nypm: 0.6.0 + ora: 8.2.0 + prompts: 2.4.2 + socket.io: 4.8.1 + tsconfig-paths: 4.2.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + react-error-boundary@4.1.2(react@19.1.0): dependencies: '@babel/runtime': 7.27.6 @@ -28412,6 +31089,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + react-remove-scroll-bar@2.3.8(@types/react@19.0.10)(react@19.0.0): + dependencies: + react: 19.0.0 + react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.10 + react-remove-scroll-bar@2.3.8(@types/react@19.1.4)(react@19.1.0): dependencies: react: 19.1.0 @@ -28431,6 +31116,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + react-remove-scroll@2.7.1(@types/react@19.0.10)(react@19.0.0): + dependencies: + react: 19.0.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.0.10)(react@19.0.0) + react-style-singleton: 2.2.3(@types/react@19.0.10)(react@19.0.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.0.10)(react@19.0.0) + use-sidecar: 1.1.3(@types/react@19.0.10)(react@19.0.0) + optionalDependencies: + '@types/react': 19.0.10 + react-remove-scroll@2.7.1(@types/react@19.1.4)(react@19.1.0): dependencies: react: 19.1.0 @@ -28518,6 +31214,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0): + dependencies: + get-nonce: 1.0.1 + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.10 + react-style-singleton@2.2.3(@types/react@19.1.4)(react@19.1.0): dependencies: get-nonce: 1.0.1 @@ -28539,6 +31243,8 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react@19.0.0: {} + react@19.0.0-rc-66855b96-20241106: {} react@19.1.0: {} @@ -28549,10 +31255,18 @@ snapshots: read-cmd-shim@5.0.0: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + real-require@0.2.0: {} recharts-scale@0.4.5: @@ -28636,6 +31350,16 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + reusify@1.1.0: {} rollup@4.35.0: @@ -28694,6 +31418,8 @@ snapshots: sax@1.4.1: {} + scheduler@0.25.0: {} + scheduler@0.25.0-rc-66855b96-20241106: {} scheduler@0.26.0: {} @@ -28757,6 +31483,33 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + sharp@0.34.1: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 + sharp@0.34.2: dependencies: color: 4.2.3 @@ -28859,7 +31612,6 @@ snapshots: simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 - optional: true sirv@2.0.4: dependencies: @@ -28897,6 +31649,47 @@ snapshots: is-plain-object: 5.0.0 tiny-warning: 1.0.3 + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-client@4.8.1: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.6.3 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -28911,6 +31704,11 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + sonner@2.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + sonner@2.0.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -28932,6 +31730,8 @@ snapshots: source-map@0.6.1: {} + spamc@0.0.5: {} + split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -28946,6 +31746,8 @@ snapshots: dependencies: type-fest: 0.7.1 + stdin-discarder@0.2.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -28970,6 +31772,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.5.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.0 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -29020,6 +31828,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -29049,6 +31861,14 @@ snapshots: strnum@2.1.1: {} + styled-jsx@5.1.6(@babel/core@7.26.10)(babel-plugin-macros@3.1.0)(react@19.0.0): + dependencies: + client-only: 0.0.1 + react: 19.0.0 + optionalDependencies: + '@babel/core': 7.26.10 + babel-plugin-macros: 3.1.0 + styled-jsx@5.1.6(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@19.0.0-rc-66855b96-20241106): dependencies: client-only: 0.0.1 @@ -29122,6 +31942,8 @@ snapshots: tailwind-merge@2.6.0: {} + tailwind-merge@3.2.0: {} + tailwind-merge@3.3.1: {} tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2))): @@ -29134,6 +31956,33 @@ snapshots: tailwindcss-radix@2.9.0: {} + tailwindcss@3.4.0(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 2.1.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.0.1(postcss@8.5.6) + postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@22.18.0)(typescript@5.9.2)) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@17.0.21)(typescript@5.9.2)): dependencies: '@alloc/quick-lru': 5.2.0 @@ -29178,6 +32027,17 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + terser-webpack-plugin@5.3.14(esbuild@0.25.0)(webpack@5.101.3(esbuild@0.25.0)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.42.0 + webpack: 5.101.3(esbuild@0.25.0) + optionalDependencies: + esbuild: 0.25.0 + terser-webpack-plugin@5.3.14(webpack@5.101.3): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -29220,6 +32080,8 @@ snapshots: tiny-warning@1.0.3: {} + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.5(picomatch@4.0.2) @@ -29311,6 +32173,12 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} turbo-darwin-64@2.5.4: @@ -29506,6 +32374,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + use-callback-ref@1.3.3(@types/react@19.0.10)(react@19.0.0): + dependencies: + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.10 + use-callback-ref@1.3.3(@types/react@19.1.4)(react@19.1.0): dependencies: react: 19.1.0 @@ -29513,6 +32388,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.4 + use-debounce@10.0.4(react@19.0.0): + dependencies: + react: 19.0.0 + use-sidecar@1.1.3(@types/react@18.3.24)(react@19.0.0-rc-66855b96-20241106): dependencies: detect-node-es: 1.1.0 @@ -29521,6 +32400,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.24 + use-sidecar@1.1.3(@types/react@19.0.10)(react@19.0.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.0.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.0.10 + use-sidecar@1.1.3(@types/react@19.1.4)(react@19.1.0): dependencies: detect-node-es: 1.1.0 @@ -29549,6 +32436,8 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + vary@1.1.2: {} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -29584,6 +32473,10 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + web-streams-polyfill@3.3.3: {} web-vitals@3.5.2: {} @@ -29645,6 +32538,38 @@ snapshots: - esbuild - uglify-js + webpack@5.101.3(esbuild@0.25.0): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.25.0 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.1 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(webpack@5.101.3(esbuild@0.25.0)) + watchpack: 2.4.4 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -29736,8 +32661,12 @@ snapshots: ws@7.5.10: {} + ws@8.17.1: {} + ws@8.18.2: {} + xmlhttprequest-ssl@2.1.2: {} + xtend@4.0.2: {} y-prosemirror@1.3.5(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27): @@ -29788,6 +32717,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + yup@1.7.0: dependencies: property-expr: 2.0.6 @@ -29795,6 +32726,8 @@ snapshots: toposort: 2.0.2 type-fest: 2.19.0 + zod@3.24.3: {} + zod@4.1.5: {} zwitch@2.0.4: {} diff --git a/supabase/migrations/202500925153100_sync_connected_online_cron_job.sql b/supabase/migrations/20250925153100_sync_connected_online_cron_job.sql similarity index 100% rename from supabase/migrations/202500925153100_sync_connected_online_cron_job.sql rename to supabase/migrations/20250925153100_sync_connected_online_cron_job.sql diff --git a/supabase/migrations/20250929144800_tto_booking_email_hook.sql b/supabase/migrations/20250929144800_tto_booking_email_hook.sql new file mode 100644 index 0000000..edc5926 --- /dev/null +++ b/supabase/migrations/20250929144800_tto_booking_email_hook.sql @@ -0,0 +1,11 @@ +create trigger tto_order_confirmation_email +after update on medreport.connected_online_reservation +for each row +when (old.status is distinct from new.status and new.status = 'CONFIRMED') +execute function supabase_functions.http_request( + 'http://host.docker.internal:3000/api/db/webhook', + 'POST', + '{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}', + '{}', + '1000' +); \ No newline at end of file