MED-197: improve analyses package compare page

MED-197
This commit is contained in:
danelkungla
2025-10-07 18:09:03 +03:00
committed by GitHub
14 changed files with 218 additions and 18 deletions

View File

@@ -28,17 +28,18 @@ async function OrderAnalysisPackagePage() {
<PageBody> <PageBody>
<div className="space-y-3 text-center"> <div className="space-y-3 text-center">
<h3> <h3>
<Trans i18nKey={'marketing:selectPackage'} /> <Trans i18nKey="order-analysis-package:selectPackage" />
</h3> </h3>
<ComparePackagesModal <ComparePackagesModal
analysisPackages={analysisPackages} analysisPackages={analysisPackages}
analysisPackageElements={analysisPackageElements} analysisPackageElements={analysisPackageElements}
triggerElement={ triggerElement={
<Button variant="secondary" className="gap-2"> <Button variant="secondary" className="gap-2">
<Trans i18nKey={'marketing:comparePackages'} /> <Trans i18nKey="order-analysis-package:comparePackages" />
<Scale className="size-4 stroke-[1.5px]" /> <Scale className="size-4 stroke-[1.5px]" />
</Button> </Button>
} }
countryCode={countryCode}
/> />
</div> </div>
<SelectAnalysisPackages <SelectAnalysisPackages

View File

@@ -0,0 +1,115 @@
'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import { AnalysisPackageWithVariant } from '@/packages/shared/src/components/select-analysis-package';
import { pathsConfig } from '@/packages/shared/src/config';
import { Spinner } from '@kit/ui/makerkit/spinner';
import { Trans } from '@kit/ui/makerkit/trans';
import { Button } from '@kit/ui/shadcn/button';
import { toast } from '@kit/ui/shadcn/sonner';
import { Table, TableBody, TableCell, TableRow } from '@kit/ui/shadcn/table';
import { handleAddToCart } from '~/lib/services/medusaCart.service';
import { cn } from '~/lib/utils';
const AddToCartButton = ({
onClick,
disabled,
isLoading,
}: {
onClick: () => void;
disabled: boolean;
isLoading: boolean;
}) => {
return (
<TableCell align="center" className="xs:px-2 px-1 py-6">
<Button
onClick={onClick}
disabled={disabled}
className="xs:p-6 xs:text-sm relative p-2 text-[10px]"
>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center">
<Spinner />
</div>
)}
<div
className={cn({
invisible: isLoading,
})}
>
<Trans i18nKey="compare-packages-modal:selectThisPackage" />
</div>
</Button>
</TableCell>
);
};
const ComparePackagesAddToCartButtons = ({
countryCode,
standardPackage,
standardPlusPackage,
premiumPackage,
}: {
countryCode: string;
standardPackage: AnalysisPackageWithVariant;
standardPlusPackage: AnalysisPackageWithVariant;
premiumPackage: AnalysisPackageWithVariant;
}) => {
const [addedPackage, setAddedPackage] = useState<string | null>(null);
const router = useRouter();
const handleSelect = async ({ variantId }: AnalysisPackageWithVariant) => {
setAddedPackage(variantId);
try {
await handleAddToCart({
selectedVariant: { id: variantId },
countryCode,
});
setAddedPackage(null);
toast.success(
<Trans i18nKey={'order-analysis-package:analysisPackageAddedToCart'} />,
);
router.push(pathsConfig.app.cart);
} catch (e) {
toast.error(
<Trans
i18nKey={'order-analysis-package:analysisPackageAddToCartError'}
/>,
);
setAddedPackage(null);
console.error(e);
}
};
return (
<Table>
<TableBody>
<TableRow>
<TableCell className="w-[30vw] py-6" />
<AddToCartButton
onClick={() => handleSelect(standardPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === standardPackage.variantId}
/>
<AddToCartButton
onClick={() => handleSelect(standardPlusPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === standardPlusPackage.variantId}
/>
<AddToCartButton
onClick={() => handleSelect(premiumPackage)}
disabled={!!addedPackage}
isLoading={addedPackage === premiumPackage.variantId}
/>
</TableRow>
</TableBody>
</Table>
);
};
export default ComparePackagesAddToCartButtons;

View File

@@ -26,6 +26,9 @@ import {
import { createI18nServerInstance } from '~/lib/i18n/i18n.server'; import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import ComparePackagesAddToCartButtons from './compare-packages-add-to-cart-buttons';
import DefaultPackageFeaturesRows from './default-package-features-rows';
export type AnalysisPackageElement = Pick< export type AnalysisPackageElement = Pick<
StoreProduct, StoreProduct,
'title' | 'id' | 'description' 'title' | 'id' | 'description'
@@ -35,7 +38,7 @@ export type AnalysisPackageElement = Pick<
isIncludedInPremium: boolean; isIncludedInPremium: boolean;
}; };
const CheckWithBackground = () => { export const CheckWithBackground = () => {
return ( return (
<div className="bg-primary w-min rounded-full p-1 text-white"> <div className="bg-primary w-min rounded-full p-1 text-white">
<Check className="size-3 stroke-2" /> <Check className="size-3 stroke-2" />
@@ -53,7 +56,7 @@ const PackageTableHead = async ({
const { title, price, nrOfAnalyses } = product; const { title, price, nrOfAnalyses } = product;
return ( return (
<TableHead className="py-2"> <TableHead className="xs:content-normal content-start py-2">
<PackageHeader <PackageHeader
title={t(title)} title={t(title)}
tagColor="bg-cyan" tagColor="bg-cyan"
@@ -69,10 +72,12 @@ const ComparePackagesModal = async ({
analysisPackages, analysisPackages,
analysisPackageElements, analysisPackageElements,
triggerElement, triggerElement,
countryCode,
}: { }: {
analysisPackages: AnalysisPackageWithVariant[]; analysisPackages: AnalysisPackageWithVariant[];
analysisPackageElements: AnalysisPackageElement[]; analysisPackageElements: AnalysisPackageElement[];
triggerElement: JSX.Element; triggerElement: JSX.Element;
countryCode: string;
}) => { }) => {
const { t } = await createI18nServerInstance(); const { t } = await createI18nServerInstance();
@@ -110,7 +115,7 @@ const ComparePackagesModal = async ({
<p className="text-muted-foreground mx-auto w-3/5 text-sm"> <p className="text-muted-foreground mx-auto w-3/5 text-sm">
{t('product:healthPackageComparison.description')} {t('product:healthPackageComparison.description')}
</p> </p>
<div className="max-h-[80vh] overflow-y-auto rounded-md border"> <div className="max-h-[50vh] overflow-y-auto rounded-md border sm:max-h-[70vh]">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
@@ -121,6 +126,8 @@ const ComparePackagesModal = async ({
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<DefaultPackageFeaturesRows />
{analysisPackageElements.map( {analysisPackageElements.map(
({ ({
title, title,
@@ -136,7 +143,7 @@ const ComparePackagesModal = async ({
return ( return (
<TableRow key={id}> <TableRow key={id}>
<TableCell className="py-6 sm:max-w-[30vw]"> <TableCell className="py-6 sm:w-[30vw]">
{title}{' '} {title}{' '}
{description && ( {description && (
<InfoTooltip <InfoTooltip
@@ -164,6 +171,12 @@ const ComparePackagesModal = async ({
</Table> </Table>
</div> </div>
</div> </div>
<ComparePackagesAddToCartButtons
countryCode={countryCode}
standardPackage={standardPackage}
premiumPackage={premiumPackage}
standardPlusPackage={standardPlusPackage}
/>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Trans } from '@kit/ui/makerkit/trans';
import { TableCell, TableRow } from '@kit/ui/shadcn/table';
import { withI18n } from '~/lib/i18n/with-i18n';
import { CheckWithBackground } from './compare-packages-modal';
const DefaultPackageFeaturesRows = () => {
return (
<>
<TableRow key="digital-doctor-feedback">
<TableCell className="max-w-[30vw] py-6">
<Trans i18nKey="order-analysis-package:digitalDoctorFeedback" />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
</TableRow>
<TableRow key="give-analyses">
<TableCell className="py-6 sm:max-w-[30vw]">
<Trans i18nKey="order-analysis-package:giveAnalyses" />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
<TableCell align="center" className="py-6">
<CheckWithBackground />
</TableCell>
</TableRow>
</>
);
};
export default withI18n(DefaultPackageFeaturesRows);

View File

@@ -2,6 +2,7 @@
import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account';
import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src'; import { MontonioOrderHandlerService } from '@/packages/billing/montonio/src';
import { getLogger } from '@/packages/shared/src/logger';
import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart'; import { addToCart, deleteLineItem, retrieveCart } from '@lib/data/cart';
import { getCartId } from '@lib/data/cookies'; import { getCartId } from '@lib/data/cookies';
import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types'; import { StoreCartLineItem, StoreProductVariant } from '@medusajs/types';
@@ -44,12 +45,17 @@ export async function handleAddToCart({
selectedVariant: Pick<StoreProductVariant, 'id'>; selectedVariant: Pick<StoreProductVariant, 'id'>;
countryCode: string; countryCode: string;
}) { }) {
try { const logger = await getLogger();
} catch (e) { const ctx = {
console.error('medusa card error: ', e); countryCode,
} selectedVariant,
};
logger.info(ctx, 'Adding to cart...');
const { account } = await loadCurrentUserAccount(); const { account } = await loadCurrentUserAccount();
if (!account) { if (!account) {
logger.error(ctx, 'Account not found');
throw new Error('Account not found'); throw new Error('Account not found');
} }

View File

@@ -7,6 +7,8 @@ import { sdk } from '@lib/config';
import medusaError from '@lib/util/medusa-error'; import medusaError from '@lib/util/medusa-error';
import { HttpTypes, StoreCart } from '@medusajs/types'; import { HttpTypes, StoreCart } from '@medusajs/types';
import { getLogger } from '@kit/shared/logger';
import { import {
getAuthHeaders, getAuthHeaders,
getCacheOptions, getCacheOptions,
@@ -135,13 +137,21 @@ export async function addToCart({
quantity: number; quantity: number;
countryCode: string; countryCode: string;
}) { }) {
const logger = await getLogger();
const ctx = {
variantId,
quantity,
countryCode,
};
if (!variantId) { if (!variantId) {
logger.error(ctx, 'Missing variant ID when adding to cart');
throw new Error('Missing variant ID when adding to cart'); throw new Error('Missing variant ID when adding to cart');
} }
const cart = await getOrSetCart(countryCode); const cart = await getOrSetCart(countryCode);
if (!cart) { if (!cart) {
logger.error(ctx, 'Error retrieving or creating cart');
throw new Error('Error retrieving or creating cart'); throw new Error('Error retrieving or creating cart');
} }

View File

@@ -18,14 +18,16 @@ export const PackageHeader = ({
return ( return (
<div className="space-y-1 text-center"> <div className="space-y-1 text-center">
<p className="text-sm sm:text-lg sm:font-medium">{title}</p> <p className="text-sm sm:text-lg sm:font-medium">{title}</p>
<h2 className="text-xl sm:text-4xl"> <h2 className="xs:text-xl text-lg sm:text-4xl">
{formatCurrency({ {formatCurrency({
currencyCode: 'eur', currencyCode: 'eur',
locale: language, locale: language,
value: price, value: price,
})} })}
</h2> </h2>
<Badge className={cn('text-xs', tagColor)}>{analysesNr}</Badge> <Badge className={cn('xs:text-xs text-[10px]', tagColor)}>
{analysesNr}
</Badge>
</div> </div>
); );
}; };

View File

@@ -117,6 +117,7 @@ export default function SelectAnalysisPackage({
<Button <Button
className="w-full text-[10px] sm:text-sm" className="w-full text-[10px] sm:text-sm"
onClick={handleSelect} onClick={handleSelect}
disabled={isAddingToCart}
> >
{isAddingToCart ? ( {isAddingToCart ? (
<Spinner /> <Spinner />

View File

@@ -72,7 +72,7 @@
"myOrders": "My orders", "myOrders": "My orders",
"analysisResults": "Analysis results", "analysisResults": "Analysis results",
"orderAnalysisPackage": "Order analysis package", "orderAnalysisPackage": "Order analysis package",
"orderAnalysis": "Order analysis", "orderAnalysis": "Order single analysis",
"orderHealthAnalysis": "Order health check", "orderHealthAnalysis": "Order health check",
"account": "Account", "account": "Account",
"companyMembers": "Manage employees", "companyMembers": "Manage employees",

View File

@@ -5,5 +5,7 @@
"selectPackage": "Select package", "selectPackage": "Select package",
"comparePackages": "Compare packages", "comparePackages": "Compare packages",
"analysisPackageAddedToCart": "Analysis package added to cart", "analysisPackageAddedToCart": "Analysis package added to cart",
"analysisPackageAddToCartError": "Adding analysis package to cart failed" "analysisPackageAddToCartError": "Adding analysis package to cart failed",
"digitalDoctorFeedback": "Digital doctor feedback",
"giveAnalyses": "Analyses"
} }

View File

@@ -72,7 +72,7 @@
"myOrders": "Minu tellimused", "myOrders": "Minu tellimused",
"analysisResults": "Analüüside vastused", "analysisResults": "Analüüside vastused",
"orderAnalysisPackage": "Telli analüüside pakett", "orderAnalysisPackage": "Telli analüüside pakett",
"orderAnalysis": "Telli analüüs", "orderAnalysis": "Telli üksikanalüüs",
"orderHealthAnalysis": "Telli terviseuuring", "orderHealthAnalysis": "Telli terviseuuring",
"account": "Konto", "account": "Konto",
"companyMembers": "Töötajate haldamine", "companyMembers": "Töötajate haldamine",

View File

@@ -15,7 +15,7 @@
"recommendedForYou": "Soovitused sulle", "recommendedForYou": "Soovitused sulle",
"heroCard": { "heroCard": {
"orderAnalysis": { "orderAnalysis": {
"title": "Telli analüüs", "title": "Telli üksikanalüüs",
"description": "Telli endale sobiv analüüs" "description": "Telli endale sobiv analüüs"
}, },
"benefits": { "benefits": {

View File

@@ -5,5 +5,7 @@
"selectPackage": "Vali pakett", "selectPackage": "Vali pakett",
"comparePackages": "Võrdle pakette", "comparePackages": "Võrdle pakette",
"analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi", "analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi",
"analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus" "analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus",
"digitalDoctorFeedback": "Digitaalne arsti kokkuvõte",
"giveAnalyses": "Analüüside võtmine"
} }

View File

@@ -5,5 +5,7 @@
"selectPackage": "Выберите пакет", "selectPackage": "Выберите пакет",
"comparePackages": "Сравнить пакеты", "comparePackages": "Сравнить пакеты",
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину", "analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину" "analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину",
"digitalDoctorFeedback": "Digital doctor feedback",
"giveAnalyses": "Analyses"
} }