diff --git a/apps/builder/.env.docker b/apps/builder/.env.docker
index b4ee669d8..fd9e2c390 100644
--- a/apps/builder/.env.docker
+++ b/apps/builder/.env.docker
@@ -10,3 +10,4 @@ NEXT_PUBLIC_E2E_TEST=
NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME=
NEXT_PUBLIC_UNSPLASH_APP_NAME=
NEXT_PUBLIC_UNSPLASH_ACCESS_KEY=
+NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID=
diff --git a/apps/builder/src/components/icons.tsx b/apps/builder/src/components/icons.tsx
index 67315d638..d7c3d9676 100644
--- a/apps/builder/src/components/icons.tsx
+++ b/apps/builder/src/components/icons.tsx
@@ -628,3 +628,10 @@ export const BookIcon = (props: IconProps) => (
)
+
+export const ChevronLastIcon = (props: IconProps) => (
+
+
+
+
+)
diff --git a/apps/builder/src/features/auth/components/OnboardingPage.tsx b/apps/builder/src/features/auth/components/OnboardingPage.tsx
new file mode 100644
index 000000000..6640d6c55
--- /dev/null
+++ b/apps/builder/src/features/auth/components/OnboardingPage.tsx
@@ -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(null)
+ const confettiCanon = useRef()
+ const { user, updateUser } = useUser()
+ const [currentStep, setCurrentStep] = useState(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 (
+
+ }
+ pos="fixed"
+ top="5"
+ right="5"
+ variant="ghost"
+ size="sm"
+ onClick={() => push('/typebots')}
+ >
+ {t('skip')}
+
+
+
+ {
+ setTimeout(() => {
+ push('/typebots/create', { query: { isFirstBot: true } })
+ }, 2000)
+ }}
+ onAnswer={updateUserInfo}
+ />
+
+
+
+ )
+}
+
+const Dots = ({
+ currentStep,
+ ...props
+}: { currentStep: number } & StackProps) => {
+ const highlightedBgColor = useColorModeValue('gray.500', 'gray.100')
+ const baseBgColor = useColorModeValue('gray.200', 'gray.700')
+ return (
+
+ {Array.from({ length: totalSteps }).map((_, index) => (
+
+ ))}
+
+ )
+}
+
+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,
+ })
+}
diff --git a/apps/builder/src/features/auth/components/SignInPage.tsx b/apps/builder/src/features/auth/components/SignInPage.tsx
index 42604daef..69442dab9 100644
--- a/apps/builder/src/features/auth/components/SignInPage.tsx
+++ b/apps/builder/src/features/auth/components/SignInPage.tsx
@@ -48,6 +48,22 @@ export const SignInPage = ({ type }: Props) => {
)}
+ {type === 'signup' ? (
+
+ {scopedT('register.aggreeToTerms', {
+ termsOfService: (
+
+ {scopedT('register.termsOfService')}
+
+ ),
+ privacyPolicy: (
+
+ {scopedT('register.privacyPolicy')}
+
+ ),
+ })}
+
+ ) : null}
)
}
diff --git a/apps/builder/src/features/blocks/bubbles/video/components/VideoUploadContent.tsx b/apps/builder/src/features/blocks/bubbles/video/components/VideoUploadContent.tsx
index 8a59b448e..1c11a5869 100644
--- a/apps/builder/src/features/blocks/bubbles/video/components/VideoUploadContent.tsx
+++ b/apps/builder/src/features/blocks/bubbles/video/components/VideoUploadContent.tsx
@@ -42,7 +42,6 @@ const parseVideoUrl = (
return { type: VideoBubbleContentType.VIMEO, url, id }
}
if (youtubeRegex.test(url)) {
- console.log(url.match(youtubeRegex)?.at(2))
const id = url.match(youtubeRegex)?.at(2)
if (!id) return { type: VideoBubbleContentType.URL, url }
return { type: VideoBubbleContentType.YOUTUBE, url, id }
diff --git a/apps/builder/src/features/dashboard/components/OnboardingModal.tsx b/apps/builder/src/features/dashboard/components/OnboardingModal.tsx
deleted file mode 100644
index 144d56174..000000000
--- a/apps/builder/src/features/dashboard/components/OnboardingModal.tsx
+++ /dev/null
@@ -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(null)
- const confettiCanon = useRef()
- const [chosenCategories, setChosenCategories] = useState([])
-
- 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 (
- <>
-
- {user?.email && (
-
- )}
- >
- )
-}
-
-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,
- })
-}
diff --git a/apps/builder/src/features/folders/components/FolderContent.tsx b/apps/builder/src/features/folders/components/FolderContent.tsx
index 0baeeb6b2..98cb6c026 100644
--- a/apps/builder/src/features/folders/components/FolderContent.tsx
+++ b/apps/builder/src/features/folders/components/FolderContent.tsx
@@ -12,7 +12,6 @@ import {
import { useTypebotDnd } from '../TypebotDndProvider'
import React, { useState } from 'react'
import { BackButton } from './BackButton'
-import { OnboardingModal } from '../../dashboard/components/OnboardingModal'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { useToast } from '@/hooks/useToast'
import { useFolders } from '../hooks/useFolders'
@@ -26,7 +25,6 @@ import { TypebotCardOverlay } from './TypebotButtonOverlay'
import { useI18n } from '@/locales'
import { useTypebots } from '@/features/dashboard/hooks/useTypebots'
import { TypebotInDashboard } from '@/features/dashboard/types'
-import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
type Props = { folder: DashboardFolder | null }
@@ -161,9 +159,6 @@ export const FolderContent = ({ folder }: Props) => {
return (
- {typebots && isCloudProdInstance && (
-
- )}
{folder?.name}
diff --git a/apps/builder/src/locales/de.ts b/apps/builder/src/locales/de.ts
index ff34227a5..32c4fb347 100644
--- a/apps/builder/src/locales/de.ts
+++ b/apps/builder/src/locales/de.ts
@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade',
remove: 'Entfernen',
pending: 'Ausstehend',
+ skip: 'Überspringen',
'folders.createFolderButton.label': 'Ordner erstellen',
'folders.createTypebotButton.label': 'Typebot erstellen',
'folders.folderButton.deleteConfirmationMessage':
@@ -77,6 +78,10 @@ export default {
'auth.register.alreadyHaveAccountLabel.preLink':
'Bereits ein Konto vorhanden?',
'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.email':
'E-Mail nicht gefunden. Versuche, dich mit einem anderen Anbieter anzumelden.',
diff --git a/apps/builder/src/locales/en.ts b/apps/builder/src/locales/en.ts
index 29346bcdf..79e9e72cf 100644
--- a/apps/builder/src/locales/en.ts
+++ b/apps/builder/src/locales/en.ts
@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade',
remove: 'Remove',
pending: 'Pending',
+ skip: 'Skip',
'folders.createFolderButton.label': 'Create a folder',
'folders.createTypebotButton.label': 'Create a typebot',
'folders.folderButton.deleteConfirmationMessage':
@@ -75,6 +76,10 @@ export default {
'auth.register.heading': 'Create an account',
'auth.register.alreadyHaveAccountLabel.preLink': 'Already have an account?',
'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.email': 'Email not found. Try signing with a different provider.',
'auth.error.oauthNotLinked':
diff --git a/apps/builder/src/locales/fr.ts b/apps/builder/src/locales/fr.ts
index d7685e01b..f7fb9620a 100644
--- a/apps/builder/src/locales/fr.ts
+++ b/apps/builder/src/locales/fr.ts
@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade',
remove: 'Retirer',
pending: 'En attente',
+ skip: 'Passer',
'folders.createFolderButton.label': 'Créer un dossier',
'folders.createTypebotButton.label': 'Créer un typebot',
'folders.folderButton.deleteConfirmationMessage':
@@ -75,6 +76,10 @@ export default {
'auth.register.heading': 'Créer un compte',
'auth.register.alreadyHaveAccountLabel.preLink': 'Tu as déjà un compte?',
'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.email':
'Email non trouvé. Essaye de te connecter avec un fournisseur différent.',
diff --git a/apps/builder/src/locales/pt.ts b/apps/builder/src/locales/pt.ts
index ff22d73a5..084825126 100644
--- a/apps/builder/src/locales/pt.ts
+++ b/apps/builder/src/locales/pt.ts
@@ -12,6 +12,7 @@ export default {
downgrade: 'Downgrade',
remove: 'Remover',
pending: 'Pendente',
+ skip: 'Pular',
'folders.createFolderButton.label': 'Criar uma pasta',
'folders.createTypebotButton.label': 'Criar um typebot',
'folders.folderButton.deleteConfirmationMessage':
@@ -76,6 +77,10 @@ export default {
'auth.register.heading': 'Criar uma conta',
'auth.register.alreadyHaveAccountLabel.preLink': 'Já tem uma conta?',
'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.email':
'E-mail não encontrado. Tente entrar com um provedor diferente.',
diff --git a/apps/builder/src/pages/api/auth/[...nextauth].ts b/apps/builder/src/pages/api/auth/[...nextauth].ts
index 919d0e40f..1a4da786e 100644
--- a/apps/builder/src/pages/api/auth/[...nextauth].ts
+++ b/apps/builder/src/pages/api/auth/[...nextauth].ts
@@ -121,8 +121,8 @@ if (isNotEmpty(process.env.CUSTOM_OAUTH_WELL_KNOWN_URL)) {
type: 'oauth',
authorization: {
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,
clientSecret: process.env.CUSTOM_OAUTH_CLIENT_SECRET,
@@ -156,6 +156,9 @@ export const authOptions: AuthOptions = {
},
pages: {
signIn: '/signin',
+ newUser: process.env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID
+ ? '/onboarding'
+ : undefined,
},
callbacks: {
session: async ({ session, user }) => {
diff --git a/apps/builder/src/pages/onboarding.tsx b/apps/builder/src/pages/onboarding.tsx
new file mode 100644
index 000000000..080b1ce5d
--- /dev/null
+++ b/apps/builder/src/pages/onboarding.tsx
@@ -0,0 +1,5 @@
+import { OnboardingPage } from '@/features/auth/components/OnboardingPage'
+
+export default function Page() {
+ return
+}
diff --git a/apps/docs/docs/self-hosting/configuration/builder.mdx b/apps/docs/docs/self-hosting/configuration/builder.mdx
index 48076175d..528789aa9 100644
--- a/apps/docs/docs/self-hosting/configuration/builder.mdx
+++ b/apps/docs/docs/self-hosting/configuration/builder.mdx
@@ -21,6 +21,7 @@ Parameters marked with 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`. |
| 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. |
+| NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID | | Typebot ID used for the onboarding. Onboarding page is skipped if not provided. |
## Email (Auth, notifications)
diff --git a/apps/landing-page/pages/terms-of-service.tsx b/apps/landing-page/pages/terms-of-service.tsx
index 3195e4e52..eb7a27b94 100644
--- a/apps/landing-page/pages/terms-of-service.tsx
+++ b/apps/landing-page/pages/terms-of-service.tsx
@@ -14,12 +14,13 @@ const PrivacyPolicies = () => {
1. Terms
- By accessing this Website, accessible from https://www.typebot.io, you
- are agreeing to be bound by these Website Terms and Conditions of Use
- and agree that you are responsible for the agreement with any
- applicable local laws. If you disagree with any of these terms, you
- are prohibited from accessing this site. The materials contained in
- this Website are protected by copyright and trade mark law.
+ By accessing this Website, accessible from https://typebot.io and
+ https://app.typebot.io, you are agreeing to be bound by these Website
+ Terms and Conditions of Use and agree that you are responsible for the
+ agreement with any applicable local laws. If you disagree with any of
+ these terms, you are prohibited from accessing this site. The
+ materials contained in this Website are protected by copyright and
+ trade mark law.
- use the materials for any commercial purpose or for any public
- display;
-
-
- attempt to reverse engineer any software contained on Typebot's
- Website;
-
remove any copyright or other proprietary notations from the
materials; or
@@ -70,12 +62,12 @@ const PrivacyPolicies = () => {
3. Disclaimer
- All the materials on Typebot’s Website are provided "as is".
- Typebot makes no warranties, may it be expressed or implied, therefore
- negates all other warranties. Furthermore, Typebot does not make any
- representations concerning the accuracy or reliability of the use of
- the materials on its Website or otherwise relating to such materials
- or any sites linked to this Website.
+ All the materials on Typebot's Website are provided "as
+ is". Typebot makes no warranties, may it be expressed or implied,
+ therefore negates all other warranties. Furthermore, Typebot does not
+ make any representations concerning the accuracy or reliability of the
+ use of the materials on its Website or otherwise relating to such
+ materials or any sites linked to this Website.
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
- Typebot’s Website, even if Typebot or an authorize representative of
- this Website has been notified, orally or written, of the possibility
- of such damage. Some jurisdiction does not allow limitations on
- implied warranties or limitations of liability for incidental damages,
- these limitations may not apply to you.
+ Typebot's Website, even if Typebot or an authorize representative
+ of this Website has been notified, orally or written, of the
+ possibility of such damage. Some jurisdiction does not allow
+ limitations on implied warranties or limitations of liability for
+ incidental damages, these limitations may not apply to you.
5. Revisions and Errata
- The materials appearing on Typebot’s Website may include technical,
- typographical, or photographic errors. Typebot will not promise that
- any of the materials in this Website are accurate, complete, or
- current. Typebot may change the materials contained on its Website at
- any time without notice. Typebot does not make any commitment to
- update the materials.
+ The materials appearing on Typebot's Website may include
+ technical, typographical, or photographic errors. Typebot will not
+ promise that any of the materials in this Website are accurate,
+ complete, or current. Typebot may change the materials contained on
+ its Website at any time without notice. Typebot does not make any
+ commitment to update the materials.
6. Links
@@ -107,7 +99,7 @@ const PrivacyPolicies = () => {
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
of any link does not imply endorsement by Typebot of the site. The use
- of any linked website is at the user’s own risk.
+ of any linked website is at the user's own risk.
7. Site Terms of Use Modifications