'use client' import { useState, useCallback, useEffect } from 'react' const PUB_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || '' const BASE = '/api/medusa' const COUNT_KEY = 'medusa_cart_count' const CART_EVENT = 'medusa-cart-updated' // Broadcast a new count to every useMedusaCart instance on the page instantly. // Also writes to localStorage so the count survives page navigation / refresh. export function broadcastCartCount(count: number) { if (typeof window === 'undefined') return localStorage.setItem(COUNT_KEY, String(count)) window.dispatchEvent(new CustomEvent(CART_EVENT, { detail: { count } })) } export function openCartDrawer() { if (typeof window === 'undefined') return window.dispatchEvent(new CustomEvent('cart-drawer-open')) } async function medusaFetch(path: string, init?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { ...init, headers: { 'content-type': 'application/json', 'x-publishable-api-key': PUB_KEY, ...(init?.headers ?? {}), }, signal: AbortSignal.timeout(30000), }) if (!res.ok) { const body = await res.text().catch(() => '') throw new Error(`Medusa ${res.status}: ${body}`) } return res.json() } export function useMedusaCart() { // Start at null/0 so server and client render identically (no hydration mismatch). // localStorage is loaded in useEffect (client-only). const [cartId, setCartId] = useState(null) const [itemCount, setItemCount] = useState(0) // Bootstrap from localStorage after mount. useEffect(() => { const storedId = localStorage.getItem('medusa_cart_id') if (storedId) setCartId(storedId) const storedCount = parseInt(localStorage.getItem(COUNT_KEY) || '0', 10) if (storedCount > 0) setItemCount(storedCount) }, []) // Listen for count changes broadcast from any component (shop add, cart delete, etc.) useEffect(() => { const handler = (e: Event) => setItemCount((e as CustomEvent).detail.count) window.addEventListener(CART_EVENT, handler) return () => window.removeEventListener(CART_EVENT, handler) }, []) // Hydrate from live Medusa cart on mount for accuracy after hard refresh. useEffect(() => { const id = localStorage.getItem('medusa_cart_id') if (!id) return medusaFetch<{ cart: { items: Array<{ quantity: number }> } }>(`/store/carts/${id}`) .then(({ cart }) => { const total = cart.items?.reduce((s, i) => s + i.quantity, 0) ?? 0 broadcastCartCount(total) }) .catch(() => { // Stale cart — clear count so nav badge resets to 0 localStorage.removeItem('medusa_cart_id') localStorage.removeItem('medusa_cart_count') broadcastCartCount(0) }) }, []) const createCart = useCallback(async () => { let regionId: string | undefined try { const data = await medusaFetch<{ regions: Array<{ id: string }> }>('/store/regions?limit=1') regionId = data.regions?.[0]?.id } catch { /* proceed without region_id */ } const data = await medusaFetch<{ cart: { id: string } }>('/store/carts', { method: 'POST', body: JSON.stringify(regionId ? { region_id: regionId } : {}), }) localStorage.setItem('medusa_cart_id', data.cart.id) setCartId(data.cart.id) return data.cart }, []) const addToCart = useCallback(async (variantId: string, quantity = 1) => { const postLineItem = async (cid: string) => medusaFetch(`/store/carts/${cid}/line-items`, { method: 'POST', body: JSON.stringify({ variant_id: variantId, quantity }), }) try { let id = cartId if (!id) { const cart = await createCart() id = cart.id } try { await postLineItem(id) } catch (err: any) { if (String(err?.message).includes('404')) { // Stale cart ID — clear and retry with a fresh cart localStorage.removeItem('medusa_cart_id') localStorage.removeItem(COUNT_KEY) setCartId(null) const fresh = await createCart() await postLineItem(fresh.id) } else { throw err } } const newCount = parseInt(localStorage.getItem(COUNT_KEY) || '0', 10) + quantity broadcastCartCount(newCount) } catch (err) { console.error('Add to cart failed:', err) alert('Could not add to cart. Please try again.') } }, [cartId, createCart]) return { cartId, itemCount, addToCart, createCart } }