"use client"; import { useModuleClient } from "@86d-app/core/client"; import { useState } from "react"; import CustomerDetailTemplate from "./customer-detail.mdx"; // ─── Types ──────────────────────────────────────────────────────────────────── interface Customer { id: string; email: string; firstName?: string | null; lastName?: string | null; phone?: string | null; dateOfBirth?: string | null; tags?: string[] | null; metadata?: Record | null; createdAt: string; updatedAt: string; } interface CustomerAddress { id: string; customerId: string; type: "billing" | "shipping"; firstName: string; lastName: string; company?: string | null; line1: string; line2?: string | null; city: string; state: string; postalCode: string; country: string; phone?: string | null; isDefault: boolean; } interface GetCustomerResult { customer?: Customer; addresses?: CustomerAddress[]; error?: string; } // ─── Module Client ─────────────────────────────────────────────────────────── function useCustomersAdminApi() { const client = useModuleClient(); return { getCustomer: client.module("customers").admin["/admin/customers/:id"], updateCustomer: client.module("customers").admin["/admin/customers/:id/update"], deleteCustomer: client.module("customers").admin["/admin/customers/:id/delete"], addTags: client.module("customers").admin["/admin/customers/:id/tags"], removeTags: client.module("customers").admin["/admin/customers/:id/tags/remove"], createAddress: client.module("customers").admin["/admin/customers/:id/addresses/create"], updateAddress: client.module("customers").admin[ "/admin/customers/:id/addresses/:addressId/update" ], deleteAddress: client.module("customers").admin[ "/admin/customers/:id/addresses/:addressId/delete" ], setDefaultAddress: client.module("customers").admin[ "/admin/customers/:id/addresses/:addressId/set-default" ], }; } // ─── Address Form State ─────────────────────────────────────────────────────── interface AddressFormValues { type: "billing" | "shipping"; firstName: string; lastName: string; company: string; line1: string; line2: string; city: string; state: string; postalCode: string; country: string; phone: string; isDefault: boolean; } const emptyAddressForm: AddressFormValues = { type: "shipping", firstName: "", lastName: "", company: "", line1: "", line2: "", city: "", state: "", postalCode: "", country: "US", phone: "", isDefault: false, }; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDate(iso: string): string { return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", }).format(new Date(iso)); } function formatDateTime(iso: string): string { return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }).format(new Date(iso)); } function formatAddress(addr: CustomerAddress): string { const parts = [addr.line1]; if (addr.line2) parts.push(addr.line2); parts.push(`${addr.city}, ${addr.state} ${addr.postalCode}`); parts.push(addr.country); return parts.join(", "); } // ─── CustomerDetail ────────────────────────────────────────────────────────── interface CustomerDetailProps { customerId?: string; params?: Record; } export function CustomerDetail(props: CustomerDetailProps) { const customerId = props.customerId ?? props.params?.id; const api = useCustomersAdminApi(); const [editing, setEditing] = useState(false); const [editForm, setEditForm] = useState({ firstName: "", lastName: "", phone: "", }); const [newTag, setNewTag] = useState(""); const [addressEditId, setAddressEditId] = useState(null); const [addressFormOpen, setAddressFormOpen] = useState(false); const [addressForm, setAddressForm] = useState(emptyAddressForm); const { data: customerData, isLoading: loading } = api.getCustomer.useQuery( { params: { id: customerId ?? "" } }, { enabled: !!customerId }, ) as { data: GetCustomerResult | undefined; isLoading: boolean; }; const updateMutation = api.updateCustomer.useMutation({ onSuccess: () => { setEditing(false); void api.getCustomer.invalidate(); }, }); const deleteMutation = api.deleteCustomer.useMutation({ onSuccess: () => { window.location.href = "/admin/customers"; }, }); const addTagMutation = api.addTags.useMutation({ onSuccess: () => { setNewTag(""); void api.getCustomer.invalidate(); }, }); const removeTagMutation = api.removeTags.useMutation({ onSuccess: () => { void api.getCustomer.invalidate(); }, }); const createAddressMutation = api.createAddress.useMutation({ onSuccess: () => { setAddressFormOpen(false); setAddressEditId(null); setAddressForm(emptyAddressForm); void api.getCustomer.invalidate(); }, }); const updateAddressMutation = api.updateAddress.useMutation({ onSuccess: () => { setAddressFormOpen(false); setAddressEditId(null); setAddressForm(emptyAddressForm); void api.getCustomer.invalidate(); }, }); const deleteAddressMutation = api.deleteAddress.useMutation({ onSuccess: () => void api.getCustomer.invalidate(), }); const setDefaultAddressMutation = api.setDefaultAddress.useMutation({ onSuccess: () => void api.getCustomer.invalidate(), }); const customer = customerData?.customer ?? null; const addresses = customerData?.addresses ?? []; const startEditing = () => { if (!customer) return; setEditForm({ firstName: customer.firstName ?? "", lastName: customer.lastName ?? "", phone: customer.phone ?? "", }); setEditing(true); }; const handleUpdate = (e: React.FormEvent) => { e.preventDefault(); updateMutation.mutate({ params: { id: customerId }, firstName: editForm.firstName || undefined, lastName: editForm.lastName || undefined, phone: editForm.phone || null, }); }; const handleDelete = () => { if ( !window.confirm( "Are you sure you want to delete this customer? This cannot be undone.", ) ) { return; } deleteMutation.mutate({ params: { id: customerId } }); }; const handleAddTag = () => { const tag = newTag.trim(); if (!tag || !customerId) return; addTagMutation.mutate({ params: { id: customerId }, tags: [tag] }); }; const handleRemoveTag = (tag: string) => { if (!customerId) return; removeTagMutation.mutate({ params: { id: customerId }, tags: [tag] }); }; const openAddAddress = () => { setAddressEditId(null); setAddressForm(emptyAddressForm); setAddressFormOpen(true); }; const openEditAddress = (addr: CustomerAddress) => { setAddressEditId(addr.id); setAddressForm({ type: addr.type, firstName: addr.firstName, lastName: addr.lastName, company: addr.company ?? "", line1: addr.line1, line2: addr.line2 ?? "", city: addr.city, state: addr.state, postalCode: addr.postalCode, country: addr.country, phone: addr.phone ?? "", isDefault: addr.isDefault, }); setAddressFormOpen(true); }; const handleAddressSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!customerId) return; const payload = { ...addressForm, company: addressForm.company || undefined, line2: addressForm.line2 || undefined, phone: addressForm.phone || undefined, }; if (addressEditId) { updateAddressMutation.mutate({ params: { customerId, addressId: addressEditId }, ...payload, }); } else { createAddressMutation.mutate({ params: { customerId }, ...payload, }); } }; if (!customerId) { return (

