From 9c88327df34bfa8dd88096f4b61277552e346186 Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Mon, 27 Jul 2026 17:10:26 +0000 Subject: [PATCH] Deploy --- src/app/cart/page.tsx | 179 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/app/cart/page.tsx diff --git a/src/app/cart/page.tsx b/src/app/cart/page.tsx new file mode 100644 index 0000000..0243d3e --- /dev/null +++ b/src/app/cart/page.tsx @@ -0,0 +1,179 @@ +'use client' +import { useState, useEffect } from 'react' +import Link from 'next/link' +import { ArrowLeft, Minus, Package, Plus, ShoppingCart, Trash2 } from 'lucide-react' +import { broadcastCartCount } from '@/hooks/useMedusaCart' + +const CURRENCY_SYMBOLS: Record = { zar: 'R', usd: '$', eur: '€', gbp: '£', kes: 'KSh', ngn: '₦', ghs: '₵' } +const formatPrice = (amount: number, code?: string) => { + const sym = CURRENCY_SYMBOLS[(code ?? '').toLowerCase()] ?? (code?.toUpperCase() ?? '') + return `${sym}${(amount / 100).toFixed(2)}` +} + +const PUB_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || '' +const BASE = '/api/medusa' + +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) throw new Error(`Medusa ${res.status}`) + return res.json() +} + +interface CartItem { + id: string + title: string + product_title?: string + thumbnail: string | null + quantity: number + unit_price: number + variant: { id: string; title: string } | null +} +interface Cart { + id: string + items: CartItem[] + subtotal: number + region: { currency_code: string } | null +} + +export default function CartPage() { + const [cart, setCart] = useState(null) + const [loading, setLoading] = useState(true) + const cartId = typeof window !== 'undefined' ? localStorage.getItem('medusa_cart_id') : null + + useEffect(() => { + if (!cartId) { setLoading(false); return } + medusaFetch<{ cart: Cart }>(`/store/carts/${cartId}`) + .then(({ cart: c }) => setCart(c)) + .catch(() => setCart(null)) + .finally(() => setLoading(false)) + }, [cartId]) + + const removeItem = async (itemId: string) => { + if (!cart) return + await medusaFetch(`/store/carts/${cart.id}/line-items/${itemId}`, { method: 'DELETE' }) + const newItems = cart.items.filter(i => i.id !== itemId) + setCart(prev => prev ? { ...prev, items: newItems } : null) + broadcastCartCount(newItems.reduce((s, i) => s + i.quantity, 0)) + } + + const updateQuantity = async (itemId: string, quantity: number) => { + if (!cart) return + if (quantity < 1) return removeItem(itemId) + const { cart: updated } = await medusaFetch<{ cart: Cart }>( + `/store/carts/${cart.id}/line-items/${itemId}`, + { method: 'POST', body: JSON.stringify({ quantity }) } + ) + setCart(updated) + broadcastCartCount(updated.items?.reduce((s, i) => s + i.quantity, 0) ?? 0) + } + + const currency = cart?.region?.currency_code?.toUpperCase() ?? 'USD' + const items = cart?.items ?? [] + const subtotal = cart?.subtotal ?? 0 + + if (loading) return ( +
+
+
+ ) + + return ( +
+
+
+ + + Continue Shopping + +

+ + Your Cart +

+
+ {items.length === 0 ? ( +
+ +

Your cart is empty.

+ + + +
+ ) : ( +
+
+ {items.map((item) => ( +
+ {item.thumbnail ? ( + {item.product_title + ) : ( +
+ +
+ )} +
+

{item.product_title || item.title}

+ {item.variant?.title && item.variant.title !== 'Default Title' && ( +

{item.variant.title}

+ )} +

+ {formatPrice(item.unit_price ?? 0, cart?.region?.currency_code)} +

+
+
+ +
+ + {item.quantity} + +
+
+
+ ))} +
+
+
+

Order Summary

+
+
+ Subtotal ({items.length} items) + {formatPrice(subtotal, cart?.region?.currency_code)} +
+
+ Shipping + Calculated at checkout +
+
+
+ Total + {formatPrice(subtotal, cart?.region?.currency_code)} +
+ + Proceed to Checkout + + + Continue Shopping + +
+
+
+ )} +
+
+ ) +}