From 2a5cac4bc55f07838281aac2815579c5f854e655 Mon Sep 17 00:00:00 2001 From: Vula Builder Date: Fri, 31 Jul 2026 14:12:35 +0000 Subject: [PATCH] Deploy --- src/app/account/orders/page.tsx | 134 ++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/app/account/orders/page.tsx diff --git a/src/app/account/orders/page.tsx b/src/app/account/orders/page.tsx new file mode 100644 index 0000000..355e41b --- /dev/null +++ b/src/app/account/orders/page.tsx @@ -0,0 +1,134 @@ +'use client' +import { useEffect, useState } from 'react' +import { usePathname, useRouter } from 'next/navigation' +import Link from 'next/link' +import { Customer, useCustomerAuth } from '@/hooks/useCustomerAuth' + +const COMMERCE_URL = process.env.NEXT_PUBLIC_VULA_COMMERCE_URL || 'https://ide.vulai.co.za' +const PROJECT_ID = process.env.NEXT_PUBLIC_VULA_PROJECT_ID || '' + +const STATUS_LABELS: Record = { + pending: 'Pending', confirmed: 'Confirmed', processing: 'Processing', + shipped: 'Shipped', delivered: 'Delivered', cancelled: 'Cancelled', +} +const STATUS_COLOURS: Record = { + pending: 'bg-yellow-100 text-yellow-800', + confirmed: 'bg-blue-100 text-blue-800', + processing: 'bg-blue-100 text-blue-800', + shipped: 'bg-purple-100 text-purple-800', + delivered: 'bg-green-100 text-green-800', + cancelled: 'bg-red-100 text-red-800', +} + +const fmt = (n: number) => `R${(n / 100).toFixed(2)}` +const fmtDate = (s: string) => new Date(s).toLocaleDateString('en-ZA', { year: 'numeric', month: 'short', day: 'numeric' }) + +interface Order { + id: string + order_number: string + status: string + total: number + created_at: string + items: Array<{ title: string; quantity: number; unit_price: number }> +} + +function AccountShell({ customer, onLogout, children }: { customer: Customer | null; onLogout: () => void; children: React.ReactNode }) { + const pathname = usePathname() + const navLink = (href: string, label: string) => ( + + {label} + + ) + return ( +
+
+
+
+

My Account

+ {customer &&

{customer.first_name} {customer.last_name}

} +
+ +
+ + {children} +
+
+ ) +} + +export default function AccountOrdersPage() { + const { customer, token, isAuthenticated, loading: authLoading, logout } = useCustomerAuth() + const router = useRouter() + const [orders, setOrders] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + if (!authLoading && !isAuthenticated) router.replace('/account/login') + }, [authLoading, isAuthenticated, router]) + + useEffect(() => { + if (!token) return + fetch(`${COMMERCE_URL}/api/commerce/${PROJECT_ID}/customers/orders`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then(r => r.json()) + .then(d => { if (d.success) setOrders(d.orders) }) + .finally(() => setLoading(false)) + }, [token]) + + const handleLogout = () => { logout(); router.push('/') } + + if (authLoading) return ( +
+
+
+ ) + + return ( + + {loading ? ( +
+
+
+ ) : orders.length === 0 ? ( +
+

No orders yet

+ + Start shopping + +
+ ) : ( +
+ {orders.map(order => ( +
+
+
+ {order.order_number} + {fmtDate(order.created_at)} +
+ + {STATUS_LABELS[order.status] ?? order.status} + +
+
+ {(order.items ?? []).map((item, i) => ( +
+ {item.title} x{item.quantity} + {fmt(item.unit_price * item.quantity)} +
+ ))} +
+
+ Total + {fmt(order.total)} +
+
+ ))} +
+ )} + + ) +}