Update AccountBillingPage.tsx
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
CreditCard,
|
||||
Download,
|
||||
@@ -21,21 +21,29 @@ import {
|
||||
getInvoices,
|
||||
getPayments,
|
||||
getCreditBalance,
|
||||
getCreditPackages,
|
||||
getAvailablePaymentMethods,
|
||||
downloadInvoicePDF,
|
||||
type Invoice,
|
||||
type Payment,
|
||||
type CreditBalance,
|
||||
type CreditPackage,
|
||||
type PaymentMethod,
|
||||
} from '../../services/billing.api';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import BillingRecentTransactions from '../../components/billing/BillingRecentTransactions';
|
||||
import PricingTable, { type PricingPlan } from '../../components/ui/pricing-table/PricingTable';
|
||||
|
||||
type TabType = 'overview' | 'invoices' | 'payments';
|
||||
type TabType = 'overview' | 'plans' | 'invoices' | 'payments' | 'methods';
|
||||
|
||||
export default function AccountBillingPage() {
|
||||
const navigate = useNavigate();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
||||
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
||||
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
||||
const [payments, setPayments] = useState<Payment[]>([]);
|
||||
const [creditPackages, setCreditPackages] = useState<CreditPackage[]>([]);
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
@@ -46,15 +54,19 @@ export default function AccountBillingPage() {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [balanceRes, invoicesRes, paymentsRes] = await Promise.all([
|
||||
const [balanceRes, invoicesRes, paymentsRes, packagesRes, methodsRes] = await Promise.all([
|
||||
getCreditBalance(),
|
||||
getInvoices(),
|
||||
getPayments(),
|
||||
getCreditPackages(),
|
||||
getAvailablePaymentMethods(),
|
||||
]);
|
||||
|
||||
setCreditBalance(balanceRes);
|
||||
setInvoices(invoicesRes.results);
|
||||
setPayments(paymentsRes.results);
|
||||
setCreditPackages(packagesRes.results || []);
|
||||
setPaymentMethods(methodsRes.results || []);
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to load billing data');
|
||||
console.error('Billing data load error:', err);
|
||||
@@ -82,11 +94,16 @@ export default function AccountBillingPage() {
|
||||
const getStatusBadge = (status: string) => {
|
||||
const styles: Record<string, { bg: string; text: string; icon: any }> = {
|
||||
paid: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
||||
pending: { bg: 'bg-yellow-100', text: 'text-yellow-800', icon: Clock },
|
||||
failed: { bg: 'bg-red-100', text: 'text-red-800', icon: XCircle },
|
||||
void: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
||||
succeeded: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
||||
completed: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
||||
pending: { bg: 'bg-yellow-100', text: 'text-yellow-800', icon: Clock },
|
||||
pending_approval: { bg: 'bg-blue-100', text: 'text-blue-800', icon: Clock },
|
||||
processing: { bg: 'bg-blue-100', text: 'text-blue-800', icon: Clock },
|
||||
failed: { bg: 'bg-red-100', text: 'text-red-800', icon: XCircle },
|
||||
refunded: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
||||
cancelled: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
||||
void: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
||||
uncollectible: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
||||
};
|
||||
|
||||
const style = styles[status] || styles.pending;
|
||||
@@ -114,11 +131,6 @@ export default function AccountBillingPage() {
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Plans & Billing</h1>
|
||||
{/* Recent Transactions (moved from Credits & Billing overview) */}
|
||||
<div>
|
||||
<BillingRecentTransactions />
|
||||
</div>
|
||||
|
||||
<p className="text-gray-600">Manage your subscription, credits, and billing</p>
|
||||
</div>
|
||||
<Link
|
||||
@@ -142,8 +154,10 @@ export default function AccountBillingPage() {
|
||||
<nav className="flex gap-8">
|
||||
{[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'plans', label: 'Plans & Credits' },
|
||||
{ id: 'invoices', label: 'Invoices' },
|
||||
{ id: 'payments', label: 'Payments' },
|
||||
{ id: 'methods', label: 'Payment Methods' },
|
||||
].map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -164,7 +178,7 @@ export default function AccountBillingPage() {
|
||||
{activeTab === 'overview' && creditBalance && (
|
||||
<div className="space-y-6">
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">Current Balance</h3>
|
||||
@@ -197,6 +211,29 @@ export default function AccountBillingPage() {
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">Credits consumed</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-sm font-medium text-gray-600 dark:text-gray-400">Plan Status</h3>
|
||||
<TrendingUp className="w-5 h-5 text-indigo-600" />
|
||||
</div>
|
||||
<div className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{creditBalance?.plan_credits_per_month ? 'Active Plan' : 'Pay as you go'}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
{creditBalance?.plan_credits_per_month
|
||||
? `${creditBalance.plan_credits_per_month.toLocaleString()} credits per month`
|
||||
: 'Upgrade to a plan for predictable billing'}
|
||||
</p>
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('plans')}
|
||||
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
|
||||
>
|
||||
View plans
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
@@ -239,6 +276,96 @@ export default function AccountBillingPage() {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Transactions */}
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">Recent Transactions</h3>
|
||||
<Link to="/account/usage" className="text-sm text-blue-600 hover:text-blue-700">
|
||||
View usage details
|
||||
</Link>
|
||||
</div>
|
||||
<BillingRecentTransactions />
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Plans Tab */}
|
||||
{activeTab === 'plans' && (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<Card className="p-6 lg:col-span-2">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">Choose a plan</h2>
|
||||
<p className="text-gray-600">Pick the package that fits your team.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate('/account/credits/purchase')}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
Purchase credits
|
||||
</button>
|
||||
</div>
|
||||
{creditPackages.length === 0 ? (
|
||||
<div className="text-center text-gray-600 py-10">
|
||||
<FileText className="w-10 h-10 mx-auto mb-3 text-gray-400" />
|
||||
No packages available yet. Please check back soon.
|
||||
</div>
|
||||
) : (
|
||||
<PricingTable
|
||||
variant="1"
|
||||
plans={creditPackages.map((pkg) => {
|
||||
const plan: PricingPlan = {
|
||||
id: pkg.id,
|
||||
name: pkg.name,
|
||||
price: Number(pkg.price),
|
||||
period: '/one-time',
|
||||
description: pkg.description,
|
||||
features: [
|
||||
`${pkg.credits.toLocaleString()} credits`,
|
||||
pkg.discount_percentage > 0 ? `${pkg.discount_percentage}% discount applied` : 'Standard pricing',
|
||||
'Manual & online payments supported',
|
||||
],
|
||||
highlighted: pkg.is_featured,
|
||||
buttonText: 'Select',
|
||||
};
|
||||
return plan;
|
||||
})}
|
||||
onPlanSelect={() => navigate('/account/credits/purchase')}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 space-y-3">
|
||||
<h3 className="text-lg font-semibold">Current plan</h3>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<div className="text-sm text-gray-600">Plan</div>
|
||||
<div className="text-xl font-semibold">
|
||||
{creditBalance?.plan_credits_per_month ? 'Active Plan' : 'Pay as you go'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm text-gray-600">Monthly allocation</div>
|
||||
<div className="text-2xl font-bold text-blue-600">
|
||||
{creditBalance?.plan_credits_per_month?.toLocaleString() || '—'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t pt-3 text-sm text-gray-600">
|
||||
Remaining this month:{' '}
|
||||
<span className="font-semibold text-gray-900">
|
||||
{creditBalance?.credits_remaining?.toLocaleString() ?? '—'} credits
|
||||
</span>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<Link to="/account/credits/purchase" className="text-blue-600 hover:text-blue-700 text-sm font-medium">
|
||||
Buy more credits
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -370,6 +497,85 @@ export default function AccountBillingPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Payment Methods Tab */}
|
||||
{activeTab === 'methods' && (
|
||||
<div className="space-y-6">
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Available payment methods</h3>
|
||||
<p className="text-sm text-gray-600">Use these options when purchasing credits.</p>
|
||||
</div>
|
||||
<Link
|
||||
to="/account/credits/purchase"
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
||||
>
|
||||
Go to purchase
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{paymentMethods.length === 0 ? (
|
||||
<div className="text-center text-gray-600 py-8">
|
||||
<CreditCard className="w-8 h-8 mx-auto mb-2 text-gray-400" />
|
||||
No payment methods available yet.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{paymentMethods.map((method) => (
|
||||
<div
|
||||
key={method.id || method.type}
|
||||
className="border rounded-lg p-4 bg-white shadow-sm"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<div className="text-sm text-gray-500 uppercase">{method.type}</div>
|
||||
<div className="text-lg font-semibold">{method.display_name || method.name}</div>
|
||||
</div>
|
||||
<span className="text-xs px-2 py-1 bg-green-100 text-green-800 rounded-full">
|
||||
{method.is_enabled ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
{method.instructions && (
|
||||
<p className="text-sm text-gray-600 mb-3">{method.instructions}</p>
|
||||
)}
|
||||
{method.bank_details && (
|
||||
<div className="text-sm text-gray-700 space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Bank</span>
|
||||
<span className="font-mono">{method.bank_details.bank_name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Account</span>
|
||||
<span className="font-mono">{method.bank_details.account_number}</span>
|
||||
</div>
|
||||
{method.bank_details.routing_number && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Routing</span>
|
||||
<span className="font-mono">{method.bank_details.routing_number}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{method.wallet_details && (
|
||||
<div className="text-sm text-gray-700 space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">Wallet</span>
|
||||
<span className="font-mono">{method.wallet_details.wallet_type}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">ID</span>
|
||||
<span className="font-mono break-all">{method.wallet_details.wallet_id}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user