Customer not found

No customer ID was provided.

Back to customers
); } // Loading skeleton if (loading) { return (
); } if (!customer) { return (

Customer not found

Back to customers
); } const fullName = [customer.firstName, customer.lastName].filter(Boolean).join(" ") || "Unnamed"; const billingAddresses = addresses.filter((a) => a.type === "billing"); const shippingAddresses = addresses.filter((a) => a.type === "shipping"); const content = (
{/* Header */}

{fullName}

{customer.email}

{/* Main column */}
{/* Edit form */} {editing && (

Edit Customer

void handleUpdate(e)} className="space-y-4" >
)} {/* Tags */}

Tags

{(customer.tags ?? []).map((tag) => ( {tag} ))} {(customer.tags ?? []).length === 0 && (

No tags

)}
setNewTag(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleAddTag(); } }} placeholder="Add a tag…" className="h-8 w-40 rounded-md border border-border bg-background px-2.5 text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring" />
{/* Addresses */}

Addresses ({addresses.length})

{/* Address form (create / edit) */} {addressFormOpen && (
void handleAddressSubmit(e)} className="mb-4 space-y-3 rounded-md border border-border bg-muted/30 p-4" >

{addressEditId ? "Edit address" : "Add address"}

{( [ ["firstName", "First name"], ["lastName", "Last name"], ] as const ).map(([field, label]) => (
setAddressForm((f) => ({ ...f, [field]: e.target.value, })) } required className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
))}
setAddressForm((f) => ({ ...f, company: e.target.value, })) } className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, line1: e.target.value })) } required className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, line2: e.target.value })) } className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, city: e.target.value, })) } required className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, state: e.target.value, })) } required className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, postalCode: e.target.value, })) } required className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, country: e.target.value.toUpperCase().slice(0, 2), })) } required maxLength={2} className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 font-mono text-foreground text-sm uppercase focus:outline-none focus:ring-1 focus:ring-ring" />
setAddressForm((f) => ({ ...f, phone: e.target.value, })) } className="w-full rounded-md border border-border bg-background px-2.5 py-1.5 text-foreground text-sm focus:outline-none focus:ring-1 focus:ring-ring" />
)} {addresses.length === 0 && !addressFormOpen ? (

No addresses on file

) : (
{shippingAddresses.length > 0 && (

Shipping

{shippingAddresses.map((addr) => ( openEditAddress(addr)} onDelete={() => { if (window.confirm("Delete this address?")) { deleteAddressMutation.mutate({ params: { customerId: customerId ?? "", addressId: addr.id, }, }); } }} onSetDefault={() => { setDefaultAddressMutation.mutate({ params: { customerId: customerId ?? "", addressId: addr.id, }, }); }} isDeleting={deleteAddressMutation.isPending} isSettingDefault={setDefaultAddressMutation.isPending} /> ))}
)} {billingAddresses.length > 0 && (

Billing

{billingAddresses.map((addr) => ( openEditAddress(addr)} onDelete={() => { if (window.confirm("Delete this address?")) { deleteAddressMutation.mutate({ params: { customerId: customerId ?? "", addressId: addr.id, }, }); } }} onSetDefault={() => { setDefaultAddressMutation.mutate({ params: { customerId: customerId ?? "", addressId: addr.id, }, }); }} isDeleting={deleteAddressMutation.isPending} isSettingDefault={setDefaultAddressMutation.isPending} /> ))}
)}
)}
{/* Metadata */} {customer.metadata && Object.keys(customer.metadata).length > 0 && (

Metadata

{Object.entries(customer.metadata).map(([key, value]) => ( ))}
Key Value
{key} {typeof value === "string" ? value : JSON.stringify(value)}
)}
{/* Sidebar */}
{/* Details */}

Details

Email
{customer.email}
{customer.phone && (
Phone
{customer.phone}
)} {customer.dateOfBirth && (
Date of birth
{formatDate(customer.dateOfBirth)}
)}
Joined
{formatDateTime(customer.createdAt)}
Last updated
{formatDateTime(customer.updatedAt)}
{/* Quick stats */}

Summary

Addresses
{addresses.length}
Customer ID
{customer.id}
); return ; } // ─── AddressCard ────────────────────────────────────────────────────────────── interface AddressCardProps { address: CustomerAddress; customerId: string; onEdit: () => void; onDelete: () => void; onSetDefault: () => void; isDeleting: boolean; isSettingDefault: boolean; } function AddressCard({ address, onEdit, onDelete, onSetDefault, isDeleting, isSettingDefault, }: AddressCardProps) { return (

{address.firstName} {address.lastName}

{address.company && (

{address.company}

)}

{formatAddress(address)}

{address.phone && (

{address.phone}

)}
{address.isDefault ? ( Default ) : ( )}
); }