'use client'; import { useCallback, useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import DownloadCTA from './DownloadCTA'; import { emit, events } from '../_lib/event-taxonomy'; import styles from './Pricing.module.css'; type BillingPeriod = 'monthly' | 'annual'; type TierId = 'SOLO' | 'TEAMS'; interface TierDef { readonly id: TierId; readonly nsKey: 'solo' | 'teams'; readonly highlighted: boolean; readonly bulletKeys: readonly string[]; readonly ctaType: 'download' | 'stripe'; } /** * Tier content mirrors `packages/shared/src/tiers.ts` (the canonical tier * system): SOLO is free forever with full memory + Harvest, unlimited * workspaces, marketplace, all connectors, and BYO cloud models; TEAMS adds * shared workspaces, WaggleDance, and governance. No bullets beyond what * tiers.ts encodes. The 15-day Team trial lives in the section subhead — * it applies to every install, so listing it as a Solo feature misreads. */ const TIER_DEFS: readonly TierDef[] = [ { id: 'SOLO', nsKey: 'solo', highlighted: false, bulletKeys: [ 'bullet_memory', 'bullet_workspaces', 'bullet_marketplace', 'bullet_models', 'bullet_skills', ], ctaType: 'download', }, { id: 'TEAMS', nsKey: 'teams', highlighted: true, bulletKeys: [ 'bullet_everything_solo', 'bullet_shared', 'bullet_dance', 'bullet_governance', ], ctaType: 'stripe', }, ]; const STRIPE_ENDPOINT = (process.env.NEXT_PUBLIC_API_URL ?? '').replace(/\/$/, '') + '/api/stripe/checkout'; const KVARK_URL = 'https://www.kvark.ai'; export default function Pricing() { const t = useTranslations('landing.pricing'); const [billing, setBilling] = useState('monthly'); const [checkoutCancelled, setCheckoutCancelled] = useState(false); useEffect(() => { const params = new URLSearchParams(window.location.search); setCheckoutCancelled(params.get('checkout') === 'cancelled'); }, []); const handleBillingChange = useCallback((mode: BillingPeriod) => { setBilling(mode); emit({ name: events.pricingBillingToggle, properties: { mode } }); }, []); const handleStripeCtaClick = useCallback( (tier: TierId) => { emit({ name: events.ctaClick, properties: { section: 'pricing', tier, billing }, }); }, [billing], ); return (

{t('eyebrow')}

{t('headline')}

{t('subhead')}

{checkoutCancelled ? (
{t('notices.cancelled')} handleStripeCtaClick('TEAMS')} className={styles.noticeLink} > {t('notices.retry')}
) : null}
{TIER_DEFS.map((tier) => { const priceKey = billing === 'monthly' ? 'price_monthly' : 'price_annual'; const cardClass = [ styles.tierCard, tier.highlighted ? styles.tierCardHighlighted : '', ] .filter(Boolean) .join(' '); return (
{tier.highlighted ? ( {t('popular_badge')} ) : null}

{t(`tiers.${tier.nsKey}.name`)}

{t(`tiers.${tier.nsKey}.tagline`)}

{t(`tiers.${tier.nsKey}.${priceKey}`)}

{t(`tiers.${tier.nsKey}.note`)}

    {tier.bulletKeys.map((bk) => (
  • {t(`tiers.${tier.nsKey}.${bk}`)}
  • ))}
{tier.ctaType === 'download' ? ( ) : ( handleStripeCtaClick(tier.id)} className={[ 'btn', tier.highlighted ? 'btn-primary' : 'btn-ghost', styles.tierCta, ].join(' ')} > {t(`tiers.${tier.nsKey}.cta`)} )}
); })} {/* Enterprise = KVARK sovereign deployment. A quieter third card (description, no checklist) so the grid fills its row without inventing a tier — pricing stays consultative. */}

{t('enterprise.name')}

{t('enterprise.tagline')}

{t('enterprise.price')}

{t('enterprise.note')}

{t('enterprise.text')}

{t('enterprise.cta')}
); } function CheckIcon() { return ( ); }