import { useEffect, useState } from "react"; import { Link, Navigate, useNavigate, useSearchParams } from "react-router-dom"; import { useAuth } from "../auth/AuthContext"; import { PageHeader } from "../components/layout/PageHeader"; import { Button, Card, Input } from "../components/ui"; import { useRepos } from "../data/DataContext"; import { useI18n } from "../i18n/I18nContext"; import { getPlanRights, planCtaLabel } from "../lib/planRights"; import { PLANS, type PlanId } from "../lib/usageMeter"; function isPlanId(v: string | null): v is PlanId { return v === "free" || v === "starter" || v === "pro"; } /** * 結帳:訂單摘要 + 權益 + 一鍵完成(mock 付款與生效合一)。 */ export function PlanCheckoutPage() { const repos = useRepos(); const { member } = useAuth(); const { t, formatPlanPrice } = useI18n(); const navigate = useNavigate(); const [params] = useSearchParams(); const planParam = params.get("plan"); const planId: PlanId | null = isPlanId(planParam) ? planParam : null; const plan = planId ? PLANS[planId] : null; const rights = planId ? getPlanRights(planId, t) : null; const [currentId, setCurrentId] = useState("free"); const [cardName, setCardName] = useState(member?.display_name || ""); const [cardLast4, setCardLast4] = useState("4242"); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); useEffect(() => { void repos.usage.getPlanId().then(setCurrentId); }, [repos.usage]); const free = plan?.price_twd === 0; const already = planId != null && currentId === planId; if (!member) return ; if (!plan || !planId || !rights) { return ( <>

{t("checkout.pickFirst")}

); } async function submit() { if (!planId || already) return; setError(""); if (!free) { const digits = cardLast4.replace(/\D/g, ""); if (digits.length < 4) { setError(t("checkout.cardLast4Required")); return; } if (!cardName.trim()) { setError(t("checkout.cardNameRequired")); return; } } setBusy(true); try { await repos.usage.purchasePlan(planId, { mock_ref: free ? "free" : `****${cardLast4.replace(/\D/g, "").slice(-4)}`, }); navigate("/app/usage", { replace: true, state: { purchaseOk: planId }, }); } catch (e) { setError(e instanceof Error ? e.message : t("checkout.fail")); } finally { setBusy(false); } } const actionLabel = free ? t("checkout.confirmFree") : t("checkout.payAndAction", { action: planCtaLabel(currentId, planId, t), price: formatPlanPrice(plan.price_twd), }); return ( <> {error ? (

{error}

) : null}

{t("checkout.subscribe")}

{plan.name}

{rights.headline}

{formatPlanPrice(plan.price_twd)} {t("checkout.perMonth")}

{t("checkout.monthlyCredits", { n: plan.monthly_credits })}

{t("checkout.youGet")}

    {rights.rights.map((line) => (
  • {line}
  • ))}

{t("checkout.quota")}

    {rights.quota.map((line) => (
  • {line}
  • ))}

{t("checkout.notes")}

    {rights.notes.map((line) => (
  • {line}
  • ))}
{!free && !already ? (
setCardName(e.target.value)} autoComplete="cc-name" disabled={busy} /> setCardLast4(e.target.value.replace(/\D/g, "").slice(0, 4))} inputMode="numeric" maxLength={4} placeholder="4242" disabled={busy} />
) : null}
); }