From adcb7940192e9724ce64e45ba5ad695e93eee709 Mon Sep 17 00:00:00 2001 From: Barkley Trimsworth Date: Sun, 29 Mar 2026 14:57:50 +0000 Subject: [PATCH] fix(portal): wire up Pay Now button with payment modal The Pay Now button in BillingPayments had no onClick handler, making it non-functional. Added: - showPaymentModal state to control modal visibility - onClick handler that opens the payment modal - PaymentModal component with invoice selection, total calculation, and simulated payment processing with success confirmation Fixes GRO-261 --- .../src/portal/sections/BillingPayments.tsx | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/apps/web/src/portal/sections/BillingPayments.tsx b/apps/web/src/portal/sections/BillingPayments.tsx index 63d1240..5869e1f 100644 --- a/apps/web/src/portal/sections/BillingPayments.tsx +++ b/apps/web/src/portal/sections/BillingPayments.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { CreditCard, Download, DollarSign, Package, Zap, Plus, Trash2 } from "lucide-react"; -import { INVOICES, SAVED_PAYMENT_METHODS, PREPAID_PACKAGES } from "../mockData.js"; +import { INVOICES, SAVED_PAYMENT_METHODS, PREPAID_PACKAGES, Invoice } from "../mockData.js"; interface Props { readOnly: boolean; @@ -16,6 +16,7 @@ export function BillingPayments({ readOnly }: Props) { const [tab, setTab] = useState<"invoices" | "payment" | "packages">("invoices"); const [autopay, setAutopay] = useState(false); const [showTipModal, setShowTipModal] = useState(false); + const [showPaymentModal, setShowPaymentModal] = useState(false); const outstanding = INVOICES.filter(i => i.status === "outstanding"); const totalOutstanding = outstanding.reduce((sum, i) => sum + i.amount, 0); @@ -38,7 +39,10 @@ export function BillingPayments({ readOnly }: Props) { > Add Tip - @@ -199,6 +203,125 @@ export function BillingPayments({ readOnly }: Props) { {showTipModal && !readOnly && ( setShowTipModal(false)} /> )} + + {/* Payment Modal */} + {showPaymentModal && !readOnly && ( + setShowPaymentModal(false)} + /> + )} + + ); +} + +function PaymentModal({ outstanding, totalOutstanding, onClose }: { outstanding: Invoice[]; totalOutstanding: number; onClose: () => void }) { + const [selectedInvoices, setSelectedInvoices] = useState>(new Set(outstanding.map(i => i.id))); + const [isProcessing, setIsProcessing] = useState(false); + const [isComplete, setIsComplete] = useState(false); + + 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); + // Simulate payment processing + await new Promise(resolve => setTimeout(resolve, 1500)); + setIsProcessing(false); + setIsComplete(true); + }; + + const selectedTotal = outstanding.filter(i => selectedInvoices.has(i.id)).reduce((sum, i) => sum + i.amount, 0); + + if (isComplete) { + return ( +
+
+
+ + + +
+

Payment Successful

+

Your payment of ${selectedTotal.toFixed(2)} has been processed. A receipt has been sent to your email.

+ +
+
+ ); + } + + return ( +
+
+
+

Pay Outstanding Balance

+ +
+ +

Select invoices to pay:

+ +
+ {outstanding.map(inv => ( + + ))} +
+ +
+
+ Total + ${selectedTotal.toFixed(2)} +
+
+ +
+ + +
+
); }