2
0

Add usage-based new pricing plans

This commit is contained in:
Baptiste Arnaud
2022-09-17 16:37:33 +02:00
committed by Baptiste Arnaud
parent 6a1eaea700
commit 898367a33b
144 changed files with 4631 additions and 1624 deletions

View File

@@ -0,0 +1,108 @@
import { Stack, HStack, Text } from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useUser } from 'contexts/UserContext'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Plan } from 'db'
import { useToast } from '../hooks/useToast'
import { ProPlanContent } from './ProPlanContent'
import { pay } from './queries/updatePlan'
import { useCurrentSubscriptionInfo } from './queries/useCurrentSubscriptionInfo'
import { StarterPlanContent } from './StarterPlanContent'
export const ChangePlanForm = () => {
const { user } = useUser()
const { workspace, refreshWorkspace } = useWorkspace()
const { showToast } = useToast()
const { data, mutate: refreshCurrentSubscriptionInfo } =
useCurrentSubscriptionInfo({
stripeId: workspace?.stripeId,
plan: workspace?.plan,
})
const handlePayClick = async ({
plan,
selectedChatsLimitIndex,
selectedStorageLimitIndex,
}: {
plan: 'STARTER' | 'PRO'
selectedChatsLimitIndex: number
selectedStorageLimitIndex: number
}) => {
if (
!user ||
!workspace ||
selectedChatsLimitIndex === undefined ||
selectedStorageLimitIndex === undefined
)
return
await pay({
stripeId: workspace.stripeId ?? undefined,
user,
plan,
workspaceId: workspace.id,
additionalChats: selectedChatsLimitIndex,
additionalStorage: selectedStorageLimitIndex,
})
refreshCurrentSubscriptionInfo({
additionalChatsIndex: selectedChatsLimitIndex,
additionalStorageIndex: selectedStorageLimitIndex,
})
refreshWorkspace({
plan,
additionalChatsIndex: selectedChatsLimitIndex,
additionalStorageIndex: selectedStorageLimitIndex,
})
showToast({
status: 'success',
description: `Workspace ${plan} plan successfully updated 🎉`,
})
}
return (
<Stack spacing={4}>
<HStack
alignItems="stretch"
spacing="4"
w="full"
pt={
workspace?.plan === Plan.STARTER || workspace?.plan === Plan.PRO
? '10'
: '0'
}
>
<StarterPlanContent
initialChatsLimitIndex={
workspace?.plan === Plan.STARTER ? data?.additionalChatsIndex : 0
}
initialStorageLimitIndex={
workspace?.plan === Plan.STARTER ? data?.additionalStorageIndex : 0
}
onPayClick={(props) =>
handlePayClick({ ...props, plan: Plan.STARTER })
}
/>
<ProPlanContent
initialChatsLimitIndex={
workspace?.plan === Plan.PRO ? data?.additionalChatsIndex : 0
}
initialStorageLimitIndex={
workspace?.plan === Plan.PRO ? data?.additionalStorageIndex : 0
}
onPayClick={(props) => handlePayClick({ ...props, plan: Plan.PRO })}
/>
</HStack>
<Text color="gray.500">
Need custom limits? Specific features?{' '}
<NextChakraLink
href={'https://typebot.io/enterprise-lead-form'}
isExternal
textDecor="underline"
>
Let me know
</NextChakraLink>
.
</Text>
</Stack>
)
}

View File

