Skip to content

Commit

Permalink
feat: closeable credits banner (#25151)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
zlwaterfield and github-actions[bot] authored Sep 26, 2024
1 parent 0a3e220 commit 2d0ade4
Show file tree
Hide file tree
Showing 16 changed files with 126 additions and 137 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/hedgehog/burning-money-hog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/lib/components/hedgehogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import blushingHog from 'public/hedgehog/blushing-hog.png'
import builderHog1 from 'public/hedgehog/builder-hog-01.png'
import builderHog2 from 'public/hedgehog/builder-hog-02.png'
import builderHog3 from 'public/hedgehog/builder-hog-03.png'
import burningMoneyHog from 'public/hedgehog/burning-money-hog.png'
import detectiveHog from 'public/hedgehog/detective-hog.png'
import experimentsHog from 'public/hedgehog/experiments-hog.png'
import explorerHog from 'public/hedgehog/explorer-hog.png'
Expand Down Expand Up @@ -144,3 +145,6 @@ export const MicrophoneHog = (props: HedgehogProps): JSX.Element => {
export const PhonePairHogs = (props: HedgehogProps): JSX.Element => {
return <SquaredHedgehog src={phonePairHogs} {...props} />
}
export const BurningMoneyHog = (props: HedgehogProps): JSX.Element => {
return <SquaredHedgehog src={burningMoneyHog} {...props} />
}
4 changes: 2 additions & 2 deletions frontend/src/scenes/billing/Billing.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const BillingWithCreditCTA = (): JSX.Element => {
'/api/billing/credits/overview': {
status: 'none',
eligible: true,
estimated_monthly_credit_amount_usd: '1200',
estimated_monthly_credit_amount_usd: 1200,
email: '[email protected]',
cc_last_four: '1234',
cc_brand: 'Visa',
Expand Down Expand Up @@ -100,7 +100,7 @@ export const BillingPurchaseCreditsModal = (): JSX.Element => {
'/api/billing/credits/overview': {
status: 'none',
eligible: true,
estimated_monthly_credit_amount_usd: '1200',
estimated_monthly_credit_amount_usd: 1200,
email: '[email protected]',
cc_last_four: '1234',
cc_brand: 'Visa',
Expand Down
61 changes: 2 additions & 59 deletions frontend/src/scenes/billing/Billing.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import './Billing.scss'

import { IconCheckCircle } from '@posthog/icons'
import { LemonButton, LemonDivider, LemonInput, Link } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { Field, Form } from 'kea-forms'
import { router } from 'kea-router'
import { SurprisedHog } from 'lib/components/hedgehogs'
import { supportLogic } from 'lib/components/Support/supportLogic'
import { dayjs } from 'lib/dayjs'
import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
Expand All @@ -32,16 +30,8 @@ export const scene: SceneExport = {
}

export function Billing(): JSX.Element {
const {
billing,
billingLoading,
isOnboarding,
showLicenseDirectInput,
isActivateLicenseSubmitting,
over20kAnnual,
isAnnualPlan,
billingError,
} = useValues(billingLogic)
const { billing, billingLoading, isOnboarding, showLicenseDirectInput, isActivateLicenseSubmitting, billingError } =
useValues(billingLogic)
const { reportBillingShown } = useActions(billingLogic)
const { preflight, isCloudOrDev } = useValues(preflightLogic)
const { openSupportForm } = useActions(supportLogic)
Expand Down Expand Up @@ -225,53 +215,6 @@ export function Billing(): JSX.Element {
</div>
)}
</div>
{!isOnboarding && !isAnnualPlan && over20kAnnual && (
<div className="bg-[var(--glass-bg-3000)] flex flex-row gap-2 relative pl-6 p-4 border rounded min-w-120 w-fit">
<div className="flex flex-col pl-2 ">
<h3>You've unlocked enterprise-grade perks:</h3>
<ul className="pl-4">
<li className="flex gap-2 items-center">
<IconCheckCircle className="text-success shrink-0" />
<span>
<strong>Save 20%</strong> by switching to up-front annual billing
</span>
</li>
<li className="flex gap-2 items-center">
<IconCheckCircle className="text-success shrink-0" />
<span>
Get <strong>discounts on bundled subscriptions</strong> to multiple products
</span>
</li>
<li className="flex gap-2 items-center">
<IconCheckCircle className="text-success shrink-0" />
<span>
Get <strong>customized training</strong> for you and your team
</span>
</li>
<li className="flex gap-2 items-center">
<IconCheckCircle className="text-success shrink-0" />
<span>
Get dedicated support via <strong>private Slack channel</strong>
</span>
</li>
<li className="flex gap-2 items-center">
<IconCheckCircle className="text-success shrink-0" />
<span>
We'll even send you <strong>awesome free merch</strong>
</span>
</li>
</ul>
<div className="pt-1 self-start flex flex-row gap-1 mt-2">
<LemonButton type="secondary" to="mailto:[email protected]">
Let's chat
</LemonButton>
</div>
</div>
<div className="h-24 self-end -scale-x-100 -ml-20 -mb-2">
<SurprisedHog className="max-h-full w-auto object-contain" />
</div>
</div>
)}
</div>

<LemonDivider className="mt-6 mb-8" />
Expand Down
104 changes: 89 additions & 15 deletions frontend/src/scenes/billing/CreditCTAHero.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LemonButton } from '@posthog/lemon-ui'
import { IconX } from '@posthog/icons'
import { LemonButton, LemonDivider } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { BlushingHog } from 'lib/components/hedgehogs'
import { BurningMoneyHog } from 'lib/components/hedgehogs'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import useResizeObserver from 'use-resize-observer'
Expand All @@ -11,8 +12,9 @@ import { PurchaseCreditsModal } from './PurchaseCreditsModal'
export const CreditCTAHero = (): JSX.Element | null => {
const { width, ref: heroRef } = useResizeObserver()

const { creditOverview, isPurchaseCreditsModalOpen } = useValues(billingLogic)
const { showPurchaseCreditsModal } = useActions(billingLogic)
const { creditOverview, isPurchaseCreditsModalOpen, isCreditCTAHeroDismissed, computedDiscount } =
useValues(billingLogic)
const { showPurchaseCreditsModal, toggleCreditCTAHeroDismissed } = useActions(billingLogic)
const { featureFlags } = useValues(featureFlagLogic)

if (!creditOverview.eligible || creditOverview.status === 'paid') {
Expand All @@ -22,9 +24,44 @@ export const CreditCTAHero = (): JSX.Element | null => {
return null
}

if (isCreditCTAHeroDismissed) {
return (
<div className="absolute top-0 right-0 z-10">
<div
className="cursor-pointer bg-mark rounded-lg pr-3 pl-2 py-1 hover:bg-mark-light transition-colors group"
onClick={() => toggleCreditCTAHeroDismissed(false)}
>
<span className="flex items-center gap-1.5">
<BurningMoneyHog
className="w-8 h-8 group-hover:animate-bounce"
style={{ animationDuration: '0.75s' }}
/>
<span>Get {computedDiscount * 100}% off</span>
</span>
</div>
</div>
)
}

return (
<div className="flex relative justify-between items-center rounded-lg bg-mark mb-6" ref={heroRef}>
<div className="p-4">
<div
className="flex relative justify-between items-start rounded-lg bg-bg-light border mb-2 gap-2"
ref={heroRef}
>
<div className="absolute top-2 right-2 z-10">
<LemonButton
icon={<IconX className="w-4 h-4" />}
size="small"
onClick={() => toggleCreditCTAHeroDismissed(true)}
aria-label="Close"
/>
</div>
{width && width > 500 && (
<div className="shrink-0 relative pt-4 overflow-hidden">
<BurningMoneyHog className="w-40 h-40" />
</div>
)}
<div className="p-4 flex-1">
{creditOverview.eligible && creditOverview.status === 'pending' && (
<>
<h1 className="mb-0">We're applying your credits</h1>
Expand All @@ -49,22 +86,59 @@ export const CreditCTAHero = (): JSX.Element | null => {
)}
{creditOverview.eligible && creditOverview.status === 'none' && (
<>
<h1 className="mb-0">Get a discount of up to 30%</h1>
<h2 className="mb-0">
Stop burning money.{' '}
<span className="text-success-light">Prepay and save {computedDiscount * 100}%</span> over
the next 12 months.
</h2>
<p className="mt-2 mb-0 max-w-xl">
Based on your usage, your monthly bill is forecasted to be an average of{' '}
<strong>${creditOverview.estimated_monthly_credit_amount_usd.toFixed(0)}/month</strong> over
the next year.
</p>
<p className="mt-2 mb-0 max-w-xl">
Buy credits in advance, at a discount of up to 30%. It helps you make costs more
predictable!
This qualifies you for a <strong>{computedDiscount * 100}% discount</strong> by
pre-purchasing usage credits. Which gives you a net savings of{' '}
<strong>
$
{Math.round(
creditOverview.estimated_monthly_credit_amount_usd * computedDiscount * 12
).toLocaleString('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})}
</strong>{' '}
over the next year.
</p>
<LemonButton type="primary" onClick={() => showPurchaseCreditsModal(true)} className="mt-4">
<LemonButton
type="primary"
status="alt"
onClick={() => showPurchaseCreditsModal(true)}
className="mt-4"
>
Buy credits
</LemonButton>
{creditOverview.estimated_monthly_credit_amount_usd > 1 && (
<>
<LemonDivider className="my-4" />
<div className="mt-2 flex justify-between items-center gap-2 w-full">
<p className="mb-2 flex-1">
<strong>Also available:</strong> Our Enterprise tier offers dedicated support in
a private Slack channel, personalized training, and most importantly, free
merch.
</p>
<LemonButton
type="primary"
to="mailto:[email protected]?subject=Let's talk enterprise!"
>
Talk to sales
</LemonButton>
</div>
</>
)}
</>
)}
</div>
{width && width > 500 && (
<div className="shrink-0 relative w-50 pt-4 overflow-hidden">
<BlushingHog className="w-50 h-50 -my-5" />
</div>
)}
{isPurchaseCreditsModalOpen && <PurchaseCreditsModal />}
</div>
)
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/scenes/billing/PurchaseCreditsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const PurchaseCreditsModal = (): JSX.Element | null => {
<LemonModal
onClose={() => showPurchaseCreditsModal(false)}
width="max(44vw)"
title="Buy credits in advance and get a discount"
title="Prepay for usage credits and get a discount"
footer={
<>
<LemonButton
Expand Down Expand Up @@ -49,7 +49,7 @@ export const PurchaseCreditsModal = (): JSX.Element | null => {
<Form formKey="creditForm" logic={billingLogic} enableFormOnSubmit>
<div className="flex flex-col gap-3.5">
<p className="mb-0">
We're giving you the option to buy credits in advance at discount of up to 30%.
We're giving you the option to buy usage credits in advance at discount of up to 30%.
</p>

<p className="mb-0">
Expand All @@ -61,7 +61,7 @@ export const PurchaseCreditsModal = (): JSX.Element | null => {
maximumFractionDigits: 0,
})}
</b>{' '}
of credits per months, for a total of{' '}
of credits per month, for a total of{' '}
<b>
$
{(+creditOverview.estimated_monthly_credit_amount_usd * 12).toLocaleString('en-US', {
Expand Down
50 changes: 26 additions & 24 deletions frontend/src/scenes/billing/billingLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FieldNamePath, forms } from 'kea-forms'
import { loaders } from 'kea-loaders'
import { router, urlToAction } from 'kea-router'
import api, { getJSONOrNull } from 'lib/api'
import { FEATURE_FLAGS } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { LemonBannerAction } from 'lib/lemon-ui/LemonBanner/LemonBanner'
import { lemonBannerLogic } from 'lib/lemon-ui/LemonBanner/lemonBannerLogic'
Expand Down Expand Up @@ -87,13 +88,15 @@ export const billingLogic = kea<billingLogicType>([
reportCreditsModalShown: true,
reportBillingShown: true,
registerInstrumentationProps: true,
reportCreditsCTAShown: (creditOverview: any) => ({ creditOverview }),
setRedirectPath: true,
setIsOnboarding: true,
determineBillingAlert: true,
setUnsubscribeError: (error: null | UnsubscribeError) => ({ error }),
resetUnsubscribeError: true,
setBillingAlert: (billingAlert: BillingAlertConfig | null) => ({ billingAlert }),
showPurchaseCreditsModal: (isOpen: boolean) => ({ isOpen }),
toggleCreditCTAHeroDismissed: (isDismissed: boolean) => ({ isDismissed }),
setComputedDiscount: (discount: number) => ({ discount }),
}),
connect(() => ({
Expand Down Expand Up @@ -189,6 +192,13 @@ export const billingLogic = kea<billingLogicType>([
showPurchaseCreditsModal: (_, { isOpen }) => isOpen,
},
],
isCreditCTAHeroDismissed: [
false,
{ persist: true },
{
toggleCreditCTAHeroDismissed: (_, { isDismissed }) => isDismissed,
},
],
computedDiscount: [
0,
{
Expand Down Expand Up @@ -329,6 +339,14 @@ export const billingLogic = kea<billingLogicType>([
Math.round(response.estimated_monthly_credit_amount_usd * 12)
)
}

if (
response.eligible &&
response.status === 'none' &&
values.featureFlags[FEATURE_FLAGS.PURCHASE_CREDITS]
) {
actions.reportCreditsCTAShown(response)
}
return response
}
// Return default values if not subscribed
Expand Down Expand Up @@ -376,29 +394,6 @@ export const billingLogic = kea<billingLogicType>([
return projectedTotal
},
],
over20kAnnual: [
(s) => [s.billing, s.preflight, s.projectedTotalAmountUsdWithBillingLimits],
(billing, preflight, projectedTotalAmountUsd) => {
if (!billing || !preflight?.cloud) {
return
}
if (
billing.current_total_amount_usd_after_discount &&
(parseFloat(billing.current_total_amount_usd_after_discount) > 1666 ||
projectedTotalAmountUsd > 1666) &&
billing.billing_period?.interval === 'month'
) {
return true
}
return false
},
],
isAnnualPlan: [
(s) => [s.billing],
(billing) => {
return billing?.billing_period?.interval === 'year'
},
],
supportPlans: [
(s) => [s.billing],
(billing: BillingType): BillingPlanType[] => {
Expand Down Expand Up @@ -530,7 +525,14 @@ export const billingLogic = kea<billingLogicType>([
},
reportCreditsFormSubmitted: ({ creditInput }) => {
posthog.capture('credits modal credit form submitted', {
creditInput,
credit_amount_usd: creditInput,
})
},
reportCreditsCTAShown: ({ creditOverview }) => {
posthog.capture('credits cta shown', {
eligible: creditOverview.eligible,
status: creditOverview.status,
estimated_monthly_credit_amount_usd: creditOverview.estimated_monthly_credit_amount_usd,
})
},
loadBillingSuccess: () => {
Expand Down
Loading

0 comments on commit 2d0ade4

Please sign in to comment.