2
0

(lp) Add custom chats and storage in pricing cards

This commit is contained in:
Baptiste Arnaud
2022-10-19 08:29:33 +02:00
parent 7b0bd08dc8
commit 57c814ceca
6 changed files with 497 additions and 171 deletions

View File

@ -18,15 +18,19 @@ const DashboardPage = () => {
const { workspace } = useWorkspace() const { workspace } = useWorkspace()
useEffect(() => { useEffect(() => {
const subscribePlan = query.subscribePlan as Plan | undefined const { subscribePlan, chats, storage } = query as {
subscribePlan: Plan | undefined
chats: string | undefined
storage: string | undefined
}
if (workspace && subscribePlan && user && workspace.plan === 'FREE') { if (workspace && subscribePlan && user && workspace.plan === 'FREE') {
setIsLoading(true) setIsLoading(true)
pay({ pay({
user, user,
plan: subscribePlan, plan: subscribePlan,
workspaceId: workspace.id, workspaceId: workspace.id,
additionalChats: 0, additionalChats: chats ? parseInt(chats) : 0,
additionalStorage: 0, additionalStorage: storage ? parseInt(storage) : 0,
}) })
} }
}, [query, user, workspace]) }, [query, user, workspace])

View File

@ -0,0 +1,45 @@
import { chakra, Tooltip, Text } from '@chakra-ui/react'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import React from 'react'
import { PricingCard } from './PricingCard'
import { ActionButton } from './PricingCard/ActionButton'
export const FreePlanCard = () => (
<PricingCard
data={{
price: 'Free',
name: 'Personal',
features: [
'Unlimited typebots',
<>
<Text>
<chakra.span fontWeight="bold">300 chats</chakra.span> included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
'Native integrations',
'Webhooks',
'Custom Javascript & CSS',
],
}}
button={
<NextChakraLink
href="https://app.typebot.io/register"
_hover={{ textDecor: 'none' }}
>
<ActionButton variant="outline">Get started</ActionButton>
</NextChakraLink>
}
/>
)

View File

@ -42,7 +42,7 @@ export const PlanComparisonTables = ({
</Th> </Th>
<Th>Free</Th> <Th>Free</Th>
<Th color="orange.200">Starter</Th> <Th color="orange.200">Starter</Th>
<Th color="purple.200">Pro</Th> <Th color="blue.200">Pro</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
@ -100,7 +100,7 @@ export const PlanComparisonTables = ({
</Th> </Th>
<Th>Free</Th> <Th>Free</Th>
<Th color="orange.200">Starter</Th> <Th color="orange.200">Starter</Th>
<Th color="purple.200">Pro</Th> <Th color="blue.200">Pro</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>

View File

@ -0,0 +1,216 @@
import {
chakra,
Tooltip,
Text,
Button,
HStack,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react'
import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import { Plan } from 'db'
import React, { useEffect, useState } from 'react'
import {
chatsLimit,
computePrice,
formatPrice,
parseNumberWithCommas,
storageLimit,
} from 'utils'
import { PricingCard } from './PricingCard'
import { ActionButton } from './PricingCard/ActionButton'
export const ProPlanCard = () => {
const [price, setPrice] = useState('89$')
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>(0)
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>(0)
useEffect(() => {
setPrice(
formatPrice(
computePrice(
Plan.PRO,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)
)
}, [selectedChatsLimitIndex, selectedStorageLimitIndex])
return (
<PricingCard
data={{
price,
name: 'Pro',
featureLabel: 'Everything in Personal, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">5 seats</chakra.span> included
</Text>,
<HStack key="chats">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronDownIcon />}
size="sm"
variant="outline"
isLoading={selectedChatsLimitIndex === undefined}
>
{selectedChatsLimitIndex !== undefined
? parseNumberWithCommas(
chatsLimit.PRO.totalIncluded +
chatsLimit.PRO.increaseStep.amount *
selectedChatsLimitIndex
)
: undefined}
</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>
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</HStack>,
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronDownIcon />}
size="sm"
variant="outline"
isLoading={selectedStorageLimitIndex === undefined}
>
{selectedStorageLimitIndex !== undefined
? parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount *
selectedStorageLimitIndex
)
: undefined}
</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>
<Tooltip
hasArrow
placement="top"
label="You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</HStack>,
'Custom domains',
'In-depth analytics',
],
}}
borderWidth="3px"
borderColor="blue.200"
button={
<NextChakraLink
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}`}
_hover={{ textDecor: 'none' }}
>
<ActionButton>Subscribe now</ActionButton>
</NextChakraLink>
}
/>
)
}

View File

@ -0,0 +1,221 @@
import {
chakra,
Tooltip,
Text,
HStack,
Menu,
MenuButton,
Button,
MenuItem,
MenuList,
} from '@chakra-ui/react'
import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import { Plan } from 'db'
import React, { useEffect, useState } from 'react'
import {
chatsLimit,
computePrice,
formatPrice,
parseNumberWithCommas,
storageLimit,
} from 'utils'
import { PricingCard } from './PricingCard'
import { ActionButton } from './PricingCard/ActionButton'
export const StarterPlanCard = () => {
const [price, setPrice] = useState('39$')
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>(0)
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>(0)
useEffect(() => {
setPrice(
formatPrice(
computePrice(
Plan.STARTER,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)
)
}, [selectedChatsLimitIndex, selectedStorageLimitIndex])
return (
<PricingCard
data={{
price,
name: 'Starter',
featureLabel: 'Everything in Personal, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">2 seats</chakra.span> included
</Text>,
<HStack key="chats">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronDownIcon />}
size="sm"
variant="outline"
isLoading={selectedChatsLimitIndex === undefined}
colorScheme="orange"
>
{selectedChatsLimitIndex !== undefined
? parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount *
selectedChatsLimitIndex
)
: undefined}
</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>
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</HStack>,
<HStack key="test">
<Text>
<Menu>
<MenuButton
as={Button}
rightIcon={<ChevronDownIcon />}
size="sm"
variant="outline"
isLoading={selectedStorageLimitIndex === undefined}
colorScheme="orange"
>
{selectedStorageLimitIndex !== undefined
? parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount *
selectedStorageLimitIndex
)
: undefined}
</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>
<Tooltip
hasArrow
placement="top"
label="You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</HStack>,
'Branding removed',
'Collect files from users',
'Create folders',
],
}}
borderWidth="1px"
borderColor="orange.200"
button={
<NextChakraLink
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}`}
_hover={{ textDecor: 'none' }}
>
<ActionButton>Subscribe now</ActionButton>
</NextChakraLink>
}
/>
)
}

View File

@ -11,23 +11,20 @@ import {
Heading, Heading,
VStack, VStack,
Text, Text,
chakra,
Tooltip,
HStack, HStack,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { Footer } from 'components/common/Footer' import { Footer } from 'components/common/Footer'
import { Header } from 'components/common/Header/Header' import { Header } from 'components/common/Header/Header'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink' import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import { SocialMetaTags } from 'components/common/SocialMetaTags' import { SocialMetaTags } from 'components/common/SocialMetaTags'
import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons' import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons'
import { PlanComparisonTables } from 'components/PricingPage/PlanComparisonTables' import { PlanComparisonTables } from 'components/PricingPage/PlanComparisonTables'
import { PricingCard } from 'components/PricingPage/PricingCard'
import { ActionButton } from 'components/PricingPage/PricingCard/ActionButton'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { formatPrice, prices } from 'utils' import { formatPrice, prices } from 'utils'
import { Plan } from 'db'
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo' import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
import { FreePlanCard } from 'components/PricingPage/FreePlanCard'
import { StarterPlanCard } from 'components/PricingPage/StarterPlanCard'
import { ProPlanCard } from 'components/PricingPage/ProPlanCard'
const Pricing = () => { const Pricing = () => {
const [starterPrice, setStarterPrice] = useState('$39') const [starterPrice, setStarterPrice] = useState('$39')
@ -101,166 +98,9 @@ const Pricing = () => {
w="full" w="full"
maxW="1200px" maxW="1200px"
> >
<PricingCard <FreePlanCard />
data={{ <StarterPlanCard />
price: 'Free', <ProPlanCard />
name: 'Personal',
features: [
'Unlimited typebots',
<>
<Text>
<chakra.span fontWeight="bold">300 chats</chakra.span>{' '}
included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
'Native integrations',
'Webhooks',
'Custom Javascript & CSS',
],
}}
button={
<NextChakraLink
href="https://app.typebot.io/register"
_hover={{ textDecor: 'none' }}
>
<ActionButton variant="outline">Get started</ActionButton>
</NextChakraLink>
}
/>
<PricingCard
data={{
price: starterPrice,
name: 'Starter',
featureLabel: 'Everything in Personal, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">2 seats</chakra.span>{' '}
included
</Text>,
<>
<Text>
<chakra.span fontWeight="bold">2,000 chats</chakra.span>{' '}
included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
<>
<Text>
<chakra.span fontWeight="bold">2 GB</chakra.span>{' '}
included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
'Branding removed',
'Collect files from users',
'Create folders',
],
}}
borderWidth="1px"
borderColor="orange.200"
button={
<NextChakraLink
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}`}
_hover={{ textDecor: 'none' }}
>
<ActionButton>Subscribe now</ActionButton>
</NextChakraLink>
}
/>
<PricingCard
data={{
price: proPrice,
name: 'Pro',
featureLabel: 'Everything in Starter, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">5 seats</chakra.span>{' '}
included
</Text>,
<>
<Text>
<chakra.span fontWeight="bold">
10,000 chats
</chakra.span>{' '}
included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
<>
<Text>
<chakra.span fontWeight="bold">10 GB</chakra.span>{' '}
included
</Text>
&nbsp;
<Tooltip
hasArrow
placement="top"
label="You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space."
>
<chakra.span cursor="pointer" h="7">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
</>,
'Custom domains',
'In-depth analytics',
],
}}
borderWidth="3px"
borderColor="blue.200"
button={
<NextChakraLink
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}`}
_hover={{ textDecor: 'none' }}
>
<ActionButton>Subscribe now</ActionButton>
</NextChakraLink>
}
/>
</Stack> </Stack>
<Text fontSize="lg"> <Text fontSize="lg">
Need custom limits? Specific features?{' '} Need custom limits? Specific features?{' '}