3 Commits

Author SHA1 Message Date
bc61db07b2 test2 2025-09-04 01:21:42 +03:00
3d58e5aa84 try to display price before adding to cart 2025-09-03 23:04:09 +03:00
8ecca096f2 fix adding to cart loading 2025-09-03 23:03:00 +03:00
11 changed files with 62 additions and 18 deletions

2
.env
View File

@@ -13,7 +13,7 @@ NEXT_PUBLIC_THEME_COLOR="#ffffff"
NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a" NEXT_PUBLIC_THEME_COLOR_DARK="#0a0a0a"
# AUTH # AUTH
NEXT_PUBLIC_AUTH_PASSWORD=true NEXT_PUBLIC_AUTH_PASSWORD=false
NEXT_PUBLIC_AUTH_MAGIC_LINK=false NEXT_PUBLIC_AUTH_MAGIC_LINK=false
NEXT_PUBLIC_CAPTCHA_SITE_KEY= NEXT_PUBLIC_CAPTCHA_SITE_KEY=

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { HeartPulse, Loader2, ShoppingCart } from 'lucide-react'; import { HeartPulse, Loader2, ShoppingCart } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { import {
@@ -15,12 +16,14 @@ import { handleAddToCart } from '~/lib/services/medusaCart.service';
import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip'; import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { toast } from '@kit/ui/sonner'; import { toast } from '@kit/ui/sonner';
import { formatCurrency } from '@/packages/shared/src/utils';
export type OrderAnalysisCard = Pick< export type OrderAnalysisCard = Pick<
StoreProduct, 'title' | 'description' | 'subtitle' StoreProduct, 'title' | 'description' | 'subtitle'
> & { > & {
isAvailable: boolean; isAvailable: boolean;
variant: { id: string }; variant: { id: string };
price: number | null;
}; };
export default function OrderAnalysesCards({ export default function OrderAnalysesCards({
@@ -30,23 +33,26 @@ export default function OrderAnalysesCards({
analyses: OrderAnalysisCard[]; analyses: OrderAnalysisCard[];
countryCode: string; countryCode: string;
}) { }) {
const [isAddingToCart, setIsAddingToCart] = useState(false);
const { i18n: { language } } = useTranslation()
const [variantAddingToCart, setVariantAddingToCart] = useState<string | null>(null);
const handleSelect = async (variantId: string) => { const handleSelect = async (variantId: string) => {
if (isAddingToCart) { if (variantAddingToCart) {
return null; return null;
} }
setIsAddingToCart(true); setVariantAddingToCart(variantId);
try { try {
await handleAddToCart({ await handleAddToCart({
selectedVariant: { id: variantId }, selectedVariant: { id: variantId },
countryCode, countryCode,
}); });
toast.success(<Trans i18nKey={'order-analysis:analysisAddedToCart'} />); toast.success(<Trans i18nKey={'order-analysis:analysisAddedToCart'} />);
setIsAddingToCart(false); setVariantAddingToCart(null);
} catch (e) { } catch (e) {
toast.error(<Trans i18nKey={'order-analysis:analysisAddToCartError'} />); toast.error(<Trans i18nKey={'order-analysis:analysisAddToCartError'} />);
setIsAddingToCart(false); setVariantAddingToCart(null);
console.error(e); console.error(e);
} }
} }
@@ -59,7 +65,15 @@ export default function OrderAnalysesCards({
description, description,
subtitle, subtitle,
isAvailable, isAvailable,
price,
}) => { }) => {
const formattedPrice = typeof price === 'number'
? formatCurrency({
currencyCode: 'eur',
locale: language,
value: price,
})
: null;
return ( return (
<Card <Card
key={title} key={title}
@@ -80,7 +94,7 @@ export default function OrderAnalysesCards({
className="px-2 text-black" className="px-2 text-black"
onClick={() => handleSelect(variant.id)} onClick={() => handleSelect(variant.id)}
> >
{isAddingToCart ? <Loader2 className="size-4 stroke-2 animate-spin" /> : <ShoppingCart className="size-4 stroke-2" />} {variantAddingToCart ? <Loader2 className="size-4 stroke-2 animate-spin" /> : <ShoppingCart className="size-4 stroke-2" />}
</Button> </Button>
</div> </div>
)} )}
@@ -91,7 +105,14 @@ export default function OrderAnalysesCards({
{description && ( {description && (
<> <>
{' '} {' '}
<InfoTooltip content={`${description}`} /> <InfoTooltip
content={
<div className='flex flex-col gap-2'>
<span>{formattedPrice}</span>
<span>{description}</span>
</div>
}
/>
</> </>
)} )}
</h5> </h5>

View File

@@ -1,11 +1,10 @@
import { cache } from 'react'; import { cache } from 'react';
import { getProductCategories } from '@lib/data/categories'; import { getProductCategories } from '@lib/data/categories';
import { listProductTypes } from '@lib/data/products'; import { listProducts, listProductTypes } from '@lib/data/products';
import { listRegions } from '@lib/data/regions'; import { listRegions } from '@lib/data/regions';
import { OrderAnalysisCard } from '../../_components/order-analyses-cards'; import { OrderAnalysisCard } from '../../_components/order-analyses-cards';
import { ServiceCategory } from '../../_components/service-categories';
async function countryCodesLoader() { async function countryCodesLoader() {
const countryCodes = await listRegions().then((regions) => const countryCodes = await listRegions().then((regions) =>
@@ -39,13 +38,20 @@ async function analysesLoader() {
const category = productCategories.find( const category = productCategories.find(
({ metadata }) => metadata?.page === 'order-analysis', ({ metadata }) => metadata?.page === 'order-analysis',
); );
const categoryProducts = category
? await listProducts({
countryCode,
queryParams: { limit: 100, category_id: category.id },
})
: null;
const serviceCategories = productCategories.filter( const serviceCategories = productCategories.filter(
({ parent_category }) => parent_category?.handle === 'tto-categories', ({ parent_category }) => parent_category?.handle === 'tto-categories',
); );
return { return {
analyses: analyses:
category?.products?.map<OrderAnalysisCard>( categoryProducts?.response.products.map<OrderAnalysisCard>(
({ title, description, subtitle, variants, status, metadata }) => { ({ title, description, subtitle, variants, status, metadata }) => {
const variant = variants![0]!; const variant = variants![0]!;
return { return {
@@ -57,6 +63,7 @@ async function analysesLoader() {
}, },
isAvailable: isAvailable:
status === 'published' && !!metadata?.analysisIdOriginal, status === 'published' && !!metadata?.analysisIdOriginal,
price: variant.calculated_price?.calculated_amount ?? null,
}; };
}, },
) ?? [], ) ?? [],

View File

@@ -5,7 +5,6 @@ import { Database } from '@kit/supabase/database';
import { import {
AnalysisResultDetails, AnalysisResultDetails,
UserAnalysis, UserAnalysis,
UserAnalysisResponse,
} from '../types/accounts'; } from '../types/accounts';
export type AccountWithParams = export type AccountWithParams =

View File

@@ -102,6 +102,7 @@ export const OauthProviders: React.FC<{
redirectTo, redirectTo,
queryParams: props.queryParams, queryParams: props.queryParams,
scopes, scopes,
skipBrowserRedirect: false,
}, },
} satisfies SignInWithOAuthCredentials; } satisfies SignInWithOAuthCredentials;

View File

@@ -14,7 +14,7 @@ export const listProducts = async ({
regionId, regionId,
}: { }: {
pageParam?: number pageParam?: number
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & { "type_id[0]"?: string; id?: string[] } queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & { "type_id[0]"?: string; id?: string[], category_id?: string }
countryCode?: string countryCode?: string
regionId?: string regionId?: string
}): Promise<{ }): Promise<{
@@ -63,7 +63,7 @@ export const listProducts = async ({
offset, offset,
region_id: region?.id, region_id: region?.id,
fields: fields:
"*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags", "*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,+status",
...queryParams, ...queryParams,
}, },
headers, headers,

View File

@@ -13,7 +13,7 @@ export function InfoTooltip({
content, content,
icon, icon,
}: { }: {
content?: string | null; content?: JSX.Element | string | null;
icon?: JSX.Element; icon?: JSX.Element;
}) { }) {
if (!content) return null; if (!content) return null;
@@ -23,7 +23,7 @@ export function InfoTooltip({
<TooltipTrigger> <TooltipTrigger>
{icon || <Info className="size-4 cursor-pointer" />} {icon || <Info className="size-4 cursor-pointer" />}
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>{content}</TooltipContent> <TooltipContent className='sm:max-w-[400px]'>{content}</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
); );

View File

@@ -40,7 +40,7 @@ const authConfig = AuthConfigSchema.parse({
providers: { providers: {
password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true', password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true',
magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true', magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true',
oAuth: ['google'], oAuth: ['keycloak'],
}, },
} satisfies z.infer<typeof AuthConfigSchema>); } satisfies z.infer<typeof AuthConfigSchema>);

View File

@@ -10,5 +10,11 @@ import { getSupabaseClientKeys } from '../get-supabase-client-keys';
export function getSupabaseBrowserClient<GenericSchema = Database>() { export function getSupabaseBrowserClient<GenericSchema = Database>() {
const keys = getSupabaseClientKeys(); const keys = getSupabaseClientKeys();
return createBrowserClient<GenericSchema>(keys.url, keys.anonKey); return createBrowserClient<GenericSchema>(keys.url, keys.anonKey, {
auth: {
flowType: 'pkce',
autoRefreshToken: true,
persistSession: true,
},
});
} }

View File

@@ -20,6 +20,11 @@ export function createMiddlewareClient<GenericSchema = Database>(
const keys = getSupabaseClientKeys(); const keys = getSupabaseClientKeys();
return createServerClient<GenericSchema>(keys.url, keys.anonKey, { return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
auth: {
flowType: 'pkce',
autoRefreshToken: true,
persistSession: true,
},
cookies: { cookies: {
getAll() { getAll() {
return request.cookies.getAll(); return request.cookies.getAll();

View File

@@ -15,6 +15,11 @@ export function getSupabaseServerClient<GenericSchema = Database>() {
const keys = getSupabaseClientKeys(); const keys = getSupabaseClientKeys();
return createServerClient<GenericSchema>(keys.url, keys.anonKey, { return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
auth: {
flowType: 'pkce',
autoRefreshToken: true,
persistSession: true,
},
cookies: { cookies: {
async getAll() { async getAll() {
const cookieStore = await cookies(); const cookieStore = await cookies();