2
0

🐛 Remove delete result cascade

This commit is contained in:
Baptiste Arnaud
2022-09-25 20:16:08 +02:00
committed by Baptiste Arnaud
parent 30dff2d5d7
commit 3c803b1345
11 changed files with 396 additions and 180 deletions

View File

@@ -0,0 +1,61 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const StripeClimateLogo = (props: IconProps) => (
<Icon
width="24px"
height="24px"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<linearGradient
id="StripeClimate-gradient-a"
gradientUnits="userSpaceOnUse"
x1="16"
y1="20.6293"
x2="16"
y2="7.8394"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset="0" stop-color="#00d924" />
<stop offset="1" stop-color="#00cb1b" />
</linearGradient>
<path
d="M0 10.82h32c0 8.84-7.16 16-16 16s-16-7.16-16-16z"
fill="url(#StripeClimate-gradient-a)"
/>
<linearGradient
id="StripeClimate-gradient-b"
gradientUnits="userSpaceOnUse"
x1="24"
y1="28.6289"
x2="24"
y2="17.2443"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset=".1562" stop-color="#009c00" />
<stop offset="1" stop-color="#00be20" />
</linearGradient>
<path
d="M32 10.82c0 2.21-1.49 4.65-5.41 4.65-3.42 0-7.27-2.37-10.59-4.65 3.52-2.43 7.39-5.63 10.59-5.63C29.86 5.18 32 8.17 32 10.82z"
fill="url(#StripeClimate-gradient-b)"
/>
<linearGradient
id="StripeClimate-gradient-c"
gradientUnits="userSpaceOnUse"
x1="8"
y1="16.7494"
x2="8"
y2="29.1239"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset="0" stop-color="#ffe37d" />
<stop offset="1" stop-color="#ffc900" />
</linearGradient>
<path
d="M0 10.82c0 2.21 1.49 4.65 5.41 4.65 3.42 0 7.27-2.37 10.59-4.65-3.52-2.43-7.39-5.64-10.59-5.64C2.14 5.18 0 8.17 0 10.82z"
fill="url(#StripeClimate-gradient-c)"
/>
</Icon>
)

View File

@@ -1,4 +1,5 @@
import { Stack, HStack, Text } from '@chakra-ui/react' import { Stack, HStack, Text } from '@chakra-ui/react'
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink' import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useUser } from 'contexts/UserContext' import { useUser } from 'contexts/UserContext'
import { useWorkspace } from 'contexts/WorkspaceContext' import { useWorkspace } from 'contexts/WorkspaceContext'
@@ -63,7 +64,21 @@ export const ChangePlanForm = () => {
} }
return ( return (
<Stack spacing={4}> <Stack spacing={6}>
<HStack maxW="500px">
<StripeClimateLogo />
<Text fontSize="xs" color="gray.500">
Typebot is contributing 1% of your subscription to remove CO from the
atmosphere.{' '}
<NextChakraLink
href="https://climate.stripe.com/5VCRAq"
isExternal
textDecor="underline"
>
More info.
</NextChakraLink>
</Text>
</HStack>
<HStack alignItems="stretch" spacing="4" w="full"> <HStack alignItems="stretch" spacing="4" w="full">
<StarterPlanContent <StarterPlanContent
initialChatsLimitIndex={ initialChatsLimitIndex={
@@ -94,9 +109,8 @@ export const ChangePlanForm = () => {
isExternal isExternal
textDecor="underline" textDecor="underline"
> >
Let me know Let's chat!
</NextChakraLink> </NextChakraLink>
.
</Text> </Text>
</Stack> </Stack>
) )

View File

@@ -38,6 +38,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const typebots = await prisma.typebot.deleteMany({ const typebots = await prisma.typebot.deleteMany({
where: canWriteTypebot(typebotId, user), where: canWriteTypebot(typebotId, user),
}) })
await prisma.result.updateMany({
where: { typebot: canWriteTypebot(typebotId, user) },
data: { isArchived: true },
})
return res.send({ typebots }) return res.send({ typebots })
} }
if (req.method === 'PUT') { if (req.method === 'PUT') {

View File

@@ -30,6 +30,17 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } }, members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
}, },
}) })
await prisma.result.updateMany({
where: {
typebot: {
workspace: {
id,
members: { some: { userId: user.id, role: WorkspaceRole.ADMIN } },
},
},
},
data: { isArchived: true },
})
return res.status(200).json({ return res.status(200).json({
message: 'success', message: 'success',
}) })

View File