@@ -0,0 +1,339 @@
import {
Stack,
Heading,
chakra,
HStack,
Menu,
MenuButton,
Button,
MenuList,
MenuItem,
Text,
Tooltip,
Flex,
Tag,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from 'assets/icons'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Plan } from 'db'
import { useEffect, useState } from 'react'
import {
chatsLimit,
getChatsLimit,
getStorageLimit,
storageLimit,
parseNumberWithCommas,
} from 'utils'
import { MoreInfoTooltip } from '../MoreInfoTooltip'
import { FeaturesList } from './components/FeaturesList'
import { computePrice, formatPrice } from './helpers'
type ProPlanContentProps = {
initialChatsLimitIndex?: number
initialStorageLimitIndex?: number
onPayClick: (props: {
selectedChatsLimitIndex: number
selectedStorageLimitIndex: number
}) => Promise<void>
}
export const ProPlanContent = ({
initialChatsLimitIndex,
initialStorageLimitIndex,
onPayClick,
}: ProPlanContentProps) => {
const { workspace } = useWorkspace()
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>()
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>()
const [isPaying, setIsPaying] = useState(false)
useEffect(() => {
if (
selectedChatsLimitIndex === undefined &&
initialChatsLimitIndex !== undefined
)
setSelectedChatsLimitIndex(initialChatsLimitIndex)
if (
selectedStorageLimitIndex === undefined &&
initialStorageLimitIndex !== undefined
)
setSelectedStorageLimitIndex(initialStorageLimitIndex)
}, [
initialChatsLimitIndex,
initialStorageLimitIndex,
selectedChatsLimitIndex,
selectedStorageLimitIndex,
])
const workspaceChatsLimit = workspace ? getChatsLimit(workspace) : undefined
const workspaceStorageLimit = workspace
? getStorageLimit(workspace)
: undefined
console.log('workspaceChatsLimit', workspaceChatsLimit)
console.log('workspaceStorageLimit', workspace)
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
const getButtonLabel = () => {
if (
selectedChatsLimitIndex === undefined ||
selectedStorageLimitIndex === undefined
)
return ''
if (workspace?.plan === Plan.PRO) {
if (isCurrentPlan) return 'Your current plan'
if (
selectedChatsLimitIndex !== initialChatsLimitIndex ||
selectedStorageLimitIndex !== initialStorageLimitIndex
)
return 'Update'
}
return 'Upgrade'
}
const handlePayClick = async () => {
if (
selectedChatsLimitIndex === undefined ||
selectedStorageLimitIndex === undefined
)
return
setIsPaying(true)
await onPayClick({
selectedChatsLimitIndex,
selectedStorageLimitIndex,
})
setIsPaying(false)
}
return (
<Flex
p="6"
pos="relative"
h="full"
flexDir="column"
flex="1"
flexShrink={0}
borderWidth="1px"
borderColor="blue.500"
rounded="lg"
>
<Flex justifyContent="center">
<Tag
pos="absolute"
top="-10px"
colorScheme="blue"
variant="solid"
fontWeight="semibold"
style={{ marginTop: 0 }}
>
Most popular
</Tag>
</Flex>
<Stack justifyContent="space-between" h="full">
<Stack spacing="4" mt={2}>
<Heading fontSize="2xl">
Upgrade to <chakra.span color="blue.400">Pro</chakra.span>
</Heading>
<Text>For agencies & growing startups.</Text>
</Stack>
<Stack spacing="4">
<Heading>
{formatPrice(
computePrice(
Plan.PRO,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)}
<chakra.span fontSize="md">/ month</chakra.span>
</Heading>
<Text fontWeight="bold">
<Tooltip
label={
<FeaturesList
features={[
'Branding removed',
'File upload input block',
'Create folders',
]}
spacing="0"
/>
}
hasArrow
placement="top"
>
<chakra.span textDecoration="underline" cursor="pointer">
Everything in Starter
</chakra.span>
</Tooltip>
, plus:
</Text>
<FeaturesList
features={[
'5 seats included',
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronLeftIcon transform="rotate(-90deg)" />}
size="sm"
isLoading={selectedChatsLimitIndex === undefined}
>
{parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount *
(selectedChatsLimitIndex ?? 0)
)}
</MenuButton>
<MenuList>
{selectedChatsLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(0)}>
{parseNumberWithCommas(chatsLimit.PRO.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>{' '}
chats/mo
</Text>
<MoreInfoTooltip>
A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives.
</MoreInfoTooltip>
</HStack>,
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronLeftIcon transform="rotate(-90deg)" />}
size="sm"
isLoading={selectedStorageLimitIndex === undefined}
>
{parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount *
(selectedStorageLimitIndex ?? 0)
)}
</MenuButton>
<MenuList>
{selectedStorageLimitIndex !== 0 && (
<MenuItem
onClick={() => setSelectedStorageLimitIndex(0)}
>
{parseNumberWithCommas(
storageLimit.PRO.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>{' '}
GB of storage
</Text>
<MoreInfoTooltip>
You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space.
</MoreInfoTooltip>
</HStack>,
'Custom domains',
'In-depth analytics',
]}
/>
<Button
colorScheme="blue"
variant="outline"
onClick={handlePayClick}
isLoading={isPaying}
isDisabled={isCurrentPlan}
>
{getButtonLabel()}
</Button>
</Stack>
</Stack>
</Flex>
)
}

View File

@@ -0,0 +1,280 @@
import {
Stack,
Heading,
chakra,
HStack,
Menu,
MenuButton,
Button,
MenuList,
MenuItem,
Text,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from 'assets/icons'
import { useWorkspace } from 'contexts/WorkspaceContext'
import { Plan } from 'db'
import { useEffect, useState } from 'react'
import {
chatsLimit,
getChatsLimit,
getStorageLimit,
storageLimit,
parseNumberWithCommas,
} from 'utils'
import { MoreInfoTooltip } from '../MoreInfoTooltip'
import { FeaturesList } from './components/FeaturesList'
import { computePrice, formatPrice } from './helpers'
type StarterPlanContentProps = {
initialChatsLimitIndex?: number
initialStorageLimitIndex?: number
onPayClick: (props: {
selectedChatsLimitIndex: number
selectedStorageLimitIndex: number
}) => Promise<void>
}
export const StarterPlanContent = ({
initialChatsLimitIndex,
initialStorageLimitIndex,
onPayClick,
}: StarterPlanContentProps) => {
const { workspace } = useWorkspace()
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>()
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>()
const [isPaying, setIsPaying] = useState(false)
useEffect(() => {
if (
selectedChatsLimitIndex === undefined &&
initialChatsLimitIndex !== undefined
)
setSelectedChatsLimitIndex(initialChatsLimitIndex)
if (
selectedStorageLimitIndex === undefined &&
initialStorageLimitIndex !== undefined
)
setSelectedStorageLimitIndex(initialStorageLimitIndex)
}, [
initialChatsLimitIndex,
initialStorageLimitIndex,
selectedChatsLimitIndex,
selectedStorageLimitIndex,
])
const workspaceChatsLimit = workspace ? getChatsLimit(workspace) : undefined
const workspaceStorageLimit = workspace
? getStorageLimit(workspace)
: undefined
const isCurrentPlan =
chatsLimit[Plan.STARTER].totalIncluded +
chatsLimit[Plan.STARTER].increaseStep.amount *
(selectedChatsLimitIndex ?? 0) ===
workspaceChatsLimit &&
storageLimit[Plan.STARTER].totalIncluded +
storageLimit[Plan.STARTER].increaseStep.amount *
(selectedStorageLimitIndex ?? 0) ===
workspaceStorageLimit
const getButtonLabel = () => {
if (
selectedChatsLimitIndex === undefined ||
selectedStorageLimitIndex === undefined
)
return ''
if (workspace?.plan === Plan.PRO) return 'Downgrade'
if (workspace?.plan === Plan.STARTER) {
if (isCurrentPlan) return 'Your current plan'
if (
selectedChatsLimitIndex !== initialChatsLimitIndex ||
selectedStorageLimitIndex !== initialStorageLimitIndex
)
return 'Update'
}
return 'Upgrade'
}
const handlePayClick = async () => {
if (
selectedChatsLimitIndex === undefined ||
selectedStorageLimitIndex === undefined
)
return
setIsPaying(true)
await onPayClick({
selectedChatsLimitIndex,
selectedStorageLimitIndex,
})
setIsPaying(false)
}
return (
<Stack spacing={6} p="6" rounded="lg" borderWidth="1px" flex="1" h="full">
<Stack spacing="4">
<Heading fontSize="2xl">
Upgrade to <chakra.span color="orange.400">Starter</chakra.span>
</Heading>
<Text>For individuals & small businesses.</Text>
<Heading>
{formatPrice(
computePrice(
Plan.STARTER,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)}
<chakra.span fontSize="md">/ month</chakra.span>
</Heading>
<FeaturesList
features={[
'2 seats included',
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronLeftIcon transform="rotate(-90deg)" />}
size="sm"
isLoading={selectedChatsLimitIndex === undefined}
>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount *
(selectedChatsLimitIndex ?? 0)
)}
</MenuButton>
<MenuList>
{selectedChatsLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(0)}>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 1 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(1)}>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 2 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(2)}>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount * 2
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 3 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(3)}>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount * 3
)}
</MenuItem>
)}
{selectedChatsLimitIndex !== 4 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(4)}>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount * 4
)}
</MenuItem>
)}
</MenuList>
</Menu>{' '}
chats/mo
</Text>
<MoreInfoTooltip>
A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives.
</MoreInfoTooltip>
</HStack>,
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronLeftIcon transform="rotate(-90deg)" />}
size="sm"
isLoading={selectedStorageLimitIndex === undefined}
>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount *
(selectedStorageLimitIndex ?? 0)
)}
</MenuButton>
<MenuList>
{selectedStorageLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(0)}>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 1 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(1)}>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 2 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(2)}>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount * 2
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 3 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(3)}>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount * 3
)}
</MenuItem>
)}
{selectedStorageLimitIndex !== 4 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(4)}>
{parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount * 4
)}
</MenuItem>
)}
</MenuList>
</Menu>{' '}
GB of storage
</Text>
<MoreInfoTooltip>
You accumulate storage for every file that your user upload into
your bot. If you delete the result, it will free up the space.
</MoreInfoTooltip>
</HStack>,
'Branding removed',
'File upload input block',
'Create folders',
]}
/>
</Stack>
<Button
colorScheme="orange"
variant="outline"
onClick={handlePayClick}
isLoading={isPaying}
isDisabled={isCurrentPlan}
>
{getButtonLabel()}
</Button>
</Stack>
)
}

