import { useState } from 'react' import { Link } from 'react-router-dom' import { CreditCard, AlertCircle, Loader2, ExternalLink, Crown } from 'lucide-react' import { billingApi } from '@/api/billing' import { Button } from '@/components/ui/Button' import { PageMeta } from '@/components/common/PageMeta' import { useBillingStore } from '@/store/billingStore' import { BillingPortalError } from '@/types/billing' import { toast } from '@/lib/toast' import { cn } from '@/lib/utils' function formatDate(value: string | null | undefined): string { if (!value) return '—' return new Date(value).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }) } function statusLabel(status: string): string { switch (status) { case 'trialing': return 'Trialing' case 'active': return 'Active' case 'past_due': return 'Past due' case 'canceled': return 'Canceled' case 'incomplete': return 'Incomplete' case 'complimentary': return 'Complimentary' default: return status } } function statusToneClass(status: string): string { switch (status) { case 'active': case 'complimentary': return 'text-success' case 'trialing': return 'text-info' case 'past_due': case 'incomplete': return 'text-warning' case 'canceled': return 'text-danger' default: return 'text-muted-foreground' } } export function BillingPage() { const subscription = useBillingStore((s) => s.subscription) const planBilling = useBillingStore((s) => s.planBilling) const isLoading = useBillingStore((s) => s.isLoading) const [openingPortal, setOpeningPortal] = useState(false) const status = subscription?.status ?? null const isComplimentary = status === 'complimentary' const isTrialing = status === 'trialing' const isPastDue = status === 'past_due' const isCanceled = status === 'canceled' const handleOpenPortal = async () => { setOpeningPortal(true) try { const { url } = await billingApi.getPortalSession() window.location.href = url } catch (err) { if (err instanceof BillingPortalError) { if (err.code === 'no_stripe_customer') { toast.error('Complete checkout first to access billing portal.') } else { toast.error('Billing portal is not available right now.') } } else { toast.error('Failed to open billing portal.') } setOpeningPortal(false) } } if (isLoading && !subscription) { return (
) } return ( <>
{/* ── Header ─────────────────────────────────────────────────────── */}

Billing

Manage your subscription, payment method, and billing history.

{/* ── Past-due banner ────────────────────────────────────────────── */} {isPastDue && (

Your last payment failed.

Update your payment method to keep access to ResolutionFlow.

)} {/* ── Subscription summary card ──────────────────────────────────── */}
{planBilling?.display_name ?? 'No active plan'}
{subscription && (
{statusLabel(subscription.status)} {subscription.cancel_at_period_end && ' · cancels at period end'}
)}
{subscription?.seat_limit != null && (
Seats
{subscription.seat_limit}
)}
{isCanceled ? 'Ends' : isTrialing ? 'Trial ends' : 'Next renewal'}
{isComplimentary ? '—' : formatDate(subscription?.current_period_end)}
Plan started
{formatDate(subscription?.current_period_start)}
{/* State-specific messaging ------------------------------------ */} {isComplimentary && (
Complimentary Pro — no billing required.
)} {isTrialing && (
Trial ends {formatDate(subscription?.current_period_end)} — pick a plan to continue.
)} {isCanceled && (
Subscription canceled. Reactivate by picking a plan.
)}
{/* ── Actions ────────────────────────────────────────────────────── */} {!isComplimentary && (
{(isTrialing || isCanceled) && ( Pick a plan )} {!isTrialing && !isCanceled && ( Change plan )}
)}
) } export default BillingPage