@@ -0,0 +1,61 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const StripeClimateLogo = (props: IconProps) => (
<Icon
width="24px"
height="24px"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<linearGradient
id="StripeClimate-gradient-a"
gradientUnits="userSpaceOnUse"
x1="16"
y1="20.6293"
x2="16"
y2="7.8394"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset="0" stop-color="#00d924" />
<stop offset="1" stop-color="#00cb1b" />
</linearGradient>
<path
d="M0 10.82h32c0 8.84-7.16 16-16 16s-16-7.16-16-16z"
fill="url(#StripeClimate-gradient-a)"
/>
<linearGradient
id="StripeClimate-gradient-b"
gradientUnits="userSpaceOnUse"
x1="24"
y1="28.6289"
x2="24"
y2="17.2443"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset=".1562" stop-color="#009c00" />
<stop offset="1" stop-color="#00be20" />
</linearGradient>
<path
d="M32 10.82c0 2.21-1.49 4.65-5.41 4.65-3.42 0-7.27-2.37-10.59-4.65 3.52-2.43 7.39-5.63 10.59-5.63C29.86 5.18 32 8.17 32 10.82z"
fill="url(#StripeClimate-gradient-b)"
/>
<linearGradient
id="StripeClimate-gradient-c"
gradientUnits="userSpaceOnUse"
x1="8"
y1="16.7494"
x2="8"
y2="29.1239"
gradientTransform="matrix(1 0 0 -1 0 34)"
>
<stop offset="0" stop-color="#ffe37d" />
<stop offset="1" stop-color="#ffc900" />
</linearGradient>
<path
d="M0 10.82c0 2.21 1.49 4.65 5.41 4.65 3.42 0 7.27-2.37 10.59-4.65-3.52-2.43-7.39-5.64-10.59-5.64C2.14 5.18 0 8.17 0 10.82z"
fill="url(#StripeClimate-gradient-c)"
/>
</Icon>
)

View File

