"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import type { MedusaCart } from "@/lib/medusa"; const CART_ID_KEY = "trptk_cart_id"; async function apiJson(url: string, init?: RequestInit): Promise { const res = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...init?.headers }, }); if (!res.ok) { const body = await res.json().catch(() => ({})); const msg = body?.error ?? `Cart API error: ${res.status}`; console.error("[cart]", msg); throw new Error(msg); } return res.json(); } type CartState = { cart: MedusaCart | null; isLoading: boolean; isAdding: boolean; itemCount: number; drawerOpen: boolean; setDrawerOpen: (open: boolean) => void; addItem: (variantId: string, quantity?: number) => Promise; removeItem: (lineItemId: string) => Promise; updateItem: (lineItemId: string, quantity: number) => Promise; applyPromo: (code: string) => Promise; removePromo: (code: string) => Promise; resetCart: () => void; }; const CartContext = createContext(null); export function CartProvider({ children }: { children: ReactNode }) { const [cart, setCart] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isAdding, setIsAdding] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false); // Restore cart from localStorage on mount useEffect(() => { async function restore() { const cartId = localStorage.getItem(CART_ID_KEY); if (cartId) { try { const existing = await apiJson(`/api/cart?id=${cartId}`); setCart(existing); } catch { localStorage.removeItem(CART_ID_KEY); } } setIsLoading(false); } restore(); }, []); const ensureCart = useCallback(async (): Promise => { if (cart?.id) return cart.id; const newCart = await apiJson("/api/cart", { method: "POST" }); localStorage.setItem(CART_ID_KEY, newCart.id); setCart(newCart); return newCart.id; }, [cart?.id]); const addItem = useCallback( async (variantId: string, quantity = 1) => { setIsAdding(true); try { const cartId = await ensureCart(); const updated = await apiJson(`/api/cart/${cartId}/items`, { method: "POST", body: JSON.stringify({ variant_id: variantId, quantity }), }); setCart(updated); setDrawerOpen(true); } finally { setIsAdding(false); } }, [ensureCart], ); const removeItem = useCallback( async (lineItemId: string) => { if (!cart?.id) return; const updated = await apiJson(`/api/cart/${cart.id}/items/${lineItemId}`, { method: "DELETE", }); setCart(updated); }, [cart?.id], ); const updateItem = useCallback( async (lineItemId: string, quantity: number) => { if (!cart?.id) return; if (quantity <= 0) { await removeItem(lineItemId); return; } const updated = await apiJson(`/api/cart/${cart.id}/items/${lineItemId}`, { method: "POST", body: JSON.stringify({ quantity }), }); setCart(updated); }, [cart?.id, removeItem], ); const applyPromo = useCallback( async (code: string) => { if (!cart?.id) return; const updated = await apiJson(`/api/cart/${cart.id}/promotions`, { method: "POST", body: JSON.stringify({ code }), }); setCart(updated); }, [cart?.id], ); const removePromo = useCallback( async (code: string) => { if (!cart?.id) return; const updated = await apiJson(`/api/cart/${cart.id}/promotions`, { method: "DELETE", body: JSON.stringify({ code }), }); setCart(updated); }, [cart?.id], ); const resetCart = useCallback(() => { setCart(null); localStorage.removeItem(CART_ID_KEY); }, []); const itemCount = useMemo( () => cart?.items?.reduce((sum, item) => sum + item.quantity, 0) ?? 0, [cart?.items], ); const value = useMemo( () => ({ cart, isLoading, isAdding, itemCount, drawerOpen, setDrawerOpen, addItem, removeItem, updateItem, applyPromo, removePromo, resetCart, }), [ cart, isLoading, isAdding, itemCount, drawerOpen, setDrawerOpen, addItem, removeItem, updateItem, applyPromo, removePromo, resetCart, ], ); return {children}; } export function useCart() { const ctx = useContext(CartContext); if (!ctx) throw new Error("useCart must be used within CartProvider"); return ctx; }