import React, { useState } from 'react'; import { useOutletContext } from 'react-router-dom'; import { CreditCard, Plus, Trash2, Star, X, TrendingUp, DollarSign, ArrowUpRight, ArrowDownRight, Download, Filter, Calendar, Wallet, BarChart3, RefreshCcw, FileSpreadsheet, FileText, ChevronLeft, ChevronRight, Loader2, AlertCircle, CheckCircle, Clock, XCircle, ExternalLink, Eye, } from 'lucide-react'; import { User, Business, PaymentMethod, Customer } from '../types'; import { CUSTOMERS } from '../mockData'; import PaymentSettingsSection from '../components/PaymentSettingsSection'; import TransactionDetailModal from '../components/TransactionDetailModal'; import Portal from '../components/Portal'; import { useTransactions, useTransactionSummary, useStripeBalance, useStripePayouts, useStripeCharges, useExportTransactions, } from '../hooks/useTransactionAnalytics'; import { usePaymentConfig } from '../hooks/usePayments'; import { TransactionFilters } from '../api/payments'; type TabType = 'overview' | 'transactions' | 'payouts' | 'settings'; const Payments: React.FC = () => { const { user: effectiveUser, business } = useOutletContext<{ user: User, business: Business }>(); const isBusiness = effectiveUser.role === 'owner' || effectiveUser.role === 'manager'; const isCustomer = effectiveUser.role === 'customer'; // Tab state const [activeTab, setActiveTab] = useState('overview'); // Filter state const [filters, setFilters] = useState({ status: 'all', transaction_type: 'all', page: 1, page_size: 20, }); const [showFilters, setShowFilters] = useState(false); const [dateRange, setDateRange] = useState<{ start: string; end: string }>({ start: '', end: '' }); // Export modal state const [showExportModal, setShowExportModal] = useState(false); const [exportFormat, setExportFormat] = useState<'csv' | 'xlsx' | 'pdf' | 'quickbooks'>('csv'); // Transaction detail modal state const [selectedTransactionId, setSelectedTransactionId] = useState(null); // Data hooks const { data: paymentConfig } = usePaymentConfig(); const canAcceptPayments = paymentConfig?.can_accept_payments || false; const activeFilters: TransactionFilters = { ...filters, start_date: dateRange.start || undefined, end_date: dateRange.end || undefined, }; const { data: transactions, isLoading: transactionsLoading, refetch: refetchTransactions } = useTransactions(activeFilters); const { data: summary, isLoading: summaryLoading } = useTransactionSummary({ start_date: dateRange.start || undefined, end_date: dateRange.end || undefined, }); const { data: balance, isLoading: balanceLoading } = useStripeBalance(); const { data: payoutsData, isLoading: payoutsLoading } = useStripePayouts(20); const { data: chargesData } = useStripeCharges(10); const exportMutation = useExportTransactions(); // Customer view state (for customer-facing) const [customerProfile, setCustomerProfile] = useState( CUSTOMERS.find(c => c.userId === effectiveUser.id) ); const [isAddCardModalOpen, setIsAddCardModalOpen] = useState(false); // Customer handlers const handleSetDefault = (pmId: string) => { if (!customerProfile) return; const updatedMethods = customerProfile.paymentMethods.map(pm => ({ ...pm, isDefault: pm.id === pmId })); setCustomerProfile({...customerProfile, paymentMethods: updatedMethods }); }; const handleDeleteMethod = (pmId: string) => { if (!customerProfile) return; if (window.confirm("Are you sure you want to delete this payment method?")) { const updatedMethods = customerProfile.paymentMethods.filter(pm => pm.id !== pmId); if (updatedMethods.length > 0 && !updatedMethods.some(pm => pm.isDefault)) { updatedMethods[0].isDefault = true; } setCustomerProfile({...customerProfile, paymentMethods: updatedMethods }); } }; const handleAddCard = (e: React.FormEvent) => { e.preventDefault(); if (!customerProfile) return; const newCard: PaymentMethod = { id: `pm_${Date.now()}`, brand: 'Visa', last4: String(Math.floor(1000 + Math.random() * 9000)), isDefault: customerProfile.paymentMethods.length === 0 }; const updatedMethods = [...customerProfile.paymentMethods, newCard]; setCustomerProfile({...customerProfile, paymentMethods: updatedMethods }); setIsAddCardModalOpen(false); }; // Export handler const handleExport = () => { exportMutation.mutate({ format: exportFormat, start_date: dateRange.start || undefined, end_date: dateRange.end || undefined, }); setShowExportModal(false); }; // Status badge helper const getStatusBadge = (status: string) => { const styles: Record = { succeeded: { bg: 'bg-green-100', text: 'text-green-800', icon: }, pending: { bg: 'bg-yellow-100', text: 'text-yellow-800', icon: }, failed: { bg: 'bg-red-100', text: 'text-red-800', icon: }, refunded: { bg: 'bg-gray-100', text: 'text-gray-800', icon: }, partially_refunded: { bg: 'bg-orange-100', text: 'text-orange-800', icon: }, }; const style = styles[status] || styles.pending; const displayStatus = status.replace('_', ' ').replace(/\b\w/g, (c) => c.toUpperCase()); return ( {style.icon} {displayStatus} ); }; // Format date helper const formatDate = (dateStr: string | number) => { const date = typeof dateStr === 'number' ? new Date(dateStr * 1000) : new Date(dateStr); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }; // Format time helper const formatDateTime = (dateStr: string | number) => { const date = typeof dateStr === 'number' ? new Date(dateStr * 1000) : new Date(dateStr); return date.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); }; // Business Owner/Manager View if (isBusiness) { return (
{/* Header */}

Payments & Analytics

Manage payments and view transaction analytics

{canAcceptPayments && ( )}
{/* Tabs */}
{/* Tab Content */} {activeTab === 'overview' && (
{!canAcceptPayments ? (

Payment Setup Required

Complete your payment setup in the Settings tab to start accepting payments and see analytics.

) : ( <> {/* Summary Cards */}
{/* Total Revenue */}

Total Revenue

{summaryLoading ? ( ) : ( <>

{summary?.net_revenue_display || '$0.00'}

{summary?.total_transactions || 0} transactions

)}
{/* Available Balance */}

Available Balance

{balanceLoading ? ( ) : ( <>

${((balance?.available_total || 0) / 100).toFixed(2)}

${((balance?.pending_total || 0) / 100).toFixed(2)} pending

)}
{/* Success Rate */}

Success Rate

{summaryLoading ? ( ) : ( <>

{summary?.total_transactions ? ((summary.successful_transactions / summary.total_transactions) * 100).toFixed(1) : '0'}%

{summary?.successful_transactions || 0} successful

)}
{/* Average Transaction */}

Avg Transaction

{summaryLoading ? ( ) : ( <>

{summary?.average_transaction_display || '$0.00'}

Platform fees: {summary?.total_fees_display || '$0.00'}

)}
{/* Recent Transactions */}

Recent Transactions

{transactionsLoading ? ( ) : transactions?.results?.length ? ( transactions.results.slice(0, 5).map((txn) => ( setSelectedTransactionId(txn.id)} className="hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors" > )) ) : ( )}
Customer Date Amount Status

{txn.customer_name || 'Unknown'}

{txn.customer_email}

{formatDateTime(txn.created_at)}

{txn.amount_display}

Fee: {txn.fee_display}

{getStatusBadge(txn.status)}
No transactions yet
)}
)} {activeTab === 'transactions' && (
{/* Filters */}
setDateRange({ ...dateRange, start: e.target.value })} className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" placeholder="Start date" /> to setDateRange({ ...dateRange, end: e.target.value })} className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" placeholder="End date" />
{/* Transactions Table */}
{transactionsLoading ? ( ) : transactions?.results?.length ? ( transactions.results.map((txn) => ( setSelectedTransactionId(txn.id)} className="hover:bg-gray-50 dark:hover:bg-gray-700/50 cursor-pointer transition-colors" > )) ) : ( )}
Transaction Customer Date Amount Net Status Action

