+ {/* Outstanding Balance Banner */}
+ {totalPending > 0 && (
+
+
+
Outstanding Balance
+
{formatCents(totalPending)}
+
+ {pending.length} unpaid invoice{pending.length > 1 ? "s" : ""}
+
- )}
-
+
+
+ )}
- {/* Packages */}
-
- Packages
- {packages.length === 0 ? (
- No packages purchased
- ) : (
-
- {packages.map((pkg, index) => (
-
- {pkg.name}
- {pkg.remaining} remaining
-
- ))}
-
- )}
-
+ {/* Tabs */}
+
+ {([
+ { id: "invoices" as const, label: "Invoices", icon: DollarSign },
+ { id: "payment" as const, label: "Payment Methods", icon: CreditCard },
+ { id: "packages" as const, label: "Packages", icon: Package },
+ ]).map(({ id, label, icon: Icon }) => (
+
+ ))}
+
{/* Invoices */}
-
- Invoice History
- {invoices.length === 0 ? (
- No invoices yet
- ) : (
-
- {invoices.map((invoice) => (
-
-
-
- {invoice.description || `Invoice ${invoice.id.slice(0, 8)}`}
-
-
{invoice.date}
+ {tab === "invoices" && (
+
+
+
+
+
+ | Date |
+ Description |
+ Amount |
+ Status |
+ |
+
+
+
+ {invoices.map((inv) => (
+
+ |
+ {new Date(inv.date).toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "numeric",
+ })}
+ |
+
+ {inv.description || `Invoice ${inv.id.slice(0, 8)}`}
+ |
+
+ {formatCents(inv.totalCents)}
+ |
+
+
+ {inv.status.charAt(0).toUpperCase() + inv.status.slice(1)}
+
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+ )}
+
+ {/* Payment Methods */}
+ {tab === "payment" && (
+
+ {paymentMethods.length === 0 ? (
+
No payment methods on file
+ ) : (
+
+ {paymentMethods.map((method) => (
+
+
+
+ {method.brand.toUpperCase()}
+
+
**** {method.last4}
+
+ {method.expiryMonth}/{method.expiryYear}
+
+
+ {!readOnly && (
+
+ )}
-
-
- {formatCents(invoice.totalCents)}
-
-
- {invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
-
+ ))}
+
+ )}
+
+ {/* Autopay */}
+
+
+
+
+
+
+
+
Autopay
+
+ Automatically charge after each appointment
+
- ))}
+ {!readOnly ? (
+
+ ) : (
+
+ {autopay ? "Enabled" : "Disabled"}
+
+ )}
+
- )}
-
+
+ )}
+
+ {/* Packages */}
+ {tab === "packages" && (
+
+ {packages.length === 0 ? (
+
No packages purchased
+ ) : (
+ packages.map((pkg, index) => (
+
+
+ {pkg.name}
+ {pkg.remaining} remaining
+
+
+ ))
+ )}
+
+ )}
+
+ {/* Payment Modal */}
+ {showPaymentModal && (
+
setShowPaymentModal(false)}
+ />
+ )}
+
+ );
+}
+
+function PaymentModal({
+ pending,
+ totalPending: _totalPending,
+ onClose,
+}: {
+ pending: Invoice[];
+ totalPending: number;
+ onClose: () => void;
+}) {
+ const [selectedInvoices, setSelectedInvoices] = useState
>(
+ new Set(pending.map((i) => i.id))
+ );
+ const [isProcessing, setIsProcessing] = useState(false);
+ const [isComplete, setIsComplete] = useState(false);
+
+ const formatCents = (cents: number) =>
+ new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: "USD",
+ }).format(cents / 100);
+
+ const toggleInvoice = (id: string) => {
+ const next = new Set(selectedInvoices);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ setSelectedInvoices(next);
+ };
+
+ const handlePay = async () => {
+ setIsProcessing(true);
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+ setIsProcessing(false);
+ setIsComplete(true);
+ };
+
+ const selectedTotal = pending
+ .filter((i) => selectedInvoices.has(i.id))
+ .reduce((sum, i) => sum + i.totalCents, 0);
+
+ if (isComplete) {
+ return (
+
+
+
+
Payment Successful
+
+ Your payment of {formatCents(selectedTotal)} has been processed. A receipt has been sent to your email.
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
Pay Outstanding Balance
+
+
+
+
Select invoices to pay:
+
+
+ {pending.map((inv) => (
+
+ ))}
+
+
+
+
+ Total
+
+ {formatCents(selectedTotal)}
+
+
+
+
+
+
+
+
+
);
}