diff --git a/src/components/shop/ProductDetailClient.tsx b/src/components/shop/ProductDetailClient.tsx new file mode 100644 index 0000000..e572e78 --- /dev/null +++ b/src/components/shop/ProductDetailClient.tsx @@ -0,0 +1,161 @@ +'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 { useVulaCart, type VulaCartItem } from '@/hooks/useVulaCart' +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.toFixed(2)}` +} + +interface VulaVariant { id: string; title: string; prices: Array<{ amount: number; currency_code: string }> } +interface VulaProductDetail { + id: string; title: string; description: string | null + thumbnail: string | null; images?: Array<{ url: string }> + variants: VulaVariant[]; collection?: { id: string; title: string } +} + +export default function ProductDetailClient({ product: initialProduct, productId }: { product: VulaProductDetail | 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 } = useVulaCart() + + useEffect(() => { + if (initialProduct) return + const run = async () => { + try { + const res = await fetch(`/api/products?id[]=${productId}`) + if (!res.ok) { setLoading(false); return } + const data = await res.json() + const p = data.product ?? (data.products?.[0] ?? null) + if (p) { + setProduct(p) + setSelectedVariant(p.variants?.[0] ?? null) + } + } catch { + setProduct(null) + } finally { + setLoading(false) + } + } + run() + }, [productId, initialProduct]) + + const handleAddToCart = async () => { + if (!selectedVariant?.id || !product) return + const price = selectedVariant.prices?.[0] + const item: VulaCartItem = { + variantId: selectedVariant.id, + productId: product.id, + title: product.title, + variant_title: selectedVariant.title ?? '', + unit_price: price?.amount ?? 0, + quantity, + thumbnail: product.thumbnail ?? null, + } + for (let i = 0; i < quantity; i++) { addToCart({ ...item, quantity: 1 }) } + 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} + +
+ +
+ +
+
+ +
+ ) +}