⚡ (billing) Automatic usage-based billing (#924)
BREAKING CHANGE: Stripe environment variables simplified. Check out the new configs to adapt your existing system. Closes #906 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ### Summary by CodeRabbit **New Features:** - Introduced a usage-based billing system, providing more flexibility and options for users. - Integrated with Stripe for a smoother and more secure payment process. - Enhanced the user interface with improvements to the billing, workspace, and pricing pages for a more intuitive experience. **Improvements:** - Simplified the billing logic, removing additional chats and yearly billing for a more streamlined user experience. - Updated email notifications to keep users informed about their usage and limits. - Improved pricing and currency formatting for better clarity and understanding. **Testing:** - Updated tests and specifications to ensure the reliability of new features and improvements. **Note:** These changes aim to provide a more flexible and user-friendly billing system, with clearer pricing and improved notifications. Users should find the new system more intuitive and easier to navigate. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -0,0 +1,91 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
Stack,
|
||||
ModalFooter,
|
||||
Heading,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
} from '@chakra-ui/react'
|
||||
import { proChatTiers } from '@typebot.io/lib/billing/constants'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const ChatsProTiersModal = ({ isOpen, onClose }: Props) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<Heading size="lg">Chats pricing table</Heading>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing="6">
|
||||
<TableContainer>
|
||||
<Table variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th isNumeric>Max chats</Th>
|
||||
<Th isNumeric>Price per month</Th>
|
||||
<Th isNumeric>Price per 1k chats</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{proChatTiers.map((tier, index) => {
|
||||
const pricePerMonth =
|
||||
proChatTiers
|
||||
.slice(0, index + 1)
|
||||
.reduce(
|
||||
(acc, slicedTier) =>
|
||||
acc + (slicedTier.flat_amount ?? 0),
|
||||
0
|
||||
) / 100
|
||||
return (
|
||||
<Tr key={tier.up_to}>
|
||||
<Td isNumeric>
|
||||
{tier.up_to === 'inf'
|
||||
? '2,000,000+'
|
||||
: tier.up_to.toLocaleString()}
|
||||
</Td>
|
||||
<Td isNumeric>
|
||||
{index === 0 ? 'included' : formatPrice(pricePerMonth)}
|
||||
</Td>
|
||||
<Td isNumeric>
|
||||
{index === proChatTiers.length - 1
|
||||
? formatPrice(4.42, { maxFractionDigits: 2 })
|
||||
: index === 0
|
||||
? 'included'
|
||||
: formatPrice(
|
||||
(((pricePerMonth * 100) /
|
||||
((tier.up_to as number) -
|
||||
(proChatTiers.at(0)?.up_to as number))) *
|
||||
1000) /
|
||||
100,
|
||||
{ maxFractionDigits: 2 }
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
)
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -1,88 +1,74 @@
|
||||
import { Heading, VStack, SimpleGrid, Stack, Text } from '@chakra-ui/react'
|
||||
import { Heading, VStack, Stack, Text, Wrap, WrapItem } 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>
|
||||
You will receive a heads up email when you reach 80% of your monthly
|
||||
limit. Once you have reached the limit, you will receive another email
|
||||
alert. Your bots will continue to run. You will be kindly asked to
|
||||
upgrade your subscription. If you don't provide an answer after
|
||||
~48h, your bots will be closed for the remaining of the month. For a
|
||||
FREE workspace, If you exceed 600 chats, your bots will be
|
||||
automatically closed.
|
||||
</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'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'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>
|
||||
<Wrap spacing={10}>
|
||||
<WrapItem maxW="500px">
|
||||
<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>
|
||||
</WrapItem>
|
||||
|
||||
<WrapItem maxW="500px">
|
||||
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
|
||||
<Heading as="h2" fontSize="2xl">
|
||||
What happens once I reach the included chats limit?
|
||||
</Heading>
|
||||
<Text>
|
||||
That's amazing, your bots are working full speed. 🚀
|
||||
<br />
|
||||
<br />
|
||||
You will first receive a heads up email when you reach 80% of your
|
||||
included limit. Once you have reached 100%, you will receive another
|
||||
email notification.
|
||||
<br />
|
||||
<br />
|
||||
After that, your chat limit be automatically upgraded to the next
|
||||
tier.
|
||||
</Text>
|
||||
</Stack>
|
||||
</WrapItem>
|
||||
|
||||
<WrapItem maxW="500px">
|
||||
<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'll get
|
||||
access to the new options right away. Your next invoice will have a
|
||||
prorated amount.
|
||||
</Text>
|
||||
</Stack>
|
||||
</WrapItem>
|
||||
<WrapItem maxW="500px">
|
||||
<Stack borderWidth={1} p="8" rounded="lg" spacing={4}>
|
||||
<Heading as="h2" fontSize="2xl">
|
||||
Do you offer annual payments?
|
||||
</Heading>
|
||||
<Text>
|
||||
No, because subscriptions pricing is based on chats usage, we can
|
||||
only offer monthly plans.
|
||||
</Text>
|
||||
</Stack>
|
||||
</WrapItem>
|
||||
</Wrap>
|
||||
</VStack>
|
||||
)
|
||||
|
@ -3,7 +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'
|
||||
import { chatsLimits } from '@typebot.io/lib/billing/constants'
|
||||
|
||||
export const FreePlanCard = () => (
|
||||
<PricingCard
|
||||
@ -14,9 +14,7 @@ export const FreePlanCard = () => (
|
||||
'Unlimited typebots',
|
||||
<>
|
||||
<Text>
|
||||
<chakra.span fontWeight="bold">
|
||||
{chatsLimit.FREE.totalIncluded}
|
||||
</chakra.span>{' '}
|
||||
<chakra.span fontWeight="bold">{chatsLimits.FREE}</chakra.span>{' '}
|
||||
chats/month
|
||||
</Text>
|
||||
|
||||
|
@ -19,15 +19,19 @@ import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import {
|
||||
chatsLimit,
|
||||
formatPrice,
|
||||
prices,
|
||||
seatsLimit,
|
||||
} from '@typebot.io/lib/pricing'
|
||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
||||
import {
|
||||
chatsLimits,
|
||||
prices,
|
||||
seatsLimits,
|
||||
} from '@typebot.io/lib/billing/constants'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
|
||||
export const PlanComparisonTables = () => (
|
||||
type Props = {
|
||||
onChatsTiersClick: () => void
|
||||
}
|
||||
|
||||
export const PlanComparisonTables = ({ onChatsTiersClick }: Props) => (
|
||||
<Stack spacing="12">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
@ -50,32 +54,23 @@ export const PlanComparisonTables = () => (
|
||||
</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>
|
||||
<Td>{chatsLimits.FREE} / month</Td>
|
||||
<Td>{parseNumberWithCommas(chatsLimits.STARTER)} / month</Td>
|
||||
<Td>{parseNumberWithCommas(chatsLimits.PRO)} / month</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Additional Chats</Td>
|
||||
<Td />
|
||||
<Td>{formatPrice(10)} per 500 chats</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}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={onChatsTiersClick}
|
||||
colorScheme="gray"
|
||||
>
|
||||
See tiers
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
@ -87,8 +82,8 @@ export const PlanComparisonTables = () => (
|
||||
<Tr>
|
||||
<Td>Members</Td>
|
||||
<Td>Just you</Td>
|
||||
<Td>{seatsLimit.STARTER.totalIncluded} seats</Td>
|
||||
<Td>{seatsLimit.PRO.totalIncluded} seats</Td>
|
||||
<Td>{seatsLimits.STARTER} seats</Td>
|
||||
<Td>{seatsLimits.PRO} seats</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Guests</Td>
|
||||
@ -276,6 +271,14 @@ export const PlanComparisonTables = () => (
|
||||
<CheckIcon />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>WhatsApp integration</Td>
|
||||
<Td />
|
||||
<Td />
|
||||
<Td>
|
||||
<CheckIcon />
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Custom domains</Td>
|
||||
<Td />
|
||||
|
@ -9,9 +9,9 @@ import {
|
||||
VStack,
|
||||
} from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
import { formatPrice } from '@typebot.io/lib/pricing'
|
||||
import { CheckCircleIcon } from '../../../assets/icons/CheckCircleIcon'
|
||||
import { Card, CardProps } from './Card'
|
||||
import { formatPrice } from '@typebot.io/lib/billing/formatPrice'
|
||||
|
||||
export interface PricingCardData {
|
||||
features: React.ReactNode[]
|
||||
|
@ -4,107 +4,75 @@ import {
|
||||
Text,
|
||||
Button,
|
||||
HStack,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Stack,
|
||||
Link,
|
||||
} from '@chakra-ui/react'
|
||||
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, { useState } from 'react'
|
||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
||||
import { chatsLimit, computePrice, seatsLimit } from '@typebot.io/lib/pricing'
|
||||
import React from 'react'
|
||||
import { PricingCard } from './PricingCard'
|
||||
import { prices, seatsLimits } from '@typebot.io/lib/billing/constants'
|
||||
|
||||
type Props = {
|
||||
isYearly: boolean
|
||||
onChatsTiersClick: () => void
|
||||
}
|
||||
|
||||
export const ProPlanCard = ({ isYearly }: Props) => {
|
||||
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
|
||||
useState<number>(0)
|
||||
|
||||
const price =
|
||||
computePrice(
|
||||
Plan.PRO,
|
||||
selectedChatsLimitIndex ?? 0,
|
||||
isYearly ? 'yearly' : 'monthly'
|
||||
) ?? NaN
|
||||
|
||||
return (
|
||||
<PricingCard
|
||||
data={{
|
||||
price,
|
||||
name: 'Pro',
|
||||
featureLabel: 'Everything in Personal, plus:',
|
||||
features: [
|
||||
<Text key="seats">
|
||||
<chakra.span fontWeight="bold">
|
||||
{seatsLimit.PRO.totalIncluded} seats
|
||||
</chakra.span>{' '}
|
||||
included
|
||||
</Text>,
|
||||
<HStack key="chats" spacing={1.5}>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
isLoading={selectedChatsLimitIndex === undefined}
|
||||
>
|
||||
{selectedChatsLimitIndex !== undefined
|
||||
? parseNumberWithCommas(
|
||||
chatsLimit.PRO.graduatedPrice[selectedChatsLimitIndex]
|
||||
.totalIncluded
|
||||
)
|
||||
: undefined}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{chatsLimit.PRO.graduatedPrice.map((price, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
onClick={() => setSelectedChatsLimitIndex(index)}
|
||||
>
|
||||
{parseNumberWithCommas(price.totalIncluded)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>{' '}
|
||||
<Text>chats/mo</Text>
|
||||
export const ProPlanCard = ({ onChatsTiersClick }: Props) => (
|
||||
<PricingCard
|
||||
data={{
|
||||
price: prices.PRO,
|
||||
name: 'Pro',
|
||||
featureLabel: 'Everything in Personal, plus:',
|
||||
features: [
|
||||
<Text key="seats">
|
||||
<chakra.span fontWeight="bold">{seatsLimits.PRO} seats</chakra.span>{' '}
|
||||
included
|
||||
</Text>,
|
||||
<Stack key="chats" spacing={0}>
|
||||
<HStack spacing={1.5}>
|
||||
<Text>10,000 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."
|
||||
independant of the number of messages he sends and receives."
|
||||
>
|
||||
<chakra.span cursor="pointer" h="7">
|
||||
<HelpCircleIcon />
|
||||
</chakra.span>
|
||||
</Tooltip>
|
||||
</HStack>,
|
||||
'WhatsApp integration',
|
||||
'Custom domains',
|
||||
'In-depth analytics',
|
||||
],
|
||||
}}
|
||||
borderWidth="3px"
|
||||
borderColor="blue.200"
|
||||
button={
|
||||
<Button
|
||||
as={Link}
|
||||
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}&chats=${selectedChatsLimitIndex}&isYearly=${isYearly}`}
|
||||
colorScheme="blue"
|
||||
size="lg"
|
||||
w="full"
|
||||
fontWeight="extrabold"
|
||||
py={{ md: '8' }}
|
||||
>
|
||||
Subscribe now
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</HStack>
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
Extra chats:{' '}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="xs"
|
||||
colorScheme="gray"
|
||||
onClick={onChatsTiersClick}
|
||||
>
|
||||
See tiers
|
||||
</Button>
|
||||
</Text>
|
||||
</Stack>,
|
||||
'WhatsApp integration',
|
||||
'Custom domains',
|
||||
'In-depth analytics',
|
||||
],
|
||||
}}
|
||||
borderWidth="3px"
|
||||
borderColor="blue.200"
|
||||
button={
|
||||
<Button
|
||||
as={Link}
|
||||
href={`https://app.typebot.io/register?subscribePlan=${Plan.PRO}`}
|
||||
colorScheme="blue"
|
||||
size="lg"
|
||||
w="full"
|
||||
fontWeight="extrabold"
|
||||
py={{ md: '8' }}
|
||||
>
|
||||
Subscribe now
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
@ -1,87 +1,43 @@
|
||||
import {
|
||||
chakra,
|
||||
Tooltip,
|
||||
Text,
|
||||
HStack,
|
||||
Menu,
|
||||
MenuButton,
|
||||
Button,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@chakra-ui/react'
|
||||
import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
|
||||
import { chakra, Tooltip, Text, HStack, Button, Stack } from '@chakra-ui/react'
|
||||
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
|
||||
import { Plan } from '@typebot.io/prisma'
|
||||
import Link from 'next/link'
|
||||
import React, { useState } from 'react'
|
||||
import { parseNumberWithCommas } from '@typebot.io/lib'
|
||||
import { chatsLimit, computePrice, seatsLimit } from '@typebot.io/lib/pricing'
|
||||
import React from 'react'
|
||||
import { PricingCard } from './PricingCard'
|
||||
import { prices, seatsLimits } from '@typebot.io/lib/billing/constants'
|
||||
|
||||
type Props = {
|
||||
isYearly: boolean
|
||||
}
|
||||
export const StarterPlanCard = ({ isYearly }: Props) => {
|
||||
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
|
||||
useState<number>(0)
|
||||
|
||||
const price =
|
||||
computePrice(
|
||||
Plan.STARTER,
|
||||
selectedChatsLimitIndex ?? 0,
|
||||
isYearly ? 'yearly' : 'monthly'
|
||||
) ?? NaN
|
||||
|
||||
export const StarterPlanCard = () => {
|
||||
return (
|
||||
<PricingCard
|
||||
data={{
|
||||
price,
|
||||
price: prices.STARTER,
|
||||
name: 'Starter',
|
||||
featureLabel: 'Everything in Personal, plus:',
|
||||
features: [
|
||||
<Text key="seats">
|
||||
<chakra.span fontWeight="bold">
|
||||
{seatsLimit.STARTER.totalIncluded} seats
|
||||
{seatsLimits.STARTER} seats
|
||||
</chakra.span>{' '}
|
||||
included
|
||||
</Text>,
|
||||
<HStack key="chats" spacing={1.5}>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
colorScheme="orange"
|
||||
>
|
||||
{parseNumberWithCommas(
|
||||
chatsLimit.STARTER.graduatedPrice[selectedChatsLimitIndex]
|
||||
.totalIncluded
|
||||
)}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{chatsLimit.STARTER.graduatedPrice.map((price, index) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
onClick={() => setSelectedChatsLimitIndex(index)}
|
||||
>
|
||||
{parseNumberWithCommas(price.totalIncluded)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>{' '}
|
||||
<Text>chats/mo</Text>
|
||||
<Tooltip
|
||||
hasArrow
|
||||
placement="top"
|
||||
label="A chat is counted whenever a user starts a discussion. It is
|
||||
<Stack key="chats" spacing={0}>
|
||||
<HStack spacing={1.5}>
|
||||
<Text>2,000 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>,
|
||||
>
|
||||
<chakra.span cursor="pointer" h="7">
|
||||
<HelpCircleIcon />
|
||||
</chakra.span>
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
Extra chats: $10 per 500
|
||||
</Text>
|
||||
</Stack>,
|
||||
'Branding removed',
|
||||
'Collect files from users',
|
||||
'Create folders',
|
||||
@ -92,7 +48,7 @@ export const StarterPlanCard = ({ isYearly }: Props) => {
|
||||
button={
|
||||
<Button
|
||||
as={Link}
|
||||
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}&chats=${selectedChatsLimitIndex}&isYearly=${isYearly}`}
|
||||
href={`https://app.typebot.io/register?subscribePlan=${Plan.STARTER}`}
|
||||
colorScheme="orange"
|
||||
size="lg"
|
||||
w="full"
|
||||
|
@ -6,15 +6,13 @@ import {
|
||||
VStack,
|
||||
Text,
|
||||
HStack,
|
||||
Switch,
|
||||
Tag,
|
||||
useDisclosure,
|
||||
} 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 { useState } from 'react'
|
||||
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
|
||||
import { FreePlanCard } from 'components/PricingPage/FreePlanCard'
|
||||
import { StarterPlanCard } from 'components/PricingPage/StarterPlanCard'
|
||||
@ -22,12 +20,14 @@ import { ProPlanCard } from 'components/PricingPage/ProPlanCard'
|
||||
import { TextLink } from 'components/common/TextLink'
|
||||
import { EnterprisePlanCard } from 'components/PricingPage/EnterprisePlanCard'
|
||||
import { Faq } from 'components/PricingPage/Faq'
|
||||
import { ChatsProTiersModal } from 'components/PricingPage/ChatsProTiersModal'
|
||||
|
||||
const Pricing = () => {
|
||||
const [isYearly, setIsYearly] = useState(true)
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
return (
|
||||
<Stack overflowX="hidden" bgColor="gray.900">
|
||||
<ChatsProTiersModal isOpen={isOpen} onClose={onClose} />
|
||||
<Flex
|
||||
pos="relative"
|
||||
flexDir="column"
|
||||
@ -87,30 +87,16 @@ const Pricing = () => {
|
||||
</TextLink>
|
||||
</Text>
|
||||
</HStack>
|
||||
<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
|
||||
direction={['column', 'row']}
|
||||
alignItems={['stretch']}
|
||||
spacing={10}
|
||||
w="full"
|
||||
maxW="1200px"
|
||||
>
|
||||
<FreePlanCard />
|
||||
<StarterPlanCard />
|
||||
<ProPlanCard onChatsTiersClick={onOpen} />
|
||||
</Stack>
|
||||
|
||||
<EnterprisePlanCard />
|
||||
@ -119,7 +105,7 @@ const Pricing = () => {
|
||||
<VStack maxW="1200px" w="full" spacing={[12, 20]} px="4">
|
||||
<Stack w="full" spacing={10} display={['none', 'flex']}>
|
||||
<Heading>Compare plans & features</Heading>
|
||||
<PlanComparisonTables />
|
||||
<PlanComparisonTables onChatsTiersClick={onOpen} />
|
||||
</Stack>
|
||||
<Faq />
|
||||
</VStack>
|
||||
|
Reference in New Issue
Block a user