feat: ✨ Add new onboarding flow
This commit is contained in:
@@ -122,7 +122,11 @@ export const PersonalInfoForm = () => {
|
|||||||
|
|
||||||
{hasUnsavedChanges && (
|
{hasUnsavedChanges && (
|
||||||
<Flex justifyContent="flex-end">
|
<Flex justifyContent="flex-end">
|
||||||
<Button colorScheme="blue" onClick={saveUser} isLoading={isSaving}>
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
onClick={() => saveUser()}
|
||||||
|
isLoading={isSaving}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { ButtonSkeleton, FolderButton } from './FolderContent/FolderButton'
|
|||||||
import { SharedTypebotsButton } from './FolderContent/SharedTypebotsButton'
|
import { SharedTypebotsButton } from './FolderContent/SharedTypebotsButton'
|
||||||
import { TypebotButton } from './FolderContent/TypebotButton'
|
import { TypebotButton } from './FolderContent/TypebotButton'
|
||||||
import { TypebotCardOverlay } from './FolderContent/TypebotButtonOverlay'
|
import { TypebotCardOverlay } from './FolderContent/TypebotButtonOverlay'
|
||||||
|
import { OnboardingModal } from './OnboardingModal'
|
||||||
|
|
||||||
type Props = { folder: DashboardFolder | null }
|
type Props = { folder: DashboardFolder | null }
|
||||||
|
|
||||||
@@ -163,6 +164,9 @@ export const FolderContent = ({ folder }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" flex="1" justify="center">
|
<Flex w="full" flex="1" justify="center">
|
||||||
|
{typebots && user && folder === null && (
|
||||||
|
<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>
|
||||||
@@ -179,6 +183,7 @@ export const FolderContent = ({ folder }: Props) => {
|
|||||||
<CreateBotButton
|
<CreateBotButton
|
||||||
folderId={folder?.id}
|
folderId={folder?.id}
|
||||||
isLoading={isTypebotLoading}
|
isLoading={isTypebotLoading}
|
||||||
|
isFirstBot={typebots?.length === 0 && folder === null}
|
||||||
/>
|
/>
|
||||||
{totalSharedTypebots > 0 && <SharedTypebotsButton />}
|
{totalSharedTypebots > 0 && <SharedTypebotsButton />}
|
||||||
{isFolderLoading && <ButtonSkeleton />}
|
{isFolderLoading && <ButtonSkeleton />}
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
import { Button, ButtonProps, Text, VStack } from '@chakra-ui/react'
|
import { Button, ButtonProps, Text, VStack } from '@chakra-ui/react'
|
||||||
import { PlusIcon } from 'assets/icons'
|
import { PlusIcon } from 'assets/icons'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import { stringify } from 'qs'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export const CreateBotButton = ({
|
export const CreateBotButton = ({
|
||||||
folderId,
|
folderId,
|
||||||
|
isFirstBot,
|
||||||
...props
|
...props
|
||||||
}: { folderId?: string } & ButtonProps) => {
|
}: { folderId?: string; isFirstBot: boolean } & ButtonProps) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const handleClick = () =>
|
const handleClick = () =>
|
||||||
folderId
|
router.push(
|
||||||
? router.push(`/typebots/create?folderId=${folderId}`)
|
`/typebots/create?${stringify({
|
||||||
: router.push('/typebots/create')
|
isFirstBot: !isFirstBot ? undefined : isFirstBot,
|
||||||
|
folderId,
|
||||||
|
})}`
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<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)
|
setRightPanel(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => {
|
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
||||||
toast(log as UseToastOptions)
|
toast(log as UseToastOptions)
|
||||||
console.log(log.details)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<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 () => {
|
return () => {
|
||||||
setLocalWebhook((localWebhook) => {
|
setLocalWebhook((localWebhook) => {
|
||||||
console.log(localWebhook)
|
|
||||||
if (!localWebhook) return
|
if (!localWebhook) return
|
||||||
updateWebhook(webhookId, localWebhook).then()
|
updateWebhook(webhookId, localWebhook).then()
|
||||||
return localWebhook
|
return localWebhook
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ export const SupportBubble = () => {
|
|||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
}/typebot-support`,
|
}/typebot-support`,
|
||||||
backgroundColor: '#ffffff',
|
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: {
|
hiddenVariables: {
|
||||||
'User ID': user?.id,
|
'User ID': user?.id,
|
||||||
Name: user?.name ?? undefined,
|
Name: user?.name ?? undefined,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { RightPanel, useEditor } from 'contexts/EditorContext'
|
|||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { isNotDefined } from 'utils'
|
||||||
import { PublishButton } from '../buttons/PublishButton'
|
import { PublishButton } from '../buttons/PublishButton'
|
||||||
import { CollaborationMenuButton } from './CollaborationMenuButton'
|
import { CollaborationMenuButton } from './CollaborationMenuButton'
|
||||||
import { EditableTypebotName } from './EditableTypebotName'
|
import { EditableTypebotName } from './EditableTypebotName'
|
||||||
@@ -21,6 +22,7 @@ export const headerHeight = 56
|
|||||||
|
|
||||||
export const TypebotHeader = () => {
|
export const TypebotHeader = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { rightPanel } = useEditor()
|
||||||
const {
|
const {
|
||||||
typebot,
|
typebot,
|
||||||
updateOnBothTypebots,
|
updateOnBothTypebots,
|
||||||
@@ -154,7 +156,7 @@ export const TypebotHeader = () => {
|
|||||||
|
|
||||||
<HStack right="40px" pos="absolute">
|
<HStack right="40px" pos="absolute">
|
||||||
<CollaborationMenuButton />
|
<CollaborationMenuButton />
|
||||||
{router.pathname.includes('/edit') && (
|
{router.pathname.includes('/edit') && isNotDefined(rightPanel) && (
|
||||||
<Button onClick={handlePreviewClick}>Preview</Button>
|
<Button onClick={handlePreviewClick}>Preview</Button>
|
||||||
)}
|
)}
|
||||||
<PublishButton />
|
<PublishButton />
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { ChevronLeftIcon } from 'assets/icons'
|
import { ChevronLeftIcon } from 'assets/icons'
|
||||||
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
import { timeSince } from 'services/utils'
|
import { timeSince } from 'services/utils'
|
||||||
import { isNotDefined } from 'utils'
|
import { isNotDefined } from 'utils'
|
||||||
|
|
||||||
export const PublishButton = () => {
|
export const PublishButton = () => {
|
||||||
|
const { push, query } = useRouter()
|
||||||
const {
|
const {
|
||||||
isPublishing,
|
isPublishing,
|
||||||
isPublished,
|
isPublished,
|
||||||
@@ -24,6 +26,11 @@ export const PublishButton = () => {
|
|||||||
restorePublishedTypebot,
|
restorePublishedTypebot,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
|
|
||||||
|
const handlePublishClick = () => {
|
||||||
|
publishTypebot()
|
||||||
|
if (!publishedTypebot) push(`/typebots/${query.typebotId}/share`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack spacing="1px">
|
<HStack spacing="1px">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -47,7 +54,7 @@ export const PublishButton = () => {
|
|||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
isLoading={isPublishing}
|
isLoading={isPublishing}
|
||||||
isDisabled={isPublished}
|
isDisabled={isPublished}
|
||||||
onClick={publishTypebot}
|
onClick={handlePublishClick}
|
||||||
borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined}
|
borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined}
|
||||||
>
|
>
|
||||||
{isPublished ? 'Published' : 'Publish'}
|
{isPublished ? 'Published' : 'Publish'}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const userContext = createContext<{
|
|||||||
hasUnsavedChanges: boolean
|
hasUnsavedChanges: boolean
|
||||||
isOAuthProvider: boolean
|
isOAuthProvider: boolean
|
||||||
updateUser: (newUser: Partial<User>) => void
|
updateUser: (newUser: Partial<User>) => void
|
||||||
saveUser: () => void
|
saveUser: (newUser?: Partial<User>) => void
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
}>({})
|
}>({})
|
||||||
@@ -75,10 +75,11 @@ export const UserContext = ({ children }: { children: ReactNode }) => {
|
|||||||
setUser({ ...user, ...newUser })
|
setUser({ ...user, ...newUser })
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveUser = async () => {
|
const saveUser = async (newUser?: Partial<User>) => {
|
||||||
if (isNotDefined(user)) return
|
if (isNotDefined(user)) return
|
||||||
setIsSaving(true)
|
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 })
|
if (error) toast({ title: error.name, description: error.message })
|
||||||
await refreshUser()
|
await refreshUser()
|
||||||
setIsSaving(false)
|
setIsSaving(false)
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Stack,
|
Stack,
|
||||||
useToast,
|
useToast,
|
||||||
|
Tooltip,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { CreateTypebotMoreButton } from 'components/templates/ImportFileMenuItem'
|
import { CreateTypebotMoreButton } from 'components/templates/ImportFileMenuItem'
|
||||||
import { TemplateButton } from 'components/templates/TemplateButton'
|
import { TemplateButton } from 'components/templates/TemplateButton'
|
||||||
import { useUser } from 'contexts/UserContext'
|
import { useUser } from 'contexts/UserContext'
|
||||||
import { defaultTheme, Typebot } from 'models'
|
import { defaultTheme, Typebot } from 'models'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { createTypebot, importTypebot } from 'services/typebots/typebots'
|
import { createTypebot, importTypebot } from 'services/typebots/typebots'
|
||||||
|
|
||||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||||
@@ -22,6 +23,8 @@ const templates: TemplateProps[] = [
|
|||||||
export const TemplatesContent = () => {
|
export const TemplatesContent = () => {
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||||
|
useState(false)
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
@@ -31,6 +34,13 @@ export const TemplatesContent = () => {
|
|||||||
title: 'An error occured',
|
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) => {
|
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||||
if (!user) return
|
if (!user) return
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
@@ -60,13 +70,23 @@ export const TemplatesContent = () => {
|
|||||||
<Flex w="full" justifyContent="center">
|
<Flex w="full" justifyContent="center">
|
||||||
<Stack maxW="1000px" flex="1" pt="6" spacing={4}>
|
<Stack maxW="1000px" flex="1" pt="6" spacing={4}>
|
||||||
<Flex justifyContent="space-between">
|
<Flex justifyContent="space-between">
|
||||||
<Button
|
<Tooltip
|
||||||
onClick={() => handleCreateSubmit()}
|
isOpen={isFromScratchTooltipOpened}
|
||||||
isLoading={isLoading}
|
label="Strongly suggested if you're new to Typebot."
|
||||||
colorScheme="blue"
|
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} />
|
<CreateTypebotMoreButton onNewTypebot={handleCreateSubmit} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"@udecode/plate-ui-toolbar": "^10.2.2",
|
"@udecode/plate-ui-toolbar": "^10.2.2",
|
||||||
"bot-engine": "*",
|
"bot-engine": "*",
|
||||||
"browser-image-compression": "^1.0.17",
|
"browser-image-compression": "^1.0.17",
|
||||||
|
"canvas-confetti": "^1.5.1",
|
||||||
"cuid": "^2.1.8",
|
"cuid": "^2.1.8",
|
||||||
"db": "*",
|
"db": "*",
|
||||||
"deep-object-diff": "^1.1.7",
|
"deep-object-diff": "^1.1.7",
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.19.2",
|
"@playwright/test": "^1.19.2",
|
||||||
|
"@types/canvas-confetti": "^1.4.2",
|
||||||
"@types/google-spreadsheet": "^3.1.5",
|
"@types/google-spreadsheet": "^3.1.5",
|
||||||
"@types/jsonwebtoken": "8.5.8",
|
"@types/jsonwebtoken": "8.5.8",
|
||||||
"@types/micro-cors": "^0.1.2",
|
"@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=Click to edit...')
|
||||||
await page.click('text=Giphy')
|
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(
|
await expect(page.locator('img[alt="Step image"]')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
new RegExp('giphy.com/media', 'gm')
|
new RegExp('giphy.com/media', 'gm')
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ test.describe('Dashboard page', () => {
|
|||||||
test.use({
|
test.use({
|
||||||
storageState: path.join(__dirname, '../freeUser.json'),
|
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.goto(`/typebots/${typebotId}/share`)
|
||||||
await page.click('text=Add my domain')
|
await page.click('text=Add my domain')
|
||||||
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
await expect(page.locator('text=Upgrade now')).toBeVisible()
|
||||||
|
|||||||
@@ -41,26 +41,4 @@ test.describe.parallel('Text input step', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(typebotViewer(page).locator(`text=Go`)).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) => {
|
const processMessage = (event: MessageEvent) => {
|
||||||
console.log(event.data)
|
|
||||||
if (event.data.from === 'typebot') refreshIframeContent()
|
if (event.data.from === 'typebot') refreshIframeContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,22 @@ export const TypebotPage = ({
|
|||||||
url,
|
url,
|
||||||
}: TypebotPageProps & { typebot: PublicTypebot }) => {
|
}: TypebotPageProps & { typebot: PublicTypebot }) => {
|
||||||
const [showTypebot, setShowTypebot] = useState(false)
|
const [showTypebot, setShowTypebot] = useState(false)
|
||||||
|
const [predefinedVariables, setPredefinedVariables] =
|
||||||
|
useState<{ [key: string]: string }>()
|
||||||
const [error, setError] = useState<Error | undefined>(
|
const [error, setError] = useState<Error | undefined>(
|
||||||
isIE ? new Error('Internet explorer is not supported') : undefined
|
isIE ? new Error('Internet explorer is not supported') : undefined
|
||||||
)
|
)
|
||||||
const [resultId, setResultId] = useState<string | 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)
|
// Workaround for react-frame-component bug (https://github.com/ryanseddon/react-frame-component/pull/207)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -83,6 +94,7 @@ export const TypebotPage = ({
|
|||||||
{showTypebot && (
|
{showTypebot && (
|
||||||
<TypebotViewer
|
<TypebotViewer
|
||||||
typebot={typebot}
|
typebot={typebot}
|
||||||
|
predefinedVariables={predefinedVariables}
|
||||||
onNewAnswer={handleNewAnswer}
|
onNewAnswer={handleNewAnswer}
|
||||||
onCompleted={handleCompleted}
|
onCompleted={handleCompleted}
|
||||||
onVariablesPrefilled={initializeResult}
|
onVariablesPrefilled={initializeResult}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
.status(404)
|
.status(404)
|
||||||
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
|
.send({ statusCode: 404, data: { message: `Couldn't find webhook` } })
|
||||||
const preparedWebhook = prepareWebhookAttributes(webhook, step.options)
|
const preparedWebhook = prepareWebhookAttributes(webhook, step.options)
|
||||||
console.log(preparedWebhook)
|
|
||||||
const result = await executeWebhook(typebot)(
|
const result = await executeWebhook(typebot)(
|
||||||
preparedWebhook,
|
preparedWebhook,
|
||||||
variables,
|
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)
|
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
|
||||||
return onBlockEnd(currentStep.outgoingEdgeId)
|
return onBlockEnd(currentStep.outgoingEdgeId)
|
||||||
}
|
}
|
||||||
const nextStep = steps[processedSteps.length]
|
const nextStep = steps[processedSteps.length + startStepIndex]
|
||||||
if (nextStep) insertStepInStack(nextStep)
|
if (nextStep) insertStepInStack(nextStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
predefinedVariables?: { [key: string]: string | undefined }
|
||||||
onNewBlockVisible: (edge: Edge) => void
|
onNewBlockVisible: (edge: Edge) => void
|
||||||
onCompleted: () => void
|
onCompleted: () => void
|
||||||
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
||||||
}
|
}
|
||||||
export const ConversationContainer = ({
|
export const ConversationContainer = ({
|
||||||
theme,
|
theme,
|
||||||
|
predefinedVariables,
|
||||||
onNewBlockVisible,
|
onNewBlockVisible,
|
||||||
onCompleted,
|
onCompleted,
|
||||||
onVariablesPrefilled,
|
onVariablesPrefilled,
|
||||||
@@ -50,23 +52,28 @@ export const ConversationContainer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prefilledVariables = injectUrlParamsIntoVariables()
|
if (predefinedVariables) {
|
||||||
if (onVariablesPrefilled) {
|
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
|
||||||
onVariablesPrefilled(prefilledVariables)
|
if (onVariablesPrefilled) {
|
||||||
setPrefilledVariables(prefilledVariables)
|
onVariablesPrefilled(prefilledVariables)
|
||||||
|
setPrefilledVariables(prefilledVariables)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const injectUrlParamsIntoVariables = () => {
|
const injectPredefinedVariables = (predefinedVariables: {
|
||||||
const urlParams = new URLSearchParams(location.search)
|
[key: string]: string | undefined
|
||||||
|
}) => {
|
||||||
const prefilledVariables: VariableWithValue[] = []
|
const prefilledVariables: VariableWithValue[] = []
|
||||||
urlParams.forEach((value, key) => {
|
Object.keys(predefinedVariables).forEach((key) => {
|
||||||
const matchingVariable = typebot.variables.find(
|
const matchingVariable = typebot.variables.find(
|
||||||
(v) => v.name.toLowerCase() === key.toLowerCase()
|
(v) => v.name.toLowerCase() === key.toLowerCase()
|
||||||
)
|
)
|
||||||
if (isNotDefined(matchingVariable)) return
|
if (isNotDefined(matchingVariable)) return
|
||||||
|
const value = predefinedVariables[key]
|
||||||
|
if (!value) return
|
||||||
updateVariableValue(matchingVariable?.id, value)
|
updateVariableValue(matchingVariable?.id, value)
|
||||||
prefilledVariables.push({ ...matchingVariable, value })
|
prefilledVariables.push({ ...matchingVariable, value })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,17 +26,20 @@ export type TypebotViewerProps = {
|
|||||||
isPreview?: boolean
|
isPreview?: boolean
|
||||||
apiHost?: string
|
apiHost?: string
|
||||||
style?: CSSProperties
|
style?: CSSProperties
|
||||||
|
predefinedVariables?: { [key: string]: string | undefined }
|
||||||
onNewBlockVisible?: (edge: Edge) => void
|
onNewBlockVisible?: (edge: Edge) => void
|
||||||
onNewAnswer?: (answer: Answer) => void
|
onNewAnswer?: (answer: Answer) => void
|
||||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||||
onCompleted?: () => void
|
onCompleted?: () => void
|
||||||
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
onVariablesPrefilled?: (prefilledVariables: VariableWithValue[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TypebotViewer = ({
|
export const TypebotViewer = ({
|
||||||
typebot,
|
typebot,
|
||||||
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL,
|
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL,
|
||||||
isPreview = false,
|
isPreview = false,
|
||||||
style,
|
style,
|
||||||
|
predefinedVariables,
|
||||||
onNewLog,
|
onNewLog,
|
||||||
onNewBlockVisible,
|
onNewBlockVisible,
|
||||||
onNewAnswer,
|
onNewAnswer,
|
||||||
@@ -104,6 +107,7 @@ export const TypebotViewer = ({
|
|||||||
theme={typebot.theme}
|
theme={typebot.theme}
|
||||||
onNewBlockVisible={handleNewBlockVisible}
|
onNewBlockVisible={handleNewBlockVisible}
|
||||||
onCompleted={handleCompleted}
|
onCompleted={handleCompleted}
|
||||||
|
predefinedVariables={predefinedVariables}
|
||||||
onVariablesPrefilled={onVariablesPrefilled}
|
onVariablesPrefilled={onVariablesPrefilled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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[]
|
customDomains CustomDomain[]
|
||||||
apiToken String?
|
apiToken String?
|
||||||
CollaboratorsOnTypebots CollaboratorsOnTypebots[]
|
CollaboratorsOnTypebots CollaboratorsOnTypebots[]
|
||||||
|
company String?
|
||||||
|
onboardingCategories String[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model CustomDomain {
|
model CustomDomain {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "typebot-js",
|
"name": "typebot-js",
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"unpkg": "dist/index.umd.min.js",
|
"unpkg": "dist/index.umd.min.js",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,40 +1,44 @@
|
|||||||
import { ButtonParams } from "../../types";
|
import { ButtonParams } from '../../types'
|
||||||
|
|
||||||
export const createButton = (params?: ButtonParams): HTMLButtonElement => {
|
export const createButton = (params?: ButtonParams): HTMLButtonElement => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement('button')
|
||||||
button.id = "typebot-bubble-button";
|
button.id = 'typebot-bubble-button'
|
||||||
button.style.backgroundColor = params?.color ?? "#0042DA";
|
button.style.backgroundColor = params?.color ?? '#0042DA'
|
||||||
button.appendChild(createButtonIcon(params?.iconUrl));
|
button.appendChild(createButtonIcon(params?.iconUrl, params?.iconStyle))
|
||||||
button.appendChild(createCloseIcon());
|
button.appendChild(createCloseIcon())
|
||||||
return button;
|
return button
|
||||||
};
|
}
|
||||||
|
|
||||||
const createButtonIcon = (src?: string): SVGElement | HTMLImageElement => {
|
const createButtonIcon = (
|
||||||
if (!src) return createDefaultIcon();
|
src?: string,
|
||||||
const icon = document.createElement("img");
|
style?: string
|
||||||
icon.classList.add("icon");
|
): SVGElement | HTMLImageElement => {
|
||||||
icon.src = src;
|
if (!src) return createDefaultIcon()
|
||||||
return icon;
|
const icon = document.createElement('img')
|
||||||
};
|
icon.classList.add('icon')
|
||||||
|
icon.src = src
|
||||||
|
if (style) icon.setAttribute('style', style)
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
|
||||||
const createDefaultIcon = (): SVGElement => {
|
const createDefaultIcon = (): SVGElement => {
|
||||||
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||||
icon.setAttribute("viewBox", "0 0 41 19");
|
icon.setAttribute('viewBox', '0 0 41 19')
|
||||||
icon.style.width = "63%";
|
icon.style.width = '63%'
|
||||||
icon.innerHTML = typebotLogoSvgTextContent();
|
icon.innerHTML = typebotLogoSvgTextContent()
|
||||||
icon.classList.add("icon");
|
icon.classList.add('icon')
|
||||||
return icon;
|
return icon
|
||||||
};
|
}
|
||||||
|
|
||||||
const createCloseIcon = (): SVGElement => {
|
const createCloseIcon = (): SVGElement => {
|
||||||
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||||
icon.setAttribute("viewBox", "0 0 512 512");
|
icon.setAttribute('viewBox', '0 0 512 512')
|
||||||
icon.innerHTML = closeSvgPath;
|
icon.innerHTML = closeSvgPath
|
||||||
icon.classList.add("close-icon");
|
icon.classList.add('close-icon')
|
||||||
return icon;
|
return icon
|
||||||
};
|
}
|
||||||
|
|
||||||
const typebotLogoSvgTextContent = () =>
|
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 = {
|
export type ButtonParams = {
|
||||||
color?: string
|
color?: string
|
||||||
iconUrl?: string
|
iconUrl?: string
|
||||||
|
iconStyle?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProactiveMessageParams = {
|
export type ProactiveMessageParams = {
|
||||||
|
|||||||
@@ -18,5 +18,6 @@
|
|||||||
"dx": {
|
"dx": {
|
||||||
"cache": false
|
"cache": false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"globalDependencies": ["$NEXT_PUBLIC_E2E_TEST"]
|
||||||
}
|
}
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -4067,6 +4067,11 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/responselike" "*"
|
"@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":
|
"@types/connect-history-api-fallback@^1.3.5":
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz#65c7f9fb7e4594fca0a333bec1d8939662377596"
|
||||||
integrity sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==
|
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:
|
ccount@^1.0.0, ccount@^1.0.3:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
|
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
|
||||||
|
|||||||
Reference in New Issue
Block a user