From 325714a74653ad2e6e1923f66d97135102b3eba7 Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Sun, 11 Oct 2026 14:20:29 +0000 Subject: [PATCH] Deploy --- src/components/shop/DishDetailClient.tsx | 152 +++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/components/shop/DishDetailClient.tsx diff --git a/src/components/shop/DishDetailClient.tsx b/src/components/shop/DishDetailClient.tsx new file mode 100644 index 0000000..a72878a --- /dev/null +++ b/src/components/shop/DishDetailClient.tsx @@ -0,0 +1,152 @@ +'use client' +import { useState, useEffect } from 'react' +import Image from 'next/image' +import Link from 'next/link' +import { Utensils, Minus, Plus, Check } from 'lucide-react' +import { useVulaCart, type VulaCartItem } from '@/hooks/useVulaCart' + +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 VulaVariant { id: string; title: string; prices: Array<{ amount: number; currency_code: string }> } +interface VulaDishDetail { + id: string; title: string; description: string | null + thumbnail: string | null; images?: Array<{ url: string }> + variants: VulaVariant[]; collection?: { id: string; title: string } +} + +export default function DishDetailClient({ dish: initialDish, dishId }: { dish: VulaDishDetail | null; dishId: string }) { + const [dish, setDish] = useState(initialDish) + const [loading, setLoading] = useState(!initialDish) + const [selectedVariant, setSelectedVariant] = useState(initialDish?.variants?.[0] ?? null) + const [activeImage, setActiveImage] = useState(0) + const [quantity, setQuantity] = useState(1) + const [added, setAdded] = useState(false) + const { addToCart } = useVulaCart() + + useEffect(() => { + if (initialDish) return + const run = async () => { + try { + const res = await fetch(`/api/products?id[]=${dishId}`) + if (!res.ok) { setLoading(false); return } + const data = await res.json() + const d = data.product ?? (data.products?.[0] ?? null) + if (d) { setDish(d); setSelectedVariant(d.variants?.[0] ?? null) } + } catch { + setDish(null) + } finally { + setLoading(false) + } + } + run() + }, [dishId, initialDish]) + + const handleAddToOrder = async () => { + if (!selectedVariant?.id || !dish) return + const price = selectedVariant.prices?.[0] + const item: VulaCartItem = { + variantId: selectedVariant.id, + productId: dish.id, + title: dish.title, + variant_title: selectedVariant.title ?? '', + unit_price: price?.amount ?? 0, + quantity, + thumbnail: dish.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 (!dish) return ( +
+ +

Dish not found.

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

{dish.title}

+ {price && ( +

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

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

{dish.description}

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

Size / Option

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