/** * Plans & Billing Page - Unified Subscription & Payment Management * Comprehensive dashboard for all billing-related features * Rich, actionable, data-driven UX following UsageDashboard patterns */ import { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { CreditCardIcon, TrendingUpIcon, FileTextIcon, WalletIcon, ArrowUpIcon, Loader2Icon, AlertCircleIcon, CheckCircleIcon, DownloadIcon, ZapIcon, GlobeIcon, UsersIcon, XIcon, CalendarIcon, RefreshCwIcon, ChevronRightIcon, PlusIcon, Building2Icon, TagIcon, LockIcon, ShootingStarIcon, } from '../../icons'; import { Card } from '../../components/ui/card'; import Badge from '../../components/ui/badge/Badge'; import Button from '../../components/ui/button/Button'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { usePageLoading } from '../../context/PageLoadingContext'; import { formatCurrency } from '../../utils'; import { getCreditBalance, getCreditPackages, getInvoices, getAvailablePaymentMethods, purchaseCreditPackage, downloadInvoicePDF, getPayments, type CreditBalance, type CreditPackage, type Invoice, type PaymentMethod, type Payment, getPlans, getSubscriptions, createSubscription, cancelSubscription, type Plan, type Subscription, // Payment gateway methods subscribeToPlan, purchaseCredits, openStripeBillingPortal, getAvailablePaymentGateways, type PaymentGateway, } from '../../services/billing.api'; import { useAuthStore } from '../../store/authStore'; export default function PlansAndBillingPage() { const { startLoading, stopLoading } = usePageLoading(); const toast = useToast(); const hasLoaded = useRef(false); const { user } = useAuthStore.getState(); const isAwsAdmin = user?.account?.slug === 'aws-admin'; // UI States const [error, setError] = useState(''); const [planLoadingId, setPlanLoadingId] = useState(null); const [purchaseLoadingId, setPurchaseLoadingId] = useState(null); const [showCancelConfirm, setShowCancelConfirm] = useState(false); const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [selectedBillingCycle, setSelectedBillingCycle] = useState<'monthly' | 'annual'>('monthly'); const [selectedGateway, setSelectedGateway] = useState('stripe'); // Data States const [creditBalance, setCreditBalance] = useState(null); const [packages, setPackages] = useState([]); const [invoices, setInvoices] = useState([]); const [payments, setPayments] = useState([]); const [paymentMethods, setPaymentMethods] = useState([]); const [plans, setPlans] = useState([]); const [subscriptions, setSubscriptions] = useState([]); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(undefined); const [availableGateways, setAvailableGateways] = useState<{ stripe: boolean; paypal: boolean; manual: boolean }>({ stripe: false, paypal: false, manual: true, }); useEffect(() => { if (hasLoaded.current) return; hasLoaded.current = true; loadData(); // Handle payment gateway return URLs const params = new URLSearchParams(window.location.search); const success = params.get('success'); const canceled = params.get('canceled'); const purchase = params.get('purchase'); if (success === 'true') { toast?.success?.('Subscription activated successfully!'); // Clean up URL window.history.replaceState({}, '', window.location.pathname); } else if (canceled === 'true') { toast?.info?.('Payment was cancelled'); window.history.replaceState({}, '', window.location.pathname); } else if (purchase === 'success') { toast?.success?.('Credits purchased successfully!'); window.history.replaceState({}, '', window.location.pathname); } else if (purchase === 'canceled') { toast?.info?.('Credit purchase was cancelled'); window.history.replaceState({}, '', window.location.pathname); } }, []); const handleError = (err: any, fallback: string) => { const message = err?.message || fallback; setError(message); toast?.error?.(message); }; const loadData = async (allowRetry = true) => { try { startLoading('Loading billing data...'); const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); const balanceData = await getCreditBalance(); const packagesPromise = getCreditPackages(); const invoicesPromise = getInvoices({}); const paymentsPromise = getPayments({}); const methodsPromise = getAvailablePaymentMethods(); const plansData = await getPlans(); await wait(400); let subsData: { results: Subscription[] } = { results: [] }; try { subsData = await getSubscriptions(); } catch (subErr: any) { if (subErr?.status === 429 && allowRetry) { await wait(2500); try { subsData = await getSubscriptions(); } catch { subsData = { results: [] }; } } } const [packagesData, invoicesData, paymentsData, methodsData] = await Promise.all([ packagesPromise, invoicesPromise, paymentsPromise, methodsPromise ]); setCreditBalance(balanceData); setPackages(packagesData.results || []); setInvoices(invoicesData.results || []); setPayments(paymentsData.results || []); const methods = (methodsData.results || []).filter((m) => m.is_enabled !== false); setPaymentMethods(methods); if (methods.length > 0) { const bank = methods.find((m) => m.type === 'bank_transfer'); const manual = methods.find((m) => m.type === 'manual'); const selected = bank || manual || methods.find((m) => m.is_default) || methods[0]; setSelectedPaymentMethod((prev) => prev || selected.type || selected.id); } // Filter plans const activePlans = (plansData.results || []).filter((p) => p.is_active !== false); const filteredPlans = activePlans.filter((p) => { const name = (p.name || '').toLowerCase(); const slug = (p.slug || '').toLowerCase(); const isEnterprise = name.includes('enterprise') || slug === 'enterprise'; return isAwsAdmin ? true : !isEnterprise; }); const accountPlan = user?.account?.plan; if (accountPlan && !filteredPlans.find((p) => p.id === accountPlan.id)) { filteredPlans.push(accountPlan as any); } setPlans(filteredPlans); const subs = subsData.results || []; if (subs.length === 0 && accountPlan) { subs.push({ id: accountPlan.id || 0, plan: accountPlan, status: 'active' } as any); } setSubscriptions(subs); // Load available payment gateways try { const gateways = await getAvailablePaymentGateways(); setAvailableGateways(gateways); // Auto-select first available gateway if (gateways.stripe) { setSelectedGateway('stripe'); } else if (gateways.paypal) { setSelectedGateway('paypal'); } else { setSelectedGateway('manual'); } } catch { // Non-critical - just keep defaults console.log('Could not load payment gateways, using defaults'); } } catch (err: any) { if (err?.status === 429 && allowRetry) { setError('Request was throttled. Retrying...'); setTimeout(() => loadData(false), 2500); } else if (err?.status !== 429) { setError(err.message || 'Failed to load billing data'); } } finally { stopLoading(); } }; const handleSelectPlan = async (planId: number) => { try { setPlanLoadingId(planId); // Use payment gateway integration for Stripe/PayPal if (selectedGateway === 'stripe' || selectedGateway === 'paypal') { const { redirect_url } = await subscribeToPlan(planId.toString(), selectedGateway); window.location.href = redirect_url; return; } // Fall back to manual/bank transfer flow await createSubscription({ plan_id: planId, payment_method: selectedPaymentMethod }); toast?.success?.('Plan upgraded successfully!'); setShowUpgradeModal(false); await loadData(); } catch (err: any) { handleError(err, 'Failed to upgrade plan'); } finally { setPlanLoadingId(null); } }; const handleCancelSubscription = async () => { if (!currentSubscription?.id) return; try { setPlanLoadingId(currentSubscription.id); await cancelSubscription(currentSubscription.id); toast?.success?.('Subscription cancelled'); setShowCancelConfirm(false); await loadData(); } catch (err: any) { handleError(err, 'Failed to cancel subscription'); } finally { setPlanLoadingId(null); } }; const handlePurchaseCredits = async (packageId: number) => { try { setPurchaseLoadingId(packageId); // Use payment gateway integration for Stripe/PayPal if (selectedGateway === 'stripe' || selectedGateway === 'paypal') { const { redirect_url } = await purchaseCredits(packageId.toString(), selectedGateway); window.location.href = redirect_url; return; } // Fall back to manual/bank transfer flow await purchaseCreditPackage({ package_id: packageId, payment_method: selectedPaymentMethod as any || 'manual' }); toast?.success?.('Credits purchased successfully!'); await loadData(); } catch (err: any) { handleError(err, 'Failed to purchase credits'); } finally { setPurchaseLoadingId(null); } }; const handleManageSubscription = async () => { try { const { portal_url } = await openStripeBillingPortal(); window.location.href = portal_url; } catch (err: any) { handleError(err, 'Failed to open billing portal'); } }; const handleDownloadInvoice = async (invoiceId: number) => { try { const blob = await downloadInvoicePDF(invoiceId); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `invoice-${invoiceId}.pdf`; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); } catch (err: any) { handleError(err, 'Failed to download invoice'); } }; // Computed values const currentSubscription = subscriptions.find((sub) => sub.status === 'active') || subscriptions[0]; const currentPlanId = typeof currentSubscription?.plan === 'object' ? currentSubscription.plan.id : currentSubscription?.plan; const accountPlanId = user?.account?.plan?.id; const effectivePlanId = currentPlanId || accountPlanId; const currentPlan = plans.find((p) => p.id === effectivePlanId) || user?.account?.plan; const hasActivePlan = Boolean(effectivePlanId); const hasPendingPayment = payments.some((p) => p.status === 'pending_approval'); // Credit usage percentage const creditUsage = creditBalance && creditBalance.plan_credits_per_month > 0 ? Math.round((creditBalance.credits_used_this_month / creditBalance.plan_credits_per_month) * 100) : 0; // Upgrade plans (exclude current and free) const upgradePlans = plans.filter(p => { const price = Number(p.price) || 0; return price > 0 && p.id !== effectivePlanId; }).sort((a, b) => (Number(a.price) || 0) - (Number(b.price) || 0)); return ( <> , color: 'blue' }} actions={ } /> {/* Alerts */} {!hasActivePlan && (

No Active Plan

Choose a plan below to activate your account and unlock all features.

)} {hasPendingPayment && (

Payment Pending Review

Your payment is being processed. Credits will be added once approved.

)} {error && (

{error}

)}
{/* SECTION 1: Current Plan Hero */}
{/* Main Plan Card */}

