MED-197: improve analyses package compare page
MED-197
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
"selectPackage": "Выберите пакет",
|
"selectPackage": "Выберите пакет",
|
||||||
"comparePackages": "Сравнить пакеты",
|
"comparePackages": "Сравнить пакеты",
|
||||||
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
|
"analysisPackageAddedToCart": "Пакет анализов добавлен в корзину",
|
||||||
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину"
|
"analysisPackageAddToCartError": "Не удалось добавить пакет анализов в корзину",
|
||||||
|
"digitalDoctorFeedback": "Digital doctor feedback",
|
||||||
|
"giveAnalyses": "Analyses"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user