From 3f899d558d17b68a8724c9e0bceb6464b2fc011f Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:36:04 +0000 Subject: [PATCH] chore: add client/src/pages/admin/customers.tsx --- client/src/pages/admin/customers.tsx | 91 ++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 client/src/pages/admin/customers.tsx diff --git a/client/src/pages/admin/customers.tsx b/client/src/pages/admin/customers.tsx new file mode 100644 index 0000000..bf8dc74 --- /dev/null +++ b/client/src/pages/admin/customers.tsx @@ -0,0 +1,91 @@ +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { AdminNav } from "./dashboard"; +import { Search, Mail } from "lucide-react"; + +interface Customer { + id: number; + email: string; + firstName?: string; + lastName?: string; + phone?: string; + storeCredit: string; + createdAt: string; +} + +export default function AdminCustomers() { + const [q, setQ] = useState(""); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + + const { data, isLoading } = useQuery({ + queryKey: ["/api/admin/customers", { q: search, page }], + queryFn: async () => { + const params = new URLSearchParams({ page: String(page), limit: "30" }); + if (search) params.set("q", search); + const r = await fetch(`/api/admin/customers?${params}`, { credentials: "include" }); + if (r.status === 401) { window.location.href = "/admin/login"; return []; } + return r.json(); + }, + }); + + return ( +
+ +
+
+

Customers

+
{ e.preventDefault(); setSearch(q); setPage(1); }} className="flex gap-2"> +
+ + setQ(e.target.value)} placeholder="Search customers..." + className="pl-8 pr-3 py-2 border border-border rounded-md text-sm bg-background focus:outline-none w-56" + data-testid="input-customer-search" /> +
+ +
+
+ +
+ + + + + + + + + + + {isLoading ? ( + Array.from({ length: 5 }).map((_, i) => ( + + )) + ) : data?.map((c) => ( + + + + + + + ))} + {!isLoading && !data?.length && ( + + )} + +
CustomerPhoneStore creditMember since
+
+
+ {(c.firstName?.[0] || c.email[0]).toUpperCase()} +
+
+ {(c.firstName || c.lastName) &&

{[c.firstName, c.lastName].filter(Boolean).join(" ")}

} +

{c.email}

+
+
+
{c.phone || "—"}{parseFloat(c.storeCredit) > 0 ? `$${parseFloat(c.storeCredit).toFixed(2)}` : "—"}{new Date(c.createdAt).toLocaleDateString()}
No customers yet
+
+
+
+ ); +}