} />
@@ -28,9 +39,15 @@ export default async function MontonioCheckoutCallbackErrorPage() {
-
+ {failedBookingData.length ? (
+ failedBookingData.map((failureReason, index) => (
+
+
+
+ ))
+ ) : (
-
+ )}
diff --git a/app/home/(user)/(dashboard)/cart/page.tsx b/app/home/(user)/(dashboard)/cart/page.tsx
index 86bc7c0..38967f1 100644
--- a/app/home/(user)/(dashboard)/cart/page.tsx
+++ b/app/home/(user)/(dashboard)/cart/page.tsx
@@ -6,11 +6,14 @@ import { listProductTypes } from '@lib/data/products';
import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';
+import { getCartReservations } from '~/lib/services/reservation.service';
+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';
export async function generateMetadata() {
const { t } = await createI18nServerInstance();
@@ -37,29 +40,32 @@ async function CartPage() {
const balanceSummary = await new AccountBalanceService().getBalanceSummary(account.id);
- const analysisPackagesType = productTypes.find(
- ({ metadata }) => metadata?.handle === 'analysis-packages',
+ const synlabAnalysisTypeId = findProductTypeIdByHandle(
+ productTypes,
+ 'synlab-analysis',
);
- const synlabAnalysisType = productTypes.find(
- ({ metadata }) => metadata?.handle === 'synlab-analysis',
+ const analysisPackagesTypeId = findProductTypeIdByHandle(
+ productTypes,
+ 'analysis-packages',
);
+
const synlabAnalyses =
- analysisPackagesType && synlabAnalysisType && cart?.items
+ analysisPackagesTypeId && synlabAnalysisTypeId && cart?.items
? cart.items.filter((item) => {
const productTypeId = item.product?.type_id;
if (!productTypeId) {
return false;
}
- return [analysisPackagesType.id, synlabAnalysisType.id].includes(
+ return [analysisPackagesTypeId, synlabAnalysisTypeId].includes(
productTypeId,
);
})
: [];
- const ttoServiceItems =
- cart?.items?.filter(
- (item) => !synlabAnalyses.some((analysis) => analysis.id === item.id),
- ) ?? [];
+ let ttoServiceItems: EnrichedCartItem[] = [];
+ if (cart?.items?.length) {
+ ttoServiceItems = await getCartReservations(cart);
+ }
const otherItemsSorted = ttoServiceItems.sort((a, b) =>
(a.updated_at ?? '') > (b.updated_at ?? '') ? -1 : 1,
);
diff --git a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
index e6bf47d..f28b5ae 100644
--- a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
+++ b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx
@@ -26,17 +26,7 @@ async function OrderConfirmedPage(props: {
params: Promise<{ orderId: string }>;
}) {
const params = await props.params;
-
- const order = await getAnalysisOrder({
- analysisOrderId: Number(params.orderId),
- }).catch(() => null);
- if (!order) {
- redirect(pathsConfig.app.myOrders);
- }
-
- const medusaOrder = await retrieveOrder(order.medusa_order_id).catch(
- () => null,
- );
+ const medusaOrder = await retrieveOrder(params.orderId).catch(() => null);
if (!medusaOrder) {
redirect(pathsConfig.app.myOrders);
}
@@ -46,7 +36,12 @@ async function OrderConfirmedPage(props: {
} />
-
+
diff --git a/app/home/(user)/(dashboard)/order/page.tsx b/app/home/(user)/(dashboard)/order/page.tsx
index 879c174..c50b7d1 100644
--- a/app/home/(user)/(dashboard)/order/page.tsx
+++ b/app/home/(user)/(dashboard)/order/page.tsx
@@ -11,7 +11,8 @@ import { PageBody } from '@kit/ui/makerkit/page';
import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { getAnalysisOrders } from '~/lib/services/order.service';
+import { getAnalysisOrders, getTtoOrders } from '~/lib/services/order.service';
+import { findProductTypeIdByHandle } from '~/lib/utils';
import { listOrders } from '~/medusa/lib/data/orders';
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
@@ -28,19 +29,25 @@ export async function generateMetadata() {
}
async function OrdersPage() {
- const [medusaOrders, analysisOrders, { productTypes }] = await Promise.all([
+ const [medusaOrders, analysisOrders, ttoOrders, { productTypes }] = await Promise.all([
listOrders(ORDERS_LIMIT),
getAnalysisOrders(),
+ getTtoOrders(),
listProductTypes(),
]);
- if (!medusaOrders || !productTypes) {
+ if (!medusaOrders || !productTypes || !ttoOrders) {
redirect(pathsConfig.auth.signIn);
}
- const analysisPackagesType = productTypes.find(
- ({ metadata }) => metadata?.handle === 'analysis-packages',
- )!;
+ const analysisPackagesTypeId = findProductTypeIdByHandle(
+ productTypes,
+ 'analysis-package',
+ );
+ const ttoServiceTypeId = findProductTypeIdByHandle(
+ productTypes,
+ 'tto-service',
+ );
return (
<>
@@ -49,34 +56,45 @@ async function OrdersPage() {
description={
}
/>
- {analysisOrders.map((analysisOrder) => {
- const medusaOrder = medusaOrders.find(
- ({ id }) => id === analysisOrder.medusa_order_id,
+ {medusaOrders.map((medusaOrder) => {
+ const analysisOrder = analysisOrders.find(
+ ({ medusa_order_id }) => medusa_order_id === medusaOrder.id,
);
+
if (!medusaOrder) {
return null;
}
const medusaOrderItems = medusaOrder.items || [];
const medusaOrderItemsAnalysisPackages = medusaOrderItems.filter(
- (item) => item.product_type_id === analysisPackagesType?.id,
+ (item) => item.product_type_id === analysisPackagesTypeId,
+ );
+ const medusaOrderItemsTtoServices = medusaOrderItems.filter(
+ (item) => item.product_type_id === ttoServiceTypeId,
);
const medusaOrderItemsOther = medusaOrderItems.filter(
- (item) => item.product_type_id !== analysisPackagesType?.id,
+ (item) =>
+ !item.product_type_id ||
+ ![analysisPackagesTypeId, ttoServiceTypeId].includes(
+ item.product_type_id,
+ ),
);
return (
-
+
);
})}
- {analysisOrders.length === 0 && (
+ {analysisOrders.length === 0 && ttoOrders.length === 0 && (
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)/_components/booking/booking-calendar.tsx b/app/home/(user)/_components/booking/booking-calendar.tsx
new file mode 100644
index 0000000..f8afb0b
--- /dev/null
+++ b/app/home/(user)/_components/booking/booking-calendar.tsx
@@ -0,0 +1,40 @@
+'use client';
+
+import { isBefore, isSameDay } from 'date-fns';
+import { uniq } from 'lodash';
+
+import { Calendar } from '@kit/ui/shadcn/calendar';
+import { Card } from '@kit/ui/shadcn/card';
+import { cn } from '@kit/ui/utils';
+
+import { useBooking } from './booking.provider';
+
+export default function BookingCalendar() {
+ const { selectedDate, setSelectedDate, isLoadingTimeSlots, timeSlots } =
+ useBooking();
+ const availableDates = uniq(timeSlots?.map((timeSlot) => timeSlot.StartTime));
+
+ return (
+
+ {
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ return (
+ isBefore(date, today) ||
+ !availableDates.some((dateWithBooking) =>
+ isSameDay(date, dateWithBooking),
+ )
+ );
+ }}
+ className={cn('rounded-md border', {
+ 'pointer-events-none rounded-md border opacity-50':
+ isLoadingTimeSlots,
+ })}
+ />
+
+ );
+}
diff --git a/app/home/(user)/_components/booking/booking-container.tsx b/app/home/(user)/_components/booking/booking-container.tsx
new file mode 100644
index 0000000..9d3b6e8
--- /dev/null
+++ b/app/home/(user)/_components/booking/booking-container.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import { StoreProduct } from '@medusajs/types';
+
+import { Trans } from '@kit/ui/trans';
+
+import { EnrichedCartItem } from '../cart/types';
+import BookingCalendar from './booking-calendar';
+import { BookingProvider } from './booking.provider';
+import LocationSelector from './location-selector';
+import ServiceSelector from './service-selector';
+import TimeSlots from './time-slots';
+
+const BookingContainer = ({
+ category,
+ cartItem,
+ onComplete,
+}: {
+ category: { products: StoreProduct[]; countryCode: string };
+ cartItem?: EnrichedCartItem;
+ onComplete?: () => void;
+}) => {
+ const products = cartItem?.product ? [cartItem.product] : category.products;
+
+ if (!cartItem || !products?.length) {
+
+
+
;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default BookingContainer;
diff --git a/app/home/(user)/_components/booking/booking.context.ts b/app/home/(user)/_components/booking/booking.context.ts
new file mode 100644
index 0000000..ce59dba
--- /dev/null
+++ b/app/home/(user)/_components/booking/booking.context.ts
@@ -0,0 +1,77 @@
+import { createContext } from 'react';
+
+import { StoreProduct } from '@medusajs/types';
+import { noop } from 'lodash';
+
+import { Tables } from '@kit/supabase/database';
+
+export type Location = Tables<
+ { schema: 'medreport' },
+ 'connected_online_locations'
+>;
+
+export type TimeSlotResponse = {
+ timeSlots: TimeSlot[];
+ locations: Location[];
+};
+
+export type TimeSlot = {
+ ClinicID: number;
+ LocationID: number;
+ UserID: number;
+ SyncUserID: number;
+ ServiceID: number;
+ HKServiceID: number;
+ StartTime: Date;
+ EndTime: Date;
+ PayorCode: string;
+ serviceProvider?: ServiceProvider;
+ syncedService?: SyncedService;
+} & { location?: Location };
+
+export type ServiceProvider = {
+ name: string;
+ id: number;
+ jobTitleEn: string | null;
+ jobTitleEt: string | null;
+ jobTitleRu: string | null;
+ clinicId: number;
+};
+
+export type SyncedService = Tables<
+ { schema: 'medreport' },
+ 'connected_online_services'
+> & {
+ providerClinic: ProviderClinic;
+};
+
+export type ProviderClinic = Tables<
+ { schema: 'medreport' },
+ 'connected_online_providers'
+> & { locations: Location[] };
+
+const BookingContext = createContext<{
+ timeSlots: TimeSlot[] | null;
+ selectedService: StoreProduct | null;
+ locations: Location[] | null;
+ selectedLocationId: number | null;
+ selectedDate?: Date;
+ isLoadingTimeSlots?: boolean;
+ setSelectedService: (selectedService: StoreProduct | null) => void;
+ setSelectedLocationId: (selectedLocationId: number | null) => void;
+ updateTimeSlots: (serviceIds: number[]) => Promise;
+ setSelectedDate: (selectedDate?: Date) => void;
+}>({
+ timeSlots: null,
+ selectedService: null,
+ locations: null,
+ selectedLocationId: null,
+ selectedDate: new Date(),
+ isLoadingTimeSlots: false,
+ setSelectedService: (_) => _,
+ setSelectedLocationId: (_) => _,
+ updateTimeSlots: async (_) => noop(),
+ setSelectedDate: (_) => _,
+});
+
+export { BookingContext };
diff --git a/app/home/(user)/_components/booking/booking.provider.tsx b/app/home/(user)/_components/booking/booking.provider.tsx
new file mode 100644
index 0000000..b0c21e1
--- /dev/null
+++ b/app/home/(user)/_components/booking/booking.provider.tsx
@@ -0,0 +1,80 @@
+import React, { useContext, useEffect, useState } from 'react';
+
+import { StoreProduct } from '@medusajs/types';
+
+import { getAvailableTimeSlotsForDisplay } from '~/lib/services/connected-online.service';
+
+import { BookingContext, Location, TimeSlot } from './booking.context';
+
+export function useBooking() {
+ const context = useContext(BookingContext);
+
+ if (!context) {
+ throw new Error('useBooking must be used within a BookingProvider.');
+ }
+
+ return context;
+}
+
+export const BookingProvider: React.FC<{
+ children: React.ReactElement;
+ category: { products: StoreProduct[] };
+ service?: StoreProduct;
+}> = ({ children, category, service }) => {
+ const [selectedService, setSelectedService] = useState(
+ (service ?? category?.products?.[0]) || null,
+ );
+ const [selectedLocationId, setSelectedLocationId] = useState(
+ null,
+ );
+ const [selectedDate, setSelectedDate] = useState();
+ const [timeSlots, setTimeSlots] = useState(null);
+ const [locations, setLocations] = useState(null);
+ const [isLoadingTimeSlots, setIsLoadingTimeSlots] = useState(false);
+
+ useEffect(() => {
+ const metadataServiceIds = selectedService?.metadata?.serviceIds as string;
+ if (metadataServiceIds) {
+ const json = JSON.parse(metadataServiceIds);
+ if (Array.isArray(json)) {
+ updateTimeSlots(json);
+ }
+ }
+ }, [selectedService, selectedLocationId]);
+
+ const updateTimeSlots = async (serviceIds: number[]) => {
+ setIsLoadingTimeSlots(true);
+ try {
+ console.log('serviceIds', serviceIds, selectedLocationId);
+ const response = await getAvailableTimeSlotsForDisplay(
+ serviceIds,
+ selectedLocationId,
+ );
+ setTimeSlots(response.timeSlots);
+ setLocations(response.locations);
+ } catch (error) {
+ setTimeSlots(null);
+ } finally {
+ setIsLoadingTimeSlots(false);
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/app/home/(user)/_components/booking/location-selector.tsx b/app/home/(user)/_components/booking/location-selector.tsx
new file mode 100644
index 0000000..4cb02aa
--- /dev/null
+++ b/app/home/(user)/_components/booking/location-selector.tsx
@@ -0,0 +1,55 @@
+import { Label } from '@medusajs/ui';
+import { useTranslation } from 'react-i18next';
+
+import { RadioGroup, RadioGroupItem } from '@kit/ui/radio-group';
+import { Card } from '@kit/ui/shadcn/card';
+import { Trans } from '@kit/ui/trans';
+
+import { useBooking } from './booking.provider';
+
+const LocationSelector = () => {
+ const { t } = useTranslation();
+ const { selectedLocationId, setSelectedLocationId, locations } = useBooking();
+
+ const onLocationSelect = (locationId: number | string | null) => {
+ if (locationId === 'all') return setSelectedLocationId(null);
+ setSelectedLocationId(Number(locationId));
+ };
+
+ return (
+
+
+
+
+
+
onLocationSelect(val)}
+ >
+
+
+
+
+ {locations?.map((location) => (
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default LocationSelector;
diff --git a/app/home/(user)/_components/booking/service-selector.tsx b/app/home/(user)/_components/booking/service-selector.tsx
new file mode 100644
index 0000000..8dd0907
--- /dev/null
+++ b/app/home/(user)/_components/booking/service-selector.tsx
@@ -0,0 +1,85 @@
+import { useState } from 'react';
+
+import { StoreProduct } from '@medusajs/types';
+import { ChevronDown } from 'lucide-react';
+
+import { Card } from '@kit/ui/shadcn/card';
+import { Label } from '@kit/ui/shadcn/label';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@kit/ui/shadcn/popover';
+import { RadioGroup, RadioGroupItem } from '@kit/ui/shadcn/radio-group';
+import { Trans } from '@kit/ui/trans';
+
+import { useBooking } from './booking.provider';
+
+const ServiceSelector = ({ products }: { products: StoreProduct[] }) => {
+ const { selectedService, setSelectedService } = useBooking();
+ const [collapsed, setCollapsed] = useState(false);
+ const [firstFourProducts] = useState(products?.slice(0, 4));
+
+ const onServiceSelect = async (productId: StoreProduct['id']) => {
+ const product = products.find((p) => p.id === productId);
+ setSelectedService(product ?? null);
+ setCollapsed(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {firstFourProducts?.map((product) => (
+
+
+
+
+ ))}
+
+ {products.length > 4 && (
+
+ setCollapsed((_) => !_)}
+ className="flex cursor-pointer items-center justify-between border-t py-1"
+ >
+
+
+
+
+
+
+ )}
+
+
+
+ {products?.map((product) => (
+
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+export default ServiceSelector;
diff --git a/app/home/(user)/_components/booking/time-slots.tsx b/app/home/(user)/_components/booking/time-slots.tsx
new file mode 100644
index 0000000..4d5fc14
--- /dev/null
+++ b/app/home/(user)/_components/booking/time-slots.tsx
@@ -0,0 +1,319 @@
+import { useMemo, useState } from 'react';
+
+import { useRouter } from 'next/navigation';
+
+import { formatCurrency } from '@/packages/shared/src/utils';
+import { addHours, isAfter, isSameDay } from 'date-fns';
+import { orderBy } from 'lodash';
+import { useTranslation } from 'react-i18next';
+
+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 { toast } from '@kit/ui/sonner';
+import { Trans } from '@kit/ui/trans';
+import { cn } from '@kit/ui/utils';
+
+import { updateReservationTime } from '~/lib/services/reservation.service';
+
+import { createInitialReservationAction } from '../../_lib/server/actions';
+import { EnrichedCartItem } from '../cart/types';
+import { ServiceProvider, TimeSlot } from './booking.context';
+import { useBooking } from './booking.provider';
+
+const getServiceProviderTitle = (
+ currentLocale: string,
+ serviceProvider?: ServiceProvider,
+) => {
+ if (!serviceProvider) return null;
+ if (currentLocale === 'en') return serviceProvider.jobTitleEn;
+ if (currentLocale === 'ru') return serviceProvider.jobTitleRu;
+
+ return serviceProvider.jobTitleEt;
+};
+
+const PAGE_SIZE = 7;
+
+const TimeSlots = ({
+ countryCode,
+ cartItem,
+ onComplete,
+}: {
+ countryCode: string;
+ cartItem?: EnrichedCartItem;
+ onComplete?: () => void;
+}) => {
+ const [currentPage, setCurrentPage] = useState(1);
+
+ const {
+ t,
+ i18n: { language: currentLocale },
+ } = useTranslation();
+
+ const booking = useBooking();
+
+ const router = useRouter();
+
+ const selectedDate = booking.selectedDate ?? new Date();
+
+ const filteredBookings = useMemo(
+ () =>
+ orderBy(
+ booking?.timeSlots?.filter(({ StartTime }) => {
+ const firstAvailableTimeToSelect = isSameDay(selectedDate, new Date())
+ ? addHours(new Date(), 0.5)
+ : selectedDate;
+ return isAfter(StartTime, firstAvailableTimeToSelect);
+ }) ?? [],
+ 'StartTime',
+ 'asc',
+ ),
+ [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;
+ }
+
+ const handleBookTime = async (timeSlot: TimeSlot, comments?: string) => {
+ const selectedService = booking.selectedService;
+
+ const selectedVariant = selectedService?.variants?.[0];
+
+ const syncedService = timeSlot.syncedService;
+ if (!syncedService || !selectedVariant) {
+ return toast.error(t('booking:serviceNotFound'));
+ }
+
+ const bookTimePromise = createInitialReservationAction(
+ selectedVariant,
+ countryCode,
+ Number(syncedService.id),
+ syncedService?.clinic_id,
+ timeSlot.UserID,
+ timeSlot.SyncUserID,
+ timeSlot.StartTime,
+ booking.selectedLocationId ? booking.selectedLocationId : null,
+ comments,
+ ).then(() => {
+ if (onComplete) {
+ onComplete();
+ }
+ router.push(pathsConfig.app.cart);
+ });
+
+ toast.promise(() => bookTimePromise, {
+ success: ,
+ error: ,
+ loading: ,
+ });
+ };
+
+ const handleChangeTime = async (
+ timeSlot: TimeSlot,
+ reservationId: number,
+ cartId: string,
+ ) => {
+ const syncedService = timeSlot.syncedService;
+ if (!syncedService) {
+ return toast.error(t('booking:serviceNotFound'));
+ }
+
+ const bookTimePromise = updateReservationTime({
+ reservationId,
+ newStartTime: timeSlot.StartTime,
+ newServiceId: Number(syncedService.id),
+ newAppointmentUserId: timeSlot.UserID,
+ newSyncUserId: timeSlot.SyncUserID,
+ newLocationId: booking.selectedLocationId
+ ? booking.selectedLocationId
+ : null,
+ cartId,
+ });
+
+ toast.promise(() => bookTimePromise, {
+ success: ,
+ error: ,
+ loading: ,
+ });
+
+ if (onComplete) {
+ onComplete();
+ }
+ };
+
+ const handleTimeSelect = async (timeSlot: TimeSlot) => {
+ if (cartItem?.reservation.id) {
+ return handleChangeTime(
+ timeSlot,
+ cartItem.reservation.id,
+ cartItem.cart_id,
+ );
+ }
+
+ return handleBookTime(timeSlot);
+ };
+
+ return (
+
+
+ {paginatedBookings.map((timeSlot, index) => {
+ const isEHIF = timeSlot.HKServiceID > 0;
+ const serviceProviderTitle = getServiceProviderTitle(
+ currentLocale,
+ timeSlot.serviceProvider,
+ );
+ const price =
+ booking.selectedService?.variants?.[0]?.calculated_price
+ ?.calculated_amount ?? cartItem?.unit_price;
+ return (
+
+
+
{formatDateAndTime(timeSlot.StartTime.toString())}
+
+
+ {timeSlot.serviceProvider?.name}
+
+ {serviceProviderTitle && (
+
+ {serviceProviderTitle}
+
+ )}
+ {isEHIF && {t('booking:ehifBooking')}}
+
+
{timeSlot.location?.address}
+
+
+
+ {formatCurrency({
+ currencyCode: 'EUR',
+ locale: 'et-EE',
+ value: price ?? '',
+ })}
+
+
+
+
+ );
+ })}
+
+ {!paginatedBookings.length && (
+
+
{t('booking:noResults')}
+
+ )}
+
+
+ {totalPages > 1 && (
+
+
+ {t('common:pageOfPages', {
+ page: currentPage,
+ total: totalPages,
+ })}
+
+
+
+
+
+ {generatePageNumbers().map((page, index) => (
+
+ ))}
+
+
+
+
+ )}
+
+ );
+};
+
+export default TimeSlots;
diff --git a/app/home/(user)/_components/cart/cart-service-item.tsx b/app/home/(user)/_components/cart/cart-service-item.tsx
new file mode 100644
index 0000000..eed6fcd
--- /dev/null
+++ b/app/home/(user)/_components/cart/cart-service-item.tsx
@@ -0,0 +1,146 @@
+'use client';
+
+import { useState } from 'react';
+
+import { formatCurrency, formatDateAndTime } from '@/packages/shared/src/utils';
+import { useTranslation } from 'react-i18next';
+
+import { Button } from '@kit/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from '@kit/ui/dialog';
+import { TableCell, TableRow } from '@kit/ui/table';
+import { Trans } from '@kit/ui/trans';
+
+import BookingContainer from '../booking/booking-container';
+import CartItemDelete from './cart-item-delete';
+import { EnrichedCartItem } from './types';
+
+const EditCartServiceItemModal = ({
+ item,
+ onComplete,
+}: {
+ item: EnrichedCartItem | null;
+ onComplete: () => void;
+}) => {
+ if (!item) return null;
+
+ return (
+
+ );
+};
+
+export default function CartServiceItem({
+ item,
+ currencyCode,
+ isUnavailable,
+}: {
+ item: EnrichedCartItem;
+ currencyCode: string;
+ isUnavailable?: boolean;
+}) {
+ const [editingItem, setEditingItem] = useState(null);
+ const {
+ i18n: { language },
+ } = useTranslation();
+
+ return (
+ <>
+
+
+
+ {item.product_title}
+
+
+
+
+ {formatDateAndTime(item.reservation.startTime.toString())}
+
+
+
+ {item.reservation.location?.address ?? '-'}
+
+
+ {item.quantity}
+
+
+ {formatCurrency({
+ value: item.unit_price,
+ currencyCode,
+ locale: language,
+ })}
+
+
+
+ {formatCurrency({
+ value: item.total,
+ currencyCode,
+ locale: language,
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isUnavailable && (
+
+
+
+
+
+ )}
+ setEditingItem(null)}
+ />
+ >
+ );
+}
diff --git a/app/home/(user)/_components/cart/cart-service-items.tsx b/app/home/(user)/_components/cart/cart-service-items.tsx
new file mode 100644
index 0000000..ad5cc12
--- /dev/null
+++ b/app/home/(user)/_components/cart/cart-service-items.tsx
@@ -0,0 +1,72 @@
+import { StoreCart } from '@medusajs/types';
+
+import {
+ Table,
+ TableBody,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@kit/ui/table';
+import { Trans } from '@kit/ui/trans';
+
+import CartServiceItem from './cart-service-item';
+import { EnrichedCartItem } from './types';
+
+export default function CartServiceItems({
+ cart,
+ items,
+ productColumnLabelKey,
+ unavailableLineItemIds,
+}: {
+ cart: StoreCart;
+ items: EnrichedCartItem[];
+ productColumnLabelKey: string;
+ unavailableLineItemIds?: string[];
+}) {
+ if (!items || items.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {items
+ .sort((a, b) =>
+ (a.created_at ?? '') > (b.created_at ?? '') ? -1 : 1,
+ )
+ .map((item) => (
+
+ ))}
+
+
+ );
+}
diff --git a/app/home/(user)/_components/cart/index.tsx b/app/home/(user)/_components/cart/index.tsx
index 328dbdf..c289b7b 100644
--- a/app/home/(user)/_components/cart/index.tsx
+++ b/app/home/(user)/_components/cart/index.tsx
@@ -13,10 +13,12 @@ import { Trans } from '@kit/ui/trans';
import AnalysisLocation from './analysis-location';
import CartItems from './cart-items';
+import CartServiceItems from './cart-service-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';
+import { EnrichedCartItem } from './types';
const IS_DISCOUNT_SHOWN = true as boolean;
@@ -30,7 +32,7 @@ export default function Cart({
accountId: string;
cart: StoreCart | null;
synlabAnalyses: StoreCartLineItem[];
- ttoServiceItems: StoreCartLineItem[];
+ ttoServiceItems: EnrichedCartItem[];
balanceSummary: AccountBalanceSummary | null;
}) {
const {
@@ -39,6 +41,9 @@ export default function Cart({
const [isInitiatingSession, setIsInitiatingSession] = useState(false);
const router = useRouter();
+ const [unavailableLineItemIds, setUnavailableLineItemIds] =
+ useState();
+
const items = cart?.items ?? [];
const hasCartItems = cart && Array.isArray(items) && items.length > 0;
@@ -66,12 +71,15 @@ export default function Cart({
setIsInitiatingSession(true);
try {
- const { url, isFullyPaidByBenefits, orderId } = await initiatePayment({
+ const { url, isFullyPaidByBenefits, orderId, unavailableLineItemIds } = await initiatePayment({
accountId,
balanceSummary: balanceSummary!,
cart: cart!,
language,
});
+ if (unavailableLineItemIds) {
+ setUnavailableLineItemIds(unavailableLineItemIds);
+ }
if (url) {
window.location.href = url;
} else if (isFullyPaidByBenefits) {
@@ -99,10 +107,11 @@ export default function Cart({
items={synlabAnalyses}
productColumnLabelKey="cart:items.synlabAnalyses.productColumnLabel"
/>
-
{hasCartItems && (
@@ -202,6 +211,10 @@ export default function Cart({
cart={{ ...cart }}
synlabAnalyses={synlabAnalyses}
/>
+
)}
diff --git a/app/home/(user)/_components/cart/types.ts b/app/home/(user)/_components/cart/types.ts
index 9150557..aa5cc6d 100644
--- a/app/home/(user)/_components/cart/types.ts
+++ b/app/home/(user)/_components/cart/types.ts
@@ -1,3 +1,6 @@
+import { StoreCartLineItem } from "@medusajs/types";
+import { Reservation } from "~/lib/types/reservation";
+
export interface MontonioOrderToken {
uuid: string;
accessKey: string;
@@ -10,6 +13,12 @@ export interface MontonioOrderToken {
| 'PENDING'
| 'EXPIRED'
| 'REFUNDED';
+ | 'PAID'
+ | 'FAILED'
+ | 'CANCELLED'
+ | 'PENDING'
+ | 'EXPIRED'
+ | 'REFUNDED';
paymentMethod: string;
grandTotal: number;
currency: string;
@@ -20,3 +29,10 @@ export interface MontonioOrderToken {
iat: number;
exp: number;
}
+
+export enum CartItemType {
+ analysisOrders = 'analysisOrders',
+ ttoServices = 'ttoServices',
+}
+
+export type EnrichedCartItem = StoreCartLineItem & { reservation: Reservation };
diff --git a/app/home/(user)/_components/order-analyses-cards.tsx b/app/home/(user)/_components/order-analyses-cards.tsx
index 8d6a04d..88574fa 100644
--- a/app/home/(user)/_components/order-analyses-cards.tsx
+++ b/app/home/(user)/_components/order-analyses-cards.tsx
@@ -103,7 +103,6 @@ export default function OrderAnalysesCards({
{title}
{description && (
<>
- {' '}
diff --git a/app/home/(user)/_components/order/order-details.tsx b/app/home/(user)/_components/order/order-details.tsx
index 0dbe973..5148a38 100644
--- a/app/home/(user)/_components/order/order-details.tsx
+++ b/app/home/(user)/_components/order/order-details.tsx
@@ -2,16 +2,18 @@ import { formatDate } from 'date-fns';
import { Trans } from '@kit/ui/trans';
-import type { AnalysisOrder } from '~/lib/types/analysis-order';
-
-export default function OrderDetails({ order }: { order: AnalysisOrder }) {
+export default function OrderDetails({
+ order,
+}: {
+ order: { id: string; created_at: string | Date };
+}) {
return (
:{' '}
- {order.medusa_order_id}
+ {order.id}
diff --git a/app/home/(user)/_components/orders/order-block.tsx b/app/home/(user)/_components/orders/order-block.tsx
index 03f0447..08daf05 100644
--- a/app/home/(user)/_components/orders/order-block.tsx
+++ b/app/home/(user)/_components/orders/order-block.tsx
@@ -5,51 +5,77 @@ import { Eye } from 'lucide-react';
import { Trans } from '@kit/ui/makerkit/trans';
-import type { AnalysisOrder } from '~/lib/types/analysis-order';
+import type { AnalysisOrder } from '~/lib/types/order';
import OrderItemsTable from './order-items-table';
export default function OrderBlock({
analysisOrder,
+ medusaOrderStatus,
itemsAnalysisPackage,
+ itemsTtoService,
itemsOther,
+ medusaOrderId,
}: {
- analysisOrder: AnalysisOrder;
+ analysisOrder?: AnalysisOrder;
+ medusaOrderStatus: string;
itemsAnalysisPackage: StoreOrderLineItem[];
+ itemsTtoService: StoreOrderLineItem[];
itemsOther: StoreOrderLineItem[];
+ medusaOrderId: string;
}) {
return (
- {` (${analysisOrder.id})`}
-
-
-
-
-
-
-
-
+ {analysisOrder && (
+
+
+
+
+
+
+
+
+ )}
-
+ {analysisOrder && (
+
+ )}
+ {itemsTtoService && (
+
+ )}
diff --git a/app/home/(user)/_components/orders/order-items-table.tsx b/app/home/(user)/_components/orders/order-items-table.tsx
index 8511c08..09d50a9 100644
--- a/app/home/(user)/_components/orders/order-items-table.tsx
+++ b/app/home/(user)/_components/orders/order-items-table.tsx
@@ -4,7 +4,6 @@ import { useRouter } from 'next/navigation';
import { StoreOrderLineItem } from '@medusajs/types';
import { formatDate } from 'date-fns';
-import { Eye } from 'lucide-react';
import { pathsConfig } from '@kit/shared/config';
import { Button } from '@kit/ui/button';
@@ -18,18 +17,22 @@ import {
} from '@kit/ui/table';
import { Trans } from '@kit/ui/trans';
-import type { AnalysisOrder } from '~/lib/types/analysis-order';
+import type { Order } from '~/lib/types/order';
import { logAnalysisResultsNavigateAction } from './actions';
+export type OrderItemType = 'analysisOrder' | 'ttoService';
+
export default function OrderItemsTable({
items,
title,
- analysisOrder,
+ order,
+ type = 'analysisOrder',
}: {
items: StoreOrderLineItem[];
title: string;
- analysisOrder: AnalysisOrder;
+ order: Order;
+ type?: OrderItemType;
}) {
const router = useRouter();
@@ -37,9 +40,15 @@ export default function OrderItemsTable({
return null;
}
- const openAnalysisResults = async () => {
- await logAnalysisResultsNavigateAction(analysisOrder.medusa_order_id);
- router.push(`${pathsConfig.app.analysisResults}/${analysisOrder.id}`);
+ const isAnalysisOrder = type === 'analysisOrder';
+
+ const openDetailedView = async () => {
+ if (isAnalysisOrder && order?.medusaOrderId && order?.id) {
+ await logAnalysisResultsNavigateAction(order.medusaOrderId);
+ router.push(`${pathsConfig.app.analysisResults}/${order.id}`);
+ } else {
+ router.push(`${pathsConfig.app.myOrders}/${order.medusaOrderId}`);
+ }
};
return (
@@ -55,7 +64,7 @@ export default function OrderItemsTable({
-
+ {isAnalysisOrder &&
}
@@ -76,11 +85,13 @@ export default function OrderItemsTable({
-
+
-
diff --git a/app/home/(user)/_components/service-categories.tsx b/app/home/(user)/_components/service-categories.tsx
index 9ef3e25..6998b78 100644
--- a/app/home/(user)/_components/service-categories.tsx
+++ b/app/home/(user)/_components/service-categories.tsx
@@ -4,17 +4,20 @@ import React from 'react';
import { redirect } from 'next/navigation';
-import { createPath, pathsConfig } from '@/packages/shared/src/config';
+import { pathsConfig } from '@/packages/shared/src/config';
+import { StoreProduct } from '@medusajs/types';
import { ComponentInstanceIcon } from '@radix-ui/react-icons';
import { cn } from '@kit/ui/shadcn';
-import { Card, CardDescription, CardTitle } from '@kit/ui/shadcn/card';
+import { Card, CardDescription } from '@kit/ui/shadcn/card';
export interface ServiceCategory {
name: string;
handle: string;
color: string;
description: string;
+ products: StoreProduct[];
+ countryCode: string;
}
const ServiceCategories = ({
diff --git a/app/home/(user)/_lib/server/actions.ts b/app/home/(user)/_lib/server/actions.ts
new file mode 100644
index 0000000..f3998ca
--- /dev/null
+++ b/app/home/(user)/_lib/server/actions.ts
@@ -0,0 +1,43 @@
+'use server';
+
+import { updateLineItem } from '@lib/data/cart';
+import { StoreProductVariant } from '@medusajs/types';
+
+import { handleAddToCart } from '~/lib/services/medusaCart.service';
+import { createInitialReservation } from '~/lib/services/reservation.service';
+
+export async function createInitialReservationAction(
+ selectedVariant: Pick,
+ countryCode: string,
+ serviceId: number,
+ clinicId: number,
+ appointmentUserId: number,
+ syncUserId: number,
+ startTime: Date,
+ locationId: number | null,
+ comments?: string,
+) {
+ const { addedItem } = await handleAddToCart({
+ selectedVariant,
+ countryCode,
+ });
+
+ if (addedItem) {
+ const reservation = await createInitialReservation({
+ serviceId,
+ clinicId,
+ appointmentUserId,
+ syncUserID: syncUserId,
+ startTime,
+ medusaLineItemId: addedItem.id,
+ locationId,
+ comments,
+ });
+
+ await updateLineItem({
+ lineId: addedItem.id,
+ quantity: addedItem.quantity,
+ metadata: { connectedOnlineReservationId: reservation.id },
+ });
+ }
+}
diff --git a/app/home/(user)/_lib/server/cart-actions.ts b/app/home/(user)/_lib/server/cart-actions.ts
index b7023f7..b6c95f8 100644
--- a/app/home/(user)/_lib/server/cart-actions.ts
+++ b/app/home/(user)/_lib/server/cart-actions.ts
@@ -17,6 +17,9 @@ import { AccountWithParams } from "@/packages/features/accounts/src/types/accoun
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 { bookAppointment } from '~/lib/services/connected-online.service';
const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages';
const ANALYSIS_TYPE_HANDLE = 'synlab-analysis';
@@ -77,14 +80,14 @@ export const initiatePayment = async ({
if (!montonioPaymentSessionId) {
throw new Error('Montonio payment session ID is missing');
}
- const url = await handleNavigateToPayment({
+ const props = await handleNavigateToPayment({
language,
paymentSessionId: montonioPaymentSessionId,
amount: totalByMontonio,
currencyCode: cart.currency_code,
cartId: cart.id,
});
- return { url };
+ return { ...props, isFullyPaidByBenefits };
} else {
// place order if all paid already
const { orderId } = await handlePlaceOrder({ cart });
@@ -109,13 +112,13 @@ export const initiatePayment = async ({
if (!webhookResponse.ok) {
throw new Error('Failed to send company benefits webhook');
}
- return { isFullyPaidByBenefits, orderId };
+ return { isFullyPaidByBenefits, orderId, unavailableLineItemIds: [] };
}
} catch (error) {
console.error('Error initiating payment', error);
}
- return { url: null }
+ return { url: null, isFullyPaidByBenefits: false, orderId: null, unavailableLineItemIds: [] };
}
export async function handlePlaceOrder({
@@ -136,6 +139,8 @@ export async function handlePlaceOrder({
medusaOrder,
});
+ const orderContainsSynlabItems = !!orderedAnalysisElements?.length;
+
try {
const existingAnalysisOrder = await getAnalysisOrder({
medusaOrderId: medusaOrder.id,
@@ -148,15 +153,38 @@ export async function handlePlaceOrder({
// ignored
}
- const orderId = await createAnalysisOrder({
- medusaOrder,
- orderedAnalysisElements,
- });
+ let orderId: number | undefined = undefined;
+ if (orderContainsSynlabItems) {
+ orderId = await createAnalysisOrder({
+ medusaOrder,
+ orderedAnalysisElements,
+ });
+ }
+
const orderResult = await getOrderResultParameters(medusaOrder);
const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } =
orderResult;
+ const orderedTtoServices = await getOrderedTtoServices({ medusaOrder });
+ let bookServiceResults: {
+ success: boolean;
+ reason?: FailureReason;
+ serviceId?: number;
+ }[] = [];
+ if (orderedTtoServices?.length) {
+ const bookingPromises = orderedTtoServices.map((service) =>
+ bookAppointment(
+ service.service_id,
+ service.clinic_id,
+ service.service_user_id,
+ service.sync_user_id,
+ service.start_time,
+ ),
+ );
+ bookServiceResults = await Promise.all(bookingPromises);
+ }
+
if (email) {
if (analysisPackageOrder) {
await sendAnalysisPackageOrderEmail({
@@ -184,6 +212,17 @@ export async function handlePlaceOrder({
await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements });
}
+ if (bookServiceResults.some(({ success }) => success === false)) {
+ const failedServiceBookings = bookServiceResults.filter(
+ ({ success }) => success === false,
+ );
+ return {
+ success: false,
+ failedServiceBookings,
+ orderId,
+ };
+ }
+
return { success: true, orderId };
} catch (error) {
console.error('Failed to place order', error);
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;
+ }
+};
diff --git a/app/home/(user)/_lib/server/load-analyses.ts b/app/home/(user)/_lib/server/load-analyses.ts
index 411d68a..8b9cf99 100644
--- a/app/home/(user)/_lib/server/load-analyses.ts
+++ b/app/home/(user)/_lib/server/load-analyses.ts
@@ -45,10 +45,6 @@ async function analysesLoader() {
})
: null;
- const serviceCategories = productCategories.filter(
- ({ parent_category }) => parent_category?.handle === 'tto-categories',
- );
-
return {
analyses:
categoryProducts?.response.products
diff --git a/app/home/(user)/_lib/server/load-category.ts b/app/home/(user)/_lib/server/load-category.ts
index 2c0479c..72e22d4 100644
--- a/app/home/(user)/_lib/server/load-category.ts
+++ b/app/home/(user)/_lib/server/load-category.ts
@@ -1,20 +1,30 @@
import { cache } from 'react';
-import { getProductCategories } from '@lib/data';
+import { getProductCategories, listProducts } from '@lib/data';
-import { ServiceCategory } from '../../_components/service-categories';
-
-async function categoryLoader({
- handle,
-}: {
- handle: string;
-}): Promise<{ category: ServiceCategory | null }> {
- const response = await getProductCategories({
- handle,
- fields: '*products, is_active, metadata',
- });
+import { loadCountryCodes } from './load-analyses';
+async function categoryLoader({ handle }: { handle: string }) {
+ const [response, countryCodes] = await Promise.all([
+ getProductCategories({
+ handle,
+ limit: 1,
+ }),
+ loadCountryCodes(),
+ ]);
const category = response.product_categories[0];
+ const countryCode = countryCodes[0]!;
+
+ if (!response.product_categories?.[0]?.id) {
+ return { category: null };
+ }
+
+ const {
+ response: { products: categoryProducts },
+ } = await listProducts({
+ countryCode,
+ queryParams: { limit: 100, category_id: response.product_categories[0].id },
+ });
return {
category: {
@@ -25,6 +35,8 @@ async function categoryLoader({
description: category?.description || '',
handle: category?.handle || '',
name: category?.name || '',
+ countryCode,
+ products: categoryProducts,
},
};
}
diff --git a/app/home/(user)/_lib/server/load-tto-services.ts b/app/home/(user)/_lib/server/load-tto-services.ts
index 3bbc4e5..530f959 100644
--- a/app/home/(user)/_lib/server/load-tto-services.ts
+++ b/app/home/(user)/_lib/server/load-tto-services.ts
@@ -10,38 +10,36 @@ async function ttoServicesLoader() {
});
const heroCategories = response.product_categories?.filter(
- ({ parent_category, is_active, metadata }) =>
- parent_category?.handle === 'tto-categories' &&
- is_active &&
- metadata?.isHero,
+ ({ parent_category, metadata }) =>
+ parent_category?.handle === 'tto-categories' && metadata?.isHero,
);
const ttoCategories = response.product_categories?.filter(
- ({ parent_category, is_active, metadata }) =>
- parent_category?.handle === 'tto-categories' &&
- is_active &&
- !metadata?.isHero,
+ ({ parent_category, metadata }) =>
+ parent_category?.handle === 'tto-categories' && !metadata?.isHero,
);
return {
heroCategories:
- heroCategories.map(
- ({ name, handle, metadata, description }) => ({
+ heroCategories.map>(
+ ({ name, handle, metadata, description, products }) => ({
name,
handle,
color:
typeof metadata?.color === 'string' ? metadata.color : 'primary',
description,
+ products: products ?? [],
}),
) ?? [],
ttoCategories:
- ttoCategories.map(
- ({ name, handle, metadata, description }) => ({
+ ttoCategories.map>(
+ ({ name, handle, metadata, description, products }) => ({
name,
handle,
color:
typeof metadata?.color === 'string' ? metadata.color : 'primary',
description,
+ products: products ?? [],
}),
) ?? [],
};
diff --git a/app/home/[account]/_components/team-account-layout-sidebar.tsx b/app/home/[account]/_components/team-account-layout-sidebar.tsx
index f2c9626..67cf05b 100644
--- a/app/home/[account]/_components/team-account-layout-sidebar.tsx
+++ b/app/home/[account]/_components/team-account-layout-sidebar.tsx
@@ -52,7 +52,6 @@ function SidebarContainer(props: {
-
);
}
diff --git a/app/home/[account]/billing/_components/health-benefit-fields.tsx b/app/home/[account]/billing/_components/health-benefit-fields.tsx
index 55c533c..a3947de 100644
--- a/app/home/[account]/billing/_components/health-benefit-fields.tsx
+++ b/app/home/[account]/billing/_components/health-benefit-fields.tsx
@@ -30,7 +30,9 @@ const HealthBenefitFields = () => {