2
0

🚸 New dedicated onboarding page

This commit is contained in:
Baptiste Arnaud
2023-07-25 16:12:40 +02:00
parent 283c55c1a4
commit 43555c171e
15 changed files with 263 additions and 183 deletions

View File

@@ -10,3 +10,4 @@ NEXT_PUBLIC_E2E_TEST=
NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME= NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME=
NEXT_PUBLIC_UNSPLASH_APP_NAME= NEXT_PUBLIC_UNSPLASH_APP_NAME=
NEXT_PUBLIC_UNSPLASH_ACCESS_KEY= NEXT_PUBLIC_UNSPLASH_ACCESS_KEY=
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID=

View File

@@ -628,3 +628,10 @@ export const BookIcon = (props: IconProps) => (
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path> <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
</Icon> </Icon>
) )
export const ChevronLastIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<path d="m7 18 6-6-6-6" />
<path d="M17 6v12" />
</Icon>
)

View File

@@ -0,0 +1,183 @@
import { ChevronLastIcon } from '@/components/icons'
import {
Button,
Flex,
HStack,
StackProps,
VStack,
chakra,
useColorModeValue,
} from '@chakra-ui/react'
import { Standard } from '@typebot.io/nextjs'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import confetti from 'canvas-confetti'
import { useUser } from '@/features/account/hooks/useUser'
import { env, isEmpty } from '@typebot.io/lib'
import { useI18n } from '@/locales'
const totalSteps = 5
export const OnboardingPage = () => {
const t = useI18n()
const { push, replace } = useRouter()
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
const confettiCanon = useRef<confetti.CreateTypes>()
const { user, updateUser } = useUser()
const [currentStep, setCurrentStep] = useState<number>(user?.name ? 2 : 1)
const isNewUser =
user &&
new Date(user.createdAt as unknown as string).toDateString() ===
new Date().toDateString()
useEffect(() => {
initConfettis()
})
useEffect(() => {
if (!user?.createdAt) return
if (isNewUser === false || isEmpty(env('ONBOARDING_TYPEBOT_ID')))
replace('/typebots')
}, [isNewUser, replace, user?.createdAt])
const initConfettis = () => {
if (!confettiCanvaContainer.current || confettiCanon.current) return
confettiCanon.current = confetti.create(confettiCanvaContainer.current, {
resize: true,
useWorker: true,
})
}
const updateUserInfo = async (answer: {
message: string
blockId: string
}) => {
const isOtherItem = [
'cl126pv7n001o2e6dajltc4qz',
'saw904bfzgspmt0l24achtiy',
].includes(answer.blockId)
if (isOtherItem) return
const isName = answer.blockId === 'cl126820m000g2e6dfleq78bt'
const isCompany = answer.blockId === 'cl126jioz000v2e6dwrk1f2cb'
const isCategories = answer.blockId === 'cl126lb8v00142e6duv5qe08l'
if (isName) updateUser({ name: answer.message })
if (isCategories) {
const onboardingCategories = answer.message.split(', ')
updateUser({ onboardingCategories })
}
if (isCompany) {
updateUser({ company: answer.message })
if (confettiCanon.current) shootConfettis(confettiCanon.current)
}
setCurrentStep((prev) => prev + 1)
}
if (!isNewUser) return null
return (
<VStack h="100vh" flexDir="column" justifyContent="center" spacing="10">
<Button
rightIcon={<ChevronLastIcon />}
pos="fixed"
top="5"
right="5"
variant="ghost"
size="sm"
onClick={() => push('/typebots')}
>
{t('skip')}
</Button>
<Dots currentStep={currentStep} pos="fixed" top="9" />
<Flex w="full" maxW="800px" h="full" maxH="70vh" rounded="lg">
<Standard
typebot={env('ONBOARDING_TYPEBOT_ID')}
style={{ borderRadius: '1rem' }}
prefilledVariables={{ Name: user?.name, Email: user?.email }}
onEnd={() => {
setTimeout(() => {
push('/typebots/create', { query: { isFirstBot: true } })
}, 2000)
}}
onAnswer={updateUserInfo}
/>
</Flex>
<chakra.canvas
ref={confettiCanvaContainer}
pos="fixed"
top="0"
left="0"
w="full"
h="full"
zIndex={9999}
pointerEvents="none"
/>
</VStack>
)
}
const Dots = ({
currentStep,
...props
}: { currentStep: number } & StackProps) => {
const highlightedBgColor = useColorModeValue('gray.500', 'gray.100')
const baseBgColor = useColorModeValue('gray.200', 'gray.700')
return (
<HStack spacing="10" {...props}>
{Array.from({ length: totalSteps }).map((_, index) => (
<chakra.div
key={index}
boxSize={'2'}
bgColor={currentStep === index + 1 ? highlightedBgColor : baseBgColor}
rounded="full"
transition="background-color 0.2s ease"
/>
))}
</HStack>
)
}
const shootConfettis = (confettiCanon: confetti.CreateTypes) => {
const count = 200
const defaults = {
origin: { y: 0.7 },
}
const fire = (
particleRatio: number,
opts: {
spread: number
startVelocity?: number
decay?: number
scalar?: number
}
) => {
confettiCanon(
Object.assign({}, defaults, opts, {
particleCount: Math.floor(count * particleRatio),
})
)
}
fire(0.25, {
spread: 26,
startVelocity: 55,
})
fire(0.2, {
spread: 60,
})
fire(0.35, {
spread: 100,
decay: 0.91,
scalar: 0.8,
})
fire(0.1, {
spread: 120,
startVelocity: 25,
decay: 0.92,
scalar: 1.2,
})
fire(0.1, {
spread: 120,
startVelocity: 45,
})
}

