import { createContext, useContext, useEffect, useMemo, useReducer } from 'react'; const CartContext = createContext(undefined); const initialState = { items: [], }; function cartReducer(state, action) { switch (action.type) { case 'HYDRATE': return { ...state, items: action.payload }; case 'ADD_ITEM': { const { item } = action.payload; const existing = state.items.find((entry) => entry.id === item.id); if (!existing) { return { ...state, items: [...state.items, item] }; } const updated = state.items.map((entry) => entry.id === item.id ? { ...entry, quantity: item.quantity, limit: item.limit } : entry ); return { ...state, items: updated }; } case 'UPDATE_QUANTITY': { const { id, quantity } = action.payload; const updated = state.items .map((entry) => (entry.id === id ? { ...entry, quantity } : entry)) .filter((entry) => entry.quantity > 0); return { ...state, items: updated }; } case 'REMOVE_ITEM': { const { id } = action.payload; return { ...state, items: state.items.filter((entry) => entry.id !== id) }; } case 'CLEAR': return { ...state, items: [] }; default: return state; } } const STORAGE_KEY = 'fullmoon.cart'; export function CartProvider({ children }) { const [state, dispatch] = useReducer(cartReducer, initialState); useEffect(() => { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; const parsed = JSON.parse(raw); if (Array.isArray(parsed)) { dispatch({ type: 'HYDRATE', payload: parsed }); } } catch (error) { console.warn('Cart hydration failed', error); } }, []); useEffect(() => { localStorage.setItem(STORAGE_KEY, JSON.stringify(state.items)); }, [state.items]); const itemCount = useMemo( () => state.items.reduce((sum, entry) => sum + entry.quantity, 0), [state.items] ); const subtotal = useMemo( () => state.items.reduce((sum, entry) => sum + entry.price * entry.quantity, 0), [state.items] ); const addItem = (product) => { if (!product?.id) return; const price = Number(product.price || 0); const maxPerOrder = Number(product.max_per_order || 0) > 0 ? Number(product.max_per_order) : Infinity; const inventoryLimit = typeof product.inventory_quantity === 'number' && product.inventory_quantity >= 0 ? product.inventory_quantity : Infinity; const limit = Math.min(maxPerOrder, inventoryLimit); if (limit <= 0) return; const numericLimit = Number.isFinite(limit) ? limit : Infinity; const existing = state.items.find((entry) => entry.id === product.id); const nextQuantity = existing ? Math.min(existing.quantity + 1, numericLimit) : 1; dispatch({ type: 'ADD_ITEM', payload: { item: { id: product.id, slug: product.slug, name: product.name, price, quantity: nextQuantity, imageUrl: product.imageUrl, limit: Number.isFinite(limit) ? limit : null, }, }, }); }; const updateQuantity = (id, quantity) => { if (quantity < 0) return; const entry = state.items.find((item) => item.id === id); if (!entry) return; const limit = Number.isFinite(entry.limit) ? entry.limit : Infinity; const clamped = Math.min(quantity, limit); dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity: clamped } }); }; const removeItem = (id) => dispatch({ type: 'REMOVE_ITEM', payload: { id } }); const clearCart = () => dispatch({ type: 'CLEAR' }); const value = useMemo( () => ({ items: state.items, addItem, updateQuantity, removeItem, clearCart, itemCount, subtotal, }), [state.items, itemCount, subtotal] ); return {children}; } export function useCart() { const context = useContext(CartContext); if (!context) { throw new Error('useCart must be used within a CartProvider'); } return context; }