Deploy
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
'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<VulaCartItem[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(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 }
|
||||
}
|
||||
Reference in New Issue
Block a user