View File

@@ -0,0 +1,21 @@
import {
ListProps,
UnorderedList,
Flex,
ListItem,
ListIcon,
} from '@chakra-ui/react'
import { CheckIcon } from 'assets/icons'
type FeaturesListProps = { features: (string | JSX.Element)[] } & ListProps
export const FeaturesList = ({ features, ...props }: FeaturesListProps) => (
<UnorderedList listStyleType="none" spacing={2} {...props}>
{features.map((feat, idx) => (
<Flex as={ListItem} key={idx} alignItems="center">
<ListIcon as={CheckIcon} />
{feat}
</Flex>
))}
</UnorderedList>
)

View File

@@ -0,0 +1,86 @@
import { Plan } from 'db'
import { chatsLimit, prices, storageLimit } from 'utils'
export const computePrice = (
plan: Plan,
selectedTotalChatsIndex: number,
selectedTotalStorageIndex: number
) => {
if (plan !== Plan.STARTER && plan !== Plan.PRO) return
const {
increaseStep: { price: chatsPrice },
} = chatsLimit[plan]
const {
increaseStep: { price: storagePrice },
} = storageLimit[plan]
return (
prices[plan] +
selectedTotalChatsIndex * chatsPrice +
selectedTotalStorageIndex * storagePrice
)
}
const europeanUnionCountryCodes = [
'AT',
'BE',
'BG',
'CY',
'CZ',
'DE',
'DK',
'EE',
'ES',
'FI',
'FR',
'GR',
'HR',
'HU',
'IE',
'IT',
'LT',
'LU',
'LV',
'MT',
'NL',
'PL',
'PT',
'RO',
'SE',
'SI',
'SK',
]
const europeanUnionExclusiveLanguageCodes = [
'fr',
'de',
'it',
'el',
'pl',
'fi',
'nl',
'hr',
'cs',
'hu',
'ro',
'sl',
'sv',
'bg',
]
export const guessIfUserIsEuropean = () =>
navigator.languages.some((language) => {
const [languageCode, countryCode] = language.split('-')
return countryCode
? europeanUnionCountryCodes.includes(countryCode)
: europeanUnionExclusiveLanguageCodes.includes(languageCode)
})
export const formatPrice = (price: number) => {
const isEuropean = guessIfUserIsEuropean()
const formatter = new Intl.NumberFormat(isEuropean ? 'fr-FR' : 'en-US', {
style: 'currency',
currency: isEuropean ? 'EUR' : 'USD',
maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
})
return formatter.format(price)
}