{txn.stripe_payment_intent_id.slice(0, 18)}...

{txn.transaction_type}

{txn.customer_name || 'Unknown'}

{txn.customer_email}

{formatDateTime(txn.created_at)}

{txn.amount_display}

{txn.transaction_type === 'refund' ? '-' : ''}${(txn.net_amount / 100).toFixed(2)}

{txn.application_fee_amount > 0 && (

-{txn.fee_display} fee

)}
{getStatusBadge(txn.status)}
No transactions found
{/* Pagination */} {transactions && transactions.total_pages > 1 && (

Showing {(filters.page! - 1) * filters.page_size! + 1} to{' '} {Math.min(filters.page! * filters.page_size!, transactions.count)} of {transactions.count}

Page {filters.page} of {transactions.total_pages}
)}
)} {activeTab === 'payouts' && (
{/* Balance Summary */}

Available for Payout

{balanceLoading ? ( ) : (

${((balance?.available_total || 0) / 100).toFixed(2)}

)}
{balance?.available?.map((item, idx) => (
{item.currency.toUpperCase()} {item.amount_display}
))}

Pending

{balanceLoading ? ( ) : (

${((balance?.pending_total || 0) / 100).toFixed(2)}

)}
{balance?.pending?.map((item, idx) => (
{item.currency.toUpperCase()} {item.amount_display}
))}
{/* Payouts List */}

Payout History

{payoutsLoading ? ( ) : payoutsData?.payouts?.length ? ( payoutsData.payouts.map((payout) => ( )) ) : ( )}
Payout ID Amount Status Arrival Date Method

{payout.id}

{payout.amount_display}

{getStatusBadge(payout.status)} {payout.arrival_date ? formatDate(payout.arrival_date) : '-'} {payout.method}
No payouts yet
)} {activeTab === 'settings' && ( )} {/* Transaction Detail Modal */} {selectedTransactionId && ( setSelectedTransactionId(null)} /> )} {/* Export Modal */} {showExportModal && (

Export Transactions

{[ { id: 'csv', label: 'CSV', icon: FileText }, { id: 'xlsx', label: 'Excel', icon: FileSpreadsheet }, { id: 'pdf', label: 'PDF', icon: FileText }, { id: 'quickbooks', label: 'QuickBooks', icon: FileSpreadsheet }, ].map((format) => ( ))}
setDateRange({ ...dateRange, start: e.target.value })} className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" /> to setDateRange({ ...dateRange, end: e.target.value })} className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
)}
); } // Customer View if (isCustomer && customerProfile) { return (

Billing

Manage your payment methods and view invoice history.

{/* Payment Methods */}

Payment Methods

{customerProfile.paymentMethods.length > 0 ? customerProfile.paymentMethods.map((pm) => (

{pm.brand} ending in {pm.last4}

{pm.isDefault && Default}
{!pm.isDefault && ( )}
)) :
No payment methods on file.
}
{/* Invoice History */}

Invoice History

No invoices yet.
{/* Add Card Modal */} {isAddCardModalOpen && (
setIsAddCardModalOpen(false)}>
e.stopPropagation()}>

Add New Card

•••• •••• •••• 4242
{effectiveUser.name}
12 / 2028
•••

This is a simulated form. No real card data is required.

)}
); } return
Access Denied or User not found.
; }; export default Payments;