Update AccountBillingPage.tsx

This commit is contained in:
alorig
2025-12-05 13:22:19 +05:00
parent 1e718105f2
commit 16134f858d

View File

@@ -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>
);