View File

@@ -48,6 +48,22 @@ export const SignInPage = ({ type }: Props) => {
</Text> </Text>
)} )}
<SignInForm defaultEmail={query.g?.toString()} /> <SignInForm defaultEmail={query.g?.toString()} />
{type === 'signup' ? (
<Text fontSize="sm" maxW="400px" textAlign="center">
{scopedT('register.aggreeToTerms', {
termsOfService: (
<TextLink href={'https://typebot.io/terms-of-service'}>
{scopedT('register.termsOfService')}
</TextLink>
),
privacyPolicy: (
<TextLink href={'https://typebot.io/privacy-policies'}>
{scopedT('register.privacyPolicy')}
</TextLink>
),
})}
</Text>
) : null}
</VStack> </VStack>
) )
} }

View File

@@ -42,7 +42,6 @@ const parseVideoUrl = (
return { type: VideoBubbleContentType.VIMEO, url, id } return { type: VideoBubbleContentType.VIMEO, url, id }
} }
if (youtubeRegex.test(url)) { if (youtubeRegex.test(url)) {
console.log(url.match(youtubeRegex)?.at(2))
const id = url.match(youtubeRegex)?.at(2) const id = url.match(youtubeRegex)?.at(2)
if (!id) return { type: VideoBubbleContentType.URL, url } if (!id) return { type: VideoBubbleContentType.URL, url }
return { type: VideoBubbleContentType.YOUTUBE, url, id } return { type: VideoBubbleContentType.YOUTUBE, url, id }

View File

@@ -1,142 +0,0 @@
import { chakra, useColorModeValue } from '@chakra-ui/react'
import { Popup } from '@typebot.io/nextjs'
import { useUser } from '@/features/account/hooks/useUser'
import React, { useEffect, useRef, useState } from 'react'
import confetti from 'canvas-confetti'
import { useRouter } from 'next/router'
type Props = { totalTypebots: number }
export const OnboardingModal = ({ totalTypebots }: Props) => {
const { push } = useRouter()
const backgroundColor = useColorModeValue('white', '#171923')
const { user, updateUser } = useUser()
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
const confettiCanon = useRef<confetti.CreateTypes>()
const [chosenCategories, setChosenCategories] = useState<string[]>([])
const isNewUser =
user &&
new Date(user?.createdAt as unknown as string).toDateString() ===
new Date().toDateString() &&
totalTypebots === 0
useEffect(() => {
initConfettis()
}, [])
const initConfettis = () => {
if (!confettiCanvaContainer.current || confettiCanon.current) return
confettiCanon.current = confetti.create(confettiCanvaContainer.current, {
resize: true,
useWorker: true,
})
}
const handleBotEnd = () => {
setTimeout(() => {
push('/typebots/create', { query: { isFirstBot: true } })
}, 2000)
}
const handleNewAnswer = async (answer: {
message: string
blockId: string
}) => {
const isName = answer.blockId === 'cl126820m000g2e6dfleq78bt'
const isCompany = answer.blockId === 'cl126jioz000v2e6dwrk1f2cb'
const isCategories = answer.blockId === 'cl126lb8v00142e6duv5qe08l'
const isOtherCategories = answer.blockId === 'cl126pv7n001o2e6dajltc4qz'
if (isName) updateUser({ name: answer.message })
if (isCompany) {
updateUser({ company: answer.message })
if (confettiCanon.current) shootConfettis(confettiCanon.current)
}
if (isCategories) {
const onboardingCategories = answer.message.split(', ')
updateUser({ onboardingCategories })
setChosenCategories(onboardingCategories)
}
if (isOtherCategories)
updateUser({
onboardingCategories: [...chosenCategories, answer.message],
})
}
return (
<>
<chakra.canvas
ref={confettiCanvaContainer}
pos="fixed"
top="0"
left="0"
w="full"
h="full"
zIndex={9999}
pointerEvents="none"
/>
{user?.email && (
<Popup
typebot="onboarding-typebot"
prefilledVariables={{
Name: user.name?.split(' ')[0] ?? undefined,
Email: user.email ?? undefined,
}}
theme={{
backgroundColor,
zIndex: 100,
}}
defaultOpen={isNewUser}
onAnswer={handleNewAnswer}
onEnd={handleBotEnd}
/>
)}
</>
)
}
const shootConfettis = (confettiCanon: confetti.CreateTypes) => {
const count = 200
const defaults = {
origin: { y: 0.7 },
}
const fire = (
particleRatio: number,
opts: {
spread: number
startVelocity?: number
decay?: number
scalar?: number
}
) => {
confettiCanon(
Object.assign({}, defaults, opts, {
particleCount: Math.floor(count * particleRatio),
})
)
}
fire(0.25, {
spread: 26,
startVelocity: 55,
})
fire(0.2, {
spread: 60,
})
fire(0.35, {
spread: 100,
decay: 0.91,
scalar: 0.8,
})
fire(0.1, {
spread: 120,
startVelocity: 25,
decay: 0.92,
scalar: 1.2,
})
fire(0.1, {
spread: 120,
startVelocity: 45,
})
}

View File

@@ -12,7 +12,6 @@ import {
import { useTypebotDnd } from '../TypebotDndProvider' import { useTypebotDnd } from '../TypebotDndProvider'
import React, { useState } from 'react' import React, { useState } from 'react'
import { BackButton } from './BackButton' import { BackButton } from './BackButton'
import { OnboardingModal } from '../../dashboard/components/OnboardingModal'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider' import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { useToast } from '@/hooks/useToast' import { useToast } from '@/hooks/useToast'
import { useFolders } from '../hooks/useFolders' import { useFolders } from '../hooks/useFolders'
@@ -26,7 +25,6 @@ import { TypebotCardOverlay } from './TypebotButtonOverlay'
import { useI18n } from '@/locales' import { useI18n } from '@/locales'
import { useTypebots } from '@/features/dashboard/hooks/useTypebots' import { useTypebots } from '@/features/dashboard/hooks/useTypebots'
import { TypebotInDashboard } from '@/features/dashboard/types' import { TypebotInDashboard } from '@/features/dashboard/types'
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
type Props = { folder: DashboardFolder | null } type Props = { folder: DashboardFolder | null }
@@ -161,9 +159,6 @@ export const FolderContent = ({ folder }: Props) => {
return ( return (
<Flex w="full" flex="1" justify="center"> <Flex w="full" flex="1" justify="center">
{typebots && isCloudProdInstance && (
<OnboardingModal totalTypebots={typebots.length} />
)}
<Stack w="1000px" spacing={6}> <Stack w="1000px" spacing={6}>
<Skeleton isLoaded={folder?.name !== undefined}> <Skeleton isLoaded={folder?.name !== undefined}>
<Heading as="h1">{folder?.name}</Heading> <Heading as="h1">{folder?.name}</Heading>

View File

@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade', downgrade: 'Downgrade',
remove: 'Entfernen', remove: 'Entfernen',
pending: 'Ausstehend', pending: 'Ausstehend',
skip: 'Überspringen',
'folders.createFolderButton.label': 'Ordner erstellen', 'folders.createFolderButton.label': 'Ordner erstellen',
'folders.createTypebotButton.label': 'Typebot erstellen', 'folders.createTypebotButton.label': 'Typebot erstellen',
'folders.folderButton.deleteConfirmationMessage': 'folders.folderButton.deleteConfirmationMessage':
@@ -77,6 +78,10 @@ export default {
'auth.register.alreadyHaveAccountLabel.preLink': 'auth.register.alreadyHaveAccountLabel.preLink':
'Bereits ein Konto vorhanden?', 'Bereits ein Konto vorhanden?',
'auth.register.alreadyHaveAccountLabel.link': 'Anmelden', 'auth.register.alreadyHaveAccountLabel.link': 'Anmelden',
'auth.register.aggreeToTerms':
'Durch die Registrierung stimmst du unseren {termsOfService} und {privacyPolicy} zu.',
'auth.register.termsOfService': 'Nutzungsbedingungen',
'auth.register.privacyPolicy': 'Datenschutzrichtlinie',
'auth.error.default': 'Versuche, dich mit einem anderen Konto anzumelden.', 'auth.error.default': 'Versuche, dich mit einem anderen Konto anzumelden.',
'auth.error.email': 'auth.error.email':
'E-Mail nicht gefunden. Versuche, dich mit einem anderen Anbieter anzumelden.', 'E-Mail nicht gefunden. Versuche, dich mit einem anderen Anbieter anzumelden.',

View File

@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade', downgrade: 'Downgrade',
remove: 'Remove', remove: 'Remove',
pending: 'Pending', pending: 'Pending',
skip: 'Skip',
'folders.createFolderButton.label': 'Create a folder', 'folders.createFolderButton.label': 'Create a folder',
'folders.createTypebotButton.label': 'Create a typebot', 'folders.createTypebotButton.label': 'Create a typebot',
'folders.folderButton.deleteConfirmationMessage': 'folders.folderButton.deleteConfirmationMessage':
@@ -75,6 +76,10 @@ export default {
'auth.register.heading': 'Create an account', 'auth.register.heading': 'Create an account',
'auth.register.alreadyHaveAccountLabel.preLink': 'Already have an account?', 'auth.register.alreadyHaveAccountLabel.preLink': 'Already have an account?',
'auth.register.alreadyHaveAccountLabel.link': 'Sign in', 'auth.register.alreadyHaveAccountLabel.link': 'Sign in',
'auth.register.aggreeToTerms':
'By signing up, you agree to our {termsOfService} and {privacyPolicy}.',
'auth.register.termsOfService': 'terms of service',
'auth.register.privacyPolicy': 'privacy policy',
'auth.error.default': 'Try signing with a different account.', 'auth.error.default': 'Try signing with a different account.',
'auth.error.email': 'Email not found. Try signing with a different provider.', 'auth.error.email': 'Email not found. Try signing with a different provider.',
'auth.error.oauthNotLinked': 'auth.error.oauthNotLinked':

View File

@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade', downgrade: 'Downgrade',
remove: 'Retirer', remove: 'Retirer',
pending: 'En attente', pending: 'En attente',
skip: 'Passer',
'folders.createFolderButton.label': 'Créer un dossier', 'folders.createFolderButton.label': 'Créer un dossier',
'folders.createTypebotButton.label': 'Créer un typebot', 'folders.createTypebotButton.label': 'Créer un typebot',
'folders.folderButton.deleteConfirmationMessage': 'folders.folderButton.deleteConfirmationMessage':
@@ -75,6 +76,10 @@ export default {
'auth.register.heading': 'Créer un compte', 'auth.register.heading': 'Créer un compte',
'auth.register.alreadyHaveAccountLabel.preLink': 'Tu as déjà un compte?', 'auth.register.alreadyHaveAccountLabel.preLink': 'Tu as déjà un compte?',
'auth.register.alreadyHaveAccountLabel.link': 'Se connecter', 'auth.register.alreadyHaveAccountLabel.link': 'Se connecter',
'auth.register.aggreeToTerms':
'En vous inscrivant, vous acceptez nos {termsOfService} et {privacyPolicy}.',
'auth.register.termsOfService': "conditions d'utilisation",
'auth.register.privacyPolicy': 'politique de confidentialité',
'auth.error.default': 'Essaye de te connecter avec un compte différent.', 'auth.error.default': 'Essaye de te connecter avec un compte différent.',
'auth.error.email': 'auth.error.email':
'Email non trouvé. Essaye de te connecter avec un fournisseur différent.', 'Email non trouvé. Essaye de te connecter avec un fournisseur différent.',

View File

@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade', downgrade: 'Downgrade',
remove: 'Remover', remove: 'Remover',
pending: 'Pendente', pending: 'Pendente',
skip: 'Pular',
'folders.createFolderButton.label': 'Criar uma pasta', 'folders.createFolderButton.label': 'Criar uma pasta',
'folders.createTypebotButton.label': 'Criar um typebot', 'folders.createTypebotButton.label': 'Criar um typebot',
'folders.folderButton.deleteConfirmationMessage': 'folders.folderButton.deleteConfirmationMessage':
@@ -76,6 +77,10 @@ export default {
'auth.register.heading': 'Criar uma conta', 'auth.register.heading': 'Criar uma conta',
'auth.register.alreadyHaveAccountLabel.preLink': 'Já tem uma conta?', 'auth.register.alreadyHaveAccountLabel.preLink': 'Já tem uma conta?',
'auth.register.alreadyHaveAccountLabel.link': 'Entrar', 'auth.register.alreadyHaveAccountLabel.link': 'Entrar',
'auth.register.aggreeToTerms':
'Ao se cadastrar, você concorda com nossos {termsOfService} e {privacyPolicy}.',
'auth.register.termsOfService': 'termos de serviço',
'auth.register.privacyPolicy': 'política de privacidade',
'auth.error.default': 'Tente entrar com uma conta diferente.', 'auth.error.default': 'Tente entrar com uma conta diferente.',
'auth.error.email': 'auth.error.email':
'E-mail não encontrado. Tente entrar com um provedor diferente.', 'E-mail não encontrado. Tente entrar com um provedor diferente.',

View File

@@ -121,8 +121,8 @@ if (isNotEmpty(process.env.CUSTOM_OAUTH_WELL_KNOWN_URL)) {
type: 'oauth', type: 'oauth',
authorization: { authorization: {
params: { params: {
scope: process.env.CUSTOM_OAUTH_SCOPE ?? 'openid profile email' scope: process.env.CUSTOM_OAUTH_SCOPE ?? 'openid profile email',
} },
}, },
clientId: process.env.CUSTOM_OAUTH_CLIENT_ID, clientId: process.env.CUSTOM_OAUTH_CLIENT_ID,
clientSecret: process.env.CUSTOM_OAUTH_CLIENT_SECRET, clientSecret: process.env.CUSTOM_OAUTH_CLIENT_SECRET,
@@ -156,6 +156,9 @@ export const authOptions: AuthOptions = {
}, },
pages: { pages: {
signIn: '/signin', signIn: '/signin',
newUser: process.env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID
? '/onboarding'
: undefined,
}, },
callbacks: { callbacks: {
session: async ({ session, user }) => { session: async ({ session, user }) => {

View File

@@ -0,0 +1,5 @@
import { OnboardingPage } from '@/features/auth/components/OnboardingPage'
export default function Page() {
return <OnboardingPage />
}

View File

@@ -21,6 +21,7 @@ Parameters marked with <Asterix/> are required.
| NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. | | NEXTAUTH_URL_INTERNAL | | The internal builder base URL. You have to set it only when `NEXTAUTH_URL` can't be reached by your builder container / server. For a docker deployment, you should set it to `http://localhost:3000`. |
| DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` | | DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` |
| DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. | | DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. |
| NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID | | Typebot ID used for the onboarding. Onboarding page is skipped if not provided. |
## Email (Auth, notifications) ## Email (Auth, notifications)

View File

@@ -14,12 +14,13 @@ const PrivacyPolicies = () => {
<Heading>1. Terms</Heading> <Heading>1. Terms</Heading>
<p> <p>
By accessing this Website, accessible from https://www.typebot.io, you By accessing this Website, accessible from https://typebot.io and
are agreeing to be bound by these Website Terms and Conditions of Use https://app.typebot.io, you are agreeing to be bound by these Website
and agree that you are responsible for the agreement with any Terms and Conditions of Use and agree that you are responsible for the
applicable local laws. If you disagree with any of these terms, you agreement with any applicable local laws. If you disagree with any of
are prohibited from accessing this site. The materials contained in these terms, you are prohibited from accessing this site. The
this Website are protected by copyright and trade mark law. materials contained in this Website are protected by copyright and
trade mark law.
</p> </p>
<Heading>2. Use License</Heading> <Heading>2. Use License</Heading>
@@ -32,15 +33,6 @@ const PrivacyPolicies = () => {
</p> </p>
<ul> <ul>
<li>modify or copy the materials;</li>
<li>
use the materials for any commercial purpose or for any public
display;
</li>
<li>
attempt to reverse engineer any software contained on Typebot&apos;s
Website;
</li>
<li> <li>
remove any copyright or other proprietary notations from the remove any copyright or other proprietary notations from the
materials; or materials; or
@@ -70,12 +62,12 @@ const PrivacyPolicies = () => {
<Heading>3. Disclaimer</Heading> <Heading>3. Disclaimer</Heading>
<p> <p>
All the materials on Typebots Website are provided &quot;as is&quot;. All the materials on Typebot&apos;s Website are provided &quot;as
Typebot makes no warranties, may it be expressed or implied, therefore is&quot;. Typebot makes no warranties, may it be expressed or implied,
negates all other warranties. Furthermore, Typebot does not make any therefore negates all other warranties. Furthermore, Typebot does not
representations concerning the accuracy or reliability of the use of make any representations concerning the accuracy or reliability of the
the materials on its Website or otherwise relating to such materials use of the materials on its Website or otherwise relating to such
or any sites linked to this Website. materials or any sites linked to this Website.
</p> </p>
<Heading>4. Limitations</Heading> <Heading>4. Limitations</Heading>
@@ -83,22 +75,22 @@ const PrivacyPolicies = () => {
<p> <p>
Typebot or its suppliers will not be hold accountable for any damages Typebot or its suppliers will not be hold accountable for any damages
that will arise with the use or inability to use the materials on that will arise with the use or inability to use the materials on
Typebots Website, even if Typebot or an authorize representative of Typebot&apos;s Website, even if Typebot or an authorize representative
this Website has been notified, orally or written, of the possibility of this Website has been notified, orally or written, of the
of such damage. Some jurisdiction does not allow limitations on possibility of such damage. Some jurisdiction does not allow
implied warranties or limitations of liability for incidental damages, limitations on implied warranties or limitations of liability for
these limitations may not apply to you. incidental damages, these limitations may not apply to you.
</p> </p>
<Heading>5. Revisions and Errata</Heading> <Heading>5. Revisions and Errata</Heading>
<p> <p>
The materials appearing on Typebots Website may include technical, The materials appearing on Typebot&apos;s Website may include
typographical, or photographic errors. Typebot will not promise that technical, typographical, or photographic errors. Typebot will not
any of the materials in this Website are accurate, complete, or promise that any of the materials in this Website are accurate,
current. Typebot may change the materials contained on its Website at complete, or current. Typebot may change the materials contained on
any time without notice. Typebot does not make any commitment to its Website at any time without notice. Typebot does not make any
update the materials. commitment to update the materials.
</p> </p>
<Heading>6. Links</Heading> <Heading>6. Links</Heading>
@@ -107,7 +99,7 @@ const PrivacyPolicies = () => {
Typebot has not reviewed all of the sites linked to its Website and is Typebot has not reviewed all of the sites linked to its Website and is
not responsible for the contents of any such linked site. The presence not responsible for the contents of any such linked site. The presence
of any link does not imply endorsement by Typebot of the site. The use of any link does not imply endorsement by Typebot of the site. The use
of any linked website is at the users own risk. of any linked website is at the user&apos;s own risk.
</p> </p>
<Heading>7. Site Terms of Use Modifications</Heading> <Heading>7. Site Terms of Use Modifications</Heading>