From 2f45581e6d551d144ff65a5fb58fc79f261865fa Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Wed, 29 Jul 2026 19:04:14 +0000 Subject: [PATCH] Deploy --- src/hooks/useVulaCart.ts | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/hooks/useVulaCart.ts diff --git a/src/hooks/useVulaCart.ts b/src/hooks/useVulaCart.ts new file mode 100644 index 0000000..35229fb --- /dev/null +++ b/src/hooks/useVulaCart.ts @@ -0,0 +1,101 @@ +'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) => { + setItems(prev => { + const exists = prev.find(i => i.variantId === item.variantId) + const next = exists + ? prev.map(i => i.variantId === item.variantId ? { ...i, quantity: i.quantity + item.quantity } : i) + : [...prev, item] + writeCart(next) + return next + }) + }, []) + + const removeFromCart = useCallback((variantId: string) => { + setItems(prev => { const next = prev.filter(i => i.variantId !== variantId); writeCart(next); return next }) + }, []) + + const updateQty = useCallback((variantId: string, qty: number) => { + setItems(prev => { + const next = qty <= 0 + ? prev.filter(i => i.variantId !== variantId) + : prev.map(i => i.variantId === variantId ? { ...i, quantity: qty } : i) + writeCart(next) + return next + }) + }, []) + + const clearCart = useCallback(() => { localStorage.removeItem(CART_KEY); 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 } +}