View File

@@ -0,0 +1 @@
export { ChangePlanForm } from './ChangePlanForm'

View File

@@ -0,0 +1,66 @@
import { loadStripe } from '@stripe/stripe-js/pure'
import { Plan, User } from 'db'
import { env, isDefined, isEmpty, sendRequest } from 'utils'
import { guessIfUserIsEuropean } from '../helpers'
type UpgradeProps = {
user: User
stripeId?: string
plan: Plan
workspaceId: string
additionalChats: number
additionalStorage: number
}
export const pay = async ({
stripeId,
...props
}: UpgradeProps): Promise<{ newPlan: Plan } | undefined | void> =>
isDefined(stripeId)
? updatePlan({ ...props, stripeId })
: redirectToCheckout(props)
export const updatePlan = async ({
stripeId,
plan,
workspaceId,
additionalChats,
additionalStorage,
}: Omit<UpgradeProps, 'user'>): Promise<{ newPlan: Plan } | undefined> => {
const { data, error } = await sendRequest<{ message: string }>({
method: 'PUT',
url: '/api/stripe/subscription',
body: { workspaceId, plan, stripeId, additionalChats, additionalStorage },
})
if (error || !data) return
return { newPlan: plan }
}
export const redirectToCheckout = async ({
user,
plan,
workspaceId,
additionalChats,
additionalStorage,
}: Omit<UpgradeProps, 'customerId'>) => {
if (isEmpty(env('STRIPE_PUBLIC_KEY')))
throw new Error('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is missing in env')
const { data, error } = await sendRequest<{ sessionId: string }>({
method: 'POST',
url: '/api/stripe/subscription',
body: {
email: user.email,
currency: guessIfUserIsEuropean() ? 'eur' : 'usd',
plan,
workspaceId,
href: location.origin + location.pathname,
additionalChats,
additionalStorage,
},
})
if (error || !data) return
const stripe = await loadStripe(env('STRIPE_PUBLIC_KEY') as string)
await stripe?.redirectToCheckout({
sessionId: data?.sessionId,
})
}

View File

@@ -0,0 +1,30 @@
import { Plan } from 'db'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
export const useCurrentSubscriptionInfo = ({
stripeId,
plan,
}: {
stripeId?: string | null
plan?: Plan
}) => {
const { data, mutate } = useSWR<
{
additionalChatsIndex: number
additionalStorageIndex: number
},
Error
>(
stripeId && (plan === Plan.STARTER || plan === Plan.PRO)
? `/api/stripe/subscription?stripeId=${stripeId}`
: null,
fetcher
)
return {
data: !stripeId
? { additionalChatsIndex: 0, additionalStorageIndex: 0 }
: data,
mutate,
}
}