@@ -13,6 +13,7 @@ import {
Text, Text,
chakra, chakra,
Tooltip, Tooltip,
HStack,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon' import { HelpCircleIcon } from 'assets/icons/HelpCircleIcon'
import { Footer } from 'components/common/Footer' import { Footer } from 'components/common/Footer'
@@ -26,6 +27,7 @@ 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 { Plan } from 'db'
import { StripeClimateLogo } from 'assets/logos/StripeClimateLogo'
const Pricing = () => { const Pricing = () => {
const [starterPrice, setStarterPrice] = useState('$39') const [starterPrice, setStarterPrice] = useState('$39')
@@ -54,173 +56,208 @@ const Pricing = () => {
</DarkMode> </DarkMode>
<VStack spacing={'24'} mt={[20, 32]} w="full"> <VStack spacing={'24'} mt={[20, 32]} w="full">
<Stack align="center" spacing="6"> <Stack align="center" spacing="12" w="full">
<Heading fontSize="6xl">Plans fit for you</Heading> <VStack>
<Text maxW="900px" fontSize="xl" textAlign="center"> <Heading fontSize="6xl">Plans fit for you</Heading>
Whether you're a{' '} <Text maxW="900px" fontSize="xl" textAlign="center">
<Text as="span" color="orange.200" fontWeight="bold"> Whether you're a{' '}
solo business owner <Text as="span" color="orange.200" fontWeight="bold">
</Text>{' '} solo business owner
or a{' '} </Text>{' '}
<Text as="span" color="blue.200" fontWeight="bold"> or a{' '}
growing startup <Text as="span" color="blue.200" fontWeight="bold">
growing startup
</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.
</Text> </Text>
, Typebot is here to help you build high-performing bots for the </VStack>
right price. Pay for as little or as much usage as you need.
<HStack
maxW="500px"
spacing="4"
bgColor="gray.800"
p="4"
rounded="md"
>
<StripeClimateLogo />
<Text fontSize="sm">
Typebot is contributing 1% of your subscription to remove CO₂
from the atmosphere.{' '}
<NextChakraLink
href="https://climate.stripe.com/5VCRAq"
isExternal
textDecor="underline"
>
More info.
</NextChakraLink>
</Text>
</HStack>
<Stack
direction={['column', 'row']}
alignItems={['stretch']}
spacing={10}
px={[4, 0]}
w="full"
maxW="1200px"
>
<PricingCard
data={{
price: 'Free',
name: 'Personal',
features: [
'Unlimited typebots',
'300 chats included',
'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 chats</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 chats</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>
<Text fontSize="lg">
Need custom limits? Specific features?{' '}
<NextChakraLink
href={'https://typebot.io/enterprise-lead-form'}
isExternal
textDecor="underline"
>
Let's chat!
</NextChakraLink>
</Text> </Text>
</Stack> </Stack>
<Stack
direction={['column', 'row']}
alignItems={['stretch']}
spacing={10}
px={[4, 0]}
w="full"
maxW="1200px"
>
<PricingCard
data={{
price: 'Free',
name: 'Personal',
features: [
'Unlimited typebots',
'300 chats included',
'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 chats</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 chats</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>
<VStack maxW="1200px" w="full" spacing={[12, 20]} px="4"> <VStack maxW="1200px" w="full" spacing={[12, 20]} px="4">
<Stack w="full" spacing={10} display={['none', 'flex']}> <Stack w="full" spacing={10} display={['none', 'flex']}>
<Heading>Compare plans & features</Heading> <Heading>Compare plans & features</Heading>
@@ -244,6 +281,38 @@ const Pricing = () => {
const Faq = () => { const Faq = () => {
return ( return (
<Accordion w="full" allowToggle defaultIndex={0}> <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't be stored anymore. You will
need to upgrade the limit or free up some space to continue collecting
your users' files.
</AccordionPanel>
</AccordionItem>
<AccordionItem> <AccordionItem>
<Heading as="h2"> <Heading as="h2">
<AccordionButton py="6"> <AccordionButton py="6">

View File

@@ -76,7 +76,6 @@ const checkStorageLimit = async (typebotId: string) => {
}) })
if (!typebot?.workspace) throw new Error('Workspace not found') if (!typebot?.workspace) throw new Error('Workspace not found')
const { workspace } = typebot const { workspace } = typebot
console.log(typebot.workspaceId)
const { const {
_sum: { storageUsed: totalStorageUsed }, _sum: { storageUsed: totalStorageUsed },
} = await prisma.answer.aggregate({ } = await prisma.answer.aggregate({

View File

@@ -4,14 +4,7 @@ import { ChatGroup } from './ChatGroup'
import { useFrame } from 'react-frame-component' import { useFrame } from 'react-frame-component'
import { setCssVariablesValue } from '../services/theme' import { setCssVariablesValue } from '../services/theme'
import { useAnswers } from '../contexts/AnswersContext' import { useAnswers } from '../contexts/AnswersContext'
import { import { Group, Edge, PublicTypebot, Theme, VariableWithValue } from 'models'
Group,
Edge,
PublicTypebot,
Theme,
VariableWithValue,
Block,
} from 'models'
import { byId, isDefined, isInputBlock, isNotDefined } from 'utils' import { byId, isDefined, isInputBlock, isNotDefined } from 'utils'
import { animateScroll as scroll } from 'react-scroll' import { animateScroll as scroll } from 'react-scroll'
import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext' import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
@@ -80,7 +73,6 @@ export const ConversationContainer = ({
} }
const nextGroup = currentTypebot.groups.find(byId(nextEdge.to.groupId)) const nextGroup = currentTypebot.groups.find(byId(nextEdge.to.groupId))
if (!nextGroup) return onCompleted() if (!nextGroup) return onCompleted()
console.log(nextGroup, nextEdge)
const startBlockIndex = nextEdge.to.blockId const startBlockIndex = nextEdge.to.blockId
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId)) ? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
: 0 : 0

View File

@@ -0,0 +1,5 @@
-- DropForeignKey
ALTER TABLE "Result" DROP CONSTRAINT "Result_typebotId_fkey";
-- AddForeignKey
ALTER TABLE "Result" ADD CONSTRAINT "Result_typebotId_fkey" FOREIGN KEY ("typebotId") REFERENCES "Typebot"("id") ON DELETE NO ACTION ON UPDATE CASCADE;

View File

@@ -212,7 +212,7 @@ model Result {
isCompleted Boolean isCompleted Boolean
hasStarted Boolean? hasStarted Boolean?
isArchived Boolean? isArchived Boolean?
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade) typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: NoAction)
answers Answer[] answers Answer[]
logs Log[] logs Log[]
} }

View File

@@ -13,10 +13,10 @@ const prisma = new PrismaClient()
const main = async () => { const main = async () => {
await injectFakeResults(prisma)({ await injectFakeResults(prisma)({
count: 150, count: 200,
typebotId: 'cl89sq4vb030109laivd9ck97', typebotId: 'cl8hl08xt009909l6pwqenf63',
isChronological: false, isChronological: false,
idPrefix: 'batch2', fakeStorage: 3 * 1024 * 1024 * 1024,
}) })
} }