diff --git a/apps/www/app/pricing/PricingContent.tsx b/apps/www/app/pricing/PricingContent.tsx index 9be45b941f3..f8634ccda96 100644 --- a/apps/www/app/pricing/PricingContent.tsx +++ b/apps/www/app/pricing/PricingContent.tsx @@ -4,13 +4,25 @@ import { ArrowDownIcon } from '@heroicons/react/outline' import DefaultLayout from '~/components/Layouts/Default' import PricingPlans from '~/components/Pricing/PricingPlans' import { useOrganizations } from '~/data/organizations' +import { hasConsented, posthogClient } from 'common' import { ArrowUpRight } from 'lucide-react' import dynamic from 'next/dynamic' import { usePathname } from 'next/navigation' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { Button } from 'ui' import { InfoTooltip } from 'ui-patterns/info-tooltip' +const EXPERIMENT_ID = 'pricingPageExperiment' as const +export type PricingPageExperimentVariant = + | 'control' + // GROWTH-694: flexibility/hourly billing + | 'flexibility' // concrete FAQ example as section between cards and buttons + | 'flexibility_card' // "no lock-in" messaging on Pro card warning + | 'hourly_rate' // actual hourly compute rate on Pro card warning + // GROWTH-697: pro project cost visibility + | 'multi_project' // "first project included, additional from $10/mo" on Pro card + | 'estimate_cta' // "estimate your cost" link on Pro card scrolling to calculator + const PricingComputeSection = dynamic(() => import('~/components/Pricing/PricingComputeSection')) const PricingAddons = dynamic(() => import('~/components/Pricing/PricingAddons')) const PricingComparisonTable = dynamic(() => import('~/components/Pricing/PricingComparisonTable')) @@ -45,6 +57,41 @@ export default function PricingContent() { const { isLoading, organizations } = useOrganizations() const hasExistingOrganizations = !isLoading && organizations.length > 0 + // Pricing value/flexibility A/B experiment + // Uses client-side PostHog directly — server-side evaluation lacks full person context for www pages. + // DevToolbar overrides (x-ph-flag-overrides cookie) are respected in local dev via posthogClient.getFeatureFlag. + const [flagValue, setFlagValue] = useState( + () => + posthogClient.getFeatureFlag(EXPERIMENT_ID) as + | PricingPageExperimentVariant + | false + | undefined + ) + + useEffect(() => { + return posthogClient.onFeatureFlags(() => { + const value = posthogClient.getFeatureFlag(EXPERIMENT_ID) + setFlagValue(value as PricingPageExperimentVariant | false | undefined) + }) + }, []) + + const validVariants: PricingPageExperimentVariant[] = [ + 'control', + 'flexibility', + 'flexibility_card', + 'hourly_rate', + 'multi_project', + 'estimate_cta', + ] + const isInExperiment = validVariants.includes(flagValue as PricingPageExperimentVariant) + const showFlexibilitySection = flagValue === 'flexibility' + + useEffect(() => { + if (!isInExperiment) return + + posthogClient.captureExperimentExposure(EXPERIMENT_ID, { variant: flagValue }, hasConsented()) + }, [isInExperiment, flagValue]) + return (
@@ -64,8 +111,20 @@ export default function PricingContent() { + {showFlexibilitySection && ( +
+

+ Need more power for a launch? Scale up instantly.{' '} + + Scale back down after — you only pay for the hours you use. + +

+
+ )} +
diff --git a/apps/www/components/Pricing/PricingPlans.tsx b/apps/www/components/Pricing/PricingPlans.tsx index d1367158c90..a785b6c7662 100644 --- a/apps/www/components/Pricing/PricingPlans.tsx +++ b/apps/www/components/Pricing/PricingPlans.tsx @@ -1,20 +1,26 @@ 'use client' -import Link from 'next/link' - -import { Check } from 'lucide-react' -import { plans } from 'shared-data/plans' -import { Button, cn } from 'ui' +import type { PricingPageExperimentVariant } from '~/app/pricing/PricingContent' import { Organization } from '~/data/organizations' import { useSendTelemetryEvent } from '~/lib/telemetry' +import { Check } from 'lucide-react' +import Link from 'next/link' +import { plans } from 'shared-data/plans' +import { Button, cn } from 'ui' + import UpgradePlan from './UpgradePlan' interface PricingPlansProps { organizations?: Organization[] hasExistingOrganizations?: boolean + experimentVariant?: PricingPageExperimentVariant } -const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansProps) => { +const PricingPlans = ({ + organizations, + hasExistingOrganizations, + experimentVariant, +}: PricingPlansProps) => { const sendTelemetryEvent = useSendTelemetryEvent() return ( @@ -66,14 +72,37 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP )}
-

- {plan.description} -

+

+ {plan.description} +

+ {isUpgradablePlan && experimentVariant === 'flexibility_card' && ( +

+ No lock-in. Scale up or down anytime — you only pay for the hours you use. +

+ )} + {isUpgradablePlan && experimentVariant === 'hourly_rate' && ( +

+ Compute from $0.01/hr. Scale anytime, pay only for what you use. +

+ )} +
{isUpgradablePlan && hasExistingOrganizations ? ( - {plan.warning && ( + {isUpgradablePlan && experimentVariant === 'multi_project' ? (
- - {plan.warning} + + First project included. Additional projects from $10/mo. - {(plan.name === 'Pro' || plan.name === 'Team') && ( - - Need more compute? - - )} + + See how pricing scales +
+ ) : isUpgradablePlan && experimentVariant === 'estimate_cta' ? ( +
+ {plan.warning} + + Estimate your cost ↓ + +
+ ) : ( + plan.warning && ( +
+ + {plan.warning} + + {(plan.name === 'Pro' || plan.name === 'Team') && ( + + Need more compute? + + )} +
+ ) )} diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 2b46ced7ad9..ded331b54fe 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -2992,6 +2992,35 @@ export interface StorageExplorerNavigateSubmittedEvent { groups: TelemetryGroups } +/** + * User was exposed to the pricing value/flexibility experiment on the /pricing page. + * + * @group Events + * @source www + * @page /pricing + */ +export interface PricingPageExperimentExposedEvent { + action: 'pricing_page_experiment_exposed' + properties: { + /** + * Experiment identifier for tracking + */ + experiment_id: 'pricingPageExperiment' + /** + * Experiment variant. + * GROWTH-694: flexibility (section), flexibility_card (on card), hourly_rate (on card) + * GROWTH-697: multi_project (on card), estimate_cta (on card) + */ + variant: + | 'control' + | 'flexibility' + | 'flexibility_card' + | 'hourly_rate' + | 'multi_project' + | 'estimate_cta' + } +} + /** * @hidden */ @@ -3160,3 +3189,4 @@ export type TelemetryEvent = | ComputeBadgeUpgradeClickedEvent | FreeMicroUpgradeBannerDismissedEvent | FreeMicroUpgradeBannerCtaClickedEvent + | PricingPageExperimentExposedEvent