feat(viewer): ✨ Add variables in URL support
This commit is contained in:
@ -13,8 +13,16 @@ export const headerHeight = 56
|
|||||||
|
|
||||||
export const TypebotHeader = () => {
|
export const TypebotHeader = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { typebot, updateOnBothTypebots, save, undo, redo, canUndo, canRedo } =
|
const {
|
||||||
useTypebot()
|
typebot,
|
||||||
|
updateOnBothTypebots,
|
||||||
|
save,
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
canUndo,
|
||||||
|
canRedo,
|
||||||
|
publishedTypebot,
|
||||||
|
} = useTypebot()
|
||||||
const { setRightPanel } = useEditor()
|
const { setRightPanel } = useEditor()
|
||||||
|
|
||||||
const handleBackClick = async () => {
|
const handleBackClick = async () => {
|
||||||
@ -77,14 +85,16 @@ export const TypebotHeader = () => {
|
|||||||
>
|
>
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{typebot?.publishedTypebotId && (
|
||||||
as={NextChakraLink}
|
<Button
|
||||||
href={`/typebots/${typebot?.id}/results`}
|
as={NextChakraLink}
|
||||||
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
|
href={`/typebots/${typebot?.id}/results`}
|
||||||
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
|
colorScheme={router.pathname.includes('results') ? 'blue' : 'gray'}
|
||||||
>
|
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
|
||||||
Results
|
>
|
||||||
</Button>
|
Results
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<Flex pos="absolute" left="1rem" justify="center" align="center">
|
<Flex pos="absolute" left="1rem" justify="center" align="center">
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
} from 'services/typebots'
|
} from 'services/typebots'
|
||||||
import { fetcher, preventUserFromRefreshing } from 'services/utils'
|
import { fetcher, preventUserFromRefreshing } from 'services/utils'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined, isNotDefined } from 'utils'
|
||||||
import { BlocksActions, blocksActions } from './actions/blocks'
|
import { BlocksActions, blocksActions } from './actions/blocks'
|
||||||
import { stepsAction, StepsActions } from './actions/steps'
|
import { stepsAction, StepsActions } from './actions/steps'
|
||||||
import { variablesAction, VariablesActions } from './actions/variables'
|
import { variablesAction, VariablesActions } from './actions/variables'
|
||||||
@ -217,6 +217,13 @@ export const TypebotContext = ({
|
|||||||
if (!localTypebot) return
|
if (!localTypebot) return
|
||||||
const publishedTypebotId = generate()
|
const publishedTypebotId = generate()
|
||||||
const newLocalTypebot = { ...localTypebot }
|
const newLocalTypebot = { ...localTypebot }
|
||||||
|
if (
|
||||||
|
localPublishedTypebot &&
|
||||||
|
isNotDefined(localTypebot.publishedTypebotId)
|
||||||
|
) {
|
||||||
|
updateLocalTypebot({ publishedTypebotId: localPublishedTypebot.id })
|
||||||
|
await saveTypebot()
|
||||||
|
}
|
||||||
if (!localPublishedTypebot) {
|
if (!localPublishedTypebot) {
|
||||||
const newPublicId = parseDefaultPublicId(
|
const newPublicId = parseDefaultPublicId(
|
||||||
localTypebot.name,
|
localTypebot.name,
|
||||||
@ -224,8 +231,8 @@ export const TypebotContext = ({
|
|||||||
)
|
)
|
||||||
updateLocalTypebot({ publicId: newPublicId, publishedTypebotId })
|
updateLocalTypebot({ publicId: newPublicId, publishedTypebotId })
|
||||||
newLocalTypebot.publicId = newPublicId
|
newLocalTypebot.publicId = newPublicId
|
||||||
|
await saveTypebot()
|
||||||
}
|
}
|
||||||
if (hasUnsavedChanges || !localPublishedTypebot) await saveTypebot()
|
|
||||||
if (localPublishedTypebot) {
|
if (localPublishedTypebot) {
|
||||||
await savePublishedTypebot({
|
await savePublishedTypebot({
|
||||||
...parseTypebotToPublicTypebot(newLocalTypebot),
|
...parseTypebotToPublicTypebot(newLocalTypebot),
|
||||||
|
@ -6,13 +6,14 @@ import { useRouter } from 'next/router'
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useStats } from 'services/analytics'
|
import { useStats } from 'services/analytics'
|
||||||
import { isFreePlan } from 'services/user'
|
import { isFreePlan } from 'services/user'
|
||||||
|
import { isDefined } from 'utils'
|
||||||
import { AnalyticsContent } from './AnalyticsContent'
|
import { AnalyticsContent } from './AnalyticsContent'
|
||||||
import { SubmissionsContent } from './SubmissionContent'
|
import { SubmissionsContent } from './SubmissionContent'
|
||||||
|
|
||||||
export const ResultsContent = () => {
|
export const ResultsContent = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const { typebot } = useTypebot()
|
const { typebot, publishedTypebot } = useTypebot()
|
||||||
const isAnalytics = useMemo(
|
const isAnalytics = useMemo(
|
||||||
() => router.pathname.endsWith('analytics'),
|
() => router.pathname.endsWith('analytics'),
|
||||||
[router.pathname]
|
[router.pathname]
|
||||||
@ -23,7 +24,7 @@ export const ResultsContent = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { stats, mutate } = useStats({
|
const { stats, mutate } = useStats({
|
||||||
typebotId: typebot?.id,
|
typebotId: publishedTypebot?.typebotId,
|
||||||
onError: (err) => toast({ title: err.name, description: err.message }),
|
onError: (err) => toast({ title: err.name, description: err.message }),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -71,12 +72,12 @@ export const ResultsContent = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex pt="60px" w="full" justify="center">
|
<Flex pt="60px" w="full" justify="center">
|
||||||
{typebot &&
|
{publishedTypebot &&
|
||||||
(isAnalytics ? (
|
(isAnalytics ? (
|
||||||
<AnalyticsContent stats={stats} />
|
<AnalyticsContent stats={stats} />
|
||||||
) : (
|
) : (
|
||||||
<SubmissionsContent
|
<SubmissionsContent
|
||||||
typebotId={typebot.id}
|
typebotId={publishedTypebot.typebotId}
|
||||||
onDeleteResults={handleDeletedResults}
|
onDeleteResults={handleDeletedResults}
|
||||||
totalResults={stats?.totalStarts ?? 0}
|
totalResults={stats?.totalStarts ?? 0}
|
||||||
totalHiddenResults={
|
totalHiddenResults={
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
useResults,
|
useResults,
|
||||||
} from 'services/results'
|
} from 'services/results'
|
||||||
import { unparse } from 'papaparse'
|
import { unparse } from 'papaparse'
|
||||||
import { PublishFirstInfo, UnlockProPlanInfo } from 'components/shared/Info'
|
import { UnlockProPlanInfo } from 'components/shared/Info'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
typebotId: string
|
typebotId: string
|
||||||
@ -111,7 +111,6 @@ export const SubmissionsContent = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack maxW="1200px" w="full" pb="28">
|
<Stack maxW="1200px" w="full" pb="28">
|
||||||
<PublishFirstInfo />
|
|
||||||
{totalHiddenResults && (
|
{totalHiddenResults && (
|
||||||
<UnlockProPlanInfo
|
<UnlockProPlanInfo
|
||||||
buttonLabel={`Unlock ${totalHiddenResults} results`}
|
buttonLabel={`Unlock ${totalHiddenResults} results`}
|
||||||
|
@ -0,0 +1,355 @@
|
|||||||
|
{
|
||||||
|
"id": "8hASqMTjwFVzB32vRcVND5",
|
||||||
|
"createdAt": "2022-02-05T06:21:16.522Z",
|
||||||
|
"updatedAt": "2022-02-05T06:21:16.522Z",
|
||||||
|
"name": "My typebot",
|
||||||
|
"ownerId": "ckzmhmiey001009mnzt5nkxu8",
|
||||||
|
"publishedTypebotId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "k6kY6gwRE6noPoYQNGzgUq",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "22HP69iipkLjJDTUcc1AWW",
|
||||||
|
"type": "start",
|
||||||
|
"label": "Start",
|
||||||
|
"blockId": "k6kY6gwRE6noPoYQNGzgUq",
|
||||||
|
"outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Start",
|
||||||
|
"graphCoordinates": { "x": 0, "y": 0 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "kinRXxYop2X4d7F9qt8WNB",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "sc1y8VwDabNJgiVTBi4qtif",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "kinRXxYop2X4d7F9qt8WNB",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>Welcome to <span class=\"slate-bold\">AA</span> (Awesome Agency)</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [
|
||||||
|
{ "text": "Welcome to " },
|
||||||
|
{ "bold": true, "text": "AA" },
|
||||||
|
{ "text": " (Awesome Agency)" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "Welcome to AA (Awesome Agency)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "s7YqZTBeyCa4Hp3wN2j922c",
|
||||||
|
"type": "image",
|
||||||
|
"blockId": "kinRXxYop2X4d7F9qt8WNB",
|
||||||
|
"content": {
|
||||||
|
"url": "https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sbjZWLJGVkHAkDqS4JQeGow",
|
||||||
|
"type": "choice input",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "hQw2zbp7FDX7XYK9cFpbgC",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "sbjZWLJGVkHAkDqS4JQeGow",
|
||||||
|
"content": "Hi!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockId": "kinRXxYop2X4d7F9qt8WNB",
|
||||||
|
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
||||||
|
"outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Welcome",
|
||||||
|
"graphCoordinates": { "x": 0, "y": 154 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "o4SH1UtKANnW5N5D67oZUz",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "sxeYubYN6XzhAfG7m9Fivhc",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "o4SH1UtKANnW5N5D67oZUz",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>Great! Nice to meet you {{Name}}</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [{ "text": "Great! Nice to meet you {{Name}}" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "Great! Nice to meet you {{Name}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scQ5kduafAtfP9T8SHUJnGi",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "o4SH1UtKANnW5N5D67oZUz",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>What's the best email we can reach you at?</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [
|
||||||
|
{ "text": "What's the best email we can reach you at?" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "What's the best email we can reach you at?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "snbsad18Bgry8yZ8DZCfdFD",
|
||||||
|
"type": "email input",
|
||||||
|
"blockId": "o4SH1UtKANnW5N5D67oZUz",
|
||||||
|
"options": {
|
||||||
|
"labels": { "button": "Send", "placeholder": "Type your email..." },
|
||||||
|
"variableId": "3VFChNVSCXQ2rXv4DrJ8Ah"
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "w3MiN1Ct38jT5NykVsgmb5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Email",
|
||||||
|
"graphCoordinates": { "x": 639, "y": 142 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "sgtE2Sy7cKykac9B223Kq9R",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>What's your name?</div>",
|
||||||
|
"richText": [
|
||||||
|
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
||||||
|
],
|
||||||
|
"plainText": "What's your name?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sqEsMo747LTDnY9FjQcEwUv",
|
||||||
|
"type": "text input",
|
||||||
|
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw",
|
||||||
|
"options": {
|
||||||
|
"isLong": false,
|
||||||
|
"labels": {
|
||||||
|
"button": "Send",
|
||||||
|
"placeholder": "Type your answer..."
|
||||||
|
},
|
||||||
|
"variableId": "giiLFGw5xXBCHzvp1qAbdX"
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "4tYbERpi5Po4goVgt6rWXg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Name",
|
||||||
|
"graphCoordinates": { "x": 322, "y": 296 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fKqRz7iswk7ULaj5PJocZL",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "su7HceVXWyTCzi2vv3m4QbK",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "fKqRz7iswk7ULaj5PJocZL",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>What services are you interested in?</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [{ "text": "What services are you interested in?" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "What services are you interested in?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"type": "choice input",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "fnLCBF4NdraSwcubnBhk8H",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"content": "Website dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a782h8ynMouY84QjH7XSnR",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"content": "Content Marketing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "jGvh94zBByvVFpSS3w97zY",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"content": "Social Media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6PRLbKUezuFmwWtLVbvAQ7",
|
||||||
|
"type": 0,
|
||||||
|
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"content": "UI / UX Design"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockId": "fKqRz7iswk7ULaj5PJocZL",
|
||||||
|
"options": { "buttonLabel": "Send", "isMultipleChoice": true },
|
||||||
|
"outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Services",
|
||||||
|
"graphCoordinates": { "x": 943, "y": -1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7qHBEyCMvKEJryBHzPmHjV",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "sqR8Sz9gW21aUYKtUikq7qZ",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "7qHBEyCMvKEJryBHzPmHjV",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>Can you tell me a bit more about your needs?</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [
|
||||||
|
{ "text": "Can you tell me a bit more about your needs?" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "Can you tell me a bit more about your needs?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sqFy2G3C1mh9p6s3QBdSS5x",
|
||||||
|
"type": "text input",
|
||||||
|
"blockId": "7qHBEyCMvKEJryBHzPmHjV",
|
||||||
|
"options": {
|
||||||
|
"isLong": true,
|
||||||
|
"labels": { "button": "Send", "placeholder": "Type your answer..." }
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "sH5nUssG2XQbm6ZidGv9BY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Additional information",
|
||||||
|
"graphCoordinates": { "x": 1308, "y": 42 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vF7AD7zSAj7SNvN3gr9N94",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"id": "seLegenCgUwMopRFeAefqZ7",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "vF7AD7zSAj7SNvN3gr9N94",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>Perfect!</div>",
|
||||||
|
"richText": [{ "type": "p", "children": [{ "text": "Perfect!" }] }],
|
||||||
|
"plainText": "Perfect!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "s779Q1y51aVaDUJVrFb16vv",
|
||||||
|
"type": "text",
|
||||||
|
"blockId": "vF7AD7zSAj7SNvN3gr9N94",
|
||||||
|
"content": {
|
||||||
|
"html": "<div>We'll get back to you at {{Email}}</div>",
|
||||||
|
"richText": [
|
||||||
|
{
|
||||||
|
"type": "p",
|
||||||
|
"children": [{ "text": "We'll get back to you at {{Email}}" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plainText": "We'll get back to you at {{Email}}"
|
||||||
|
},
|
||||||
|
"outgoingEdgeId": "r2zwZYe33EdggUeG9Lmi3R"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Bye",
|
||||||
|
"graphCoordinates": { "x": 1668, "y": 143 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variables": [
|
||||||
|
{ "id": "giiLFGw5xXBCHzvp1qAbdX", "name": "Name" },
|
||||||
|
{ "id": "3VFChNVSCXQ2rXv4DrJ8Ah", "name": "Email" },
|
||||||
|
{ "id": "8Q8t9YCc3ieAEXSYkmnCxH", "name": "utm_source" },
|
||||||
|
{ "id": "ds54chWAyWC4zjkdWAm3Vc", "name": "utm_userid" }
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "oNvqaqNExdSH2kKEhKZHuE",
|
||||||
|
"to": { "blockId": "kinRXxYop2X4d7F9qt8WNB" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "22HP69iipkLjJDTUcc1AWW",
|
||||||
|
"blockId": "k6kY6gwRE6noPoYQNGzgUq"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "i51YhHpk1dtSyduFNf5Wim",
|
||||||
|
"to": { "blockId": "q5dAhqSTCaNdiGSJm9B9Rw" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "sbjZWLJGVkHAkDqS4JQeGow",
|
||||||
|
"blockId": "kinRXxYop2X4d7F9qt8WNB"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4tYbERpi5Po4goVgt6rWXg",
|
||||||
|
"to": { "blockId": "o4SH1UtKANnW5N5D67oZUz" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "sqEsMo747LTDnY9FjQcEwUv",
|
||||||
|
"blockId": "q5dAhqSTCaNdiGSJm9B9Rw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "w3MiN1Ct38jT5NykVsgmb5",
|
||||||
|
"to": { "blockId": "fKqRz7iswk7ULaj5PJocZL" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "snbsad18Bgry8yZ8DZCfdFD",
|
||||||
|
"blockId": "o4SH1UtKANnW5N5D67oZUz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ohTRakmcYJ7GdFWRZrWRjk",
|
||||||
|
"to": { "blockId": "7qHBEyCMvKEJryBHzPmHjV" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"blockId": "fKqRz7iswk7ULaj5PJocZL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sH5nUssG2XQbm6ZidGv9BY",
|
||||||
|
"to": { "blockId": "vF7AD7zSAj7SNvN3gr9N94" },
|
||||||
|
"from": {
|
||||||
|
"stepId": "sqFy2G3C1mh9p6s3QBdSS5x",
|
||||||
|
"blockId": "7qHBEyCMvKEJryBHzPmHjV"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme": {
|
||||||
|
"chat": {
|
||||||
|
"inputs": {
|
||||||
|
"color": "#303235",
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"placeholderColor": "#9095A0"
|
||||||
|
},
|
||||||
|
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
||||||
|
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
|
||||||
|
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
||||||
|
},
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"general": { "isBrandingEnabled": true },
|
||||||
|
"metadata": {
|
||||||
|
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
|
||||||
|
},
|
||||||
|
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
|
||||||
|
},
|
||||||
|
"publicId": null
|
||||||
|
}
|
@ -160,7 +160,7 @@ const parseTestTypebot = (partialTypebot: Partial<Typebot>): Typebot => ({
|
|||||||
publicId: null,
|
publicId: null,
|
||||||
publishedTypebotId: null,
|
publishedTypebotId: null,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
variables: [],
|
variables: [{ id: 'var1', name: 'var1' }],
|
||||||
...partialTypebot,
|
...partialTypebot,
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
@ -207,7 +207,7 @@ export const parseDefaultBlockWithStep = (
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const importTypebotInDatabase = (
|
export const importTypebotInDatabase = async (
|
||||||
path: string,
|
path: string,
|
||||||
updates?: Partial<Typebot>
|
updates?: Partial<Typebot>
|
||||||
) => {
|
) => {
|
||||||
@ -216,7 +216,13 @@ export const importTypebotInDatabase = (
|
|||||||
...updates,
|
...updates,
|
||||||
ownerId: 'proUser',
|
ownerId: 'proUser',
|
||||||
}
|
}
|
||||||
return prisma.typebot.create({
|
await prisma.typebot.create({
|
||||||
data: typebot,
|
data: typebot,
|
||||||
})
|
})
|
||||||
|
return prisma.publicTypebot.create({
|
||||||
|
data: parseTypebotToPublicTypebot(
|
||||||
|
updates?.id ? `${updates?.id}-public` : 'publicBot',
|
||||||
|
typebot
|
||||||
|
),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { defaultTextInputOptions, InputStepType } from 'models'
|
|||||||
import { typebotViewer } from '../../services/selectorUtils'
|
import { typebotViewer } from '../../services/selectorUtils'
|
||||||
import { generate } from 'short-uuid'
|
import { generate } from 'short-uuid'
|
||||||
|
|
||||||
test.describe('Text input step', () => {
|
test.describe.parallel('Text input step', () => {
|
||||||
test('options should work', async ({ page }) => {
|
test('options should work', async ({ page }) => {
|
||||||
const typebotId = generate()
|
const typebotId = generate()
|
||||||
await createTypebots([
|
await createTypebots([
|
||||||
@ -41,4 +41,26 @@ test.describe('Text input step', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
|
await expect(typebotViewer(page).locator(`text=Go`)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('variable in URL should prefill the input', async ({ page }) => {
|
||||||
|
const typebotId = generate()
|
||||||
|
await createTypebots([
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
...parseDefaultBlockWithStep({
|
||||||
|
type: InputStepType.TEXT,
|
||||||
|
options: { ...defaultTextInputOptions, variableId: 'var1' },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
await page.goto(`/typebots/${typebotId}/edit?var1=My prefilled answer`)
|
||||||
|
await page.click('text=Preview')
|
||||||
|
await expect(
|
||||||
|
typebotViewer(page).locator(
|
||||||
|
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
|
||||||
|
)
|
||||||
|
).toHaveAttribute('value', 'My prefilled answer')
|
||||||
|
await expect(typebotViewer(page).locator(`button`)).toBeEnabled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -7,6 +7,7 @@ import { generate } from 'short-uuid'
|
|||||||
import {
|
import {
|
||||||
createResults,
|
createResults,
|
||||||
createTypebots,
|
createTypebots,
|
||||||
|
importTypebotInDatabase,
|
||||||
parseDefaultBlockWithStep,
|
parseDefaultBlockWithStep,
|
||||||
} from '../services/database'
|
} from '../services/database'
|
||||||
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
|
import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
|
||||||
@ -14,6 +15,30 @@ import { deleteButtonInConfirmDialog } from '../services/selectorUtils'
|
|||||||
const typebotId = generate()
|
const typebotId = generate()
|
||||||
|
|
||||||
test.describe('Results page', () => {
|
test.describe('Results page', () => {
|
||||||
|
test('Submission table header should be parsed correctly', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const typebotId = generate()
|
||||||
|
await importTypebotInDatabase(
|
||||||
|
path.join(
|
||||||
|
__dirname,
|
||||||
|
'../fixtures/typebots/results/submissionHeader.json'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
id: typebotId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await page.goto(`/typebots/${typebotId}/results`)
|
||||||
|
await expect(page.locator('text=Submitted at')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Welcome')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Email')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Name')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Services')).toBeVisible()
|
||||||
|
await expect(page.locator('text=Additional information')).toBeVisible()
|
||||||
|
await expect(page.locator('text=utm_source')).toBeVisible()
|
||||||
|
await expect(page.locator('text=utm_userid')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('results should be deletable', async ({ page }) => {
|
test('results should be deletable', async ({ page }) => {
|
||||||
await createTypebots([
|
await createTypebots([
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ test.describe.parallel('Theme page', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Chat', () => {
|
test.describe('Chat', () => {
|
||||||
test.only('should reflect change in real-time', async ({ page }) => {
|
test('should reflect change in real-time', async ({ page }) => {
|
||||||
const typebotId = 'chat-theme-typebot'
|
const typebotId = 'chat-theme-typebot'
|
||||||
try {
|
try {
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Block, PublicBlock, PublicStep, PublicTypebot, Typebot } from 'models'
|
import { Block, PublicBlock, PublicStep, PublicTypebot, Typebot } from 'models'
|
||||||
import shortId from 'short-uuid'
|
import shortId from 'short-uuid'
|
||||||
import { HStack, Text } from '@chakra-ui/react'
|
import { HStack, Text } from '@chakra-ui/react'
|
||||||
import { CalendarIcon } from 'assets/icons'
|
import { CalendarIcon, CodeIcon } from 'assets/icons'
|
||||||
import { StepIcon } from 'components/editor/StepsSideBar/StepIcon'
|
import { StepIcon } from 'components/editor/StepsSideBar/StepIcon'
|
||||||
import { isInputStep, sendRequest } from 'utils'
|
import { isInputStep, sendRequest } from 'utils'
|
||||||
import { isDefined } from '@udecode/plate-common'
|
import { isDefined } from '@udecode/plate-common'
|
||||||
@ -64,30 +64,58 @@ export const parseSubmissionsColumns = (
|
|||||||
),
|
),
|
||||||
accessor: 'createdAt',
|
accessor: 'createdAt',
|
||||||
},
|
},
|
||||||
...typebot.blocks
|
...parseBlocksHeaders(typebot),
|
||||||
.filter(
|
...parseVariablesHeaders(typebot),
|
||||||
(block) => typebot && block.steps.some((step) => isInputStep(step))
|
|
||||||
)
|
|
||||||
.map((block) => {
|
|
||||||
const inputStep = block.steps.find((step) => isInputStep(step))
|
|
||||||
if (!inputStep || !isInputStep(inputStep)) return
|
|
||||||
return {
|
|
||||||
Header: (
|
|
||||||
<HStack
|
|
||||||
minW={
|
|
||||||
'isLong' in inputStep.options && inputStep.options.isLong
|
|
||||||
? '400px'
|
|
||||||
: '150px'
|
|
||||||
}
|
|
||||||
maxW="500px"
|
|
||||||
>
|
|
||||||
<StepIcon type={inputStep.type} />
|
|
||||||
<Text>{block.title}</Text>
|
|
||||||
</HStack>
|
|
||||||
),
|
|
||||||
accessor: block.id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(isDefined),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseBlocksHeaders = (typebot: PublicTypebot) =>
|
||||||
|
typebot.blocks
|
||||||
|
.filter((block) => typebot && block.steps.some((step) => isInputStep(step)))
|
||||||
|
.map((block) => {
|
||||||
|
const inputStep = block.steps.find((step) => isInputStep(step))
|
||||||
|
if (!inputStep || !isInputStep(inputStep)) return
|
||||||
|
return {
|
||||||
|
Header: (
|
||||||
|
<HStack
|
||||||
|
minW={
|
||||||
|
'isLong' in inputStep.options && inputStep.options.isLong
|
||||||
|
? '400px'
|
||||||
|
: '150px'
|
||||||
|
}
|
||||||
|
maxW="500px"
|
||||||
|
>
|
||||||
|
<StepIcon type={inputStep.type} />
|
||||||
|
<Text>{block.title}</Text>
|
||||||
|
</HStack>
|
||||||
|
),
|
||||||
|
accessor: block.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(isDefined)
|
||||||
|
|
||||||
|
const parseVariablesHeaders = (typebot: PublicTypebot) =>
|
||||||
|
typebot.variables
|
||||||
|
.map((v) => {
|
||||||
|
const isVariableInInputStep = isDefined(
|
||||||
|
typebot.blocks.find((b) => {
|
||||||
|
const inputStep = b.steps.find((step) => isInputStep(step))
|
||||||
|
return (
|
||||||
|
inputStep &&
|
||||||
|
isInputStep(inputStep) &&
|
||||||
|
inputStep.options.variableId === v.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (isVariableInInputStep) return
|
||||||
|
return {
|
||||||
|
Header: (
|
||||||
|
<HStack minW={'150px'} maxW="500px">
|
||||||
|
<CodeIcon />
|
||||||
|
<Text>{v.name}</Text>
|
||||||
|
</HStack>
|
||||||
|
),
|
||||||
|
accessor: v.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(isDefined)
|
||||||
|
@ -4,7 +4,7 @@ import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
|
|||||||
import { InputStep, InputStepType, PublicStep } from 'models'
|
import { InputStep, InputStepType, PublicStep } from 'models'
|
||||||
import { GuestBubble } from './bubbles/GuestBubble'
|
import { GuestBubble } from './bubbles/GuestBubble'
|
||||||
import { TextForm } from './inputs/TextForm'
|
import { TextForm } from './inputs/TextForm'
|
||||||
import { isBubbleStep, isInputStep } from 'utils'
|
import { byId, isBubbleStep, isInputStep } from 'utils'
|
||||||
import { DateForm } from './inputs/DateForm'
|
import { DateForm } from './inputs/DateForm'
|
||||||
import { ChoiceForm } from './inputs/ChoiceForm'
|
import { ChoiceForm } from './inputs/ChoiceForm'
|
||||||
import { HostBubble } from './bubbles/HostBubble'
|
import { HostBubble } from './bubbles/HostBubble'
|
||||||
@ -43,6 +43,9 @@ const InputChatStep = ({
|
|||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const { addNewAvatarOffset } = useHostAvatars()
|
const { addNewAvatarOffset } = useHostAvatars()
|
||||||
const [answer, setAnswer] = useState<string>()
|
const [answer, setAnswer] = useState<string>()
|
||||||
|
const { variableId } = step.options
|
||||||
|
const defaultValue =
|
||||||
|
variableId && typebot.variables.find(byId(variableId))?.value
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addNewAvatarOffset()
|
addNewAvatarOffset()
|
||||||
@ -71,7 +74,13 @@ const InputChatStep = ({
|
|||||||
case InputStepType.EMAIL:
|
case InputStepType.EMAIL:
|
||||||
case InputStepType.URL:
|
case InputStepType.URL:
|
||||||
case InputStepType.PHONE:
|
case InputStepType.PHONE:
|
||||||
return <TextForm step={step} onSubmit={handleSubmit} />
|
return (
|
||||||
|
<TextForm
|
||||||
|
step={step}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case InputStepType.DATE:
|
case InputStepType.DATE:
|
||||||
return <DateForm options={step.options} onSubmit={handleSubmit} />
|
return <DateForm options={step.options} onSubmit={handleSubmit} />
|
||||||
case InputStepType.CHOICE:
|
case InputStepType.CHOICE:
|
||||||
|
@ -17,10 +17,11 @@ type TextFormProps = {
|
|||||||
| UrlInputStep
|
| UrlInputStep
|
||||||
| PhoneNumberInputStep
|
| PhoneNumberInputStep
|
||||||
onSubmit: (value: string) => void
|
onSubmit: (value: string) => void
|
||||||
|
defaultValue?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextForm = ({ step, onSubmit }: TextFormProps) => {
|
export const TextForm = ({ step, onSubmit, defaultValue }: TextFormProps) => {
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState(defaultValue ?? '')
|
||||||
|
|
||||||
const handleChange = (inputValue: string) => setInputValue(inputValue)
|
const handleChange = (inputValue: string) => setInputValue(inputValue)
|
||||||
|
|
||||||
@ -38,7 +39,11 @@ export const TextForm = ({ step, onSubmit }: TextFormProps) => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
data-testid="input"
|
data-testid="input"
|
||||||
>
|
>
|
||||||
<TextInput step={step} onChange={handleChange} />
|
<TextInput
|
||||||
|
step={step}
|
||||||
|
onChange={handleChange}
|
||||||
|
defaultValue={defaultValue ?? ''}
|
||||||
|
/>
|
||||||
<SendButton
|
<SendButton
|
||||||
label={step.options?.labels?.button ?? 'Send'}
|
label={step.options?.labels?.button ?? 'Send'}
|
||||||
isDisabled={inputValue === ''}
|
isDisabled={inputValue === ''}
|
||||||
|
@ -22,10 +22,11 @@ type TextInputProps = {
|
|||||||
| NumberInputStep
|
| NumberInputStep
|
||||||
| UrlInputStep
|
| UrlInputStep
|
||||||
| PhoneNumberInputStep
|
| PhoneNumberInputStep
|
||||||
|
defaultValue: string
|
||||||
onChange: (value: string) => void
|
onChange: (value: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextInput = ({ step, onChange }: TextInputProps) => {
|
export const TextInput = ({ step, defaultValue, onChange }: TextInputProps) => {
|
||||||
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null)
|
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -49,6 +50,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
placeholder={
|
placeholder={
|
||||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||||
}
|
}
|
||||||
|
defaultValue={defaultValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -57,6 +59,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
placeholder={
|
placeholder={
|
||||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||||
}
|
}
|
||||||
|
defaultValue={defaultValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -68,6 +71,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
placeholder={
|
placeholder={
|
||||||
step.options?.labels?.placeholder ?? 'Type your email...'
|
step.options?.labels?.placeholder ?? 'Type your email...'
|
||||||
}
|
}
|
||||||
|
defaultValue={defaultValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
@ -80,6 +84,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
placeholder={
|
placeholder={
|
||||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||||
}
|
}
|
||||||
|
defaultValue={defaultValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
type="number"
|
type="number"
|
||||||
style={{ appearance: 'auto' }}
|
style={{ appearance: 'auto' }}
|
||||||
@ -94,6 +99,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
<ShortTextInput
|
<ShortTextInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder={step.options?.labels?.placeholder ?? 'Type your URL...'}
|
placeholder={step.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||||
|
defaultValue={defaultValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
type="url"
|
type="url"
|
||||||
/>
|
/>
|
||||||
@ -105,6 +111,7 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
ref={inputRef as any}
|
ref={inputRef as any}
|
||||||
onChange={handlePhoneNumberChange}
|
onChange={handlePhoneNumberChange}
|
||||||
|
defaultValue={defaultValue}
|
||||||
placeholder={
|
placeholder={
|
||||||
step.options?.labels?.placeholder ?? 'Your phone number...'
|
step.options?.labels?.placeholder ?? 'Your phone number...'
|
||||||
}
|
}
|
||||||
@ -131,7 +138,11 @@ const ShortTextInput = React.forwardRef(
|
|||||||
|
|
||||||
const LongTextInput = React.forwardRef(
|
const LongTextInput = React.forwardRef(
|
||||||
(
|
(
|
||||||
props: { placeholder: string; onChange: ChangeEventHandler },
|
props: {
|
||||||
|
placeholder: string
|
||||||
|
defaultValue: string
|
||||||
|
onChange: ChangeEventHandler
|
||||||
|
},
|
||||||
ref: React.ForwardedRef<HTMLTextAreaElement>
|
ref: React.ForwardedRef<HTMLTextAreaElement>
|
||||||
) => (
|
) => (
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -22,7 +22,7 @@ export const ConversationContainer = ({
|
|||||||
onNewAnswer,
|
onNewAnswer,
|
||||||
onCompleted,
|
onCompleted,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot, updateVariableValue } = useTypebot()
|
||||||
const { document: frameDocument } = useFrame()
|
const { document: frameDocument } = useFrame()
|
||||||
const [displayedBlocks, setDisplayedBlocks] = useState<
|
const [displayedBlocks, setDisplayedBlocks] = useState<
|
||||||
{ block: PublicBlock; startStepIndex: number }[]
|
{ block: PublicBlock; startStepIndex: number }[]
|
||||||
@ -48,10 +48,21 @@ export const ConversationContainer = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
injectUrlParamsIntoVariables()
|
||||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const injectUrlParamsIntoVariables = () => {
|
||||||
|
const urlParams = new URLSearchParams(location.search)
|
||||||
|
urlParams.forEach((value, key) => {
|
||||||
|
const matchingVariable = typebot.variables.find(
|
||||||
|
(v) => v.name.toLowerCase() === key.toLowerCase()
|
||||||
|
)
|
||||||
|
if (matchingVariable) updateVariableValue(matchingVariable?.id, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCssVariablesValue(theme, frameDocument.body.style)
|
setCssVariablesValue(theme, frameDocument.body.style)
|
||||||
}, [theme, frameDocument])
|
}, [theme, frameDocument])
|
||||||
|
@ -31,7 +31,10 @@ export const TypebotContext = ({
|
|||||||
const [localTypebot, setLocalTypebot] = useState<PublicTypebot>(typebot)
|
const [localTypebot, setLocalTypebot] = useState<PublicTypebot>(typebot)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalTypebot({ ...localTypebot, theme: typebot.theme })
|
setLocalTypebot((localTypebot) => ({
|
||||||
|
...localTypebot,
|
||||||
|
theme: typebot.theme,
|
||||||
|
}))
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [typebot.theme])
|
}, [typebot.theme])
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user