'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/navigation'; import { useTranslation } from 'react-i18next'; import { apiClient } from '@/infrastructure/api/api-client'; import { Check, Zap } from 'lucide-react'; interface Plan { id: string; name: string; displayName: string; monthlyPriceUsd: number; monthlyPriceCny: number; includedTokensPerMonth: number; overageRateUsdPerMToken: number; maxServers: number; maxUsers: number; maxStandingOrders: number; hardLimitPercent: number; trialDays: number; } interface Subscription { plan: { name: string } | null; status: string; } function formatTokens(n: number) { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(0)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`; return `${n}`; } export default function PlansPage() { const queryClient = useQueryClient(); const router = useRouter(); const { t } = useTranslation('billing'); const [currency, setCurrency] = useState<'USD' | 'CNY'>('USD'); const [selectedPlan, setSelectedPlan] = useState(null); const { data: plans = [] } = useQuery({ queryKey: ['billing', 'plans'], queryFn: () => apiClient('/api/v1/billing/plans'), }); const { data: sub } = useQuery({ queryKey: ['billing', 'subscription'], queryFn: () => apiClient('/api/v1/billing/subscription'), retry: false, }); const currentPlanName = sub?.plan?.name; const upgradeMutation = useMutation({ mutationFn: (planName: string) => apiClient('/api/v1/billing/subscription/upgrade', { method: 'POST', body: { planName, currency }, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['billing'] }); router.push('/billing'); }, }); const handleSelectPlan = (planName: string) => { if (planName === currentPlanName) return; setSelectedPlan(planName); }; const handleConfirm = () => { if (!selectedPlan) return; upgradeMutation.mutate(selectedPlan); }; return (

{t('plans.title')}

{plans.map((plan) => { const isCurrent = plan.name === currentPlanName; const isSelected = plan.name === selectedPlan; const price = currency === 'CNY' ? plan.monthlyPriceCny : plan.monthlyPriceUsd; const symbol = currency === 'CNY' ? '¥' : '$'; const features = t(`plans.features.${plan.name}`, { returnObjects: true }) as string[]; const isEnterprise = plan.name === 'enterprise'; return (
handleSelectPlan(plan.name)} className={`relative rounded-xl border-2 p-6 cursor-pointer transition-all space-y-4 ${ isEnterprise ? 'border-primary' : 'border-border' } ${isSelected ? 'ring-2 ring-primary ring-offset-2' : ''} ${ isCurrent ? 'opacity-70 cursor-default' : 'hover:border-primary/50' }`} > {isEnterprise && (
{t('plans.mostPopular')}
)} {isCurrent && (
{t('plans.current')}
)}

{plan.displayName}

{symbol}{price.toFixed(2)} {t('plans.perMonth')}
{plan.trialDays > 0 && (

{t('plans.freeTrial', { days: plan.trialDays })}

)}
{formatTokens(plan.includedTokensPerMonth)} {t('plans.tokensPerMonth')}
{plan.overageRateUsdPerMToken > 0 && (

{t('plans.overage', { rate: currency === 'CNY' ? `¥${(plan.overageRateUsdPerMToken * 7.2).toFixed(2)}` : `$${plan.overageRateUsdPerMToken.toFixed(2)}`, })}

)}
    {Array.isArray(features) && features.map((f) => (
  • {f}
  • ))}
{!isCurrent && ( )}
); })}
{selectedPlan && (
)} {upgradeMutation.isError && (

{t('plans.changePlanFailed')}

)}
); }