'use client' import { useState, useEffect, useCallback } from 'react' const COMMERCE_URL = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL || 'https://ide.vulai.co.za' const PROJECT_ID = process.env.NEXT_PUBLIC_VULA_PROJECT_ID || '' const CART_KEY = `vula_cart_${PROJECT_ID}` export interface VulaCartItem { variantId: string productId: string title: string variant_title: string unit_price: number quantity: number thumbnail: string | null } export interface CheckoutDetails { customerName: string customerEmail: string customerPhone?: string fulfillmentType: string shippingAddress?: { address_line1: string; city: string; province?: string; postal_code?: string } notes?: string } function readCart(): VulaCartItem[] { try { return JSON.parse(localStorage.getItem(CART_KEY) || '[]') } catch { return [] } } function writeCart(items: VulaCartItem[]) { localStorage.setItem(CART_KEY, JSON.stringify(items)) // Notify all other useVulaCart instances in this tab to re-sync. window.dispatchEvent(new CustomEvent('vula-cart-updated')) } export function useVulaCart() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { setItems(readCart()) const onUpdate = () => setItems(readCart()) window.addEventListener('vula-cart-updated', onUpdate) return () => window.removeEventListener('vula-cart-updated', onUpdate) }, []) const addToCart = useCallback((item: VulaCartItem) => { // Read directly from localStorage (not from React state) so the side effect // (writeCart + event dispatch) stays outside the state updater. // React StrictMode calls updater functions twice, which would double-fire // writeCart and cause duplicate items. const current = readCart() const exists = current.find(i => i.variantId === item.variantId) const next = exists ? current.map(i => i.variantId === item.variantId ? { ...i, quantity: i.quantity + item.quantity } : i) : [...current, item] writeCart(next) setItems(next) }, []) const removeFromCart = useCallback((variantId: string) => { const next = readCart().filter(i => i.variantId !== variantId) writeCart(next) setItems(next) }, []) const updateQty = useCallback((variantId: string, qty: number) => { const current = readCart() const next = qty <= 0 ? current.filter(i => i.variantId !== variantId) : current.map(i => i.variantId === variantId ? { ...i, quantity: qty } : i) writeCart(next) setItems(next) }, []) // clearCart must also dispatch so the nav badge and other instances clear. const clearCart = useCallback(() => { localStorage.removeItem(CART_KEY) window.dispatchEvent(new CustomEvent('vula-cart-updated')) setItems([]) }, []) const checkout = useCallback(async (details: CheckoutDetails) => { setLoading(true); setError(null) try { const cart = readCart() if (!cart.length) throw new Error('Your cart is empty') const r = await fetch(`${COMMERCE_URL}/api/store/${PROJECT_ID}/checkout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items: cart, ...details }) }) const data = await r.json() if (!data.success) throw new Error(data.error || 'Checkout failed') clearCart() return data.order } catch (e: any) { setError(e.message) throw e } finally { setLoading(false) } }, [clearCart]) const subtotal = items.reduce((sum, i) => sum + i.unit_price * i.quantity, 0) const itemCount = items.reduce((sum, i) => sum + i.quantity, 0) return { items, subtotal, itemCount, loading, error, addToCart, removeFromCart, updateQty, clearCart, checkout } }