import { useState } from "react"; import ReactDOM from "react-dom"; import { createCheckoutSession, createCustomTemplateCheckout, } from "../api/client"; import { LITE_MONTHLY_PRICE, LITE_ANNUAL_MONTHLY_PRICE, LITE_CUSTOM_TEMPLATE_COUNT, LITE_AI_EDIT_ALLOWANCE, STANDARD_MONTHLY_PRICE, STANDARD_ANNUAL_MONTHLY_PRICE, STANDARD_AI_EDIT_ALLOWANCE, PRO_MONTHLY_PRICE, PRO_ANNUAL_MONTHLY_PRICE, PRO_AI_EDIT_ALLOWANCE, STANDARD_CUSTOM_TEMPLATE_COUNT, PRO_CUSTOM_TEMPLATE_COUNT, } from "../content/pricingContent"; import { useAuth } from "../hooks/useAuth"; interface Props { open: boolean; onClose: () => void; /** Optional custom heading */ title?: string; /** Optional custom subtext below the heading */ subtitle?: string; } /** * Compact, plan-tiered upgrade modal for the custom-template creation limit. * * Unlike the general UpgradePlanModal, this is purpose-built for the templates * tab: it shows only the plans that are an actual step up for the current user * (free → Lite + Standard + Pro, lite → Standard + Pro, standard → Pro, pro → none) * plus a one-off $5 slot that's always available. No per-video card, and it fits * without scrolling. */ export default function CustomTemplateLimitModal({ open, onClose, title: titleProp, subtitle: subtitleProp, }: Props) { const { user } = useAuth(); const [loadingPlan, setLoadingPlan] = useState(null); const [billingCycle, setBillingCycle] = useState<"monthly" | "annual">("monthly"); const [slotQty, setSlotQty] = useState(1); const SLOT_PRICE = 5; const SLOT_MIN = 1; const SLOT_MAX = 20; if (!open) return null; const plan = user?.plan ?? "free"; const showLite = plan === "free"; const showStandard = plan === "free" || plan === "lite"; const showPro = plan !== "pro"; const handleSubscribe = async (target: "lite" | "standard" | "pro") => { if (!user) { window.location.href = "/pricing"; return; } setLoadingPlan(target); try { const res = await createCheckoutSession({ plan: target, billing_cycle: billingCycle, }); window.location.href = res.data.checkout_url; } catch { setLoadingPlan(null); } }; const handleExtraSlot = async () => { if (!user) { window.location.href = "/pricing"; return; } setLoadingPlan("extra_slot"); try { const res = await createCustomTemplateCheckout(slotQty); if (res.data.checkout_url) window.location.href = res.data.checkout_url; } catch { setLoadingPlan(null); } }; const clampQty = (n: number) => Math.max(SLOT_MIN, Math.min(n, SLOT_MAX)); const planCards = (showLite ? 1 : 0) + (showStandard ? 1 : 0) + (showPro ? 1 : 0); const gridCols = planCards >= 3 ? "sm:grid-cols-3" : planCards >= 2 ? "sm:grid-cols-2" : "sm:grid-cols-1 sm:max-w-sm sm:mx-auto"; return ReactDOM.createPortal(
e.stopPropagation()} >

{titleProp ?? "You've reached your custom-template limit"}

{subtitleProp ?? "Upgrade your plan for more custom templates, or grab a single extra slot."}

{planCards > 0 && ( <> {/* Monthly / Annual toggle */}
Monthly Annual Save 20%
{showLite && ( handleSubscribe("lite")} /> )} {showStandard && ( handleSubscribe("standard")} /> )} {showPro && ( handleSubscribe("pro")} /> )}
)} {/* One-off extra slots — always available, even on Pro. */}

{slotQty > 1 ? `Need ${slotQty} more templates?` : "Just need one more template?"}

Buy custom template slots — one-time $5 each, no subscription.

{/* Ticker counter */}
{slotQty}
{/* Share B2V disabled Share B2V to get 5 videos free */}
, document.body ); } function PlanCard({ name, blurb, monthly, annualMonthly, billingCycle, templateCount, videoCount, aiEditLine, cta, highlight, loading, disabled, onClick, }: { name: string; blurb: string; monthly: number; annualMonthly: number; billingCycle: "monthly" | "annual"; templateCount: number; videoCount: number; aiEditLine?: string; cta: string; highlight?: boolean; loading: boolean; disabled: boolean; onClick: () => void; }) { const isAnnual = billingCycle === "annual"; return (
{highlight && (
Best value
)}

{name}

{blurb}

${isAnnual ? annualMonthly : monthly} /month

{isAnnual ? `billed annually` : <>or ${annualMonthly}/mo billed annually}

  • {templateCount} custom video templates
  • {videoCount} videos / month
  • {aiEditLine}
  • Premium voiceover + cloning
  • Priority support
); } function CheckMark() { return ( ); }