2
0

🛂 Add new yearly plans and graduated pricing

BREAKING CHANGE: Stripe environment variables have changed. New ones are required. Check out the new Stripe configuration in the
docs.

Closes #457
This commit is contained in:
Baptiste Arnaud
2023-04-13 11:39:10 +02:00
parent 39d0dba18c
commit 2cbf8348c3
33 changed files with 1257 additions and 1399 deletions

View File

@@ -15,10 +15,9 @@ import {
useColorModeValue,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { Plan } from '@typebot.io/prisma'
import { useEffect, useState } from 'react'
import { parseNumberWithCommas } from '@typebot.io/lib'
import { isDefined, parseNumberWithCommas } from '@typebot.io/lib'
import {
chatsLimit,
computePrice,
@@ -30,12 +29,23 @@ import {
import { FeaturesList } from './FeaturesList'
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
import { useI18n, useScopedI18n } from '@/locales'
import { Workspace } from '@typebot.io/schemas'
type Props = {
initialChatsLimitIndex?: number
initialStorageLimitIndex?: number
workspace: Pick<
Workspace,
| 'additionalChatsIndex'
| 'additionalStorageIndex'
| 'plan'
| 'customChatsLimit'
| 'customStorageLimit'
>
currentSubscription: {
isYearly?: boolean
}
currency?: 'usd' | 'eur'
isLoading: boolean
isYearly: boolean
onPayClick: (props: {
selectedChatsLimitIndex: number
selectedStorageLimitIndex: number
@@ -43,15 +53,15 @@ type Props = {
}
export const ProPlanPricingCard = ({
initialChatsLimitIndex,
initialStorageLimitIndex,
workspace,
currentSubscription,
currency,
isLoading,
isYearly,
onPayClick,
}: Props) => {
const t = useI18n()
const scopedT = useScopedI18n('billing.pricingCard')
const { workspace } = useWorkspace()
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>()
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
@@ -59,20 +69,23 @@ export const ProPlanPricingCard = ({
useEffect(() => {
if (
selectedChatsLimitIndex === undefined &&
initialChatsLimitIndex !== undefined
isDefined(selectedChatsLimitIndex) ||
isDefined(selectedStorageLimitIndex)
)
setSelectedChatsLimitIndex(initialChatsLimitIndex)
if (
selectedStorageLimitIndex === undefined &&
initialStorageLimitIndex !== undefined
)
setSelectedStorageLimitIndex(initialStorageLimitIndex)
return
if (workspace.plan !== Plan.PRO) {
setSelectedChatsLimitIndex(0)
setSelectedStorageLimitIndex(0)
return
}
setSelectedChatsLimitIndex(workspace.additionalChatsIndex ?? 0)
setSelectedStorageLimitIndex(workspace.additionalStorageIndex ?? 0)
}, [
initialChatsLimitIndex,
initialStorageLimitIndex,
selectedChatsLimitIndex,
selectedStorageLimitIndex,
workspace.additionalChatsIndex,
workspace.additionalStorageIndex,
workspace?.plan,
])
const workspaceChatsLimit = workspace ? getChatsLimit(workspace) : undefined
@@ -81,14 +94,11 @@ export const ProPlanPricingCard = ({
: undefined
const isCurrentPlan =
chatsLimit[Plan.PRO].totalIncluded +
chatsLimit[Plan.PRO].increaseStep.amount *
(selectedChatsLimitIndex ?? 0) ===
workspaceChatsLimit &&
storageLimit[Plan.PRO].totalIncluded +
storageLimit[Plan.PRO].increaseStep.amount *
(selectedStorageLimitIndex ?? 0) ===
workspaceStorageLimit
chatsLimit[Plan.PRO].graduatedPrice[selectedChatsLimitIndex ?? 0]
.totalIncluded === workspaceChatsLimit &&
storageLimit[Plan.PRO].graduatedPrice[selectedStorageLimitIndex ?? 0]
.totalIncluded === workspaceStorageLimit &&
isYearly === currentSubscription?.isYearly
const getButtonLabel = () => {
if (
@@ -100,8 +110,8 @@ export const ProPlanPricingCard = ({
if (isCurrentPlan) return scopedT('upgradeButton.current')
if (
selectedChatsLimitIndex !== initialChatsLimitIndex ||
selectedStorageLimitIndex !== initialStorageLimitIndex
selectedChatsLimitIndex !== workspace.additionalChatsIndex ||
selectedStorageLimitIndex !== workspace.additionalStorageIndex
)
return t('update')
}
@@ -149,7 +159,11 @@ export const ProPlanPricingCard = ({
<Stack spacing="4" mt={2}>
<Heading fontSize="2xl">
{scopedT('heading', {
plan: <chakra.span color="blue.400">Pro</chakra.span>,
plan: (
<chakra.span color={useColorModeValue('blue.400', 'blue.300')}>
Pro
</chakra.span>
),
})}
</Heading>
<Text>{scopedT('pro.description')}</Text>
@@ -160,7 +174,8 @@ export const ProPlanPricingCard = ({
computePrice(
Plan.PRO,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
selectedStorageLimitIndex ?? 0,
isYearly ? 'yearly' : 'monthly'
) ?? NaN,
currency
)}
@@ -201,50 +216,21 @@ export const ProPlanPricingCard = ({
>
{selectedChatsLimitIndex !== undefined
? parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount *
selectedChatsLimitIndex
chatsLimit.PRO.graduatedPrice[
selectedChatsLimitIndex
].totalIncluded
)
: undefined}
</MenuButton>
<MenuList>
{selectedChatsLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(0)}>
{parseNumberWithCommas(chatsLimit.PRO.totalIncluded)}
{chatsLimit.PRO.graduatedPrice.map((price, index) => (
<MenuItem
key={index}
onClick={() => setSelectedChatsLimitIndex(index)}
>
{parseNumberWithCommas(price.totalIncluded)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 1 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(1)}>
{parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 2 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(2)}>
{parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount * 2
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 3 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(3)}>
{parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount * 3
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 4 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(4)}>
{parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount * 4
)}
</MenuItem>
)}
))}
</MenuList>
</Menu>{' '}
{scopedT('chatsPerMonth')}
@@ -262,62 +248,21 @@ export const ProPlanPricingCard = ({
>
{selectedStorageLimitIndex !== undefined
? parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount *
selectedStorageLimitIndex
storageLimit.PRO.graduatedPrice[
selectedStorageLimitIndex
].totalIncluded
)
: undefined}
</MenuButton>
<MenuList>
{selectedStorageLimitIndex !== 0 && (
{storageLimit.PRO.graduatedPrice.map((price, index) => (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(0)}
key={index}
onClick={() => setSelectedStorageLimitIndex(index)}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded
)}
{parseNumberWithCommas(price.totalIncluded)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 1 && (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(1)}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 2 && (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(2)}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount * 2
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 3 && (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(3)}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount * 3
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 4 && (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(4)}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount * 4
)}
</MenuItem>
)}
))}
</MenuList>
</Menu>{' '}
{scopedT('storageLimit')}