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

@ -2,41 +2,36 @@ import Icon, { IconProps } from '@chakra-ui/icon'
import React from 'react'
export const Logo = (props: IconProps) => (
<Icon
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<rect width="500" height="500" rx="75" fill={'#0042DA'} />
<Icon w="50px" h="50px" viewBox="0 0 800 800" {...props}>
<rect width="800" height="800" rx="80" fill={'#0042DA'} />
<rect
x="438.709"
y="170.968"
width="64.5161"
height="290.323"
rx="32.2581"
transform="rotate(90 438.709 170.968)"
x="650"
y="293"
width="85.4704"
height="384.617"
rx="20"
transform="rotate(90 650 293)"
fill="#FF8E20"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M93.5481 235.484C111.364 235.484 125.806 221.041 125.806 203.226C125.806 185.41 111.364 170.968 93.5481 170.968C75.7325 170.968 61.29 185.41 61.29 203.226C61.29 221.041 75.7325 235.484 93.5481 235.484Z"
d="M192.735 378.47C216.337 378.47 235.47 359.337 235.47 335.735C235.47 312.133 216.337 293 192.735 293C169.133 293 150 312.133 150 335.735C150 359.337 169.133 378.47 192.735 378.47Z"
fill="#FF8E20"
/>
<rect
x="61.29"
y="332.259"
width="64.5161"
height="290.323"
rx="32.2581"
transform="rotate(-90 61.29 332.259)"
x="150"
y="506.677"
width="85.4704"
height="384.617"
rx="20"
transform="rotate(-90 150 506.677)"
fill={'white'}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M406.451 267.742C388.635 267.742 374.193 282.184 374.193 300C374.193 317.815 388.635 332.258 406.451 332.258C424.267 332.258 438.709 317.815 438.709 300C438.709 282.184 424.267 267.742 406.451 267.742Z"
d="M607.265 421.206C583.663 421.206 564.53 440.34 564.53 463.942C564.53 487.544 583.663 506.677 607.265 506.677C630.867 506.677 650 487.544 650 463.942C650 440.34 630.867 421.206 607.265 421.206Z"
fill={'white'}
/>
</Icon>

View File

@ -33,7 +33,7 @@ export const Hero = () => {
bgClip="text"
data-aos="fade-up"
>
Open-source conversational forms builder
Build advanced chatbots visually
</Heading>
<Text
fontSize={['lg', 'xl']}

View File

@ -49,7 +49,7 @@ export const IntroducingChatApps = () => {
textAlign="center"
data-aos="fade"
>
Introducing Conversational Apps
Replace your old school forms with chatbots
</Heading>
<Text
textAlign="center"

View File

@ -0,0 +1,57 @@
import {
Stack,
Heading,
Button,
List,
ListItem,
ListIcon,
Text,
Link,
} from '@chakra-ui/react'
import { CheckCircleIcon } from 'assets/icons'
export const EnterprisePlanCard = () => (
<Stack
direction={['column', 'row']}
align="center"
p="10"
rounded="lg"
bgColor="gray.800"
borderWidth="2px"
spacing={10}
>
<Stack maxW="300px" spacing={4}>
<Heading fontSize="xl">Enterprise</Heading>
<Text>
Ideal for large companies looking to generate leads and automate
customer support at scale
</Text>
<Text fontSize="lg">
<Button
as={Link}
href="https://typebot.io/enterprise-lead-form"
isExternal
variant="outline"
>
Get a quote
</Button>
</Text>
</Stack>
<Stack flex="1">
<List spacing="4">
<ListItem fontWeight="medium" display="flex" alignItems="center">
<ListIcon fontSize="xl" as={CheckCircleIcon} marginEnd={2} />
Custom chats limits & seats for all your team
</ListItem>
<ListItem fontWeight="medium" display="flex" alignItems="center">
<ListIcon fontSize="xl" as={CheckCircleIcon} marginEnd={2} />
SSO & Granular access rights
</ListItem>
<ListItem fontWeight="medium" display="flex" alignItems="center">
<ListIcon fontSize="xl" as={CheckCircleIcon} marginEnd={2} />
Yearly contract with dedicated support representative
</ListItem>
</List>
</Stack>
</Stack>
)

View File

@ -0,0 +1,86 @@
import { Heading, VStack, SimpleGrid, Stack, Text } from '@chakra-ui/react'
export const Faq = () => (
<VStack w="full" spacing="10">
<Heading textAlign="center">Frequently asked questions</Heading>
<SimpleGrid columns={[1, 2]} spacing={10}>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
What is considered a monthly chat?
</Heading>
<Text>
A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives. For
example if a user starts a discussion and sends 10 messages to the
bot, it will count as 1 chat. If the user chats again later and its
session is remembered, it will not be counted as a new chat. <br />
<br />
An easy way to think about it: 1 chat equals to a row in your Results
table
</Text>
</Stack>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
What happens once I reach the monthly chats limit?
</Heading>
<Text>
When you exceed the number of chats included in your plan, you will
receive a heads up by email. There won&apos;t be any immediate
additional charges and your bots will continue to run. If you continue
to exceed the limit, you will be kindly asked you to upgrade your
subscription.
</Text>
</Stack>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
What is considered as storage?
</Heading>
<Text>
You accumulate storage for every file that your user upload into your
bot. If you delete the associated result, it will free up the used
space.
</Text>
</Stack>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
What happens once I reach the storage limit?
</Heading>
<Text>
When you exceed the storage size included in your plan, you will
receive a heads up by email. There won&apos;t be any immediate
additional charges and your bots will continue to store new files. If
you continue to exceed the limit, you will be kindly asked you to
upgrade your subscription.
</Text>
</Stack>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
Can I cancel or change my subscription any time?
</Heading>
<Text>
Yes, you can cancel, upgrade or downgrade your subscription at any
time. There is no minimum time commitment or lock-in.
<br />
<br />
When you upgrade or downgrade your subscription, you&apos;ll get
access to the new options right away. Your next invoice will have a
prorated amount.
</Text>
</Stack>
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
<Heading as="h2" fontSize="2xl">
Do you offer annual payments?
</Heading>
<Text>
Yes. Starter and Pro plans can be purchased with monthly or annual
billing.
<br />
<br />
Annual plans are cheaper and give you a 16% discount compared to
monthly payments. Enterprise plans are only available with annual
billing.
</Text>
</Stack>
</SimpleGrid>
</VStack>
)

View File

@ -3,6 +3,7 @@ import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import Link from 'next/link'
import React from 'react'
import { PricingCard } from './PricingCard'
import { chatsLimit } from '@typebot.io/lib/pricing'
export const FreePlanCard = () => (
<PricingCard
@ -13,7 +14,10 @@ export const FreePlanCard = () => (
'Unlimited typebots',
<>
<Text>
<chakra.span fontWeight="bold">300 chats</chakra.span> included
<chakra.span fontWeight="bold">
{chatsLimit.FREE.totalIncluded}
</chakra.span>{' '}
chats/month
</Text>
&nbsp;
<Tooltip
@ -37,7 +41,7 @@ export const FreePlanCard = () => (
as={Link}
href="https://app.typebot.io/register"
variant="outline"
colorScheme="blue"
colorScheme="gray"
size="lg"
w="full"
fontWeight="extrabold"

View File

@ -8,7 +8,6 @@ import {
Td,
Text,
Stack,
StackProps,
HStack,
Tooltip,
chakra,
@ -19,367 +18,383 @@ import { CheckIcon } from 'assets/icons/CheckIcon'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { Plan } from '@typebot.io/prisma'
import Link from 'next/link'
import React, { useEffect, useState } from 'react'
import { chatsLimit, formatPrice, storageLimit } from '@typebot.io/lib/pricing'
import React from 'react'
import {
chatsLimit,
formatPrice,
prices,
seatsLimit,
storageLimit,
} from '@typebot.io/lib/pricing'
import { parseNumberWithCommas } from '@typebot.io/lib'
type Props = {
starterPrice: string
proPrice: string
} & StackProps
export const PlanComparisonTables = ({
starterPrice,
proPrice,
...props
}: Props) => {
const [additionalChatsPrice, setAdditionalChatsPrice] = useState(
`$${chatsLimit.STARTER.increaseStep.price}`
)
const [additionalStoragePrice, setAdditionalStoragePrice] = useState(
`$${storageLimit.STARTER.increaseStep.price}`
)
useEffect(() => {
setAdditionalChatsPrice(formatPrice(chatsLimit.STARTER.increaseStep.price))
setAdditionalStoragePrice(
formatPrice(storageLimit.STARTER.increaseStep.price)
)
}, [])
return (
<Stack spacing="12" {...props}>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Usage
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>Total bots</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
<Tr>
<Td>Chats</Td>
<Td>300 / month</Td>
<Td>2,000 / month</Td>
<Td>10,000 / month</Td>
</Tr>
<Tr>
<Td>Additional Chats</Td>
<Td />
<Td>{additionalChatsPrice} per 500</Td>
<Td>{additionalChatsPrice} per 1,000</Td>
</Tr>
<Tr>
<Td>Storage</Td>
<Td />
<Td>2 GB</Td>
<Td>10 GB</Td>
</Tr>
<Tr>
<Td>Additional Storage</Td>
<Td />
<Td>{additionalStoragePrice} per 1 GB</Td>
<Td>{additionalStoragePrice} per 1 GB</Td>
</Tr>
<Tr>
<Td>Members</Td>
<Td>Just you</Td>
<Td>2 seats</Td>
<Td>5 seats</Td>
</Tr>
<Tr>
<Td>Guests</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Features
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<TdWithTooltip
text="20+ blocks"
tooltip="Includes display bubbles (text, image, video, embed), question inputs (email, url, phone number...) and logic blocks (conditions, set variables...)"
/>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Starter templates</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Webhooks</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Google Sheets</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Google Analytics</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Send emails</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Zapier</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Pabbly Connect</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Make.com</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Custom Javascript & CSS</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Export CSV</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>File upload inputs</Td>
<Td />
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<TdWithTooltip
text="Folders"
tooltip="Organize your typebots into folders"
/>
<Td />
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
<Tr>
<Td>Remove branding</Td>
<Td />
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Custom domains</Td>
<Td />
<Td />
<Td>Unlimited</Td>
</Tr>
<Tr>
<TdWithTooltip
text="In-depth analytics"
tooltip="Analytics graph that shows your form drop-off rate, submission rate, and more."
/>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Support
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>Priority support</Td>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Feature request priority</Td>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<Stack
direction={['column', 'row']}
spacing={4}
w="full"
justify="space-around"
>
<Stack spacing={4}>
<Heading as="h3" size="md">
Personal
</Heading>
<Heading as="h3">Free</Heading>
<Link href="https://app.typebot.io/register">
<Button variant="outline">Get started</Button>
</Link>
</Stack>
<Stack spacing={4}>
<Heading as="h3" size="md" color="orange.200">
Starter
</Heading>
<Heading as="h3">
{starterPrice} <chakra.span fontSize="lg">/ month</chakra.span>
</Heading>
<Link
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}`}
>
<Button>Subscribe</Button>
</Link>
</Stack>
<Stack spacing={4}>
<Heading as="h3" size="md" color="blue.200">
Pro
</Heading>
<Heading as="h3">
{proPrice} <chakra.span fontSize="lg">/ month</chakra.span>
</Heading>
<Link
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}`}
>
<Button>Subscribe</Button>
</Link>
</Stack>
export const PlanComparisonTables = () => (
<Stack spacing="12">
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Usage
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>Total bots</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
<Tr>
<Td>Chats</Td>
<Td>{chatsLimit.FREE.totalIncluded} / month</Td>
<Td>
{parseNumberWithCommas(
chatsLimit.STARTER.graduatedPrice[0].totalIncluded
)}{' '}
/ month
</Td>
<Td>
{parseNumberWithCommas(
chatsLimit.PRO.graduatedPrice[0].totalIncluded
)}{' '}
/ month
</Td>
</Tr>
<Tr>
<Td>Additional Chats</Td>
<Td />
<Td>
{formatPrice(chatsLimit.STARTER.graduatedPrice[1].price)} per{' '}
{chatsLimit.STARTER.graduatedPrice[1].totalIncluded -
chatsLimit.STARTER.graduatedPrice[0].totalIncluded}
</Td>
<Td>
{formatPrice(chatsLimit.PRO.graduatedPrice[1].price)} per{' '}
{chatsLimit.PRO.graduatedPrice[1].totalIncluded -
chatsLimit.PRO.graduatedPrice[0].totalIncluded}
</Td>
</Tr>
<Tr>
<Td>Storage</Td>
<Td />
<Td>2 GB</Td>
<Td>10 GB</Td>
</Tr>
<Tr>
<Td>Additional Storage</Td>
<Td />
<Td>
{formatPrice(storageLimit.STARTER.graduatedPrice[1].price)} per{' '}
{storageLimit.STARTER.graduatedPrice[1].totalIncluded -
storageLimit.STARTER.graduatedPrice[0].totalIncluded}{' '}
GB
</Td>
<Td>
{formatPrice(storageLimit.PRO.graduatedPrice[1].price)} per{' '}
{storageLimit.PRO.graduatedPrice[1].totalIncluded -
storageLimit.PRO.graduatedPrice[0].totalIncluded}{' '}
GB
</Td>
</Tr>
<Tr>
<Td>Members</Td>
<Td>Just you</Td>
<Td>{seatsLimit.STARTER.totalIncluded} seats</Td>
<Td>{seatsLimit.PRO.totalIncluded} seats</Td>
</Tr>
<Tr>
<Td>Guests</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Features
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<TdWithTooltip
text="20+ blocks"
tooltip="Includes display bubbles (text, image, video, embed), question inputs (email, url, phone number...) and logic blocks (conditions, set variables...)"
/>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Starter templates</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Webhooks</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Google Sheets</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Google Analytics</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Send emails</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Zapier</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Pabbly Connect</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Make.com</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Custom Javascript & CSS</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Export CSV</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>File upload inputs</Td>
<Td />
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<TdWithTooltip
text="Folders"
tooltip="Organize your typebots into folders"
/>
<Td />
<Td>Unlimited</Td>
<Td>Unlimited</Td>
</Tr>
<Tr>
<Td>Remove branding</Td>
<Td />
<Td>
<CheckIcon />
</Td>
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Custom domains</Td>
<Td />
<Td />
<Td>Unlimited</Td>
</Tr>
<Tr>
<TdWithTooltip
text="In-depth analytics"
tooltip="Analytics graph that shows your form drop-off rate, submission rate, and more."
/>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th fontWeight="bold" color="white" w="400px">
Support
</Th>
<Th>Free</Th>
<Th color="orange.200">Starter</Th>
<Th color="blue.200">Pro</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>Priority support</Td>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
<Tr>
<Td>Feature request priority</Td>
<Td />
<Td />
<Td>
<CheckIcon />
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
<Stack
direction={['column', 'row']}
spacing={4}
w="full"
justify="space-around"
>
<Stack spacing={4}>
<Heading as="h3" size="md">
Personal
</Heading>
<Heading as="h3">Free</Heading>
<Link href="https://app.typebot.io/register">
<Button variant="outline" colorScheme="gray">
Get started
</Button>
</Link>
</Stack>
<Stack spacing={4}>
<Heading as="h3" size="md" color="orange.200">
Starter
</Heading>
<Heading as="h3">
{formatPrice(prices.STARTER)}{' '}
<chakra.span fontSize="lg">/ month</chakra.span>
</Heading>
<Link
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}`}
>
<Button variant="outline" colorScheme="orange">
Subscribe
</Button>
</Link>
</Stack>
<Stack spacing={4}>
<Heading as="h3" size="md" color="blue.200">
Pro
</Heading>
<Heading as="h3">
{formatPrice(prices.PRO)}{' '}
<chakra.span fontSize="lg">/ month</chakra.span>
</Heading>
<Link
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}`}
>
<Button>Subscribe</Button>
</Link>
</Stack>
</Stack>
)
}
</Stack>
)
const TdWithTooltip = ({
text,

View File

@ -9,7 +9,6 @@ import {
VStack,
} from '@chakra-ui/react'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { formatPrice } from '@typebot.io/lib/pricing'
import { CheckCircleIcon } from '../../../assets/icons/CheckCircleIcon'
import { Card, CardProps } from './Card'
@ -34,12 +33,8 @@ export const PricingCard = ({
...rest
}: PricingCardProps) => {
const { features, price, name } = data
const [formattedPrice, setFormattedPrice] = useState(price)
const accentColor = useColorModeValue('blue.500', 'white')
useEffect(() => {
setFormattedPrice(typeof price === 'number' ? formatPrice(price) : price)
}, [price])
const formattedPrice = typeof price === 'number' ? formatPrice(price) : price
return (
<Card rounded="xl" bgColor="gray.800" {...rest}>

View File

@ -13,28 +13,33 @@ import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { Plan } from '@typebot.io/prisma'
import Link from 'next/link'
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'
import { parseNumberWithCommas } from '@typebot.io/lib'
import { chatsLimit, computePrice, storageLimit } from '@typebot.io/lib/pricing'
import {
chatsLimit,
computePrice,
seatsLimit,
storageLimit,
} from '@typebot.io/lib/pricing'
import { PricingCard } from './PricingCard'
export const ProPlanCard = () => {
const [price, setPrice] = useState(89)
type Props = {
isYearly: boolean
}
export const ProPlanCard = ({ isYearly }: Props) => {
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>(0)
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>(0)
useEffect(() => {
setPrice(
computePrice(
Plan.PRO,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)
}, [selectedChatsLimitIndex, selectedStorageLimitIndex])
const price =
computePrice(
Plan.PRO,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0,
isYearly ? 'yearly' : 'monthly'
) ?? NaN
return (
<PricingCard
@ -44,7 +49,10 @@ export const ProPlanCard = () => {
featureLabel: 'Everything in Personal, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">5 seats</chakra.span> included
<chakra.span fontWeight="bold">
{seatsLimit.PRO.totalIncluded} seats
</chakra.span>{' '}
included
</Text>,
<HStack key="chats" spacing={1.5}>
<Menu>
@ -57,50 +65,20 @@ export const ProPlanCard = () => {
>
{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>{' '}
<Text>chats/mo</Text>
@ -126,50 +104,20 @@ export const ProPlanCard = () => {
>
{selectedStorageLimitIndex !== undefined
? parseNumberWithCommas(
storageLimit.PRO.totalIncluded +
storageLimit.PRO.increaseStep.amount *
selectedStorageLimitIndex
storageLimit.PRO.graduatedPrice[selectedStorageLimitIndex]
.totalIncluded
)
: undefined}
</MenuButton>
<MenuList>
{selectedStorageLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(0)}>
{parseNumberWithCommas(storageLimit.PRO.totalIncluded)}
{storageLimit.PRO.graduatedPrice.map((price, index) => (
<MenuItem
key={index}
onClick={() => setSelectedStorageLimitIndex(index)}
>
{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>{' '}
<Text>GB of storage</Text>
@ -194,7 +142,7 @@ export const ProPlanCard = () => {
button={
<Button
as={Link}
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}`}
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}&isYearly=${isYearly}`}
colorScheme="blue"
size="lg"
w="full"

View File

@ -13,28 +13,32 @@ import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { Plan } from '@typebot.io/prisma'
import Link from 'next/link'
import React, { useEffect, useState } from 'react'
import React, { useState } from 'react'
import { parseNumberWithCommas } from '@typebot.io/lib'
import { chatsLimit, computePrice, storageLimit } from '@typebot.io/lib/pricing'
import {
chatsLimit,
computePrice,
seatsLimit,
storageLimit,
} from '@typebot.io/lib/pricing'
import { PricingCard } from './PricingCard'
export const StarterPlanCard = () => {
const [price, setPrice] = useState(39)
type Props = {
isYearly: boolean
}
export const StarterPlanCard = ({ isYearly }: Props) => {
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>(0)
const [selectedStorageLimitIndex, setSelectedStorageLimitIndex] =
useState<number>(0)
useEffect(() => {
setPrice(
computePrice(
Plan.STARTER,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0
) ?? NaN
)
}, [selectedChatsLimitIndex, selectedStorageLimitIndex])
const price =
computePrice(
Plan.STARTER,
selectedChatsLimitIndex ?? 0,
selectedStorageLimitIndex ?? 0,
isYearly ? 'yearly' : 'monthly'
) ?? NaN
return (
<PricingCard
@ -44,7 +48,10 @@ export const StarterPlanCard = () => {
featureLabel: 'Everything in Personal, plus:',
features: [
<Text key="seats">
<chakra.span fontWeight="bold">2 seats</chakra.span> included
<chakra.span fontWeight="bold">
{seatsLimit.STARTER.totalIncluded} seats
</chakra.span>{' '}
included
</Text>,
<HStack key="chats" spacing={1.5}>
<Menu>
@ -56,49 +63,19 @@ export const StarterPlanCard = () => {
colorScheme="orange"
>
{parseNumberWithCommas(
chatsLimit.STARTER.totalIncluded +
chatsLimit.STARTER.increaseStep.amount *
selectedChatsLimitIndex
chatsLimit.STARTER.graduatedPrice[selectedChatsLimitIndex]
.totalIncluded
)}
</MenuButton>
<MenuList>
{selectedChatsLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedChatsLimitIndex(0)}>
{parseNumberWithCommas(chatsLimit.STARTER.totalIncluded)}
{chatsLimit.STARTER.graduatedPrice.map((price, index) => (
<MenuItem
key={index}
onClick={() => setSelectedChatsLimitIndex(index)}
>
{parseNumberWithCommas(price.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>{' '}
<Text>chats/mo</Text>
@ -125,50 +102,21 @@ export const StarterPlanCard = () => {
>
{selectedStorageLimitIndex !== undefined
? parseNumberWithCommas(
storageLimit.STARTER.totalIncluded +
storageLimit.STARTER.increaseStep.amount *
selectedStorageLimitIndex
storageLimit.STARTER.graduatedPrice[
selectedStorageLimitIndex
].totalIncluded
)
: undefined}
</MenuButton>
<MenuList>
{selectedStorageLimitIndex !== 0 && (
<MenuItem onClick={() => setSelectedStorageLimitIndex(0)}>
{parseNumberWithCommas(storageLimit.STARTER.totalIncluded)}
{storageLimit.STARTER.graduatedPrice.map((price, index) => (
<MenuItem
key={index}
onClick={() => setSelectedStorageLimitIndex(index)}
>
{parseNumberWithCommas(price.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>{' '}
<Text>GB of storage</Text>
@ -194,12 +142,13 @@ export const StarterPlanCard = () => {
button={
<Button
as={Link}
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}`}
colorScheme="blue"
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}&chats=${selectedChatsLimitIndex}&storage=${selectedStorageLimitIndex}&isYearly=${isYearly}`}
colorScheme="orange"
size="lg"
w="full"
fontWeight="extrabold"
py={{ md: '8' }}
variant="outline"
>
Subscribe now
</Button>

View File

@ -1,39 +1,30 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
DarkMode,
Flex,
Stack,
Box,
Heading,
VStack,
Text,
HStack,
Switch,
Tag,
} from '@chakra-ui/react'
import { Footer } from 'components/common/Footer'
import { Header } from 'components/common/Header/Header'
import { SocialMetaTags } from 'components/common/SocialMetaTags'
import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons'
import { PlanComparisonTables } from 'components/PricingPage/PlanComparisonTables'
import { useEffect, useState } from 'react'
import { formatPrice, prices } from '@typebot.io/lib/pricing'
import { useState } from 'react'
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
import { FreePlanCard } from 'components/PricingPage/FreePlanCard'
import { StarterPlanCard } from 'components/PricingPage/StarterPlanCard'
import { ProPlanCard } from 'components/PricingPage/ProPlanCard'
import { TextLink } from 'components/common/TextLink'
import { EnterprisePlanCard } from 'components/PricingPage/EnterprisePlanCard'
import { Faq } from 'components/PricingPage/Faq'
const Pricing = () => {
const [starterPrice, setStarterPrice] = useState('$39')
const [proPrice, setProPrice] = useState('$89')
useEffect(() => {
setStarterPrice(formatPrice(prices.STARTER))
setProPrice(formatPrice(prices.PRO))
}, [])
const [isYearly, setIsYearly] = useState(true)
return (
<Stack overflowX="hidden" bgColor="gray.900">
@ -52,20 +43,31 @@ const Pricing = () => {
</DarkMode>
<VStack spacing={'24'} mt={[20, 32]} w="full">
<Stack align="center" spacing="12" w="full">
<Stack align="center" spacing="12" w="full" px={4}>
<VStack>
<Heading fontSize="6xl">Plans fit for you</Heading>
<Text maxW="900px" fontSize="xl" textAlign="center">
<Heading fontSize={{ base: '4xl', xl: '6xl' }}>
Plans fit for you
</Heading>
<Text
maxW="900px"
textAlign="center"
fontSize={{ base: 'lg', xl: 'xl' }}
>
Whether you&apos;re a{' '}
<Text as="span" color="orange.200" fontWeight="bold">
solo business owner
</Text>{' '}
or a{' '}
</Text>
, a{' '}
<Text as="span" color="blue.200" fontWeight="bold">
growing startup
</Text>{' '}
or a{' '}
<Text as="span" fontWeight="bold">
large company
</Text>
, Typebot is here to help you build high-performing bots for the
right price. Pay for as little or as much usage as you need.
, Typebot is here to help you build high-performing chat forms
for the right price. Pay for as little or as much usage as you
need.
</Text>
</VStack>
@ -85,38 +87,41 @@ const Pricing = () => {
</TextLink>
</Text>
</HStack>
<Stack
direction={['column', 'row']}
alignItems={['stretch']}
spacing={10}
px={[4, 0]}
w="full"
maxW="1200px"
>
<FreePlanCard />
<StarterPlanCard />
<ProPlanCard />
<Stack align="flex-end" maxW="1200px" w="full" spacing={4}>
<HStack>
<Text>Monthly</Text>
<Switch
isChecked={isYearly}
onChange={() => setIsYearly(!isYearly)}
/>
<HStack>
<Text>Yearly</Text>
<Tag colorScheme="blue">16% off</Tag>
</HStack>
</HStack>
<Stack
direction={['column', 'row']}
alignItems={['stretch']}
spacing={10}
w="full"
maxW="1200px"
>
<FreePlanCard />
<StarterPlanCard isYearly={isYearly} />
<ProPlanCard isYearly={isYearly} />
</Stack>
</Stack>
<Text fontSize="lg">
Need custom limits? Specific features?{' '}
<TextLink href={'https://typebot.io/enterprise-lead-form'}>
Let&apos;s chat!
</TextLink>
</Text>
<EnterprisePlanCard />
</Stack>
<VStack maxW="1200px" w="full" spacing={[12, 20]} px="4">
<Stack w="full" spacing={10} display={['none', 'flex']}>
<Heading>Compare plans & features</Heading>
<PlanComparisonTables
starterPrice={starterPrice}
proPrice={proPrice}
/>
<PlanComparisonTables />
</Stack>
<VStack w="full" spacing="10">
<Heading textAlign="center">Frequently asked questions</Heading>
<Faq />
</VStack>
<Faq />
</VStack>
</VStack>
</Flex>
@ -125,75 +130,4 @@ const Pricing = () => {
)
}
const Faq = () => {
return (
<Accordion w="full" allowToggle defaultIndex={0}>
<AccordionItem>
<Heading as="h2">
<AccordionButton py="6">
<Box flex="1" textAlign="left" fontSize="2xl">
What happens once I reach the monthly chats limit?
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
You will receive an email notification once you reached 80% of this
limit. Then, once you reach 100%, the bot will be closed to new users.
Upgrading your limit will automatically reopen the bot.
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<Heading as="h2">
<AccordionButton py="6">
<Box flex="1" textAlign="left" fontSize="2xl">
What happens once I reach the storage limit?
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
You will receive an email notification once you reached 80% of this
limit. Then, once you reach 100%, your users will still be able to
chat with your bot but their uploads won&apos;t be stored anymore. You
will need to upgrade the limit or free up some space to continue
collecting your users&apos; files.
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<Heading as="h2">
<AccordionButton py="6">
<Box flex="1" textAlign="left" fontSize="2xl">
Why is there no trial?
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
For now, Typebot offers a Freemium based business model. My goal is to
make sure you have time to create awesome bots and collect valuable
results. If you need advanced features then you can upgrade any time.
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<Heading as="h2">
<AccordionButton py="6">
<Box flex="1" textAlign="left" fontSize="2xl">
If I change my mind, can I get a refund?
</Box>
<AccordionIcon />
</AccordionButton>
</Heading>
<AccordionPanel pb={4}>
Sure! Just{' '}
<TextLink href="mailto:baptiste@typebot.io">
shoot me an email
</TextLink>{' '}
and we&apos;ll figure things out 😀
</AccordionPanel>
</AccordionItem>
</Accordion>
)
}
export default Pricing