diff --git a/client/src/pages/admin/orders.tsx b/client/src/pages/admin/orders.tsx
new file mode 100644
index 0000000..59fafa0
--- /dev/null
+++ b/client/src/pages/admin/orders.tsx
@@ -0,0 +1,183 @@
+import { useState } from "react";
+import { useQuery, useMutation } from "@tanstack/react-query";
+import { queryClient, apiRequest } from "../../lib/queryClient";
+import { AdminNav } from "./dashboard";
+import { toast } from "../../hooks/use-toast";
+import { Search, ChevronDown, ChevronUp } from "lucide-react";
+
+interface Order {
+ id: number;
+ orderNumber: string;
+ email: string;
+ status: string;
+ paymentStatus: string;
+ fulfillmentStatus: string;
+ total: string;
+ subtotal: string;
+ discountAmount?: string;
+ discountCode?: string;
+ shipping: string;
+ tax: string;
+ paymentProcessor: string;
+ trackingNumber?: string;
+ trackingCarrier?: string;
+ shippingAddress?: any;
+ notes?: string;
+ createdAt: string;
+}
+
+const STATUS_OPTIONS = ["", "pending", "confirmed", "fulfilled", "cancelled", "refunded"];
+const FULFILLMENT_OPTIONS = ["unfulfilled", "partial", "fulfilled", "returned"];
+
+function OrderRow({ order }: { order: Order }) {
+ const [expanded, setExpanded] = useState(false);
+ const [tracking, setTracking] = useState(order.trackingNumber || "");
+ const [carrier, setCarrier] = useState(order.trackingCarrier || "");
+ const [status, setStatus] = useState(order.status);
+ const [saving, setSaving] = useState(false);
+
+ const updateMutation = useMutation({
+ mutationFn: (body: object) => apiRequest("PATCH", `/api/admin/orders/${order.id}`, body),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["/api/admin/orders"] });
+ toast({ title: "Order updated" });
+ setSaving(false);
+ },
+ onError: (err: any) => {
+ toast({ title: "Error", description: err.message, variant: "destructive" });
+ setSaving(false);
+ },
+ });
+
+ const addr = order.shippingAddress;
+
+ return (
+
+
setExpanded((v) => !v)}>
+
+
+ #{order.orderNumber}
+ {order.status}
+ {order.fulfillmentStatus}
+
+
{order.email}
+
+
+
${parseFloat(order.total).toFixed(2)}
+
{new Date(order.createdAt).toLocaleDateString()}
+
+ {expanded ?
:
}
+
+
+ {expanded && (
+
+
+
+
Ship to
+ {addr ? (
+
{addr.firstName} {addr.lastName}
{addr.address1}{addr.address2 ? `, ${addr.address2}` : ""}
{addr.city}, {addr.state} {addr.zip}
+ ) :
No address
}
+
+
+
Payment
+
{order.paymentProcessor} — {order.paymentStatus}
+
+
+
+
+
+
+
+
+
+
+ setTracking(e.target.value)} placeholder="e.g. 1Z..."
+ className="w-full px-3 py-1.5 border border-border rounded-md text-sm bg-background focus:outline-none"
+ data-testid={`input-tracking-${order.id}`} />
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
+
+export default function AdminOrders() {
+ const [q, setQ] = useState("");
+ const [search, setSearch] = useState("");
+ const [statusFilter, setStatusFilter] = useState("");
+ const [page, setPage] = useState(1);
+
+ const { data, isLoading } = useQuery<{ orders: Order[]; total: number }>({
+ queryKey: ["/api/admin/orders", { q: search, status: statusFilter, page }],
+ queryFn: async () => {
+ const params = new URLSearchParams({ page: String(page), limit: "20" });
+ if (search) params.set("q", search);
+ if (statusFilter) params.set("status", statusFilter);
+ const r = await fetch(`/api/admin/orders?${params}`, { credentials: "include" });
+ if (r.status === 401) { window.location.href = "/admin/login"; return { orders: [], total: 0 }; }
+ return r.json();
+ },
+ });
+
+ return (
+
+
+
+
+
Orders {data?.total !== undefined && ({data.total})}
+
+
+
+
+
+
+ {isLoading ? (
+ {[1,2,3,4,5].map((i) =>
)}
+ ) : (
+
+ {data?.orders.map((o) =>
)}
+ {!data?.orders.length &&
No orders found
}
+
+ )}
+
+ {data && data.total > 20 && (
+
+
+ Page {page}
+
+
+ )}
+
+
+ );
+}