✨ Introduce bot v2 in builder (#328)
Also, the new engine is the default for updated typebots for viewer Closes #211
This commit is contained in:
@@ -1,11 +1,7 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const { withSentryConfig } = require('@sentry/nextjs')
|
const { withSentryConfig } = require('@sentry/nextjs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const withTM = require('next-transpile-modules')([
|
const withTM = require('next-transpile-modules')(['utils', 'models', 'emails'])
|
||||||
'utils',
|
|
||||||
'models',
|
|
||||||
'emails',
|
|
||||||
'bot-engine',
|
|
||||||
])
|
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = withTM({
|
const nextConfig = withTM({
|
||||||
|
|||||||
@@ -17,13 +17,6 @@
|
|||||||
"@chakra-ui/css-reset": "2.0.11",
|
"@chakra-ui/css-reset": "2.0.11",
|
||||||
"@chakra-ui/react": "2.5.0",
|
"@chakra-ui/react": "2.5.0",
|
||||||
"@chakra-ui/theme-tools": "^2.0.16",
|
"@chakra-ui/theme-tools": "^2.0.16",
|
||||||
"@codemirror/lang-css": "6.0.1",
|
|
||||||
"@codemirror/lang-html": "6.4.1",
|
|
||||||
"@codemirror/lang-javascript": "6.1.2",
|
|
||||||
"@codemirror/lang-json": "6.0.1",
|
|
||||||
"@codemirror/lint": "6.1.0",
|
|
||||||
"@codemirror/state": "6.2.0",
|
|
||||||
"@codemirror/theme-one-dark": "^6.1.0",
|
|
||||||
"@dnd-kit/core": "6.0.7",
|
"@dnd-kit/core": "6.0.7",
|
||||||
"@dnd-kit/sortable": "7.0.2",
|
"@dnd-kit/sortable": "7.0.2",
|
||||||
"@dnd-kit/utilities": "3.2.1",
|
"@dnd-kit/utilities": "3.2.1",
|
||||||
@@ -52,12 +45,17 @@
|
|||||||
"@udecode/plate-ui-toolbar": "19.2.0",
|
"@udecode/plate-ui-toolbar": "19.2.0",
|
||||||
"@use-gesture/react": "^10.2.24",
|
"@use-gesture/react": "^10.2.24",
|
||||||
"aws-sdk": "2.1304.0",
|
"aws-sdk": "2.1304.0",
|
||||||
"bot-engine": "workspace:*",
|
|
||||||
"browser-image-compression": "2.0.0",
|
"browser-image-compression": "2.0.0",
|
||||||
"canvas-confetti": "1.6.0",
|
"canvas-confetti": "1.6.0",
|
||||||
"chakra-react-select": "^4.4.3",
|
"chakra-react-select": "^4.4.3",
|
||||||
"codemirror": "6.0.1",
|
"codemirror": "6.0.1",
|
||||||
"@paralleldrive/cuid2": "2.0.1",
|
"@paralleldrive/cuid2": "2.0.1",
|
||||||
|
"@typebot.io/js": "workspace:*",
|
||||||
|
"@typebot.io/react": "workspace:*",
|
||||||
|
"@uiw/codemirror-extensions-langs": "^4.19.7",
|
||||||
|
"@uiw/codemirror-theme-github": "^4.19.7",
|
||||||
|
"@uiw/codemirror-theme-tokyo-night": "^4.19.7",
|
||||||
|
"@uiw/react-codemirror": "^4.19.7",
|
||||||
"deep-object-diff": "1.1.9",
|
"deep-object-diff": "1.1.9",
|
||||||
"dequal": "2.0.3",
|
"dequal": "2.0.3",
|
||||||
"emails": "workspace:*",
|
"emails": "workspace:*",
|
||||||
@@ -94,7 +92,6 @@
|
|||||||
"swr": "2.0.3",
|
"swr": "2.0.3",
|
||||||
"tinycolor2": "1.5.2",
|
"tinycolor2": "1.5.2",
|
||||||
"trpc-openapi": "1.1.2",
|
"trpc-openapi": "1.1.2",
|
||||||
"typebot-js": "workspace:*",
|
|
||||||
"use-debounce": "9.0.3"
|
"use-debounce": "9.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -6,21 +6,22 @@
|
|||||||
"folderId": null,
|
"folderId": null,
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"id": "cl1265zct0000mb1a6bir36w7",
|
"id": "clcueadzi00043b6s1r8wnql8",
|
||||||
|
"title": "Start",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1265zct0001mb1afel460do",
|
"id": "cl1265zct0001mb1afel460do",
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"label": "Start",
|
"label": "Start",
|
||||||
"groupId": "cl1265zct0000mb1a6bir36w7",
|
"groupId": "clcueadzi00043b6s1r8wnql8",
|
||||||
"outgoingEdgeId": "cl1266kt100082e6d1wks5dtp"
|
"outgoingEdgeId": "clcuefdfv000r3b6sqzv3prz3"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Start",
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1266bah00032e6dgdnj4vgz",
|
"id": "clcueadzi00063b6sch7b1f32",
|
||||||
|
"title": "Name",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1266bam00042e6dm0gn22vy",
|
"id": "cl1266bam00042e6dm0gn22vy",
|
||||||
@@ -40,47 +41,39 @@
|
|||||||
],
|
],
|
||||||
"logicalOperator": "AND"
|
"logicalOperator": "AND"
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl12bk3j6000c2e69bak89ja9"
|
"outgoingEdgeId": "clcueadzi000l3b6smlf218u7"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
"outgoingEdgeId": "cl12bnfyd000g2e69g7lr3czq"
|
},
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #1",
|
|
||||||
"graphCoordinates": { "x": 266, "y": 162 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl1267q2c000e2e6dynjeg83n",
|
"id": "cl1267q2c000e2e6dynjeg83n",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Welcome 👋</div>",
|
"html": "<div>Welcome 👋</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Welcome 👋" }] }
|
{ "type": "p", "children": [{ "text": "Welcome 👋" }] }
|
||||||
],
|
],
|
||||||
"plainText": "Welcome 👋"
|
"plainText": "Welcome 👋"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1267y1u000f2e6d4rlglv6g",
|
"id": "cl1267y1u000f2e6d4rlglv6g",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>What's your name?</div>",
|
"html": "<div>What's your name?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
||||||
],
|
],
|
||||||
"plainText": "What's your name?"
|
"plainText": "What's your name?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126820m000g2e6dfleq78bt",
|
"id": "cl126820m000g2e6dfleq78bt",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
"groupId": "clcueadzi00063b6sch7b1f32",
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
"isLong": false,
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -88,41 +81,31 @@
|
|||||||
"placeholder": "Type your answer..."
|
"placeholder": "Type your answer..."
|
||||||
},
|
},
|
||||||
"variableId": "cl126f4hf000i2e6d8zvzc3t1"
|
"variableId": "cl126f4hf000i2e6d8zvzc3t1"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1289y1s00142e6dvbkpvbje",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"options": {
|
|
||||||
"name": "Store Name in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeName\", content: {{Name}}}, \"*\")"
|
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl12bk56s000d2e69oll3nqxm"
|
"outgoingEdgeId": "clcuecvjo000q3b6s42ouw3zz"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #3",
|
"graphCoordinates": { "x": 328.22861564828236, "y": -1.7421511890097776 }
|
||||||
"graphCoordinates": { "x": 269, "y": 381 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126ixoq000p2e6dfbz9sype",
|
"id": "clcueadzi00073b6sqz8n5vxp",
|
||||||
|
"title": "Company",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1266v6f000a2e6db7wj3ux7",
|
"id": "cl1266v6f000a2e6db7wj3ux7",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Welcome {{Name}} 👋</div>",
|
"html": "<div>Welcome {{Name}} 👋</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] }
|
{ "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] }
|
||||||
],
|
],
|
||||||
"plainText": "Welcome {{Name}} 👋"
|
"plainText": "Welcome {{Name}} 👋"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126hb9m000l2e6d5qk3mohn",
|
"id": "cl126hb9m000l2e6d5qk3mohn",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>I'm super pumped that you've decided to try out Typebot 😍</div>",
|
"html": "<div>I'm super pumped that you've decided to try out Typebot 😍</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -136,12 +119,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "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 😍"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126hpw1000m2e6dneousygl",
|
"id": "cl126hpw1000m2e6dneousygl",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>You are small steps away from meaningful, hyper-personalized experience for your users</div>",
|
"html": "<div>You are small steps away from meaningful, hyper-personalized experience for your users</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -155,12 +138,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "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"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126guhd000k2e6d6ypkex9z",
|
"id": "cl126guhd000k2e6d6ypkex9z",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Let's get you set up for your Typebot journey.</div>",
|
"html": "<div>Let's get you set up for your Typebot journey.</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -172,12 +155,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "Let's get you set up for your Typebot journey."
|
"plainText": "Let's get you set up for your Typebot journey."
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126ixp9000q2e6dslh0zypi",
|
"id": "cl126ixp9000q2e6dslh0zypi",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Do you work for a specific company?</div>",
|
"html": "<div>Do you work for a specific company?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -187,7 +170,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "Do you work for a specific company?"
|
"plainText": "Do you work for a specific company?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126jb2q000r2e6dgqlnxnt8",
|
"id": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
@@ -197,31 +181,23 @@
|
|||||||
"id": "cl126jb2q000s2e6dm60yq5p2",
|
"id": "cl126jb2q000s2e6dm60yq5p2",
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"content": "Yes",
|
"content": "Yes"
|
||||||
"outgoingEdgeId": "cl126jsoo000x2e6ditu7dgf8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126jc5a000t2e6dqv91w7j6",
|
"id": "cl126jc5a000t2e6dqv91w7j6",
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"content": "No",
|
"content": "No",
|
||||||
"outgoingEdgeId": "cl126l5tx00122e6dmisci6h5"
|
"outgoingEdgeId": "clcueadzi000f3b6sb7lxyeta"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"title": "Group #5",
|
|
||||||
"graphCoordinates": { "x": 614, "y": 244 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126jioj000u2e6dqssno3hv",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl126jioz000v2e6dwrk1f2cb",
|
"id": "cl126jioz000v2e6dwrk1f2cb",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
"isLong": false,
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -229,43 +205,34 @@
|
|||||||
"placeholder": "Type the company name..."
|
"placeholder": "Type the company name..."
|
||||||
},
|
},
|
||||||
"variableId": "cl126jqww000w2e6dq9yv4ifq"
|
"variableId": "cl126jqww000w2e6dq9yv4ifq"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl12890kw00132e6dp9v5dexm",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv",
|
|
||||||
"options": {
|
|
||||||
"name": "Store company in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeCompany\", content: {{Company}}}, \"*\")"
|
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl128ag8i00162e6dufv3tgo0"
|
"outgoingEdgeId": "clcueb0cl000p3b6sisrc741n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #6",
|
"graphCoordinates": { "x": 679.828061917379, "y": 3.351428911218571 }
|
||||||
"graphCoordinates": { "x": 969, "y": 308 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126krbp00102e6dnjelmfa1",
|
"id": "clcueadzi00093b6s82ivles8",
|
||||||
|
"title": "Bot category",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl126krck00112e6d1m6ctxpn",
|
"id": "cl126krck00112e6d1m6ctxpn",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>What type of forms are you planning to build with Typebot?</div>",
|
"html": "<div>What type of bots are you planning to build with Typebot?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
"type": "p",
|
"type": "p",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"text": "What type of forms are you planning to build with Typebot?"
|
"text": "What type of bots are you planning to build with Typebot?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "What type of forms are you planning to build with Typebot?"
|
"plainText": "What type of bots are you planning to build with Typebot?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126lb8v00142e6duv5qe08l",
|
"id": "cl126lb8v00142e6duv5qe08l",
|
||||||
@@ -320,153 +287,13 @@
|
|||||||
"content": "Other"
|
"content": "Other"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
"groupId": "clcueadzi00093b6s82ivles8",
|
||||||
"options": {
|
"options": {
|
||||||
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
||||||
"buttonLabel": "Send",
|
"buttonLabel": "Send",
|
||||||
"isMultipleChoice": true
|
"isMultipleChoice": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "cl128ain900172e6d1osj4u90",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
|
||||||
"options": {
|
|
||||||
"name": "Store categories in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeCategories\", content: {{Categories}}}, \"*\")"
|
|
||||||
},
|
|
||||||
"outgoingEdgeId": "cl128azam00182e6dct61k7v5"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #6",
|
|
||||||
"graphCoordinates": { "x": 1218, "y": 510 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"id": "cl126p76d001k2e6dbhnf2ysq",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"options": {
|
|
||||||
"name": "Shoot confettis",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"shootConfettis\"}, \"*\")"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126rfy6001t2e6d21gcb6b0",
|
|
||||||
"type": "image",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"content": {
|
|
||||||
"url": "https://media4.giphy.com/media/l0amJzVHIAfl7jMDos/giphy.gif?cid=fe3852a3i4c33635xdtj3nesr9uq4zteujaab6b0jr42gpxx&rid=giphy.gif&ct=g"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126txta001y2e6dtxrbsnek",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"type": "Code",
|
|
||||||
"options": {
|
|
||||||
"name": "Go to typebot creation",
|
|
||||||
"content": "setTimeout(() => {window.location.href = \"https://app.typebot.io/typebots/create?isFirstBot=true\"}, 4000)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #7",
|
|
||||||
"graphCoordinates": { "x": 1612, "y": 1103 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"id": "cl127yxym000b2e6d9hksxo6h",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"content": {
|
|
||||||
"html": "<div>What else?</div>",
|
|
||||||
"richText": [
|
|
||||||
{ "type": "p", "children": [{ "text": "What else?" }] }
|
|
||||||
],
|
|
||||||
"plainText": "What else?"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126pv7n001o2e6dajltc4qz",
|
|
||||||
"type": "text input",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"options": {
|
|
||||||
"isLong": false,
|
|
||||||
"labels": { "button": "Send", "placeholder": "Type your answer" },
|
|
||||||
"variableId": "cl126q38p001q2e6d0hj23f6b"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128b34o00192e6dqjxs3cxf",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"options": {
|
|
||||||
"name": "Store Other categories in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeOtherCategories\", content: {{Other categories}}}, \"*\")"
|
|
||||||
},
|
|
||||||
"outgoingEdgeId": "cl128c0fu001a2e6droq69g6z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #8",
|
|
||||||
"graphCoordinates": { "x": 1943, "y": 895 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1278gx9002v2e6d4kf3v89s",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl1278gyk002w2e6d744eb87n",
|
"id": "cl1278gyk002w2e6d744eb87n",
|
||||||
"type": "Condition",
|
"type": "Condition",
|
||||||
@@ -486,15 +313,110 @@
|
|||||||
],
|
],
|
||||||
"logicalOperator": "AND"
|
"logicalOperator": "AND"
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl1278r3b002z2e6d6d6rk9dh"
|
"outgoingEdgeId": "clcueadzi000g3b6sdb6o0xet"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s",
|
"groupId": "clcueadzi00093b6s82ivles8",
|
||||||
"outgoingEdgeId": "cl1278trd00312e6dxmzhcmmn"
|
"outgoingEdgeId": "clcueadzi000h3b6shpxplygo"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #13",
|
"graphCoordinates": { "x": 1030.2081982319628, "y": -0.2818258211374715 }
|
||||||
"graphCoordinates": { "x": 1585, "y": 792 }
|
},
|
||||||
|
{
|
||||||
|
"id": "clcueadzi000a3b6spk404zpz",
|
||||||
|
"title": "Bye",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cl126p76d001k2e6dbhnf2ysq",
|
||||||
|
"type": "text",
|
||||||
|
"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!"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126rfy6001t2e6d21gcb6b0",
|
||||||
|
"type": "image",
|
||||||
|
"content": {
|
||||||
|
"url": "https://media4.giphy.com/media/l0amJzVHIAfl7jMDos/giphy.gif?cid=fe3852a3i4c33635xdtj3nesr9uq4zteujaab6b0jr42gpxx&rid=giphy.gif&ct=g"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126txta001y2e6dtxrbsnek",
|
||||||
|
"type": "text",
|
||||||
|
"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 🤓"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl12buyly00172e6991bz38ch",
|
||||||
|
"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..."
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphCoordinates": { "x": 1585.6402200792238, "y": 219.28927860860924 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"title": "Other category",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cl127yxym000b2e6d9hksxo6h",
|
||||||
|
"type": "text",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>What else?</div>",
|
||||||
|
"richText": [
|
||||||
|
{ "type": "p", "children": [{ "text": "What else?" }] }
|
||||||
|
],
|
||||||
|
"plainText": "What else?"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126pv7n001o2e6dajltc4qz",
|
||||||
|
"type": "text input",
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"options": {
|
||||||
|
"isLong": false,
|
||||||
|
"labels": { "button": "Send", "placeholder": "Type your answer" },
|
||||||
|
"variableId": "cl126q38p001q2e6d0hj23f6b"
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "clcuehrt2000s3b6skmk7rhje"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphCoordinates": { "x": 1369.6844213687823, "y": -7.90789096298402 }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variables": [
|
"variables": [
|
||||||
@@ -505,99 +427,71 @@
|
|||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "cl1266kt100082e6d1wks5dtp",
|
"id": "clcueadzi000f3b6sb7lxyeta",
|
||||||
"to": { "groupId": "cl1266bah00032e6dgdnj4vgz" },
|
"to": { "groupId": "clcueadzi00093b6s82ivles8" },
|
||||||
"from": {
|
|
||||||
"blockId": "cl1265zct0001mb1afel460do",
|
|
||||||
"groupId": "cl1265zct0000mb1a6bir36w7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126jsoo000x2e6ditu7dgf8",
|
|
||||||
"to": { "groupId": "cl126jioj000u2e6dqssno3hv" },
|
|
||||||
"from": {
|
|
||||||
"itemId": "cl126jb2q000s2e6dm60yq5p2",
|
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126l5tx00122e6dmisci6h5",
|
|
||||||
"to": { "groupId": "cl126krbp00102e6dnjelmfa1" },
|
|
||||||
"from": {
|
"from": {
|
||||||
"itemId": "cl126jc5a000t2e6dqv91w7j6",
|
"itemId": "cl126jc5a000t2e6dqv91w7j6",
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype"
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1278r3b002z2e6d6d6rk9dh",
|
"id": "clcueadzi000g3b6sdb6o0xet",
|
||||||
"to": { "groupId": "cl126pv6w001n2e6dp0qkvthu" },
|
"to": { "groupId": "clcueadzi000b3b6sv6936vs4" },
|
||||||
"from": {
|
"from": {
|
||||||
"itemId": "cl1278gyk002x2e6dwmpzs3nf",
|
"itemId": "cl1278gyk002x2e6dwmpzs3nf",
|
||||||
"blockId": "cl1278gyk002w2e6d744eb87n",
|
"blockId": "cl1278gyk002w2e6d744eb87n",
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s"
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1278trd00312e6dxmzhcmmn",
|
"id": "clcueadzi000h3b6shpxplygo",
|
||||||
"to": { "groupId": "cl126p75m001j2e6d73qmes0m" },
|
"to": { "groupId": "clcueadzi000a3b6spk404zpz" },
|
||||||
"from": {
|
"from": {
|
||||||
"blockId": "cl1278gyk002w2e6d744eb87n",
|
"blockId": "cl1278gyk002w2e6d744eb87n",
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s"
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl128ag8i00162e6dufv3tgo0",
|
"id": "clcueadzi000l3b6smlf218u7",
|
||||||
"to": { "groupId": "cl126krbp00102e6dnjelmfa1" },
|
"to": { "groupId": "clcueadzi00073b6sqz8n5vxp" },
|
||||||
"from": {
|
"from": {
|
||||||
"blockId": "cl12890kw00132e6dp9v5dexm",
|
"itemId": "cl1266bam00052e6dn1sdjnax",
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128azam00182e6dct61k7v5",
|
|
||||||
"to": { "groupId": "cl1278gx9002v2e6d4kf3v89s" },
|
|
||||||
"from": {
|
|
||||||
"blockId": "cl128ain900172e6d1osj4u90",
|
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128c0fu001a2e6droq69g6z",
|
|
||||||
"to": { "groupId": "cl126p75m001j2e6d73qmes0m" },
|
|
||||||
"from": {
|
|
||||||
"blockId": "cl128b34o00192e6dqjxs3cxf",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": {
|
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
|
||||||
"blockId": "cl1266bam00042e6dm0gn22vy",
|
"blockId": "cl1266bam00042e6dm0gn22vy",
|
||||||
"itemId": "cl1266bam00052e6dn1sdjnax"
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
}
|
||||||
"to": { "groupId": "cl126ixoq000p2e6dfbz9sype" },
|
|
||||||
"id": "cl12bk3j6000c2e69bak89ja9"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"from": {
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"blockId": "cl1289y1s00142e6dvbkpvbje"
|
"blockId": "cl126jioz000v2e6dwrk1f2cb"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": { "groupId": "clcueadzi00093b6s82ivles8" },
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
"id": "clcueb0cl000p3b6sisrc741n"
|
||||||
"blockId": "cl126hb9m000l2e6d5qk3mohn"
|
|
||||||
},
|
|
||||||
"id": "cl12bk56s000d2e69oll3nqxm"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"from": {
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
"groupId": "clcueadzi00063b6sch7b1f32",
|
||||||
"blockId": "cl1266bam00042e6dm0gn22vy"
|
"blockId": "cl126820m000g2e6dfleq78bt"
|
||||||
},
|
},
|
||||||
"to": { "groupId": "cl1267q1z000d2e6d949f2ge4" },
|
"to": { "groupId": "clcueadzi00073b6sqz8n5vxp" },
|
||||||
"id": "cl12bnfyd000g2e69g7lr3czq"
|
"id": "clcuecvjo000q3b6s42ouw3zz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"groupId": "clcueadzi00043b6s1r8wnql8",
|
||||||
|
"blockId": "cl1265zct0001mb1afel460do"
|
||||||
|
},
|
||||||
|
"to": { "groupId": "clcueadzi00063b6sch7b1f32" },
|
||||||
|
"id": "clcuefdfv000r3b6sqzv3prz3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"blockId": "cl126pv7n001o2e6dajltc4qz"
|
||||||
|
},
|
||||||
|
"to": { "groupId": "clcueadzi000a3b6spk404zpz" },
|
||||||
|
"id": "clcuehrt2000s3b6skmk7rhje"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme": {
|
"theme": {
|
||||||
@@ -632,6 +526,10 @@
|
|||||||
},
|
},
|
||||||
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||||
},
|
},
|
||||||
"publicId": "typebot-onboarding",
|
"publicId": null,
|
||||||
"customDomain": null
|
"customDomain": null,
|
||||||
|
"workspaceId": "proWorkspace",
|
||||||
|
"resultsTablePreferences": null,
|
||||||
|
"isArchived": false,
|
||||||
|
"isClosed": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,22 @@
|
|||||||
"folderId": null,
|
"folderId": null,
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"id": "cl1265zct0000mb1a6bir36w7",
|
"id": "clcueadzi00043b6s1r8wnql8",
|
||||||
|
"title": "Start",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1265zct0001mb1afel460do",
|
"id": "cl1265zct0001mb1afel460do",
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"label": "Start",
|
"label": "Start",
|
||||||
"groupId": "cl1265zct0000mb1a6bir36w7",
|
"groupId": "clcueadzi00043b6s1r8wnql8",
|
||||||
"outgoingEdgeId": "cl1266kt100082e6d1wks5dtp"
|
"outgoingEdgeId": "clcuefdfv000r3b6sqzv3prz3"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Start",
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1266bah00032e6dgdnj4vgz",
|
"id": "clcueadzi00063b6sch7b1f32",
|
||||||
|
"title": "Name",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1266bam00042e6dm0gn22vy",
|
"id": "cl1266bam00042e6dm0gn22vy",
|
||||||
@@ -40,47 +41,39 @@
|
|||||||
],
|
],
|
||||||
"logicalOperator": "AND"
|
"logicalOperator": "AND"
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl12bk3j6000c2e69bak89ja9"
|
"outgoingEdgeId": "clcueadzi000l3b6smlf218u7"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
"outgoingEdgeId": "cl12bnfyd000g2e69g7lr3czq"
|
},
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #1",
|
|
||||||
"graphCoordinates": { "x": 266, "y": 162 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl1267q2c000e2e6dynjeg83n",
|
"id": "cl1267q2c000e2e6dynjeg83n",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Welcome 👋</div>",
|
"html": "<div>Welcome 👋</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Welcome 👋" }] }
|
{ "type": "p", "children": [{ "text": "Welcome 👋" }] }
|
||||||
],
|
],
|
||||||
"plainText": "Welcome 👋"
|
"plainText": "Welcome 👋"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1267y1u000f2e6d4rlglv6g",
|
"id": "cl1267y1u000f2e6d4rlglv6g",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>What's your name?</div>",
|
"html": "<div>What's your name?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
||||||
],
|
],
|
||||||
"plainText": "What's your name?"
|
"plainText": "What's your name?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126820m000g2e6dfleq78bt",
|
"id": "cl126820m000g2e6dfleq78bt",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
"groupId": "clcueadzi00063b6sch7b1f32",
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
"isLong": false,
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -88,41 +81,31 @@
|
|||||||
"placeholder": "Type your answer..."
|
"placeholder": "Type your answer..."
|
||||||
},
|
},
|
||||||
"variableId": "cl126f4hf000i2e6d8zvzc3t1"
|
"variableId": "cl126f4hf000i2e6d8zvzc3t1"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1289y1s00142e6dvbkpvbje",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
|
||||||
"options": {
|
|
||||||
"name": "Store Name in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeName\", content: {{Name}}}, \"*\")"
|
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl12bk56s000d2e69oll3nqxm"
|
"outgoingEdgeId": "clcuecvjo000q3b6s42ouw3zz"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #3",
|
"graphCoordinates": { "x": 328.22861564828236, "y": -1.7421511890097776 }
|
||||||
"graphCoordinates": { "x": 269, "y": 381 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126ixoq000p2e6dfbz9sype",
|
"id": "clcueadzi00073b6sqz8n5vxp",
|
||||||
|
"title": "Company",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl1266v6f000a2e6db7wj3ux7",
|
"id": "cl1266v6f000a2e6db7wj3ux7",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Welcome {{Name}} 👋</div>",
|
"html": "<div>Welcome {{Name}} 👋</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] }
|
{ "type": "p", "children": [{ "text": "Welcome {{Name}} 👋" }] }
|
||||||
],
|
],
|
||||||
"plainText": "Welcome {{Name}} 👋"
|
"plainText": "Welcome {{Name}} 👋"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126hb9m000l2e6d5qk3mohn",
|
"id": "cl126hb9m000l2e6d5qk3mohn",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>I'm super pumped that you've decided to try out Typebot 😍</div>",
|
"html": "<div>I'm super pumped that you've decided to try out Typebot 😍</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -136,12 +119,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "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 😍"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126hpw1000m2e6dneousygl",
|
"id": "cl126hpw1000m2e6dneousygl",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>You are small steps away from meaningful, hyper-personalized experience for your users</div>",
|
"html": "<div>You are small steps away from meaningful, hyper-personalized experience for your users</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -155,12 +138,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "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"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126guhd000k2e6d6ypkex9z",
|
"id": "cl126guhd000k2e6d6ypkex9z",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Let's get you set up for your Typebot journey.</div>",
|
"html": "<div>Let's get you set up for your Typebot journey.</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -172,12 +155,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "Let's get you set up for your Typebot journey."
|
"plainText": "Let's get you set up for your Typebot journey."
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126ixp9000q2e6dslh0zypi",
|
"id": "cl126ixp9000q2e6dslh0zypi",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>Do you work for a specific company?</div>",
|
"html": "<div>Do you work for a specific company?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
@@ -187,7 +170,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "Do you work for a specific company?"
|
"plainText": "Do you work for a specific company?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126jb2q000r2e6dgqlnxnt8",
|
"id": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
@@ -197,31 +181,23 @@
|
|||||||
"id": "cl126jb2q000s2e6dm60yq5p2",
|
"id": "cl126jb2q000s2e6dm60yq5p2",
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"content": "Yes",
|
"content": "Yes"
|
||||||
"outgoingEdgeId": "cl126jsoo000x2e6ditu7dgf8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126jc5a000t2e6dqv91w7j6",
|
"id": "cl126jc5a000t2e6dqv91w7j6",
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"content": "No",
|
"content": "No",
|
||||||
"outgoingEdgeId": "cl126l5tx00122e6dmisci6h5"
|
"outgoingEdgeId": "clcueadzi000f3b6sb7lxyeta"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false }
|
||||||
}
|
},
|
||||||
],
|
|
||||||
"title": "Group #5",
|
|
||||||
"graphCoordinates": { "x": 614, "y": 244 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126jioj000u2e6dqssno3hv",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl126jioz000v2e6dwrk1f2cb",
|
"id": "cl126jioz000v2e6dwrk1f2cb",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
"isLong": false,
|
||||||
"labels": {
|
"labels": {
|
||||||
@@ -229,43 +205,34 @@
|
|||||||
"placeholder": "Type the company name..."
|
"placeholder": "Type the company name..."
|
||||||
},
|
},
|
||||||
"variableId": "cl126jqww000w2e6dq9yv4ifq"
|
"variableId": "cl126jqww000w2e6dq9yv4ifq"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl12890kw00132e6dp9v5dexm",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv",
|
|
||||||
"options": {
|
|
||||||
"name": "Store company in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeCompany\", content: {{Company}}}, \"*\")"
|
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl128ag8i00162e6dufv3tgo0"
|
"outgoingEdgeId": "clcueb0cl000p3b6sisrc741n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #6",
|
"graphCoordinates": { "x": 679.828061917379, "y": 3.351428911218571 }
|
||||||
"graphCoordinates": { "x": 969, "y": 308 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126krbp00102e6dnjelmfa1",
|
"id": "clcueadzi00093b6s82ivles8",
|
||||||
|
"title": "Bot category",
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl126krck00112e6d1m6ctxpn",
|
"id": "cl126krck00112e6d1m6ctxpn",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
|
||||||
"content": {
|
"content": {
|
||||||
"html": "<div>What type of forms are you planning to build with Typebot?</div>",
|
"html": "<div>What type of bots are you planning to build with Typebot?</div>",
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
"type": "p",
|
"type": "p",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"text": "What type of forms are you planning to build with Typebot?"
|
"text": "What type of bots are you planning to build with Typebot?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plainText": "What type of forms are you planning to build with Typebot?"
|
"plainText": "What type of bots are you planning to build with Typebot?"
|
||||||
}
|
},
|
||||||
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl126lb8v00142e6duv5qe08l",
|
"id": "cl126lb8v00142e6duv5qe08l",
|
||||||
@@ -320,153 +287,13 @@
|
|||||||
"content": "Other"
|
"content": "Other"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
"groupId": "clcueadzi00093b6s82ivles8",
|
||||||
"options": {
|
"options": {
|
||||||
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
"variableId": "cl126mo3t001b2e6dvyi16bkd",
|
||||||
"buttonLabel": "Send",
|
"buttonLabel": "Send",
|
||||||
"isMultipleChoice": true
|
"isMultipleChoice": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "cl128ain900172e6d1osj4u90",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1",
|
|
||||||
"options": {
|
|
||||||
"name": "Store categories in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeCategories\", content: {{Categories}}}, \"*\")"
|
|
||||||
},
|
|
||||||
"outgoingEdgeId": "cl128azam00182e6dct61k7v5"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #6",
|
|
||||||
"graphCoordinates": { "x": 1218, "y": 510 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"id": "cl126p76d001k2e6dbhnf2ysq",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"options": {
|
|
||||||
"name": "Shoot confettis",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"shootConfettis\"}, \"*\")"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126rfy6001t2e6d21gcb6b0",
|
|
||||||
"type": "image",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"content": {
|
|
||||||
"url": "https://media4.giphy.com/media/l0amJzVHIAfl7jMDos/giphy.gif?cid=fe3852a3i4c33635xdtj3nesr9uq4zteujaab6b0jr42gpxx&rid=giphy.gif&ct=g"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126txta001y2e6dtxrbsnek",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "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",
|
|
||||||
"groupId": "cl126p75m001j2e6d73qmes0m",
|
|
||||||
"type": "Code",
|
|
||||||
"options": {
|
|
||||||
"name": "Go to typebot creation",
|
|
||||||
"content": "setTimeout(() => {window.location.href = \"https://app.typebot.io/typebots/create?isFirstBot=true\"}, 4000)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #7",
|
|
||||||
"graphCoordinates": { "x": 1612, "y": 1103 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"blocks": [
|
|
||||||
{
|
|
||||||
"id": "cl127yxym000b2e6d9hksxo6h",
|
|
||||||
"type": "text",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"content": {
|
|
||||||
"html": "<div>What else?</div>",
|
|
||||||
"richText": [
|
|
||||||
{ "type": "p", "children": [{ "text": "What else?" }] }
|
|
||||||
],
|
|
||||||
"plainText": "What else?"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126pv7n001o2e6dajltc4qz",
|
|
||||||
"type": "text input",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"options": {
|
|
||||||
"isLong": false,
|
|
||||||
"labels": { "button": "Send", "placeholder": "Type your answer" },
|
|
||||||
"variableId": "cl126q38p001q2e6d0hj23f6b"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128b34o00192e6dqjxs3cxf",
|
|
||||||
"type": "Code",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu",
|
|
||||||
"options": {
|
|
||||||
"name": "Store Other categories in DB",
|
|
||||||
"content": "postMessage({from: \"typebot\", action: \"storeOtherCategories\", content: {{Other categories}}}, \"*\")"
|
|
||||||
},
|
|
||||||
"outgoingEdgeId": "cl128c0fu001a2e6droq69g6z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Group #8",
|
|
||||||
"graphCoordinates": { "x": 1943, "y": 895 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl1278gx9002v2e6d4kf3v89s",
|
|
||||||
"blocks": [
|
|
||||||
{
|
{
|
||||||
"id": "cl1278gyk002w2e6d744eb87n",
|
"id": "cl1278gyk002w2e6d744eb87n",
|
||||||
"type": "Condition",
|
"type": "Condition",
|
||||||
@@ -486,15 +313,110 @@
|
|||||||
],
|
],
|
||||||
"logicalOperator": "AND"
|
"logicalOperator": "AND"
|
||||||
},
|
},
|
||||||
"outgoingEdgeId": "cl1278r3b002z2e6d6d6rk9dh"
|
"outgoingEdgeId": "clcueadzi000g3b6sdb6o0xet"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s",
|
"groupId": "clcueadzi00093b6s82ivles8",
|
||||||
"outgoingEdgeId": "cl1278trd00312e6dxmzhcmmn"
|
"outgoingEdgeId": "clcueadzi000h3b6shpxplygo"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Group #13",
|
"graphCoordinates": { "x": 1030.2081982319628, "y": -0.2818258211374715 }
|
||||||
"graphCoordinates": { "x": 1585, "y": 792 }
|
},
|
||||||
|
{
|
||||||
|
"id": "clcueadzi000a3b6spk404zpz",
|
||||||
|
"title": "Bye",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cl126p76d001k2e6dbhnf2ysq",
|
||||||
|
"type": "text",
|
||||||
|
"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!"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126rfy6001t2e6d21gcb6b0",
|
||||||
|
"type": "image",
|
||||||
|
"content": {
|
||||||
|
"url": "https://media4.giphy.com/media/l0amJzVHIAfl7jMDos/giphy.gif?cid=fe3852a3i4c33635xdtj3nesr9uq4zteujaab6b0jr42gpxx&rid=giphy.gif&ct=g"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126txta001y2e6dtxrbsnek",
|
||||||
|
"type": "text",
|
||||||
|
"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 🤓"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl12buyly00172e6991bz38ch",
|
||||||
|
"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..."
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000a3b6spk404zpz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphCoordinates": { "x": 1585.6402200792238, "y": 219.28927860860924 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"title": "Other category",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "cl127yxym000b2e6d9hksxo6h",
|
||||||
|
"type": "text",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>What else?</div>",
|
||||||
|
"richText": [
|
||||||
|
{ "type": "p", "children": [{ "text": "What else?" }] }
|
||||||
|
],
|
||||||
|
"plainText": "What else?"
|
||||||
|
},
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cl126pv7n001o2e6dajltc4qz",
|
||||||
|
"type": "text input",
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"options": {
|
||||||
|
"isLong": false,
|
||||||
|
"labels": { "button": "Send", "placeholder": "Type your answer" },
|
||||||
|
"variableId": "cl126q38p001q2e6d0hj23f6b"
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "clcuehrt2000s3b6skmk7rhje"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphCoordinates": { "x": 1369.6844213687823, "y": -7.90789096298402 }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variables": [
|
"variables": [
|
||||||
@@ -505,99 +427,71 @@
|
|||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "cl1266kt100082e6d1wks5dtp",
|
"id": "clcueadzi000f3b6sb7lxyeta",
|
||||||
"to": { "groupId": "cl1266bah00032e6dgdnj4vgz" },
|
"to": { "groupId": "clcueadzi00093b6s82ivles8" },
|
||||||
"from": {
|
|
||||||
"blockId": "cl1265zct0001mb1afel460do",
|
|
||||||
"groupId": "cl1265zct0000mb1a6bir36w7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126jsoo000x2e6ditu7dgf8",
|
|
||||||
"to": { "groupId": "cl126jioj000u2e6dqssno3hv" },
|
|
||||||
"from": {
|
|
||||||
"itemId": "cl126jb2q000s2e6dm60yq5p2",
|
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl126l5tx00122e6dmisci6h5",
|
|
||||||
"to": { "groupId": "cl126krbp00102e6dnjelmfa1" },
|
|
||||||
"from": {
|
"from": {
|
||||||
"itemId": "cl126jc5a000t2e6dqv91w7j6",
|
"itemId": "cl126jc5a000t2e6dqv91w7j6",
|
||||||
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
"blockId": "cl126jb2q000r2e6dgqlnxnt8",
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype"
|
"groupId": "clcueadzi00073b6sqz8n5vxp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1278r3b002z2e6d6d6rk9dh",
|
"id": "clcueadzi000g3b6sdb6o0xet",
|
||||||
"to": { "groupId": "cl126pv6w001n2e6dp0qkvthu" },
|
"to": { "groupId": "clcueadzi000b3b6sv6936vs4" },
|
||||||
"from": {
|
"from": {
|
||||||
"itemId": "cl1278gyk002x2e6dwmpzs3nf",
|
"itemId": "cl1278gyk002x2e6dwmpzs3nf",
|
||||||
"blockId": "cl1278gyk002w2e6d744eb87n",
|
"blockId": "cl1278gyk002w2e6d744eb87n",
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s"
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl1278trd00312e6dxmzhcmmn",
|
"id": "clcueadzi000h3b6shpxplygo",
|
||||||
"to": { "groupId": "cl126p75m001j2e6d73qmes0m" },
|
"to": { "groupId": "clcueadzi000a3b6spk404zpz" },
|
||||||
"from": {
|
"from": {
|
||||||
"blockId": "cl1278gyk002w2e6d744eb87n",
|
"blockId": "cl1278gyk002w2e6d744eb87n",
|
||||||
"groupId": "cl1278gx9002v2e6d4kf3v89s"
|
"groupId": "clcueadzi00093b6s82ivles8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl128ag8i00162e6dufv3tgo0",
|
"id": "clcueadzi000l3b6smlf218u7",
|
||||||
"to": { "groupId": "cl126krbp00102e6dnjelmfa1" },
|
"to": { "groupId": "clcueadzi00073b6sqz8n5vxp" },
|
||||||
"from": {
|
"from": {
|
||||||
"blockId": "cl12890kw00132e6dp9v5dexm",
|
"itemId": "cl1266bam00052e6dn1sdjnax",
|
||||||
"groupId": "cl126jioj000u2e6dqssno3hv"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128azam00182e6dct61k7v5",
|
|
||||||
"to": { "groupId": "cl1278gx9002v2e6d4kf3v89s" },
|
|
||||||
"from": {
|
|
||||||
"blockId": "cl128ain900172e6d1osj4u90",
|
|
||||||
"groupId": "cl126krbp00102e6dnjelmfa1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cl128c0fu001a2e6droq69g6z",
|
|
||||||
"to": { "groupId": "cl126p75m001j2e6d73qmes0m" },
|
|
||||||
"from": {
|
|
||||||
"blockId": "cl128b34o00192e6dqjxs3cxf",
|
|
||||||
"groupId": "cl126pv6w001n2e6dp0qkvthu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"from": {
|
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
|
||||||
"blockId": "cl1266bam00042e6dm0gn22vy",
|
"blockId": "cl1266bam00042e6dm0gn22vy",
|
||||||
"itemId": "cl1266bam00052e6dn1sdjnax"
|
"groupId": "clcueadzi00063b6sch7b1f32"
|
||||||
},
|
}
|
||||||
"to": { "groupId": "cl126ixoq000p2e6dfbz9sype" },
|
|
||||||
"id": "cl12bk3j6000c2e69bak89ja9"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"from": {
|
||||||
"groupId": "cl1267q1z000d2e6d949f2ge4",
|
"groupId": "clcueadzi00073b6sqz8n5vxp",
|
||||||
"blockId": "cl1289y1s00142e6dvbkpvbje"
|
"blockId": "cl126jioz000v2e6dwrk1f2cb"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": { "groupId": "clcueadzi00093b6s82ivles8" },
|
||||||
"groupId": "cl126ixoq000p2e6dfbz9sype",
|
"id": "clcueb0cl000p3b6sisrc741n"
|
||||||
"blockId": "cl126hb9m000l2e6d5qk3mohn"
|
|
||||||
},
|
|
||||||
"id": "cl12bk56s000d2e69oll3nqxm"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"from": {
|
||||||
"groupId": "cl1266bah00032e6dgdnj4vgz",
|
"groupId": "clcueadzi00063b6sch7b1f32",
|
||||||
"blockId": "cl1266bam00042e6dm0gn22vy"
|
"blockId": "cl126820m000g2e6dfleq78bt"
|
||||||
},
|
},
|
||||||
"to": { "groupId": "cl1267q1z000d2e6d949f2ge4" },
|
"to": { "groupId": "clcueadzi00073b6sqz8n5vxp" },
|
||||||
"id": "cl12bnfyd000g2e69g7lr3czq"
|
"id": "clcuecvjo000q3b6s42ouw3zz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"groupId": "clcueadzi00043b6s1r8wnql8",
|
||||||
|
"blockId": "cl1265zct0001mb1afel460do"
|
||||||
|
},
|
||||||
|
"to": { "groupId": "clcueadzi00063b6sch7b1f32" },
|
||||||
|
"id": "clcuefdfv000r3b6sqzv3prz3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"groupId": "clcueadzi000b3b6sv6936vs4",
|
||||||
|
"blockId": "cl126pv7n001o2e6dajltc4qz"
|
||||||
|
},
|
||||||
|
"to": { "groupId": "clcueadzi000a3b6spk404zpz" },
|
||||||
|
"id": "clcuehrt2000s3b6skmk7rhje"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme": {
|
"theme": {
|
||||||
@@ -608,12 +502,12 @@
|
|||||||
"placeholderColor": "#9095A0"
|
"placeholderColor": "#9095A0"
|
||||||
},
|
},
|
||||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||||
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
|
||||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" },
|
|
||||||
"hostAvatar": {
|
"hostAvatar": {
|
||||||
"isEnabled": true,
|
"url": "https://avatars.githubusercontent.com/u/16015833?v=4",
|
||||||
"url": "https://s3.eu-west-3.amazonaws.com/typebot/typebots/ckzp7a2za005809lczf2knzix/273013187_1315820332248257_6244778509534754615_n.jpg"
|
"isEnabled": true
|
||||||
}
|
},
|
||||||
|
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||||
|
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"font": "Open Sans",
|
"font": "Open Sans",
|
||||||
@@ -631,6 +525,10 @@
|
|||||||
},
|
},
|
||||||
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||||
},
|
},
|
||||||
"publicId": "typebot-onboarding",
|
"publicId": null,
|
||||||
"customDomain": null
|
"customDomain": null,
|
||||||
|
"workspaceId": "proWorkspace",
|
||||||
|
"resultsTablePreferences": null,
|
||||||
|
"isArchived": false,
|
||||||
|
"isClosed": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"general": {
|
"general": {
|
||||||
"isBrandingEnabled": false,
|
"isBrandingEnabled": true,
|
||||||
"isInputPrefillEnabled": true,
|
"isInputPrefillEnabled": true,
|
||||||
"isHideQueryParamsEnabled": true,
|
"isHideQueryParamsEnabled": true,
|
||||||
"isNewResultOnRefreshEnabled": false
|
"isNewResultOnRefreshEnabled": false
|
||||||
|
|||||||
@@ -337,7 +337,10 @@
|
|||||||
},
|
},
|
||||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||||
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" },
|
||||||
|
"hostAvatar": {
|
||||||
|
"isEnabled": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"font": "Open Sans",
|
"font": "Open Sans",
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
.cm-editor {
|
|
||||||
outline: 0px solid transparent !important;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-scroller {
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,26 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
BoxProps,
|
BoxProps,
|
||||||
|
Fade,
|
||||||
HStack,
|
HStack,
|
||||||
useColorMode,
|
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
|
useDisclosure,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { EditorView, basicSetup } from 'codemirror'
|
import { useRef, useState } from 'react'
|
||||||
import { EditorState } from '@codemirror/state'
|
|
||||||
import { json, jsonParseLinter } from '@codemirror/lang-json'
|
|
||||||
import { css } from '@codemirror/lang-css'
|
|
||||||
import { javascript } from '@codemirror/lang-javascript'
|
|
||||||
import { html } from '@codemirror/lang-html'
|
|
||||||
import { oneDark } from '@codemirror/theme-one-dark'
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { linter, LintSource } from '@codemirror/lint'
|
|
||||||
import { VariablesButton } from '@/features/variables'
|
import { VariablesButton } from '@/features/variables'
|
||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import { env } from 'utils'
|
import { env } from 'utils'
|
||||||
|
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
||||||
const linterExtension = linter(jsonParseLinter() as unknown as LintSource)
|
import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night'
|
||||||
|
import { githubLight } from '@uiw/codemirror-theme-github'
|
||||||
|
import { LanguageName, loadLanguage } from '@uiw/codemirror-extensions-langs'
|
||||||
|
import { isDefined } from '@udecode/plate-common'
|
||||||
|
import { CopyButton } from './CopyButton'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string
|
value?: string
|
||||||
lang?: 'css' | 'json' | 'js' | 'html'
|
defaultValue?: string
|
||||||
|
lang: LanguageName
|
||||||
isReadOnly?: boolean
|
isReadOnly?: boolean
|
||||||
debounceTimeout?: number
|
debounceTimeout?: number
|
||||||
withVariableButton?: boolean
|
withVariableButton?: boolean
|
||||||
@@ -31,7 +28,7 @@ type Props = {
|
|||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
}
|
}
|
||||||
export const CodeEditor = ({
|
export const CodeEditor = ({
|
||||||
value,
|
defaultValue,
|
||||||
lang,
|
lang,
|
||||||
onChange,
|
onChange,
|
||||||
height = '250px',
|
height = '250px',
|
||||||
@@ -40,91 +37,25 @@ export const CodeEditor = ({
|
|||||||
debounceTimeout = 1000,
|
debounceTimeout = 1000,
|
||||||
...props
|
...props
|
||||||
}: Props & Omit<BoxProps, 'onChange'>) => {
|
}: Props & Omit<BoxProps, 'onChange'>) => {
|
||||||
const isDark = useColorMode().colorMode === 'dark'
|
const theme = useColorModeValue(githubLight, tokyoNight)
|
||||||
const editorContainer = useRef<HTMLDivElement | null>(null)
|
const codeEditor = useRef<ReactCodeMirrorRef | null>(null)
|
||||||
const editorView = useRef<EditorView | null>(null)
|
|
||||||
const [, setPlainTextValue] = useState(value)
|
|
||||||
const [carretPosition, setCarretPosition] = useState<number>(0)
|
const [carretPosition, setCarretPosition] = useState<number>(0)
|
||||||
const isVariableButtonDisplayed = withVariableButton && !isReadOnly
|
const isVariableButtonDisplayed = withVariableButton && !isReadOnly
|
||||||
|
const [value, _setValue] = useState(defaultValue ?? '')
|
||||||
|
const { onOpen, onClose, isOpen } = useDisclosure()
|
||||||
|
|
||||||
const debounced = useDebouncedCallback(
|
const setValue = useDebouncedCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
setPlainTextValue(value)
|
_setValue(value)
|
||||||
onChange && onChange(value)
|
onChange && onChange(value)
|
||||||
},
|
},
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
debounced.flush()
|
|
||||||
},
|
|
||||||
[debounced]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!editorView.current || !isReadOnly) return
|
|
||||||
editorView.current.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: 0,
|
|
||||||
to: editorView.current.state.doc.length,
|
|
||||||
insert: value,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const editor = initEditor(value)
|
|
||||||
return () => {
|
|
||||||
editor?.destroy()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const initEditor = (value: string) => {
|
|
||||||
if (!editorContainer.current) return
|
|
||||||
const updateListenerExtension = EditorView.updateListener.of((update) => {
|
|
||||||
if (update.docChanged && onChange)
|
|
||||||
debounced(update.state.doc.toJSON().join('\n'))
|
|
||||||
})
|
|
||||||
const extensions = [
|
|
||||||
updateListenerExtension,
|
|
||||||
basicSetup,
|
|
||||||
EditorState.readOnly.of(isReadOnly),
|
|
||||||
]
|
|
||||||
if (isDark) extensions.push(oneDark)
|
|
||||||
if (lang === 'json') {
|
|
||||||
extensions.push(json())
|
|
||||||
extensions.push(linterExtension)
|
|
||||||
}
|
|
||||||
if (lang === 'css') extensions.push(css())
|
|
||||||
if (lang === 'js') extensions.push(javascript())
|
|
||||||
if (lang === 'html') extensions.push(html())
|
|
||||||
extensions.push(
|
|
||||||
EditorView.theme({
|
|
||||||
'&': { maxHeight: '500px' },
|
|
||||||
'.cm-gutter,.cm-content': { minHeight: isReadOnly ? '0' : height },
|
|
||||||
'.cm-scroller': { overflow: 'auto' },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const editor = new EditorView({
|
|
||||||
state: EditorState.create({
|
|
||||||
extensions,
|
|
||||||
}),
|
|
||||||
parent: editorContainer.current,
|
|
||||||
})
|
|
||||||
editor.dispatch({
|
|
||||||
changes: { from: 0, insert: value },
|
|
||||||
})
|
|
||||||
editorView.current = editor
|
|
||||||
return editor
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleVariableSelected = (variable?: Pick<Variable, 'id' | 'name'>) => {
|
const handleVariableSelected = (variable?: Pick<Variable, 'id' | 'name'>) => {
|
||||||
editorView.current?.focus()
|
codeEditor.current?.view?.focus()
|
||||||
const insert = `{{${variable?.name}}}`
|
const insert = `{{${variable?.name}}}`
|
||||||
editorView.current?.dispatch({
|
codeEditor.current?.view?.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: carretPosition,
|
from: carretPosition,
|
||||||
insert,
|
insert,
|
||||||
@@ -133,9 +64,10 @@ export const CodeEditor = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyUp = () => {
|
const handleChange = (newValue: string) => {
|
||||||
if (!editorContainer.current) return
|
if (isDefined(props.value)) return
|
||||||
setCarretPosition(editorView.current?.state.selection.main.from ?? 0)
|
setValue(newValue)
|
||||||
|
setCarretPosition(codeEditor.current?.state?.selection.main.head ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -143,19 +75,61 @@ export const CodeEditor = ({
|
|||||||
align="flex-end"
|
align="flex-end"
|
||||||
spacing={0}
|
spacing={0}
|
||||||
borderWidth={'1px'}
|
borderWidth={'1px'}
|
||||||
borderRadius="md"
|
rounded="md"
|
||||||
bg={useColorModeValue('#FCFCFC', '#282C34')}
|
bg={useColorModeValue('white', '#1A1B26')}
|
||||||
|
width="full"
|
||||||
|
h="full"
|
||||||
|
pos="relative"
|
||||||
|
onMouseEnter={onOpen}
|
||||||
|
onMouseLeave={onClose}
|
||||||
|
sx={{
|
||||||
|
'& .cm-editor': {
|
||||||
|
maxH: '70vh',
|
||||||
|
outline: '0px solid transparent !important',
|
||||||
|
rounded: 'md',
|
||||||
|
},
|
||||||
|
'& .cm-scroller': {
|
||||||
|
rounded: 'md',
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
'& .cm-gutter,.cm-content': {
|
||||||
|
minH: isReadOnly ? '0' : height,
|
||||||
|
},
|
||||||
|
'& .ͼ1 .cm-scroller': {
|
||||||
|
fontSize: '14px',
|
||||||
|
fontFamily:
|
||||||
|
'JetBrainsMono, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<CodeMirror
|
||||||
w={isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%'}
|
|
||||||
ref={editorContainer}
|
|
||||||
data-testid="code-editor"
|
data-testid="code-editor"
|
||||||
{...props}
|
ref={codeEditor}
|
||||||
onKeyUp={handleKeyUp}
|
value={props.value ?? value}
|
||||||
|
onChange={handleChange}
|
||||||
|
theme={theme}
|
||||||
|
extensions={[loadLanguage(lang)].filter(isDefined)}
|
||||||
|
editable={!isReadOnly}
|
||||||
|
style={{
|
||||||
|
width: isVariableButtonDisplayed ? 'calc(100% - 32px)' : '100%',
|
||||||
|
}}
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
{isVariableButtonDisplayed && (
|
{isVariableButtonDisplayed && (
|
||||||
<VariablesButton onSelectVariable={handleVariableSelected} size="sm" />
|
<VariablesButton onSelectVariable={handleVariableSelected} size="sm" />
|
||||||
)}
|
)}
|
||||||
|
{isReadOnly && (
|
||||||
|
<Fade in={isOpen}>
|
||||||
|
<CopyButton
|
||||||
|
textToCopy={props.value ?? value}
|
||||||
|
pos="absolute"
|
||||||
|
right={0.5}
|
||||||
|
top={0.5}
|
||||||
|
size="xs"
|
||||||
|
colorScheme="blue"
|
||||||
|
/>
|
||||||
|
</Fade>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ const colorsSelection: `#${string}`[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialColor: string
|
initialColor?: string
|
||||||
onColorChange: (color: string) => void
|
onColorChange: (color: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColorPicker = ({ initialColor, onColorChange }: Props) => {
|
export const ColorPicker = ({ initialColor, onColorChange }: Props) => {
|
||||||
const [color, setColor] = useState(initialColor)
|
const [color, setColor] = useState(initialColor ?? '')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onColorChange(color)
|
onColorChange(color)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import Head from 'next/head'
|
|||||||
|
|
||||||
export const Seo = ({
|
export const Seo = ({
|
||||||
title,
|
title,
|
||||||
currentUrl = 'https://app.typebot.io',
|
|
||||||
description = 'Create and publish conversational forms that collect 4 times more answers and feel native to your product',
|
description = 'Create and publish conversational forms that collect 4 times more answers and feel native to your product',
|
||||||
imagePreviewUrl = 'https://app.typebot.io/site-preview.png',
|
imagePreviewUrl = 'https://app.typebot.io/site-preview.png',
|
||||||
}: {
|
}: {
|
||||||
@@ -20,9 +19,6 @@ export const Seo = ({
|
|||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title} />
|
||||||
<meta property="twitter:title" content={title} />
|
<meta property="twitter:title" content={title} />
|
||||||
|
|
||||||
<meta property="twitter:url" content={currentUrl} />
|
|
||||||
<meta property="og:url" content={currentUrl} />
|
|
||||||
|
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta property="twitter:description" content={description} />
|
<meta property="twitter:description" content={description} />
|
||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
|
|||||||
@@ -1,43 +1,35 @@
|
|||||||
import { useTypebot } from '@/features/editor'
|
import { useTypebot } from '@/features/editor'
|
||||||
import { useUser } from '@/features/account'
|
import { useUser } from '@/features/account'
|
||||||
import { useWorkspace } from '@/features/workspace'
|
import { useWorkspace } from '@/features/workspace'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React from 'react'
|
||||||
import { initBubble } from 'typebot-js'
|
import { Bubble } from '@typebot.io/react'
|
||||||
import { isCloudProdInstance } from '@/utils/helpers'
|
|
||||||
import { planToReadable } from '@/features/billing'
|
import { planToReadable } from '@/features/billing'
|
||||||
|
import { isCloudProdInstance } from '@/utils/helpers'
|
||||||
|
|
||||||
export const SupportBubble = () => {
|
export const SupportBubble = () => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const [localTypebotId, setLocalTypebotId] = useState(typebot?.id)
|
|
||||||
const [localUserId, setLocalUserId] = useState(user?.id)
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (!isCloudProdInstance) return null
|
||||||
if (
|
|
||||||
isCloudProdInstance &&
|
|
||||||
(localTypebotId !== typebot?.id || localUserId !== user?.id)
|
|
||||||
) {
|
|
||||||
setLocalTypebotId(typebot?.id)
|
|
||||||
setLocalUserId(user?.id)
|
|
||||||
initBubble({
|
|
||||||
url: `https://viewer.typebot.io/typebot-support`,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
button: {
|
|
||||||
color: '#0042DA',
|
|
||||||
},
|
|
||||||
hiddenVariables: {
|
|
||||||
'User ID': user?.id,
|
|
||||||
'First name': user?.name?.split(' ')[0] ?? undefined,
|
|
||||||
Email: user?.email ?? undefined,
|
|
||||||
'Typebot ID': typebot?.id,
|
|
||||||
'Avatar URL': user?.image ?? undefined,
|
|
||||||
Plan: planToReadable(workspace?.plan),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [user, typebot])
|
|
||||||
|
|
||||||
return <></>
|
return (
|
||||||
|
<Bubble
|
||||||
|
apiHost="https://viewer.typebot.io"
|
||||||
|
typebot="typebot-support"
|
||||||
|
prefilledVariables={{
|
||||||
|
'User ID': user?.id,
|
||||||
|
'First name': user?.name?.split(' ')[0] ?? undefined,
|
||||||
|
Email: user?.email ?? undefined,
|
||||||
|
'Typebot ID': typebot?.id,
|
||||||
|
'Avatar URL': user?.image ?? undefined,
|
||||||
|
Plan: planToReadable(workspace?.plan),
|
||||||
|
}}
|
||||||
|
theme={{
|
||||||
|
chatWindow: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const SmartNumberInput = <HasVariable extends boolean>({
|
|||||||
as={HStack}
|
as={HStack}
|
||||||
isRequired={isRequired}
|
isRequired={isRequired}
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
|
width={label ? 'full' : 'auto'}
|
||||||
>
|
>
|
||||||
{label && (
|
{label && (
|
||||||
<FormLabel mb="0" flexShrink={0}>
|
<FormLabel mb="0" flexShrink={0}>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ export const mockedUser: User = {
|
|||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
email: 'user@email.com',
|
email: 'user@email.com',
|
||||||
company: null,
|
company: null,
|
||||||
createdAt: new Date(),
|
createdAt: new Date('2022-01-01'),
|
||||||
emailVerified: null,
|
emailVerified: null,
|
||||||
graphNavigation: 'TRACKPAD',
|
graphNavigation: 'TRACKPAD',
|
||||||
preferredAppAppearance: null,
|
preferredAppAppearance: null,
|
||||||
image: 'https://avatars.githubusercontent.com/u/16015833?v=4',
|
image: 'https://avatars.githubusercontent.com/u/16015833?v=4',
|
||||||
lastActivityAt: new Date(),
|
lastActivityAt: new Date('2022-01-01'),
|
||||||
onboardingCategories: [],
|
onboardingCategories: [],
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date('2022-01-01'),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
|||||||
import { BubbleBlockType, defaultAudioBubbleContent } from 'models'
|
import { BubbleBlockType, defaultAudioBubbleContent } from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
const audioSampleUrl =
|
const audioSampleUrl =
|
||||||
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
|
'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
|
||||||
@@ -34,7 +33,7 @@ test('should work as expected', async ({ page }) => {
|
|||||||
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
|
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
|
||||||
)
|
)
|
||||||
await page.getByRole('button', { name: 'Preview', exact: true }).click()
|
await page.getByRole('button', { name: 'Preview', exact: true }).click()
|
||||||
await expect(typebotViewer(page).locator('audio')).toHaveAttribute(
|
await expect(page.locator('audio')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
|
RegExp(`/public/typebots/${typebotId}/blocks`, 'gm')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { BubbleBlockType, defaultEmbedBubbleContent } from 'models'
|
|||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
const pdfSrc = 'https://www.orimi.com/pdf-test.pdf'
|
const pdfSrc = 'https://www.orimi.com/pdf-test.pdf'
|
||||||
const siteSrc = 'https://app.cal.com/baptistearno/15min'
|
const siteSrc = 'https://app.cal.com/baptistearno/15min'
|
||||||
@@ -47,9 +46,10 @@ test.describe.parallel('Embed bubble block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(page.locator('iframe#embed-bubble-content')).toHaveAttribute(
|
||||||
typebotViewer(page).locator('iframe#embed-bubble-content')
|
'src',
|
||||||
).toHaveAttribute('src', siteSrc)
|
siteSrc
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
|
|||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { BubbleBlockType, defaultImageBubbleContent } from 'models'
|
import { BubbleBlockType, defaultImageBubbleContent } from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
const unsplashImageSrc =
|
const unsplashImageSrc =
|
||||||
@@ -117,10 +116,7 @@ test.describe.parallel('Image bubble block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator('img')).toHaveAttribute(
|
await expect(page.locator('img')).toHaveAttribute('src', unsplashImageSrc)
|
||||||
'src',
|
|
||||||
unsplashImageSrc
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
|
|||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { BubbleBlockType, defaultTextBubbleContent } from 'models'
|
import { BubbleBlockType, defaultTextBubbleContent } from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
test.describe('Text bubble block', () => {
|
test.describe('Text bubble block', () => {
|
||||||
test('rich text features should work', async ({ page }) => {
|
test('rich text features should work', async ({ page }) => {
|
||||||
@@ -51,17 +50,17 @@ test.describe('Text bubble block', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'Create test' }).click()
|
await page.getByRole('menuitem', { name: 'Create test' }).click()
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
|
await expect(page.locator('span.slate-bold >> nth=0')).toHaveText(
|
||||||
|
'Bold text'
|
||||||
|
)
|
||||||
|
await expect(page.locator('span.slate-italic >> nth=0')).toHaveText(
|
||||||
|
'Italic text'
|
||||||
|
)
|
||||||
|
await expect(page.locator('span.slate-underline >> nth=0')).toHaveText(
|
||||||
|
'Underlined text'
|
||||||
|
)
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('span.slate-bold >> nth=0')
|
page.locator('typebot-standard').locator('a[href="https://github.com"]')
|
||||||
).toHaveText('Bold text')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('span.slate-italic >> nth=0')
|
|
||||||
).toHaveText('Italic text')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('span.slate-underline >> nth=0')
|
|
||||||
).toHaveText('Underlined text')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('a[href="https://github.com"]')
|
|
||||||
).toHaveText('My super link')
|
).toHaveText('My super link')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
VideoBubbleContentType,
|
VideoBubbleContentType,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
|
|
||||||
const videoSrc =
|
const videoSrc =
|
||||||
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
|
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4'
|
||||||
@@ -57,9 +56,10 @@ test.describe.parallel('Video bubble block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(page.locator('video > source').nth(1)).toHaveAttribute(
|
||||||
typebotViewer(page).locator('video > source')
|
'src',
|
||||||
).toHaveAttribute('src', videoSrc)
|
videoSrc
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should display youtube video correctly', async ({ page }) => {
|
test('should display youtube video correctly', async ({ page }) => {
|
||||||
@@ -80,7 +80,7 @@ test.describe.parallel('Video bubble block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
|
await expect(page.locator('iframe').nth(1)).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
'https://www.youtube.com/embed/dQw4w9WgXcQ'
|
'https://www.youtube.com/embed/dQw4w9WgXcQ'
|
||||||
)
|
)
|
||||||
@@ -104,7 +104,7 @@ test.describe.parallel('Video bubble block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator('iframe')).toHaveAttribute(
|
await expect(page.locator('iframe').nth(1)).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
'https://player.vimeo.com/video/649301125'
|
'https://player.vimeo.com/video/649301125'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultChoiceInputOptions, InputBlockType, ItemType } from 'models'
|
import { defaultChoiceInputOptions, InputBlockType, ItemType } from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
test.describe.parallel('Buttons input block', () => {
|
test.describe.parallel('Buttons input block', () => {
|
||||||
@@ -42,10 +41,10 @@ test.describe.parallel('Buttons input block', () => {
|
|||||||
await expect(page.locator('text=Item 2')).toBeHidden()
|
await expect(page.locator('text=Item 2')).toBeHidden()
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
const item3Button = typebotViewer(page).locator('button >> text=Item 3')
|
const item3Button = page.locator('button >> text=Item 3')
|
||||||
await item3Button.click()
|
await item3Button.click()
|
||||||
await expect(item3Button).toBeHidden()
|
await expect(item3Button).toBeHidden()
|
||||||
await expect(typebotViewer(page).locator('text=Item 3')).toBeVisible()
|
await expect(page.getByTestId('guest-bubble')).toHaveText('Item 3')
|
||||||
await page.click('button[aria-label="Close"]')
|
await page.click('button[aria-label="Close"]')
|
||||||
|
|
||||||
await page.click('[data-testid="block2-icon"]')
|
await page.click('[data-testid="block2-icon"]')
|
||||||
@@ -64,13 +63,11 @@ test.describe.parallel('Buttons input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
|
|
||||||
await typebotViewer(page).locator('button >> text="Item 3"').click()
|
await page.locator('button >> text="Item 3"').click()
|
||||||
await typebotViewer(page).locator('button >> text="Item 1"').click()
|
await page.locator('button >> text="Item 1"').click()
|
||||||
await typebotViewer(page).locator('text=Go').click()
|
await page.locator('text=Go').click()
|
||||||
|
|
||||||
await expect(
|
await expect(page.locator('text="Item 3, Item 1"')).toBeVisible()
|
||||||
typebotViewer(page).locator('text="Item 3, Item 1"')
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -85,18 +82,18 @@ test('Variable buttons should work', async ({ page }) => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('text=Variable item').click()
|
await page.getByRole('button', { name: 'Variable item' }).click()
|
||||||
await expect(typebotViewer(page).locator('text=Variable item')).toBeVisible()
|
await expect(page.getByTestId('guest-bubble')).toHaveText('Variable item')
|
||||||
await expect(typebotViewer(page).locator('text=Ok great!')).toBeVisible()
|
await expect(page.locator('text=Ok great!')).toBeVisible()
|
||||||
await page.click('text="Item 1"')
|
await page.click('text="Item 1"')
|
||||||
await page.fill('input[value="Item 1"]', '{{Item 2}}')
|
await page.fill('input[value="Item 1"]', '{{Item 2}}')
|
||||||
await page.click('[data-testid="block1-icon"]')
|
await page.click('[data-testid="block1-icon"]')
|
||||||
await page.click('text=Multiple choice?')
|
await page.click('text=Multiple choice?')
|
||||||
await page.click('text="Restart"')
|
await page.click('text="Restart"')
|
||||||
await typebotViewer(page).locator('text="Variable item" >> nth=0').click()
|
await page.getByTestId('button').first().click()
|
||||||
await typebotViewer(page).locator('text="Variable item" >> nth=1').click()
|
await page.getByTestId('button').nth(1).click()
|
||||||
await typebotViewer(page).locator('text="Send"').click()
|
await page.locator('text="Send"').click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text="Variable item, Variable item"')
|
page.locator('text="Variable item, Variable item"')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultDateInputOptions, InputBlockType } from 'models'
|
import { defaultDateInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe('Date input block', () => {
|
test.describe('Date input block', () => {
|
||||||
@@ -21,15 +20,14 @@ test.describe('Date input block', () => {
|
|||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(page.locator('[data-testid="from-date"]')).toHaveAttribute(
|
||||||
typebotViewer(page).locator('[data-testid="from-date"]')
|
'type',
|
||||||
).toHaveAttribute('type', 'date')
|
'date'
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
)
|
||||||
await typebotViewer(page)
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
|
||||||
.locator('[data-testid="from-date"]')
|
await page.locator('[data-testid="from-date"]').fill('2021-01-01')
|
||||||
.fill('2021-01-01')
|
await page.getByRole('button', { name: 'Send' }).click()
|
||||||
await typebotViewer(page).locator(`button`).click()
|
await expect(page.locator('text="01/01/2021"')).toBeVisible()
|
||||||
await expect(typebotViewer(page).locator('text="01/01/2021"')).toBeVisible()
|
|
||||||
|
|
||||||
await page.click(`text=Pick a date...`)
|
await page.click(`text=Pick a date...`)
|
||||||
await page.click('text=Is range?')
|
await page.click('text=Is range?')
|
||||||
@@ -39,23 +37,19 @@ test.describe('Date input block', () => {
|
|||||||
await page.fill('#button', 'Go')
|
await page.fill('#button', 'Go')
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
|
await expect(page.locator(`[data-testid="from-date"]`)).toHaveAttribute(
|
||||||
|
'type',
|
||||||
|
'datetime-local'
|
||||||
|
)
|
||||||
|
await expect(page.locator(`[data-testid="to-date"]`)).toHaveAttribute(
|
||||||
|
'type',
|
||||||
|
'datetime-local'
|
||||||
|
)
|
||||||
|
await page.locator('[data-testid="from-date"]').fill('2021-01-01T11:00')
|
||||||
|
await page.locator('[data-testid="to-date"]').fill('2022-01-01T09:00')
|
||||||
|
await page.getByRole('button', { name: 'Go' }).click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(`[data-testid="from-date"]`)
|
page.locator('text="01/01/2021, 11:00 AM to 01/01/2022, 09:00 AM"')
|
||||||
).toHaveAttribute('type', 'datetime-local')
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator(`[data-testid="to-date"]`)
|
|
||||||
).toHaveAttribute('type', 'datetime-local')
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator('[data-testid="from-date"]')
|
|
||||||
.fill('2021-01-01T11:00')
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator('[data-testid="to-date"]')
|
|
||||||
.fill('2022-01-01T09:00')
|
|
||||||
await typebotViewer(page).locator(`button`).click()
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator(
|
|
||||||
'text="01/01/2021, 11:00 AM to 01/01/2022, 09:00 AM"'
|
|
||||||
)
|
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultEmailInputOptions, InputBlockType } from 'models'
|
import { defaultEmailInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe('Email input block', () => {
|
test.describe('Email input block', () => {
|
||||||
@@ -22,11 +21,11 @@ test.describe('Email input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(
|
page.locator(
|
||||||
`input[placeholder="${defaultEmailInputOptions.labels.placeholder}"]`
|
`input[placeholder="${defaultEmailInputOptions.labels.placeholder}"]`
|
||||||
)
|
)
|
||||||
).toHaveAttribute('type', 'email')
|
).toHaveAttribute('type', 'email')
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
|
||||||
|
|
||||||
await page.click(`text=${defaultEmailInputOptions.labels.placeholder}`)
|
await page.click(`text=${defaultEmailInputOptions.labels.placeholder}`)
|
||||||
await page.fill(
|
await page.fill(
|
||||||
@@ -41,19 +40,13 @@ test.describe('Email input block', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await typebotViewer(page)
|
await page.locator(`input[placeholder="Your email..."]`).fill('test@test')
|
||||||
.locator(`input[placeholder="Your email..."]`)
|
await page.getByRole('button', { name: 'Go' }).click()
|
||||||
.fill('test@test')
|
await expect(page.locator('text=Try again bro')).toBeVisible()
|
||||||
await typebotViewer(page).locator('text=Go').click()
|
await page
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('text=Try again bro')
|
|
||||||
).toBeVisible()
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator(`input[placeholder="Your email..."]`)
|
.locator(`input[placeholder="Your email..."]`)
|
||||||
.fill('test@test.com')
|
.fill('test@test.com')
|
||||||
await typebotViewer(page).locator('text=Go').click()
|
await page.getByRole('button', { name: 'Go' }).click()
|
||||||
await expect(
|
await expect(page.locator('text=test@test.com')).toBeVisible()
|
||||||
typebotViewer(page).locator('text=test@test.com')
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
lang="html"
|
lang="html"
|
||||||
onChange={handlePlaceholderLabelChange}
|
onChange={handlePlaceholderLabelChange}
|
||||||
value={options.labels.placeholder}
|
defaultValue={options.labels.placeholder}
|
||||||
height={'100px'}
|
height={'100px'}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultFileInputOptions, InputBlockType } from 'models'
|
import { defaultFileInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { freeWorkspaceId } from 'utils/playwright/databaseSetup'
|
import { freeWorkspaceId } from 'utils/playwright/databaseSetup'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -24,14 +23,12 @@ test('options should work', async ({ page }) => {
|
|||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(page.locator(`text=Click to upload`)).toBeVisible()
|
||||||
typebotViewer(page).locator(`text=Click to upload`)
|
await expect(page.locator(`text="Skip"`)).toBeHidden()
|
||||||
).toBeVisible()
|
await page
|
||||||
await expect(typebotViewer(page).locator(`text="Skip"`)).toBeHidden()
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator(`input[type="file"]`)
|
.locator(`input[type="file"]`)
|
||||||
.setInputFiles([getTestAsset('avatar.jpg')])
|
.setInputFiles([getTestAsset('avatar.jpg')])
|
||||||
await expect(typebotViewer(page).locator(`text=File uploaded`)).toBeVisible()
|
await expect(page.locator(`text=File uploaded`)).toBeVisible()
|
||||||
await page.click('text="Collect file"')
|
await page.click('text="Collect file"')
|
||||||
await page.click('text="Required?"')
|
await page.click('text="Required?"')
|
||||||
await page.click('text="Allow multiple files?"')
|
await page.click('text="Allow multiple files?"')
|
||||||
@@ -41,20 +38,18 @@ test('options should work', async ({ page }) => {
|
|||||||
await page.fill('[value="Skip"]', 'Pass')
|
await page.fill('[value="Skip"]', 'Pass')
|
||||||
await page.fill('input[value="10"]', '20')
|
await page.fill('input[value="10"]', '20')
|
||||||
await page.click('text="Restart"')
|
await page.click('text="Restart"')
|
||||||
await expect(typebotViewer(page).locator(`text="Pass"`)).toBeVisible()
|
await expect(page.locator(`text="Pass"`)).toBeVisible()
|
||||||
await expect(typebotViewer(page).locator(`text="Upload now!!"`)).toBeVisible()
|
await expect(page.locator(`text="Upload now!!"`)).toBeVisible()
|
||||||
await typebotViewer(page)
|
await page
|
||||||
.locator(`input[type="file"]`)
|
.locator(`input[type="file"]`)
|
||||||
.setInputFiles([
|
.setInputFiles([
|
||||||
getTestAsset('avatar.jpg'),
|
getTestAsset('avatar.jpg'),
|
||||||
getTestAsset('avatar.jpg'),
|
getTestAsset('avatar.jpg'),
|
||||||
getTestAsset('avatar.jpg'),
|
getTestAsset('avatar.jpg'),
|
||||||
])
|
])
|
||||||
await expect(typebotViewer(page).locator(`text="3"`)).toBeVisible()
|
await expect(page.locator(`text="3"`)).toBeVisible()
|
||||||
await typebotViewer(page).locator('text="Go"').click()
|
await page.locator('text="Go"').click()
|
||||||
await expect(
|
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
|
||||||
typebotViewer(page).locator(`text="3 files uploaded"`)
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Free workspace', () => {
|
test.describe('Free workspace', () => {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultNumberInputOptions, InputBlockType } from 'models'
|
import { defaultNumberInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe('Number input block', () => {
|
test.describe('Number input block', () => {
|
||||||
@@ -22,11 +21,11 @@ test.describe('Number input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(
|
page.locator(
|
||||||
`input[placeholder="${defaultNumberInputOptions.labels.placeholder}"]`
|
`input[placeholder="${defaultNumberInputOptions.labels.placeholder}"]`
|
||||||
)
|
)
|
||||||
).toHaveAttribute('type', 'number')
|
).toHaveAttribute('type', 'number')
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
|
||||||
|
|
||||||
await page.click(`text=${defaultNumberInputOptions.labels.placeholder}`)
|
await page.click(`text=${defaultNumberInputOptions.labels.placeholder}`)
|
||||||
await page.fill('#placeholder', 'Your number...')
|
await page.fill('#placeholder', 'Your number...')
|
||||||
@@ -37,15 +36,13 @@ test.describe('Number input block', () => {
|
|||||||
await page.fill('[role="spinbutton"] >> nth=2', '10')
|
await page.fill('[role="spinbutton"] >> nth=2', '10')
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
const input = typebotViewer(page).locator(
|
const input = page.locator(`input[placeholder="Your number..."]`)
|
||||||
`input[placeholder="Your number..."]`
|
|
||||||
)
|
|
||||||
await input.fill('-1')
|
await input.fill('-1')
|
||||||
await input.press('Enter')
|
await input.press('Enter')
|
||||||
await input.fill('150')
|
await input.fill('150')
|
||||||
await input.press('Enter')
|
await input.press('Enter')
|
||||||
await input.fill('50')
|
await input.fill('50')
|
||||||
await input.press('Enter')
|
await input.press('Enter')
|
||||||
await expect(typebotViewer(page).locator('text=50')).toBeVisible()
|
await expect(page.locator('text=50')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { createTypebots } from 'utils/playwright/databaseActions'
|
|||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultPaymentInputOptions, InputBlockType } from 'models'
|
import { defaultPaymentInputOptions, InputBlockType } from 'models'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { stripePaymentForm } from '@/test/utils/selectorUtils'
|
import { stripePaymentForm } from '@/test/utils/selectorUtils'
|
||||||
|
|
||||||
test.describe('Payment input block', () => {
|
test.describe('Payment input block', () => {
|
||||||
@@ -59,9 +58,9 @@ test.describe('Payment input block', () => {
|
|||||||
.locator(`[placeholder="MM / YY"]`)
|
.locator(`[placeholder="MM / YY"]`)
|
||||||
.fill('12 / 25')
|
.fill('12 / 25')
|
||||||
await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240')
|
await stripePaymentForm(page).locator(`[placeholder="CVC"]`).fill('240')
|
||||||
await typebotViewer(page).locator(`text="Pay 30€"`).click()
|
await page.locator(`text="Pay 30€"`).click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(`text="Your card has been declined."`)
|
page.locator(`text="Your card has been declined."`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await stripePaymentForm(page)
|
await stripePaymentForm(page)
|
||||||
.locator(`[placeholder="1234 1234 1234 1234"]`)
|
.locator(`[placeholder="1234 1234 1234 1234"]`)
|
||||||
@@ -69,7 +68,7 @@ test.describe('Payment input block', () => {
|
|||||||
const zipInput = stripePaymentForm(page).getByPlaceholder('90210')
|
const zipInput = stripePaymentForm(page).getByPlaceholder('90210')
|
||||||
const isZipInputVisible = await zipInput.isVisible()
|
const isZipInputVisible = await zipInput.isVisible()
|
||||||
if (isZipInputVisible) await zipInput.fill('12345')
|
if (isZipInputVisible) await zipInput.fill('12345')
|
||||||
await typebotViewer(page).locator(`text="Pay 30€"`).click()
|
await page.locator(`text="Pay 30€"`).click()
|
||||||
await expect(typebotViewer(page).locator(`text="Success"`)).toBeVisible()
|
await expect(page.locator(`text="Success"`)).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultPhoneInputOptions, InputBlockType } from 'models'
|
import { defaultPhoneInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe('Phone input block', () => {
|
test.describe('Phone input block', () => {
|
||||||
@@ -22,11 +21,11 @@ test.describe('Phone input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(
|
page.locator(
|
||||||
`input[placeholder="${defaultPhoneInputOptions.labels.placeholder}"]`
|
`input[placeholder="${defaultPhoneInputOptions.labels.placeholder}"]`
|
||||||
)
|
)
|
||||||
).toHaveAttribute('type', 'tel')
|
).toHaveAttribute('type', 'tel')
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
|
||||||
|
|
||||||
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
|
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
|
||||||
await page.fill('#placeholder', '+33 XX XX XX XX')
|
await page.fill('#placeholder', '+33 XX XX XX XX')
|
||||||
@@ -37,21 +36,14 @@ test.describe('Phone input block', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await typebotViewer(page)
|
await page.locator(`input[placeholder="+33 XX XX XX XX"]`).type('+33 6 73')
|
||||||
.locator(`input[placeholder="+33 XX XX XX XX"]`)
|
await expect(page.getByRole('combobox')).toHaveText(/🇫🇷.+/)
|
||||||
.fill('+33 6 73')
|
await page.locator('button >> text="Go"').click()
|
||||||
await expect(typebotViewer(page).locator(`img`)).toHaveAttribute(
|
await expect(page.locator('text=Try again bro')).toBeVisible()
|
||||||
'alt',
|
await page
|
||||||
'France'
|
|
||||||
)
|
|
||||||
await typebotViewer(page).locator('button >> text="Go"').click()
|
|
||||||
await expect(
|
|
||||||
typebotViewer(page).locator('text=Try again bro')
|
|
||||||
).toBeVisible()
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator(`input[placeholder="+33 XX XX XX XX"]`)
|
.locator(`input[placeholder="+33 XX XX XX XX"]`)
|
||||||
.fill('+33 6 73 54 45 67')
|
.fill('+33 6 73 54 45 67')
|
||||||
await typebotViewer(page).locator('button >> text="Go"').click()
|
await page.locator('button >> text="Go"').click()
|
||||||
await expect(typebotViewer(page).locator('text=+33673544567')).toBeVisible()
|
await expect(page.locator('text=+33 6 73 54 45 67')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultRatingInputOptions, InputBlockType } from 'models'
|
import { defaultRatingInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
const boxSvg = `<svg
|
const boxSvg = `<svg
|
||||||
@@ -33,10 +32,10 @@ test('options should work', async ({ page }) => {
|
|||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator(`text=Send`)).toBeHidden()
|
await expect(page.locator(`text=Send`)).toBeHidden()
|
||||||
await typebotViewer(page).locator(`text=8`).click()
|
await page.locator(`text=8`).click()
|
||||||
await typebotViewer(page).locator(`text=Send`).click()
|
await page.locator(`text=Send`).click()
|
||||||
await expect(typebotViewer(page).locator(`text=8`)).toBeVisible()
|
await expect(page.locator(`text=8`)).toBeVisible()
|
||||||
await page.click('text=Rate from 0 to 10')
|
await page.click('text=Rate from 0 to 10')
|
||||||
await page.click('text="10"')
|
await page.click('text="10"')
|
||||||
await page.click('text="5"')
|
await page.click('text="5"')
|
||||||
@@ -48,14 +47,10 @@ test('options should work', async ({ page }) => {
|
|||||||
await page.fill('[placeholder="Not likely at all"]', 'Not likely at all')
|
await page.fill('[placeholder="Not likely at all"]', 'Not likely at all')
|
||||||
await page.fill('[placeholder="Extremely likely"]', 'Extremely likely')
|
await page.fill('[placeholder="Extremely likely"]', 'Extremely likely')
|
||||||
await page.click('text="Restart"')
|
await page.click('text="Restart"')
|
||||||
await expect(typebotViewer(page).locator(`text=8`)).toBeHidden()
|
await expect(page.locator(`text=8`)).toBeHidden()
|
||||||
await expect(typebotViewer(page).locator(`text=4`)).toBeHidden()
|
await expect(page.locator(`text=4`)).toBeHidden()
|
||||||
await expect(
|
await expect(page.locator(`text=Not likely at all`)).toBeVisible()
|
||||||
typebotViewer(page).locator(`text=Not likely at all`)
|
await expect(page.locator(`text=Extremely likely`)).toBeVisible()
|
||||||
).toBeVisible()
|
await page.locator(`svg >> nth=4`).click()
|
||||||
await expect(
|
await expect(page.locator(`text=5`)).toBeVisible()
|
||||||
typebotViewer(page).locator(`text=Extremely likely`)
|
|
||||||
).toBeVisible()
|
|
||||||
await typebotViewer(page).locator(`svg >> nth=4`).click()
|
|
||||||
await expect(typebotViewer(page).locator(`text=5`)).toBeVisible()
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultTextInputOptions, InputBlockType } from 'models'
|
import { defaultTextInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe.parallel('Text input block', () => {
|
test.describe.parallel('Text input block', () => {
|
||||||
@@ -22,11 +21,11 @@ test.describe.parallel('Text input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(
|
page.locator(
|
||||||
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
||||||
)
|
)
|
||||||
).toHaveAttribute('type', 'text')
|
).toHaveAttribute('type', 'text')
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Send' })).toBeDisabled()
|
||||||
|
|
||||||
await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
|
await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
|
||||||
await page.fill('#placeholder', 'Your name...')
|
await page.fill('#placeholder', 'Your name...')
|
||||||
@@ -35,8 +34,8 @@ test.describe.parallel('Text input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(`textarea[placeholder="Your name..."]`)
|
page.locator(`textarea[placeholder="Your name..."]`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||||
import { defaultUrlInputOptions, InputBlockType } from 'models'
|
import { defaultUrlInputOptions, InputBlockType } from 'models'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
test.describe('Url input block', () => {
|
test.describe('Url input block', () => {
|
||||||
@@ -22,11 +21,13 @@ test.describe('Url input block', () => {
|
|||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator(
|
page.locator(
|
||||||
`input[placeholder="${defaultUrlInputOptions.labels.placeholder}"]`
|
`input[placeholder="${defaultUrlInputOptions.labels.placeholder}"]`
|
||||||
)
|
)
|
||||||
).toHaveAttribute('type', 'url')
|
).toHaveAttribute('type', 'url')
|
||||||
await expect(typebotViewer(page).locator(`button`)).toBeDisabled()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator(`button`)
|
||||||
|
).toBeDisabled()
|
||||||
|
|
||||||
await page.click(`text=${defaultUrlInputOptions.labels.placeholder}`)
|
await page.click(`text=${defaultUrlInputOptions.labels.placeholder}`)
|
||||||
await page.fill('#placeholder', 'Your URL...')
|
await page.fill('#placeholder', 'Your URL...')
|
||||||
@@ -38,19 +39,15 @@ test.describe('Url input block', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
.locator(`input[placeholder="Your URL..."]`)
|
.locator(`input[placeholder="Your URL..."]`)
|
||||||
.fill('https://https://test')
|
.fill('https://https://test')
|
||||||
await typebotViewer(page).locator('button >> text="Go"').click()
|
await page.locator('button >> text="Go"').click()
|
||||||
await expect(
|
await expect(page.locator('text=Try again bro')).toBeVisible()
|
||||||
typebotViewer(page).locator('text=Try again bro')
|
await page
|
||||||
).toBeVisible()
|
|
||||||
await typebotViewer(page)
|
|
||||||
.locator(`input[placeholder="Your URL..."]`)
|
.locator(`input[placeholder="Your URL..."]`)
|
||||||
.fill('https://website.com')
|
.fill('https://website.com')
|
||||||
await typebotViewer(page).locator('button >> text="Go"').click()
|
await page.locator('button >> text="Go"').click()
|
||||||
await expect(
|
await expect(page.locator('text=https://website.com')).toBeVisible()
|
||||||
typebotViewer(page).locator('text=https://website.com')
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ test.describe('Chatwoot block', () => {
|
|||||||
await page.getByLabel('Phone number').fill('+33654347543')
|
await page.getByLabel('Phone number').fill('+33654347543')
|
||||||
await page.getByRole('button', { name: 'Preview', exact: true }).click()
|
await page.getByRole('button', { name: 'Preview', exact: true }).click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("Chatwoot won't open in preview mode").nth(0)
|
page.getByText('Chatwoot block is not supported in preview').nth(0)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import test, { expect, Page } from '@playwright/test'
|
import test, { expect, Page } from '@playwright/test'
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
@@ -33,25 +32,17 @@ test.describe.parallel('Google sheets integration', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type your email..."]')
|
.locator('input[placeholder="Type your email..."]')
|
||||||
.fill('georges@gmail.com')
|
.fill('georges@gmail.com')
|
||||||
await Promise.all([
|
await page
|
||||||
page.waitForResponse(
|
.locator('typebot-standard')
|
||||||
(resp) =>
|
.locator('input[placeholder="Type your email..."]')
|
||||||
resp
|
.press('Enter')
|
||||||
.request()
|
await expect(
|
||||||
.url()
|
page.getByText('Succesfully inserted row in CRM > Sheet1').nth(0)
|
||||||
.includes(
|
).toBeVisible()
|
||||||
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
|
|
||||||
) &&
|
|
||||||
resp.status() === 200 &&
|
|
||||||
resp.request().method() === 'POST'
|
|
||||||
),
|
|
||||||
typebotViewer(page)
|
|
||||||
.locator('input[placeholder="Type your email..."]')
|
|
||||||
.press('Enter'),
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Update row should work', async ({ page }) => {
|
test('Update row should work', async ({ page }) => {
|
||||||
@@ -82,25 +73,17 @@ test.describe.parallel('Google sheets integration', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type your email..."]')
|
.locator('input[placeholder="Type your email..."]')
|
||||||
.fill('test@test.com')
|
.fill('test@test.com')
|
||||||
await Promise.all([
|
await page
|
||||||
page.waitForResponse(
|
.locator('typebot-standard')
|
||||||
(resp) =>
|
.locator('input[placeholder="Type your email..."]')
|
||||||
resp
|
.press('Enter')
|
||||||
.request()
|
await expect(
|
||||||
.url()
|
page.getByText('Succesfully updated row in CRM > Sheet1').nth(0)
|
||||||
.includes(
|
).toBeVisible()
|
||||||
'/api/integrations/google-sheets/spreadsheets/1k_pIDw3YHl9tlZusbBVSBRY0PeRPd2H6t4Nj7rwnOtM/sheets/0'
|
|
||||||
) &&
|
|
||||||
resp.status() === 200 &&
|
|
||||||
resp.request().method() === 'POST'
|
|
||||||
),
|
|
||||||
typebotViewer(page)
|
|
||||||
.locator('input[placeholder="Type your email..."]')
|
|
||||||
.press('Enter'),
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Get row should work', async ({ page }) => {
|
test('Get row should work', async ({ page }) => {
|
||||||
@@ -143,15 +126,17 @@ test.describe.parallel('Google sheets integration', () => {
|
|||||||
await createNewVar(page, 'Last name')
|
await createNewVar(page, 'Last name')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type your email..."]')
|
.locator('input[placeholder="Type your email..."]')
|
||||||
.fill('test2@test.com')
|
.fill('test2@test.com')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type your email..."]')
|
.locator('input[placeholder="Type your email..."]')
|
||||||
.press('Enter')
|
.press('Enter')
|
||||||
await expect(typebotViewer(page).locator('text=Your name is:')).toHaveText(
|
await expect(
|
||||||
/John|Fred|Georges/
|
page.locator('typebot-standard').locator('text=Your name is:')
|
||||||
)
|
).toHaveText(/John|Fred|Georges/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
{options.isBodyCode ? (
|
{options.isBodyCode ? (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={options.body ?? ''}
|
defaultValue={options.body ?? ''}
|
||||||
onChange={handleBodyChange}
|
onChange={handleBodyChange}
|
||||||
lang="html"
|
lang="html"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ test.describe('Send email block', () => {
|
|||||||
await page.fill('[data-testid="body-input"]', 'Here is my email')
|
await page.fill('[data-testid="body-input"]', 'Here is my email')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('text=Go').click()
|
await page.locator('typebot-standard').locator('text=Go').click()
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('text=Emails are not sent in preview mode >> nth=0')
|
page.locator('text=Emails are not sent in preview mode >> nth=0')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export const WebhookSettings = ({
|
|||||||
/>
|
/>
|
||||||
{(options.isCustomBody ?? true) && (
|
{(options.isCustomBody ?? true) && (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={localWebhook.body ?? ''}
|
defaultValue={localWebhook.body ?? ''}
|
||||||
lang="json"
|
lang="json"
|
||||||
onChange={handleBodyChange}
|
onChange={handleBodyChange}
|
||||||
debounceTimeout={0}
|
debounceTimeout={0}
|
||||||
@@ -262,7 +262,7 @@ export const WebhookSettings = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{testResponse && (
|
{testResponse && (
|
||||||
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
<CodeEditor isReadOnly lang="json" defaultValue={testResponse} />
|
||||||
)}
|
)}
|
||||||
{(testResponse || options?.responseVariableMapping.length > 0) && (
|
{(testResponse || options?.responseVariableMapping.length > 0) && (
|
||||||
<Accordion allowMultiple>
|
<Accordion allowMultiple>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -51,30 +50,33 @@ test.describe('Condition block', () => {
|
|||||||
await page.fill('input[placeholder="Type a value..."]', '20')
|
await page.fill('input[placeholder="Type a value..."]', '20')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type a number..."]')
|
.locator('input[placeholder="Type a number..."]')
|
||||||
.fill('15')
|
.fill('15')
|
||||||
await typebotViewer(page).locator('text=Send').click()
|
await page.locator('typebot-standard').locator('text=Send').click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=You are younger than 20')
|
page.locator('typebot-standard').getByText('You are younger than 20')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type a number..."]')
|
.locator('input[placeholder="Type a number..."]')
|
||||||
.fill('45')
|
.fill('45')
|
||||||
await typebotViewer(page).locator('text=Send').click()
|
await page.locator('typebot-standard').locator('text=Send').click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=You are older than 20')
|
page.locator('typebot-standard').getByText('You are older than 20')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.click('text=Restart')
|
await page.click('text=Restart')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type a number..."]')
|
.locator('input[placeholder="Type a number..."]')
|
||||||
.fill('90')
|
.fill('90')
|
||||||
await typebotViewer(page).locator('text=Send').click()
|
await page.locator('typebot-standard').locator('text=Send').click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=You are older than 80')
|
page.locator('typebot-standard').getByText('You are older than 80')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -20,7 +19,7 @@ test.describe('Redirect block', () => {
|
|||||||
await page.fill('input[placeholder="Type a URL..."]', 'google.com')
|
await page.fill('input[placeholder="Type a URL..."]', 'google.com')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('text=Go to URL').click()
|
await page.locator('typebot-standard').locator('text=Go to URL').click()
|
||||||
await expect(page).toHaveURL('https://www.google.com')
|
await expect(page).toHaveURL('https://www.google.com')
|
||||||
await page.goBack()
|
await page.goBack()
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ test.describe('Redirect block', () => {
|
|||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
const [newPage] = await Promise.all([
|
const [newPage] = await Promise.all([
|
||||||
context.waitForEvent('page'),
|
context.waitForEvent('page'),
|
||||||
typebotViewer(page).locator('text=Go to URL').click(),
|
page.locator('typebot-standard').locator('text=Go to URL').click(),
|
||||||
])
|
])
|
||||||
await newPage.waitForLoadState()
|
await newPage.waitForLoadState()
|
||||||
await expect(newPage).toHaveURL('https://www.google.com')
|
await expect(newPage).toHaveURL('https://www.google.com')
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ export const ScriptSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Text>Code:</Text>
|
<Text>Code:</Text>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={options.content ?? ''}
|
defaultValue={options.content ?? ''}
|
||||||
lang="js"
|
lang="javascript"
|
||||||
onChange={handleCodeChange}
|
onChange={handleCodeChange}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -20,7 +19,7 @@ test.describe('Script block', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('text=Trigger code').click()
|
await page.getByRole('button', { name: 'Trigger code' }).click()
|
||||||
await expect(page).toHaveURL('https://www.google.com')
|
await expect(page).toHaveURL('https://www.google.com')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
|
|
||||||
{options.isCode ?? false ? (
|
{options.isCode ?? false ? (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={options.expressionToEvaluate ?? ''}
|
defaultValue={options.expressionToEvaluate ?? ''}
|
||||||
onChange={handleExpressionChange}
|
onChange={handleExpressionChange}
|
||||||
lang="js"
|
lang="javascript"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -42,18 +41,19 @@ test.describe('Set variable block', () => {
|
|||||||
await page.fill('textarea', '1000 + {{Total}}')
|
await page.fill('textarea', '1000 + {{Total}}')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page)
|
await page
|
||||||
|
.locator('typebot-standard')
|
||||||
.locator('input[placeholder="Type a number..."]')
|
.locator('input[placeholder="Type a number..."]')
|
||||||
.fill('365')
|
.fill('365')
|
||||||
await typebotViewer(page).locator('text=Send').click()
|
await page.locator('typebot-standard').locator('text=Send').click()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=Multiplication: 365000')
|
page.locator('typebot-standard').locator('text=Multiplication: 365000')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=Custom var: Custom value')
|
page.locator('typebot-standard').locator('text=Custom var: Custom value')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text=Addition: 366000')
|
page.locator('typebot-standard').locator('text=Addition: 366000')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -35,7 +34,9 @@ test('should be configurable', async ({ page }) => {
|
|||||||
await page.click('text=Group #2')
|
await page.click('text=Group #2')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator('text=Second block')).toBeVisible()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator('text=Second block')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
await page.click('[aria-label="Close"]')
|
await page.click('[aria-label="Close"]')
|
||||||
await page.click('text=Jump to Group #2 in My link typebot 2')
|
await page.click('text=Jump to Group #2 in My link typebot 2')
|
||||||
@@ -44,9 +45,11 @@ test('should be configurable', async ({ page }) => {
|
|||||||
await page.click('button >> text=Start')
|
await page.click('button >> text=Start')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('input').fill('Hello there!')
|
await page.locator('typebot-standard').locator('input').fill('Hello there!')
|
||||||
await typebotViewer(page).locator('input').press('Enter')
|
await page.locator('typebot-standard').locator('input').press('Enter')
|
||||||
await expect(typebotViewer(page).locator('text=Hello there!')).toBeVisible()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator('text=Hello there!')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
await page.click('[aria-label="Close"]')
|
await page.click('[aria-label="Close"]')
|
||||||
await page.click('text=Jump to Start in My link typebot 2')
|
await page.click('text=Jump to Start in My link typebot 2')
|
||||||
@@ -61,5 +64,7 @@ test('should be configurable', async ({ page }) => {
|
|||||||
await page.click('button >> text=Hello')
|
await page.click('button >> text=Hello')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await expect(typebotViewer(page).locator('text=Hello world')).toBeVisible()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator('text=Hello world')
|
||||||
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
|
||||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
@@ -17,10 +16,14 @@ test.describe('Wait block', () => {
|
|||||||
await page.getByRole('textbox', { name: 'Seconds to wait for:' }).fill('3')
|
await page.getByRole('textbox', { name: 'Seconds to wait for:' }).fill('3')
|
||||||
|
|
||||||
await page.click('text=Preview')
|
await page.click('text=Preview')
|
||||||
await typebotViewer(page).locator('text=Wait now').click()
|
await page.getByRole('button', { name: 'Wait now' }).click()
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeHidden()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator('text="Hi there!"')
|
||||||
|
).toBeHidden()
|
||||||
await page.waitForTimeout(3000)
|
await page.waitForTimeout(3000)
|
||||||
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeVisible()
|
await expect(
|
||||||
|
page.locator('typebot-standard').locator('text="Hi there!"')
|
||||||
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const parseNewTypebot = ({
|
|||||||
return {
|
return {
|
||||||
folderId,
|
folderId,
|
||||||
name,
|
name,
|
||||||
|
version: '3',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
groups: [startGroup],
|
groups: [startGroup],
|
||||||
edges: [],
|
edges: [],
|
||||||
|
|||||||
@@ -1,64 +1,49 @@
|
|||||||
import {
|
import { chakra, useColorModeValue } from '@chakra-ui/react'
|
||||||
chakra,
|
import { Popup } from '@typebot.io/react'
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalContent,
|
|
||||||
ModalOverlay,
|
|
||||||
useColorModeValue,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { TypebotViewer } from 'bot-engine'
|
|
||||||
import { useUser } from '@/features/account'
|
import { useUser } from '@/features/account'
|
||||||
import { AnswerInput, Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { getViewerUrl, sendRequest } from 'utils'
|
import { sendRequest } from 'utils'
|
||||||
import confetti from 'canvas-confetti'
|
import confetti from 'canvas-confetti'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { parseTypebotToPublicTypebot } from '@/features/publish'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
type Props = { totalTypebots: number }
|
type Props = { totalTypebots: number }
|
||||||
|
|
||||||
export const OnboardingModal = ({ totalTypebots }: Props) => {
|
export const OnboardingModal = ({ totalTypebots }: Props) => {
|
||||||
|
const { push } = useRouter()
|
||||||
const botPath = useColorModeValue(
|
const botPath = useColorModeValue(
|
||||||
'/bots/onboarding.json',
|
'/bots/onboarding.json',
|
||||||
'/bots/onboarding-dark.json'
|
'/bots/onboarding-dark.json'
|
||||||
)
|
)
|
||||||
const { user, updateUser } = useUser()
|
const { user, updateUser } = useUser()
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
|
||||||
const [typebot, setTypebot] = useState<Typebot>()
|
const [typebot, setTypebot] = useState<Typebot>()
|
||||||
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
const confettiCanvaContainer = useRef<HTMLCanvasElement | null>(null)
|
||||||
const confettiCanon = useRef<confetti.CreateTypes>()
|
const confettiCanon = useRef<confetti.CreateTypes>()
|
||||||
const [chosenCategories, setChosenCategories] = useState<string[]>([])
|
const [chosenCategories, setChosenCategories] = useState<string[]>([])
|
||||||
const [openedOnce, setOpenedOnce] = useState(false)
|
|
||||||
|
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
|
|
||||||
useEffect(() => {
|
const isNewUser =
|
||||||
fetchTemplate()
|
user &&
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
new Date(user?.createdAt as unknown as string).toDateString() ===
|
||||||
}, [])
|
new Date().toDateString() &&
|
||||||
|
totalTypebots === 0
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (openedOnce) return
|
const fetchTemplate = async () => {
|
||||||
const isNewUser =
|
const { data, error } = await sendRequest(botPath)
|
||||||
user &&
|
if (error)
|
||||||
new Date(user?.createdAt as unknown as string).toDateString() ===
|
return showToast({ title: error.name, description: error.message })
|
||||||
new Date().toDateString() &&
|
setTypebot(data as Typebot)
|
||||||
totalTypebots === 0
|
|
||||||
if (isNewUser) {
|
|
||||||
onOpen()
|
|
||||||
setOpenedOnce(true)
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [user])
|
fetchTemplate()
|
||||||
|
}, [botPath, showToast])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initConfettis()
|
initConfettis()
|
||||||
return () => {
|
}, [])
|
||||||
window.removeEventListener('message', handleIncomingMessage)
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [confettiCanvaContainer.current])
|
|
||||||
|
|
||||||
const initConfettis = () => {
|
const initConfettis = () => {
|
||||||
if (!confettiCanvaContainer.current || confettiCanon.current) return
|
if (!confettiCanvaContainer.current || confettiCanon.current) return
|
||||||
@@ -66,48 +51,41 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
|||||||
resize: true,
|
resize: true,
|
||||||
useWorker: true,
|
useWorker: true,
|
||||||
})
|
})
|
||||||
window.addEventListener('message', handleIncomingMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleIncomingMessage = (message: MessageEvent) => {
|
const handleBotEnd = () => {
|
||||||
if (message.data.from === 'typebot') {
|
setTimeout(() => {
|
||||||
if (message.data.action === 'shootConfettis' && confettiCanon.current)
|
push('/typebots/create', { query: { isFirstBot: true } })
|
||||||
shootConfettis(confettiCanon.current)
|
}, 2000)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchTemplate = async () => {
|
const handleNewAnswer = async (answer: {
|
||||||
const { data, error } = await sendRequest(botPath)
|
message: string
|
||||||
if (error)
|
blockId: string
|
||||||
return showToast({ title: error.name, description: error.message })
|
}) => {
|
||||||
setTypebot(data as Typebot)
|
const isName = answer.blockId === 'cl126820m000g2e6dfleq78bt'
|
||||||
}
|
const isCompany = answer.blockId === 'cl126jioz000v2e6dwrk1f2cb'
|
||||||
|
const isCategories = answer.blockId === 'cl126lb8v00142e6duv5qe08l'
|
||||||
const handleNewAnswer = async (answer: AnswerInput) => {
|
const isOtherCategories = answer.blockId === 'cl126pv7n001o2e6dajltc4qz'
|
||||||
const isName = answer.variableId === 'cl126f4hf000i2e6d8zvzc3t1'
|
const answeredAllQuestions =
|
||||||
const isCompany = answer.variableId === 'cl126jqww000w2e6dq9yv4ifq'
|
isOtherCategories || (isCategories && !answer.message.includes('Other'))
|
||||||
const isCategories = answer.variableId === 'cl126mo3t001b2e6dvyi16bkd'
|
if (answeredAllQuestions && confettiCanon.current)
|
||||||
const isOtherCategories = answer.variableId === 'cl126q38p001q2e6d0hj23f6b'
|
shootConfettis(confettiCanon.current)
|
||||||
if (isName) updateUser({ name: answer.content })
|
if (isName) updateUser({ name: answer.message })
|
||||||
if (isCompany) updateUser({ company: answer.content })
|
if (isCompany) updateUser({ company: answer.message })
|
||||||
if (isCategories) {
|
if (isCategories) {
|
||||||
const onboardingCategories = answer.content.split(', ')
|
const onboardingCategories = answer.message.split(', ')
|
||||||
updateUser({ onboardingCategories })
|
updateUser({ onboardingCategories })
|
||||||
setChosenCategories(onboardingCategories)
|
setChosenCategories(onboardingCategories)
|
||||||
}
|
}
|
||||||
if (isOtherCategories)
|
if (isOtherCategories)
|
||||||
updateUser({
|
updateUser({
|
||||||
onboardingCategories: [...chosenCategories, answer.content],
|
onboardingCategories: [...chosenCategories, answer.message],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
size="3xl"
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
blockScrollOnMount={false}
|
|
||||||
>
|
|
||||||
<chakra.canvas
|
<chakra.canvas
|
||||||
ref={confettiCanvaContainer}
|
ref={confettiCanvaContainer}
|
||||||
pos="fixed"
|
pos="fixed"
|
||||||
@@ -118,23 +96,18 @@ export const OnboardingModal = ({ totalTypebots }: Props) => {
|
|||||||
zIndex={9999}
|
zIndex={9999}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
<ModalOverlay />
|
{typebot && (
|
||||||
<ModalContent h="85vh">
|
<Popup
|
||||||
<ModalBody p="10">
|
typebot={typebot}
|
||||||
{typebot && (
|
prefilledVariables={{
|
||||||
<TypebotViewer
|
Name: user?.name?.split(' ')[0] ?? undefined,
|
||||||
apiHost={getViewerUrl()}
|
}}
|
||||||
typebot={parseTypebotToPublicTypebot(typebot)}
|
defaultOpen={isNewUser}
|
||||||
predefinedVariables={{
|
onAnswer={handleNewAnswer}
|
||||||
Name: user?.name?.split(' ')[0] ?? undefined,
|
onEnd={handleBotEnd}
|
||||||
}}
|
/>
|
||||||
onNewAnswer={handleNewAnswer}
|
)}
|
||||||
style={{ borderRadius: '0.25rem' }}
|
</>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const duplicateTypebot = (
|
|||||||
return {
|
return {
|
||||||
typebot: {
|
typebot: {
|
||||||
...typebot,
|
...typebot,
|
||||||
|
version: '3',
|
||||||
id,
|
id,
|
||||||
name: `${typebot.name} copy`,
|
name: `${typebot.name} copy`,
|
||||||
publicId: null,
|
publicId: null,
|
||||||
|
|||||||
@@ -11,32 +11,25 @@ import {
|
|||||||
UseToastOptions,
|
UseToastOptions,
|
||||||
VStack,
|
VStack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { TypebotViewer } from 'bot-engine'
|
|
||||||
import { useToast } from '@/hooks/useToast'
|
|
||||||
import { useEditor } from '../providers/EditorProvider'
|
import { useEditor } from '../providers/EditorProvider'
|
||||||
import { useGraph } from '@/features/graph'
|
import { useGraph } from '@/features/graph'
|
||||||
import { useTypebot } from '../providers/TypebotProvider'
|
import { useTypebot } from '../providers/TypebotProvider'
|
||||||
import { Log } from 'db'
|
import React, { useState } from 'react'
|
||||||
import React, { useMemo, useState } from 'react'
|
|
||||||
import { getViewerUrl } from 'utils'
|
|
||||||
import { headerHeight } from '../constants'
|
import { headerHeight } from '../constants'
|
||||||
import { parseTypebotToPublicTypebot } from '@/features/publish'
|
import { Standard } from '@typebot.io/react'
|
||||||
|
import { ChatReply } from 'models'
|
||||||
|
import { useToast } from '@/hooks/useToast'
|
||||||
|
|
||||||
export const PreviewDrawer = () => {
|
export const PreviewDrawer = () => {
|
||||||
const isDark = useColorMode().colorMode === 'dark'
|
const isDark = useColorMode().colorMode === 'dark'
|
||||||
const { typebot } = useTypebot()
|
const { typebot, save, isSavingLoading } = useTypebot()
|
||||||
const { setRightPanel, startPreviewAtGroup } = useEditor()
|
const { setRightPanel, startPreviewAtGroup } = useEditor()
|
||||||
const { setPreviewingEdge } = useGraph()
|
const { setPreviewingBlock } = useGraph()
|
||||||
const [isResizing, setIsResizing] = useState(false)
|
const [isResizing, setIsResizing] = useState(false)
|
||||||
const [width, setWidth] = useState(500)
|
const [width, setWidth] = useState(500)
|
||||||
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false)
|
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false)
|
||||||
const [restartKey, setRestartKey] = useState(0)
|
const [restartKey, setRestartKey] = useState(0)
|
||||||
|
|
||||||
const publicTypebot = useMemo(
|
|
||||||
() => (typebot ? { ...parseTypebotToPublicTypebot(typebot) } : undefined),
|
|
||||||
[typebot]
|
|
||||||
)
|
|
||||||
|
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
|
|
||||||
const handleMouseDown = () => {
|
const handleMouseDown = () => {
|
||||||
@@ -54,15 +47,19 @@ export const PreviewDrawer = () => {
|
|||||||
}
|
}
|
||||||
useEventListener('mouseup', handleMouseUp)
|
useEventListener('mouseup', handleMouseUp)
|
||||||
|
|
||||||
const handleRestartClick = () => setRestartKey((key) => key + 1)
|
const handleRestartClick = async () => {
|
||||||
|
await save()
|
||||||
|
setRestartKey((key) => key + 1)
|
||||||
|
}
|
||||||
|
|
||||||
const handleCloseClick = () => {
|
const handleCloseClick = () => {
|
||||||
setPreviewingEdge(undefined)
|
setPreviewingBlock(undefined)
|
||||||
setRightPanel(undefined)
|
setRightPanel(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
|
const handleNewLogs = (logs: ChatReply['logs']) => {
|
||||||
showToast(log as UseToastOptions)
|
logs?.forEach((log) => showToast(log as UseToastOptions))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -92,29 +89,25 @@ export const PreviewDrawer = () => {
|
|||||||
|
|
||||||
<VStack w="full" spacing={4}>
|
<VStack w="full" spacing={4}>
|
||||||
<Flex justifyContent={'space-between'} w="full">
|
<Flex justifyContent={'space-between'} w="full">
|
||||||
<Button onClick={handleRestartClick}>Restart</Button>
|
<Button onClick={handleRestartClick} isLoading={isSavingLoading}>
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
<CloseButton onClick={handleCloseClick} />
|
<CloseButton onClick={handleCloseClick} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{publicTypebot && (
|
{typebot && (
|
||||||
<Flex
|
<Standard
|
||||||
borderWidth={'1px'}
|
|
||||||
borderRadius={'lg'}
|
|
||||||
h="full"
|
|
||||||
w="full"
|
|
||||||
key={restartKey + (startPreviewAtGroup ?? '')}
|
key={restartKey + (startPreviewAtGroup ?? '')}
|
||||||
pointerEvents={isResizing ? 'none' : 'auto'}
|
typebot={typebot}
|
||||||
>
|
startGroupId={startPreviewAtGroup}
|
||||||
<TypebotViewer
|
onNewInputBlock={setPreviewingBlock}
|
||||||
apiHost={getViewerUrl()}
|
onNewLogs={handleNewLogs}
|
||||||
typebot={publicTypebot}
|
style={{
|
||||||
onNewGroupVisible={setPreviewingEdge}
|
borderWidth: '1px',
|
||||||
onNewLog={handleNewLog}
|
borderRadius: '0.25rem',
|
||||||
startGroupId={startPreviewAtGroup}
|
pointerEvents: isResizing ? 'none' : 'auto',
|
||||||
isPreview
|
}}
|
||||||
style={{ borderRadius: '10px' }}
|
/>
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useRouter } from 'next/router'
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { isDefined, isNotDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
import { EditableTypebotName } from './EditableTypebotName'
|
import { EditableTypebotName } from './EditableTypebotName'
|
||||||
import { getBubbleActions } from 'typebot-js'
|
import { open as openSupportBubble } from '@typebot.io/js'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { isCloudProdInstance } from '@/utils/helpers'
|
import { isCloudProdInstance } from '@/utils/helpers'
|
||||||
import { headerHeight } from '../../constants'
|
import { headerHeight } from '../../constants'
|
||||||
@@ -70,7 +70,7 @@ export const TypebotHeader = () => {
|
|||||||
|
|
||||||
const handleHelpClick = () => {
|
const handleHelpClick = () => {
|
||||||
isCloudProdInstance
|
isCloudProdInstance
|
||||||
? getBubbleActions().open()
|
? openSupportBubble()
|
||||||
: window.open('https://docs.typebot.io', '_blank')
|
: window.open('https://docs.typebot.io', '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
importTypebotInDatabase,
|
importTypebotInDatabase,
|
||||||
} from 'utils/playwright/databaseActions'
|
} from 'utils/playwright/databaseActions'
|
||||||
import {
|
import {
|
||||||
typebotViewer,
|
|
||||||
waitForSuccessfulDeleteRequest,
|
waitForSuccessfulDeleteRequest,
|
||||||
waitForSuccessfulPostRequest,
|
waitForSuccessfulPostRequest,
|
||||||
waitForSuccessfulPutRequest,
|
waitForSuccessfulPutRequest,
|
||||||
@@ -200,16 +199,16 @@ test('Preview from group should work', async ({ page }) => {
|
|||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
|
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text="Hello this is group 1"')
|
page.locator('typebot-standard').locator('text="Hello this is group 1"')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
|
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text="Hello this is group 2"')
|
page.locator('typebot-standard').locator('text="Hello this is group 2"')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.click('[aria-label="Close"]')
|
await page.click('[aria-label="Close"]')
|
||||||
await page.click('text="Preview"')
|
await page.click('text="Preview"')
|
||||||
await expect(
|
await expect(
|
||||||
typebotViewer(page).locator('text="Hello this is group 1"')
|
page.locator('typebot-standard').locator('text="Hello this is group 1"')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { DashboardFolder, WorkspaceRole } from 'db'
|
import { DashboardFolder, WorkspaceRole } from 'db'
|
||||||
import { env } from 'utils'
|
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
Wrap,
|
Wrap,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useTypebotDnd } from '../TypebotDndProvider'
|
import { useTypebotDnd } from '../TypebotDndProvider'
|
||||||
import { useUser } from '@/features/account'
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { BackButton } from './BackButton'
|
import { BackButton } from './BackButton'
|
||||||
import { OnboardingModal } from '../../dashboard/components/OnboardingModal'
|
import { OnboardingModal } from '../../dashboard/components/OnboardingModal'
|
||||||
@@ -26,14 +24,12 @@ import { CreateFolderButton } from './CreateFolderButton'
|
|||||||
import { ButtonSkeleton, FolderButton } from './FolderButton'
|
import { ButtonSkeleton, FolderButton } from './FolderButton'
|
||||||
import { TypebotButton } from './TypebotButton'
|
import { TypebotButton } from './TypebotButton'
|
||||||
import { TypebotCardOverlay } from './TypebotButtonOverlay'
|
import { TypebotCardOverlay } from './TypebotButtonOverlay'
|
||||||
import { isCloudProdInstance } from '@/utils/helpers'
|
|
||||||
|
|
||||||
type Props = { folder: DashboardFolder | null }
|
type Props = { folder: DashboardFolder | null }
|
||||||
|
|
||||||
const dragDistanceTolerance = 20
|
const dragDistanceTolerance = 20
|
||||||
|
|
||||||
export const FolderContent = ({ folder }: Props) => {
|
export const FolderContent = ({ folder }: Props) => {
|
||||||
const { user } = useUser()
|
|
||||||
const { workspace, currentRole } = useWorkspace()
|
const { workspace, currentRole } = useWorkspace()
|
||||||
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
|
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
|
||||||
const {
|
const {
|
||||||
@@ -160,14 +156,7 @@ export const FolderContent = ({ folder }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w="full" flex="1" justify="center">
|
<Flex w="full" flex="1" justify="center">
|
||||||
{typebots &&
|
{typebots && <OnboardingModal totalTypebots={typebots.length} />}
|
||||||
!isTypebotLoading &&
|
|
||||||
user &&
|
|
||||||
isCloudProdInstance &&
|
|
||||||
folder === null &&
|
|
||||||
env('E2E_TEST') !== 'true' && (
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const BlockNode = ({
|
|||||||
setFocusedGroupId,
|
setFocusedGroupId,
|
||||||
previewingEdge,
|
previewingEdge,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
|
previewingBlock,
|
||||||
} = useGraph()
|
} = useGraph()
|
||||||
const { mouseOverBlock, setMouseOverBlock } = useBlockDnd()
|
const { mouseOverBlock, setMouseOverBlock } = useBlockDnd()
|
||||||
const { typebot, updateBlock } = useTypebot()
|
const { typebot, updateBlock } = useTypebot()
|
||||||
@@ -76,7 +77,10 @@ export const BlockNode = ({
|
|||||||
)
|
)
|
||||||
const blockRef = useRef<HTMLDivElement | null>(null)
|
const blockRef = useRef<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
const isPreviewing = isConnecting || previewingEdge?.to.blockId === block.id
|
const isPreviewing =
|
||||||
|
isConnecting ||
|
||||||
|
previewingEdge?.to.blockId === block.id ||
|
||||||
|
previewingBlock?.id === block.id
|
||||||
|
|
||||||
const onDrag = (position: NodePosition) => {
|
const onDrag = (position: NodePosition) => {
|
||||||
if (block.type === 'start' || !onMouseDown) return
|
if (block.type === 'start' || !onMouseDown) return
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ const NonMemoizedDraggableGroupNode = ({
|
|||||||
connectingIds,
|
connectingIds,
|
||||||
setConnectingIds,
|
setConnectingIds,
|
||||||
previewingEdge,
|
previewingEdge,
|
||||||
|
previewingBlock,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
focusedGroupId,
|
focusedGroupId,
|
||||||
setFocusedGroupId,
|
setFocusedGroupId,
|
||||||
@@ -91,6 +92,7 @@ const NonMemoizedDraggableGroupNode = ({
|
|||||||
}, [group.title])
|
}, [group.title])
|
||||||
|
|
||||||
const isPreviewing =
|
const isPreviewing =
|
||||||
|
previewingBlock?.groupId === group.id ||
|
||||||
previewingEdge?.from.groupId === group.id ||
|
previewingEdge?.from.groupId === group.id ||
|
||||||
(previewingEdge?.to.groupId === group.id &&
|
(previewingEdge?.to.groupId === group.id &&
|
||||||
isNotDefined(previewingEdge.to.blockId))
|
isNotDefined(previewingEdge.to.blockId))
|
||||||
|
|||||||
@@ -62,11 +62,18 @@ export type Endpoint = {
|
|||||||
|
|
||||||
export type GroupsCoordinates = IdMap<Coordinates>
|
export type GroupsCoordinates = IdMap<Coordinates>
|
||||||
|
|
||||||
|
type PreviewingBlock = {
|
||||||
|
id: string
|
||||||
|
groupId: string
|
||||||
|
}
|
||||||
|
|
||||||
const graphContext = createContext<{
|
const graphContext = createContext<{
|
||||||
graphPosition: Position
|
graphPosition: Position
|
||||||
setGraphPosition: Dispatch<SetStateAction<Position>>
|
setGraphPosition: Dispatch<SetStateAction<Position>>
|
||||||
connectingIds: ConnectingIds | null
|
connectingIds: ConnectingIds | null
|
||||||
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
|
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
|
||||||
|
previewingBlock?: PreviewingBlock
|
||||||
|
setPreviewingBlock: Dispatch<SetStateAction<PreviewingBlock | undefined>>
|
||||||
previewingEdge?: Edge
|
previewingEdge?: Edge
|
||||||
setPreviewingEdge: Dispatch<SetStateAction<Edge | undefined>>
|
setPreviewingEdge: Dispatch<SetStateAction<Edge | undefined>>
|
||||||
sourceEndpoints: IdMap<Endpoint>
|
sourceEndpoints: IdMap<Endpoint>
|
||||||
@@ -99,6 +106,7 @@ export const GraphProvider = ({
|
|||||||
)
|
)
|
||||||
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
|
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
|
||||||
const [previewingEdge, setPreviewingEdge] = useState<Edge>()
|
const [previewingEdge, setPreviewingEdge] = useState<Edge>()
|
||||||
|
const [previewingBlock, setPreviewingBlock] = useState<PreviewingBlock>()
|
||||||
const [sourceEndpoints, setSourceEndpoints] = useState<IdMap<Endpoint>>({})
|
const [sourceEndpoints, setSourceEndpoints] = useState<IdMap<Endpoint>>({})
|
||||||
const [targetEndpoints, setTargetEndpoints] = useState<IdMap<Endpoint>>({})
|
const [targetEndpoints, setTargetEndpoints] = useState<IdMap<Endpoint>>({})
|
||||||
const [openedBlockId, setOpenedBlockId] = useState<string>()
|
const [openedBlockId, setOpenedBlockId] = useState<string>()
|
||||||
@@ -139,6 +147,8 @@ export const GraphProvider = ({
|
|||||||
isReadOnly,
|
isReadOnly,
|
||||||
focusedGroupId,
|
focusedGroupId,
|
||||||
setFocusedGroupId,
|
setFocusedGroupId,
|
||||||
|
setPreviewingBlock,
|
||||||
|
previewingBlock,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
|
useColorModeValue,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
@@ -27,6 +28,7 @@ import { ChangePlanModal, isFreePlan, LimitReached } from '@/features/billing'
|
|||||||
import { timeSince } from '@/utils/helpers'
|
import { timeSince } from '@/utils/helpers'
|
||||||
|
|
||||||
export const PublishButton = (props: ButtonProps) => {
|
export const PublishButton = (props: ButtonProps) => {
|
||||||
|
const warningTextColor = useColorModeValue('red.300', 'red.600')
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const { push, query } = useRouter()
|
const { push, query } = useRouter()
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
@@ -71,12 +73,17 @@ export const PublishButton = (props: ButtonProps) => {
|
|||||||
type={LimitReached.FILE_INPUT}
|
type={LimitReached.FILE_INPUT}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
borderRadius="md"
|
|
||||||
hasArrow
|
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
label={
|
label={
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>There are non published changes.</Text>
|
{!publishedTypebot?.version ? (
|
||||||
|
<Text color={warningTextColor} fontWeight="semibold">
|
||||||
|
This will deploy your bot with an updated engine. Make sure to
|
||||||
|
test it properly in preview mode before publishing.
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Text>There are non published changes.</Text>
|
||||||
|
)}
|
||||||
<Text fontStyle="italic">
|
<Text fontStyle="italic">
|
||||||
Published version from{' '}
|
Published version from{' '}
|
||||||
{publishedTypebot &&
|
{publishedTypebot &&
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
IframeModal,
|
IframeModal,
|
||||||
WixModal,
|
WixModal,
|
||||||
} from './modals'
|
} from './modals'
|
||||||
|
import { OtherModal } from './modals/OtherModal'
|
||||||
|
|
||||||
export type ModalProps = {
|
export type ModalProps = {
|
||||||
publicId: string
|
publicId: string
|
||||||
@@ -139,7 +140,7 @@ export const integrationsList = [
|
|||||||
<EmbedButton
|
<EmbedButton
|
||||||
logo={<OtherLogo height={100} width="70px" />}
|
logo={<OtherLogo height={100} width="70px" />}
|
||||||
label="Other"
|
label="Other"
|
||||||
Modal={JavascriptModal}
|
Modal={OtherModal}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { AlertInfo } from '@/components/AlertInfo'
|
||||||
|
import { ChevronLeftIcon } from '@/components/icons'
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Heading,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalBody,
|
||||||
|
Stack,
|
||||||
|
ModalFooter,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { capitalize } from 'utils'
|
||||||
|
import { EmbedTypeMenu } from './EmbedTypeMenu/EmbedTypeMenu'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
selectedEmbedType: 'standard' | 'popup' | 'bubble' | undefined
|
||||||
|
titlePrefix: string
|
||||||
|
isOpen: boolean
|
||||||
|
isPublished: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
onClose: () => void
|
||||||
|
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble' | undefined) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmbedModal = ({
|
||||||
|
selectedEmbedType,
|
||||||
|
isOpen,
|
||||||
|
isPublished,
|
||||||
|
titlePrefix,
|
||||||
|
children,
|
||||||
|
onSelectEmbedType,
|
||||||
|
onClose,
|
||||||
|
}: Props) => (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
size={!selectedEmbedType ? '2xl' : 'xl'}
|
||||||
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<HStack>
|
||||||
|
{selectedEmbedType && (
|
||||||
|
<IconButton
|
||||||
|
icon={<ChevronLeftIcon />}
|
||||||
|
aria-label="back"
|
||||||
|
variant="ghost"
|
||||||
|
colorScheme="gray"
|
||||||
|
mr={2}
|
||||||
|
onClick={() => onSelectEmbedType(undefined)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Heading size="md">
|
||||||
|
{titlePrefix}{' '}
|
||||||
|
{selectedEmbedType && `- ${capitalize(selectedEmbedType)}`}
|
||||||
|
</Heading>
|
||||||
|
</HStack>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody as={Stack} spacing={4} pt={0}>
|
||||||
|
{!isPublished && (
|
||||||
|
<AlertInfo>You need to publish your bot first.</AlertInfo>
|
||||||
|
)}
|
||||||
|
{!selectedEmbedType ? (
|
||||||
|
<EmbedTypeMenu onSelectEmbedType={onSelectEmbedType} />
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter />
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { MotionStack } from '@/components/MotionStack'
|
||||||
|
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
|
||||||
|
import { BubbleIllustration } from './illustrations/BubbleIllustration'
|
||||||
|
|
||||||
|
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
|
||||||
|
|
||||||
|
export const BubbleMenuButton = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<MotionStack
|
||||||
|
as={Button}
|
||||||
|
fontWeight="normal"
|
||||||
|
alignItems="center"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
whiteSpace={'normal'}
|
||||||
|
spacing="6"
|
||||||
|
flex="1"
|
||||||
|
height="250px"
|
||||||
|
animate="default"
|
||||||
|
whileHover="animateBubbles"
|
||||||
|
transition={{ staggerChildren: 0.1 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<BubbleIllustration />
|
||||||
|
<Stack>
|
||||||
|
<Text fontSize="lg" fontWeight="semibold">
|
||||||
|
Bubble
|
||||||
|
</Text>
|
||||||
|
<Text textColor="gray.500">Embed in a chat bubble</Text>
|
||||||
|
</Stack>
|
||||||
|
</MotionStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { HStack } from '@chakra-ui/react'
|
||||||
|
import { BubbleMenuButton } from './BubbleMenuButton'
|
||||||
|
import { PopupMenuButton } from './PopupMenuButton'
|
||||||
|
import { StandardMenuButton } from './StandardMenuButton'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmbedTypeMenu = ({ onSelectEmbedType }: Props) => {
|
||||||
|
return (
|
||||||
|
<HStack spacing={4}>
|
||||||
|
<StandardMenuButton onClick={() => onSelectEmbedType('standard')} />
|
||||||
|
<PopupMenuButton onClick={() => onSelectEmbedType('popup')} />
|
||||||
|
<BubbleMenuButton onClick={() => onSelectEmbedType('bubble')} />
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { MotionStack } from '@/components/MotionStack'
|
||||||
|
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
|
||||||
|
import { PopupIllustration } from './illustrations/PopupIllustration'
|
||||||
|
|
||||||
|
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
|
||||||
|
|
||||||
|
export const PopupMenuButton = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<MotionStack
|
||||||
|
as={Button}
|
||||||
|
fontWeight="normal"
|
||||||
|
alignItems="center"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
whiteSpace={'normal'}
|
||||||
|
spacing="6"
|
||||||
|
height="250px"
|
||||||
|
flex="1"
|
||||||
|
animate="default"
|
||||||
|
whileHover="animateBubbles"
|
||||||
|
transition={{ staggerChildren: 0.1 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<PopupIllustration />
|
||||||
|
<Stack>
|
||||||
|
<Text fontSize="lg" fontWeight="semibold">
|
||||||
|
Popup
|
||||||
|
</Text>
|
||||||
|
<Text textColor="gray.500">
|
||||||
|
Embed in a popup on top of your website
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</MotionStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { MotionStack } from '@/components/MotionStack'
|
||||||
|
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
|
||||||
|
import { StandardIllustration } from './illustrations/StandardIllustration'
|
||||||
|
|
||||||
|
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
|
||||||
|
|
||||||
|
export const StandardMenuButton = (props: Props) => {
|
||||||
|
return (
|
||||||
|
<MotionStack
|
||||||
|
as={Button}
|
||||||
|
fontWeight="normal"
|
||||||
|
alignItems="center"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
whiteSpace={'normal'}
|
||||||
|
spacing="6"
|
||||||
|
height="250px"
|
||||||
|
flex="1"
|
||||||
|
animate="default"
|
||||||
|
whileHover="animateBubbles"
|
||||||
|
transition={{ staggerChildren: 0.1 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<StandardIllustration />
|
||||||
|
<Stack>
|
||||||
|
<Text fontSize="lg" fontWeight="semibold">
|
||||||
|
Standard
|
||||||
|
</Text>
|
||||||
|
<Text textColor="gray.500">Embed in a container on your site</Text>
|
||||||
|
</Stack>
|
||||||
|
</MotionStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { colors } from '@/lib/theme'
|
||||||
|
import { useColorModeValue } from '@chakra-ui/react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { animationVariants } from './animationVariants'
|
||||||
|
|
||||||
|
export const BubbleIllustration = () => {
|
||||||
|
const bubbleColor = useColorModeValue('white', colors.blue[100])
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="500"
|
||||||
|
height="500"
|
||||||
|
rx="20"
|
||||||
|
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
|
||||||
|
/>
|
||||||
|
<rect x="164" y="59" width="287" height="305" rx="10" fill="#0042DA" />
|
||||||
|
<motion.rect
|
||||||
|
x="227"
|
||||||
|
y="91"
|
||||||
|
width="156"
|
||||||
|
height="34"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="227"
|
||||||
|
y="134"
|
||||||
|
width="156"
|
||||||
|
height="65"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.circle
|
||||||
|
cx="198"
|
||||||
|
cy="228"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="227"
|
||||||
|
y="208"
|
||||||
|
width="156"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.circle
|
||||||
|
cx="412"
|
||||||
|
cy="277"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="253"
|
||||||
|
y="257"
|
||||||
|
width="130"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<circle cx="411" cy="430" r="40" fill="#0042DA" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { colors } from '@/lib/theme'
|
||||||
|
import { useColorModeValue } from '@chakra-ui/react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { animationVariants } from './animationVariants'
|
||||||
|
|
||||||
|
export const PopupIllustration = () => {
|
||||||
|
const bubbleColor = useColorModeValue('white', colors.blue[100])
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="100"
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="500"
|
||||||
|
height="500"
|
||||||
|
rx="20"
|
||||||
|
fill={useColorModeValue(colors.gray['400'], colors.gray['900'])}
|
||||||
|
/>
|
||||||
|
<rect x="105" y="77" width="290" height="352" rx="10" fill="#0042DA" />
|
||||||
|
<motion.rect
|
||||||
|
x="171"
|
||||||
|
y="117"
|
||||||
|
width="156"
|
||||||
|
height="34"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="171"
|
||||||
|
y="160"
|
||||||
|
width="156"
|
||||||
|
height="65"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.circle
|
||||||
|
cx="142"
|
||||||
|
cy="254"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="171"
|
||||||
|
y="234"
|
||||||
|
width="156"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.circle
|
||||||
|
cx="356"
|
||||||
|
cy="303"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x="197"
|
||||||
|
y="283"
|
||||||
|
width="130"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
variants={animationVariants}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { colors } from '@/lib/theme'
|
||||||
|
import { useColorModeValue } from '@chakra-ui/react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { animationVariants } from './animationVariants'
|
||||||
|
|
||||||
|
export const StandardIllustration = () => {
|
||||||
|
const gray = useColorModeValue(colors.gray[400], colors.gray[700])
|
||||||
|
const bubbleColor = useColorModeValue('white', colors.blue[100])
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 500 500"
|
||||||
|
width="100"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
width="500"
|
||||||
|
height="500"
|
||||||
|
rx="20"
|
||||||
|
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
|
||||||
|
/>
|
||||||
|
<rect x="49" y="49" width="108" height="109" rx="10" fill={gray} />
|
||||||
|
<rect x="188" y="74" width="263" height="25" rx="5" fill={gray} />
|
||||||
|
<rect x="188" y="111" width="263" height="25" rx="5" fill={gray} />
|
||||||
|
<rect x="49" y="189" width="402" height="262" rx="10" fill="#0042DA" />
|
||||||
|
|
||||||
|
<motion.rect
|
||||||
|
variants={animationVariants}
|
||||||
|
x="121"
|
||||||
|
y="217"
|
||||||
|
width="218"
|
||||||
|
height="34"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
variants={animationVariants}
|
||||||
|
x="121"
|
||||||
|
y="260"
|
||||||
|
width="218"
|
||||||
|
height="65"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
<motion.circle
|
||||||
|
variants={animationVariants}
|
||||||
|
cx="93"
|
||||||
|
cy="354"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
variants={animationVariants}
|
||||||
|
x="121"
|
||||||
|
y="334"
|
||||||
|
width="218"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
<motion.circle
|
||||||
|
variants={animationVariants}
|
||||||
|
cx="407"
|
||||||
|
cy="410"
|
||||||
|
r="20"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
variants={animationVariants}
|
||||||
|
x="250"
|
||||||
|
y="390"
|
||||||
|
width="130"
|
||||||
|
height="40"
|
||||||
|
rx="10"
|
||||||
|
fill={bubbleColor}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Variants } from 'framer-motion'
|
||||||
|
|
||||||
|
export const animationVariants: Variants = {
|
||||||
|
animateBubbles: {
|
||||||
|
opacity: [0, 1],
|
||||||
|
},
|
||||||
|
default: { opacity: 1 },
|
||||||
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import prettier from 'prettier/standalone'
|
|
||||||
import parserHtml from 'prettier/parser-html'
|
|
||||||
import { BubbleParams } from 'typebot-js'
|
|
||||||
import { parseInitBubbleCode, typebotJsHtml } from '../params'
|
|
||||||
import { useTypebot } from '@/features/editor'
|
|
||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
import { env, getViewerUrl } from 'utils'
|
|
||||||
import { FlexProps } from '@chakra-ui/react'
|
|
||||||
|
|
||||||
type ChatEmbedCodeProps = {
|
|
||||||
withStarterVariables?: boolean
|
|
||||||
onCopied?: () => void
|
|
||||||
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
|
|
||||||
|
|
||||||
export const ChatEmbedCode = ({
|
|
||||||
proactiveMessage,
|
|
||||||
button,
|
|
||||||
}: ChatEmbedCodeProps & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
|
|
||||||
const snippet = prettier.format(
|
|
||||||
createSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
button,
|
|
||||||
proactiveMessage,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'html',
|
|
||||||
plugins: [parserHtml],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
const createSnippet = (params: BubbleParams): string => {
|
|
||||||
const jsCode = parseInitBubbleCode(params)
|
|
||||||
return `${typebotJsHtml}
|
|
||||||
<script>${jsCode}</script>`
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
import {
|
|
||||||
StackProps,
|
|
||||||
Stack,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
Input,
|
|
||||||
Flex,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
NumberInput,
|
|
||||||
NumberInputField,
|
|
||||||
Switch,
|
|
||||||
Text,
|
|
||||||
Image,
|
|
||||||
NumberInputStepper,
|
|
||||||
NumberIncrementStepper,
|
|
||||||
NumberDecrementStepper,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { useTypebot } from '@/features/editor'
|
|
||||||
import { useUser } from '@/features/account'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { BubbleParams } from 'typebot-js'
|
|
||||||
import { ColorPicker } from '@/components/ColorPicker'
|
|
||||||
|
|
||||||
type ChatEmbedSettingsProps = {
|
|
||||||
onUpdateSettings: (
|
|
||||||
windowSettings: Pick<BubbleParams, 'button' | 'proactiveMessage'>
|
|
||||||
) => void
|
|
||||||
}
|
|
||||||
export const ChatEmbedSettings = ({
|
|
||||||
onUpdateSettings,
|
|
||||||
...props
|
|
||||||
}: ChatEmbedSettingsProps & StackProps) => {
|
|
||||||
const { user } = useUser()
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const [proactiveMessageChecked, setProactiveMessageChecked] = useState(false)
|
|
||||||
const [isCustomIconChecked, setIsCustomIconChecked] = useState(false)
|
|
||||||
|
|
||||||
const [rememberProMessageChecked] = useState(true)
|
|
||||||
const [customIconInputValue, setCustomIconInputValue] = useState('')
|
|
||||||
|
|
||||||
const [inputValues, setInputValues] = useState({
|
|
||||||
messageDelay: '0',
|
|
||||||
messageContent: 'I have a question for you!',
|
|
||||||
avatarUrl: typebot?.theme.chat.hostAvatar?.url ?? user?.image ?? '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const [bubbleColor, setBubbleColor] = useState(
|
|
||||||
typebot?.theme.chat.buttons.backgroundColor ?? '#0042DA'
|
|
||||||
)
|
|
||||||
|
|
||||||
const [bubbleIconColor, setIconBubbleColor] = useState(
|
|
||||||
typebot?.theme.chat.buttons.color ?? '#FFFFFF'
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (proactiveMessageChecked) {
|
|
||||||
onUpdateSettings({
|
|
||||||
button: {
|
|
||||||
color: bubbleColor,
|
|
||||||
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
|
|
||||||
iconColor:
|
|
||||||
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
|
|
||||||
},
|
|
||||||
proactiveMessage: {
|
|
||||||
delay: parseInt(inputValues.messageDelay) * 1000,
|
|
||||||
textContent: inputValues.messageContent,
|
|
||||||
avatarUrl: inputValues.avatarUrl,
|
|
||||||
rememberClose: rememberProMessageChecked,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
onUpdateSettings({
|
|
||||||
button: {
|
|
||||||
color: bubbleColor,
|
|
||||||
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
|
|
||||||
iconColor:
|
|
||||||
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
|
|
||||||
},
|
|
||||||
proactiveMessage: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [
|
|
||||||
inputValues,
|
|
||||||
bubbleColor,
|
|
||||||
rememberProMessageChecked,
|
|
||||||
customIconInputValue,
|
|
||||||
bubbleIconColor,
|
|
||||||
proactiveMessageChecked,
|
|
||||||
isCustomIconChecked,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack {...props} spacing="4">
|
|
||||||
<Heading fontSize="md" fontWeight="semibold">
|
|
||||||
Chat bubble settings
|
|
||||||
</Heading>
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
<Text>Button color</Text>
|
|
||||||
<ColorPicker
|
|
||||||
initialColor={bubbleColor}
|
|
||||||
onColorChange={setBubbleColor}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<HStack justify="space-between">
|
|
||||||
<Text>Icon color</Text>
|
|
||||||
<ColorPicker
|
|
||||||
initialColor={bubbleIconColor}
|
|
||||||
onColorChange={setIconBubbleColor}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
<HStack justifyContent="space-between">
|
|
||||||
<FormLabel htmlFor="custom-icon" mb="0" flexShrink={0}>
|
|
||||||
Custom button icon?
|
|
||||||
</FormLabel>
|
|
||||||
<Switch
|
|
||||||
id="custom-icon"
|
|
||||||
onChange={() => setIsCustomIconChecked(!isCustomIconChecked)}
|
|
||||||
isChecked={isCustomIconChecked}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
{isCustomIconChecked && (
|
|
||||||
<>
|
|
||||||
<HStack pl="4">
|
|
||||||
<Text>Url:</Text>
|
|
||||||
<Input
|
|
||||||
placeholder={'Paste image link (.png, .svg)'}
|
|
||||||
value={customIconInputValue}
|
|
||||||
onChange={(e) => setCustomIconInputValue(e.target.value)}
|
|
||||||
minW="0"
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<FormControl
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
w="full"
|
|
||||||
justifyContent="space-between"
|
|
||||||
>
|
|
||||||
<FormLabel htmlFor="fullscreen-option" mb="0">
|
|
||||||
Enable popup message?
|
|
||||||
</FormLabel>
|
|
||||||
<Switch
|
|
||||||
id="fullscreen-option"
|
|
||||||
onChange={() =>
|
|
||||||
setProactiveMessageChecked(!proactiveMessageChecked)
|
|
||||||
}
|
|
||||||
isChecked={proactiveMessageChecked}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Flex>
|
|
||||||
{proactiveMessageChecked && (
|
|
||||||
<>
|
|
||||||
<Flex pl="4">
|
|
||||||
<HStack
|
|
||||||
bgColor="white"
|
|
||||||
shadow="md"
|
|
||||||
rounded="md"
|
|
||||||
p="3"
|
|
||||||
maxW="280px"
|
|
||||||
spacing={4}
|
|
||||||
>
|
|
||||||
{inputValues.avatarUrl && (
|
|
||||||
// eslint-disable-next-line jsx-a11y/alt-text
|
|
||||||
<Image src={inputValues.avatarUrl} w="40px" rounded="full" />
|
|
||||||
)}
|
|
||||||
<Text>{inputValues.messageContent}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center" pl="4">
|
|
||||||
<Text>Appearance delay</Text>
|
|
||||||
<NumberInput
|
|
||||||
onChange={(messageDelay) =>
|
|
||||||
setInputValues({
|
|
||||||
...inputValues,
|
|
||||||
messageDelay,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
value={inputValues.messageDelay}
|
|
||||||
min={0}
|
|
||||||
>
|
|
||||||
<NumberInputField />
|
|
||||||
<NumberInputStepper>
|
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberInputStepper>
|
|
||||||
</NumberInput>
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center" pl="4">
|
|
||||||
<Text>Avatar URL</Text>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
onChange={(e) =>
|
|
||||||
setInputValues({
|
|
||||||
...inputValues,
|
|
||||||
avatarUrl: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
value={inputValues.avatarUrl}
|
|
||||||
placeholder={'Paste image link (.png, .jpg)'}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center" pl="4">
|
|
||||||
<Text>Message content</Text>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
onChange={(e) =>
|
|
||||||
setInputValues({
|
|
||||||
...inputValues,
|
|
||||||
messageContent: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
value={inputValues.messageContent}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { FlexProps } from '@chakra-ui/react'
|
|
||||||
import parserHtml from 'prettier/parser-html'
|
|
||||||
import prettier from 'prettier/standalone'
|
|
||||||
import { parseInitContainerCode, typebotJsHtml } from '../params'
|
|
||||||
import { IframeParams } from 'typebot-js'
|
|
||||||
import { useTypebot } from '@/features/editor'
|
|
||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
import { env, getViewerUrl } from 'utils'
|
|
||||||
|
|
||||||
type ContainerEmbedCodeProps = {
|
|
||||||
widthLabel: string
|
|
||||||
heightLabel: string
|
|
||||||
withStarterVariables?: boolean
|
|
||||||
onCopied?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ContainerEmbedCode = ({
|
|
||||||
widthLabel,
|
|
||||||
heightLabel,
|
|
||||||
}: ContainerEmbedCodeProps & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
|
|
||||||
const snippet = prettier.format(
|
|
||||||
parseSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
heightLabel,
|
|
||||||
widthLabel,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'html',
|
|
||||||
plugins: [parserHtml],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
type SnippetProps = IframeParams &
|
|
||||||
Pick<ContainerEmbedCodeProps, 'widthLabel' | 'heightLabel'>
|
|
||||||
|
|
||||||
const parseSnippet = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
...embedProps
|
|
||||||
}: SnippetProps): string => {
|
|
||||||
const jsCode = parseInitContainerCode({
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
url,
|
|
||||||
})
|
|
||||||
return `${typebotJsHtml}
|
|
||||||
<div id="typebot-container" style="width: ${embedProps.widthLabel}; height: ${embedProps.heightLabel};"></div>
|
|
||||||
<script>${jsCode}</script>`
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
import {
|
|
||||||
StackProps,
|
|
||||||
Stack,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
Switch,
|
|
||||||
Input,
|
|
||||||
HStack,
|
|
||||||
Text,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { DropdownList } from '@/components/DropdownList'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
|
|
||||||
type StandardEmbedWindowSettingsProps = {
|
|
||||||
onUpdateWindowSettings: (windowSettings: {
|
|
||||||
heightLabel: string
|
|
||||||
widthLabel: string
|
|
||||||
}) => void
|
|
||||||
}
|
|
||||||
export const StandardEmbedWindowSettings = ({
|
|
||||||
onUpdateWindowSettings,
|
|
||||||
...props
|
|
||||||
}: StandardEmbedWindowSettingsProps & StackProps) => {
|
|
||||||
const [fullscreen, setFullscreen] = useState(false)
|
|
||||||
const [inputValues, setInputValues] = useState({
|
|
||||||
widthValue: '100',
|
|
||||||
widthType: '%',
|
|
||||||
heightValue: '600',
|
|
||||||
heightType: 'px',
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onUpdateWindowSettings({
|
|
||||||
widthLabel: fullscreen
|
|
||||||
? '100%'
|
|
||||||
: inputValues.widthValue + inputValues.widthType,
|
|
||||||
heightLabel: fullscreen
|
|
||||||
? '100vh'
|
|
||||||
: inputValues.heightValue + inputValues.heightType,
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [inputValues, fullscreen])
|
|
||||||
|
|
||||||
const handleWidthTypeSelect = (widthType: string) =>
|
|
||||||
setInputValues({ ...inputValues, widthType })
|
|
||||||
const handleHeightTypeSelect = (heightType: string) =>
|
|
||||||
setInputValues({ ...inputValues, heightType })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack {...props}>
|
|
||||||
<Flex alignItems="center" justifyContent="space-between">
|
|
||||||
<Heading fontSize="md" fontWeight="semibold" style={{ flexShrink: 0 }}>
|
|
||||||
Window settings
|
|
||||||
</Heading>
|
|
||||||
<FormControl
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
w="full"
|
|
||||||
justifyContent="flex-end"
|
|
||||||
>
|
|
||||||
<FormLabel htmlFor="fullscreen-option" mb="1">
|
|
||||||
Set to fullscreen?
|
|
||||||
</FormLabel>
|
|
||||||
<Switch
|
|
||||||
id="fullscreen-option"
|
|
||||||
onChange={() => setFullscreen(!fullscreen)}
|
|
||||||
isChecked={fullscreen}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{!fullscreen && (
|
|
||||||
<>
|
|
||||||
<Flex justify="space-between" align="center" mb="2">
|
|
||||||
<Text>Width</Text>
|
|
||||||
<HStack>
|
|
||||||
<Input
|
|
||||||
onChange={(e) =>
|
|
||||||
setInputValues({
|
|
||||||
...inputValues,
|
|
||||||
widthValue: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
w="70px"
|
|
||||||
value={inputValues.widthValue}
|
|
||||||
/>
|
|
||||||
<DropdownList<string>
|
|
||||||
items={['px', '%']}
|
|
||||||
onItemSelect={handleWidthTypeSelect}
|
|
||||||
currentItem={inputValues.widthType}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
<Flex justify="space-between" align="center" mb="2">
|
|
||||||
<Text>Height</Text>
|
|
||||||
<HStack>
|
|
||||||
<Input
|
|
||||||
onChange={(e) =>
|
|
||||||
setInputValues({
|
|
||||||
...inputValues,
|
|
||||||
heightValue: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
w="70px"
|
|
||||||
value={inputValues.heightValue}
|
|
||||||
/>
|
|
||||||
<DropdownList<string>
|
|
||||||
items={['px', '%']}
|
|
||||||
onItemSelect={handleHeightTypeSelect}
|
|
||||||
currentItem={inputValues.heightType}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { FlexProps } from '@chakra-ui/react'
|
|
||||||
import { useTypebot } from '@/features/editor'
|
|
||||||
import parserHtml from 'prettier/parser-html'
|
|
||||||
import prettier from 'prettier/standalone'
|
|
||||||
import { PopupParams } from 'typebot-js'
|
|
||||||
import { env, getViewerUrl } from 'utils'
|
|
||||||
import { parseInitPopupCode, typebotJsHtml } from '../params'
|
|
||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
|
|
||||||
type PopupEmbedCodeProps = {
|
|
||||||
delay?: number
|
|
||||||
withStarterVariables?: boolean
|
|
||||||
onCopied?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const snippet = prettier.format(
|
|
||||||
createSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
delay,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'html',
|
|
||||||
plugins: [parserHtml],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
const createSnippet = (params: PopupParams): string => {
|
|
||||||
const jsCode = parseInitPopupCode(params)
|
|
||||||
return `${typebotJsHtml}
|
|
||||||
<script>${jsCode}</script>`
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import {
|
|
||||||
StackProps,
|
|
||||||
Stack,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
NumberInput,
|
|
||||||
NumberInputField,
|
|
||||||
Switch,
|
|
||||||
HStack,
|
|
||||||
NumberIncrementStepper,
|
|
||||||
NumberDecrementStepper,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
import { PopupParams } from 'typebot-js'
|
|
||||||
|
|
||||||
type PopupEmbedSettingsProps = {
|
|
||||||
onUpdateSettings: (windowSettings: Pick<PopupParams, 'delay'>) => void
|
|
||||||
}
|
|
||||||
export const PopupEmbedSettings = ({
|
|
||||||
onUpdateSettings,
|
|
||||||
...props
|
|
||||||
}: PopupEmbedSettingsProps & StackProps) => {
|
|
||||||
const [isEnabled, setIsEnabled] = useState(false)
|
|
||||||
const [inputValue, setInputValue] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onUpdateSettings({
|
|
||||||
delay: isEnabled ? inputValue * 1000 : undefined,
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [inputValue, isEnabled])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack {...props}>
|
|
||||||
<Flex alignItems="center" justifyContent="space-between">
|
|
||||||
<Heading fontSize="md" fontWeight="semibold">
|
|
||||||
Popup settings
|
|
||||||
</Heading>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex justify="space-between" align="center" mb="2">
|
|
||||||
<HStack>
|
|
||||||
<p>Appearance delay</p>
|
|
||||||
<Switch
|
|
||||||
isChecked={isEnabled}
|
|
||||||
onChange={(e) => setIsEnabled(e.target.checked)}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{isEnabled && (
|
|
||||||
<NumberInput
|
|
||||||
onChange={(_, val) => setInputValue(val)}
|
|
||||||
value={inputValue}
|
|
||||||
min={0}
|
|
||||||
>
|
|
||||||
<NumberInputField />
|
|
||||||
<NumberIncrementStepper>
|
|
||||||
<NumberIncrementStepper />
|
|
||||||
<NumberDecrementStepper />
|
|
||||||
</NumberIncrementStepper>
|
|
||||||
</NumberInput>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
import { FlexProps } from '@chakra-ui/react'
|
|
||||||
import React from 'react'
|
|
||||||
import { BubbleParams, IframeParams, PopupParams } from 'typebot-js'
|
|
||||||
import {
|
|
||||||
parseInitBubbleCode,
|
|
||||||
parseInitContainerCode,
|
|
||||||
parseInitPopupCode,
|
|
||||||
} from './params'
|
|
||||||
import parserBabel from 'prettier/parser-babel'
|
|
||||||
import prettier from 'prettier/standalone'
|
|
||||||
import { useTypebot } from '@/features/editor'
|
|
||||||
import { env, getViewerUrl } from 'utils'
|
|
||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
|
|
||||||
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
|
|
||||||
export const StandardReactDiv = ({
|
|
||||||
widthLabel,
|
|
||||||
heightLabel,
|
|
||||||
}: StandardReactDivProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const snippet = prettier.format(
|
|
||||||
parseContainerSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
heightLabel,
|
|
||||||
widthLabel,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'babel',
|
|
||||||
plugins: [parserBabel],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <CodeEditor value={snippet} lang="js" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
type SnippetProps = IframeParams &
|
|
||||||
Pick<StandardReactDivProps, 'widthLabel' | 'heightLabel'>
|
|
||||||
|
|
||||||
const parseContainerSnippet = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
...embedProps
|
|
||||||
}: SnippetProps): string => {
|
|
||||||
const jsCode = parseInitContainerCode({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
})
|
|
||||||
return `import Typebot from "typebot-js";
|
|
||||||
|
|
||||||
const Component = () => {
|
|
||||||
useEffect(()=> {
|
|
||||||
${jsCode}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div id="typebot-container" style={{width: "${embedProps.widthLabel}", height: "${embedProps.heightLabel}"}} />
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PopupEmbedCodeProps = {
|
|
||||||
delay?: number
|
|
||||||
withStarterVariables?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const snippet = prettier.format(
|
|
||||||
parsePopupSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
delay,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'babel',
|
|
||||||
plugins: [parserBabel],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <CodeEditor value={snippet} lang="js" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsePopupSnippet = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
delay,
|
|
||||||
}: PopupParams): string => {
|
|
||||||
const jsCode = parseInitPopupCode({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
delay,
|
|
||||||
})
|
|
||||||
return `import Typebot from "typebot-js";
|
|
||||||
|
|
||||||
const Component = () => {
|
|
||||||
useEffect(()=> {
|
|
||||||
${jsCode}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatEmbedCodeProps = {
|
|
||||||
withStarterVariables?: boolean
|
|
||||||
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
|
|
||||||
|
|
||||||
export const ChatReactCode = ({
|
|
||||||
proactiveMessage,
|
|
||||||
button,
|
|
||||||
}: ChatEmbedCodeProps & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const snippet = prettier.format(
|
|
||||||
parseBubbleSnippet({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
|
||||||
typebot?.publicId
|
|
||||||
}`,
|
|
||||||
button,
|
|
||||||
proactiveMessage,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
parser: 'babel',
|
|
||||||
plugins: [parserBabel],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return <CodeEditor value={snippet} lang="js" isReadOnly />
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseBubbleSnippet = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
proactiveMessage,
|
|
||||||
button,
|
|
||||||
}: BubbleParams): string => {
|
|
||||||
const jsCode = parseInitBubbleCode({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
proactiveMessage,
|
|
||||||
button,
|
|
||||||
})
|
|
||||||
return `import Typebot from "typebot-js";
|
|
||||||
|
|
||||||
const Component = () => {
|
|
||||||
useEffect(()=> {
|
|
||||||
${jsCode}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <></>
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import {
|
|
||||||
BubbleParams,
|
|
||||||
ButtonParams,
|
|
||||||
IframeParams,
|
|
||||||
PopupParams,
|
|
||||||
ProactiveMessageParams,
|
|
||||||
} from 'typebot-js'
|
|
||||||
import parserBabel from 'prettier/parser-babel'
|
|
||||||
import prettier from 'prettier/standalone'
|
|
||||||
import { isDefined } from 'utils'
|
|
||||||
|
|
||||||
const parseStringParam = (fieldName: string, fieldValue?: string) =>
|
|
||||||
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
|
|
||||||
|
|
||||||
const parseNonStringParam = (
|
|
||||||
fieldName: string,
|
|
||||||
fieldValue?: number | boolean
|
|
||||||
) => (isDefined(fieldValue) ? `${fieldName}: ${fieldValue},` : ``)
|
|
||||||
|
|
||||||
const parseCustomDomain = (domain?: string): string =>
|
|
||||||
parseStringParam('customDomain', domain)
|
|
||||||
|
|
||||||
const parseHiddenVariables = (
|
|
||||||
variables: { [key: string]: string | undefined } | undefined
|
|
||||||
): string => (variables ? `hiddenVariables: ${JSON.stringify(variables)},` : ``)
|
|
||||||
|
|
||||||
const parseBackgroundColor = (bgColor?: string): string =>
|
|
||||||
parseStringParam('backgroundColor', bgColor)
|
|
||||||
|
|
||||||
const parseDelay = (delay?: number) => parseNonStringParam('delay', delay)
|
|
||||||
|
|
||||||
const parseButton = (button?: ButtonParams): string => {
|
|
||||||
if (!button) return ''
|
|
||||||
const iconUrlString = parseStringParam('iconUrl', button.iconUrl)
|
|
||||||
const buttonColorstring = parseStringParam('color', button.color)
|
|
||||||
const buttonIconColorString = parseStringParam('iconColor', button.iconColor)
|
|
||||||
return `button: {${iconUrlString}${buttonColorstring}${buttonIconColorString}},`
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseProactiveMessage = (
|
|
||||||
proactiveMessage?: ProactiveMessageParams
|
|
||||||
): string => {
|
|
||||||
if (!proactiveMessage) return ``
|
|
||||||
const { avatarUrl, textContent, delay } = proactiveMessage
|
|
||||||
const avatarUrlString = parseStringParam('avatarUrl', avatarUrl)
|
|
||||||
const textContentString = parseStringParam('textContent', textContent)
|
|
||||||
const delayString = parseNonStringParam('delay', delay)
|
|
||||||
return `proactiveMessage: {${avatarUrlString}${textContentString}${delayString}},`
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseIframeParams = ({
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
}: Pick<
|
|
||||||
IframeParams,
|
|
||||||
'customDomain' | 'hiddenVariables' | 'backgroundColor'
|
|
||||||
>) => ({
|
|
||||||
customDomainString: parseCustomDomain(customDomain),
|
|
||||||
hiddenVariablesString: parseHiddenVariables(hiddenVariables),
|
|
||||||
bgColorString: parseBackgroundColor(backgroundColor),
|
|
||||||
})
|
|
||||||
|
|
||||||
const parsePopupParams = ({ delay }: Pick<PopupParams, 'delay'>) => ({
|
|
||||||
delayString: parseDelay(delay),
|
|
||||||
})
|
|
||||||
|
|
||||||
const parseBubbleParams = ({
|
|
||||||
button,
|
|
||||||
proactiveMessage,
|
|
||||||
}: Pick<BubbleParams, 'button' | 'proactiveMessage'>) => ({
|
|
||||||
proactiveMessageString: parseProactiveMessage(proactiveMessage),
|
|
||||||
buttonString: parseButton(button),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const parseInitContainerCode = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
backgroundColor,
|
|
||||||
hiddenVariables,
|
|
||||||
}: IframeParams) => {
|
|
||||||
const { customDomainString, hiddenVariablesString, bgColorString } =
|
|
||||||
parseIframeParams({
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
})
|
|
||||||
return prettier.format(
|
|
||||||
`Typebot.initContainer("typebot-container", {
|
|
||||||
url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}
|
|
||||||
});`,
|
|
||||||
{ parser: 'babel', plugins: [parserBabel] }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseInitPopupCode = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
delay,
|
|
||||||
}: PopupParams) => {
|
|
||||||
const { customDomainString, hiddenVariablesString, bgColorString } =
|
|
||||||
parseIframeParams({
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
})
|
|
||||||
const { delayString } = parsePopupParams({ delay })
|
|
||||||
return prettier.format(
|
|
||||||
`var typebotCommands = Typebot.initPopup({url: "${url}",${delayString}${bgColorString}${customDomainString}${hiddenVariablesString}});`,
|
|
||||||
{ parser: 'babel', plugins: [parserBabel] }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseInitBubbleCode = ({
|
|
||||||
url,
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
button,
|
|
||||||
proactiveMessage,
|
|
||||||
}: BubbleParams) => {
|
|
||||||
const { customDomainString, hiddenVariablesString, bgColorString } =
|
|
||||||
parseIframeParams({
|
|
||||||
customDomain,
|
|
||||||
hiddenVariables,
|
|
||||||
backgroundColor,
|
|
||||||
})
|
|
||||||
const { buttonString, proactiveMessageString } = parseBubbleParams({
|
|
||||||
button,
|
|
||||||
proactiveMessage,
|
|
||||||
})
|
|
||||||
return prettier.format(
|
|
||||||
`var typebotCommands = Typebot.initBubble({url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}${proactiveMessageString}${buttonString}});`,
|
|
||||||
{ parser: 'babel', plugins: [parserBabel] }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.2"></script>`
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
import { HStack, Button, Text, Stack } from '@chakra-ui/react'
|
|
||||||
|
|
||||||
type ChooseEmbedTypeListProps = {
|
|
||||||
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
|
|
||||||
disabledTypes?: ('standard' | 'popup' | 'bubble')[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ChooseEmbedTypeList = ({
|
|
||||||
onSelectEmbedType,
|
|
||||||
disabledTypes = [],
|
|
||||||
}: ChooseEmbedTypeListProps) => {
|
|
||||||
return (
|
|
||||||
<HStack mx="auto">
|
|
||||||
<Stack
|
|
||||||
as={Button}
|
|
||||||
fontWeight="normal"
|
|
||||||
alignItems="center"
|
|
||||||
variant="outline"
|
|
||||||
colorScheme="gray"
|
|
||||||
style={{ width: '225px', height: '270px' }}
|
|
||||||
onClick={() => onSelectEmbedType('standard')}
|
|
||||||
whiteSpace={'normal'}
|
|
||||||
spacing="6"
|
|
||||||
isDisabled={disabledTypes.includes('standard')}
|
|
||||||
>
|
|
||||||
<StandardEmbedSvg />
|
|
||||||
<Stack>
|
|
||||||
<Text fontSize="lg" fontWeight="semibold">
|
|
||||||
Standard
|
|
||||||
</Text>
|
|
||||||
<Text textColor="gray.500">Embed in a container on your site</Text>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
<Stack
|
|
||||||
as={Button}
|
|
||||||
fontWeight="normal"
|
|
||||||
alignItems="center"
|
|
||||||
variant="outline"
|
|
||||||
colorScheme="gray"
|
|
||||||
style={{ width: '225px', height: '270px' }}
|
|
||||||
onClick={() => onSelectEmbedType('popup')}
|
|
||||||
whiteSpace={'normal'}
|
|
||||||
spacing="6"
|
|
||||||
isDisabled={disabledTypes.includes('popup')}
|
|
||||||
>
|
|
||||||
<PopupEmbedSvg />
|
|
||||||
<Stack>
|
|
||||||
<Text fontSize="lg" fontWeight="semibold">
|
|
||||||
Popup
|
|
||||||
</Text>
|
|
||||||
<Text textColor="gray.500">
|
|
||||||
Embed in a popup window on top of your website
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
<Stack
|
|
||||||
as={Button}
|
|
||||||
fontWeight="normal"
|
|
||||||
alignItems="center"
|
|
||||||
variant="outline"
|
|
||||||
colorScheme="gray"
|
|
||||||
style={{ width: '225px', height: '270px' }}
|
|
||||||
onClick={() => onSelectEmbedType('bubble')}
|
|
||||||
whiteSpace={'normal'}
|
|
||||||
spacing="6"
|
|
||||||
isDisabled={disabledTypes.includes('bubble')}
|
|
||||||
>
|
|
||||||
<BubbleEmbedSvg />
|
|
||||||
<Stack>
|
|
||||||
<Text fontSize="lg" fontWeight="semibold">
|
|
||||||
Bubble
|
|
||||||
</Text>
|
|
||||||
<Text textColor="gray.500">
|
|
||||||
Embed in a chat bubble on the corner of your site
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</HStack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandardEmbedSvg = () => (
|
|
||||||
<svg
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="100" height="100" rx="5" fill="#0042DA" />
|
|
||||||
<rect x="10" y="28" width="80" height="42" rx="6" fill="#FF8E20" />
|
|
||||||
<circle cx="18" cy="37" r="5" fill="white" />
|
|
||||||
<rect x="24" y="33" width="45" height="8" rx="4" fill="white" />
|
|
||||||
<circle cx="18" cy="61" r="5" fill="white" />
|
|
||||||
<rect x="24" y="57" width="45" height="8" rx="4" fill="white" />
|
|
||||||
<rect x="31" y="45" width="45" height="8" rx="4" fill="white" />
|
|
||||||
<circle cx="82" cy="49" r="5" fill="white" />
|
|
||||||
<rect x="10" y="9" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
<rect x="10" y="14" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
<rect x="10" y="19" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
<rect x="10" y="80" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
<rect x="10" y="85" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
<rect x="10" y="90" width="80" height="1" rx="0.5" fill="white" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const PopupEmbedSvg = () => (
|
|
||||||
<svg
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="100" height="100" rx="5" fill="#0042DA" />
|
|
||||||
<rect x="19" y="20" width="63" height="63" rx="6" fill="#FF8E20" />
|
|
||||||
<circle cx="25.7719" cy="33.7719" r="3.77193" fill="white" />
|
|
||||||
<rect x="31" y="30" width="27" height="8" rx="4" fill="white" />
|
|
||||||
<circle
|
|
||||||
r="3.77193"
|
|
||||||
transform="matrix(-1 0 0 1 75.2281 43.7719)"
|
|
||||||
fill="white"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
width="22"
|
|
||||||
height="8"
|
|
||||||
rx="4"
|
|
||||||
transform="matrix(-1 0 0 1 70 40)"
|
|
||||||
fill="white"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="31.0527"
|
|
||||||
y="52"
|
|
||||||
width="26.9473"
|
|
||||||
height="7.54386"
|
|
||||||
rx="3.77193"
|
|
||||||
fill="white"
|
|
||||||
/>
|
|
||||||
<circle cx="25.7719" cy="67.7719" r="3.77193" fill="white" />
|
|
||||||
<rect x="31" y="64" width="27" height="8" rx="4" fill="white" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const BubbleEmbedSvg = () => (
|
|
||||||
<svg
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect width="100" height="100" rx="5" fill="#0042DA" />
|
|
||||||
<circle cx="85.5" cy="85.5" r="7.5" fill="#FF8E20" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { BubbleParams } from 'typebot-js'
|
|
||||||
import { env, getViewerUrl } from 'utils'
|
|
||||||
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
|
|
||||||
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
|
|
||||||
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
|
|
||||||
import {
|
|
||||||
parseInitContainerCode,
|
|
||||||
typebotJsHtml,
|
|
||||||
} from '../../codeSnippets/params'
|
|
||||||
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
|
|
||||||
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
|
|
||||||
import { ModalProps } from '../../EmbedButton'
|
|
||||||
|
|
||||||
type GtmInstructionsProps = {
|
|
||||||
type: 'standard' | 'popup' | 'bubble'
|
|
||||||
publicId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'standard': {
|
|
||||||
return <StandardInstructions publicId={publicId} />
|
|
||||||
}
|
|
||||||
case 'popup': {
|
|
||||||
return <PopupInstructions />
|
|
||||||
}
|
|
||||||
case 'bubble': {
|
|
||||||
return <BubbleInstructions />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
|
|
||||||
const [windowSizes, setWindowSizes] = useState({
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
})
|
|
||||||
|
|
||||||
const jsCode = parseInitContainerCode({
|
|
||||||
url: `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${publicId}`,
|
|
||||||
})
|
|
||||||
const headCode = `${typebotJsHtml}
|
|
||||||
<script>
|
|
||||||
${jsCode}
|
|
||||||
</script>`
|
|
||||||
|
|
||||||
const elementCode = `<div id="typebot-container" style="height: ${windowSizes.height}; width: ${windowSizes.width}"></div>`
|
|
||||||
return (
|
|
||||||
<OrderedList spacing={2} mb={4}>
|
|
||||||
<ListItem>
|
|
||||||
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Choose Custom <Tag>HTML tag</Tag> type
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Paste the code below:
|
|
||||||
<CodeEditor value={headCode} mt={2} isReadOnly lang="html" />
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
On your webpage, you need to have an element on which the typebot will
|
|
||||||
go. It needs to have the id <Tag>typebot-container</Tag>:
|
|
||||||
<StandardEmbedWindowSettings
|
|
||||||
my={4}
|
|
||||||
onUpdateWindowSettings={(sizes) =>
|
|
||||||
setWindowSizes({
|
|
||||||
height: sizes.heightLabel,
|
|
||||||
width: sizes.widthLabel,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CodeEditor value={elementCode} mt={2} isReadOnly lang="html" />
|
|
||||||
</ListItem>
|
|
||||||
</OrderedList>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopupInstructions = () => {
|
|
||||||
const [inputValue, setInputValue] = useState<number>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OrderedList spacing={2} mb={4}>
|
|
||||||
<ListItem>
|
|
||||||
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Choose Custom <Tag>HTML tag</Tag> type
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Paste the code below:
|
|
||||||
<PopupEmbedSettings
|
|
||||||
my={4}
|
|
||||||
onUpdateSettings={(settings) => setInputValue(settings.delay)}
|
|
||||||
/>
|
|
||||||
<PopupEmbedCode delay={inputValue} />
|
|
||||||
</ListItem>
|
|
||||||
</OrderedList>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const BubbleInstructions = () => {
|
|
||||||
const [inputValues, setInputValues] = useState<
|
|
||||||
Pick<BubbleParams, 'proactiveMessage' | 'button'>
|
|
||||||
>({
|
|
||||||
proactiveMessage: undefined,
|
|
||||||
button: {
|
|
||||||
color: '',
|
|
||||||
iconUrl: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OrderedList spacing={2} mb={4}>
|
|
||||||
<ListItem>
|
|
||||||
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Choose Custom <Tag>HTML tag</Tag> type
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
|
||||||
Paste the code below:
|
|
||||||
<ChatEmbedSettings
|
|
||||||
onUpdateSettings={(settings) => setInputValues({ ...settings })}
|
|
||||||
/>
|
|
||||||
<ChatEmbedCode my={4} {...inputValues} />
|
|
||||||
</ListItem>
|
|
||||||
</OrderedList>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { ModalProps } from '../../EmbedButton'
|
||||||
|
import { EmbedModal } from '../../EmbedModal'
|
||||||
|
import { isDefined } from '@udecode/plate-common'
|
||||||
|
import { GtmInstructions } from './instructions/GtmInstructions'
|
||||||
|
|
||||||
|
export const GtmModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
isPublished,
|
||||||
|
publicId,
|
||||||
|
}: ModalProps) => {
|
||||||
|
const [selectedEmbedType, setSelectedEmbedType] = useState<
|
||||||
|
'standard' | 'popup' | 'bubble' | undefined
|
||||||
|
>()
|
||||||
|
return (
|
||||||
|
<EmbedModal
|
||||||
|
titlePrefix="GTM"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
isPublished={isPublished}
|
||||||
|
onSelectEmbedType={setSelectedEmbedType}
|
||||||
|
selectedEmbedType={selectedEmbedType}
|
||||||
|
>
|
||||||
|
{isDefined(selectedEmbedType) && (
|
||||||
|
<GtmInstructions type={selectedEmbedType} publicId={publicId} />
|
||||||
|
)}
|
||||||
|
</EmbedModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './GtmModal'
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import {
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalBody,
|
|
||||||
ModalFooter,
|
|
||||||
IconButton,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { ChevronLeftIcon } from '@/components/icons'
|
|
||||||
import React, { useState } from 'react'
|
|
||||||
import { ModalProps } from '../../EmbedButton'
|
|
||||||
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
|
|
||||||
import { capitalize } from 'utils'
|
|
||||||
import { GtmInstructions } from './GtmInstructions'
|
|
||||||
import { AlertInfo } from '@/components/AlertInfo'
|
|
||||||
|
|
||||||
export const GtmModal = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
isPublished,
|
|
||||||
publicId,
|
|
||||||
}: ModalProps) => {
|
|
||||||
const [chosenEmbedType, setChosenEmbedType] = useState<
|
|
||||||
'standard' | 'popup' | 'bubble' | undefined
|
|
||||||
>()
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
size={!chosenEmbedType ? '2xl' : 'xl'}
|
|
||||||
>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>
|
|
||||||
<HStack>
|
|
||||||
{chosenEmbedType && (
|
|
||||||
<IconButton
|
|
||||||
icon={<ChevronLeftIcon />}
|
|
||||||
aria-label="back"
|
|
||||||
variant="ghost"
|
|
||||||
colorScheme="gray"
|
|
||||||
mr={2}
|
|
||||||
onClick={() => setChosenEmbedType(undefined)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Heading size="md">
|
|
||||||
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
|
|
||||||
</Heading>
|
|
||||||
</HStack>
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
{!isPublished && (
|
|
||||||
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
|
|
||||||
)}
|
|
||||||
{!chosenEmbedType ? (
|
|
||||||
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
|
|
||||||
) : (
|
|
||||||
<GtmInstructions type={chosenEmbedType} publicId={publicId} />
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
|
||||||
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||||
|
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
|
||||||
|
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
|
||||||
|
|
||||||
|
export const GtmBubbleInstructions = () => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
const [theme, setTheme] = useState<BubbleProps['theme']>(
|
||||||
|
parseDefaultBubbleTheme(typebot)
|
||||||
|
)
|
||||||
|
const [previewMessage, setPreviewMessage] =
|
||||||
|
useState<BubbleProps['previewMessage']>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderedList spacing={4} pl={5}>
|
||||||
|
<ListItem>
|
||||||
|
On your GTM account dashboard, click on <Code>Add a new tag</Code>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
Choose Custom <Code>HTML tag</Code> type
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<BubbleSettings
|
||||||
|
theme={theme}
|
||||||
|
previewMessage={previewMessage}
|
||||||
|
defaultPreviewMessageAvatar={
|
||||||
|
typebot?.theme.chat.hostAvatar?.url ?? ''
|
||||||
|
}
|
||||||
|
onThemeChange={setTheme}
|
||||||
|
onPreviewMessageChange={setPreviewMessage}
|
||||||
|
/>
|
||||||
|
<Text>Paste the code below:</Text>
|
||||||
|
<JavascriptBubbleSnippet
|
||||||
|
theme={theme}
|
||||||
|
previewMessage={previewMessage}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
</OrderedList>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { GtmBubbleInstructions } from './GtmBubbleInstructions'
|
||||||
|
import { GtmPopupInstructions } from './GtmPopupInstructions'
|
||||||
|
import { GtmStandardInstructions } from './GtmStandardInstructions'
|
||||||
|
|
||||||
|
type GtmInstructionsProps = {
|
||||||
|
type: 'standard' | 'popup' | 'bubble'
|
||||||
|
publicId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'standard': {
|
||||||
|
return <GtmStandardInstructions publicId={publicId} />
|
||||||
|
}
|
||||||
|
case 'popup': {
|
||||||
|
return <GtmPopupInstructions />
|
||||||
|
}
|
||||||
|
case 'bubble': {
|
||||||
|
return <GtmBubbleInstructions />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { PopupSettings } from '../../../settings/PopupSettings'
|
||||||
|
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
|
||||||
|
|
||||||
|
export const GtmPopupInstructions = () => {
|
||||||
|
const [inputValue, setInputValue] = useState<number>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderedList spacing={4} pl={5}>
|
||||||
|
<ListItem>
|
||||||
|
On your GTM account dashboard, click on <Code>Add a new tag</Code>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
Choose Custom <Code>HTML tag</Code> type
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<PopupSettings
|
||||||
|
onUpdateSettings={(settings) =>
|
||||||
|
setInputValue(settings.autoShowDelay)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text>Paste the code below:</Text>
|
||||||
|
<JavascriptPopupSnippet autoShowDelay={inputValue} />
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
</OrderedList>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
|
||||||
|
import { Typebot } from 'models'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { StandardSettings } from '../../../settings/StandardSettings'
|
||||||
|
import {
|
||||||
|
parseStandardElementCode,
|
||||||
|
parseStandardHeadCode,
|
||||||
|
} from '../../Javascript/JavascriptStandardSnippet'
|
||||||
|
|
||||||
|
export const GtmStandardInstructions = ({
|
||||||
|
publicId,
|
||||||
|
}: Pick<Typebot, 'publicId'>) => {
|
||||||
|
const [windowSizes, setWindowSizes] = useState<{
|
||||||
|
height: string
|
||||||
|
width?: string
|
||||||
|
}>({
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
})
|
||||||
|
|
||||||
|
const headCode = parseStandardHeadCode(publicId)
|
||||||
|
|
||||||
|
const elementCode = parseStandardElementCode(
|
||||||
|
windowSizes.width,
|
||||||
|
windowSizes.height
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderedList spacing={4} pl={5}>
|
||||||
|
<ListItem>
|
||||||
|
On your GTM account dashboard, click on <Code>Add a new tag</Code>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
Choose Custom <Code>HTML tag</Code> type
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Text>Paste the code below:</Text>
|
||||||
|
<CodeEditor value={headCode} isReadOnly lang="html" />
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<StandardSettings
|
||||||
|
onUpdateWindowSettings={(sizes) =>
|
||||||
|
setWindowSizes({
|
||||||
|
height: sizes.heightLabel,
|
||||||
|
width: sizes.widthLabel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
On your web page, you need to have an element on which the typebot
|
||||||
|
will go:
|
||||||
|
</Text>
|
||||||
|
<CodeEditor value={elementCode} isReadOnly lang="html" />
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
</OrderedList>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,12 +11,15 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { StandardEmbedWindowSettings } from '../codeSnippets/Container/EmbedSettings'
|
import { ModalProps } from '../../EmbedButton'
|
||||||
import { IframeEmbedCode } from '../codeSnippets/Iframe/EmbedCode'
|
import { StandardSettings } from '../../settings/StandardSettings'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { IframeSnippet } from './IframeSnippet'
|
||||||
|
|
||||||
export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
|
export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
|
||||||
const [inputValues, setInputValues] = useState({
|
const [inputValues, setInputValues] = useState<{
|
||||||
|
heightLabel: string
|
||||||
|
widthLabel?: string
|
||||||
|
}>({
|
||||||
heightLabel: '100%',
|
heightLabel: '100%',
|
||||||
widthLabel: '100%',
|
widthLabel: '100%',
|
||||||
})
|
})
|
||||||
@@ -27,17 +30,21 @@ export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
|
|||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>Iframe</ModalHeader>
|
<ModalHeader>Iframe</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody as={Stack} spacing={4}>
|
<ModalBody as={Stack} spacing={4} pt="0">
|
||||||
{!isPublished && (
|
{!isPublished && (
|
||||||
<AlertInfo>You need to publish your bot first.</AlertInfo>
|
<AlertInfo>You need to publish your bot first.</AlertInfo>
|
||||||
)}
|
)}
|
||||||
<Text>Paste this anywhere in your HTML code:</Text>
|
<StandardSettings
|
||||||
<StandardEmbedWindowSettings
|
|
||||||
onUpdateWindowSettings={(settings) =>
|
onUpdateWindowSettings={(settings) =>
|
||||||
setInputValues({ ...settings })
|
setInputValues({ ...settings })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<IframeEmbedCode {...inputValues} />
|
<Text>Paste this anywhere in your HTML code:</Text>
|
||||||
|
|
||||||
|
<IframeSnippet
|
||||||
|
widthLabel={inputValues.widthLabel ?? '100%'}
|
||||||
|
heightLabel={inputValues.heightLabel}
|
||||||
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter />
|
<ModalFooter />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
@@ -2,21 +2,24 @@ import { FlexProps } from '@chakra-ui/react'
|
|||||||
import { useTypebot } from '@/features/editor'
|
import { useTypebot } from '@/features/editor'
|
||||||
import { env, getViewerUrl } from 'utils'
|
import { env, getViewerUrl } from 'utils'
|
||||||
import { CodeEditor } from '@/components/CodeEditor'
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import parserHtml from 'prettier/parser-html'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
widthLabel: string
|
widthLabel: string
|
||||||
heightLabel: string
|
heightLabel: string
|
||||||
onCopied?: () => void
|
onCopied?: () => void
|
||||||
}
|
} & FlexProps
|
||||||
export const IframeEmbedCode = ({
|
|
||||||
widthLabel,
|
export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => {
|
||||||
heightLabel,
|
|
||||||
}: Props & FlexProps) => {
|
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
||||||
typebot?.publicId
|
typebot?.publicId
|
||||||
}`
|
}`
|
||||||
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" style="border: none"></iframe>`
|
const code = prettier.format(
|
||||||
|
`<iframe src="${src}" style="border: none; width='${widthLabel}'; height='${heightLabel}'"></iframe>`,
|
||||||
|
{ parser: 'html', plugins: [parserHtml] }
|
||||||
|
)
|
||||||
|
|
||||||
return <CodeEditor value={code} lang="html" isReadOnly />
|
return <CodeEditor value={code} lang="html" isReadOnly />
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import parserHtml from 'prettier/parser-html'
|
||||||
|
import { parseInitBubbleCode } from '../../snippetParsers'
|
||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
|
import { isCloudProdInstance } from '@/utils/helpers'
|
||||||
|
import { env, getViewerUrl } from 'utils'
|
||||||
|
|
||||||
|
type Props = Pick<BubbleProps, 'theme' | 'previewMessage'>
|
||||||
|
|
||||||
|
export const JavascriptBubbleSnippet = ({ theme, previewMessage }: Props) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
|
||||||
|
const snippet = prettier.format(
|
||||||
|
`<script type="module">${parseInitBubbleCode({
|
||||||
|
typebot: typebot?.publicId ?? '',
|
||||||
|
apiHost: isCloudProdInstance
|
||||||
|
? undefined
|
||||||
|
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
|
||||||
|
theme: {
|
||||||
|
...theme,
|
||||||
|
chatWindow: {
|
||||||
|
backgroundColor: typebot?.theme.general.background.content ?? '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
previewMessage,
|
||||||
|
})}</script>`,
|
||||||
|
{
|
||||||
|
parser: 'html',
|
||||||
|
plugins: [parserHtml],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
||||||
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { Stack, Tag, Text } from '@chakra-ui/react'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { BubbleParams } from 'typebot-js'
|
|
||||||
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
|
|
||||||
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
|
|
||||||
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
|
|
||||||
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
|
|
||||||
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
|
|
||||||
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
|
|
||||||
|
|
||||||
type JavascriptInstructionsProps = {
|
|
||||||
type: 'standard' | 'popup' | 'bubble'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JavascriptInstructions = ({
|
|
||||||
type,
|
|
||||||
}: JavascriptInstructionsProps) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'standard': {
|
|
||||||
return <StandardInstructions />
|
|
||||||
}
|
|
||||||
case 'popup': {
|
|
||||||
return <PopupInstructions />
|
|
||||||
}
|
|
||||||
case 'bubble': {
|
|
||||||
return <BubbleInstructions />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandardInstructions = () => {
|
|
||||||
const [inputValues, setInputValues] = useState({
|
|
||||||
heightLabel: '100%',
|
|
||||||
widthLabel: '100%',
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<Text>
|
|
||||||
Paste this anywhere in the <Tag>body</Tag>
|
|
||||||
</Text>
|
|
||||||
<StandardEmbedWindowSettings
|
|
||||||
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
|
|
||||||
/>
|
|
||||||
<ContainerEmbedCode withStarterVariables={true} {...inputValues} mt={4} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopupInstructions = () => {
|
|
||||||
const [inputValue, setInputValue] = useState<number>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<Text>
|
|
||||||
Paste this anywhere in the <Tag>body</Tag>
|
|
||||||
</Text>
|
|
||||||
<PopupEmbedSettings
|
|
||||||
mb={4}
|
|
||||||
onUpdateSettings={(settings) => setInputValue(settings.delay)}
|
|
||||||
/>
|
|
||||||
<PopupEmbedCode delay={inputValue} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const BubbleInstructions = () => {
|
|
||||||
const [inputValues, setInputValues] = useState<
|
|
||||||
Pick<BubbleParams, 'proactiveMessage' | 'button'>
|
|
||||||
>({
|
|
||||||
proactiveMessage: undefined,
|
|
||||||
button: {
|
|
||||||
color: '',
|
|
||||||
iconUrl: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<Text>
|
|
||||||
Paste this anywhere in the <Tag>body</Tag>
|
|
||||||
</Text>
|
|
||||||
<ChatEmbedSettings
|
|
||||||
onUpdateSettings={(settings) => setInputValues({ ...settings })}
|
|
||||||
/>
|
|
||||||
<ChatEmbedCode {...inputValues} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,69 +1,29 @@
|
|||||||
import {
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalBody,
|
|
||||||
ModalFooter,
|
|
||||||
IconButton,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { ChevronLeftIcon } from '@/components/icons'
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ModalProps } from '../../EmbedButton'
|
import { ModalProps } from '../../EmbedButton'
|
||||||
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
|
import { EmbedModal } from '../../EmbedModal'
|
||||||
import { capitalize } from 'utils'
|
import { isDefined } from '@udecode/plate-common'
|
||||||
import { JavascriptInstructions } from './JavascriptInstructions'
|
import { JavascriptInstructions } from './instructions/JavascriptInstructions'
|
||||||
import { AlertInfo } from '@/components/AlertInfo'
|
|
||||||
|
|
||||||
export const JavascriptModal = ({
|
export const JavascriptModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
isPublished,
|
isPublished,
|
||||||
}: ModalProps) => {
|
}: ModalProps) => {
|
||||||
const [chosenEmbedType, setChosenEmbedType] = useState<
|
const [selectedEmbedType, setSelectedEmbedType] = useState<
|
||||||
'standard' | 'popup' | 'bubble' | undefined
|
'standard' | 'popup' | 'bubble' | undefined
|
||||||
>()
|
>()
|
||||||
return (
|
return (
|
||||||
<Modal
|
<EmbedModal
|
||||||
|
titlePrefix="Javascript"
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
size={!chosenEmbedType ? '2xl' : 'xl'}
|
isPublished={isPublished}
|
||||||
|
onSelectEmbedType={setSelectedEmbedType}
|
||||||
|
selectedEmbedType={selectedEmbedType}
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
{isDefined(selectedEmbedType) && (
|
||||||
<ModalContent>
|
<JavascriptInstructions type={selectedEmbedType} />
|
||||||
<ModalHeader>
|
)}
|
||||||
<HStack>
|
</EmbedModal>
|
||||||
{chosenEmbedType && (
|
|
||||||
<IconButton
|
|
||||||
icon={<ChevronLeftIcon />}
|
|
||||||
aria-label="back"
|
|
||||||
variant="ghost"
|
|
||||||
colorScheme="gray"
|
|
||||||
mr={2}
|
|
||||||
onClick={() => setChosenEmbedType(undefined)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Heading size="md">
|
|
||||||
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
|
|
||||||
</Heading>
|
|
||||||
</HStack>
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
{!isPublished && (
|
|
||||||
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
|
|
||||||
)}
|
|
||||||
{!chosenEmbedType ? (
|
|
||||||
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
|
|
||||||
) : (
|
|
||||||
<JavascriptInstructions type={chosenEmbedType} />
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import parserHtml from 'prettier/parser-html'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import { parseInitPopupCode } from '../../snippetParsers'
|
||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { PopupProps } from '@typebot.io/js'
|
||||||
|
import { isCloudProdInstance } from '@/utils/helpers'
|
||||||
|
import { env, getViewerUrl } from 'utils'
|
||||||
|
|
||||||
|
type Props = Pick<PopupProps, 'autoShowDelay'>
|
||||||
|
|
||||||
|
export const JavascriptPopupSnippet = ({ autoShowDelay }: Props) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
const snippet = prettier.format(
|
||||||
|
createSnippet({
|
||||||
|
typebot: typebot?.publicId ?? '',
|
||||||
|
apiHost: isCloudProdInstance
|
||||||
|
? undefined
|
||||||
|
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
|
||||||
|
autoShowDelay,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
parser: 'html',
|
||||||
|
plugins: [parserHtml],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSnippet = (params: PopupProps): string => {
|
||||||
|
const jsCode = parseInitPopupCode(params)
|
||||||
|
return `<script type="module">${jsCode}</script>`
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import parserHtml from 'prettier/parser-html'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import { parseInitStandardCode } from '../../snippetParsers'
|
||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { isCloudProdInstance } from '@/utils/helpers'
|
||||||
|
import { env, getViewerUrl } from 'utils'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
widthLabel?: string
|
||||||
|
heightLabel?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JavascriptStandardSnippet = ({
|
||||||
|
widthLabel,
|
||||||
|
heightLabel,
|
||||||
|
}: Props) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
|
||||||
|
const snippet = prettier.format(
|
||||||
|
`${parseStandardHeadCode(typebot?.publicId)}
|
||||||
|
${parseStandardElementCode(widthLabel, heightLabel)}`,
|
||||||
|
{
|
||||||
|
parser: 'html',
|
||||||
|
plugins: [parserHtml],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return <CodeEditor value={snippet} lang="html" isReadOnly />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseStandardHeadCode = (publicId?: string | null) =>
|
||||||
|
prettier.format(
|
||||||
|
`<script type="module">${parseInitStandardCode({
|
||||||
|
typebot: publicId ?? '',
|
||||||
|
apiHost: isCloudProdInstance
|
||||||
|
? undefined
|
||||||
|
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
|
||||||
|
})}</script>`,
|
||||||
|
{ parser: 'html', plugins: [parserHtml] }
|
||||||
|
)
|
||||||
|
|
||||||
|
export const parseStandardElementCode = (width?: string, height?: string) => {
|
||||||
|
if (!width && !height) return '<typebot-standard></typebot-standard>'
|
||||||
|
return prettier.format(
|
||||||
|
`<typebot-standard style="${width ? `width: ${width}; ` : ''}${
|
||||||
|
height ? `height: ${height}; ` : ''
|
||||||
|
}"></typebot-standard>`,
|
||||||
|
{ parser: 'html', plugins: [parserHtml] }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { Stack, Code, Text } from '@chakra-ui/react'
|
||||||
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
|
import { Typebot } from 'models'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||||
|
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
|
||||||
|
|
||||||
|
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
|
||||||
|
button: {
|
||||||
|
backgroundColor: typebot?.theme.chat.buttons.backgroundColor,
|
||||||
|
iconColor: typebot?.theme.chat.buttons.color,
|
||||||
|
},
|
||||||
|
previewMessage: {
|
||||||
|
backgroundColor: typebot?.theme.general.background.content ?? 'white',
|
||||||
|
textColor: 'black',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const JavascriptBubbleInstructions = () => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
const [theme, setTheme] = useState<BubbleProps['theme']>(
|
||||||
|
parseDefaultBubbleTheme(typebot)
|
||||||
|
)
|
||||||
|
const [previewMessage, setPreviewMessage] =
|
||||||
|
useState<BubbleProps['previewMessage']>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<BubbleSettings
|
||||||
|
theme={theme}
|
||||||
|
previewMessage={previewMessage}
|
||||||
|
defaultPreviewMessageAvatar={typebot?.theme.chat.hostAvatar?.url ?? ''}
|
||||||
|
onThemeChange={setTheme}
|
||||||
|
onPreviewMessageChange={setPreviewMessage}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
Paste this anywhere in the <Code>{'<body>'}</Code>:
|
||||||
|
</Text>
|
||||||
|
<JavascriptBubbleSnippet theme={theme} previewMessage={previewMessage} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { JavascriptBubbleInstructions } from './JavascriptBubbleInstructions'
|
||||||
|
import { JavascriptPopupInstructions } from './JavascriptPopupInstructions'
|
||||||
|
import { JavascriptStandardInstructions } from './JavascriptStandardInstructions'
|
||||||
|
|
||||||
|
type JavascriptInstructionsProps = {
|
||||||
|
type: 'standard' | 'popup' | 'bubble'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JavascriptInstructions = ({
|
||||||
|
type,
|
||||||
|
}: JavascriptInstructionsProps) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'standard': {
|
||||||
|
return <JavascriptStandardInstructions />
|
||||||
|
}
|
||||||
|
case 'popup': {
|
||||||
|
return <JavascriptPopupInstructions />
|
||||||
|
}
|
||||||
|
case 'bubble': {
|
||||||
|
return <JavascriptBubbleInstructions />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Stack, Code, Text } from '@chakra-ui/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { PopupSettings } from '../../../settings/PopupSettings'
|
||||||
|
import { JavascriptPopupSnippet } from '../JavascriptPopupSnippet'
|
||||||
|
|
||||||
|
export const JavascriptPopupInstructions = () => {
|
||||||
|
const [inputValue, setInputValue] = useState<number>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<PopupSettings
|
||||||
|
onUpdateSettings={(settings) => setInputValue(settings.autoShowDelay)}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
Paste this anywhere in the <Code>{'<body>'}</Code>:
|
||||||
|
</Text>
|
||||||
|
<JavascriptPopupSnippet autoShowDelay={inputValue} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Stack, Code, Text } from '@chakra-ui/react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { StandardSettings } from '../../../settings/StandardSettings'
|
||||||
|
import { JavascriptStandardSnippet } from '../JavascriptStandardSnippet'
|
||||||
|
|
||||||
|
export const JavascriptStandardInstructions = () => {
|
||||||
|
const [inputValues, setInputValues] = useState<{
|
||||||
|
heightLabel: string
|
||||||
|
widthLabel?: string
|
||||||
|
}>({
|
||||||
|
heightLabel: '100%',
|
||||||
|
widthLabel: '100%',
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<StandardSettings
|
||||||
|
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
|
||||||
|
/>
|
||||||
|
<Text>
|
||||||
|
Paste this anywhere in the <Code>{'<body>'}</Code>:
|
||||||
|
</Text>
|
||||||
|
<JavascriptStandardSnippet {...inputValues} />
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,11 +10,13 @@ import {
|
|||||||
ModalBody,
|
ModalBody,
|
||||||
OrderedList,
|
OrderedList,
|
||||||
ListItem,
|
ListItem,
|
||||||
Tag,
|
Code,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
Input,
|
Input,
|
||||||
InputRightElement,
|
InputRightElement,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
|
Text,
|
||||||
|
Stack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { env, getViewerUrl } from 'utils'
|
import { env, getViewerUrl } from 'utils'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { ModalProps } from '../EmbedButton'
|
||||||
@@ -37,28 +39,30 @@ export const NotionModal = ({
|
|||||||
{!isPublished && (
|
{!isPublished && (
|
||||||
<AlertInfo mb="4">You need to publish your bot first.</AlertInfo>
|
<AlertInfo mb="4">You need to publish your bot first.</AlertInfo>
|
||||||
)}
|
)}
|
||||||
<OrderedList spacing={3}>
|
<OrderedList spacing={4}>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
Type <Tag>/embed</Tag>
|
Type <Code>/embed</Code>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
Paste your typebot URL
|
<Stack>
|
||||||
<InputGroup size="md" mt={2}>
|
<Text>Paste your typebot URL</Text>
|
||||||
<Input
|
<InputGroup size="sm">
|
||||||
pr="4.5rem"
|
<Input
|
||||||
type={'text'}
|
type={'text'}
|
||||||
defaultValue={`${
|
defaultValue={`${
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
|
||||||
}/${publicId}`}
|
|
||||||
/>
|
|
||||||
<InputRightElement width="4.5rem">
|
|
||||||
<CopyButton
|
|
||||||
textToCopy={`${
|
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
||||||
}/${publicId}`}
|
}/${publicId}`}
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
<InputRightElement width="60px">
|
||||||
</InputGroup>
|
<CopyButton
|
||||||
|
size="sm"
|
||||||
|
textToCopy={`${
|
||||||
|
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
||||||
|
}/${publicId}`}
|
||||||
|
/>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</Stack>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</OrderedList>
|
</OrderedList>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { isDefined } from '@udecode/plate-common'
|
||||||
|
import { EmbedModal } from '../EmbedModal'
|
||||||
|
import { JavascriptInstructions } from './Javascript/instructions/JavascriptInstructions'
|
||||||
|
import { ModalProps } from '../EmbedButton'
|
||||||
|
|
||||||
|
export const OtherModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
|
||||||
|
const [selectedEmbedType, setSelectedEmbedType] = useState<
|
||||||
|
'standard' | 'popup' | 'bubble' | undefined
|
||||||
|
>()
|
||||||
|
return (
|
||||||
|
<EmbedModal
|
||||||
|
titlePrefix="Other"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={onClose}
|
||||||
|
isPublished={isPublished}
|
||||||
|
onSelectEmbedType={setSelectedEmbedType}
|
||||||
|
selectedEmbedType={selectedEmbedType}
|
||||||
|
>
|
||||||
|
{isDefined(selectedEmbedType) && (
|
||||||
|
<JavascriptInstructions type={selectedEmbedType} />
|
||||||
|
)}
|
||||||
|
</EmbedModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
|
||||||
|
export const InstallReactPackageSnippet = () => {
|
||||||
|
return (
|
||||||
|
<CodeEditor
|
||||||
|
value={`npm install @typebot.io/js @typebot.io/react`}
|
||||||
|
isReadOnly
|
||||||
|
lang="shell"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
|
import parserBabel from 'prettier/parser-babel'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import { parseReactBubbleProps } from '../../snippetParsers'
|
||||||
|
|
||||||
|
export const ReactBubbleSnippet = ({
|
||||||
|
theme,
|
||||||
|
previewMessage,
|
||||||
|
}: Pick<BubbleProps, 'theme' | 'previewMessage'>) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
|
||||||
|
const snippet = prettier.format(
|
||||||
|
`import { Bubble } from "@typebot.io/react";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return <Bubble ${parseReactBubbleProps({
|
||||||
|
typebot: typebot?.publicId ?? '',
|
||||||
|
theme,
|
||||||
|
previewMessage,
|
||||||
|
})}/>
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
parser: 'babel',
|
||||||
|
plugins: [parserBabel],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
|
||||||
|
}
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { CodeEditor } from '@/components/CodeEditor'
|
|
||||||
import { Stack, Text } from '@chakra-ui/react'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { BubbleParams } from 'typebot-js'
|
|
||||||
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
|
|
||||||
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
|
|
||||||
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
|
|
||||||
import {
|
|
||||||
StandardReactDiv,
|
|
||||||
PopupReactCode,
|
|
||||||
ChatReactCode,
|
|
||||||
} from '../../codeSnippets/ReactCode'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
type: 'standard' | 'popup' | 'bubble'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ReactInstructions = ({ type }: Props) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'standard': {
|
|
||||||
return <StandardInstructions />
|
|
||||||
}
|
|
||||||
case 'popup': {
|
|
||||||
return <PopupInstructions />
|
|
||||||
}
|
|
||||||
case 'bubble': {
|
|
||||||
return <BubbleInstructions />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const StandardInstructions = () => {
|
|
||||||
const [inputValues, setInputValues] = useState({
|
|
||||||
heightLabel: '100%',
|
|
||||||
widthLabel: '100%',
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<InstallPackageInstruction />
|
|
||||||
<StandardEmbedWindowSettings
|
|
||||||
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
|
|
||||||
/>
|
|
||||||
<Text>Insert the typebot container</Text>
|
|
||||||
<StandardReactDiv {...inputValues} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PopupInstructions = () => {
|
|
||||||
const [inputValue, setInputValue] = useState<number>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<InstallPackageInstruction />
|
|
||||||
<PopupEmbedSettings
|
|
||||||
onUpdateSettings={(settings) => setInputValue(settings.delay)}
|
|
||||||
/>
|
|
||||||
<Text>Initialize the typebot</Text>
|
|
||||||
<PopupReactCode withStarterVariables={true} delay={inputValue} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const BubbleInstructions = () => {
|
|
||||||
const [inputValues, setInputValues] = useState<
|
|
||||||
Pick<BubbleParams, 'proactiveMessage' | 'button'>
|
|
||||||
>({
|
|
||||||
proactiveMessage: undefined,
|
|
||||||
button: {
|
|
||||||
color: '',
|
|
||||||
iconUrl: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<InstallPackageInstruction />
|
|
||||||
<ChatEmbedSettings
|
|
||||||
onUpdateSettings={(settings) => setInputValues({ ...settings })}
|
|
||||||
/>
|
|
||||||
<Text>Initialize the typebot</Text>
|
|
||||||
<ChatReactCode withStarterVariables={true} {...inputValues} mt={4} />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const InstallPackageInstruction = () => {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Text>Install the package:</Text>
|
|
||||||
<CodeEditor value={`npm install typebot-js`} isReadOnly />
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,65 +1,25 @@
|
|||||||
import {
|
|
||||||
Modal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalCloseButton,
|
|
||||||
ModalBody,
|
|
||||||
ModalFooter,
|
|
||||||
IconButton,
|
|
||||||
Heading,
|
|
||||||
HStack,
|
|
||||||
} from '@chakra-ui/react'
|
|
||||||
import { ChevronLeftIcon } from '@/components/icons'
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ModalProps } from '../../EmbedButton'
|
import { ModalProps } from '../../EmbedButton'
|
||||||
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
|
import { EmbedModal } from '../../EmbedModal'
|
||||||
import { capitalize } from 'utils'
|
import { isDefined } from '@udecode/plate-common'
|
||||||
import { ReactInstructions } from './ReactInstructions'
|
import { ReactInstructions } from './instructions/ReactInstructions'
|
||||||
import { AlertInfo } from '@/components/AlertInfo'
|
|
||||||
|
|
||||||
export const ReactModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
|
export const ReactModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
|
||||||
const [chosenEmbedType, setChosenEmbedType] = useState<
|
const [selectedEmbedType, setSelectedEmbedType] = useState<
|
||||||
'standard' | 'popup' | 'bubble' | undefined
|
'standard' | 'popup' | 'bubble' | undefined
|
||||||
>()
|
>()
|
||||||
return (
|
return (
|
||||||
<Modal
|
<EmbedModal
|
||||||
|
titlePrefix="React"
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
size={!chosenEmbedType ? '2xl' : 'xl'}
|
isPublished={isPublished}
|
||||||
|
onSelectEmbedType={setSelectedEmbedType}
|
||||||
|
selectedEmbedType={selectedEmbedType}
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
{isDefined(selectedEmbedType) && (
|
||||||
<ModalContent>
|
<ReactInstructions type={selectedEmbedType} />
|
||||||
<ModalHeader>
|
)}
|
||||||
<HStack>
|
</EmbedModal>
|
||||||
{chosenEmbedType && (
|
|
||||||
<IconButton
|
|
||||||
icon={<ChevronLeftIcon />}
|
|
||||||
aria-label="back"
|
|
||||||
variant="ghost"
|
|
||||||
colorScheme="gray"
|
|
||||||
mr={2}
|
|
||||||
onClick={() => setChosenEmbedType(undefined)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Heading size="md">
|
|
||||||
React {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
|
|
||||||
</Heading>
|
|
||||||
</HStack>
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
|
||||||
{!isPublished && (
|
|
||||||
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
|
|
||||||
)}
|
|
||||||
{!chosenEmbedType ? (
|
|
||||||
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
|
|
||||||
) : (
|
|
||||||
<ReactInstructions type={chosenEmbedType} />
|
|
||||||
)}
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter />
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { PopupProps } from '@typebot.io/js'
|
||||||
|
import parserBabel from 'prettier/parser-babel'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import { parseReactPopupProps } from '../../snippetParsers'
|
||||||
|
|
||||||
|
export const ReactPopupSnippet = ({
|
||||||
|
autoShowDelay,
|
||||||
|
}: Pick<PopupProps, 'autoShowDelay'>) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
|
||||||
|
const snippet = prettier.format(
|
||||||
|
`import { Popup } from "@typebot.io/react";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return <Popup ${parseReactPopupProps({
|
||||||
|
typebot: typebot?.publicId ?? '',
|
||||||
|
autoShowDelay,
|
||||||
|
})}/>;
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
parser: 'babel',
|
||||||
|
plugins: [parserBabel],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { CodeEditor } from '@/components/CodeEditor'
|
||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import parserBabel from 'prettier/parser-babel'
|
||||||
|
import prettier from 'prettier/standalone'
|
||||||
|
import { parseReactBotProps } from '../../snippetParsers'
|
||||||
|
|
||||||
|
type ReactStandardSnippetProps = { widthLabel?: string; heightLabel: string }
|
||||||
|
|
||||||
|
export const ReactStandardSnippet = ({
|
||||||
|
widthLabel,
|
||||||
|
heightLabel,
|
||||||
|
}: ReactStandardSnippetProps) => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
const snippet = prettier.format(
|
||||||
|
`import { Standard } from "@typebot.io/react";
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return <Standard ${parseReactBotProps({
|
||||||
|
typebot: typebot?.publicId ?? '',
|
||||||
|
})} style={{width: "${widthLabel}", height: "${heightLabel}"}} />
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
parser: 'babel',
|
||||||
|
plugins: [parserBabel],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { useTypebot } from '@/features/editor'
|
||||||
|
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
|
||||||
|
import { BubbleProps } from '@typebot.io/js'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||||
|
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
|
||||||
|
import { ReactBubbleSnippet } from '../ReactBubbleSnippet'
|
||||||
|
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
|
||||||
|
|
||||||
|
export const ReactBubbleInstructions = () => {
|
||||||
|
const { typebot } = useTypebot()
|
||||||
|
const [theme, setTheme] = useState<BubbleProps['theme']>(
|
||||||
|
parseDefaultBubbleTheme(typebot)
|
||||||
|
)
|
||||||
|
const [previewMessage, setPreviewMessage] =
|
||||||
|
useState<BubbleProps['previewMessage']>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderedList spacing={4} pl={5}>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Text>Install the packages</Text>
|
||||||
|
<InstallReactPackageSnippet />
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<BubbleSettings
|
||||||
|
theme={theme}
|
||||||
|
previewMessage={previewMessage}
|
||||||
|
defaultPreviewMessageAvatar={
|
||||||
|
typebot?.theme.chat.hostAvatar?.url ?? ''
|
||||||
|
}
|
||||||
|
onThemeChange={setTheme}
|
||||||
|
onPreviewMessageChange={setPreviewMessage}
|
||||||
|
/>
|
||||||
|
<ReactBubbleSnippet theme={theme} previewMessage={previewMessage} />
|
||||||
|
</Stack>
|
||||||
|
</ListItem>
|
||||||
|
</OrderedList>
|
||||||
|
)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user