"use client"; import { useState, useEffect, useRef } from "react"; import crypto from "crypto"; import { type WholesaleOrder, type WholesaleCustomer, type WholesaleProduct, type WholesaleSettings, type WholesaleDashboardStats, type NotificationRecipient, getWholesaleOrders, getWholesaleCustomers, getWholesaleProducts, getWholesaleSettings, getWholesaleDashboardStats, markWholesaleOrderFulfilled, updateWholesaleOrderStatus, deleteWholesaleOrder, deleteWholesaleCustomer, deleteWholesaleProduct, saveWholesaleCustomer, saveWholesaleProduct, saveWholesaleSettings, recordWholesaleDeposit, bulkFulfillWholesaleOrders, bulkRecordWholesaleDeposit, enqueueWholesaleNotification, getWebhookSettings, saveWebhookSettings, enqueueWholesaleWebhook, getRecentWebhookActivity, } from "@/actions/wholesale"; import DepositModal from "@/components/wholesale/DepositModal"; import OrderDetailsModal from "@/components/wholesale/OrderDetailsModal"; import { getPendingWholesaleRegistrations, approveWholesaleRegistration, getWholesaleCustomerPricing, upsertWholesaleCustomerPricing, deleteWholesaleCustomerPricing } from "@/actions/wholesale-register"; import { getCurrentAdminUser } from "@/actions/admin-user"; import { type AdminUser } from "@/lib/admin-permissions"; import { formatDate } from "@/lib/format-date"; type Tab = "dashboard" | "customers" | "products" | "orders" | "settings"; export default function WholesaleClient({ brandId }: { brandId: string }) { const [tab, setTab] = useState("dashboard"); const [msg, setMsg] = useState<{ kind: "success" | "error"; text: string } | null>(null); const [adminUser, setAdminUser] = useState(null); // Data state const [orders, setOrders] = useState([]); const [customers, setCustomers] = useState([]); const [products, setProducts] = useState([]); const [settings, setSettings] = useState(null); const [stats, setStats] = useState(null); const [registrations, setRegistrations] = useState>([]); const [loading, setLoading] = useState(true); const [webhookActivity, setWebhookActivity] = useState>([]); useEffect(() => { async function load() { setLoading(true); const [au, o, c, p, s, st, r, wa] = await Promise.all([ getCurrentAdminUser(), getWholesaleOrders(brandId), getWholesaleCustomers(brandId), getWholesaleProducts(brandId), getWholesaleSettings(brandId), getWholesaleDashboardStats(brandId), getPendingWholesaleRegistrations(brandId), getRecentWebhookActivity(brandId, 5), ]); setAdminUser(au); setOrders(o); setCustomers(c); setProducts(p); setSettings(s); setStats(st); setRegistrations(r as typeof registrations); setWebhookActivity(wa); setLoading(false); } load(); }, [brandId]); function showMsg(kind: "success" | "error", text: string) { setMsg({ kind, text }); setTimeout(() => setMsg(null), 4000); } if (loading) { return (

Loading wholesale data...

); } return (
{/* Header */}

Wholesale Portal

Manage wholesale orders, customers, and products

{/* Tab nav */}
{msg && (
{msg.text}
)} {tab === "dashboard" && ( )} {tab === "customers" && ( { const r = await getPendingWholesaleRegistrations(brandId); setRegistrations(r as typeof registrations); }} /> )} {tab === "products" && ( { const p = await getWholesaleProducts(brandId); setProducts(p); }} /> )} {tab === "orders" && ( { const o = await getWholesaleOrders(brandId); setOrders(o); }} /> )} {tab === "settings" && ( { const s = await getWholesaleSettings(brandId); setSettings(s); }} canManageSettings={adminUser?.can_manage_settings ?? false} /> )}
); } // ── Dashboard Tab ───────────────────────────────────────────────────────────── function DashboardTab({ stats, recentOrders, brandId, onMsg, webhookActivity }: { stats: WholesaleDashboardStats; recentOrders: WholesaleOrder[]; brandId: string; onMsg: (kind: "success" | "error", text: string) => void; webhookActivity: Array<{ id: string; event_type: string; order_id: string | null; status: string; attempts: number; created_at: string; response: string | null; }>; }) { return (
{/* Stat cards */}
{[ { label: "Open Orders", value: stats.open_orders, color: "bg-blue-50 border-blue-200" }, { label: "Pickup Today", value: stats.pickup_today, color: "bg-yellow-50 border-yellow-200" }, { label: "Past Due", value: stats.past_due, color: "bg-red-900/30 border-red-200" }, { label: "Total Unpaid", value: `$${stats.total_unpaid.toFixed(2)}`, color: "bg-orange-50 border-orange-200" }, { label: "Awaiting Deposit", value: stats.awaiting_deposit, color: "bg-purple-50 border-purple-200" }, { label: "Fulfilled Today", value: stats.fulfilled_today, color: "bg-green-900/30 border-green-200" }, ].map((card) => (

{card.label}

{card.value}

))}
{/* Recent orders */}

Recent Orders

