From 31bc4b6cffd7cff1d90855bc8cf00b513b2d4d0a Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Thu, 28 Aug 2025 13:15:39 +0300 Subject: [PATCH] initial commit --- app/api/job/handler/sync-connected-online.ts | 18 +-- .../(dashboard)/booking/[handle]/page.tsx | 33 +++++ app/home/(user)/(dashboard)/booking/page.tsx | 10 +- app/home/(user)/_components/order-cards.tsx | 94 ++++++------- .../(user)/_components/service-categories.tsx | 61 ++++++++ app/home/(user)/_lib/server/load-analyses.ts | 49 ++++--- .../(user)/_lib/server/load-tto-services.ts | 49 +++++++ .../src/lib/data/categories.ts | 45 +++--- packages/shared/src/config/paths.config.ts | 2 + packages/supabase/src/database.types.ts | 133 +++++++++++------- .../migrations/20250827134000_bookings.sql | 2 + 11 files changed, 345 insertions(+), 151 deletions(-) create mode 100644 app/home/(user)/(dashboard)/booking/[handle]/page.tsx create mode 100644 app/home/(user)/_components/service-categories.tsx create mode 100644 app/home/(user)/_lib/server/load-tto-services.ts create mode 100644 supabase/migrations/20250827134000_bookings.sql diff --git a/app/api/job/handler/sync-connected-online.ts b/app/api/job/handler/sync-connected-online.ts index 829ba54..39a5fb3 100644 --- a/app/api/job/handler/sync-connected-online.ts +++ b/app/api/job/handler/sync-connected-online.ts @@ -71,19 +71,19 @@ export default async function syncConnectedOnline() { return { id: service.ID, clinic_id: service.ClinicID, - code: service.Code, - description: service.Description || null, - display: service.Display, - duration: service.Duration, - has_free_codes: !!service.HasFreeCodes, + sync_id: service.SyncID, name: service.Name, + description: service.Description || null, + price: service.Price, + requires_payment: !!service.RequiresPayment, + duration: service.Duration, neto_duration: service.NetoDuration, + display: service.Display, + price_periods: service.PricePeriods || null, online_hide_duration: service.OnlineHideDuration, online_hide_price: service.OnlineHidePrice, - price: service.Price, - price_periods: service.PricePeriods || null, - requires_payment: !!service.RequiresPayment, - sync_id: service.SyncID, + code: service.Code, + has_free_codes: !!service.HasFreeCodes, }; }); diff --git a/app/home/(user)/(dashboard)/booking/[handle]/page.tsx b/app/home/(user)/(dashboard)/booking/[handle]/page.tsx new file mode 100644 index 0000000..9036447 --- /dev/null +++ b/app/home/(user)/(dashboard)/booking/[handle]/page.tsx @@ -0,0 +1,33 @@ +import { use } from 'react'; + +import { PageBody } from '@kit/ui/page'; +import { Trans } from '@kit/ui/trans'; + +import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; +import { withI18n } from '~/lib/i18n/with-i18n'; + +import { HomeLayoutPageHeader } from '../../../_components/home-page-header'; + +export const generateMetadata = async () => { + const i18n = await createI18nServerInstance(); + const title = i18n.t('booking:title'); + + return { + title, + }; +}; + +function BookingHandlePage() { + return ( + <> + } + description={} + /> + + + + ); +} + +export default withI18n(BookingHandlePage); diff --git a/app/home/(user)/(dashboard)/booking/page.tsx b/app/home/(user)/(dashboard)/booking/page.tsx index def99c9..14b1c1a 100644 --- a/app/home/(user)/(dashboard)/booking/page.tsx +++ b/app/home/(user)/(dashboard)/booking/page.tsx @@ -1,12 +1,15 @@ +import { use } from 'react'; + import { PageBody } from '@kit/ui/page'; import { Trans } from '@kit/ui/trans'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { withI18n } from '~/lib/i18n/with-i18n'; - import { HomeLayoutPageHeader } from '../../_components/home-page-header'; import OrderCards from '../../_components/order-cards'; +import ServiceCategories from '../../_components/service-categories'; +import { loadTtoServices } from '../../_lib/server/load-tto-services'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); @@ -18,6 +21,8 @@ export const generateMetadata = async () => { }; function BookingPage() { + const { heroCategories, ttoCategories } = use(loadTtoServices()); + console.log('ttoCategories', heroCategories, ttoCategories); return ( <> - + + ); diff --git a/app/home/(user)/_components/order-cards.tsx b/app/home/(user)/_components/order-cards.tsx index bf95ace..b179d18 100644 --- a/app/home/(user)/_components/order-cards.tsx +++ b/app/home/(user)/_components/order-cards.tsx @@ -1,74 +1,68 @@ -"use client"; +'use client'; -import { ChevronRight, HeartPulse } from 'lucide-react'; import Link from 'next/link'; +import { cn } from '@/lib/utils'; +import { pathsConfig } from '@/packages/shared/src/config'; +import { ComponentInstanceIcon } from '@radix-ui/react-icons'; +import { ChevronRight, HeartPulse } from 'lucide-react'; + import { Button } from '@kit/ui/button'; import { Card, - CardHeader, CardDescription, - CardProps, CardFooter, + CardHeader, + CardProps, } from '@kit/ui/card'; -import { Trans } from '@kit/ui/trans'; -import { cn } from '@/lib/utils'; -const dummyCards = [ - { - title: 'booking:analysisPackages.title', - description: 'booking:analysisPackages.description', - descriptionColor: 'text-primary', - icon: ( - - - - ), - cardVariant: 'gradient-success' as CardProps['variant'], - iconBg: 'bg-warning', - }, -]; +import { ServiceCategory } from './service-categories'; -export default function OrderCards() { +export default function OrderCards({ + heroCategories, +}: { + heroCategories: ServiceCategory[]; +}) { return ( -
- {dummyCards.map(({ - title, - description, - icon, - cardVariant, - descriptionColor, - iconBg, - }) => ( +
+ {heroCategories.map(({ name, description, color, handle }) => ( - +
- {icon} + +
+
+ + +
- -
- -
-
- -
- - - + +
{name}
+ {description}
))} diff --git a/app/home/(user)/_components/service-categories.tsx b/app/home/(user)/_components/service-categories.tsx new file mode 100644 index 0000000..148f109 --- /dev/null +++ b/app/home/(user)/_components/service-categories.tsx @@ -0,0 +1,61 @@ +'use client'; + +import React from 'react'; + +import { redirect } from 'next/navigation'; + +import { createPath, pathsConfig } from '@/packages/shared/src/config'; +import { ComponentInstanceIcon } from '@radix-ui/react-icons'; + +import { cn } from '@kit/ui/shadcn'; +import { Card, CardDescription, CardTitle } from '@kit/ui/shadcn/card'; + +export interface ServiceCategory { + name: string; + handle: string; + color: string; + description: string; +} + +const ServiceCategories = ({ + categories, +}: { + categories: ServiceCategory[]; +}) => { + return ( +
+ {categories.map((category, index) => ( + { + redirect( + pathsConfig.app.bookingHandle.replace( + '[handle]', + category.handle, + ), + ); + }} + > +
+ +
+
+
{category.name}
+ + {category.description} + +
+
+ ))} +
+ ); +}; + +export default ServiceCategories; diff --git a/app/home/(user)/_lib/server/load-analyses.ts b/app/home/(user)/_lib/server/load-analyses.ts index 424ff25..db22011 100644 --- a/app/home/(user)/_lib/server/load-analyses.ts +++ b/app/home/(user)/_lib/server/load-analyses.ts @@ -1,9 +1,11 @@ import { cache } from 'react'; -import { listProductTypes } from "@lib/data/products"; -import { listRegions } from '@lib/data/regions'; import { getProductCategories } from '@lib/data/categories'; +import { listProductTypes } from '@lib/data/products'; +import { listRegions } from '@lib/data/regions'; + import { OrderAnalysisCard } from '../../_components/order-analyses-cards'; +import { ServiceCategory } from '../../_components/service-categories'; async function countryCodesLoader() { const countryCodes = await listRegions().then((regions) => @@ -14,7 +16,9 @@ async function countryCodesLoader() { export const loadCountryCodes = cache(countryCodesLoader); async function productCategoriesLoader() { - const productCategories = await getProductCategories({ fields: "*products, *products.variants" }); + const productCategories = await getProductCategories({ + fields: '*products, *products.variants, is_active', + }); return productCategories.product_categories ?? []; } export const loadProductCategories = cache(productCategoriesLoader); @@ -29,25 +33,34 @@ async function analysesLoader() { const [countryCodes, productCategories] = await Promise.all([ loadCountryCodes(), loadProductCategories(), - ]); + ]); const countryCode = countryCodes[0]!; - const category = productCategories.find(({ metadata }) => metadata?.page === 'order-analysis'); - + const category = productCategories.find( + ({ metadata }) => metadata?.page === 'order-analysis', + ); + const serviceCategories = productCategories.filter( + ({ parent_category }) => parent_category?.handle === 'tto-categories', + ); + console.log('serviceCategories', serviceCategories); return { - analyses: category?.products?.map(({ title, description, subtitle, variants, status, metadata }) => { - const variant = variants![0]!; - return { - title, - description, - subtitle, - variant: { - id: variant.id, + analyses: + category?.products?.map( + ({ title, description, subtitle, variants, status, metadata }) => { + const variant = variants![0]!; + return { + title, + description, + subtitle, + variant: { + id: variant.id, + }, + isAvailable: + status === 'published' && !!metadata?.analysisIdOriginal, + }; }, - isAvailable: status === 'published' && !!metadata?.analysisIdOriginal, - }; - }) ?? [], + ) ?? [], countryCode, - } + }; } export const loadAnalyses = cache(analysesLoader); diff --git a/app/home/(user)/_lib/server/load-tto-services.ts b/app/home/(user)/_lib/server/load-tto-services.ts new file mode 100644 index 0000000..a8b4e1b --- /dev/null +++ b/app/home/(user)/_lib/server/load-tto-services.ts @@ -0,0 +1,49 @@ +import { cache } from 'react'; + +import { getProductCategories } from '@lib/data'; + +import { ServiceCategory } from '../../_components/service-categories'; + +async function ttoServicesLoader() { + const response = await getProductCategories({ + fields: '*products, is_active, metadata', + }); + console.log('response.product_categories', response.product_categories); + const heroCategories = response.product_categories?.filter( + ({ parent_category, is_active, metadata }) => + parent_category?.handle === 'tto-categories' && + is_active && + metadata?.isHero, + ); + + const ttoCategories = response.product_categories?.filter( + ({ parent_category, is_active, metadata }) => + parent_category?.handle === 'tto-categories' && + is_active && + !metadata?.isHero, + ); + + return { + heroCategories: + heroCategories.map( + ({ name, handle, metadata, description }) => ({ + name, + handle, + color: + typeof metadata?.color === 'string' ? metadata.color : 'primary', + description, + }), + ) ?? [], + ttoCategories: + ttoCategories.map( + ({ name, handle, metadata, description }) => ({ + name, + handle, + color: + typeof metadata?.color === 'string' ? metadata.color : 'primary', + description, + }), + ) ?? [], + }; +} +export const loadTtoServices = cache(ttoServicesLoader); diff --git a/packages/features/medusa-storefront/src/lib/data/categories.ts b/packages/features/medusa-storefront/src/lib/data/categories.ts index 7b3987d..b4db69d 100644 --- a/packages/features/medusa-storefront/src/lib/data/categories.ts +++ b/packages/features/medusa-storefront/src/lib/data/categories.ts @@ -1,13 +1,13 @@ -import { sdk } from "@lib/config" -import { HttpTypes } from "@medusajs/types" -import { getCacheOptions } from "./cookies" +import { sdk } from "@lib/config"; +import { HttpTypes } from "@medusajs/types"; +import { getCacheOptions } from "./cookies"; export const listCategories = async (query?: Record) => { const next = { ...(await getCacheOptions("categories")), - } + }; - const limit = query?.limit || 100 + const limit = query?.limit || 100; return sdk.client .fetch<{ product_categories: HttpTypes.StoreProductCategory[] }>( @@ -23,8 +23,8 @@ export const listCategories = async (query?: Record) => { cache: "force-cache", } ) - .then(({ product_categories }) => product_categories) -} + .then(({ product_categories }) => product_categories); +}; export const getCategoryByHandle = async (categoryHandle: string[]) => { const { product_categories } = await getProductCategories({ @@ -32,7 +32,7 @@ export const getCategoryByHandle = async (categoryHandle: string[]) => { limit: 1, }); return product_categories[0]; -} +}; export const getProductCategories = async ({ handle, @@ -45,19 +45,18 @@ export const getProductCategories = async ({ } = {}) => { const next = { ...(await getCacheOptions("categories")), - } + }; - return sdk.client - .fetch( - `/store/product-categories`, - { - query: { - fields, - handle, - limit, - }, - next, - //cache: "force-cache", - } - ); -} + return sdk.client.fetch( + `/store/product-categories`, + { + query: { + fields, + handle, + limit, + }, + next, + //cache: "force-cache", + } + ); +}; diff --git a/packages/shared/src/config/paths.config.ts b/packages/shared/src/config/paths.config.ts index d21c445..4e400e4 100644 --- a/packages/shared/src/config/paths.config.ts +++ b/packages/shared/src/config/paths.config.ts @@ -16,6 +16,7 @@ const PathsSchema = z.object({ home: z.string().min(1), selectPackage: z.string().min(1), booking: z.string().min(1), + bookingHandle: z.string().min(1), myOrders: z.string().min(1), analysisResults: z.string().min(1), orderAnalysisPackage: z.string().min(1), @@ -64,6 +65,7 @@ const pathsConfig = PathsSchema.parse({ joinTeam: '/join', selectPackage: '/select-package', booking: '/home/booking', + bookingHandle: '/home/booking/[handle]', orderAnalysisPackage: '/home/order-analysis-package', myOrders: '/home/order', analysisResults: '/home/analysis-results', diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 4137545..a070462 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -108,6 +108,63 @@ export type Database = { } Relationships: [] } + medipost_dispatch: { + Row: { + changed_by: string | null + created_at: string + error_message: string | null + id: number + is_medipost_error: boolean + is_success: boolean + medusa_order_id: string + } + Insert: { + changed_by?: string | null + created_at?: string + error_message?: string | null + id?: number + is_medipost_error: boolean + is_success: boolean + medusa_order_id: string + } + Update: { + changed_by?: string | null + created_at?: string + error_message?: string | null + id?: number + is_medipost_error?: boolean + is_success?: boolean + medusa_order_id?: string + } + Relationships: [] + } + medusa_action: { + Row: { + action: string + created_at: string + id: number + medusa_user_id: string + page: string | null + user_email: string + } + Insert: { + action: string + created_at?: string + id?: number + medusa_user_id: string + page?: string | null + user_email: string + } + Update: { + action?: string + created_at?: string + id?: number + medusa_user_id?: string + page?: string | null + user_email?: string + } + Relationships: [] + } page_views: { Row: { account_id: string @@ -201,28 +258,6 @@ export type Database = { } Relationships: [] } - medusa_action: { - Row: { - id: number - medusa_user_id: string - user_email: string - action: string - page: string - created_at: string - } - Insert: { - medusa_user_id: string - user_email: string - action: string - page: string - } - Update: { - medusa_user_id?: string - user_email?: string - action?: string - page?: string - } - } } Views: { [_ in never]: never @@ -329,6 +364,7 @@ export type Database = { id: string is_personal_account: boolean last_name: string | null + medusa_account_id: string | null name: string personal_code: string | null phone: string | null @@ -336,7 +372,6 @@ export type Database = { primary_owner_user_id: string public_data: Json slug: string | null - medusa_account_id: string | null updated_at: string | null updated_by: string | null } @@ -351,6 +386,7 @@ export type Database = { id?: string is_personal_account?: boolean last_name?: string | null + medusa_account_id?: string | null name: string personal_code?: string | null phone?: string | null @@ -358,7 +394,6 @@ export type Database = { primary_owner_user_id?: string public_data?: Json slug?: string | null - medusa_account_id?: string | null updated_at?: string | null updated_by?: string | null } @@ -373,6 +408,7 @@ export type Database = { id?: string is_personal_account?: boolean last_name?: string | null + medusa_account_id?: string | null name?: string personal_code?: string | null phone?: string | null @@ -380,7 +416,6 @@ export type Database = { primary_owner_user_id?: string public_data?: Json slug?: string | null - medusa_account_id?: string | null updated_at?: string | null updated_by?: string | null } @@ -393,6 +428,7 @@ export type Database = { created_at: string created_by: string | null has_seen_confirmation: boolean + id: string updated_at: string updated_by: string | null user_id: string @@ -403,6 +439,7 @@ export type Database = { created_at?: string created_by?: string | null has_seen_confirmation?: boolean + id?: string updated_at?: string updated_by?: string | null user_id: string @@ -413,6 +450,7 @@ export type Database = { created_at?: string created_by?: string | null has_seen_confirmation?: boolean + id?: string updated_at?: string updated_by?: string | null user_id?: string @@ -1022,7 +1060,7 @@ export type Database = { price: number price_periods: string | null requires_payment: boolean - sync_id: number + sync_id: string | null updated_at: string | null } Insert: { @@ -1041,7 +1079,7 @@ export type Database = { price: number price_periods?: string | null requires_payment: boolean - sync_id: number + sync_id?: string | null updated_at?: string | null } Update: { @@ -1060,7 +1098,7 @@ export type Database = { price?: number price_periods?: string | null requires_payment?: boolean - sync_id?: number + sync_id?: string | null updated_at?: string | null } Relationships: [ @@ -1081,7 +1119,7 @@ export type Database = { doctor_user_id: string | null id: number status: Database["medreport"]["Enums"]["analysis_feedback_status"] - updated_at: string + updated_at: string | null updated_by: string | null user_id: string value: string | null @@ -1093,7 +1131,7 @@ export type Database = { doctor_user_id?: string | null id?: number status?: Database["medreport"]["Enums"]["analysis_feedback_status"] - updated_at?: string + updated_at?: string | null updated_by?: string | null user_id: string value?: string | null @@ -1105,7 +1143,7 @@ export type Database = { doctor_user_id?: string | null id?: number status?: Database["medreport"]["Enums"]["analysis_feedback_status"] - updated_at?: string + updated_at?: string | null updated_by?: string | null user_id?: string value?: string | null @@ -1784,9 +1822,7 @@ export type Database = { Returns: Json } create_team_account: { - Args: - | { account_name: string } - | { account_name: string; new_personal_code: string } + Args: { account_name: string; new_personal_code: string } Returns: { application_role: Database["medreport"]["Enums"]["application_role"] city: string | null @@ -1798,6 +1834,7 @@ export type Database = { id: string is_personal_account: boolean last_name: string | null + medusa_account_id: string | null name: string personal_code: string | null phone: string | null @@ -1836,6 +1873,7 @@ export type Database = { primary_owner_user_id: string name: string email: string + personal_code: string picture_url: string created_at: string updated_at: string @@ -1853,10 +1891,18 @@ export type Database = { account_id: string }[] } + get_medipost_dispatch_tries: { + Args: { p_medusa_order_id: string } + Returns: number + } get_nonce_status: { Args: { p_id: string } Returns: Json } + get_order_possible_actions: { + Args: { p_medusa_order_id: string } + Returns: Json + } get_upper_system_role: { Args: Record Returns: string @@ -1937,6 +1983,10 @@ export type Database = { Args: { account_id: string; user_id: string } Returns: boolean } + medipost_retry_dispatch: { + Args: { order_id: string } + Returns: Json + } revoke_nonce: { Args: { p_id: string; p_reason?: string } Returns: boolean @@ -2057,21 +2107,6 @@ export type Database = { } Returns: Json } - medipost_retry_dispatch: { - Args: { - order_id: string - } - Returns: { - success: boolean - error: string | null - } - } - get_medipost_dispatch_tries: { - Args: { - p_medusa_order_id: string - } - Returns: number - } } Enums: { analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED" diff --git a/supabase/migrations/20250827134000_bookings.sql b/supabase/migrations/20250827134000_bookings.sql new file mode 100644 index 0000000..9d77627 --- /dev/null +++ b/supabase/migrations/20250827134000_bookings.sql @@ -0,0 +1,2 @@ +ALTER TABLE medreport.connected_online_services +ALTER COLUMN sync_id TYPE text USING sync_id::text; \ No newline at end of file