import React from 'react'; import { IUser } from '../../types'; import { Plan, PlanType, basePlans, PLAN_ORDER } from '../../utils/plan'; import { Spinner } from '../ui/Spinner'; import { Check } from '../ui/Check'; import Button from '../widgets/Button'; const formatUSD = (amount: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: amount % 1 === 0 ? 0 : 2, }).format(amount); const CrossIcon = ({ className = '', ...props }: React.SVGProps) => ( ); const FEATURE_ROWS: { label: string; values: Partial>; }[] = [ { label: 'AI Discoverability Audit', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Personalized Action Plan', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'AI-Ready Titles', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'AI-Optimized Product Descriptions', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'AI Q&A Snippets', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'LLM.txt', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Continuous AI Discoverability Monitoring', values: { [PlanType.FREE]: 'Included', [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Included Product Optimizations / month', values: { [PlanType.FREE]: '50', [PlanType.FAST_GROWING]: '100', [PlanType.SCALING]: '1,000', [PlanType.HYPER_SCALING]: '10,000', }, }, { label: 'Cost per Extra Product', values: { [PlanType.FREE]: '$0.50', [PlanType.FAST_GROWING]: '$0.40', [PlanType.SCALING]: '$0.30', [PlanType.HYPER_SCALING]: '$0.20', }, }, { label: 'Brand Voice Controls', values: { [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Bulk Titles, Descriptions and Q&A Update', values: { [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Conversational AI Sales Agent', values: { [PlanType.FAST_GROWING]: 'Included', [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Smart AI Summary', values: { [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Custom Brand Styling', values: { [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Advanced AI Prompting', values: { [PlanType.SCALING]: 'Included', [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Multi-language Content', values: { [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'AI-Ready Product Manuals', values: { [PlanType.HYPER_SCALING]: 'Included', }, }, { label: 'Custom Integrations', values: { [PlanType.HYPER_SCALING]: 'Included', }, }, ]; interface NormalPricingProps { user: IUser | null; isUserLoaded: boolean; hasActiveSub: boolean; isFreeTrialActive: boolean; isCanceled: boolean; currentPlan: Plan | null; checkingOutKey: PlanType | null; onInitializePayment: (priceId: string, planKey: PlanType) => void; } /** * Regular monthly plan grid, shown to non-AppSumo merchants. Owns the feature * matrix and the per-plan CTA/state derivation; the shared subscription state * is supplied by the Pricing page. * * @param {NormalPricingProps} props * @return {JSX.Element} */ export const NormalPricing = ({ user, isUserLoaded, hasActiveSub, isFreeTrialActive, isCanceled, currentPlan, checkingOutKey, onInitializePayment, }: NormalPricingProps): JSX.Element => { const plans = basePlans; const stripeStatusRaw = (user?.stripe_subscription_status || '') .toString() .toLowerCase(); const getCtaLabel = (plan: Plan): string => { if (plan?.enterprise) return 'Contact Sales'; if (plan.key === PlanType.FREE) return 'Included'; if (isCanceled && user?.plan_snapshot === plan.key) { return 'Re-activate Plan'; } if (isFreeTrialActive) { if (plan.key === PlanType.HYPER_SCALING) return 'Current Plan'; if (plan.key === PlanType.FAST_GROWING || plan.key === PlanType.SCALING) return 'Downgrade Plan'; return 'Choose Plan'; } if (hasActiveSub && currentPlan) { const diff = PLAN_ORDER[plan.key] - PLAN_ORDER[currentPlan.key]; if (plan.key === currentPlan.key) return 'Current Plan'; if (diff > 0) return 'Upgrade Plan'; if (diff < 0) return 'Downgrade Plan'; return 'Choose Plan'; } return 'Choose Plan'; }; const isCtaDisabled = (plan: Plan): boolean => { if (!isUserLoaded) return true; if (plan.key === PlanType.FREE) return true; if (isFreeTrialActive && plan.key === PlanType.HYPER_SCALING) return true; if (hasActiveSub && currentPlan && plan.key === currentPlan.key) return true; return false; }; const isCurrentByTrial = (plan: Plan) => { return isFreeTrialActive && plan.key === PlanType.HYPER_SCALING; }; const isCurrentPaid = (plan: Plan) => { return hasActiveSub && !!currentPlan && plan.key === currentPlan.key; }; return (
{plans .filter(plan => !plan.enterprise) .map(plan => { const isPopular = !!plan.popular; const isHyperScaling = plan.key === PlanType.HYPER_SCALING; const isFreePlan = plan.key === PlanType.FREE; const isInactiveHighlighted = ['canceled', 'cancelled', 'expired', 'incomplete_expired'].includes( stripeStatusRaw ) && user?.plan_snapshot === plan.key; const currentByTrial = isCurrentByTrial(plan); const currentPaid = isCurrentPaid(plan); const showCurrentTag = currentByTrial || currentPaid; const ctaLabel = getCtaLabel(plan); const isDisabled = isFreePlan || (isCtaDisabled(plan) && user?.stripe_subscription_status !== 'canceled') || checkingOutKey === plan.key; const isLoadingThisPlan = checkingOutKey === plan.key; let buttonClass = 'mb-6 w-full justify-center rounded-md border px-4 py-2 text-sm font-medium transition-colors '; if (isLoadingThisPlan) { buttonClass += 'border-gray-200 bg-gray-100 text-gray-500 cursor-wait opacity-100'; } else if (isFreePlan) { buttonClass += 'border-gray-200 bg-gray-100 text-gray-500 cursor-not-allowed opacity-100'; } else if (showCurrentTag && ctaLabel !== 'Re-activate Plan') { buttonClass += 'border-emerald-200 bg-emerald-50 text-emerald-700 cursor-not-allowed opacity-100'; } else if (isDisabled) { buttonClass += 'border-gray-200! bg-gray-100! text-gray-500! cursor-not-allowed opacity-100'; } else if ( isHyperScaling && !showCurrentTag && !isInactiveHighlighted ) { buttonClass += 'border-transparent bg-gray-900 text-white hover:bg-gray-800'; } else { buttonClass += 'border-gray-200 bg-white text-gray-900 hover:bg-gray-50'; } return (
{currentByTrial && ( FREE TRIAL )} {!currentByTrial && isPopular && !currentPaid && !isInactiveHighlighted && ( MOST POPULAR )} {!currentByTrial && currentPaid && ( {`CURRENT PLAN`} )} {isInactiveHighlighted && ( {stripeStatusRaw.toUpperCase()} )}

{plan.name}

{plan.blurb}

{isFreePlan ? 'Free' : formatUSD(plan.monthly)} {!isFreePlan && ( /mo )}

    {FEATURE_ROWS.map(row => { const value = row.values[plan.key]; const isIncluded = value !== undefined; const description = value && value !== 'Included' ? ( {row.label}:{' '} {value} ) : ( row.label ); return (
  • {isIncluded ? ( ) : ( )} {description}
  • ); })}
); })}
); }; export default NormalPricing;