From aa878703503e0492222af684fba4c5d5831e0148 Mon Sep 17 00:00:00 2001 From: Goutam Date: Fri, 12 Jun 2026 19:57:23 +0530 Subject: [PATCH] Updated project changes --- src/components/Layout.jsx | 2 +- .../payments/components/OutstandingPanel.jsx | 152 ++++++++++++++++++ src/modules/payments/data/invoices.js | 36 +++++ src/modules/payments/pages/PaymentsPage.jsx | 14 +- 4 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/modules/payments/components/OutstandingPanel.jsx diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index bd90f48..eb5190c 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -209,7 +209,7 @@ const Layout = () => { ]; default: // Customer or Fallback return [ - { to: "/customer/profile", icon: LayoutDashboard, label: "Dashboard" }, + { to: "/portal/profile", icon: LayoutDashboard, label: "Dashboard" }, { to: "/portal/payments", icon: CreditCard, label: "Payments" }, ...commonItems, ]; diff --git a/src/modules/payments/components/OutstandingPanel.jsx b/src/modules/payments/components/OutstandingPanel.jsx new file mode 100644 index 0000000..6f18774 --- /dev/null +++ b/src/modules/payments/components/OutstandingPanel.jsx @@ -0,0 +1,152 @@ +import React, { useMemo } from 'react'; +import { AlertCircle, CalendarClock, FileText, Lock, Loader2 } from 'lucide-react'; +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; +import { SpotlightCard } from '../../../components/SpotlightCard'; +import { formatAmount } from '../data/invoices'; +import { stripeConfig } from '../config/stripeConfig'; +import { startCheckout } from '../services/checkoutService'; + +/** + * Outstanding tab — summarizes everything the customer still owes: a headline + * total-due figure plus the list of unpaid invoices, with overdue ones flagged. + * Reads the same invoice/paid data as the "Make a Payment" tab so the two views + * never disagree. + */ +const OutstandingPanel = ({ invoices, paidIds = [], customerEmail }) => { + const navigate = useNavigate(); + const [redirectingId, setRedirectingId] = useState(null); + + const due = useMemo( + () => + invoices + .filter((i) => !(i.status === 'paid' || paidIds.includes(i.id))) + .sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate)), + [invoices, paidIds] + ); + + // Total owed, assuming all unpaid invoices share a currency (they do here). + const totalCents = due.reduce((sum, i) => sum + (i.amount || 0), 0); + const currency = due[0]?.currency || 'usd'; + + // Midnight today, so an invoice due today is not counted as overdue. + const today = new Date(); + today.setHours(0, 0, 0, 0); + const isOverdue = (inv) => new Date(inv.dueDate) < today; + const overdueCount = due.filter(isOverdue).length; + + const handlePay = async (invoice) => { + if (redirectingId) return; + setRedirectingId(invoice.id); + + if (!stripeConfig.isConfigured) { + navigate(`/payments/demo-checkout?invoice=${encodeURIComponent(invoice.id)}`); + return; + } + + try { + await startCheckout({ invoiceId: invoice.id, customerEmail }); + } catch (err) { + toast.error('Unable to start checkout', { description: err.message }); + setRedirectingId(null); + } + }; + + if (due.length === 0) { + return ( +
+ You have no outstanding balance — everything is paid up. 🎉 +
+ ); + } + + return ( +
+ {/* Total due summary */} + +
+
+

+ Total Outstanding +

+

+ {formatAmount(totalCents, currency, stripeConfig.locale)} +

+

+ Across {due.length} invoice{due.length === 1 ? '' : 's'} + {overdueCount > 0 && ( + + {' '}· {overdueCount} overdue + + )} +

+
+
+ +
+
+
+ + {/* Outstanding invoice list */} +
+ {due.map((inv) => { + const busy = redirectingId === inv.id; + const overdue = isOverdue(inv); + return ( + +
+
+
+ +
+
+
+

{inv.title}

+ + {inv.id} + +
+

+ {inv.description} +

+

+ {overdue && } + {overdue ? 'Overdue · ' : 'Due '} + {new Date(inv.dueDate).toLocaleDateString()} +

+
+
+ +
+ + {formatAmount(inv.amount, inv.currency, stripeConfig.locale)} + + +
+
+
+ ); + })} +
+
+ ); +}; + +export default OutstandingPanel; diff --git a/src/modules/payments/data/invoices.js b/src/modules/payments/data/invoices.js index dcb42c9..30636c8 100644 --- a/src/modules/payments/data/invoices.js +++ b/src/modules/payments/data/invoices.js @@ -35,6 +35,42 @@ export const INVOICES = [ amount: 14900, currency: 'usd', }, + { + id: 'INV-2055', + title: 'Siding Replacement — West Wall', + description: 'Storm-damaged vinyl siding replacement, 3 panels.', + property: '2612 Dunwick Dr, Plano, TX 75023', + dueDate: '2026-07-10', + amount: 95000, + currency: 'usd', + }, + { + id: 'INV-2061', + title: 'Chimney Flashing Repair & Sealant', + description: 'Re-flashed chimney base and applied weatherproof sealant.', + property: '2612 Dunwick Dr, Plano, TX 75023', + dueDate: '2026-05-30', + amount: 42500, + currency: 'usd', + }, + { + id: 'INV-2063', + title: 'Skylight Installation — Living Room', + description: 'Supply and install one energy-efficient skylight unit.', + property: '2612 Dunwick Dr, Plano, TX 75023', + dueDate: '2026-06-28', + amount: 128000, + currency: 'usd', + }, + { + id: 'INV-2067', + title: 'Annual Roof Maintenance Plan', + description: 'Twice-yearly inspection and minor repair coverage.', + property: '2612 Dunwick Dr, Plano, TX 75023', + dueDate: '2026-08-01', + amount: 56000, + currency: 'usd', + }, ]; /** Format an integer cent amount into a localized currency string. */ diff --git a/src/modules/payments/pages/PaymentsPage.jsx b/src/modules/payments/pages/PaymentsPage.jsx index 412d1d4..785c23a 100644 --- a/src/modules/payments/pages/PaymentsPage.jsx +++ b/src/modules/payments/pages/PaymentsPage.jsx @@ -4,6 +4,7 @@ import { useAuth } from '../../../context/AuthContext'; import { usePayments } from '../context/PaymentProvider'; import { stripeConfig } from '../config/stripeConfig'; import InvoiceList from '../components/InvoiceList'; +import OutstandingPanel from '../components/OutstandingPanel'; import TransactionHistory from '../components/TransactionHistory'; import StripeConfigNotice from '../components/StripeConfigNotice'; @@ -58,18 +59,27 @@ const PaymentsPage = () => { {/* Tabs */}
+
{/* Content */}
- {activeTab === 'pay' ? ( + {activeTab === 'pay' && ( - ) : ( + )} + {activeTab === 'outstanding' && ( + + )} + {activeTab === 'history' && ( )}