feat: ✨ Add new onboarding flow
This commit is contained in:
@ -122,7 +122,11 @@ export const PersonalInfoForm = () => {
|
||||
|
||||
{hasUnsavedChanges && (
|
||||
<Flex justifyContent="flex-end">
|
||||
<Button colorScheme="blue" onClick={saveUser} isLoading={isSaving}>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
onClick={() => saveUser()}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Flex>
|
||||
|
@ -27,6 +27,7 @@ import { ButtonSkeleton, FolderButton } from './FolderContent/FolderButton'
|
||||
import { SharedTypebotsButton } from './FolderContent/SharedTypebotsButton'
|
||||
import { TypebotButton } from './FolderContent/TypebotButton'
|
||||
import { TypebotCardOverlay } from './FolderContent/TypebotButtonOverlay'
|
||||
import { OnboardingModal } from './OnboardingModal'
|
||||
|
||||
type Props = { folder: DashboardFolder | null }
|
||||
|
||||
@ -163,6 +164,9 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
|
||||
return (
|
||||
<Flex w="full" flex="1" justify="center">
|
||||
{typebots && user && folder === null && (
|
||||
<OnboardingModal totalTypebots={typebots.length} />
|
||||
)}
|
||||
<Stack w="1000px" spacing={6}>
|
||||
<Skeleton isLoaded={folder?.name !== undefined}>
|
||||
<Heading as="h1">{folder?.name}</Heading>
|
||||
@ -179,6 +183,7 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
<CreateBotButton
|
||||
folderId={folder?.id}
|
||||
isLoading={isTypebotLoading}
|
||||
isFirstBot={typebots?.length === 0 && folder === null}
|
||||
/>
|
||||
{totalSharedTypebots > 0 && <SharedTypebotsButton />}
|
||||
{isFolderLoading && <ButtonSkeleton />}
|
||||
|
@ -1,18 +1,23 @@
|
||||
import { Button, ButtonProps, Text, VStack } from '@chakra-ui/react'
|
||||
import { PlusIcon } from 'assets/icons'
|
||||
import { useRouter } from 'next/router'
|
||||
import { stringify } from 'qs'
|
||||
import React from 'react'
|
||||
|
||||
export const CreateBotButton = ({
|
||||
folderId,
|
||||
isFirstBot,
|
||||
...props
|
||||
}: { folderId?: string } & ButtonProps) => {
|
||||
}: { folderId?: string; isFirstBot: boolean } & ButtonProps) => {
|
||||
const router = useRouter()
|
||||
|
||||
const handleClick = () =>
|
||||
folderId
|
||||
? router.push(`/typebots/create?folderId=${folderId}`)
|
||||
: router.push('/typebots/create')
|
||||
router.push(
|
||||
`/typebots/create?${stringify({
|
||||
isFirstBot: !isFirstBot ? undefined : isFirstBot,
|
||||
folderId,
|
||||
})}`
|
||||
)
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
176
apps/builder/components/dashboard/OnboardingModal.tsx
Normal file
176
apps/builder/components/dashboard/OnboardingModal.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
import {
|
||||
chakra,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { Answer, Typebot } from 'models'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
||||
import { sendRequest } from 'utils'
|
||||
import confetti from 'canvas-confetti'
|
||||
|
||||
type Props = { totalTypebots: number }
|
||||
|
||||
export const OnboardingModal = ({ totalTypebots }: Props) => {
|
||||
const { user, saveUser } = useUser()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [typebot, setTypebot] = useState<Typebot>()
|
||||
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
||||
const confettiCanon = useRef<confetti.CreateTypes>()
|
||||
const [chosenCategories, setChosenCategories] = useState<string[]>([])
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplate()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const isNewUser =
|
||||
user &&
|
||||
new Date(user?.createdAt as unknown as string).toDateString() ===
|
||||
new Date().toDateString() &&
|
||||
totalTypebots === 0
|
||||
if (isNewUser) onOpen()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user])
|
||||
|
||||
useEffect(() => {
|
||||
initConfettis()
|
||||
return () => {
|
||||
window.removeEventListener('message', handleIncomingMessage)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [confettiCanvaContainer.current])
|
||||
|
||||
const initConfettis = () => {
|
||||
if (!confettiCanvaContainer.current || confettiCanon.current) return
|
||||
confettiCanon.current = confetti.create(confettiCanvaContainer.current, {
|
||||
resize: true,
|
||||
useWorker: true,
|
||||
})
|
||||
window.addEventListener('message', handleIncomingMessage)
|
||||
}
|
||||
|
||||
const handleIncomingMessage = (message: MessageEvent) => {
|
||||
if (message.data.from === 'typebot') {
|
||||
if (message.data.action === 'shootConfettis' && confettiCanon.current)
|
||||
shootConfettis(confettiCanon.current)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchTemplate = async () => {
|
||||
const { data, error } = await sendRequest(`/bots/onboarding.json`)
|
||||
if (error) return toast({ title: error.name, description: error.message })
|
||||
setTypebot(data as Typebot)
|
||||
}
|
||||
|
||||
const handleNewAnswer = (answer: Answer) => {
|
||||
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
|
||||
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
||||
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
||||
const isOtherCategories = answer.variableId === 'cl126q38p001q2e6d0hj23f6b'
|
||||
if (isName) saveUser({ name: answer.content })
|
||||
if (isCompany) saveUser({ company: answer.content })
|
||||
if (isCategories) {
|
||||
const onboardingCategories = answer.content.split(', ')
|
||||
saveUser({ onboardingCategories })
|
||||
setChosenCategories(onboardingCategories)
|
||||
}
|
||||
if (isOtherCategories)
|
||||
saveUser({
|
||||
onboardingCategories: [...chosenCategories, answer.content],
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="3xl"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
blockScrollOnMount={false}
|
||||
>
|
||||
<chakra.canvas
|
||||
ref={confettiCanvaContainer}
|
||||
pos="fixed"
|
||||
top="0"
|
||||
left="0"
|
||||
w="full"
|
||||
h="full"
|
||||
zIndex={9999}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<ModalOverlay />
|
||||
<ModalContent h="85vh">
|
||||
<ModalBody>
|
||||
{typebot && (
|
||||
<TypebotViewer
|
||||
typebot={parseTypebotToPublicTypebot(typebot)}
|
||||
predefinedVariables={{
|
||||
Name: user?.name?.split(' ')[0] ?? undefined,
|
||||
}}
|
||||
onNewAnswer={handleNewAnswer}
|
||||
/>
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
@ -59,10 +59,8 @@ export const PreviewDrawer = () => {
|
||||
setRightPanel(undefined)
|
||||
}
|
||||
|
||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => {
|
||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
||||
toast(log as UseToastOptions)
|
||||
console.log(log.details)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
@ -139,4 +139,4 @@ export const parseInitBubbleCode = ({
|
||||
)
|
||||
}
|
||||
|
||||
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.2.0"></script>`
|
||||
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.2.1"></script>`
|
||||
|
@ -80,7 +80,6 @@ export const WebhookSettings = ({
|
||||
|
||||
return () => {
|
||||
setLocalWebhook((localWebhook) => {
|
||||
console.log(localWebhook)
|
||||
if (!localWebhook) return
|
||||
updateWebhook(webhookId, localWebhook).then()
|
||||
return localWebhook
|
||||
|
@ -17,7 +17,12 @@ export const SupportBubble = () => {
|
||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
||||
}/typebot-support`,
|
||||
backgroundColor: '#ffffff',
|
||||
button: { color: '#0042DA' },
|
||||
button: {
|
||||
color: '#0042DA',
|
||||
iconUrl:
|
||||
'https://user-images.githubusercontent.com/16015833/159536717-35bb78f8-f659-49f2-ad7f-00172be69cfb.svg',
|
||||
iconStyle: 'border-radius: 0; width: 50%',
|
||||
},
|
||||
hiddenVariables: {
|
||||
'User ID': user?.id,
|
||||
Name: user?.name ?? undefined,
|
||||
|
@ -13,6 +13,7 @@ import { RightPanel, useEditor } from 'contexts/EditorContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { isNotDefined } from 'utils'
|
||||
import { PublishButton } from '../buttons/PublishButton'
|
||||
import { CollaborationMenuButton } from './CollaborationMenuButton'
|
||||
import { EditableTypebotName } from './EditableTypebotName'
|
||||
@ -21,6 +22,7 @@ export const headerHeight = 56
|
||||
|
||||
export const TypebotHeader = () => {
|
||||
const router = useRouter()
|
||||
const { rightPanel } = useEditor()
|
||||
const {
|
||||
typebot,
|
||||
updateOnBothTypebots,
|
||||
@ -154,7 +156,7 @@ export const TypebotHeader = () => {
|
||||
|
||||
<HStack right="40px" pos="absolute">
|
||||
<CollaborationMenuButton />
|
||||
{router.pathname.includes('/edit') && (
|
||||
{router.pathname.includes('/edit') && isNotDefined(rightPanel) && (
|
||||
<Button onClick={handlePreviewClick}>Preview</Button>
|
||||
)}
|
||||
<PublishButton />
|
||||
|
@ -12,10 +12,12 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { ChevronLeftIcon } from 'assets/icons'
|
||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import { timeSince } from 'services/utils'
|
||||
import { isNotDefined } from 'utils'
|
||||
|
||||
export const PublishButton = () => {
|
||||
const { push, query } = useRouter()
|
||||
const {
|
||||
isPublishing,
|
||||
isPublished,
|
||||
@ -24,6 +26,11 @@ export const PublishButton = () => {
|
||||
restorePublishedTypebot,
|
||||
} = useTypebot()
|
||||
|
||||
const handlePublishClick = () => {
|
||||
publishTypebot()
|
||||
if (!publishedTypebot) push(`/typebots/${query.typebotId}/share`)
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack spacing="1px">
|
||||
<Tooltip
|
||||
@ -47,7 +54,7 @@ export const PublishButton = () => {
|
||||
colorScheme="blue"
|
||||
isLoading={isPublishing}
|
||||
isDisabled={isPublished}
|
||||
onClick={publishTypebot}
|
||||
onClick={handlePublishClick}
|
||||
borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined}
|
||||
>
|
||||
{isPublished ? 'Published' : 'Publish'}
|
||||
|
@ -21,7 +21,7 @@ const userContext = createContext<{
|
||||
hasUnsavedChanges: boolean
|
||||
isOAuthProvider: boolean
|
||||
updateUser: (newUser: Partial<User>) => void
|
||||
saveUser: () => void
|
||||
saveUser: (newUser?: Partial<User>) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
@ -75,10 +75,11 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
|
||||
setUser({ ...user, ...newUser })
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
const saveUser = async (newUser?: Partial<User>) => {
|
||||
if (isNotDefined(user)) return
|
||||
setIsSaving(true)
|
||||
const { error } = await updateUserInDb(user.id, user)
|
||||
if (newUser) updateUser(newUser)
|
||||
const { error } = await updateUserInDb(user.id, { ...user, ...newUser })
|
||||
if (error) toast({ title: error.name, description: error.message })
|
||||
await refreshUser()
|
||||
setIsSaving(false)
|
||||
|
@ -6,13 +6,14 @@ import {
|
||||
Text,
|
||||
Stack,
|
||||
useToast,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react'
|
||||
import { CreateTypebotMoreButton } from 'components/templates/ImportFileMenuItem'
|
||||
import { TemplateButton } from 'components/templates/TemplateButton'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { defaultTheme, Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { createTypebot, importTypebot } from 'services/typebots/typebots'
|
||||
|
||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||
@ -22,6 +23,8 @@ const templates: TemplateProps[] = [
|
||||
export const TemplatesContent = () => {
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||
useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
@ -31,6 +34,13 @@ export const TemplatesContent = () => {
|
||||
title: 'An error occured',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const isFirstBot = router.query.isFirstBot as string | undefined
|
||||
if (isFirstBot) setIsFromScratchTooltipOpened(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||
if (!user) return
|
||||
setIsLoading(true)
|
||||
@ -60,13 +70,23 @@ export const TemplatesContent = () => {
|
||||
<Flex w="full" justifyContent="center">
|
||||
<Stack maxW="1000px" flex="1" pt="6" spacing={4}>
|
||||
<Flex justifyContent="space-between">
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
colorScheme="blue"
|
||||
<Tooltip
|
||||
isOpen={isFromScratchTooltipOpened}
|
||||
label="Strongly suggested if you're new to Typebot."
|
||||
maxW="200px"
|
||||
hasArrow
|
||||
placement="right"
|
||||
rounded="md"
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
colorScheme="blue"
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<CreateTypebotMoreButton onNewTypebot={handleCreateSubmit} />
|
||||
</Flex>
|
||||
<Divider />
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@udecode/plate-ui-toolbar": "^10.2.2",
|
||||
"bot-engine": "*",
|
||||
"browser-image-compression": "^1.0.17",
|
||||
"canvas-confetti": "^1.5.1",
|
||||
"cuid": "^2.1.8",
|
||||
"db": "*",
|
||||
"deep-object-diff": "^1.1.7",
|
||||
@ -85,6 +86,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.19.2",
|
||||
"@types/canvas-confetti": "^1.4.2",
|
||||
"@types/google-spreadsheet": "^3.1.5",
|
||||
"@types/jsonwebtoken": "8.5.8",
|
||||
"@types/micro-cors": "^0.1.2",
|
||||
|
@ -80,7 +80,10 @@ test.describe.parallel('Image bubble step', () => {
|
||||
|
||||
await page.click('text=Click to edit...')
|
||||
await page.click('text=Giphy')
|
||||
await page.click('img >> nth=3', { force: true })
|
||||
await page.click('img >> nth=3', {
|
||||
force: true,
|
||||
position: { x: 0, y: 0 },
|
||||
})
|
||||
await expect(page.locator('img[alt="Step image"]')).toHaveAttribute(
|
||||
'src',
|
||||
new RegExp('giphy.com/media', 'gm')
|
||||
|
@ -54,7 +54,7 @@ test.describe('Dashboard page', () => {
|
||||
test.use({
|
||||
storageState: path.join(__dirname, '../freeUser.json'),
|
||||
})
|
||||
test("create folder shouldn't be available", async ({ page }) => {
|
||||
test("Add my domain shouldn't be available", async ({ page }) => {
|
||||
await page.goto(`/typebots/${typebotId}/share`)
|
||||
await page.click('text=Add my domain')
|
||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||
|
@ -41,26 +41,4 @@ test.describe.parallel('Text input step', () => {
|
||||
).toBeVisible()
|
||||
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
|
||||
})
|
||||
|
||||
test('variable in URL should prefill the input', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
...parseDefaultBlockWithStep({
|
||||
type: InputStepType.TEXT,
|
||||
options: { ...defaultTextInputOptions, variableId: 'var1' },
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit?var1=My prefilled answer`)
|
||||
await page.click('text=Preview')
|
||||
await expect(
|
||||
typebotViewer(page).locator(
|
||||
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
||||
)
|
||||
).toHaveAttribute('value', 'My prefilled answer')
|
||||
await expect(typebotViewer(page).locator(`button`)).toBeEnabled()
|
||||
})
|
||||
})
|
||||
|
635
apps/builder/public/bots/onboarding.json
Normal file
635
apps/builder/public/bots/onboarding.json
Normal file
@ -0,0 +1,635 @@
|
||||
{
|
||||
"id": "cl128l5vx007509il86n74oer",
|
||||
"createdAt": "2022-03-22T14:33:05.037Z",
|
||||
"updatedAt": "2022-03-22T16:33:37.928Z",
|
||||
"name": "Onboarding",
|
||||
"ownerId": "ckzmhmiey001009mnzt5nkxu8",
|
||||
"publishedTypebotId": "cl128n64i00092e69wenv1dlx",
|
||||
"folderId": null,
|
||||
"blocks": [
|
||||
{
|
||||
"id": "cl1265zct0000mb1a6bir36w7",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl1265zct0001mb1afel460do",
|
||||
"type": "start",
|
||||
"label": "Start",
|
||||
"blockId": "cl1265zct0000mb1a6bir36w7",
|
||||
"outgoingEdgeId": "cl1266kt100082e6d1wks5dtp"
|
||||
}
|
||||
],
|
||||
"title": "Start",
|
||||
"graphCoordinates": { "x": 0, "y": 0 }
|
||||
},
|
||||
{
|
||||
"id": "cl1266bah00032e6dgdnj4vgz",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl1266bam00042e6dm0gn22vy",
|
||||
"type": "Condition",
|
||||
"items": [
|
||||
{
|
||||
"id": "cl1266bam00052e6dn1sdjnax",
|
||||
"type": 1,
|
||||
"stepId": "cl1266bam00042e6dm0gn22vy",
|
||||
"content": {
|
||||
"comparisons": [
|
||||
{
|
||||
"id": "cl1266cg600062e6d76qwk74v",
|
||||
"variableId": "cl126f4hf000i2e6d8zvzc3t1",
|
||||
"comparisonOperator": "Is set"
|
||||
}
|
||||
],
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
"outgoingEdgeId": "cl12bk3j6000c2e69bak89ja9"
|
||||
}
|
||||
],
|
||||
"blockId": "cl1266bah00032e6dgdnj4vgz",
|
||||
"outgoingEdgeId": "cl12bnfyd000g2e69g7lr3czq"
|
||||
}
|
||||
],
|
||||
"title": "Block #1",
|
||||
"graphCoordinates": { "x": 266, "y": 162 }
|
||||
},
|
||||
{
|
||||
"id": "cl1267q1z000d2e6d949f2ge4",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl1267q2c000e2e6dynjeg83n",
|
||||
"type": "text",
|
||||
"blockId": "cl1267q1z000d2e6d949f2ge4",
|
||||
"content": {
|
||||
"html": "<div>Welcome 👋</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "Welcome 👋" }] }
|
||||
],
|
||||
"plainText": "Welcome 👋"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl1267y1u000f2e6d4rlglv6g",
|
||||
"type": "text",
|
||||
"blockId": "cl1267q1z000d2e6d949f2ge4",
|
||||
"content": {
|
||||
"html": "<div>What's your name?</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
||||
],
|
||||
"plainText": "What's your name?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126820m000g2e6dfleq78bt",
|
||||
"type": "text input",
|
||||
"blockId": "cl1267q1z000d2e6d949f2ge4",
|
||||
"options": {
|
||||
"isLong": false,
|
||||
"labels": {
|
||||
"button": "Send",
|
||||
"placeholder": "Type your answer..."
|
||||
},
|
||||
"variableId": "cl126f4hf000i2e6d8zvzc3t1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl1289y1s00142e6dvbkpvbje",
|
||||
"type": "Code",
|
||||
"blockId": "cl1267q1z000d2e6d949f2ge4",
|
||||
"options": {
|
||||
"name": "Store Name in DB",
|
||||
"content": "postMessage({from: \"typebot\", action: \"storeName\", content: \"{{Name}}\"}, \"*\")"
|
||||
},
|
||||
"outgoingEdgeId": "cl12bk56s000d2e69oll3nqxm"
|
||||
}
|
||||
],
|
||||
"title": "Block #3",
|
||||
"graphCoordinates": { "x": 269, "y": 381 }
|
||||
},
|
||||
{
|
||||
"id": "cl126ixoq000p2e6dfbz9sype",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl1266v6f000a2e6db7wj3ux7",
|
||||
"type": "text",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"content": {
|
||||
"html": "<div>Welcome {{Name}} 👋</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] }
|
||||
],
|
||||
"plainText": "Welcome {{Name}} 👋"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126hb9m000l2e6d5qk3mohn",
|
||||
"type": "text",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"content": {
|
||||
"html": "<div>I'm super pumped that you've decided to try out Typebot 😍</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{
|
||||
"text": "I'm super pumped that you've decided to try out Typebot 😍"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "I'm super pumped that you've decided to try out Typebot 😍"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126hpw1000m2e6dneousygl",
|
||||
"type": "text",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"content": {
|
||||
"html": "<div>You are small steps away from meaningful, hyper-personalized experience for your users</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{
|
||||
"text": "You are small steps away from meaningful, hyper-personalized experience for your users"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "You are small steps away from meaningful, hyper-personalized experience for your users"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126guhd000k2e6d6ypkex9z",
|
||||
"type": "text",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"content": {
|
||||
"html": "<div>Let's get you set up for your Typebot journey.</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{ "text": "Let's get you set up for your Typebot journey." }
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "Let's get you set up for your Typebot journey."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126ixp9000q2e6dslh0zypi",
|
||||
"type": "text",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"content": {
|
||||
"html": "<div>Do you work for a specific company?</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [{ "text": "Do you work for a specific company?" }]
|
||||
}
|
||||
],
|
||||
"plainText": "Do you work for a specific company?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126jb2q000r2e6dgqlnxnt8",
|
||||
"type": "choice input",
|
||||
"items": [
|
||||
{
|
||||
"id": "cl126jb2q000s2e6dm60yq5p2",
|
||||
"type": 0,
|
||||
"stepId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||
"content": "Yes",
|
||||
"outgoingEdgeId": "cl126jsoo000x2e6ditu7dgf8"
|
||||
},
|
||||
{
|
||||
"id": "cl126jc5a000t2e6dqv91w7j6",
|
||||
"type": 0,
|
||||
"stepId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||
"content": "No",
|
||||
"outgoingEdgeId": "cl126l5tx00122e6dmisci6h5"
|
||||
}
|
||||
],
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
||||
}
|
||||
],
|
||||
"title": "Block #5",
|
||||
"graphCoordinates": { "x": 614, "y": 244 }
|
||||
},
|
||||
{
|
||||
"id": "cl126jioj000u2e6dqssno3hv",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl126jioz000v2e6dwrk1f2cb",
|
||||
"type": "text input",
|
||||
"blockId": "cl126jioj000u2e6dqssno3hv",
|
||||
"options": {
|
||||
"isLong": false,
|
||||
"labels": {
|
||||
"button": "Send",
|
||||
"placeholder": "Type the company name..."
|
||||
},
|
||||
"variableId": "cl126jqww000w2e6dq9yv4ifq"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl12890kw00132e6dp9v5dexm",
|
||||
"type": "Code",
|
||||
"blockId": "cl126jioj000u2e6dqssno3hv",
|
||||
"options": {
|
||||
"name": "Store company in DB",
|
||||
"content": "postMessage({from: \"typebot\", action: \"storeCompany\", content: \"{{Company}}\"}, \"*\")"
|
||||
},
|
||||
"outgoingEdgeId": "cl128ag8i00162e6dufv3tgo0"
|
||||
}
|
||||
],
|
||||
"title": "Block #6",
|
||||
"graphCoordinates": { "x": 969, "y": 308 }
|
||||
},
|
||||
{
|
||||
"id": "cl126krbp00102e6dnjelmfa1",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl126krck00112e6d1m6ctxpn",
|
||||
"type": "text",
|
||||
"blockId": "cl126krbp00102e6dnjelmfa1",
|
||||
"content": {
|
||||
"html": "<div>What type of forms are you planning to build with Typebot?</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{
|
||||
"text": "What type of forms are you planning to build with Typebot?"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "What type of forms are you planning to build with Typebot?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126lb8v00142e6duv5qe08l",
|
||||
"type": "choice input",
|
||||
"items": [
|
||||
{
|
||||
"id": "cl126onz9001g2e6dk0nbjeu6",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Lead qualification"
|
||||
},
|
||||
{
|
||||
"id": "cl126lm6c00172e6d1pfvdiju",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Customer support"
|
||||
},
|
||||
{
|
||||
"id": "cl126orr2001h2e6d0fqs7737",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Customer research"
|
||||
},
|
||||
{
|
||||
"id": "cl126oudu001i2e6dktwi7qwv",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "User onboarding"
|
||||
},
|
||||
{
|
||||
"id": "cl126luv500192e6dl317ssyr",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Quizzes"
|
||||
},
|
||||
{
|
||||
"id": "cl126lz8q001a2e6d8b9lb3b5",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Content distribution"
|
||||
},
|
||||
{
|
||||
"id": "cl126nf7k001d2e6dg2zczjgz",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "FAQ"
|
||||
},
|
||||
{
|
||||
"id": "cl126ngy8001e2e6ddfo5s9fm",
|
||||
"type": 0,
|
||||
"stepId": "cl126lb8v00142e6duv5qe08l",
|
||||
"content": "Other"
|
||||
}
|
||||
],
|
||||
"blockId": "cl126krbp00102e6dnjelmfa1",
|
||||
"options": {
|
||||
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
||||
"buttonLabel": "Send",
|
||||
"isMultipleChoice": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128ain900172e6d1osj4u90",
|
||||
"type": "Code",
|
||||
"blockId": "cl126krbp00102e6dnjelmfa1",
|
||||
"options": {
|
||||
"name": "Store categories in DB",
|
||||
"content": "postMessage({from: \"typebot\", action: \"storeCategories\", content: \"{{Categories}}\"}, \"*\")"
|
||||
},
|
||||
"outgoingEdgeId": "cl128azam00182e6dct61k7v5"
|
||||
}
|
||||
],
|
||||
"title": "Block #6",
|
||||
"graphCoordinates": { "x": 1218, "y": 510 }
|
||||
},
|
||||
{
|
||||
"id": "cl126p75m001j2e6d73qmes0m",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl126p76d001k2e6dbhnf2ysq",
|
||||
"type": "text",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"content": {
|
||||
"html": "<div>Thank you for answering those questions!</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{ "text": "Thank you for answering those questions!" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "Thank you for answering those questions!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128375600112e6d4l0jtuyf",
|
||||
"type": "Code",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"options": {
|
||||
"name": "Shoot confettis",
|
||||
"content": "postMessage({from: \"typebot\", action: \"shootConfettis\"}, \"*\")"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126rfy6001t2e6d21gcb6b0",
|
||||
"type": "image",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"content": {
|
||||
"url": "https://media4.giphy.com/media/l0amJzVHIAfl7jMDos/giphy.gif?cid=fe3852a3i4c33635xdtj3nesr9uq4zteujaab6b0jr42gpxx&rid=giphy.gif&ct=g"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126txta001y2e6dtxrbsnek",
|
||||
"type": "text",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"content": {
|
||||
"html": "<div>You can reach out to me using the contact bubble on the bottom right corner 🤓</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [
|
||||
{
|
||||
"text": "You can reach out to me using the contact bubble on the bottom right corner 🤓"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"plainText": "You can reach out to me using the contact bubble on the bottom right corner 🤓"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl12buyly00172e6991bz38ch",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"type": "text",
|
||||
"content": {
|
||||
"html": "<div>Let's create your first typebot...</div>",
|
||||
"richText": [
|
||||
{
|
||||
"type": "p",
|
||||
"children": [{ "text": "Let's create your first typebot..." }]
|
||||
}
|
||||
],
|
||||
"plainText": "Let's create your first typebot..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl12bwpi800182e69kcivnp1s",
|
||||
"blockId": "cl126p75m001j2e6d73qmes0m",
|
||||
"type": "Code",
|
||||
"options": {
|
||||
"name": "Go to typebot creation",
|
||||
"content": "setTimeout(() => {window.location.href = \"https://app.typebot.io/typebots/create?isFirstBot=true\"}, 4000)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "Block #7",
|
||||
"graphCoordinates": { "x": 1612, "y": 1103 }
|
||||
},
|
||||
{
|
||||
"id": "cl126pv6w001n2e6dp0qkvthu",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl127yxym000b2e6d9hksxo6h",
|
||||
"type": "text",
|
||||
"blockId": "cl126pv6w001n2e6dp0qkvthu",
|
||||
"content": {
|
||||
"html": "<div>What else?</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "What else?" }] }
|
||||
],
|
||||
"plainText": "What else?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126pv7n001o2e6dajltc4qz",
|
||||
"type": "text input",
|
||||
"blockId": "cl126pv6w001n2e6dp0qkvthu",
|
||||
"options": {
|
||||
"isLong": false,
|
||||
"labels": { "button": "Send", "placeholder": "Type your answer" },
|
||||
"variableId": "cl126q38p001q2e6d0hj23f6b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128b34o00192e6dqjxs3cxf",
|
||||
"type": "Code",
|
||||
"blockId": "cl126pv6w001n2e6dp0qkvthu",
|
||||
"options": {
|
||||
"name": "Store Other categories in DB",
|
||||
"content": "postMessage({from: \"typebot\", action: \"storeOtherCategories\", content: \"{{Other categories}}\"}, \"*\")"
|
||||
},
|
||||
"outgoingEdgeId": "cl128c0fu001a2e6droq69g6z"
|
||||
}
|
||||
],
|
||||
"title": "Block #8",
|
||||
"graphCoordinates": { "x": 1943, "y": 895 }
|
||||
},
|
||||
{
|
||||
"id": "cl1278gx9002v2e6d4kf3v89s",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl1278gyk002w2e6d744eb87n",
|
||||
"type": "Condition",
|
||||
"items": [
|
||||
{
|
||||
"id": "cl1278gyk002x2e6dwmpzs3nf",
|
||||
"type": 1,
|
||||
"stepId": "cl1278gyk002w2e6d744eb87n",
|
||||
"content": {
|
||||
"comparisons": [
|
||||
{
|
||||
"id": "cl1278irq002y2e6dv4965diw",
|
||||
"value": "Other",
|
||||
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
||||
"comparisonOperator": "Contains"
|
||||
}
|
||||
],
|
||||
"logicalOperator": "AND"
|
||||
},
|
||||
"outgoingEdgeId": "cl1278r3b002z2e6d6d6rk9dh"
|
||||
}
|
||||
],
|
||||
"blockId": "cl1278gx9002v2e6d4kf3v89s",
|
||||
"outgoingEdgeId": "cl1278trd00312e6dxmzhcmmn"
|
||||
}
|
||||
],
|
||||
"title": "Block #13",
|
||||
"graphCoordinates": { "x": 1585, "y": 792 }
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{ "id": "cl126f4hf000i2e6d8zvzc3t1", "name": "Name" },
|
||||
{ "id": "cl126jqww000w2e6dq9yv4ifq", "name": "Company" },
|
||||
{ "id": "cl126mo3t001b2e6dvyi16bkd", "name": "Categories" },
|
||||
{ "id": "cl126q38p001q2e6d0hj23f6b", "name": "Other categories" }
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "cl1266kt100082e6d1wks5dtp",
|
||||
"to": { "blockId": "cl1266bah00032e6dgdnj4vgz" },
|
||||
"from": {
|
||||
"stepId": "cl1265zct0001mb1afel460do",
|
||||
"blockId": "cl1265zct0000mb1a6bir36w7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126jsoo000x2e6ditu7dgf8",
|
||||
"to": { "blockId": "cl126jioj000u2e6dqssno3hv" },
|
||||
"from": {
|
||||
"itemId": "cl126jb2q000s2e6dm60yq5p2",
|
||||
"stepId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl126l5tx00122e6dmisci6h5",
|
||||
"to": { "blockId": "cl126krbp00102e6dnjelmfa1" },
|
||||
"from": {
|
||||
"itemId": "cl126jc5a000t2e6dqv91w7j6",
|
||||
"stepId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl1278r3b002z2e6d6d6rk9dh",
|
||||
"to": { "blockId": "cl126pv6w001n2e6dp0qkvthu" },
|
||||
"from": {
|
||||
"itemId": "cl1278gyk002x2e6dwmpzs3nf",
|
||||
"stepId": "cl1278gyk002w2e6d744eb87n",
|
||||
"blockId": "cl1278gx9002v2e6d4kf3v89s"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl1278trd00312e6dxmzhcmmn",
|
||||
"to": { "blockId": "cl126p75m001j2e6d73qmes0m" },
|
||||
"from": {
|
||||
"stepId": "cl1278gyk002w2e6d744eb87n",
|
||||
"blockId": "cl1278gx9002v2e6d4kf3v89s"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128ag8i00162e6dufv3tgo0",
|
||||
"to": { "blockId": "cl126krbp00102e6dnjelmfa1" },
|
||||
"from": {
|
||||
"stepId": "cl12890kw00132e6dp9v5dexm",
|
||||
"blockId": "cl126jioj000u2e6dqssno3hv"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128azam00182e6dct61k7v5",
|
||||
"to": { "blockId": "cl1278gx9002v2e6d4kf3v89s" },
|
||||
"from": {
|
||||
"stepId": "cl128ain900172e6d1osj4u90",
|
||||
"blockId": "cl126krbp00102e6dnjelmfa1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl128c0fu001a2e6droq69g6z",
|
||||
"to": { "blockId": "cl126p75m001j2e6d73qmes0m" },
|
||||
"from": {
|
||||
"stepId": "cl128b34o00192e6dqjxs3cxf",
|
||||
"blockId": "cl126pv6w001n2e6dp0qkvthu"
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": {
|
||||
"blockId": "cl1266bah00032e6dgdnj4vgz",
|
||||
"stepId": "cl1266bam00042e6dm0gn22vy",
|
||||
"itemId": "cl1266bam00052e6dn1sdjnax"
|
||||
},
|
||||
"to": { "blockId": "cl126ixoq000p2e6dfbz9sype" },
|
||||
"id": "cl12bk3j6000c2e69bak89ja9"
|
||||
},
|
||||
{
|
||||
"from": {
|
||||
"blockId": "cl1267q1z000d2e6d949f2ge4",
|
||||
"stepId": "cl1289y1s00142e6dvbkpvbje"
|
||||
},
|
||||
"to": {
|
||||
"blockId": "cl126ixoq000p2e6dfbz9sype",
|
||||
"stepId": "cl126hb9m000l2e6d5qk3mohn"
|
||||
},
|
||||
"id": "cl12bk56s000d2e69oll3nqxm"
|
||||
},
|
||||
{
|
||||
"from": {
|
||||
"blockId": "cl1266bah00032e6dgdnj4vgz",
|
||||
"stepId": "cl1266bam00042e6dm0gn22vy"
|
||||
},
|
||||
"to": { "blockId": "cl1267q1z000d2e6d949f2ge4" },
|
||||
"id": "cl12bnfyd000g2e69g7lr3czq"
|
||||
}
|
||||
],
|
||||
"theme": {
|
||||
"chat": {
|
||||
"inputs": {
|
||||
"color": "#303235",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"placeholderColor": "#9095A0"
|
||||
},
|
||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" },
|
||||
"hostAvatar": {
|
||||
"isEnabled": true,
|
||||
"url": "https://s3.eu-west-3.amazonaws.com/typebot/typebots/ckzp7a2za005809lczf2knzix/273013187_1315820332248257_6244778509534754615_n.jpg"
|
||||
}
|
||||
},
|
||||
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
||||
},
|
||||
"settings": {
|
||||
"general": {
|
||||
"isBrandingEnabled": true,
|
||||
"isInputPrefillEnabled": true,
|
||||
"isNewResultOnRefreshEnabled": false
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
|
||||
},
|
||||
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||
},
|
||||
"publicId": "typebot-onboarding",
|
||||
"customDomain": null
|
||||
}
|
@ -32,7 +32,6 @@ export const RealTimeResults = () => {
|
||||
}, [])
|
||||
|
||||
const processMessage = (event: MessageEvent) => {
|
||||
console.log(event.data)
|
||||
if (event.data.from === 'typebot') refreshIframeContent()
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,22 @@ export const TypebotPage = ({
|
||||
url,
|
||||
}: TypebotPageProps & { typebot: PublicTypebot }) => {
|
||||
const [showTypebot, setShowTypebot] = useState(false)
|
||||
const [predefinedVariables, setPredefinedVariables] =
|
||||
useState<{ [key: string]: string }>()
|
||||
const [error, setError] = useState<Error | undefined>(
|
||||
isIE ? new Error('Internet explorer is not supported') : undefined
|
||||
)
|
||||
const [resultId, setResultId] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
const predefinedVariables: { [key: string]: string } = {}
|
||||
urlParams.forEach((value, key) => {
|
||||
predefinedVariables[key] = value
|
||||
})
|
||||
setPredefinedVariables(predefinedVariables)
|
||||
}, [])
|
||||
|
||||
// Workaround for react-frame-component bug (https://github.com/ryanseddon/react-frame-component/pull/207)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
@ -83,6 +94,7 @@ export const TypebotPage = ({
|
||||
{showTypebot && (
|
||||
<TypebotViewer
|
||||
typebot={typebot}
|
||||
predefinedVariables={predefinedVariables}
|
||||
onNewAnswer={handleNewAnswer}
|
||||
onCompleted={handleCompleted}
|
||||
onVariablesPrefilled={initializeResult}
|
||||
|
@ -46,7 +46,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
.status(404)
|
||||
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
|
||||
const preparedWebhook = prepareWebhookAttributes(webhook, step.options)
|
||||
console.log(preparedWebhook)
|
||||
const result = await executeWebhook(typebot)(
|
||||
preparedWebhook,
|
||||
variables,
|
||||
|
@ -0,0 +1,122 @@
|
||||
{
|
||||
"id": "cl13bgvlm0050t71aq6a3w777",
|
||||
"createdAt": "2022-03-23T08:41:30.106Z",
|
||||
"updatedAt": "2022-03-23T08:41:30.106Z",
|
||||
"name": "My typebot",
|
||||
"ownerId": "cl139ni700000481a01gxhw4z",
|
||||
"publishedTypebotId": null,
|
||||
"folderId": null,
|
||||
"blocks": [
|
||||
{
|
||||
"id": "cl13bgvlk0000t71a4wabccvw",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl13bgvlk0001t71a3pilbj53",
|
||||
"type": "start",
|
||||
"label": "Start",
|
||||
"blockId": "cl13bgvlk0000t71a4wabccvw",
|
||||
"outgoingEdgeId": "cl13bgz4800062e6dv7ejcchb"
|
||||
}
|
||||
],
|
||||
"title": "Start",
|
||||
"graphCoordinates": { "x": 0, "y": 0 }
|
||||
},
|
||||
{
|
||||
"id": "cl13bgy1s00042e6dao1wyobm",
|
||||
"graphCoordinates": { "x": 329, "y": 65 },
|
||||
"title": "Block #1",
|
||||
"steps": [
|
||||
{
|
||||
"id": "cl13bgy1w00052e6d5x57wt7o",
|
||||
"blockId": "cl13bgy1s00042e6dao1wyobm",
|
||||
"type": "text",
|
||||
"content": {
|
||||
"html": "<div>Hey I know you!</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "Hey I know you!" }] }
|
||||
],
|
||||
"plainText": "Hey I know you!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl13bh6jd00072e6dftdirwy4",
|
||||
"blockId": "cl13bgy1s00042e6dao1wyobm",
|
||||
"type": "text",
|
||||
"content": {
|
||||
"html": "<div>Your name is {{Name}}</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "Your name is {{Name}}" }] }
|
||||
],
|
||||
"plainText": "Your name is {{Name}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl13bhfxd00092e6dydvcqlhm",
|
||||
"blockId": "cl13bgy1s00042e6dao1wyobm",
|
||||
"type": "text",
|
||||
"content": {
|
||||
"html": "<div>What's your email?</div>",
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "What's your email?" }] }
|
||||
],
|
||||
"plainText": "What's your email?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cl13bhnay000a2e6dxa630dh3",
|
||||
"blockId": "cl13bgy1s00042e6dao1wyobm",
|
||||
"type": "email input",
|
||||
"options": {
|
||||
"labels": { "button": "Send", "placeholder": "Type your email..." },
|
||||
"retryMessageContent": "This email doesn't seem to be valid. Can you type it again?",
|
||||
"variableId": "cl13bhr3w000b2e6d3c9kid0p"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variables": [
|
||||
{ "id": "cl13bha3l00082e6duaz0xm6f", "name": "Name" },
|
||||
{ "id": "cl13bhr3w000b2e6d3c9kid0p", "name": "Email" }
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"from": {
|
||||
"blockId": "cl13bgvlk0000t71a4wabccvw",
|
||||
"stepId": "cl13bgvlk0001t71a3pilbj53"
|
||||
},
|
||||
"to": { "blockId": "cl13bgy1s00042e6dao1wyobm" },
|
||||
"id": "cl13bgz4800062e6dv7ejcchb"
|
||||
}
|
||||
],
|
||||
"theme": {
|
||||
"chat": {
|
||||
"inputs": {
|
||||
"color": "#303235",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"placeholderColor": "#9095A0"
|
||||
},
|
||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||
"hostAvatar": {
|
||||
"url": "https://avatars.githubusercontent.com/u/16015833?v=4",
|
||||
"isEnabled": true
|
||||
},
|
||||
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
||||
},
|
||||
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
||||
},
|
||||
"settings": {
|
||||
"general": {
|
||||
"isBrandingEnabled": true,
|
||||
"isInputPrefillEnabled": true,
|
||||
"isNewResultOnRefreshEnabled": false
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
|
||||
},
|
||||
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||
},
|
||||
"publicId": null,
|
||||
"customDomain": null
|
||||
}
|
22
apps/viewer/playwright/tests/predefinedVariables.spec.ts
Normal file
22
apps/viewer/playwright/tests/predefinedVariables.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { importTypebotInDatabase } from '../services/database'
|
||||
import cuid from 'cuid'
|
||||
import path from 'path'
|
||||
import { typebotViewer } from '../services/selectorUtils'
|
||||
|
||||
test('should correctly be injected', async ({ page }) => {
|
||||
const typebotId = cuid()
|
||||
await importTypebotInDatabase(
|
||||
path.join(__dirname, '../fixtures/typebots/predefinedVariables.json'),
|
||||
{ id: typebotId, publicId: `${typebotId}-public` }
|
||||
)
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
await expect(typebotViewer(page).locator('text="Your name is"')).toBeVisible()
|
||||
await page.goto(`/${typebotId}-public?Name=Baptiste&Email=email@test.com`)
|
||||
await expect(
|
||||
typebotViewer(page).locator('text="Your name is Baptiste"')
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
typebotViewer(page).locator('input[value="email@test.com"]')
|
||||
).toBeVisible()
|
||||
})
|
@ -158,7 +158,7 @@ export const ChatBlock = ({
|
||||
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
|
||||
return onBlockEnd(currentStep.outgoingEdgeId)
|
||||
}
|
||||
const nextStep = steps[processedSteps.length]
|
||||
const nextStep = steps[processedSteps.length + startStepIndex]
|
||||
if (nextStep) insertStepInStack(nextStep)
|
||||
}
|
||||
|
||||
|
@ -11,12 +11,14 @@ import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
|
||||
|
||||
type Props = {
|
||||
theme: Theme
|
||||
predefinedVariables?: { [key: string]: string | undefined }
|
||||
onNewBlockVisible: (edge: Edge) => void
|
||||
onCompleted: () => void
|
||||
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
||||
}
|
||||
export const ConversationContainer = ({
|
||||
theme,
|
||||
predefinedVariables,
|
||||
onNewBlockVisible,
|
||||
onCompleted,
|
||||
onVariablesPrefilled,
|
||||
@ -50,23 +52,28 @@ export const ConversationContainer = ({
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prefilledVariables = injectUrlParamsIntoVariables()
|
||||
if (onVariablesPrefilled) {
|
||||
onVariablesPrefilled(prefilledVariables)
|
||||
setPrefilledVariables(prefilledVariables)
|
||||
if (predefinedVariables) {
|
||||
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
|
||||
if (onVariablesPrefilled) {
|
||||
onVariablesPrefilled(prefilledVariables)
|
||||
setPrefilledVariables(prefilledVariables)
|
||||
}
|
||||
}
|
||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const injectUrlParamsIntoVariables = () => {
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
const injectPredefinedVariables = (predefinedVariables: {
|
||||
[key: string]: string | undefined
|
||||
}) => {
|
||||
const prefilledVariables: VariableWithValue[] = []
|
||||
urlParams.forEach((value, key) => {
|
||||
Object.keys(predefinedVariables).forEach((key) => {
|
||||
const matchingVariable = typebot.variables.find(
|
||||
(v) => v.name.toLowerCase() === key.toLowerCase()
|
||||
)
|
||||
if (isNotDefined(matchingVariable)) return
|
||||
const value = predefinedVariables[key]
|
||||
if (!value) return
|
||||
updateVariableValue(matchingVariable?.id, value)
|
||||
prefilledVariables.push({ ...matchingVariable, value })
|
||||
})
|
||||
|
@ -26,17 +26,20 @@ export type TypebotViewerProps = {
|
||||
isPreview?: boolean
|
||||
apiHost?: string
|
||||
style?: CSSProperties
|
||||
predefinedVariables?: { [key: string]: string | undefined }
|
||||
onNewBlockVisible?: (edge: Edge) => void
|
||||
onNewAnswer?: (answer: Answer) => void
|
||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||
onCompleted?: () => void
|
||||
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
||||
}
|
||||
|
||||
export const TypebotViewer = ({
|
||||
typebot,
|
||||
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL,
|
||||
isPreview = false,
|
||||
style,
|
||||
predefinedVariables,
|
||||
onNewLog,
|
||||
onNewBlockVisible,
|
||||
onNewAnswer,
|
||||
@ -104,6 +107,7 @@ export const TypebotViewer = ({
|
||||
theme={typebot.theme}
|
||||
onNewBlockVisible={handleNewBlockVisible}
|
||||
onCompleted={handleCompleted}
|
||||
predefinedVariables={predefinedVariables}
|
||||
onVariablesPrefilled={onVariablesPrefilled}
|
||||
/>
|
||||
</div>
|
||||
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "company" TEXT,
|
||||
ADD COLUMN "onboardingCategories" TEXT[];
|
@ -56,6 +56,8 @@ model User {
|
||||
customDomains CustomDomain[]
|
||||
apiToken String?
|
||||
CollaboratorsOnTypebots CollaboratorsOnTypebots[]
|
||||
company String?
|
||||
onboardingCategories String[]
|
||||
}
|
||||
|
||||
model CustomDomain {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "typebot-js",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"main": "dist/index.js",
|
||||
"unpkg": "dist/index.umd.min.js",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
@ -1,40 +1,44 @@
|
||||
import { ButtonParams } from "../../types";
|
||||
import { ButtonParams } from '../../types'
|
||||
|
||||
export const createButton = (params?: ButtonParams): HTMLButtonElement => {
|
||||
const button = document.createElement("button");
|
||||
button.id = "typebot-bubble-button";
|
||||
button.style.backgroundColor = params?.color ?? "#0042DA";
|
||||
button.appendChild(createButtonIcon(params?.iconUrl));
|
||||
button.appendChild(createCloseIcon());
|
||||
return button;
|
||||
};
|
||||
const button = document.createElement('button')
|
||||
button.id = 'typebot-bubble-button'
|
||||
button.style.backgroundColor = params?.color ?? '#0042DA'
|
||||
button.appendChild(createButtonIcon(params?.iconUrl, params?.iconStyle))
|
||||
button.appendChild(createCloseIcon())
|
||||
return button
|
||||
}
|
||||
|
||||
const createButtonIcon = (src?: string): SVGElement | HTMLImageElement => {
|
||||
if (!src) return createDefaultIcon();
|
||||
const icon = document.createElement("img");
|
||||
icon.classList.add("icon");
|
||||
icon.src = src;
|
||||
return icon;
|
||||
};
|
||||
const createButtonIcon = (
|
||||
src?: string,
|
||||
style?: string
|
||||
): SVGElement | HTMLImageElement => {
|
||||
if (!src) return createDefaultIcon()
|
||||
const icon = document.createElement('img')
|
||||
icon.classList.add('icon')
|
||||
icon.src = src
|
||||
if (style) icon.setAttribute('style', style)
|
||||
return icon
|
||||
}
|
||||
|
||||
const createDefaultIcon = (): SVGElement => {
|
||||
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
icon.setAttribute("viewBox", "0 0 41 19");
|
||||
icon.style.width = "63%";
|
||||
icon.innerHTML = typebotLogoSvgTextContent();
|
||||
icon.classList.add("icon");
|
||||
return icon;
|
||||
};
|
||||
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
icon.setAttribute('viewBox', '0 0 41 19')
|
||||
icon.style.width = '63%'
|
||||
icon.innerHTML = typebotLogoSvgTextContent()
|
||||
icon.classList.add('icon')
|
||||
return icon
|
||||
}
|
||||
|
||||
const createCloseIcon = (): SVGElement => {
|
||||
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
icon.setAttribute("viewBox", "0 0 512 512");
|
||||
icon.innerHTML = closeSvgPath;
|
||||
icon.classList.add("close-icon");
|
||||
return icon;
|
||||
};
|
||||
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||
icon.setAttribute('viewBox', '0 0 512 512')
|
||||
icon.innerHTML = closeSvgPath
|
||||
icon.classList.add('close-icon')
|
||||
return icon
|
||||
}
|
||||
|
||||
const typebotLogoSvgTextContent = () =>
|
||||
`<rect x="40.29" y="0.967773" width="6.83761" height="30.7692" rx="3.4188" transform="rotate(90 40.29 0.967773)"></rect> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.70884 7.80538C5.597 7.80538 7.12765 6.27473 7.12765 4.38658C7.12765 2.49842 5.597 0.967773 3.70884 0.967773C1.82069 0.967773 0.290039 2.49842 0.290039 4.38658C0.290039 6.27473 1.82069 7.80538 3.70884 7.80538Z" fill="white"></path> <rect x="0.290039" y="18.0615" width="6.83761" height="30.7692" rx="3.4188" transform="rotate(-90 0.290039 18.0615)" fill="white"></rect> <path fill-rule="evenodd" clip-rule="evenodd" d="M36.8712 11.2239C34.9831 11.2239 33.4524 12.7546 33.4524 14.6427C33.4524 16.5309 34.9831 18.0615 36.8712 18.0615C38.7594 18.0615 40.29 16.5309 40.29 14.6427C40.29 12.7546 38.7594 11.2239 36.8712 11.2239Z" fill="white"></path>`;
|
||||
`<rect x="40.29" y="0.967773" width="6.83761" height="30.7692" rx="3.4188" transform="rotate(90 40.29 0.967773)"></rect> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.70884 7.80538C5.597 7.80538 7.12765 6.27473 7.12765 4.38658C7.12765 2.49842 5.597 0.967773 3.70884 0.967773C1.82069 0.967773 0.290039 2.49842 0.290039 4.38658C0.290039 6.27473 1.82069 7.80538 3.70884 7.80538Z" fill="white"></path> <rect x="0.290039" y="18.0615" width="6.83761" height="30.7692" rx="3.4188" transform="rotate(-90 0.290039 18.0615)" fill="white"></rect> <path fill-rule="evenodd" clip-rule="evenodd" d="M36.8712 11.2239C34.9831 11.2239 33.4524 12.7546 33.4524 14.6427C33.4524 16.5309 34.9831 18.0615 36.8712 18.0615C38.7594 18.0615 40.29 16.5309 40.29 14.6427C40.29 12.7546 38.7594 11.2239 36.8712 11.2239Z" fill="white"></path>`
|
||||
|
||||
export const closeSvgPath = `<path d="M278.6 256l68.2-68.2c6.2-6.2 6.2-16.4 0-22.6-6.2-6.2-16.4-6.2-22.6 0L256 233.4l-68.2-68.2c-6.2-6.2-16.4-6.2-22.6 0-3.1 3.1-4.7 7.2-4.7 11.3 0 4.1 1.6 8.2 4.7 11.3l68.2 68.2-68.2 68.2c-3.1 3.1-4.7 7.2-4.7 11.3 0 4.1 1.6 8.2 4.7 11.3 6.2 6.2 16.4 6.2 22.6 0l68.2-68.2 68.2 68.2c6.2 6.2 16.4 6.2 22.6 0 6.2-6.2 6.2-16.4 0-22.6L278.6 256z"></path>`;
|
||||
export const closeSvgPath = `<path d="M278.6 256l68.2-68.2c6.2-6.2 6.2-16.4 0-22.6-6.2-6.2-16.4-6.2-22.6 0L256 233.4l-68.2-68.2c-6.2-6.2-16.4-6.2-22.6 0-3.1 3.1-4.7 7.2-4.7 11.3 0 4.1 1.6 8.2 4.7 11.3l68.2 68.2-68.2 68.2c-3.1 3.1-4.7 7.2-4.7 11.3 0 4.1 1.6 8.2 4.7 11.3 6.2 6.2 16.4 6.2 22.6 0l68.2-68.2 68.2 68.2c6.2 6.2 16.4 6.2 22.6 0 6.2-6.2 6.2-16.4 0-22.6L278.6 256z"></path>`
|
||||
|
@ -29,6 +29,7 @@ export type BubbleParams = {
|
||||
export type ButtonParams = {
|
||||
color?: string
|
||||
iconUrl?: string
|
||||
iconStyle?: string
|
||||
}
|
||||
|
||||
export type ProactiveMessageParams = {
|
||||
|
@ -18,5 +18,6 @@
|
||||
"dx": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"globalDependencies": ["$NEXT_PUBLIC_E2E_TEST"]
|
||||
}
|
||||
|
10
yarn.lock
10
yarn.lock
@ -4067,6 +4067,11 @@
|
||||
"@types/node" "*"
|
||||
"@types/responselike" "*"
|
||||
|
||||
"@types/canvas-confetti@^1.4.2":
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/canvas-confetti/-/canvas-confetti-1.4.2.tgz#35c99fc904492fdcc6515c742509e04f3527211c"
|
||||
integrity sha512-t45KUDHlwrD9PJVRHc5z1SlXhO82BQEgMKUXGEV1KnWLFMPA6Y5LfUsLTHHzH9KcKDHZLEiYYH5nIDcjRKWNTg==
|
||||
|
||||
"@types/connect-history-api-fallback@^1.3.5":
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae"
|
||||
@ -5896,6 +5901,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001283, caniuse-lite@^1.0.30001297, can
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz#65c7f9fb7e4594fca0a333bec1d8939662377596"
|
||||
integrity sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==
|
||||
|
||||
canvas-confetti@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.5.1.tgz#bf5b8622ef3bcd347378a972fc4194a89cfe0c9b"
|
||||
integrity sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==
|
||||
|
||||
ccount@^1.0.0, ccount@^1.0.3:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
|
||||
|
Reference in New Issue
Block a user