2
0

feat(editor): Start preview from any block

This commit is contained in:
Baptiste Arnaud
2022-06-02 10:07:50 +02:00
parent 12f2e40152
commit 89d91f9114
15 changed files with 128 additions and 29 deletions

View File

@@ -451,3 +451,9 @@ export const CreditCardIcon = (props: IconProps) => (
<line x1="1" y1="10" x2="23" y2="10"></line> <line x1="1" y1="10" x2="23" y2="10"></line>
</Icon> </Icon>
) )
export const PlayIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<polygon points="5 3 19 12 5 21 5 3"></polygon>
</Icon>
)

View File

@@ -21,7 +21,7 @@ import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
export const PreviewDrawer = () => { export const PreviewDrawer = () => {
const { typebot } = useTypebot() const { typebot } = useTypebot()
const { setRightPanel } = useEditor() const { setRightPanel, startPreviewAtBlock } = useEditor()
const { setPreviewingEdge } = useGraph() const { setPreviewingEdge } = useGraph()
const [isResizing, setIsResizing] = useState(false) const [isResizing, setIsResizing] = useState(false)
const [width, setWidth] = useState(500) const [width, setWidth] = useState(500)
@@ -96,13 +96,14 @@ export const PreviewDrawer = () => {
borderRadius={'lg'} borderRadius={'lg'}
h="full" h="full"
w="full" w="full"
key={restartKey} key={restartKey + (startPreviewAtBlock ?? '')}
pointerEvents={isResizing ? 'none' : 'auto'} pointerEvents={isResizing ? 'none' : 'auto'}
> >
<TypebotViewer <TypebotViewer
typebot={publicTypebot} typebot={publicTypebot}
onNewBlockVisible={setPreviewingEdge} onNewBlockVisible={setPreviewingEdge}
onNewLog={handleNewLog} onNewLog={handleNewLog}
startBlockId={startPreviewAtBlock}
isPreview isPreview
/> />
</Flex> </Flex>

View File

@@ -2,6 +2,7 @@ import {
Editable, Editable,
EditableInput, EditableInput,
EditablePreview, EditablePreview,
IconButton,
Stack, Stack,
} from '@chakra-ui/react' } from '@chakra-ui/react'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
@@ -16,6 +17,8 @@ import { BlockNodeContextMenu } from './BlockNodeContextMenu'
import { useDebounce } from 'use-debounce' import { useDebounce } from 'use-debounce'
import { setMultipleRefs } from 'services/utils' import { setMultipleRefs } from 'services/utils'
import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable' import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable'
import { PlayIcon } from 'assets/icons'
import { RightPanel, useEditor } from 'contexts/EditorContext'
type Props = { type Props = {
block: Block block: Block
@@ -38,6 +41,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
const { setMouseOverBlock, mouseOverBlock } = useStepDnd() const { setMouseOverBlock, mouseOverBlock } = useStepDnd()
const [isMouseDown, setIsMouseDown] = useState(false) const [isMouseDown, setIsMouseDown] = useState(false)
const [isConnecting, setIsConnecting] = useState(false) const [isConnecting, setIsConnecting] = useState(false)
const { setRightPanel, setStartPreviewAtBlock } = useEditor()
const isPreviewing = const isPreviewing =
previewingEdge?.from.blockId === block.id || previewingEdge?.from.blockId === block.id ||
(previewingEdge?.to.blockId === block.id && (previewingEdge?.to.blockId === block.id &&
@@ -99,6 +103,12 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
setFocusedBlockId(block.id) setFocusedBlockId(block.id)
setIsMouseDown(true) setIsMouseDown(true)
} }
const startPreviewAtThisBlock = () => {
setStartPreviewAtBlock(block.id)
setRightPanel(RightPanel.PREVIEW)
}
const onDragStop = () => setIsMouseDown(false) const onDragStop = () => setIsMouseDown(false)
return ( return (
<ContextMenu<HTMLDivElement> <ContextMenu<HTMLDivElement>
@@ -165,6 +175,16 @@ export const BlockNode = ({ block, blockIndex }: Props) => {
isStartBlock={isStartBlock} isStartBlock={isStartBlock}
/> />
)} )}
<IconButton
icon={<PlayIcon />}
aria-label={'Preview bot from this group'}
pos="absolute"
right={2}
top={0}
size="sm"
variant="outline"
onClick={startPreviewAtThisBlock}
/>
</Stack> </Stack>
</DraggableCore> </DraggableCore>
)} )}

View File

