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