import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { useCountUp } from '@/hooks/use-count-up'; import { useLocale } from '@/hooks/use-locale'; import { cn } from '@/lib/utils'; import { dashboard } from '@/routes'; import { index as connectionsIndex } from '@/routes/settings/connections'; import { checkout } from '@/routes/subscribe'; import { 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 { CheckIcon, FolderIcon, LandmarkIcon, LockIcon, PiggyBankIcon, ReceiptIcon, TrendingUpIcon, UsersIcon, WalletIcon, } from 'lucide-react'; import { useEffect, useState } from 'react'; interface PaywallStats { accountsCount: number; transactionsCount: number; categoriesCount: number; automationRulesCount: number; balancesByCurrency: Record; } interface PaywallPageProps extends SharedData { stats: PaywallStats; canUseFreePlan: boolean; canManageConnectionsForFreePlan: boolean; } function getEquivalentBillingLabel( billingPeriod: string | null, t: typeof __, ): string { if (!billingPeriod) { return t('one-time'); } return t('/month'); } const socialProofs = [ { icon: TrendingUpIcon, highlightKey: '15% more savings', textKey: 'after 3 months with Whisper Money', }, { icon: PiggyBankIcon, highlightKey: '23% better', textKey: 'spending awareness reported', }, { icon: LockIcon, highlightKey: '100% private', textKey: '- we never sell your data', }, { icon: UsersIcon, highlightKey: '1,200+ users', textKey: 'taking control of their finances', }, ]; function SocialProofSlider() { const [currentIndex, setCurrentIndex] = useState(0); useEffect(() => { const interval = setInterval(() => { setCurrentIndex((prev) => (prev + 1) % socialProofs.length); }, 4000); return () => clearInterval(interval); }, []); const currentProof = socialProofs[currentIndex]; const Icon = currentProof.icon; return (

{__(currentProof.highlightKey)} {' '} {__(currentProof.textKey)}

{socialProofs.map((_, index) => (
); } function StatItem({ icon: Icon, value, label, delay = 0, }: { icon: React.ElementType; value: number; label: string; delay?: number; }) { const animatedValue = useCountUp(value, { delay }); return (
{animatedValue} {label}
); } function BalanceDisplay({ balancesByCurrency, }: { balancesByCurrency: Record; }) { const locale = useLocale(); const entries = Object.entries(balancesByCurrency); if (entries.length === 0) { return null; } return (
{entries.map(([currency, amount]) => ( {formatCurrency( Math.abs(amount), currency, locale, 0, 0, )} ))}
{__('Balance')}
); } function FinancialSnapshot({ stats }: { stats: PaywallStats }) { const hasData = stats.accountsCount > 0 || stats.transactionsCount > 0 || stats.categoriesCount > 0; if (!hasData) { return null; } return ( {stats.accountsCount > 0 && ( )} {stats.transactionsCount > 0 && ( )} {stats.categoriesCount > 0 && ( )} {Object.keys(stats.balancesByCurrency).length > 0 && ( )} ); } function FeaturesSection({ features }: { features: string[] }) { return (

{__('Connected banks')}

{__( 'Sync transactions and balances automatically. Forget about manually importing CSVs from your bank.', )}

{features.length > 0 && (
    {features.map((feature) => (
  • {__(feature)}
  • ))}
)}
); } function CompactPlanCard({ plan, isSelected, onSelect, currency, }: { plan: Plan; isSelected: boolean; onSelect: () => void; currency: string; }) { const locale = useLocale(); const savingsPercent = plan.original_price && plan.billing_period === 'year' ? Math.round( ((plan.original_price - plan.price) / plan.original_price) * 100, ) : null; const monthlyEquivalent = plan.billing_period === 'year' ? plan.price / 12 : plan.price; return ( ); } function PricingSection({ planEntries, defaultPlan, currency, canUseFreePlan, canManageConnectionsForFreePlan, }: { planEntries: [string, Plan][]; defaultPlan: string; currency: string; canUseFreePlan: boolean; canManageConnectionsForFreePlan: boolean; }) { const [selectedPlan, setSelectedPlan] = useState(defaultPlan); const [freeButtonVisible, setFreeButtonVisible] = useState(false); const selectedPlanData = planEntries.find( ([key]) => key === selectedPlan, )?.[1]; useEffect(() => { if (!canUseFreePlan) { return; } const timer = setTimeout(() => setFreeButtonVisible(true), 5000); return () => clearTimeout(timer); }, [canUseFreePlan]); return (
{selectedPlanData && ( feature !== 'Connect bank accounts', )} /> )}
{planEntries.map(([key, plan]) => ( setSelectedPlan(key)} currency={currency} /> ))}
{canManageConnectionsForFreePlan && (

{__( 'Want to continue for free? Disconnect all bank connections in Settings.', )}

)} {canUseFreePlan && (
)}
); } export default function Paywall() { const { pricing, stats, canUseFreePlan, canManageConnectionsForFreePlan } = usePage().props; const planEntries = Object.entries(pricing.plans); if (planEntries.length === 0) { return null; } return ( <>
); }