export type PlanTier = 'starter' | 'growth' | 'pro' | 'enterprise'; export type BillingInterval = 'monthly' | 'annual' | 'custom'; export type PlanListingCategory = 'pricing_page_primary' | 'pricing_page_hidden' | 'internal_only'; export type FeatureReadiness = 'launch_ready' | 'marketed_not_enforced' | 'future'; export type ActivePlanCode = | 'starter_monthly' | 'starter_annual' | 'growth_monthly' | 'growth_annual' | 'pro_monthly' | 'pro_annual' | 'enterprise_custom'; export type ReservedPlanCode = 'founder_lifetime' | 'founder_pro_lifetime'; export type PlanCode = ActivePlanCode | ReservedPlanCode; export type AddonCode = 'export_pack_10k' | 'export_pack_50k' | 'enrichment_pack_1k' | 'ai_assistant_monthly' | 'white_label_monthly'; export type ProcessingTier = 'standard' | 'priority' | 'dedicated'; export interface PlanLimits { researchRunsPerMonth: number | null; exportsPerMonth: number | null; // These are commercial packaging allowances for now. Hard enforcement comes later // after workspace-scoped ownership and collaboration rules are ready. usersIncluded: number | null; workspacesIncluded: number | null; enrichmentCreditsIncluded: number | null; pooledUsage: boolean; } export interface PlanFeatures { csvExport: boolean; mapSearch: boolean; radiusSearch: boolean; basicFilters: boolean; advancedFilters: boolean; savedSearches: boolean; territoryMapping: boolean; deduplication: boolean; exportHistory: boolean; taggingNotes: boolean; sharedLists: boolean; scheduledResearch: boolean; bulkExports: boolean; crmIntegrations: boolean; apiAccess: boolean; webhooks: boolean; collaboration: boolean; enrichments: boolean; prioritySupport: boolean; sso: boolean; sla: boolean; whiteLabel: boolean; } export interface PlanDefinition { code: ActivePlanCode; tier: PlanTier; planFamily: PlanTier; name: string; billingInterval: BillingInterval; listingCategory: PlanListingCategory; isSelfServe: boolean; contactSalesRequired: boolean; priceCents: number | null; currencyCode: 'USD'; annualDiscountPercent: number | null; limits: PlanLimits; features: PlanFeatures; featureReadiness: Partial>; processingTier: ProcessingTier; eligibleAddonCodes: AddonCode[]; } export interface PlanDisplayMeta { audience: string; summary: string; ctaLabel: string; ctaMode: 'sign_in' | 'sign_up'; badgeLabel?: string; qualitativeBullets: string[]; } function createFeatureFlags(overrides: Partial): PlanFeatures { return { csvExport: false, mapSearch: false, radiusSearch: false, basicFilters: false, advancedFilters: false, savedSearches: false, territoryMapping: false, deduplication: false, exportHistory: false, taggingNotes: false, sharedLists: false, scheduledResearch: false, bulkExports: false, crmIntegrations: false, apiAccess: false, webhooks: false, collaboration: false, enrichments: false, prioritySupport: false, sso: false, sla: false, whiteLabel: false, ...overrides, }; } function createPlan(plan: PlanDefinition): PlanDefinition { return plan; } const activePlanCatalog = [ createPlan({ code: 'starter_monthly', tier: 'starter', planFamily: 'starter', name: 'Starter', billingInterval: 'monthly', listingCategory: 'pricing_page_primary', isSelfServe: true, contactSalesRequired: false, priceCents: 3900, currencyCode: 'USD', annualDiscountPercent: null, limits: { researchRunsPerMonth: 25, exportsPerMonth: 2500, usersIncluded: 1, workspacesIncluded: 1, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, }), featureReadiness: {}, processingTier: 'standard', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k'], }), createPlan({ code: 'starter_annual', tier: 'starter', planFamily: 'starter', name: 'Starter', billingInterval: 'annual', listingCategory: 'pricing_page_hidden', isSelfServe: true, contactSalesRequired: false, priceCents: 37440, currencyCode: 'USD', annualDiscountPercent: 20, limits: { researchRunsPerMonth: 25, exportsPerMonth: 2500, usersIncluded: 1, workspacesIncluded: 1, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, }), featureReadiness: {}, processingTier: 'standard', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k'], }), createPlan({ code: 'growth_monthly', tier: 'growth', planFamily: 'growth', name: 'Growth', billingInterval: 'monthly', listingCategory: 'pricing_page_primary', isSelfServe: true, contactSalesRequired: false, priceCents: 9900, currencyCode: 'USD', annualDiscountPercent: null, limits: { researchRunsPerMonth: 150, exportsPerMonth: 15000, usersIncluded: 3, workspacesIncluded: 5, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, advancedFilters: true, savedSearches: true, territoryMapping: true, deduplication: true, exportHistory: true, taggingNotes: true, prioritySupport: true, }), featureReadiness: { savedSearches: 'marketed_not_enforced', territoryMapping: 'launch_ready', deduplication: 'marketed_not_enforced', exportHistory: 'marketed_not_enforced', taggingNotes: 'marketed_not_enforced', }, processingTier: 'priority', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k'], }), createPlan({ code: 'growth_annual', tier: 'growth', planFamily: 'growth', name: 'Growth', billingInterval: 'annual', listingCategory: 'pricing_page_hidden', isSelfServe: true, contactSalesRequired: false, priceCents: 95040, currencyCode: 'USD', annualDiscountPercent: 20, limits: { researchRunsPerMonth: 150, exportsPerMonth: 15000, usersIncluded: 3, workspacesIncluded: 5, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, advancedFilters: true, savedSearches: true, territoryMapping: true, deduplication: true, exportHistory: true, taggingNotes: true, prioritySupport: true, }), featureReadiness: { savedSearches: 'marketed_not_enforced', territoryMapping: 'launch_ready', deduplication: 'marketed_not_enforced', exportHistory: 'marketed_not_enforced', taggingNotes: 'marketed_not_enforced', }, processingTier: 'priority', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k'], }), createPlan({ code: 'pro_monthly', tier: 'pro', planFamily: 'pro', name: 'Pro', billingInterval: 'monthly', listingCategory: 'pricing_page_primary', isSelfServe: true, contactSalesRequired: false, priceCents: 24900, currencyCode: 'USD', annualDiscountPercent: null, limits: { researchRunsPerMonth: 500, exportsPerMonth: 75000, usersIncluded: 15, workspacesIncluded: null, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, advancedFilters: true, savedSearches: true, territoryMapping: true, deduplication: true, exportHistory: true, taggingNotes: true, sharedLists: true, scheduledResearch: true, bulkExports: true, crmIntegrations: true, apiAccess: true, webhooks: true, collaboration: true, enrichments: true, prioritySupport: true, }), featureReadiness: { savedSearches: 'marketed_not_enforced', deduplication: 'marketed_not_enforced', exportHistory: 'marketed_not_enforced', taggingNotes: 'marketed_not_enforced', sharedLists: 'marketed_not_enforced', scheduledResearch: 'marketed_not_enforced', bulkExports: 'marketed_not_enforced', crmIntegrations: 'marketed_not_enforced', apiAccess: 'marketed_not_enforced', webhooks: 'marketed_not_enforced', collaboration: 'marketed_not_enforced', enrichments: 'marketed_not_enforced', }, processingTier: 'priority', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k', 'ai_assistant_monthly', 'white_label_monthly'], }), createPlan({ code: 'pro_annual', tier: 'pro', planFamily: 'pro', name: 'Pro', billingInterval: 'annual', listingCategory: 'pricing_page_hidden', isSelfServe: true, contactSalesRequired: false, priceCents: 239040, currencyCode: 'USD', annualDiscountPercent: 20, limits: { researchRunsPerMonth: 500, exportsPerMonth: 75000, usersIncluded: 15, workspacesIncluded: null, enrichmentCreditsIncluded: 0, pooledUsage: false, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, advancedFilters: true, savedSearches: true, territoryMapping: true, deduplication: true, exportHistory: true, taggingNotes: true, sharedLists: true, scheduledResearch: true, bulkExports: true, crmIntegrations: true, apiAccess: true, webhooks: true, collaboration: true, enrichments: true, prioritySupport: true, }), featureReadiness: { savedSearches: 'marketed_not_enforced', deduplication: 'marketed_not_enforced', exportHistory: 'marketed_not_enforced', taggingNotes: 'marketed_not_enforced', sharedLists: 'marketed_not_enforced', scheduledResearch: 'marketed_not_enforced', bulkExports: 'marketed_not_enforced', crmIntegrations: 'marketed_not_enforced', apiAccess: 'marketed_not_enforced', webhooks: 'marketed_not_enforced', collaboration: 'marketed_not_enforced', enrichments: 'marketed_not_enforced', }, processingTier: 'priority', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k', 'ai_assistant_monthly', 'white_label_monthly'], }), createPlan({ code: 'enterprise_custom', tier: 'enterprise', planFamily: 'enterprise', name: 'Enterprise', billingInterval: 'custom', listingCategory: 'pricing_page_primary', isSelfServe: false, contactSalesRequired: true, priceCents: null, currencyCode: 'USD', annualDiscountPercent: null, limits: { researchRunsPerMonth: null, exportsPerMonth: null, usersIncluded: null, workspacesIncluded: null, enrichmentCreditsIncluded: null, pooledUsage: true, }, features: createFeatureFlags({ csvExport: true, mapSearch: true, radiusSearch: true, basicFilters: true, advancedFilters: true, savedSearches: true, territoryMapping: true, deduplication: true, exportHistory: true, taggingNotes: true, sharedLists: true, scheduledResearch: true, bulkExports: true, crmIntegrations: true, apiAccess: true, webhooks: true, collaboration: true, enrichments: true, prioritySupport: true, sso: true, sla: true, whiteLabel: true, }), featureReadiness: { sharedLists: 'marketed_not_enforced', scheduledResearch: 'marketed_not_enforced', bulkExports: 'marketed_not_enforced', crmIntegrations: 'marketed_not_enforced', apiAccess: 'marketed_not_enforced', webhooks: 'marketed_not_enforced', collaboration: 'marketed_not_enforced', enrichments: 'marketed_not_enforced', sso: 'future', sla: 'future', whiteLabel: 'future', }, processingTier: 'dedicated', eligibleAddonCodes: ['export_pack_10k', 'export_pack_50k', 'enrichment_pack_1k', 'ai_assistant_monthly', 'white_label_monthly'], }), ] as const satisfies readonly PlanDefinition[]; const activePlanCatalogByCode = new Map(activePlanCatalog.map((plan) => [plan.code, plan])); const planDisplayMetaByCode: Record = { starter_monthly: { audience: 'For freelancers and solo operators', summary: 'A focused entry point for recurring local market research.', ctaLabel: 'Choose Starter', ctaMode: 'sign_up', qualitativeBullets: ['CSV export, map search, radius search, and basic filters'], }, starter_annual: { audience: 'For freelancers and solo operators', summary: 'A focused entry point for recurring local market research.', ctaLabel: 'Choose Starter', ctaMode: 'sign_up', badgeLabel: 'Save 20%', qualitativeBullets: ['CSV export, map search, radius search, and basic filters'], }, growth_monthly: { audience: 'For agencies and outbound teams', summary: 'The best-value plan for repeatable territory workflows.', ctaLabel: 'Choose Growth', ctaMode: 'sign_up', badgeLabel: 'Best Value', qualitativeBullets: ['Saved searches, territory mapping, advanced filtering, deduplication, export history, and priority support'], }, growth_annual: { audience: 'For agencies and outbound teams', summary: 'The best-value plan for repeatable territory workflows.', ctaLabel: 'Choose Growth', ctaMode: 'sign_up', badgeLabel: 'Save 20%', qualitativeBullets: ['Saved searches, territory mapping, advanced filtering, deduplication, export history, and priority support'], }, pro_monthly: { audience: 'For power users and scaling teams', summary: 'Operational infrastructure for teams that need more scale and automation.', ctaLabel: 'Choose Pro', ctaMode: 'sign_up', qualitativeBullets: ['Scheduled research, bulk exports, CRM integrations, API access, enrichment access, and collaboration features'], }, pro_annual: { audience: 'For power users and scaling teams', summary: 'Operational infrastructure for teams that need more scale and automation.', ctaLabel: 'Choose Pro', ctaMode: 'sign_up', badgeLabel: 'Save 20%', qualitativeBullets: ['Scheduled research, bulk exports, CRM integrations, API access, enrichment access, and collaboration features'], }, enterprise_custom: { audience: 'For multi-location and enterprise rollouts', summary: 'Custom market intelligence infrastructure for security, governance, and scale.', ctaLabel: 'Talk to sales', ctaMode: 'sign_in', qualitativeBullets: ['SSO, SLA, onboarding, and account management', 'White-labeling, custom enrichments, and custom integrations', 'Dedicated infrastructure and advanced API scaling'], }, }; export const ACTIVE_PLAN_CATALOG = [...activePlanCatalog]; export function getActivePlanCatalog() { return ACTIVE_PLAN_CATALOG; } export function getPlanByCode(code: PlanCode) { return activePlanCatalogByCode.get(code as ActivePlanCode) ?? null; } export function getSelfServePlans() { return ACTIVE_PLAN_CATALOG.filter((plan) => plan.isSelfServe); } export function getAnnualPlans() { return ACTIVE_PLAN_CATALOG.filter((plan) => plan.billingInterval === 'annual'); } export function getPublicPricingPlans() { return ACTIVE_PLAN_CATALOG.filter((plan) => plan.listingCategory === 'pricing_page_primary'); } export function getPlanVariant(tier: PlanTier, billingInterval: BillingInterval) { return ACTIVE_PLAN_CATALOG.find((plan) => plan.planFamily === tier && plan.billingInterval === billingInterval) ?? null; } export function getSiblingPlans(code: ActivePlanCode) { const selectedPlan = getPlanByCode(code); if (!selectedPlan) { return []; } return ACTIVE_PLAN_CATALOG.filter((plan) => plan.planFamily === selectedPlan.planFamily); } export function isAnnualPlan(code: ActivePlanCode) { return getPlanByCode(code)?.billingInterval === 'annual'; } export function getPlanDisplayMeta(code: ActivePlanCode) { return planDisplayMetaByCode[code]; } export function getPlanCardBullets(code: ActivePlanCode) { const plan = getPlanByCode(code); const display = getPlanDisplayMeta(code); if (!plan) { return display.qualitativeBullets; } const quantitativeBullets = [ formatResearchRunsBullet(plan.limits.researchRunsPerMonth), formatExportsBullet(plan.limits.exportsPerMonth), formatSeatAllowanceBullet(plan.limits.usersIncluded, plan.limits.workspacesIncluded, plan.limits.pooledUsage), ].filter((bullet): bullet is string => bullet !== null); return [...quantitativeBullets, ...display.qualitativeBullets]; } function formatResearchRunsBullet(researchRunsPerMonth: number | null) { if (researchRunsPerMonth === null) { return 'Pooled or custom research capacity'; } return `${formatCount(researchRunsPerMonth)} research runs / month`; } function formatExportsBullet(exportsPerMonth: number | null) { if (exportsPerMonth === null) { return null; } return `${formatCount(exportsPerMonth)} exports / month`; } function formatSeatAllowanceBullet(usersIncluded: number | null, workspacesIncluded: number | null, pooledUsage: boolean) { if (pooledUsage) { return 'Pooled or custom team usage'; } if (usersIncluded === null && workspacesIncluded === null) { return null; } if (usersIncluded !== null && workspacesIncluded === null) { return usersIncluded === 1 ? '1 user and unlimited workspaces' : `${formatCount(usersIncluded)} users and unlimited workspaces`; } if (usersIncluded === null && workspacesIncluded !== null) { return workspacesIncluded === 1 ? '1 workspace' : `${formatCount(workspacesIncluded)} workspaces`; } const userLabel = usersIncluded === 1 ? '1 user' : `${formatCount(usersIncluded as number)} users`; const workspaceLabel = workspacesIncluded === 1 ? '1 workspace' : `${formatCount(workspacesIncluded as number)} workspaces`; return `${userLabel} and ${workspaceLabel}`; } function formatCount(value: number) { return new Intl.NumberFormat('en-US').format(value); }