import React from 'react'; import { Loader2Icon } from 'lucide-react'; import { classNames } from '../../lib/utils'; import { Button } from '../ui/button'; import type { IPlanSelection } from '../../service/user/user.interface'; import { PlanPeriod } from '../../utils/plan'; import { formatCount, formatUSD, limitWords, stopBillNote, stopMonthlyPrice, type PricingModel, type PricingPlan, } from '../../utils/pricingModel'; import { voucherAppliesTo, voucherDurationNote, voucherLabel, voucherMonthlyPrice, type AppliedVoucher, } from '../../utils/voucher'; import { PlanDots } from './PlanDots'; /** What the chosen plan's button says and whether it is live. */ export interface PlanSelectorCta { label: string; disabled: boolean; loading: boolean; /** The plan is the account's current one. */ current: boolean; } interface PlanSelectorProps { model: PricingModel; planIndex: number; period: PlanPeriod; onPeriodChange: (period: PlanPeriod) => void; /** Agents ticked on the per-agent plan. */ pickedAgents: string[]; onPickedAgentsChange: (agents: string[]) => void; /** Tapping an agent on Free jumps to the per-agent plan with it ticked. */ onJumpToStarter: (agent: string) => void; cta: PlanSelectorCta; /** The merchant is on the launch trial of this very plan: the launch price shows. */ launchMonthly: number | null; launchMonths: number; /** The list price the launch trial reverts to on this plan; the catalog's when null. */ launchListMonthly: number | null; /** A priced voucher applied on the page; the price shows it when it names this plan. */ voucher: AppliedVoucher | null; onCheckout: (selection: IPlanSelection) => void; } const LIMIT_PREFIX = 'up to '; /** * The one plan the slider stopped on, the way recomaze.ai/pricing shows it: * price and period on the left, how hard each agent runs on the right, the * agent picker and the plan's limits below. */ export const PlanSelector = ({ model, planIndex, period, onPeriodChange, pickedAgents, onPickedAgentsChange, onJumpToStarter, cta, launchMonthly, launchMonths, launchListMonthly, voucher, onCheckout, }: PlanSelectorProps): JSX.Element => { const plan: PricingPlan = model.plans[planIndex]; const isFree = !plan.soldOn[PlanPeriod.MONTHLY] && !plan.soldOn[PlanPeriod.YEARLY]; const isStarter = plan.perAgent; const shownPeriod = plan.soldOn[period] || isFree ? period : PlanPeriod.MONTHLY; const isYearly = shownPeriod === PlanPeriod.YEARLY; const units = isStarter ? Math.max(pickedAgents.length, 1) : 1; const deal = isStarter && !!plan.bundle && plan.bundle.period === shownPeriod && pickedAgents.length === plan.bundle.units; const price = stopMonthlyPrice(plan, shownPeriod, units); const listMonthly = stopMonthlyPrice(plan, PlanPeriod.MONTHLY, units); const wasPrice = isYearly && price < listMonthly ? listMonthly : deal && plan.bundle ? plan.bundle.was : 0; const credits = isStarter ? plan.credits * units : plan.credits; const showLaunch = launchMonthly !== null && cta.current && !isYearly; const launchRevertsTo: number = launchListMonthly ?? listMonthly; const voucherOn: boolean = !isFree && !cta.current && voucherAppliesTo(voucher, plan.key); const voucherPrice: number = voucherOn && voucher ? voucherMonthlyPrice(price, shownPeriod, voucher) : price; const agentFill = (agentKey: string): number => { if (isFree) return 0; if (isStarter && !deal) return pickedAgents.includes(agentKey) ? plan.fill : 0; return plan.fill; }; const limitRows = (() => { if (isFree) return []; let names: string[]; if (isStarter) { names = model.agents .filter(agent => pickedAgents.includes(agent.key)) .flatMap(agent => agent.solo); } else { const unlocked = new Set(model.agents.flatMap(agent => agent.rows)); names = model.limitRows.filter(row => unlocked.has(row)); } return names .map(row => ({ row, value: model.rowLimits[row]?.[planIndex] ?? '', prefix: model.fixedLimitRows.includes(row) ? '' : LIMIT_PREFIX, })) .filter(limit => limit.value !== ''); })(); const toggleAgent = (agentKey: string): void => { if (isFree) { onJumpToStarter(agentKey); return; } if (!isStarter) return; const on = pickedAgents.includes(agentKey); if (on && pickedAgents.length === 1) return; onPickedAgentsChange( on ? pickedAgents.filter(key => key !== agentKey) : [...pickedAgents, agentKey] ); }; const agentsTitle = isFree ? 'Add an agent to start' : isStarter ? 'Pick your agents, bundled at one price' : 'All six agents, included'; return (

{plan.name}

{(deal || plan.badge || cta.current) && ( {cta.current ? 'Current plan' : deal ? 'Special offer' : plan.badge} )}

{deal ? 'All six agents, for the price of one plan.' : plan.fit}

{isFree ? 'Free' : formatUSD( showLaunch ? (launchMonthly as number) : voucherPrice )} {!isFree && ( /mo )} {!isFree && !showLaunch && (voucherOn ? price > voucherPrice : wasPrice > price) && ( {formatUSD(voucherOn ? price : wasPrice)}/mo )} {voucherOn && voucher && ( {voucher.code}: {voucherLabel(voucher)} )} {showLaunch && ( {formatUSD(launchRevertsTo)}/mo )}

{showLaunch ? `Your launch price for the first ${launchMonths} months, then ${formatUSD(launchRevertsTo)}/mo.` : isFree ? plan.billMonthly : voucherOn && voucher ? `${voucherLabel(voucher)} ${voucherDurationNote(voucher)}. ${stopBillNote(plan, shownPeriod, units, deal)}` : stopBillNote(plan, shownPeriod, units, deal)}

{!isFree && (
{[ { value: PlanPeriod.YEARLY, label: 'Pay annually', note: 'Best value', }, { value: PlanPeriod.MONTHLY, label: 'Pay monthly', note: 'Cancel any month', }, ].map(option => ( ))}
)}
{deal && plan.bundle && (
Congrats! You unlocked a special offer! All six agents for {formatUSD(plan.bundle.monthly)} a month on the annual plan, instead of {formatUSD(plan.bundle.was)}.
)}

A month on this plan

{plan.month}

Audit and Plan
{model.agents.map(agent => { const filled = agentFill(agent.key); return (
{agent.name}
); })}
AI agents volume work Audits {' '} Not on this plan

{agentsTitle}

{model.agents.map(agent => { const locked = !isFree && !isStarter; const checked = isFree ? false : locked ? true : pickedAgents.includes(agent.key); return ( ); })} {isStarter && !deal && plan.bundle && ( )}

{isFree ? 'What you get, free' : 'What the plan does'}

  • AI HQ and Company AI Brain analyze your business and propose what to do next
  • {isFree ? (
  • Fix Pack: every failed check, with the fix, ready to export
  • ) : ( <>
  • Full free audits
  • {limitRows.map(limit => (
  • {limit.prefix} {limit.value} {' '} {limitWords( model.limitLabels[limit.row] ?? limit.row, limit.value )}
  • ))}
  • Unlimited seats
  • )}

{formatCount(credits)} AI credits {' '} {isFree ? 'a month to test everything' : isStarter ? `a month, ${formatCount(plan.credits)} per agent you pick` : 'a month'}

All limits are per month. "Up to" means the most the plan does; every job shows its cost before it runs.

); };