Update AccountBillingPage.tsx
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
CreditCard,
|
CreditCard,
|
||||||
Download,
|
Download,
|
||||||
@@ -21,21 +21,29 @@ import {
|
|||||||
getInvoices,
|
getInvoices,
|
||||||
getPayments,
|
getPayments,
|
||||||
getCreditBalance,
|
getCreditBalance,
|
||||||
|
getCreditPackages,
|
||||||
|
getAvailablePaymentMethods,
|
||||||
downloadInvoicePDF,
|
downloadInvoicePDF,
|
||||||
type Invoice,
|
type Invoice,
|
||||||
type Payment,
|
type Payment,
|
||||||
type CreditBalance,
|
type CreditBalance,
|
||||||
|
type CreditPackage,
|
||||||
|
type PaymentMethod,
|
||||||
} from '../../services/billing.api';
|
} from '../../services/billing.api';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import BillingRecentTransactions from '../../components/billing/BillingRecentTransactions';
|
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() {
|
export default function AccountBillingPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
||||||
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
||||||
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
||||||
const [payments, setPayments] = useState<Payment[]>([]);
|
const [payments, setPayments] = useState<Payment[]>([]);
|
||||||
|
const [creditPackages, setCreditPackages] = useState<CreditPackage[]>([]);
|
||||||
|
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
|
||||||
@@ -46,15 +54,19 @@ export default function AccountBillingPage() {
|
|||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const [balanceRes, invoicesRes, paymentsRes] = await Promise.all([
|
const [balanceRes, invoicesRes, paymentsRes, packagesRes, methodsRes] = await Promise.all([
|
||||||
getCreditBalance(),
|
getCreditBalance(),
|
||||||
getInvoices(),
|
getInvoices(),
|
||||||
getPayments(),
|
getPayments(),
|
||||||
|
getCreditPackages(),
|
||||||
|
getAvailablePaymentMethods(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setCreditBalance(balanceRes);
|
setCreditBalance(balanceRes);
|
||||||
setInvoices(invoicesRes.results);
|
setInvoices(invoicesRes.results);
|
||||||
setPayments(paymentsRes.results);
|
setPayments(paymentsRes.results);
|
||||||
|
setCreditPackages(packagesRes.results || []);
|
||||||
|
setPaymentMethods(methodsRes.results || []);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message || 'Failed to load billing data');
|
setError(err.message || 'Failed to load billing data');
|
||||||
console.error('Billing data load error:', err);
|
console.error('Billing data load error:', err);
|
||||||
@@ -82,11 +94,16 @@ export default function AccountBillingPage() {
|
|||||||
const getStatusBadge = (status: string) => {
|
const getStatusBadge = (status: string) => {
|
||||||
const styles: Record<string, { bg: string; text: string; icon: any }> = {
|
const styles: Record<string, { bg: string; text: string; icon: any }> = {
|
||||||
paid: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
paid: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
||||||
pending: { bg: 'bg-yellow-100', text: 'text-yellow-800', icon: Clock },
|
succeeded: { bg: 'bg-green-100', text: 'text-green-800', icon: CheckCircle },
|
||||||
failed: { bg: 'bg-red-100', text: 'text-red-800', icon: XCircle },
|
|
||||||
void: { bg: 'bg-gray-100', text: 'text-gray-800', icon: XCircle },
|
|
||||||
completed: { 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 },
|
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;
|
const style = styles[status] || styles.pending;
|
||||||
@@ -114,11 +131,6 @@ export default function AccountBillingPage() {
|
|||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold">Plans & Billing</h1>
|
<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>
|
<p className="text-gray-600">Manage your subscription, credits, and billing</p>
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
@@ -142,8 +154,10 @@ export default function AccountBillingPage() {
|
|||||||
<nav className="flex gap-8">
|
<nav className="flex gap-8">
|
||||||
{[
|
{[
|
||||||
{ id: 'overview', label: 'Overview' },
|
{ id: 'overview', label: 'Overview' },
|
||||||
|
{ id: 'plans', label: 'Plans & Credits' },
|
||||||
{ id: 'invoices', label: 'Invoices' },
|
{ id: 'invoices', label: 'Invoices' },
|
||||||
{ id: 'payments', label: 'Payments' },
|
{ id: 'payments', label: 'Payments' },
|
||||||
|
{ id: 'methods', label: 'Payment Methods' },
|
||||||
].map((tab) => (
|
].map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
@@ -164,7 +178,7 @@ export default function AccountBillingPage() {
|
|||||||
{activeTab === 'overview' && creditBalance && (
|
{activeTab === 'overview' && creditBalance && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Stats Cards */}
|
{/* 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">
|
<Card className="p-6">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<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>
|
<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>
|
</div>
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">Credits consumed</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">Credits consumed</p>
|
||||||
</Card>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
@@ -239,6 +276,96 @@ export default function AccountBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -370,6 +497,85 @@ export default function AccountBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user