diff --git a/apps/builder/components/account/PersonalInfoForm.tsx b/apps/builder/components/account/PersonalInfoForm.tsx index b3805210a..a4bcbdcda 100644 --- a/apps/builder/components/account/PersonalInfoForm.tsx +++ b/apps/builder/components/account/PersonalInfoForm.tsx @@ -122,7 +122,11 @@ export const PersonalInfoForm = () => { {hasUnsavedChanges && ( - diff --git a/apps/builder/components/dashboard/FolderContent.tsx b/apps/builder/components/dashboard/FolderContent.tsx index 67d8c8e7f..549cb36f2 100644 --- a/apps/builder/components/dashboard/FolderContent.tsx +++ b/apps/builder/components/dashboard/FolderContent.tsx @@ -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 ( + {typebots && user && folder === null && ( + + )} {folder?.name} @@ -179,6 +183,7 @@ export const FolderContent = ({ folder }: Props) => { {totalSharedTypebots > 0 && } {isFolderLoading && } diff --git a/apps/builder/components/dashboard/FolderContent/CreateBotButton.tsx b/apps/builder/components/dashboard/FolderContent/CreateBotButton.tsx index ec1a5db38..9b0a627c1 100644 --- a/apps/builder/components/dashboard/FolderContent/CreateBotButton.tsx +++ b/apps/builder/components/dashboard/FolderContent/CreateBotButton.tsx @@ -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 ( )} diff --git a/apps/builder/components/shared/buttons/PublishButton.tsx b/apps/builder/components/shared/buttons/PublishButton.tsx index d0a776a1b..5b06c96b7 100644 --- a/apps/builder/components/shared/buttons/PublishButton.tsx +++ b/apps/builder/components/shared/buttons/PublishButton.tsx @@ -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 ( { colorScheme="blue" isLoading={isPublishing} isDisabled={isPublished} - onClick={publishTypebot} + onClick={handlePublishClick} borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined} > {isPublished ? 'Published' : 'Publish'} diff --git a/apps/builder/contexts/UserContext.tsx b/apps/builder/contexts/UserContext.tsx index 50c5ffb96..56147b5b5 100644 --- a/apps/builder/contexts/UserContext.tsx +++ b/apps/builder/contexts/UserContext.tsx @@ -21,7 +21,7 @@ const userContext = createContext<{ hasUnsavedChanges: boolean isOAuthProvider: boolean updateUser: (newUser: Partial) => void - saveUser: () => void + saveUser: (newUser?: Partial) => 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) => { 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) diff --git a/apps/builder/layouts/dashboard/TemplatesContent.tsx b/apps/builder/layouts/dashboard/TemplatesContent.tsx index 720baeec2..7f105cf63 100644 --- a/apps/builder/layouts/dashboard/TemplatesContent.tsx +++ b/apps/builder/layouts/dashboard/TemplatesContent.tsx @@ -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 = () => { - + + + diff --git a/apps/builder/package.json b/apps/builder/package.json index 9808864e9..ec28309f1 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -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", diff --git a/apps/builder/playwright/tests/bubbles/image.spec.ts b/apps/builder/playwright/tests/bubbles/image.spec.ts index 342a31982..f90b164e7 100644 --- a/apps/builder/playwright/tests/bubbles/image.spec.ts +++ b/apps/builder/playwright/tests/bubbles/image.spec.ts @@ -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') diff --git a/apps/builder/playwright/tests/customDomains.spec.ts b/apps/builder/playwright/tests/customDomains.spec.ts index 28e529e55..2e8cf25c3 100644 --- a/apps/builder/playwright/tests/customDomains.spec.ts +++ b/apps/builder/playwright/tests/customDomains.spec.ts @@ -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() diff --git a/apps/builder/playwright/tests/inputs/text.spec.ts b/apps/builder/playwright/tests/inputs/text.spec.ts index a85b630f0..8b929e0dd 100644 --- a/apps/builder/playwright/tests/inputs/text.spec.ts +++ b/apps/builder/playwright/tests/inputs/text.spec.ts @@ -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() - }) }) diff --git a/apps/builder/public/bots/onboarding.json b/apps/builder/public/bots/onboarding.json new file mode 100644 index 000000000..482d0a56a --- /dev/null +++ b/apps/builder/public/bots/onboarding.json @@ -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": "
Welcome 👋
", + "richText": [ + { "type": "p", "children": [{ "text": "Welcome 👋" }] } + ], + "plainText": "Welcome 👋" + } + }, + { + "id": "cl1267y1u000f2e6d4rlglv6g", + "type": "text", + "blockId": "cl1267q1z000d2e6d949f2ge4", + "content": { + "html": "
What's your name?
", + "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": "
Welcome {{Name}} 👋
", + "richText": [ + { "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] } + ], + "plainText": "Welcome {{Name}} 👋" + } + }, + { + "id": "cl126hb9m000l2e6d5qk3mohn", + "type": "text", + "blockId": "cl126ixoq000p2e6dfbz9sype", + "content": { + "html": "
I'm super pumped that you've decided to try out Typebot 😍
", + "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": "
You are small steps away from meaningful, hyper-personalized experience for your users
", + "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": "
Let's get you set up for your Typebot journey.
", + "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": "
Do you work for a specific company?
", + "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": "
What type of forms are you planning to build with Typebot?
", + "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": "
Thank you for answering those questions!
", + "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": "
You can reach out to me using the contact bubble on the bottom right corner 🤓
", + "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": "
Let's create your first typebot...
", + "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": "
What else?
", + "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 +} diff --git a/apps/landing-page/components/Homepage/RealTimeResults.tsx b/apps/landing-page/components/Homepage/RealTimeResults.tsx index 6642b2613..4ff87952a 100644 --- a/apps/landing-page/components/Homepage/RealTimeResults.tsx +++ b/apps/landing-page/components/Homepage/RealTimeResults.tsx @@ -32,7 +32,6 @@ export const RealTimeResults = () => { }, []) const processMessage = (event: MessageEvent) => { - console.log(event.data) if (event.data.from === 'typebot') refreshIframeContent() } diff --git a/apps/viewer/layouts/TypebotPage.tsx b/apps/viewer/layouts/TypebotPage.tsx index 06afde504..ebbbe35f9 100644 --- a/apps/viewer/layouts/TypebotPage.tsx +++ b/apps/viewer/layouts/TypebotPage.tsx @@ -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( isIE ? new Error('Internet explorer is not supported') : undefined ) const [resultId, setResultId] = useState() + 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 && ( { .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, diff --git a/apps/viewer/playwright/fixtures/typebots/predefinedVariables.json b/apps/viewer/playwright/fixtures/typebots/predefinedVariables.json new file mode 100644 index 000000000..d792a7100 --- /dev/null +++ b/apps/viewer/playwright/fixtures/typebots/predefinedVariables.json @@ -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": "
Hey I know you!
", + "richText": [ + { "type": "p", "children": [{ "text": "Hey I know you!" }] } + ], + "plainText": "Hey I know you!" + } + }, + { + "id": "cl13bh6jd00072e6dftdirwy4", + "blockId": "cl13bgy1s00042e6dao1wyobm", + "type": "text", + "content": { + "html": "
Your name is {{Name}}
", + "richText": [ + { "type": "p", "children": [{ "text": "Your name is {{Name}}" }] } + ], + "plainText": "Your name is {{Name}}" + } + }, + { + "id": "cl13bhfxd00092e6dydvcqlhm", + "blockId": "cl13bgy1s00042e6dao1wyobm", + "type": "text", + "content": { + "html": "
What's your email?
", + "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 +} diff --git a/apps/viewer/playwright/tests/predefinedVariables.spec.ts b/apps/viewer/playwright/tests/predefinedVariables.spec.ts new file mode 100644 index 000000000..7c70c3fa8 --- /dev/null +++ b/apps/viewer/playwright/tests/predefinedVariables.spec.ts @@ -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() +}) diff --git a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx index 08a47af25..56ce3f8db 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx @@ -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) } diff --git a/packages/bot-engine/src/components/ConversationContainer.tsx b/packages/bot-engine/src/components/ConversationContainer.tsx index 8b6c82bb4..d683d1126 100644 --- a/packages/bot-engine/src/components/ConversationContainer.tsx +++ b/packages/bot-engine/src/components/ConversationContainer.tsx @@ -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 }) }) diff --git a/packages/bot-engine/src/components/TypebotViewer.tsx b/packages/bot-engine/src/components/TypebotViewer.tsx index 6b7757863..94ab3cc6d 100644 --- a/packages/bot-engine/src/components/TypebotViewer.tsx +++ b/packages/bot-engine/src/components/TypebotViewer.tsx @@ -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) => 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} /> diff --git a/packages/db/prisma/migrations/20220322143206_add_company_and_categories/migration.sql b/packages/db/prisma/migrations/20220322143206_add_company_and_categories/migration.sql new file mode 100644 index 000000000..6c3952dcb --- /dev/null +++ b/packages/db/prisma/migrations/20220322143206_add_company_and_categories/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "company" TEXT, +ADD COLUMN "onboardingCategories" TEXT[]; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 98fe7fd51..1d2f9e8ee 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -56,6 +56,8 @@ model User { customDomains CustomDomain[] apiToken String? CollaboratorsOnTypebots CollaboratorsOnTypebots[] + company String? + onboardingCategories String[] } model CustomDomain { diff --git a/packages/typebot-js/package.json b/packages/typebot-js/package.json index 94b794dd8..18ef93a2c 100644 --- a/packages/typebot-js/package.json +++ b/packages/typebot-js/package.json @@ -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", diff --git a/packages/typebot-js/src/embedTypes/chat/button.ts b/packages/typebot-js/src/embedTypes/chat/button.ts index 1195f32e8..f4adf1c3f 100644 --- a/packages/typebot-js/src/embedTypes/chat/button.ts +++ b/packages/typebot-js/src/embedTypes/chat/button.ts @@ -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 = () => - ` `; + ` ` -export const closeSvgPath = ``; +export const closeSvgPath = `` diff --git a/packages/typebot-js/src/types.ts b/packages/typebot-js/src/types.ts index 1f3b1864d..8394b3335 100644 --- a/packages/typebot-js/src/types.ts +++ b/packages/typebot-js/src/types.ts @@ -29,6 +29,7 @@ export type BubbleParams = { export type ButtonParams = { color?: string iconUrl?: string + iconStyle?: string } export type ProactiveMessageParams = { diff --git a/turbo.json b/turbo.json index 2ae30bb6c..77bfd8c23 100644 --- a/turbo.json +++ b/turbo.json @@ -18,5 +18,6 @@ "dx": { "cache": false } - } + }, + "globalDependencies": ["$NEXT_PUBLIC_E2E_TEST"] } diff --git a/yarn.lock b/yarn.lock index d028e6d2d..80e51e465 100644 --- a/yarn.lock +++ b/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"