feat: improve upgrade page in onboarding + conclude experiment (#29471)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 68 KiB |
BIN
frontend/public/hedgehog/superman-hog.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||