import HeadingSmall from '@/components/heading-small'; import { ProBadge } from '@/components/pro-badge'; import { PlanCard, UpgradeDialog, } from '@/components/subscription/upgrade-dialog'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import AppLayout from '@/layouts/app-layout'; import SettingsLayout from '@/layouts/settings/layout'; import { destroy as revokeConsent, store as storeConsent, } from '@/routes/ai/consent'; import { billing } from '@/routes/settings'; import { portal, refund as refundRoute } from '@/routes/settings/billing'; import { checkout } from '@/routes/subscribe'; import { type BreadcrumbItem, type SharedData } from '@/types'; import { Plan } from '@/types/pricing'; import { formatCurrency } from '@/utils/currency'; import { __ } from '@/utils/i18n'; import { Head, router, usePage } from '@inertiajs/react'; import axios from 'axios'; import { CheckIcon, CreditCardIcon, InfinityIcon, InfoIcon, LandmarkIcon, ShieldCheckIcon, SparklesIcon, ZapIcon, } from 'lucide-react'; import { useState } from 'react'; import { toast } from 'sonner'; const breadcrumbs: BreadcrumbItem[] = [ { title: 'Manage Plan', href: billing().url, }, ]; const benefits = [ { icon: LandmarkIcon, title: __('Connected Bank Accounts'), description: __( 'Automatically sync transactions directly from your bank. No manual imports needed.', ), }, { icon: InfinityIcon, title: __('Unlimited Everything'), description: __( 'No limits on bank accounts, transactions, or categories.', ), }, { icon: ShieldCheckIcon, title: __('Privacy First'), description: __( 'Your data is never shared with third parties. You are always the owner.', ), }, { icon: SparklesIcon, title: __('Smart Automation'), description: __( 'Automation rules to categorize transactions automatically.', ), }, { icon: CreditCardIcon, title: __('Priority Support'), description: __( 'Get help when you need it with priority email support.', ), }, ]; function BenefitsGrid() { return (
{benefits.map((benefit) => (

{benefit.title}

{benefit.description}

))}
); } function UpgradeSection({ planEntries, defaultPlan, currency, locale, }: { planEntries: [string, Plan][]; defaultPlan: string; currency: string; locale: string; }) { const [selectedPlan, setSelectedPlan] = useState(defaultPlan); const selectedPlanData = planEntries.find( ([key]) => key === selectedPlan, )?.[1]; return (

{__('Choose your billing cycle')}

{planEntries.map(([key, plan]) => ( setSelectedPlan(key)} currency={currency} locale={locale} /> ))}
{selectedPlanData && selectedPlanData.features.length > 0 && (
    {selectedPlanData.features .slice(0, 4) .map((feature) => (
  • {__(feature)}
  • ))}
)}
); } interface RefundInfo { canSelfRefund: boolean; deadline: string | null; } function RefundCard({ deadline, locale, }: { deadline: string | null; locale: string; }) { const [confirming, setConfirming] = useState(false); const [processing, setProcessing] = useState(false); const deadlineLabel = deadline ? new Date(deadline).toLocaleDateString(locale, { day: 'numeric', month: 'long', }) : null; const submit = () => { setProcessing(true); router.post( refundRoute.url(), {}, { onFinish: () => setProcessing(false) }, ); }; return (

{__('Money-back guarantee')}

{deadlineLabel ? __( 'Changed your mind? Request a full refund until :date. This cancels your subscription and disconnects your bank accounts — the data you already imported is kept.', { date: deadlineLabel }, ) : __( 'Changed your mind? Request a full refund. This cancels your subscription and disconnects your bank accounts — the data you already imported is kept.', )}

{confirming ? (
) : ( )}
); } function SubscribedSection({ isDemoAccount, defaultPlan, currency, locale, refund, }: { isDemoAccount: boolean; defaultPlan: Plan | undefined; currency: string; locale: string; refund?: RefundInfo; }) { return (
{isDemoAccount && ( {__( 'Billing management is not available on the demo account.', )} )}
{__('Pro Plan Active')} {defaultPlan && ( {`\u2014 ${formatCurrency(defaultPlan.price * 100, currency, locale)}/${defaultPlan.billing_period}`} )}

{__( 'Manage your subscription, update payment methods, or view invoices through the Stripe billing portal.', )}

{!isDemoAccount && ( )}
{refund?.canSelfRefund && ( )}
); } function AiConsentSection({ initialConsent, hasProPlan, onUpgradeNeeded, }: { initialConsent: boolean; hasProPlan: boolean; onUpgradeNeeded: () => void; }) { const [consented, setConsented] = useState(initialConsent); const [saving, setSaving] = useState(false); const handleToggle = async (checked: boolean) => { // Free users can't enable AI directly — it needs a paid plan. Prompt // them to subscribe instead of recording consent (which would silently // lock them behind the paywall on the next navigation). if (checked && !hasProPlan) { onUpgradeNeeded(); return; } setSaving(true); try { if (checked) { await axios.post(storeConsent.url()); } else { await axios.delete(revokeConsent.url()); } setConsented(checked); toast.success( checked ? __('AI categorization enabled') : __('AI categorization disabled'), ); } catch { toast.error(__('Something went wrong.')); } finally { setSaving(false); } }; return (

{__('AI Categorization')}

{__( 'Let AI suggest categories for your transactions automatically.', )}

); } export default function Billing() { const { auth, pricing, locale, hasAiConsent, refund } = usePage< SharedData & { hasAiConsent: boolean; refund?: RefundInfo } >().props; const isDemoAccount = auth?.isDemoAccount ?? false; const hasProPlan = auth?.hasProPlan ?? false; const planEntries = Object.entries(pricing.plans); const defaultPlan = pricing.plans[pricing.defaultPlan]; const [showAiUpgrade, setShowAiUpgrade] = useState(false); return (
{hasProPlan ? ( ) : ( )} setShowAiUpgrade(true)} /> {!hasProPlan && ( )}
); }