From 60df2d658f3378fe9c41c10bf1f32c2b28742205 Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Mon, 27 Jul 2026 18:34:56 +0000 Subject: [PATCH] Deploy --- src/components/shop/ProductDetailClient.tsx | 159 ++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/components/shop/ProductDetailClient.tsx diff --git a/src/components/shop/ProductDetailClient.tsx b/src/components/shop/ProductDetailClient.tsx new file mode 100644 index 0000000..18c6bb4 --- /dev/null +++ b/src/components/shop/ProductDetailClient.tsx @@ -0,0 +1,159 @@ +'use client' +import { useState, useEffect } from 'react' +import Image from 'next/image' +import Link from 'next/link' +import { Check, Minus, Plus, ShoppingBag } from 'lucide-react' +import { useMedusaCart } from '@/hooks/useMedusaCart' +import YouMayAlsoLike from '@/components/shop/YouMayAlsoLike' + +const CURRENCY_SYMBOLS: Record = { zar: 'R', usd: '$', eur: '€', gbp: '£', kes: 'KSh', ngn: '₦', ghs: '₵' } +const formatPrice = (amount: number, code?: string) => { + const sym = CURRENCY_SYMBOLS[(code ?? '').toLowerCase()] ?? (code?.toUpperCase() ?? '') + return `${sym}${(amount / 100).toFixed(2)}` +} + +interface MedusaVariant { id: string; title: string; prices: Array<{ amount: number; currency_code: string }> } +interface MedusaProduct { + id: string; title: string; description: string | null + thumbnail: string | null; images?: Array<{ url: string }> + variants: MedusaVariant[]; collection?: { id: string; title: string } +} + +export default function ProductDetailClient({ product: initialProduct, productId }: { product: MedusaProduct | null; productId: string }) { + const [product, setProduct] = useState(initialProduct) + const [loading, setLoading] = useState(!initialProduct) + const [selectedVariant, setSelectedVariant] = useState(initialProduct?.variants?.[0] ?? null) + const [activeImage, setActiveImage] = useState(0) + const [quantity, setQuantity] = useState(1) + const [added, setAdded] = useState(false) + const { addToCart } = useMedusaCart() + + useEffect(() => { + if (initialProduct) return + const run = async () => { + try { + const regionRes = await fetch('/api/medusa/store/regions?limit=1') + const regionData = regionRes.ok ? await regionRes.json() : {} + const regionId = regionData?.regions?.[0]?.id + const params = new URLSearchParams({ fields: '+variants.prices.amount,+variants.prices.currency_code,+collection,+categories,+images' }) + if (regionId) params.set('region_id', regionId) + const res = await fetch(`/api/medusa/store/products/${productId}?${params}`) + if (!res.ok) { setLoading(false); return } + const { product: p } = await res.json() + p.variants = (p.variants ?? []).map((v: any) => ({ + ...v, + prices: v.prices?.length ? v.prices : v.calculated_price + ? [{ amount: v.calculated_price.calculated_amount, currency_code: v.calculated_price.currency_code }] + : [], + })) + setProduct(p) + setSelectedVariant(p.variants?.[0] ?? null) + } catch { + setProduct(null) + } finally { + setLoading(false) + } + } + run() + }, [productId, initialProduct]) + + const handleAddToCart = async () => { + if (!selectedVariant?.id) return + await addToCart(selectedVariant.id, quantity) + setAdded(true) + setTimeout(() => setAdded(false), 2500) + window.dispatchEvent(new CustomEvent('cart-drawer-open')) + } + + if (loading) return ( +
+
+
+ ) + + if (!product) return ( +
+ +

Product not found.

+ Back to Products +
+ ) + + const images = [product.thumbnail, ...(product.images?.map(i => i.url) ?? [])].filter(Boolean) as string[] + const price = selectedVariant?.prices?.[0] + const mainImage = images[activeImage] ?? images[0] + + return ( +
+
+
+
+ {mainImage ? ( + {product.title} + ) : ( +
+ +
+ )} +
+ {images.length > 1 && ( +
+ {images.map((url, i) => ( + + ))} +
+ )} +
+
+ {product.collection?.title && ( + {product.collection.title} + )} +

{product.title}

+ {price && ( +

+ {formatPrice(price.amount, price.currency_code)} +

+ )} + {product.description && ( +

{product.description}

+ )} + {product.variants.length > 1 && ( +
+

Option

+
+ {product.variants.map(v => ( + + ))} +
+
+ )} +
+
+ + {quantity} + +
+ +
+ +
+
+ +
+ ) +}