{recentOrders.length === 0 ? (

No wholesale orders yet.

) : (
{recentOrders.map((order) => ( ))}
Invoice Customer Pickup Date Total Status Payment
{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"}
)}
{/* Recent webhook activity */} {webhookActivity.length > 0 && (

Recent Webhook Activity

{webhookActivity.map((entry) => ( ))}
Event Order Status Attempts Sent At
{entry.event_type} {entry.order_id ? entry.order_id.slice(0, 8) : "—"} {entry.status} {entry.attempts} {new Date(entry.created_at).toLocaleString()}
)}
); } // ── Customers Tab ──────────────────────────────────────────────────────────── function CustomersTab({ customers, products, brandId, onMsg, registrations = [], onRefresh }: { customers: WholesaleCustomer[]; products: WholesaleProduct[]; brandId: string; onMsg: (kind: "success" | "error", text: string) => void; registrations?: Array<{ id: string; company_name: string | null; contact_name: string | null; email: string; phone: string | null; account_status: string; created_at: string; }>; onRefresh: () => void; }) { const [showForm, setShowForm] = useState(false); const [editing, setEditing] = useState(null); const [subTab, setSubTab] = useState<"customers" | "registrations">("customers"); const [form, setForm] = useState({ companyName: "", contactName: "", email: "", phone: "", accountStatus: "active", creditLimit: 0, depositsEnabled: false, depositThreshold: "", depositPercentage: "", orderEmail: "", invoiceEmail: "", adminNotes: "", }); const [saving, setSaving] = useState(false); const [processingReg, setProcessingReg] = useState(null); const [pricingCustomer, setPricingCustomer] = useState(null); const [selectedCustomers, setSelectedCustomers] = useState>(new Set()); const [sendingPriceSheet, setSendingPriceSheet] = useState(false); const [priceSheetTarget, setPriceSheetTarget] = useState<{ customerIds: string[]; defaultSubject: string; } | null>(null); const [openCustomerActions, setOpenCustomerActions] = useState(null); const [deletingCustomer, setDeletingCustomer] = useState(null); function toggleSelectCustomer(id: string) { setSelectedCustomers(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function toggleAllCustomers() { if (selectedCustomers.size === customers.length) { setSelectedCustomers(new Set()); } else { setSelectedCustomers(new Set(customers.map(c => c.id))); } } function openPriceSheetModal(customerIds: string[]) { const ids = customerIds; if (ids.length === 0) return; // Generate default subject from brand name in settings const brandName = ""; // will be filled by the API call setPriceSheetTarget({ customerIds: ids, defaultSubject: `Wholesale Price Sheet — ${new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}`, }); } async function handleSendPriceSheet(customerId?: string) { const ids = customerId ? [customerId] : Array.from(selectedCustomers); if (ids.length === 0) return; openPriceSheetModal(ids); } async function handleConfirmPriceSheet(subject: string, customNote: string) { if (!priceSheetTarget) return; setSendingPriceSheet(true); setPriceSheetTarget(null); try { const res = await fetch("/api/wholesale/price-sheet", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ customerIds: priceSheetTarget.customerIds, brandId, subject, customNote: customNote || undefined, }), }); const data = await res.json(); if (res.ok) { onMsg("success", `Price sheet sent to ${data.enqueued} customer(s).`); setSelectedCustomers(new Set()); } else { onMsg("error", data.error ?? "Failed to send price sheet."); } } catch { onMsg("error", "Failed to send price sheet."); } finally { setSendingPriceSheet(false); } } async function handleApproveReject(regId: string, action: "approve" | "reject") { setProcessingReg(regId); const result = await approveWholesaleRegistration(regId, brandId, action); setProcessingReg(null); if (result.success) { onMsg("success", action === "approve" ? "Registration approved." : "Registration rejected."); onRefresh(); } else { onMsg("error", result.error ?? "Failed to process registration."); } } // Close customer actions dropdown when clicking outside useEffect(() => { function handleClick(e: MouseEvent) { if (!(e.target as Element).closest(".customer-actions-cell")) { setOpenCustomerActions(null); } } document.addEventListener("click", handleClick); return () => document.removeEventListener("click", handleClick); }, []); function toggleCustomerActions(customerId: string, e: React.MouseEvent) { e.stopPropagation(); setOpenCustomerActions(prev => prev === customerId ? null : customerId); } async function handleDeleteCustomer(customerId: string) { if (!confirm("Delete this customer? This cannot be undone. Customers with existing orders cannot be deleted.")) return; setDeletingCustomer(customerId); const result = await deleteWholesaleCustomer(customerId); setDeletingCustomer(null); if (result.success) { onMsg("success", "Customer deleted."); onRefresh(); } else { onMsg("error", result.error ?? "Failed to delete customer."); } } async function handleSave() { setSaving(true); const result = await saveWholesaleCustomer({ brandId, userId: editing?.user_id ?? undefined, companyName: form.companyName || undefined, contactName: form.contactName || undefined, email: form.email || undefined, phone: form.phone || undefined, accountStatus: form.accountStatus, creditLimit: form.creditLimit, depositsEnabled: form.depositsEnabled, depositThreshold: form.depositThreshold ? Number(form.depositThreshold) : undefined, depositPercentage: form.depositPercentage ? Number(form.depositPercentage) : undefined, orderEmail: form.orderEmail || undefined, invoiceEmail: form.invoiceEmail || undefined, adminNotes: form.adminNotes || undefined, }); setSaving(false); if (result.success) { onMsg("success", editing ? "Customer updated." : "Customer created."); setShowForm(false); onRefresh(); } else { onMsg("error", result.error ?? "Failed to save."); } } function openNew() { setEditing(null); setForm({ companyName: "", contactName: "", email: "", phone: "", accountStatus: "active", creditLimit: 0, depositsEnabled: false, depositThreshold: "", depositPercentage: "", orderEmail: "", invoiceEmail: "", adminNotes: "" }); setShowForm(true); } function openEdit(c: WholesaleCustomer) { setEditing(c); setForm({ companyName: c.company_name ?? "", contactName: c.contact_name ?? "", email: c.email ?? "", phone: c.phone ?? "", accountStatus: c.account_status ?? "active", creditLimit: Number(c.credit_limit), depositsEnabled: c.deposits_enabled, depositThreshold: c.deposit_threshold?.toString() ?? "", depositPercentage: c.deposit_percentage?.toString() ?? "", orderEmail: c.order_email ?? "", invoiceEmail: c.invoice_email ?? "", adminNotes: c.admin_notes ?? "", }); setShowForm(true); } return (
{/* Sub-tab nav */}
{subTab === "customers" && ( )}
{subTab === "registrations" && (

Pending Registrations

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

No pending registrations.

) : (
{registrations.filter(r => r.account_status === "pending_approval").map(r => ( ))}
Company Contact Status Registered
{r.company_name ?? "—"} {r.contact_name ?? "—"}
{r.email}
Pending {formatDate(new Date(r.created_at))}
)}
)} {subTab === "customers" && ( <> {showForm && (

{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" />
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" />
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" />
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" />
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" />

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" />
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" />
)}
)}
{customers.length === 0 ? ( ) : customers.map(c => ( ))} {selectedCustomers.size > 0 && ( )}
0} onChange={toggleAllCustomers} /> Company Contact Status Credit Deposits Created
No customers yet.
toggleSelectCustomer(c.id)} /> {c.company_name ?? "—"} {c.contact_name ?? "—"}
{c.email}
{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))}
{openCustomerActions === c.id && (
e.stopPropagation()} > View Portal As
)}
{selectedCustomers.size} selected
)} {/* Customer Pricing Overrides Panel */} {pricingCustomer && ( setPricingCustomer(null)} onMsg={onMsg} /> )} {/* Price Sheet Modal */} {priceSheetTarget && ( { setPriceSheetTarget(null); setSendingPriceSheet(false); }} /> )}
); } // ── Customer Pricing Panel ────────────────────────────────────────────────────── function CustomerPricingPanel({ customer, products, onClose, onMsg }: { customer: WholesaleCustomer; products: WholesaleProduct[]; onClose: () => void; onMsg: (kind: "success" | "error", text: string) => void; }) { const [overrides, setOverrides] = useState>({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); useEffect(() => { getWholesaleCustomerPricing(customer.id).then(pricing => { const map: Record = {}; for (const p of pricing) { map[p.product_id] = p.custom_unit_price.toString(); } setOverrides(map); setLoading(false); }); }, [customer.id]); async function handleSave(productId: string, price: string) { if (!price || isNaN(Number(price))) { await deleteWholesaleCustomerPricing({ customerId: customer.id, productId }); setOverrides(prev => { const n = { ...prev }; delete n[productId]; return n; }); } else { await upsertWholesaleCustomerPricing({ customerId: customer.id, productId, customUnitPrice: Number(price) }); setOverrides(prev => ({ ...prev, [productId]: price })); } onMsg("success", "Pricing updated."); } return (

Pricing Overrides

{customer.company_name}

{loading ? (

Loading...

) : products.length === 0 ? (

No products available.

) : ( {products.map(p => { const standardPrice = p.price_tiers?.[0]?.price ?? 0; const overridePrice = overrides[p.id] ?? ""; return ( ); })}
Product Standard Price Override Price
{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" /> {overridePrice && overridePrice !== standardPrice.toFixed(2) && ( Custom )}
)}

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

); } // ── Price Sheet Modal ────────────────────────────────────────────────────────── function PriceSheetModal({ customerCount, defaultSubject, onConfirm, onClose, }: { customerCount: number; defaultSubject: string; onConfirm: (subject: string, customNote: string) => void; onClose: () => void; }) { const [subject, setSubject] = useState(defaultSubject); const [note, setNote] = useState(""); const [sending, setSending] = useState(false); function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!subject.trim()) return; setSending(true); onConfirm(subject.trim(), note.trim()); } return (
e.stopPropagation()}>

Send Price Sheet

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

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" />