From 7203cf1ead1e8f24abd36607406f9a0236063e6c Mon Sep 17 00:00:00 2001 From: default Date: Tue, 2 Jun 2026 03:31:54 +0000 Subject: [PATCH] feat(admin): design system audit fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CSS design tokens for consistency (--admin-danger-hover, --admin-accent-dot, shadow-md warm tone) - Replace unicode icons with inline SVG (⋮, ✕, ▼, ▶) - Consolidate duplicate PageHeader/AdminPageHeader components - Standardize border-radius (rounded-xl → rounded-lg for ViewModeTabs) - Remove hardcoded brand UUID in products page - Replace hardcoded bg-red-600 with CSS variables in delete buttons - Add AdminButton, AdminFilterTabs, AdminSearchInput, PageHeader design system components - Fix GlassModal and AdminModal close button styling - Remove duplicate 'New Lot' button on Route Trace dashboard --- src/app/admin/advanced/page.tsx | 29 +- src/app/admin/import/ImportCenterClient.tsx | 145 ++- src/app/admin/import/page.tsx | 21 +- src/app/admin/orders/page.tsx | 18 +- src/app/admin/products/page.tsx | 42 +- src/app/admin/reports/page.tsx | 11 +- src/app/admin/stops/page.tsx | 43 +- src/app/admin/taxes/page.tsx | 11 +- src/app/admin/time-tracking/page.tsx | 12 +- .../water-log/headgates/HeadgatesManager.tsx | 203 ++-- src/app/admin/water-log/page.tsx | 24 +- src/app/admin/wholesale/WholesaleClient.tsx | 830 +++++++-------- src/components/admin/AdminHeader.tsx | 5 + src/components/admin/AdminOrdersPanel.tsx | 86 +- src/components/admin/AdminSidebar.tsx | 100 +- src/components/admin/AdminStopsPanel.tsx | 8 +- .../admin/AdvancedSettingsClient.tsx | 97 +- src/components/admin/BrandSettingsForm.tsx | 255 ++--- src/components/admin/CommunicationsPage.tsx | 171 ++-- src/components/admin/DashboardClient.tsx | 132 ++- src/components/admin/DriverPickupPanel.tsx | 22 +- src/components/admin/GlassModal.tsx | 35 +- .../HarvestReach/CampaignComposerPage.tsx | 104 +- .../HarvestReach/MatchingCustomersPanel.tsx | 53 +- .../admin/HarvestReach/SegmentBuilderPage.tsx | 36 +- .../HarvestReach/SegmentBuilderPanel.tsx | 33 +- .../admin/HarvestReach/SegmentEditModal.tsx | 21 +- .../admin/HarvestReach/SegmentListSidebar.tsx | 53 +- src/components/admin/HeadgateEditForm.tsx | 68 +- src/components/admin/MessageLogPanel.tsx | 97 +- src/components/admin/NewProductForm.tsx | 190 ++-- src/components/admin/NewStopForm.tsx | 184 ++-- src/components/admin/OrderEditForm.tsx | 69 +- src/components/admin/PaymentSettingsForm.tsx | 25 +- src/components/admin/ProductEditForm.tsx | 98 +- src/components/admin/ProductTableBody.tsx | 21 +- src/components/admin/ProductTableClient.tsx | 4 +- src/components/admin/ProductsClient.tsx | 218 ++-- src/components/admin/ReportsDashboard.tsx | 81 +- src/components/admin/SettingsClient.tsx | 76 +- src/components/admin/SettingsSections.tsx | 28 +- src/components/admin/ShippingSettingsForm.tsx | 95 +- src/components/admin/StopEditForm.tsx | 150 ++- src/components/admin/StopTableClient.tsx | 157 +-- src/components/admin/StopsHeaderActions.tsx | 29 +- src/components/admin/TaxDashboard.tsx | 57 +- src/components/admin/TaxQuarterlySummary.tsx | 16 +- .../admin/TimeTrackingAdminPanel.tsx | 226 ++--- .../admin/TimeTrackingSettingsClient.tsx | 334 +++--- src/components/admin/UsersPage.tsx | 6 +- src/components/admin/WaterLogAdminPanel.tsx | 958 +++++++++--------- .../admin/WaterLogEntryEditForm.tsx | 70 +- src/components/admin/WaterUserEditForm.tsx | 57 +- .../admin/design-system/AdminActionMenu.tsx | 6 +- .../admin/design-system/AdminButton.tsx | 135 +++ .../admin/design-system/AdminFilterTabs.tsx | 252 +++++ .../admin/design-system/AdminFormElements.tsx | 57 +- .../admin/design-system/AdminModal.tsx | 2 +- .../admin/design-system/AdminPageHeader.tsx | 35 +- .../admin/design-system/AdminSearchInput.tsx | 118 +++ .../admin/design-system/PageHeader.tsx | 84 ++ src/components/admin/design-system/index.tsx | 11 +- .../route-trace/RouteTraceDashboard.tsx | 6 - .../route-trace/StickerPreviewModal.tsx | 6 +- src/styles/admin-design-system.css | 28 +- 65 files changed, 3478 insertions(+), 3176 deletions(-) create mode 100644 src/components/admin/design-system/AdminButton.tsx create mode 100644 src/components/admin/design-system/AdminFilterTabs.tsx create mode 100644 src/components/admin/design-system/AdminSearchInput.tsx create mode 100644 src/components/admin/design-system/PageHeader.tsx diff --git a/src/app/admin/advanced/page.tsx b/src/app/admin/advanced/page.tsx index f8fd438..211444d 100644 --- a/src/app/admin/advanced/page.tsx +++ b/src/app/admin/advanced/page.tsx @@ -3,6 +3,7 @@ import { getAdminUser } from "@/lib/admin-permissions"; import { getBrands } from "@/actions/admin/users"; import { getStripeConnectStatus } from "@/actions/stripe-connect"; import AdvancedSettingsClient from "@/components/admin/AdvancedSettingsClient"; +import { PageHeader } from "@/components/admin/design-system"; export const metadata = { title: "Advanced Settings - Route Commerce Admin", @@ -25,10 +26,28 @@ export default async function AdvancedSettingsPage() { const stripeConnect = brandId ? await getStripeConnectStatus(brandId) : null; return ( - +
+
+ + + + } + /> + +
+
); } \ No newline at end of file diff --git a/src/app/admin/import/ImportCenterClient.tsx b/src/app/admin/import/ImportCenterClient.tsx index 5d977b4..d0f72e7 100644 --- a/src/app/admin/import/ImportCenterClient.tsx +++ b/src/app/admin/import/ImportCenterClient.tsx @@ -136,22 +136,8 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: const analysisLabels = ["Reading your file...", "AI is mapping columns...", "Cleaning and normalizing data...", "Finalizing preview..."]; return ( -
-
- - {/* Header */} -
-
- - - -
-
-

Import Center

-

AI-powered data import for products, orders, contacts, and stops.

-
-
- +
+
{/* Step indicator */}
{(["upload", "analysis", "preview", "import"] as Step[]).map((s, i) => { @@ -161,12 +147,12 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: return (
{isPast ? "✓" : i + 1}
- {labels[i]} - {i < 3 &&
} + {labels[i]} + {i < 3 &&
}
); })} @@ -174,12 +160,12 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {/* Brand selector */} {isPlatformAdmin && ( -
- +
+ @@ -193,29 +179,29 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: onDragOver={(e) => { e.preventDefault(); }} onDrop={handleDrop} onClick={() => inputRef.current?.click()} - className="flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed border-stone-300 bg-white p-12 cursor-pointer hover:border-violet-500 hover:bg-stone-50 transition-colors" + className="flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed border-[var(--admin-border)] bg-white p-12 cursor-pointer hover:border-[var(--admin-accent)] hover:bg-[var(--admin-bg)] transition-colors" > - +
-

Drag & drop your file here

-

or click to browse

+

Drag & drop your file here

+

or click to browse

-

CSV, XLSX, XLS, TXT · max 5,000 rows · 10MB

+

CSV, XLSX, XLS, TXT · max 5,000 rows · 10MB

{ const f = e.target.files?.[0]; if (f) handleFile(f); }} />
{fileName && ( -
- +
+
-

{fileName}

-

Ready to analyze

+

{fileName}

+

Ready to analyze

- +
)} @@ -226,7 +212,7 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: -
-

AI-assisted import

-

The Import Center auto-detects your data type, maps columns intelligently, and normalizes messy data (phone numbers, dates, prices). Supports products, orders, contacts, and stops.

+
+

AI-assisted import

+

The Import Center auto-detects your data type, maps columns intelligently, and normalizes messy data (phone numbers, dates, prices). Supports products, orders, contacts, and stops.

)} {/* ── Analysis Loading ── */} {step === "analysis" && ( -
-
-

{analysisLabels[analysisStep]}

-

This usually takes 5–10 seconds

+
+
+

{analysisLabels[analysisStep]}

+

This usually takes 5–10 seconds

)} @@ -254,33 +240,33 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {step === "preview" && analysis && (
{/* Detected type banner */} -
+
0.8 ? "bg-emerald-100 text-emerald-700 border border-emerald-200" : analysis.confidence > 0.5 ? "bg-amber-100 text-amber-700 border border-amber-200" : "bg-red-100 text-red-700 border border-red-200" + analysis.confidence > 0.8 ? "bg-green-100 text-green-700 border border-green-200" : analysis.confidence > 0.5 ? "bg-amber-100 text-amber-700 border border-amber-200" : "bg-red-100 text-red-700 border border-red-200" }`}> {ENTITY_LABELS[analysis.detectedType] ?? "Unknown"} - + {Math.round(analysis.confidence * 100)}% confidence
-

+

{analysis.rowCount.toLocaleString()} rows detected {analysis.autoFixApplied.length > 0 && ( - · {analysis.autoFixApplied.join(", ")} + · {analysis.autoFixApplied.join(", ")} )}

{analysis.confidence < 0.8 && (
-

Is this wrong?

+

Is this wrong?

{(["products", "orders", "stops", "contacts"] as ImportEntityType[]).map((t) => ( ))}
@@ -301,18 +287,18 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: )} {/* Column mapping table */} -
-
-

Column Mappings

- AI auto-detected — click to change +
+
+

Column Mappings

+ AI auto-detected — click to change
- - - - + + + + @@ -321,19 +307,19 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: const sampleIdx = analysis.headers.indexOf(header); const samples = analysis.rawRows.slice(0, 3).map((r) => r[sampleIdx] ?? "").filter(Boolean); return ( - - + + - + ); })} @@ -343,28 +329,28 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {/* Data preview */} -
-
-

Data Preview (first 5 rows)

+
+
+

Data Preview (first 5 rows)

Your ColumnMaps ToSample Values
Your ColumnMaps ToSample Values
{header}
{header} {samples.join(", ") || "—"}{samples.join(", ") || "—"}
- + {analysis.headers.map((h) => ( - + ))} {analysis.rawRows.slice(0, 5).map((row, ri) => ( - + {analysis.headers.map((h, ci) => { const field = editedMappings[h]; const val = row[ci] ?? ""; const isMapped = field && field !== "ignore"; return ( - ); @@ -378,13 +364,13 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {/* Actions */}
- @@ -396,38 +382,37 @@ export default function ImportCenterClient({ brandId: initialBrandId, brandName: {step === "import" && (
{importResult?.success ? ( -
-
- +
+
+
-

Import Complete

- {importResult.created !== undefined &&

{importResult.created} created{importResult.updated ? `, ${importResult.updated} updated` : ""}

} +

Import Complete

+ {importResult.created !== undefined &&

{importResult.created} created{importResult.updated ? `, ${importResult.updated} updated` : ""}

} {importResult.errors.length > 0 && (

{importResult.errors.length} rows had errors

)}
- - + View {analysis ? ENTITY_LABELS[analysis.detectedType] : ""}
) : ( -
-

Import Failed

-

{importError ?? "An error occurred during import."}

-
)}
)} -
); diff --git a/src/app/admin/import/page.tsx b/src/app/admin/import/page.tsx index 0379c02..42d6e1a 100644 --- a/src/app/admin/import/page.tsx +++ b/src/app/admin/import/page.tsx @@ -1,6 +1,7 @@ import { getAdminUser } from "@/lib/admin-permissions"; import ImportCenterClient from "./ImportCenterClient"; import { getBrands } from "@/actions/admin/users"; +import { PageHeader } from "@/components/admin/design-system"; export default async function ImportCenterPage() { const adminUser = await getAdminUser(); @@ -20,13 +21,21 @@ export default async function ImportCenterPage() { } return ( -
+
- + + + + } + /> - {/* Header */}
-
-
- + -
-
-

Orders

-

Manage customer orders and pickup status

-
-
+ } + />
{/* Content */} diff --git a/src/app/admin/products/page.tsx b/src/app/admin/products/page.tsx index f8f649a..bc7914d 100644 --- a/src/app/admin/products/page.tsx +++ b/src/app/admin/products/page.tsx @@ -3,17 +3,15 @@ import { getAdminUser } from "@/lib/admin-permissions"; import AdminAccessDenied from "@/components/admin/AdminAccessDenied"; import ProductsClient from "@/components/admin/ProductsClient"; -// Icon components -const Icons = { - package: (className: string) => ( - - - - - - - ), -}; +// Icon for page header +const PackageIcon = () => ( + + + + + + +); export default async function AdminProductsPage() { const adminUser = await getAdminUser(); @@ -31,7 +29,7 @@ export default async function AdminProductsPage() { ); } - const brandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de"; + const brandId = adminUser.brand_id; let query = supabase .from("products") @@ -71,25 +69,7 @@ export default async function AdminProductsPage() { return (
- {/* Header */} -
-
-
- {Icons.package("h-5 w-5 sm:h-6 sm:w-6 text-white")} -
-
-

Products

-

Manage your product catalog

-
-
-
- - {/* Content */} -
-
- -
-
+
); } \ No newline at end of file diff --git a/src/app/admin/reports/page.tsx b/src/app/admin/reports/page.tsx index 089e49e..3fb469c 100644 --- a/src/app/admin/reports/page.tsx +++ b/src/app/admin/reports/page.tsx @@ -2,6 +2,7 @@ import { supabase } from "@/lib/supabase"; import { getAdminUser } from "@/lib/admin-permissions"; import AdminAccessDenied from "@/components/admin/AdminAccessDenied"; import ReportsDashboard from "@/components/admin/ReportsDashboard"; +import { PageHeader } from "@/components/admin/design-system"; import { redirect } from "next/navigation"; export default async function ReportsPage() { @@ -23,12 +24,10 @@ export default async function ReportsPage() { return (
-
-

Reports

-

- Operational visibility across orders, fulfillment, contacts, and campaigns. -

-
+ ( + + + + +); + export default async function AdminStopsPage() { const adminUser = await getAdminUser(); @@ -67,32 +75,17 @@ export default async function AdminStopsPage() { return (
- {/* Header */}
-
-
-
- - - - -
-
-

Stops & Routes

-

- {adminUser?.brand_id ? "Managing stops for your brand." : "Manage routes, pickup locations, dates, and cutoff times."} -

-
-
- -
- - {/* Breadcrumb */} -
- Admin - / - Stops & Routes -
+ } + title="Stops & Routes" + subtitle={adminUser?.brand_id ? "Managing stops for your brand." : "Manage routes, pickup locations, dates, and cutoff times."} + actions={} + />
{/* Content */} diff --git a/src/app/admin/taxes/page.tsx b/src/app/admin/taxes/page.tsx index f8dee10..5c95d0f 100644 --- a/src/app/admin/taxes/page.tsx +++ b/src/app/admin/taxes/page.tsx @@ -4,6 +4,7 @@ import { getAdminUser } from "@/lib/admin-permissions"; import { supabase } from "@/lib/supabase"; import AdminAccessDenied from "@/components/admin/AdminAccessDenied"; import TaxDashboard from "@/components/admin/TaxDashboard"; +import { PageHeader } from "@/components/admin/design-system"; type Props = { params: Promise<{ brandId?: string }>; @@ -55,12 +56,10 @@ export default async function TaxesPage({ params }: Props) { return (
-
-

Tax Dashboard

-

- Sales tax collected on orders shipped to nexus states. -

-
+ +
+ +
+ +
+
); } \ No newline at end of file diff --git a/src/app/admin/water-log/headgates/HeadgatesManager.tsx b/src/app/admin/water-log/headgates/HeadgatesManager.tsx index 2c8710e..9a029b0 100644 --- a/src/app/admin/water-log/headgates/HeadgatesManager.tsx +++ b/src/app/admin/water-log/headgates/HeadgatesManager.tsx @@ -8,6 +8,7 @@ import { regenerateHeadgateToken, updateWaterHeadgate, } from "@/actions/water-log/admin"; +import { AdminButton } from "@/components/admin/design-system"; type Headgate = { id: string; @@ -159,20 +160,20 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) { } return ( -
+
{/* Header */} -
+
- -

Headgates & QR Codes

+ +

Headgates & QR Codes

-

Manage headgates and generate QR codes for field access

+

Manage headgates and generate QR codes for field access

- +
@@ -180,138 +181,137 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) { {/* Bulk actions bar */} {headgates.length > 0 && ( -
- - | - {selected.size} selected + | + {selected.size} selected
- + Print {selected.size > 0 ? `(${selected.size})` : ""} QR Codes +
)} {/* Add form */} {showAdd && ( -
+
- + setNewName(e.target.value)} placeholder="e.g. North Field Gate 1" - className="w-full rounded-xl border border-stone-300 px-4 py-3 text-sm outline-none focus:border-stone-900" + className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]" required />
- +
- - + + Create + + setShowAdd(false)}> + Cancel +
)} {/* Table */} {headgates.length === 0 ? ( -
+
No headgates yet. Create one to get started.
) : ( -
+
{h}{h}
+ {String(val).slice(0, 60)}
- + - - - - - - + + + + + + {headgates.map((hg) => ( - + @@ -324,7 +324,7 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) { {/* Toast */} {toast && ( -
+
{toast.msg}
)} @@ -332,29 +332,33 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) { {/* Edit Modal */} {editHg && (
setEditHg(null)}> -
e.stopPropagation()}> -
-

Edit Headgate

- +
e.stopPropagation()}> +
+

Edit Headgate

+
- + setEditName(e.target.value)} - className="w-full rounded-xl border border-stone-300 px-4 py-3 text-sm outline-none focus:border-stone-900" + className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]" required />
- +
-
- + setEditHigh(e.target.value)} placeholder="e.g. 15.0" - className="w-full rounded-xl border border-stone-300 px-4 py-3 text-sm outline-none focus:border-stone-900" + className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]" />
- + setEditLow(e.target.value)} placeholder="e.g. 5.0" - className="w-full rounded-xl border border-stone-300 px-4 py-3 text-sm outline-none focus:border-stone-900" + className="w-full rounded-xl border border-[var(--admin-border)] px-4 py-3 text-sm outline-none focus:border-[var(--admin-accent)]" />
-

Leave thresholds blank to disable alerts for this headgate.

+

Leave thresholds blank to disable alerts for this headgate.

- - + + Save Changes + + setEditHg(null)}> + Cancel +
@@ -454,21 +460,25 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v return (
-
e.stopPropagation()}> +
e.stopPropagation()}> {/* Header */} -
+
-

{hg.name}

-

QR Label Preview

+

{hg.name}

+

QR Label Preview

- +
{/* ── Simplified label preview ── */} -
+
{/* Mini version of the new label design */} -
+
{hg.name}
{code}
@@ -478,17 +488,17 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v className="w-28 h-28 rounded" />
-
{waterUrl}
+
{waterUrl}
{/* Tab toggle */} -
+
{(["preview", "print", "download"] as const).map((t) => ( @@ -497,38 +507,39 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v
{tab === "preview" ? ( -
+

This label is optimized for physical signs and thermal printing:

-
    +
    • Large name (24pt bold) — scannable from distance
    • -
    • Short code (e.g. {code}) — field reference
    • +
    • Short code (e.g. {code}) — field reference
    • High-contrast QR — error correction H for outdoor use
    • Tiny URL at bottom — reference only
    - +
) : tab === "print" ? ( - + + 🖨️ Print Label + ) : ( - + + 💾 Download PNG + )} - + -
-

Token

- {hg.headgate_token} +
+

Token

+ {hg.headgate_token}
diff --git a/src/app/admin/water-log/page.tsx b/src/app/admin/water-log/page.tsx index 546b46a..a35a7ef 100644 --- a/src/app/admin/water-log/page.tsx +++ b/src/app/admin/water-log/page.tsx @@ -2,6 +2,7 @@ import WaterLogAdminPanel from "@/components/admin/WaterLogAdminPanel"; import { getAdminUser } from "@/lib/admin-permissions"; import { getWaterIrrigators, getWaterHeadgatesAdmin, getWaterEntries } from "@/actions/water-log/admin"; import { redirect } from "next/navigation"; +import { PageHeader } from "@/components/admin/design-system"; const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de"; @@ -29,12 +30,21 @@ export default async function AdminWaterLogPage() { ]); return ( - +
+ +
+ +
+
); } \ No newline at end of file diff --git a/src/app/admin/wholesale/WholesaleClient.tsx b/src/app/admin/wholesale/WholesaleClient.tsx index 0ae5b7e..1f32eb6 100644 --- a/src/app/admin/wholesale/WholesaleClient.tsx +++ b/src/app/admin/wholesale/WholesaleClient.tsx @@ -37,6 +37,7 @@ import { getPendingWholesaleRegistrations, approveWholesaleRegistration, getWhol import { getCurrentAdminUser } from "@/actions/admin-user"; import { type AdminUser } from "@/lib/admin-permissions"; import { formatDate } from "@/lib/format-date"; +import { PageHeader, AdminButton, AdminSearchInput, AdminFilterTabs, AdminBadge } from "@/components/admin/design-system"; type Tab = "dashboard" | "customers" | "products" | "orders" | "settings"; @@ -94,46 +95,47 @@ export default function WholesaleClient({ brandId }: { brandId: string }) { if (loading) { return ( -
-

Loading wholesale data...

+
+

Loading wholesale data...

); } + // SVG Icon for Wholesale + const WholesaleIcon = () => ( + + + + ); + + const tabs = [ + { value: "dashboard", label: "Dashboard", count: undefined }, + { value: "customers", label: "Customers", count: customers.filter(c => c.account_status !== "pending_approval" && c.account_status !== "rejected").length }, + { value: "products", label: "Products", count: products.length }, + { value: "orders", label: "Orders", count: orders.length }, + { value: "settings", label: "Settings" }, + ]; + return ( -
- {/* Header */} -
-
-
-

Wholesale Portal

-

Manage wholesale orders, customers, and products

-
-
-
+
+ } + title="Wholesale Portal" + subtitle="Manage wholesale orders, customers, and products" + className="mb-0" + /> {/* Tab nav */} -
-
- -
+
+ setTab(value as Tab)} + tabs={tabs} + size="md" + />
-
+
{msg && (
{[ - { label: "Open Orders", value: stats.open_orders, color: "bg-white border-stone-200" }, - { label: "Pickup Today", value: stats.pickup_today, color: "bg-white border-stone-200" }, - { label: "Past Due", value: stats.past_due, color: "bg-red-50 border-red-200" }, - { label: "Total Unpaid", value: `$${stats.total_unpaid.toFixed(2)}`, color: "bg-white border-stone-200" }, - { label: "Awaiting Deposit", value: stats.awaiting_deposit, color: "bg-amber-50 border-amber-200" }, - { label: "Fulfilled Today", value: stats.fulfilled_today, color: "bg-emerald-50 border-emerald-200" }, + { label: "Open Orders", value: stats.open_orders, variant: "default" }, + { label: "Pickup Today", value: stats.pickup_today, variant: "default" }, + { label: "Past Due", value: stats.past_due, variant: "danger" }, + { label: "Total Unpaid", value: `$${stats.total_unpaid.toFixed(2)}`, variant: "default" }, + { label: "Awaiting Deposit", value: stats.awaiting_deposit, variant: "warning" }, + { label: "Fulfilled Today", value: stats.fulfilled_today, variant: "success" }, ].map((card) => ( -
-

{card.label}

-

{card.value}

+
+

{card.label}

+

{card.value}

))}
{/* Recent orders */} -
-

Recent Orders

+
+

Recent Orders

{recentOrders.length === 0 ? ( -

No wholesale orders yet.

+

No wholesale orders yet.

) : (
NameTokenUnitLast UsedStatusQR CodeNameTokenUnitLast UsedStatusQR Code
toggleSelect(hg.id)} />
- {hg.name} + {hg.name} {hg.high_threshold != null && ( - + High: {hg.high_threshold} )} {hg.low_threshold != null && ( - + Low: {hg.low_threshold} )}
- {hg.headgate_token?.slice(0, 8)}… + {hg.headgate_token?.slice(0, 8)}… - {hg.unit} + {hg.unit} - + {hg.last_used_at ? new Date(hg.last_used_at).toLocaleDateString("en-US", { month: "short", day: "numeric" }) : "Never"} - + {hg.active ? "Active" : "Inactive"}
- + {/* QR preview thumbnail */} - +
- + @@ -255,19 +257,19 @@ function DashboardTab({ stats, recentOrders, brandId, onMsg, webhookActivity }: - + {recentOrders.map((order) => ( - - - - - + + + + +
Invoice Customer Pickup DatePayment
{order.invoice_number ?? "—"}{order.company_name}{order.anticipated_pickup_date ?? "—"}${Number(order.subtotal).toFixed(2)}
{order.invoice_number ?? "—"}{order.company_name}{order.anticipated_pickup_date ?? "—"}${Number(order.subtotal).toFixed(2)} {order.payment_status === "paid" ? "Paid" : order.balance_due > 0 ? `$${Number(order.balance_due).toFixed(2)} due` : "Partial"} @@ -282,12 +284,12 @@ function DashboardTab({ stats, recentOrders, brandId, onMsg, webhookActivity }: {/* Recent webhook activity */} {webhookActivity.length > 0 && ( -
-

Recent Webhook Activity

+
+

Recent Webhook Activity

- + @@ -295,25 +297,25 @@ function DashboardTab({ stats, recentOrders, brandId, onMsg, webhookActivity }: - + {webhookActivity.map((entry) => ( - + - + - - + + ))} @@ -519,31 +521,41 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [],
{/* Sub-tab nav */}
- - + {subTab === "customers" && ( - + )}
{subTab === "registrations" && (
-

Pending Registrations

+

Pending Registrations

{registrations.filter(r => r.account_status === "pending_approval").length === 0 ? ( -

No pending registrations.

+

No pending registrations.

) : ( -
+
Event Order StatusSent At
- {entry.event_type} + {entry.event_type} {entry.order_id ? entry.order_id.slice(0, 8) : "—"}{entry.order_id ? entry.order_id.slice(0, 8) : "—"} {entry.status} {entry.attempts}{new Date(entry.created_at).toLocaleString()}{entry.attempts}{new Date(entry.created_at).toLocaleString()}
- + @@ -552,27 +564,34 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [], - + {registrations.filter(r => r.account_status === "pending_approval").map(r => ( - - - + + + - + @@ -587,33 +606,33 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [], {subTab === "customers" && ( <> {showForm && ( -
-

{editing ? "Edit Customer" : "New Customer"}

+
+

{editing ? "Edit Customer" : "New Customer"}

- + setForm(f => ({ ...f, companyName: e.target.value }))} - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" /> + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
- + setForm(f => ({ ...f, contactName: e.target.value }))} - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" /> + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
- + setForm(f => ({ ...f, email: e.target.value }))} - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" /> + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
- + setForm(f => ({ ...f, phone: e.target.value }))} - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" /> + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
- +
- + setForm(f => ({ ...f, creditLimit: Number(e.target.value) }))} - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" placeholder="0 = unlimited" /> + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" placeholder="0 = unlimited" />
-
-

Deposit Rules

+
+

Deposit Rules

setForm(f => ({ ...f, depositsEnabled: e.target.checked }))} className="rounded" id="dep-enabled" /> - +
{form.depositsEnabled && (
- + setForm(f => ({ ...f, depositThreshold: e.target.value }))} - className="w-full rounded-lg border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" placeholder="0" /> + className="w-full rounded-lg border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" placeholder="0" />
- + setForm(f => ({ ...f, depositPercentage: e.target.value }))} - className="w-full rounded-lg border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900" placeholder="20" min="1" max="100" /> + className="w-full rounded-lg border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" placeholder="20" min="1" max="100" />
)}
- - +
)} -
+
Company Contact
{r.company_name ?? "—"}{r.contact_name ?? "—"}
{r.email}
{r.company_name ?? "—"}{r.contact_name ?? "—"}
{r.email}
- Pending + Pending {formatDate(new Date(r.created_at))}{formatDate(new Date(r.created_at))}
- - +
- + - + {customers.length === 0 ? ( - + ) : customers.map(c => ( - + - - + + - - - + {selectedCustomers.size > 0 && ( - + @@ -789,7 +810,7 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [], )} - {/* Customer Pricing Overrides Panel */} + {/* Customer Pricing Overlays Panel */} {pricingCustomer && ( -
-
+
+
-

Pricing Overrides

-

{customer.company_name}

+

Pricing Overrides

+

{customer.company_name}

-
{loading ? ( -

Loading...

+

Loading...

) : products.length === 0 ? ( -

No products available.

+

No products available.

) : (
- 0} onChange={toggleAllCustomers} /> @@ -683,38 +700,37 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [],
No customers yet.
No customers yet.
- toggleSelectCustomer(c.id)} /> {c.company_name ?? "—"}{c.contact_name ?? "—"}
{c.email}
{c.company_name ?? "—"}{c.contact_name ?? "—"}
{c.email}
- {c.account_status} + + {c.account_status} + + {c.credit_limit <= 0 ? "Unlimited" : `$${Number(c.credit_limit).toFixed(2)}`} + {c.deposits_enabled ? `${c.deposit_percentage}%` : "—"} {formatDate(new Date(c.created_at))}{formatDate(new Date(c.created_at))}
@@ -722,45 +738,45 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [], {openCustomerActions === c.id && (
e.stopPropagation()} > - + View Portal As -
+
@@ -772,14 +788,19 @@ function CustomersTab({ customers, products, brandId, onMsg, registrations = [], ))}
- {selectedCustomers.size} selected - +
- + - + {products.map(p => { const standardPrice = p.price_tiers?.[0]?.price ?? 0; const overridePrice = overrides[p.id] ?? ""; return ( - - + + ); @@ -915,7 +937,7 @@ function CustomerPricingPanel({ customer, products, onClose, onMsg }: {
Product Standard Price Override Price
{p.name}${standardPrice.toFixed(2)}{p.name}${standardPrice.toFixed(2)}
- $ + $ setOverrides(prev => ({ ...prev, [p.id]: e.target.value }))} placeholder={standardPrice.toFixed(2)} - className="w-24 rounded-lg border border-zinc-600 px-2 py-1 text-sm outline-none focus:border-green-600" + className="w-24 rounded-lg border border-[var(--admin-border)] px-2 py-1 text-sm outline-none focus:border-[var(--admin-accent)]" /> {overridePrice && overridePrice !== standardPrice.toFixed(2) && ( - Custom + Custom )}
- +
)} -

+

Leave override price blank to remove custom pricing and use standard price tiers.

@@ -950,15 +972,15 @@ function PriceSheetModal({ return (
-
e.stopPropagation()}> -
+
e.stopPropagation()}> +
-

Send Price Sheet

-

+

Send Price Sheet

+

{customerCount === 1 ? "1 customer" : `${customerCount} customers`}

-
@@ -966,44 +988,42 @@ function PriceSheetModal({
- + setSubject(e.target.value)} required - className="w-full rounded-xl border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-green-600" + className="w-full rounded-xl border border-[var(--admin-border)] px-3 py-2 text-sm outline-none focus:border-[var(--admin-accent)]" />
-