From c3b09e39ea050f71b4257dfab466ad6112356c67 Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:35:06 +0000 Subject: [PATCH] chore: add client/src/pages/account.tsx --- client/src/pages/account.tsx | 197 +++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 client/src/pages/account.tsx diff --git a/client/src/pages/account.tsx b/client/src/pages/account.tsx new file mode 100644 index 0000000..858bdd2 --- /dev/null +++ b/client/src/pages/account.tsx @@ -0,0 +1,197 @@ +import { useState, useEffect } from "react"; +import { useSearch, Link } from "wouter"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { queryClient, apiRequest } from "../lib/queryClient"; +import { toast } from "../hooks/use-toast"; +import { Package, User, LogOut, Mail } from "lucide-react"; + +interface Customer { + id: number; + email: string; + firstName?: string; + lastName?: string; + phone?: string; +} + +interface Order { + id: number; + orderNumber: string; + status: string; + paymentStatus: string; + fulfillmentStatus: string; + total: string; + createdAt: string; + trackingNumber?: string; + trackingCarrier?: string; +} + +function LoginForm() { + const [email, setEmail] = useState(""); + const [sent, setSent] = useState(false); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + try { + await apiRequest("POST", "/api/auth/magic-link", { email }); + setSent(true); + } catch (err: any) { + toast({ title: "Error", description: err.message, variant: "destructive" }); + } finally { + setLoading(false); + } + }; + + if (sent) { + return ( +
+ +

Check your email

+

We sent a sign-in link to {email}. Click it to sign in.

+

Link expires in 15 minutes.

+
+ ); + } + + return ( +
+

Sign in

+

We'll send a magic link to your email — no password needed.

+
+
+ + setEmail(e.target.value)} required + placeholder="you@example.com" + className="w-full px-3 py-2 border border-border rounded-md text-sm bg-background focus:outline-none focus:ring-2 focus:ring-primary/30" + data-testid="input-login-email" + /> +
+ +
+
+ ); +} + +function AccountDashboard({ customer }: { customer: Customer }) { + const { data: orders, isLoading } = useQuery({ + queryKey: ["/api/account/orders"], + queryFn: async () => { const r = await fetch("/api/account/orders", { credentials: "include" }); return r.json(); }, + }); + + const logoutMutation = useMutation({ + mutationFn: () => apiRequest("POST", "/api/auth/logout"), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["/api/auth/me"] }), + }); + + const statusBadge = (status: string) => { + const colors: Record = { + confirmed: "bg-blue-100 text-blue-700", + pending: "bg-yellow-100 text-yellow-700", + cancelled: "bg-red-100 text-red-700", + fulfilled: "bg-green-100 text-green-700", + }; + return colors[status] || "bg-muted text-muted-foreground"; + }; + + return ( +
+
+
+

My Account

+

{customer.email}

+
+ +
+ +
+

Orders

+ {isLoading ? ( +
+ {[1, 2, 3].map((i) =>
)} +
+ ) : !orders?.length ? ( +
+ +

No orders yet

+ Start shopping +
+ ) : ( +
+ {orders.map((order) => ( +
+
+
+

#{order.orderNumber}

+

{new Date(order.createdAt).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })}

+
+
+ {order.status} + ${parseFloat(order.total).toFixed(2)} +
+
+ {order.trackingNumber && ( +

Tracking: {order.trackingNumber}{order.trackingCarrier && ` (${order.trackingCarrier})`}

+ )} +
+ ))} +
+ )} +
+
+ ); +} + +export default function AccountPage() { + const search = useSearch(); + const token = new URLSearchParams(search).get("token"); + const [verifying, setVerifying] = useState(!!token); + + const { data, isLoading } = useQuery<{ customer: Customer | null }>({ + queryKey: ["/api/auth/me"], + queryFn: async () => { const r = await fetch("/api/auth/me", { credentials: "include" }); return r.json(); }, + }); + + useEffect(() => { + if (!token) return; + (async () => { + try { + const res = await fetch(`/api/auth/verify?token=${token}`, { credentials: "include" }); + if (res.ok) { + await queryClient.invalidateQueries({ queryKey: ["/api/auth/me"] }); + toast({ title: "Signed in!", description: "Welcome back." }); + } else { + toast({ title: "Link expired", description: "Please request a new sign-in link.", variant: "destructive" }); + } + } finally { + setVerifying(false); + window.history.replaceState({}, "", "/account"); + } + })(); + }, [token]); + + if (isLoading || verifying) { + return
Loading...
; + } + + return ( +
+ {data?.customer ? ( + + ) : ( + + )} +
+ ); +}