{currentPlan?.name || 'No Plan'}

{hasActivePlan ? 'Active' : 'Inactive'}

{currentPlan?.description || 'Select a plan to unlock features'}

{availableGateways.stripe && hasActivePlan && ( )}
{/* Quick Stats */}
Credits
{creditBalance?.credits?.toLocaleString() || 0}
Available now
Used
{creditBalance?.credits_used_this_month?.toLocaleString() || 0}
This month ({creditUsage}%)
Renews
{currentSubscription?.current_period_end ? new Date(currentSubscription.current_period_end).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : '—'}
Next billing
Monthly
${Number(currentPlan?.price || 0).toFixed(0)}
Per month
{/* Credit Usage Bar */}
Credit Usage {creditBalance?.credits_used_this_month?.toLocaleString() || 0} / {creditBalance?.plan_credits_per_month?.toLocaleString() || 0}
= 90 ? 'bg-error-500' : creditUsage >= 70 ? 'bg-warning-500' : 'bg-gradient-to-r from-brand-500 to-purple-500' }`} style={{ width: `${Math.min(creditUsage, 100)}%` }} />
{/* Plan Limits Summary */}

Plan Limits

View Usage
Sites
{currentPlan?.max_sites === 9999 ? '∞' : currentPlan?.max_sites || 0}
Team Members
{currentPlan?.max_users === 9999 ? '∞' : currentPlan?.max_users || 0}
Keywords
{currentPlan?.max_keywords ? currentPlan.max_keywords.toLocaleString() : 0}
Monthly Credits
{currentPlan?.included_credits?.toLocaleString() || 0}
{hasActivePlan && ( )}
{/* SECTION 2: Buy Credits + Quick Upgrade */}
{/* Buy Additional Credits */}

Buy Credits

Top up your credit balance

{/* Compact Payment Gateway Selector for Credits */} {(availableGateways.stripe || availableGateways.paypal) && (
{availableGateways.stripe && ( )} {availableGateways.paypal && ( )}
)}
{packages.slice(0, 4).map((pkg) => ( ))}
{/* Quick Upgrade Options */}

Upgrade Plan

Get more features & credits

{upgradePlans.slice(0, 3).map((plan) => (
{plan.name}
{plan.included_credits?.toLocaleString() || 0} credits/mo
${plan.price}/mo
))} {upgradePlans.length === 0 && (

You're on the best plan!

)}
{/* SECTION 3: Billing History */}

Billing History

Recent invoices and transactions

{invoices.length === 0 ? ( ) : ( invoices.slice(0, 5).map((invoice) => ( )) )}
Invoice Date Amount Status Actions

No invoices yet

Your billing history will appear here

{invoice.invoice_number} {new Date(invoice.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} {formatCurrency(invoice.total_amount, invoice.currency)} {invoice.status}
{/* SECTION 4: Payment Methods */}

Payment Methods

Manage how you pay

{paymentMethods.map((method) => (
{method.type === 'bank_transfer' ? ( ) : ( )}
{method.display_name}
{method.type?.replace('_', ' ')}
{method.is_default && ( Default )}
{selectedPaymentMethod !== method.type && ( )} {selectedPaymentMethod === method.type && (
Selected for payment
)}
))} {paymentMethods.length === 0 && (
No payment methods available
)}
{/* Upgrade Modal */} {showUpgradeModal && (

Choose Your Plan

Select the plan that fits your needs

{/* Billing Toggle & Payment Gateway */}
{/* Billing Cycle Toggle */}
{/* Payment Gateway Selector */} {(availableGateways.stripe || availableGateways.paypal) && (
{availableGateways.stripe && ( )} {availableGateways.paypal && ( )} {availableGateways.manual && ( )}
)}
{/* Plans Grid */}
{upgradePlans.map((plan, index) => { const isPopular = index === 1; const planPrice = Number(plan.price) || 0; const annualPrice = planPrice * 0.8 * 12; const displayPrice = selectedBillingCycle === 'annual' ? (annualPrice / 12).toFixed(0) : planPrice; return (
{isPopular && (
Most Popular
)}

{plan.name}

${displayPrice} /mo
{selectedBillingCycle === 'annual' && (
${annualPrice.toFixed(0)} billed annually
)}
  • {plan.included_credits?.toLocaleString() || 0} credits/month
  • {plan.max_sites === 9999 ? 'Unlimited' : plan.max_sites} sites
  • {plan.max_users === 9999 ? 'Unlimited' : plan.max_users} team members
  • {plan.max_keywords?.toLocaleString() || 0} keywords
); })}
{/* Policy Info */}
Flexible billing: Upgrades take effect immediately with prorated billing. Downgrades apply at the end of your billing period. Cancel anytime with no penalties.
)} {/* Cancel Confirmation Modal */} {showCancelConfirm && (

Cancel Subscription

Are you sure?

Your subscription will remain active until the end of your billing period.

  • You'll lose access to premium features
  • Remaining credits preserved for 30 days
  • Resubscribe anytime to restore access
)} ); }