Deploy
This commit is contained in:
@@ -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 }
|
||||
}
|
||||
Reference in New Issue
Block a user