feat: improve upgrade page in onboarding + conclude experiment (#29471)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Joshua Snyder
2025-03-05 16:16:35 +00:00
committed by GitHub
parent 7a44402b25
commit 61c331c448
11 changed files with 154 additions and 187 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -27,6 +27,7 @@ import runningHog from 'public/hedgehog/running-hog.png'
import sleepingHog from 'public/hedgehog/sleeping-hog.png'
import spaceHog from 'public/hedgehog/space-hog.png'
import starHog from 'public/hedgehog/star-hog.png'
import supermanHog from 'public/hedgehog/superman-hog.png'
import supportHeroHog from 'public/hedgehog/support-hero-hog.png'
import surprisedHog from 'public/hedgehog/surprised-hog.png'
import tronHog from 'public/hedgehog/tron-hog.png'
@@ -152,3 +153,7 @@ export const BurningMoneyHog = (props: HedgehogProps): JSX.Element => {
export const FilmCameraHog = (props: HedgehogProps): JSX.Element => {
return <SquaredHedgehog src={filmCameraHog} {...props} />
}
export const SupermanHog = (props: HedgehogProps): JSX.Element => {
return <SquaredHedgehog src={supermanHog} {...props} />
}

View File

@@ -234,7 +234,6 @@ export const FEATURE_FLAGS = {
EXPERIMENT_INTERVAL_TIMESERIES: 'experiments-interval-timeseries', // owner: @jurajmajerik #team-experiments
EXPERIMENT_P_VALUE: 'experiment-p-value', // owner: @jurajmajerik #team-experiments
WEB_ANALYTICS_IMPROVED_PATH_CLEANING: 'web-analytics-improved-path-cleaning', // owner: @rafaeelaudibert #team-web-analytics
ONBOARDING_NEW_PLANS_STEP: 'onboarding-new-plans-step', // owner: @joshsny #team-growth
EXPERIMENTAL_DASHBOARD_ITEM_RENDERING: 'experimental-dashboard-item-rendering', // owner: @thmsobrmlr #team-product-analytics
RECORDINGS_AI_FILTER: 'recordings-ai-filter', // owner: @veryayskiy #team-replay
PATHS_V2: 'paths-v2', // owner: @thmsobrmlr #team-product-analytics

View File

@@ -16,7 +16,6 @@ import { AvailableFeature, ProductKey } from '~/types'
import { OnboardingUpgradeStep } from './billing/OnboardingUpgradeStep'
import { OnboardingDataWarehouseSourcesStep } from './data-warehouse/OnboardingDataWarehouseSourcesStep'
import { OnboardingBillingStep } from './OnboardingBillingStep'
import { OnboardingInviteTeammates } from './OnboardingInviteTeammates'
import { onboardingLogic, OnboardingStepKey } from './onboardingLogic'
import { OnboardingProductConfiguration } from './OnboardingProductConfiguration'
@@ -56,8 +55,6 @@ const OnboardingWrapper = ({ children }: { children: React.ReactNode }): JSX.Ele
const { setAllOnboardingSteps } = useActions(onboardingLogic)
const [allSteps, setAllSteps] = useState<JSX.Element[]>([])
const showNewPlansStep = useFeatureFlag('ONBOARDING_NEW_PLANS_STEP', 'test')
useEffect(() => {
let steps = []
if (Array.isArray(children)) {
@@ -83,11 +80,8 @@ const OnboardingWrapper = ({ children }: { children: React.ReactNode }): JSX.Ele
const billingProduct = billing?.products.find((p) => p.type === productKey)
if (shouldShowBillingStep && billingProduct) {
const BillingStep = showNewPlansStep ? (
<OnboardingUpgradeStep product={billingProduct} stepKey={OnboardingStepKey.PLANS} />
) : (
<OnboardingBillingStep product={billingProduct} stepKey={OnboardingStepKey.PLANS} />
)
const BillingStep = <OnboardingUpgradeStep product={billingProduct} stepKey={OnboardingStepKey.PLANS} />
steps = [...steps, BillingStep]
}

View File

@@ -1,107 +0,0 @@
import { IconCheckCircle } from '@posthog/icons'
import { LemonButton } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { BillingUpgradeCTA } from 'lib/components/BillingUpgradeCTA'
import { StarHog } from 'lib/components/hedgehogs'
import { Spinner } from 'lib/lemon-ui/Spinner'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { useState } from 'react'
import { AllProductsPlanComparison } from 'scenes/billing/AllProductsPlanComparison'
import { getUpgradeProductLink } from 'scenes/billing/billing-utils'
import { BillingHero } from 'scenes/billing/BillingHero'
import { billingLogic } from 'scenes/billing/billingLogic'
import { PlanComparison } from 'scenes/billing/PlanComparison'
import { BillingProductV2Type } from '~/types'
import { onboardingLogic, OnboardingStepKey } from './onboardingLogic'
import { OnboardingStep } from './OnboardingStep'
export const OnboardingBillingStep = ({
product,
stepKey = OnboardingStepKey.PLANS,
}: {
product: BillingProductV2Type
stepKey?: OnboardingStepKey
}): JSX.Element => {
const { billing, redirectPath, billingLoading } = useValues(billingLogic)
const { productKey } = useValues(onboardingLogic)
const { reportBillingUpgradeClicked } = useActions(eventUsageLogic)
const [showPlanComp, setShowPlanComp] = useState(false)
const action = billing?.subscription_level === 'custom' ? 'Subscribe' : 'Upgrade'
return (
<OnboardingStep
title="Plans"
showSkip={!product.subscribed}
stepKey={stepKey}
continueOverride={
product?.subscribed && !billingLoading ? undefined : (
<BillingUpgradeCTA
// TODO: redirect path won't work properly until navigation is properly set up
to={getUpgradeProductLink({
product,
redirectPath,
includeAddons: true,
})}
type="primary"
status="alt"
center
disabledReason={billingLoading && 'Please wait...'}
disableClientSideRouting
onClick={() => {
reportBillingUpgradeClicked(product.type)
}}
data-attr="onboarding-subscribe-button"
>
{action}
</BillingUpgradeCTA>
)
}
>
{billing?.products && productKey && product && !billingLoading ? (
<div className="mt-6">
{product.subscribed && (
<div className="mb-8">
<div className="bg-success-highlight rounded p-6 flex justify-between items-center">
<div className="flex gap-x-4 min-w-0">
<IconCheckCircle className="text-success text-3xl mb-6 flex-shrink-0" />
<div className="flex-1 min-w-0">
<h3 className="text-lg font-bold mb-1 text-left">{action} successful</h3>
<p className="mx-0 mb-0">You're all ready to use {product.name}.</p>
</div>
</div>
<div className="h-20 w-20 flex-shrink-0">
<StarHog className="h-full w-full object-contain" />
</div>
</div>
<LemonButton
data-attr="show-plans"
className="mt-2"
onClick={() => setShowPlanComp(!showPlanComp)}
>
{showPlanComp ? 'Hide' : 'Show'} plans
</LemonButton>
</div>
)}
{(!product.subscribed || showPlanComp) && (
<>
<BillingHero />
{billing?.subscription_level === 'custom' ? (
<PlanComparison product={product} />
) : (
<AllProductsPlanComparison product={product} />
)}
</>
)}
</div>
) : (
<div className="flex items-center justify-center my-20">
<Spinner className="text-2xl text-secondary w-10 h-10" />
</div>
)}
</OnboardingStep>
)
}

View File

@@ -1,7 +1,8 @@
import { IconCheckCircle } from '@posthog/icons'
import { Spinner } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { StarHog } from 'lib/components/hedgehogs'
import { SupermanHog } from 'lib/components/hedgehogs'
import { useHogfetti } from 'lib/components/Hogfetti/Hogfetti'
import { useEffect } from 'react'
import { billingLogic } from 'scenes/billing/billingLogic'
import type { BillingProductV2Type } from '~/types'
@@ -16,9 +17,7 @@ type Props = {
}
export const OnboardingUpgradeStep = ({ product, stepKey }: Props): JSX.Element => {
const { billing, billingLoading } = useValues(billingLogic)
const action = billing?.subscription_level === 'custom' ? 'Subscribe' : 'Upgrade'
const { billingLoading } = useValues(billingLogic)
if (billingLoading) {
return (
@@ -35,26 +34,32 @@ export const OnboardingUpgradeStep = ({ product, stepKey }: Props): JSX.Element
continueOverride={!product.subscribed ? <></> : undefined}
>
{!product.subscribed && <PlanCards product={product} />}
{product.subscribed && <ProductSubscribed product={product} action={action} />}
{product.subscribed && <ProductSubscribed product={product} />}
</OnboardingStep>
)
}
const ProductSubscribed = ({ product, action }: { product: BillingProductV2Type; action: string }): JSX.Element => {
const ProductSubscribed = ({ product }: { product: BillingProductV2Type }): JSX.Element => {
const { trigger, HogfettiComponent } = useHogfetti({ count: 100, duration: 3000 })
useEffect(() => {
trigger()
}, [trigger])
return (
<div className="mb-8">
<div className="bg-success-highlight rounded p-6 flex justify-between items-center">
<div className="flex gap-x-4 min-w-0 justify-center items-center">
<IconCheckCircle className="text-success text-3xl flex-shrink-0" />
<div className="flex-1 min-w-0">
<h3 className="text-lg font-bold mb-1 text-left">{action} successful</h3>
<p className="mx-0 mb-0">You're all ready to use {product.name}.</p>
</div>
</div>
<div className="h-20 w-20 flex-shrink-0">
<StarHog className="h-full w-full object-contain" />
</div>
<div className="relative flex flex-col items-center text-center">
<HogfettiComponent />
{/* Superman Hog floating animation */}
<div className="w-40 h-40 animate-float">
<SupermanHog className="w-full h-full object-contain" />
</div>
{/* Text Below */}
<h3 className="text-2xl font-bold mt-6">Go forth and build amazing products!</h3>
<p className="text-gray-700">
You've unlocked all features for <strong>{product.name}</strong>.
</p>
</div>
)
}

View File

@@ -380,10 +380,18 @@
// Backgrounds
// Behind surfaces, large areas: app scenes, etc.
// //////////////////////////////////////////////////////////
--bg-primary: var(--primitive-3000-50); /* Most prominent/used background (replaces --bg-3000) */
--bg-surface-primary: var(--color-white); /* Most prominent surface (Cards, tables, etc.) */
--bg-surface-secondary: var(--primitive-3000-100); /* Second most prominent surface (Toolbars) */
--bg-surface-tertiary: var(--primitive-3000-150); /* Most prominent surface (Cards, tables, etc.) */
--bg-primary: var(--primitive-3000-50);
/* Most prominent/used background (replaces --bg-3000) */
--bg-surface-primary: var(--color-white);
/* Most prominent surface (Cards, tables, etc.) */
--bg-surface-secondary: var(--primitive-3000-100);
/* Second most prominent surface (Toolbars) */
--bg-surface-tertiary: var(--primitive-3000-150);
/* Most prominent surface (Cards, tables, etc.) */
--bg-surface-tooltip: var(--primitive-neutral-cool-850);
--bg-surface-tooltip-inverse: var(--primitive-neutral-cool-250);
--bg-surface-popover: var(--color-white);
@@ -403,42 +411,50 @@
--bg-fill-highlight-inverse-200: color-mix(in oklab, var(--color-white) 20%, transparent);
// Highlights
--bg-fill-primary-highlight: color-mix(
in oklab,
var(--color-primary-500) 10%,
transparent
); /* highlight bg for primary blocks */
--bg-fill-primary-highlight: color-mix(in oklab, var(--color-primary-500) 10%, transparent);
/* highlight bg for primary blocks */
// Info fills
--bg-fill-info-secondary: var(--color-blue-100); /* promonent bg for info blocks */
--bg-fill-info-tertiary: var(--color-blue-50); /* subtle bg for info blocks */
--bg-fill-info-secondary: var(--color-blue-100);
/* promonent bg for info blocks */
--bg-fill-info-tertiary: var(--color-blue-50);
/* subtle bg for info blocks */
// Warning fills
--bg-fill-warning-secondary: var(--color-yellow-100); /* prominent bg for warning blocks */
--bg-fill-warning-tertiary: var(--color-yellow-50); /* subtle bg for warning blocks */
--bg-fill-warning-highlight: color-mix(
in oklab,
var(--color-yellow-500) 15%,
transparent
); /* highlight bg for warning blocks */
--bg-fill-warning-secondary: var(--color-yellow-100);
/* prominent bg for warning blocks */
--bg-fill-warning-tertiary: var(--color-yellow-50);
/* subtle bg for warning blocks */
--bg-fill-warning-highlight: color-mix(in oklab, var(--color-yellow-500) 15%, transparent);
/* highlight bg for warning blocks */
// Error fills
--bg-fill-error-secondary: var(--color-red-100); /* prominent bg for error blocks */
--bg-fill-error-tertiary: var(--color-red-50); /* subtle bg for error blocks */
--bg-fill-error-highlight: color-mix(
in oklab,
var(--color-red-500) 10%,
transparent
); /* highlight bg for error blocks */
--bg-fill-error-secondary: var(--color-red-100);
/* prominent bg for error blocks */
--bg-fill-error-tertiary: var(--color-red-50);
/* subtle bg for error blocks */
--bg-fill-error-highlight: color-mix(in oklab, var(--color-red-500) 10%, transparent);
/* highlight bg for error blocks */
// Success fills
--bg-fill-success-secondary: var(--color-green-100); /* promonent bg for success blocks */
--bg-fill-success-tertiary: var(--color-green-50); /* subtle bg for success blocks */
--bg-fill-success-highlight: color-mix(
in oklab,
var(--color-green-500) 10%,
transparent
); /* highlight bg for success blocks */
--bg-fill-success-secondary: var(--color-green-100);
/* promonent bg for success blocks */
--bg-fill-success-tertiary: var(--color-green-50);
/* subtle bg for success blocks */
--bg-fill-success-highlight: color-mix(in oklab, var(--color-green-500) 10%, transparent);
/* highlight bg for success blocks */
// Inputs
--bg-fill-input: var(--color-white);
@@ -458,10 +474,18 @@
// Content on fills
// Texts which are on colourful fills, ensures contrast
// //////////////////////////////////////////////////////////
--text-info-on-fill: var(--color-blue-800); /* On-fill text for info */
--text-warning-on-fill: var(--color-yellow-800); /* On-fill text for warning */
--text-error-on-fill: var(--color-red-800); /* On-fill text for error */
--text-success-on-fill: var(--color-green-800); /* On-fill text for success */
--text-info-on-fill: var(--color-blue-800);
/* On-fill text for info */
--text-warning-on-fill: var(--color-yellow-800);
/* On-fill text for warning */
--text-error-on-fill: var(--color-red-800);
/* On-fill text for error */
--text-success-on-fill: var(--color-green-800);
/* On-fill text for success */
// Borders
// Borders for surfaces/fills (TODO: perhaps we need to do border-fill-primary, border-fill-warning, etc.)
@@ -469,19 +493,31 @@
--border-primary: var(--primitive-3000-200);
--border-secondary: var(--primitive-3000-400);
--border-info: var(--color-blue-400);
--border-warning: var(--color-yellow-400); /* Border for warning */
--border-error: var(--color-red-400); /* Border for error */
--border-success: var(--color-green-400); /* Border for success */
--border-warning: var(--color-yellow-400);
/* Border for warning */
--border-error: var(--color-red-400);
/* Border for error */
--border-success: var(--color-green-400);
/* Border for success */
// Graph colors
// //////////////////////////////////////////////////////////
--color-graph-axis-label: var(--text-secondary); /* Labels around graph */
--color-graph-axis-line: var(
--primitive-neutral-cool-100
); /* Background lines in a graph, horizontal and vertical */
--color-graph-axis-label: var(--text-secondary);
--color-graph-axis: var(--text-secondary); /* Line around a graph */
--color-graph-crosshair: var(--primitive-neutral-cool-450); /* Crosshair line, horizontal or vertical */
/* Labels around graph */
--color-graph-axis-line: var(--primitive-neutral-cool-100);
/* Background lines in a graph, horizontal and vertical */
--color-graph-axis: var(--text-secondary);
/* Line around a graph */
--color-graph-crosshair: var(--primitive-neutral-cool-450);
/* Crosshair line, horizontal or vertical */
// Skeleton colors
// //////////////////////////////////////////////////////////
@@ -864,10 +900,18 @@
// Content on fills (dark mode)
// Texts which are on colourful fills, ensures contrast
// //////////////////////////////////////////////////////////
--text-info-on-fill: var(--color-blue-50); /* On-fill text for info */
--text-warning-on-fill: var(--color-yellow-50); /* On-fill text for warning */
--text-error-on-fill: var(--color-red-50); /* On-fill text for error */
--text-success-on-fill: var(--color-green-50); /* On-fill text for success */
--text-info-on-fill: var(--color-blue-50);
/* On-fill text for info */
--text-warning-on-fill: var(--color-yellow-50);
/* On-fill text for warning */
--text-error-on-fill: var(--color-red-50);
/* On-fill text for error */
--text-success-on-fill: var(--color-green-50);
/* On-fill text for success */
// Borders (dark mode)
// Borders for surfaces/fills (TODO: perhaps we need to do border-fill-primary, border-fill-warning, etc.)
@@ -875,9 +919,15 @@
--border-primary: var(--primitive-neutral-cool-800);
--border-secondary: var(--primitive-neutral-cool-600);
--border-info: var(--color-blue-900);
--border-warning: var(--color-yellow-900); /* Border for warning */
--border-error: var(--color-red-900); /* Border for error */
--border-success: var(--color-green-900); /* Border for success */
--border-warning: var(--color-yellow-900);
/* Border for warning */
--border-error: var(--color-red-900);
/* Border for error */
--border-success: var(--color-green-900);
/* Border for success */
// Graph colors (dark mode)
// //////////////////////////////////////////////////////////
@@ -1496,3 +1546,24 @@ img {
transform: rotateZ(360deg);
}
}
// Floating animation
/* stylelint-disable-next-line keyframes-name-pattern */
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}
.animate-float {
animation: float 3s infinite ease-in-out;
}