@@ -23,7 +23,6 @@ export const headerHeight = 56
export const TypebotHeader = () => { export const TypebotHeader = () => {
const router = useRouter() const router = useRouter()
const { rightPanel } = useEditor()
const { const {
typebot, typebot,
updateOnBothTypebots, updateOnBothTypebots,
@@ -35,13 +34,14 @@ export const TypebotHeader = () => {
canRedo, canRedo,
isSavingLoading, isSavingLoading,
} = useTypebot() } = useTypebot()
const { setRightPanel } = useEditor() const { setRightPanel, rightPanel, setStartPreviewAtBlock } = useEditor()
const handleNameSubmit = (name: string) => updateOnBothTypebots({ name }) const handleNameSubmit = (name: string) => updateOnBothTypebots({ name })
const handleChangeIcon = (icon: string) => updateTypebot({ icon }) const handleChangeIcon = (icon: string) => updateTypebot({ icon })
const handlePreviewClick = async () => { const handlePreviewClick = async () => {
setStartPreviewAtBlock(undefined)
save().then() save().then()
setRightPanel(RightPanel.PREVIEW) setRightPanel(RightPanel.PREVIEW)
} }

View File

@@ -13,18 +13,23 @@ export enum RightPanel {
const editorContext = createContext<{ const editorContext = createContext<{
rightPanel?: RightPanel rightPanel?: RightPanel
setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>> setRightPanel: Dispatch<SetStateAction<RightPanel | undefined>>
startPreviewAtBlock: string | undefined
setStartPreviewAtBlock: Dispatch<SetStateAction<string | undefined>>
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
}>({}) }>({})
export const EditorContext = ({ children }: { children: ReactNode }) => { export const EditorContext = ({ children }: { children: ReactNode }) => {
const [rightPanel, setRightPanel] = useState<RightPanel>() const [rightPanel, setRightPanel] = useState<RightPanel>()
const [startPreviewAtBlock, setStartPreviewAtBlock] = useState<string>()
return ( return (
<editorContext.Provider <editorContext.Provider
value={{ value={{
rightPanel, rightPanel,
setRightPanel, setRightPanel,
startPreviewAtBlock,
setStartPreviewAtBlock,
}} }}
> >
{children} {children}

View File

@@ -440,9 +440,11 @@ const useLinkedTypebots = ({
}, },
Error Error
>( >(
typebotIds?.every((id) => typebotId === id) workspaceId
? undefined ? typebotIds?.every((id) => typebotId === id)
: `/api/typebots?${params}`, ? undefined
: `/api/typebots?${params}`
: null,
fetcher fetcher
) )
if (error) onError(error) if (error) onError(error)

View File

@@ -5,7 +5,7 @@ import {
useEffect, useEffect,
useState, useState,
} from 'react' } from 'react'
import { byId } from 'utils' import { byId, isNotEmpty } from 'utils'
import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db' import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db'
import { import {
createNewWorkspace, createNewWorkspace,

View File

@@ -0,0 +1 @@
{"id":"cl3wo63la1004801amwsqzbof","createdAt":"2022-06-02T07:01:46.030Z","updatedAt":"2022-06-02T07:34:02.336Z","icon":null,"name":"My typebot","publishedTypebotId":null,"folderId":null,"blocks":[{"id":"cl3wo63l80000801ae4lxgvad","steps":[{"id":"cl3wo63l80001801a8u9g96sp","type":"start","label":"Start","blockId":"cl3wo63l80000801ae4lxgvad","outgoingEdgeId":"cl3wo83ha000j2e6gdrk1crro"}],"title":"Start","graphCoordinates":{"x":0,"y":0}},{"id":"cl3wo7ucc000g2e6gdus80qeb","graphCoordinates":{"x":355,"y":-13},"title":"Group #1","steps":[{"id":"cl3wo7uce000h2e6gr9r3b11k","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text","content":{"html":"<div>Hello this is group 1</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 1"}]}],"plainText":"Hello this is group 1"}},{"id":"cl3wo8047000i2e6glma69ddz","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text","content":{"html":"<div>What&#x27;s your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo85e8000k2e6gdb8qk860","blockId":"cl3wo7ucc000g2e6gdus80qeb","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]},{"id":"cl3wo87et000l2e6ga64ipat6","graphCoordinates":{"x":22,"y":260},"title":"Group #1 copy","steps":[{"id":"cl3wo87eu000m2e6g5h90qs9u","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text","content":{"html":"<div>Hello this is group 2</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 2"}]}],"plainText":"Hello this is group 2"}},{"id":"cl3wo87ev000n2e6gp7vn2z62","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text","content":{"html":"<div>What&#x27;s your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo87ev000o2e6g71r3hvor","blockId":"cl3wo87et000l2e6ga64ipat6","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]},{"id":"cl3wo8kfl000p2e6gszlvkub0","graphCoordinates":{"x":367,"y":294},"title":"Group #1 copy copy","steps":[{"id":"cl3wo8kfl000q2e6gci1itvj3","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text","content":{"html":"<div>Hello this is group 3</div>","richText":[{"type":"p","children":[{"text":"Hello this is group 3"}]}],"plainText":"Hello this is group 3"}},{"id":"cl3wo8kfl000r2e6gx0lxwitf","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text","content":{"html":"<div>What&#x27;s your name?</div>","richText":[{"type":"p","children":[{"text":"What's your name?"}]}],"plainText":"What's your name?"}},{"id":"cl3wo8kfl000s2e6g6ckc9om4","blockId":"cl3wo8kfl000p2e6gszlvkub0","type":"text input","options":{"isLong":false,"labels":{"button":"Send","placeholder":"Type your answer..."}}}]}],"variables":[],"edges":[{"from":{"blockId":"cl3wo63l80000801ae4lxgvad","stepId":"cl3wo63l80001801a8u9g96sp"},"to":{"blockId":"cl3wo7ucc000g2e6gdus80qeb"},"id":"cl3wo83ha000j2e6gdrk1crro"}],"theme":{"chat":{"inputs":{"color":"#303235","backgroundColor":"#FFFFFF","placeholderColor":"#9095A0"},"buttons":{"color":"#FFFFFF","backgroundColor":"#0042DA"},"hostAvatar":{"url":"https://avatars.githubusercontent.com/u/16015833?v=4","isEnabled":true},"hostBubbles":{"color":"#303235","backgroundColor":"#F7F8FF"},"guestBubbles":{"color":"#FFFFFF","backgroundColor":"#FF8E21"}},"general":{"font":"Open Sans","background":{"type":"None"}}},"settings":{"general":{"isBrandingEnabled":true,"isInputPrefillEnabled":true,"isHideQueryParamsEnabled":true,"isNewResultOnRefreshEnabled":false},"metadata":{"description":"Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."},"typingEmulation":{"speed":300,"enabled":true,"maxDelay":1.5}},"publicId":null,"customDomain":null,"workspaceId":"cl3ncues300081a1as58wmkxz"}

View File

@@ -11,7 +11,8 @@
{ {
"name": "typebot-20-modal", "name": "typebot-20-modal",
"value": "hide" "value": "hide"
} },
{ "name": "workspaceId", "value": "freeWorkspace" }
] ]
} }
] ]

View File

@@ -87,7 +87,7 @@ export const createUsers = async () => {
role: WorkspaceRole.ADMIN, role: WorkspaceRole.ADMIN,
workspace: { workspace: {
create: { create: {
id: freeWorkspaceId, id: 'free',
name: "Free user's workspace", name: "Free user's workspace",
plan: Plan.FREE, plan: Plan.FREE,
}, },
@@ -98,12 +98,15 @@ export const createUsers = async () => {
}) })
await prisma.workspace.create({ await prisma.workspace.create({
data: { data: {
id: 'free', id: freeWorkspaceId,
name: 'Free workspace', name: 'Free Shared Workspace',
plan: Plan.FREE, plan: Plan.FREE,
members: { members: {
createMany: { createMany: {
data: [{ role: WorkspaceRole.ADMIN, userId: 'proUser' }], data: [
{ role: WorkspaceRole.MEMBER, userId: 'proUser' },
{ role: WorkspaceRole.ADMIN, userId: 'freeUser' },
],
}, },
}, },
}, },

View File

@@ -7,6 +7,7 @@ import {
import { defaultTextInputOptions, InputStepType } from 'models' import { defaultTextInputOptions, InputStepType } from 'models'
import path from 'path' import path from 'path'
import cuid from 'cuid' import cuid from 'cuid'
import { typebotViewer } from '../services/selectorUtils'
test.describe.parallel('Editor', () => { test.describe.parallel('Editor', () => {
test('Edges connection should work', async ({ page }) => { test('Edges connection should work', async ({ page }) => {
@@ -151,4 +152,29 @@ test.describe.parallel('Editor', () => {
await expect(page.locator('text="😍"')).toBeVisible() await expect(page.locator('text="😍"')).toBeVisible()
await expect(page.locator('text="My superb typebot"')).toBeVisible() await expect(page.locator('text="My superb typebot"')).toBeVisible()
}) })
test('Preview from group should work', async ({ page }) => {
const typebotId = cuid()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/editor/previewFromGroup.json'),
{
id: typebotId,
}
)
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('[aria-label="Preview bot from this group"] >> nth=1')
await expect(
typebotViewer(page).locator('text="Hello this is group 1"')
).toBeVisible()
await page.click('[aria-label="Preview bot from this group"] >> nth=2')
await expect(
typebotViewer(page).locator('text="Hello this is group 2"')
).toBeVisible()
await page.click('[aria-label="Close"]')
await page.click('text="Preview"')
await expect(
typebotViewer(page).locator('text="Hello this is group 1"')
).toBeVisible()
})
}) })

View File

@@ -35,7 +35,7 @@ test('can switch between workspaces and access typebot', async ({ page }) => {
await page.goto('/typebots') await page.goto('/typebots')
await expect(page.locator('text="Pro typebot"')).toBeVisible() await expect(page.locator('text="Pro typebot"')).toBeVisible()
await page.click("text=Pro user's workspace") await page.click("text=Pro user's workspace")
await page.click('text=Shared workspace') await page.click('text="Shared workspace"')
await expect(page.locator('text="Pro typebot"')).toBeHidden() await expect(page.locator('text="Pro typebot"')).toBeHidden()
await page.click('text="Shared typebot"') await page.click('text="Shared typebot"')
await expect(page.locator('text="Hey there"')).toBeVisible() await expect(page.locator('text="Hey there"')).toBeVisible()
@@ -135,7 +135,7 @@ test('can manage members', async ({ page }) => {
test("can't edit workspace as a member", async ({ page }) => { test("can't edit workspace as a member", async ({ page }) => {
await page.goto('/typebots') await page.goto('/typebots')
await page.click("text=Pro user's workspace") await page.click("text=Pro user's workspace")
await page.click('text=Shared workspace') await page.click('text="Shared workspace"')
await page.click('text=Settings & Members') await page.click('text=Settings & Members')
await expect(page.locator('text="Settings"')).toBeHidden() await expect(page.locator('text="Settings"')).toBeHidden()
await page.click('text="Members"') await page.click('text="Members"')

View File

@@ -33,10 +33,13 @@ type ChatBlockProps = {
startStepIndex: number startStepIndex: number
blockTitle: string blockTitle: string
keepShowingHostAvatar: boolean keepShowingHostAvatar: boolean
onBlockEnd: ( onBlockEnd: ({
edgeId?: string, edgeId,
updatedTypebot,
}: {
edgeId?: string
updatedTypebot?: PublicTypebot | LinkedTypebot updatedTypebot?: PublicTypebot | LinkedTypebot
) => void }) => void
} }
type ChatDisplayChunk = { bubbles: BubbleStep[]; input?: InputStep } type ChatDisplayChunk = { bubbles: BubbleStep[]; input?: InputStep }
@@ -129,7 +132,9 @@ export const ChatBlock = ({
currentStep.type === LogicStepType.REDIRECT && currentStep.type === LogicStepType.REDIRECT &&
currentStep.options.isNewTab === false currentStep.options.isNewTab === false
if (isRedirecting) return if (isRedirecting) return
nextEdgeId ? onBlockEnd(nextEdgeId, linkedTypebot) : displayNextStep() nextEdgeId
? onBlockEnd({ edgeId: nextEdgeId, updatedTypebot: linkedTypebot })
: displayNextStep()
} }
if (isIntegrationStep(currentStep)) { if (isIntegrationStep(currentStep)) {
const nextEdgeId = await executeIntegration({ const nextEdgeId = await executeIntegration({
@@ -149,9 +154,10 @@ export const ChatBlock = ({
resultId, resultId,
}, },
}) })
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep() nextEdgeId ? onBlockEnd({ edgeId: nextEdgeId }) : displayNextStep()
} }
if (currentStep.type === 'start') onBlockEnd(currentStep.outgoingEdgeId) if (currentStep.type === 'start')
onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
} }
const displayNextStep = (answerContent?: string, isRetry?: boolean) => { const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
@@ -175,14 +181,14 @@ export const ChatBlock = ({
const nextEdgeId = currentStep.items.find( const nextEdgeId = currentStep.items.find(
(i) => i.content === answerContent (i) => i.content === answerContent
)?.outgoingEdgeId )?.outgoingEdgeId
if (nextEdgeId) return onBlockEnd(nextEdgeId) if (nextEdgeId) return onBlockEnd({ edgeId: nextEdgeId })
} }
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length) if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
return onBlockEnd(currentStep.outgoingEdgeId) return onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
} }
const nextStep = steps[processedSteps.length + startStepIndex] const nextStep = steps[processedSteps.length + startStepIndex]
nextStep ? insertStepInStack(nextStep) : onBlockEnd() nextStep ? insertStepInStack(nextStep) : onBlockEnd({})
} }
const avatarSrc = typebot.theme.chat.hostAvatar?.url const avatarSrc = typebot.theme.chat.hostAvatar?.url

View File

@@ -13,12 +13,14 @@ import { ChatContext } from 'contexts/ChatContext'
type Props = { type Props = {
theme: Theme theme: Theme
predefinedVariables?: { [key: string]: string | undefined } predefinedVariables?: { [key: string]: string | undefined }
startBlockId?: string
onNewBlockVisible: (edge: Edge) => void onNewBlockVisible: (edge: Edge) => void
onCompleted: () => void onCompleted: () => void
} }
export const ConversationContainer = ({ export const ConversationContainer = ({
theme, theme,
predefinedVariables, predefinedVariables,
startBlockId,
onNewBlockVisible, onNewBlockVisible,
onCompleted, onCompleted,
}: Props) => { }: Props) => {
@@ -36,17 +38,35 @@ export const ConversationContainer = ({
const bottomAnchor = useRef<HTMLDivElement | null>(null) const bottomAnchor = useRef<HTMLDivElement | null>(null)
const scrollableContainer = useRef<HTMLDivElement | null>(null) const scrollableContainer = useRef<HTMLDivElement | null>(null)
const displayNextBlock = ( const displayNextBlock = ({
edgeId?: string, edgeId,
updatedTypebot,
blockId,
}: {
edgeId?: string
blockId?: string
updatedTypebot?: PublicTypebot | LinkedTypebot updatedTypebot?: PublicTypebot | LinkedTypebot
) => { }) => {
const currentTypebot = updatedTypebot ?? typebot const currentTypebot = updatedTypebot ?? typebot
if (blockId) {
const nextBlock = currentTypebot.blocks.find(byId(blockId))
if (!nextBlock) return
onNewBlockVisible({
id: 'edgeId',
from: { blockId: 'block', stepId: 'step' },
to: { blockId },
})
return setDisplayedBlocks([
...displayedBlocks,
{ block: nextBlock, startStepIndex: 0 },
])
}
const nextEdge = currentTypebot.edges.find(byId(edgeId)) const nextEdge = currentTypebot.edges.find(byId(edgeId))
if (!nextEdge) { if (!nextEdge) {
if (linkedBotQueue.length > 0) { if (linkedBotQueue.length > 0) {
const nextEdgeId = linkedBotQueue[0].edgeId const nextEdgeId = linkedBotQueue[0].edgeId
popEdgeIdFromLinkedTypebotQueue() popEdgeIdFromLinkedTypebotQueue()
displayNextBlock(nextEdgeId) displayNextBlock({ edgeId: nextEdgeId })
} }
return onCompleted() return onCompleted()
} }
@@ -65,7 +85,12 @@ export const ConversationContainer = ({
useEffect(() => { useEffect(() => {
const prefilledVariables = injectPredefinedVariables(predefinedVariables) const prefilledVariables = injectPredefinedVariables(predefinedVariables)
updateVariables(prefilledVariables) updateVariables(prefilledVariables)
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId) displayNextBlock({
edgeId: startBlockId
? undefined
: typebot.blocks[0].steps[0].outgoingEdgeId,
blockId: startBlockId,
})
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])

View File

@@ -30,6 +30,7 @@ export type TypebotViewerProps = {
style?: CSSProperties style?: CSSProperties
predefinedVariables?: { [key: string]: string | undefined } predefinedVariables?: { [key: string]: string | undefined }
resultId?: string resultId?: string
startBlockId?: string
onNewBlockVisible?: (edge: Edge) => void onNewBlockVisible?: (edge: Edge) => void
onNewAnswer?: (answer: Answer) => Promise<void> onNewAnswer?: (answer: Answer) => Promise<void>
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
@@ -43,6 +44,7 @@ export const TypebotViewer = ({
isPreview = false, isPreview = false,
style, style,
resultId, resultId,
startBlockId,
predefinedVariables, predefinedVariables,
onNewLog, onNewLog,
onNewBlockVisible, onNewBlockVisible,
@@ -116,6 +118,7 @@ export const TypebotViewer = ({
onNewBlockVisible={handleNewBlockVisible} onNewBlockVisible={handleNewBlockVisible}
onCompleted={handleCompleted} onCompleted={handleCompleted}
predefinedVariables={predefinedVariables} predefinedVariables={predefinedVariables}
startBlockId={startBlockId}
/> />
</div> </div>
{typebot.settings.general.isBrandingEnabled && <LiteBadge />} {typebot.settings.general.isBrandingEnabled && <LiteBadge />}