diff --git a/apps/www/app/pricing/PricingContent.tsx b/apps/www/app/pricing/PricingContent.tsx index 688cba00b2b..a0bfea47e27 100644 --- a/apps/www/app/pricing/PricingContent.tsx +++ b/apps/www/app/pricing/PricingContent.tsx @@ -13,6 +13,16 @@ import HashAnchorScroll from './HashAnchorScroll' import PricingComparisonSection from './PricingComparisonSection' import PricingPlansSection from './PricingPlansSection' +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 + export default function PricingContent() { return ( diff --git a/apps/www/app/pricing/PricingPlansSection.tsx b/apps/www/app/pricing/PricingPlansSection.tsx index 7d153f3fa60..7857f1b3ddc 100644 --- a/apps/www/app/pricing/PricingPlansSection.tsx +++ b/apps/www/app/pricing/PricingPlansSection.tsx @@ -2,15 +2,71 @@ import PricingPlans from '~/components/Pricing/PricingPlans' import { useOrganizations } from '~/data/organizations' +import { hasConsented, posthogClient } from 'common' +import { useEffect, useState } from 'react' + +import type { PricingPageExperimentVariant } from './PricingContent' + +const EXPERIMENT_ID = 'pricingPageExperiment' as const + +const VALID_VARIANTS: PricingPageExperimentVariant[] = [ + 'control', + 'flexibility', + 'flexibility_card', + 'hourly_rate', + 'multi_project', + 'estimate_cta', +] export default function PricingPlansSection() { 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 isInExperiment = VALID_VARIANTS.includes(flagValue as PricingPageExperimentVariant) + const showFlexibilitySection = flagValue === 'flexibility' + + useEffect(() => { + if (!isInExperiment) return + + posthogClient.captureExperimentExposure(EXPERIMENT_ID, { variant: flagValue }, hasConsented()) + }, [isInExperiment, flagValue]) + return ( - + <> + + + {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 6d9fd86fbab..a785b6c7662 100644 --- a/apps/www/components/Pricing/PricingPlans.tsx +++ b/apps/www/components/Pricing/PricingPlans.tsx @@ -1,5 +1,6 @@ 'use client' +import type { PricingPageExperimentVariant } from '~/app/pricing/PricingContent' import { Organization } from '~/data/organizations' import { useSendTelemetryEvent } from '~/lib/telemetry' import { Check } from 'lucide-react' @@ -12,9 +13,14 @@ 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,10 +72,36 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP )} -
-

+

+

{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 2dd383b9a83..2546c4da2f3 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -3115,6 +3115,35 @@ export interface StoragePublicBucketSelectPolicyWarningDismissButtonClickedEvent 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' + } +} + /** * Triggered when an access token is successfully created. * @@ -3377,6 +3406,7 @@ export type TelemetryEvent = | ComputeBadgeUpgradeClickedEvent | FreeMicroUpgradeBannerDismissedEvent | FreeMicroUpgradeBannerCtaClickedEvent + | PricingPageExperimentExposedEvent | HeaderUpgradeCtaClickedEvent | AccessTokenCreatedEvent | AccessTokenRemovedEvent