This commit is contained in:
Vula Builder
2026-07-27 18:59:53 +00:00
parent a9992711d2
commit d78ea1b586
+127
View File
@@ -0,0 +1,127 @@
'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<T>(path: string, init?: RequestInit): Promise<T> {
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<string | null>(null)
const [itemCount, setItemCount] = useState<number>(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 }
}