2
0

refactor(graph): ♻️ Add Edges table in Typebot

This commit is contained in:
Baptiste Arnaud
2022-01-19 18:54:49 +01:00
parent ab34f95cce
commit 8bbd8977b2
59 changed files with 1118 additions and 991 deletions

View File

@ -5,26 +5,31 @@ import {
getAnchorsPosition,
computeEdgePath,
getEndpointTopOffset,
getSourceEndpointId,
} from 'services/graph'
type Props = { stepId: string }
type Props = { edgeId: string }
export const Edge = ({ stepId }: Props) => {
export const Edge = ({ edgeId }: Props) => {
const { typebot } = useAnalyticsGraph()
const step = typebot?.steps.byId[stepId]
const edge = typebot?.edges.byId[edgeId]
const { sourceEndpoints, targetEndpoints, graphPosition } = useGraph()
const [sourceTop, setSourceTop] = useState(
getEndpointTopOffset(graphPosition, sourceEndpoints, stepId)
getEndpointTopOffset(
graphPosition,
sourceEndpoints,
getSourceEndpointId(edge)
)
)
const [targetTop, setTargetTop] = useState(
getEndpointTopOffset(graphPosition, sourceEndpoints, step?.target?.stepId)
getEndpointTopOffset(graphPosition, sourceEndpoints, edge?.to.stepId)
)
useEffect(() => {
const newSourceTop = getEndpointTopOffset(
graphPosition,
sourceEndpoints,
stepId
getSourceEndpointId(edge)
)
const sensibilityThreshold = 10
const newSourceTopIsTooClose =
@ -39,7 +44,7 @@ export const Edge = ({ stepId }: Props) => {
const newTargetTop = getEndpointTopOffset(
graphPosition,
targetEndpoints,
step?.target?.stepId
edge?.to.stepId
)
const sensibilityThreshold = 10
const newSourceTopIsTooClose =
@ -51,15 +56,14 @@ export const Edge = ({ stepId }: Props) => {
}, [graphPosition])
const { sourceBlock, targetBlock } = useMemo(() => {
if (!typebot) return {}
if (!step?.target) return {}
const targetBlock = typebot.blocks.byId[step.target.blockId]
const sourceBlock = typebot.blocks.byId[step.blockId]
if (!typebot || !edge) return {}
const targetBlock = typebot.blocks.byId[edge.to.blockId]
const sourceBlock = typebot.blocks.byId[edge.from.blockId]
return {
sourceBlock,
targetBlock,
}
}, [step?.blockId, step?.target, typebot])
}, [edge, typebot])
const path = useMemo(() => {
if (!sourceBlock || !targetBlock) return ``

View File

@ -1,8 +1,7 @@
import { chakra } from '@chakra-ui/system'
import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider'
import React, { useMemo } from 'react'
import React from 'react'
import { AnswersCount } from 'services/analytics'
import { isDefined } from 'utils'
import { DropOffBlock } from '../blocks/DropOffBlock'
import { DropOffEdge } from './DropOffEdge'
import { Edge } from './Edge'
@ -12,13 +11,6 @@ type Props = { answersCounts?: AnswersCount[] }
export const Edges = ({ answersCounts }: Props) => {
const { typebot } = useAnalyticsGraph()
const stepIdsWithTarget: string[] = useMemo(() => {
if (!typebot) return []
return typebot.steps.allIds.filter((stepId) =>
isDefined(typebot.steps.byId[stepId].target)
)
}, [typebot])
return (
<>
<chakra.svg
@ -29,8 +21,8 @@ export const Edges = ({ answersCounts }: Props) => {
left="0"
top="0"
>
{stepIdsWithTarget.map((stepId) => (
<Edge key={stepId} stepId={stepId} />
{typebot?.edges.allIds.map((edgeId) => (
<Edge key={edgeId} edgeId={edgeId} />
))}
<marker
id={'arrow'}

View File

@ -3,6 +3,7 @@ import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider'
import React, { useMemo } from 'react'
import { AnswersCount } from 'services/analytics'
import { computeSourceCoordinates } from 'services/graph'
import { isDefined } from 'utils'
type Props = {
answersCounts: AnswersCount[]
@ -20,11 +21,12 @@ export const DropOffBlock = ({ answersCounts, blockId }: Props) => {
const { totalDroppedUser, dropOffRate } = useMemo(() => {
if (!typebot || totalAnswers === undefined)
return { previousTotal: undefined, dropOffRate: undefined }
const previousBlockIds = typebot.blocks.allIds.filter(() =>
typebot.steps.allIds.find(
(sId) => typebot.steps.byId[sId].target?.blockId === blockId
)
)
const previousBlockIds = typebot.edges.allIds
.map((edgeId) => {
const edge = typebot.edges.byId[edgeId]
return edge.to.blockId === blockId ? edge.from.blockId : undefined
})
.filter((blockId) => isDefined(blockId))
const previousTotal = answersCounts
.filter((a) => previousBlockIds.includes(a.blockId))
.reduce((prev, acc) => acc.totalAnswers + prev, 0)

View File

@ -20,18 +20,17 @@ type Props = {
}
export const BlockNode = ({ block }: Props) => {
const { connectingIds, setConnectingIds, previewingIds } = useGraph()
const { connectingIds, setConnectingIds, previewingEdgeId } = useGraph()
const { typebot, updateBlock } = useTypebot()
const { setMouseOverBlockId } = useDnd()
const { draggedStep, draggedStepType } = useDnd()
const [isMouseDown, setIsMouseDown] = useState(false)
const [isConnecting, setIsConnecting] = useState(false)
const isPreviewing = useMemo(
() =>
previewingIds.sourceId === block.id ||
previewingIds.targetId === block.id,
[block.id, previewingIds.sourceId, previewingIds.targetId]
)
const isPreviewing = useMemo(() => {
if (!previewingEdgeId) return
const edge = typebot?.edges.byId[previewingEdgeId]
return edge?.to.blockId === block.id || edge?.from.blockId === block.id
}, [block.id, previewingEdgeId, typebot?.edges.byId])
useEffect(() => {
setIsConnecting(

View File

@ -128,7 +128,7 @@ export const ChoiceItemNode = ({
source={{
blockId: typebot.steps.byId[item.stepId].blockId,
stepId: item.stepId,
choiceItemId: item.id,
nodeId: item.id,
}}
pos="absolute"
right="15px"

View File

@ -49,9 +49,7 @@ export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => {
<PopoverContent onMouseDown={handleMouseDown} pos="relative">
<PopoverArrow />
<PopoverBody
px="6"
pb="6"
pt="4"
p="6"
overflowY="scroll"
maxH="400px"
ref={ref}

View File

@ -1,12 +1,13 @@
import { Box, BoxProps, Flex } from '@chakra-ui/react'
import { ConnectingSourceIds, useGraph } from 'contexts/GraphContext'
import { useGraph } from 'contexts/GraphContext'
import { Source } from 'models'
import React, { MouseEvent, useEffect, useRef } from 'react'
export const SourceEndpoint = ({
source,
...props
}: BoxProps & {
source: ConnectingSourceIds
source: Source
}) => {
const { setConnectingIds, addSourceEndpoint: addEndpoint } = useGraph()
const ref = useRef<HTMLDivElement | null>(null)
@ -18,8 +19,7 @@ export const SourceEndpoint = ({
useEffect(() => {
if (!ref.current) return
const id =
source.choiceItemId ?? source.stepId + (source.conditionType ?? '')
const id = source.nodeId ?? source.stepId + (source.conditionType ?? '')
addEndpoint({
id,
ref,

View File

@ -1,5 +1,4 @@
import {
Box,
Flex,
HStack,
Popover,
@ -7,12 +6,11 @@ import {
useDisclosure,
useEventListener,
} from '@chakra-ui/react'
import React, { useEffect, useMemo, useState } from 'react'
import { Block, DraggableStep, Step } from 'models'
import React, { useEffect, useState } from 'react'
import { DraggableStep, Step } from 'models'
import { useGraph } from 'contexts/GraphContext'
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
import {
isDefined,
isInputStep,
isLogicStep,
isTextBubbleStep,
@ -50,7 +48,7 @@ export const StepNode = ({
}) => {
const { query } = useRouter()
const { setConnectingIds, connectingIds } = useGraph()
const { moveStep, typebot } = useTypebot()
const { moveStep } = useTypebot()
const [isConnecting, setIsConnecting] = useState(false)
const [mouseDownEvent, setMouseDownEvent] =
useState<{ absolute: Coordinates; relative: Coordinates }>()
@ -132,30 +130,6 @@ export const StepNode = ({
setIsEditing(false)
}
const connectedStubPosition: 'right' | 'left' | undefined = useMemo(() => {
if (!typebot) return
const currentBlock = typebot.blocks?.byId[step.blockId]
const isDragginConnectorFromCurrentBlock =
connectingIds?.source.blockId === currentBlock?.id &&
connectingIds?.target?.blockId
const targetBlockId = isDragginConnectorFromCurrentBlock
? connectingIds.target?.blockId
: step.target?.blockId
const targetedBlock = targetBlockId && typebot.blocks.byId[targetBlockId]
return targetedBlock
? targetedBlock.graphCoordinates.x <
(currentBlock as Block).graphCoordinates.x
? 'left'
: 'right'
: undefined
}, [
typebot,
step.blockId,
step.target?.blockId,
connectingIds?.source.blockId,
connectingIds?.target?.blockId,
])
return isEditing && isTextBubbleStep(step) ? (
<TextEditor
stepId={step.id}
@ -184,16 +158,6 @@ export const StepNode = ({
data-testid={`step-${step.id}`}
w="full"
>
{connectedStubPosition === 'left' && (
<Box
h="2px"
pos="absolute"
left="-18px"
top="25px"
w="18px"
bgColor="blue.500"
/>
)}
<HStack
flex="1"
userSelect="none"
@ -225,24 +189,6 @@ export const StepNode = ({
/>
)}
</HStack>
{isDefined(connectedStubPosition) &&
hasDefaultConnector(step) &&
isConnectable && (
<Box
h="2px"
pos="absolute"
right={
connectedStubPosition === 'left' ? undefined : '-18px'
}
left={
connectedStubPosition === 'left' ? '-18px' : undefined
}
top="25px"
w="18px"
bgColor="gray.500"
/>
)}
</Flex>
</PopoverTrigger>
{hasPopover(step) && (

View File

@ -1,8 +1,9 @@
import { useEventListener } from '@chakra-ui/hooks'
import assert from 'assert'
import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader'
import { useGraph, ConnectingIds } from 'contexts/GraphContext'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { Step, Target } from 'models'
import { Target } from 'models'
import React, { useMemo, useState } from 'react'
import {
computeConnectingEdgePath,
@ -18,7 +19,7 @@ export const DrawingEdge = () => {
sourceEndpoints,
targetEndpoints,
} = useGraph()
const { typebot, updateStep, updateChoiceItem } = useTypebot()
const { typebot, createEdge } = useTypebot()
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 })
const sourceBlock = useMemo(
@ -32,7 +33,7 @@ export const DrawingEdge = () => {
return getEndpointTopOffset(
graphPosition,
sourceEndpoints,
connectingIds.source.choiceItemId ??
connectingIds.source.nodeId ??
connectingIds.source.stepId + (connectingIds.source.conditionType ?? '')
)
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -79,23 +80,8 @@ export const DrawingEdge = () => {
})
const createNewEdge = (connectingIds: ConnectingIds) => {
if (connectingIds.source.choiceItemId) {
updateChoiceItem(connectingIds.source.choiceItemId, {
target: connectingIds.target,
})
} else if (connectingIds.source.conditionType === 'true') {
updateStep(connectingIds.source.stepId, {
trueTarget: connectingIds.target,
} as Step)
} else if (connectingIds.source.conditionType === 'false') {
updateStep(connectingIds.source.stepId, {
falseTarget: connectingIds.target,
} as Step)
} else {
updateStep(connectingIds.source.stepId, {
target: connectingIds.target,
})
}
assert(connectingIds.target)
createEdge({ from: connectingIds.source, to: connectingIds.target })
}
if ((mousePosition.x === 0 && mousePosition.y === 0) || !connectingIds)

View File

@ -1,14 +1,11 @@
import { isDefined } from '@udecode/plate-core'
import assert from 'assert'
import { Coordinates, useGraph } from 'contexts/GraphContext'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { ChoiceItem } from 'models'
import React, { useEffect, useMemo, useState } from 'react'
import {
getAnchorsPosition,
computeEdgePath,
getEndpointTopOffset,
getTarget,
getSourceEndpointId,
} from 'services/graph'
export type AnchorsPositionProps = {
@ -18,44 +15,31 @@ export type AnchorsPositionProps = {
totalSegments: number
}
export enum EdgeType {
STEP,
CHOICE_ITEM,
CONDITION_TRUE,
CONDITION_FALSE,
}
export const Edge = ({
type,
stepId,
item,
}: {
type: EdgeType
stepId: string
item?: ChoiceItem
}) => {
export const Edge = ({ edgeId }: { edgeId: string }) => {
const { typebot } = useTypebot()
const { previewingIds, sourceEndpoints, targetEndpoints, graphPosition } =
const { previewingEdgeId, sourceEndpoints, targetEndpoints, graphPosition } =
useGraph()
const step = typebot?.steps.byId[stepId]
const isPreviewing = useMemo(
() =>
previewingIds.sourceId === step?.blockId &&
previewingIds.targetId === step?.target?.blockId,
[previewingIds.sourceId, previewingIds.targetId, step]
const edge = useMemo(
() => typebot?.edges.byId[edgeId],
[edgeId, typebot?.edges.byId]
)
const isPreviewing = previewingEdgeId === edgeId
const [sourceTop, setSourceTop] = useState(
getEndpointTopOffset(graphPosition, sourceEndpoints, item?.id ?? step?.id)
getEndpointTopOffset(
graphPosition,
sourceEndpoints,
getSourceEndpointId(edge)
)
)
const [targetTop, setTargetTop] = useState(
getEndpointTopOffset(graphPosition, targetEndpoints, step?.id)
getEndpointTopOffset(graphPosition, targetEndpoints, edge?.to.stepId)
)
useEffect(() => {
const newSourceTop = getEndpointTopOffset(
graphPosition,
sourceEndpoints,
getSourceEndpointId()
getSourceEndpointId(edge)
)
const sensibilityThreshold = 10
const newSourceTopIsTooClose =
@ -66,26 +50,12 @@ export const Edge = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [typebot?.blocks, typebot?.steps, graphPosition, sourceEndpoints])
const getSourceEndpointId = () => {
switch (type) {
case EdgeType.STEP:
return step?.id
case EdgeType.CHOICE_ITEM:
return item?.id
case EdgeType.CONDITION_TRUE:
return step?.id + 'true'
case EdgeType.CONDITION_FALSE:
return step?.id + 'false'
}
}
useEffect(() => {
if (!step) return
const target = getTarget(step, type)
if (!edge) return
const newTargetTop = getEndpointTopOffset(
graphPosition,
targetEndpoints,
target?.blockId ?? target?.stepId
edge?.to.stepId
)
const sensibilityThreshold = 10
const newSourceTopIsTooClose =
@ -97,20 +67,17 @@ export const Edge = ({
}, [typebot?.blocks, typebot?.steps, graphPosition, targetEndpoints])
const { sourceBlock, targetBlock } = useMemo(() => {
if (!typebot) return {}
const step = typebot.steps.byId[stepId]
const sourceBlock = typebot.blocks.byId[step.blockId]
const targetBlockId = getTarget(step, type)?.blockId
assert(isDefined(targetBlockId))
const targetBlock = typebot.blocks.byId[targetBlockId]
if (!typebot || !edge?.from.stepId) return {}
const sourceBlock = typebot.blocks.byId[edge.from.blockId]
const targetBlock = typebot.blocks.byId[edge.to.blockId]
return {
sourceBlock,
targetBlock,
}
}, [stepId, type, typebot])
}, [edge?.from.blockId, edge?.from.stepId, edge?.to.blockId, typebot])
const path = useMemo(() => {
if (!sourceBlock || !targetBlock || !step) return ``
if (!sourceBlock || !targetBlock) return ``
const anchorsPosition = getAnchorsPosition({
sourceBlock,
targetBlock,
@ -118,7 +85,7 @@ export const Edge = ({
targetTop,
})
return computeEdgePath(anchorsPosition)
}, [sourceBlock, sourceTop, step, targetBlock, targetTop])
}, [sourceBlock, sourceTop, targetBlock, targetTop])
if (sourceTop === 0) return <></>
return (

View File

@ -1,39 +1,11 @@
import { chakra } from '@chakra-ui/system'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { ChoiceItem, ConditionStep } from 'models'
import React, { useMemo } from 'react'
import { isConditionStep, isDefined, isSingleChoiceInput } from 'utils'
import React from 'react'
import { DrawingEdge } from './DrawingEdge'
import { Edge, EdgeType } from './Edge'
import { Edge } from './Edge'
export const Edges = () => {
const { typebot } = useTypebot()
const stepIdsWithTarget: string[] = useMemo(() => {
if (!typebot) return []
return typebot.steps.allIds.filter((stepId) =>
isDefined(typebot.steps.byId[stepId].target)
)
}, [typebot])
const singleChoiceItemsWithTarget: ChoiceItem[] = useMemo(() => {
if (!typebot) return []
return typebot.choiceItems.allIds
.filter(
(itemId) =>
isDefined(typebot.choiceItems.byId[itemId].target) &&
isSingleChoiceInput(
typebot.steps.byId[typebot.choiceItems.byId[itemId].stepId]
)
)
.map((itemId) => typebot.choiceItems.byId[itemId])
}, [typebot])
const conditionStepIdsWithTarget = useMemo(
() =>
typebot?.steps.allIds.filter((stepId) => {
const step = typebot.steps.byId[stepId]
return isConditionStep(step) && (step.trueTarget || step.falseTarget)
}),
[typebot?.steps.allIds, typebot?.steps.byId]
)
return (
<chakra.svg
@ -45,19 +17,8 @@ export const Edges = () => {
top="0"
>
<DrawingEdge />
{stepIdsWithTarget.map((stepId) => (
<Edge key={stepId} stepId={stepId} type={EdgeType.STEP} />
))}
{singleChoiceItemsWithTarget.map((item) => (
<Edge
key={item.id}
stepId={item.stepId}
item={item}
type={EdgeType.CHOICE_ITEM}
/>
))}
{conditionStepIdsWithTarget?.map((stepId) => (
<ConditionStepEdges key={stepId} stepId={stepId} />
{typebot?.edges.allIds.map((edgeId) => (
<Edge key={edgeId} edgeId={edgeId} />
))}
<marker
id={'arrow'}
@ -92,18 +53,3 @@ export const Edges = () => {
</chakra.svg>
)
}
const ConditionStepEdges = ({ stepId }: { stepId: string }) => {
const { typebot } = useTypebot()
const step = typebot?.steps.byId[stepId] as ConditionStep
return (
<>
{step.trueTarget && (
<Edge type={EdgeType.CONDITION_TRUE} stepId={stepId} />
)}
{step.falseTarget && (
<Edge type={EdgeType.CONDITION_FALSE} stepId={stepId} />
)}
</>
)
}

View File

@ -19,7 +19,7 @@ import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
export const PreviewDrawer = () => {
const { typebot } = useTypebot()
const { setRightPanel } = useEditor()
const { previewingIds, setPreviewingIds } = useGraph()
const { setPreviewingEdgeId } = useGraph()
const [isResizing, setIsResizing] = useState(false)
const [width, setWidth] = useState(500)
const [isResizeHandleVisible, setIsResizeHandleVisible] = useState(false)
@ -45,13 +45,7 @@ export const PreviewDrawer = () => {
}
useEventListener('mouseup', handleMouseUp)
const handleNewBlockVisible = (targetId: string) =>
setPreviewingIds({
sourceId: !previewingIds.sourceId
? typebot?.blocks.allIds[0]
: previewingIds.targetId,
targetId: targetId,
})
const handleNewBlockVisible = (edgeId: string) => setPreviewingEdgeId(edgeId)
const handleRestartClick = () => setRestartKey((key) => key + 1)

View File

@ -1,9 +1,8 @@
import {
HStack,
IconButton,
Input,
InputGroup,
InputProps,
InputRightElement,
Popover,
PopoverContent,
PopoverTrigger,
@ -55,6 +54,7 @@ export const InputWithVariableButton = ({
if (!inputRef.current) return
inputRef.current.selectionStart = inputRef.current.selectionEnd =
carretPosition + `{{${variable.name}}}`.length
setCarretPosition(inputRef.current.selectionStart)
}, 100)
}
@ -67,7 +67,7 @@ export const InputWithVariableButton = ({
setValue(e.target.value)
return (
<InputGroup>
<HStack>
<Input
ref={inputRef}
onKeyUp={handleKeyUp}
@ -77,26 +77,24 @@ export const InputWithVariableButton = ({
{...props}
bgColor={'white'}
/>
<InputRightElement>
<Popover matchWidth isLazy closeOnBlur={false}>
<PopoverTrigger>
<IconButton
aria-label="Insert a variable"
icon={<UserIcon />}
size="sm"
pos="relative"
/>
</PopoverTrigger>
<PopoverContent w="full">
<VariableSearchInput
onSelectVariable={handleVariableSelected}
placeholder="Search for a variable"
shadow="lg"
isDefaultOpen
/>
</PopoverContent>
</Popover>
</InputRightElement>
</InputGroup>
<Popover matchWidth isLazy>
<PopoverTrigger>
<IconButton
aria-label="Insert a variable"
icon={<UserIcon />}
pos="relative"
ml="2"
/>
</PopoverTrigger>
<PopoverContent w="full">
<VariableSearchInput
onSelectVariable={handleVariableSelected}
placeholder="Search for a variable"
shadow="lg"
isDefaultOpen
/>
</PopoverContent>
</Popover>
</HStack>
)
}

View File

@ -9,7 +9,6 @@ import {
Button,
InputProps,
IconButton,
Portal,
} from '@chakra-ui/react'
import { PlusIcon, TrashIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext'
@ -98,8 +97,8 @@ export const VariableSearchInput = ({
isOpen={isOpen}
initialFocusRef={inputRef}
matchWidth
offset={[0, 0]}
isLazy
offset={[0, 2]}
>
<PopoverTrigger>
<Input

View File

@ -1,4 +1,4 @@
import { Block, Step, Table, Target } from 'models'
import { Block, Source, Step, Table, Target } from 'models'
import {
createContext,
Dispatch,
@ -43,19 +43,10 @@ export type Node = Omit<Block, 'steps'> & {
const graphPositionDefaultValue = { x: 400, y: 100, scale: 1 }
export type ConnectingIds = {
source: ConnectingSourceIds
source: Source
target?: Target
}
export type ConnectingSourceIds = {
blockId: string
stepId: string
choiceItemId?: string
conditionType?: 'true' | 'false'
}
type PreviewingIdsProps = { sourceId?: string; targetId?: string }
type StepId = string
type NodeId = string
export type Endpoint = {
@ -68,8 +59,8 @@ const graphContext = createContext<{
setGraphPosition: Dispatch<SetStateAction<Position>>
connectingIds: ConnectingIds | null
setConnectingIds: Dispatch<SetStateAction<ConnectingIds | null>>
previewingIds: PreviewingIdsProps
setPreviewingIds: Dispatch<SetStateAction<PreviewingIdsProps>>
previewingEdgeId?: string
setPreviewingEdgeId: Dispatch<SetStateAction<string | undefined>>
sourceEndpoints: Table<Endpoint>
addSourceEndpoint: (endpoint: Endpoint) => void
targetEndpoints: Table<Endpoint>
@ -84,7 +75,7 @@ const graphContext = createContext<{
export const GraphProvider = ({ children }: { children: ReactNode }) => {
const [graphPosition, setGraphPosition] = useState(graphPositionDefaultValue)
const [connectingIds, setConnectingIds] = useState<ConnectingIds | null>(null)
const [previewingIds, setPreviewingIds] = useState<PreviewingIdsProps>({})
const [previewingEdgeId, setPreviewingEdgeId] = useState<string>()
const [sourceEndpoints, setSourceEndpoints] = useState<Table<Endpoint>>({
byId: {},
allIds: [],
@ -115,8 +106,8 @@ export const GraphProvider = ({ children }: { children: ReactNode }) => {
setGraphPosition,
connectingIds,
setConnectingIds,
previewingIds,
setPreviewingIds,
previewingEdgeId,
setPreviewingEdgeId,
sourceEndpoints,
targetEndpoints,
addSourceEndpoint,

View File

@ -28,6 +28,7 @@ import { useImmer, Updater } from 'use-immer'
import { stepsAction, StepsActions } from './actions/steps'
import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
import { variablesAction, VariablesActions } from './actions/variables'
import { edgesAction, EdgesActions } from './actions/edges'
type UpdateTypebotPayload = Partial<{
theme: Theme
@ -50,7 +51,8 @@ const typebotContext = createContext<
} & BlocksActions &
StepsActions &
ChoiceItemsActions &
VariablesActions
VariablesActions &
EdgesActions
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
>({})
@ -211,6 +213,7 @@ export const TypebotContext = ({
...stepsAction(setLocalTypebot as Updater<Typebot>),
...choiceItemsAction(setLocalTypebot as Updater<Typebot>),
...variablesAction(setLocalTypebot as Updater<Typebot>),
...edgesAction(setLocalTypebot as Updater<Typebot>),
}}
>
{children}

View File

@ -3,6 +3,7 @@ import { WritableDraft } from 'immer/dist/internal'
import { Block, DraggableStep, DraggableStepType, Typebot } from 'models'
import { parseNewBlock } from 'services/typebots'
import { Updater } from 'use-immer'
import { deleteEdgeDraft } from './edges'
import { createStepDraft, deleteStepDraft } from './steps'
export type BlocksActions = {
@ -44,6 +45,7 @@ export const blocksActions = (setTypebot: Updater<Typebot>): BlocksActions => ({
deleteBlock: (blockId: string) =>
setTypebot((typebot) => {
deleteStepsInsideBlock(typebot, blockId)
deleteAssociatedEdges(typebot, blockId)
deleteBlockDraft(typebot)(blockId)
}),
})
@ -55,6 +57,16 @@ export const removeEmptyBlocks = (typebot: WritableDraft<Typebot>) => {
emptyBlockIds.forEach(deleteBlockDraft(typebot))
}
const deleteAssociatedEdges = (
typebot: WritableDraft<Typebot>,
blockId: string
) => {
typebot.edges.allIds.forEach((edgeId) => {
if (typebot.edges.byId[edgeId].to.blockId === blockId)
deleteEdgeDraft(typebot, edgeId)
})
}
const deleteStepsInsideBlock = (
typebot: WritableDraft<Typebot>,
blockId: string

View File

@ -0,0 +1,69 @@
import { Typebot, Edge, ConditionStep } from 'models'
import { Updater } from 'use-immer'
import { WritableDraft } from 'immer/dist/types/types-external'
import { generate } from 'short-uuid'
export type EdgesActions = {
createEdge: (edge: Omit<Edge, 'id'>) => void
updateEdge: (edgeId: string, updates: Partial<Omit<Edge, 'id'>>) => void
deleteEdge: (edgeId: string) => void
}
export const edgesAction = (setTypebot: Updater<Typebot>): EdgesActions => ({
createEdge: (edge: Omit<Edge, 'id'>) => {
setTypebot((typebot) => {
const newEdge = {
...edge,
id: generate(),
}
if (edge.from.nodeId) {
deleteEdgeDraft(
typebot,
typebot.choiceItems.byId[edge.from.nodeId].edgeId
)
typebot.choiceItems.byId[edge.from.nodeId].edgeId = newEdge.id
} else if (edge.from.conditionType === 'true') {
deleteEdgeDraft(
typebot,
(typebot.steps.byId[edge.from.stepId] as ConditionStep).trueEdgeId
)
;(typebot.steps.byId[edge.from.stepId] as ConditionStep).trueEdgeId =
newEdge.id
} else if (edge.from.conditionType === 'false') {
deleteEdgeDraft(
typebot,
(typebot.steps.byId[edge.from.stepId] as ConditionStep).falseEdgeId
)
;(typebot.steps.byId[edge.from.stepId] as ConditionStep).falseEdgeId =
newEdge.id
} else {
deleteEdgeDraft(typebot, typebot.steps.byId[edge.from.stepId].edgeId)
typebot.steps.byId[edge.from.stepId].edgeId = newEdge.id
}
typebot.edges.byId[newEdge.id] = newEdge
typebot.edges.allIds.push(newEdge.id)
})
},
updateEdge: (edgeId: string, updates: Partial<Omit<Edge, 'id'>>) =>
setTypebot((typebot) => {
typebot.edges.byId[edgeId] = {
...typebot.edges.byId[edgeId],
...updates,
}
}),
deleteEdge: (edgeId: string) => {
setTypebot((typebot) => {
deleteEdgeDraft(typebot, edgeId)
})
},
})
export const deleteEdgeDraft = (
typebot: WritableDraft<Typebot>,
edgeId?: string
) => {
if (!edgeId) return
delete typebot.edges.byId[edgeId]
const index = typebot.edges.allIds.indexOf(edgeId)
if (index !== -1) typebot.edges.allIds.splice(index, 1)
}

View File

@ -11,6 +11,7 @@ import { removeEmptyBlocks } from './blocks'
import { WritableDraft } from 'immer/dist/types/types-external'
import { createChoiceItemDraft, deleteChoiceItemDraft } from './choiceItems'
import { isChoiceInput } from 'utils'
import { deleteEdgeDraft } from './edges'
export type StepsActions = {
createStep: (
@ -50,12 +51,23 @@ export const stepsAction = (setTypebot: Updater<Typebot>): StepsActions => ({
setTypebot((typebot) => {
const step = typebot.steps.byId[stepId]
if (isChoiceInput(step)) deleteChoiceItemsInsideStep(typebot, step)
deleteAssociatedEdges(typebot, stepId)
removeStepIdFromBlock(typebot, stepId)
deleteStepDraft(typebot, stepId)
})
},
})
const deleteAssociatedEdges = (
typebot: WritableDraft<Typebot>,
stepId: string
) => {
typebot.edges.allIds.forEach((edgeId) => {
if (typebot.edges.byId[edgeId].from.stepId === stepId)
deleteEdgeDraft(typebot, edgeId)
})
}
const removeStepIdFromBlock = (
typebot: WritableDraft<Typebot>,
stepId: string

View File

@ -1,71 +1,96 @@
{
"id": "typebot4",
"createdAt": "2022-01-17T14:37:01.826Z",
"updatedAt": "2022-01-17T14:37:01.826Z",
"id": "ckylszb9z0354z31a623dg7ji",
"createdAt": "2022-01-19T17:12:27.863Z",
"updatedAt": "2022-01-19T17:12:27.863Z",
"name": "My typebot",
"ownerId": "user2",
"ownerId": "ckylsz8yy0335z31amvq0jwtt",
"publishedTypebotId": null,
"folderId": null,
"blocks": {
"byId": {
"bec5A5bLwenmZpCJc8FRaM": {
"id": "bec5A5bLwenmZpCJc8FRaM",
"j24wz82YG3rjXMgrmCiTLy": {
"id": "j24wz82YG3rjXMgrmCiTLy",
"title": "Start",
"stepIds": ["uDMB7a2ucg17WGvbQJjeRn"],
"stepIds": ["1NdXPCiRicqDA8k4JfnXfi"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"bcyiT7P6E99YnHKnpxs4Yux": {
"id": "bcyiT7P6E99YnHKnpxs4Yux",
"bmaKTUXkT2cc3wtKfK7ra71": {
"id": "bmaKTUXkT2cc3wtKfK7ra71",
"title": "Block #2",
"stepIds": ["step1"],
"graphCoordinates": { "x": 411, "y": 108 }
"graphCoordinates": { "x": 175, "y": 197 },
"stepIds": ["spHxPWbSqkVZW9gqH86ovC5"]
},
"bgpNxHtBBXWrP1QMe2A8hZ9": {
"id": "bgpNxHtBBXWrP1QMe2A8hZ9",
"bnt8fM5Wgc8gBg2iSmUcfJu": {
"id": "bnt8fM5Wgc8gBg2iSmUcfJu",
"title": "Block #3",
"graphCoordinates": { "x": 1, "y": 236 },
"stepIds": ["sj72oDhJEe4N92KTt64GKWs"]
"graphCoordinates": { "x": 504, "y": 347 },
"stepIds": ["siPoEE9H27hVHqykth3a7Kj"]
}
},
"allIds": [
"bec5A5bLwenmZpCJc8FRaM",
"bcyiT7P6E99YnHKnpxs4Yux",
"bgpNxHtBBXWrP1QMe2A8hZ9"
"j24wz82YG3rjXMgrmCiTLy",
"bmaKTUXkT2cc3wtKfK7ra71",
"bnt8fM5Wgc8gBg2iSmUcfJu"
]
},
"steps": {
"byId": {
"step1": {
"id": "step1",
"type": "Google Sheets",
"blockId": "bcyiT7P6E99YnHKnpxs4Yux"
},
"uDMB7a2ucg17WGvbQJjeRn": {
"id": "uDMB7a2ucg17WGvbQJjeRn",
"1NdXPCiRicqDA8k4JfnXfi": {
"id": "1NdXPCiRicqDA8k4JfnXfi",
"type": "start",
"label": "Start",
"target": { "blockId": "bgpNxHtBBXWrP1QMe2A8hZ9" },
"blockId": "bec5A5bLwenmZpCJc8FRaM"
"blockId": "j24wz82YG3rjXMgrmCiTLy",
"edgeId": "benDCcLMUWNvi6Fg6CXE9H"
},
"sj72oDhJEe4N92KTt64GKWs": {
"id": "sj72oDhJEe4N92KTt64GKWs",
"blockId": "bgpNxHtBBXWrP1QMe2A8hZ9",
"spHxPWbSqkVZW9gqH86ovC5": {
"id": "spHxPWbSqkVZW9gqH86ovC5",
"blockId": "bmaKTUXkT2cc3wtKfK7ra71",
"type": "email input",
"target": { "blockId": "bcyiT7P6E99YnHKnpxs4Yux" },
"options": { "variableId": "8H3aQsNji2Gyfpp3RPozNN" }
"options": { "variableId": "oexLr4sJQNVdSnYCGgGRB3" },
"edgeId": "6Tax9rw7L8kmRn9JRD2Mrg"
},
"siPoEE9H27hVHqykth3a7Kj": {
"id": "siPoEE9H27hVHqykth3a7Kj",
"blockId": "bnt8fM5Wgc8gBg2iSmUcfJu",
"type": "Google Sheets"
}
},
"allIds": ["uDMB7a2ucg17WGvbQJjeRn", "step1", "sj72oDhJEe4N92KTt64GKWs"]
"allIds": [
"1NdXPCiRicqDA8k4JfnXfi",
"spHxPWbSqkVZW9gqH86ovC5",
"siPoEE9H27hVHqykth3a7Kj"
]
},
"choiceItems": { "byId": {}, "allIds": [] },
"variables": {
"byId": {
"8H3aQsNji2Gyfpp3RPozNN": {
"id": "8H3aQsNji2Gyfpp3RPozNN",
"oexLr4sJQNVdSnYCGgGRB3": {
"id": "oexLr4sJQNVdSnYCGgGRB3",
"name": "Email"
}
},
"allIds": ["8H3aQsNji2Gyfpp3RPozNN"]
"allIds": ["oexLr4sJQNVdSnYCGgGRB3"]
},
"edges": {
"byId": {
"benDCcLMUWNvi6Fg6CXE9H": {
"from": {
"blockId": "j24wz82YG3rjXMgrmCiTLy",
"stepId": "1NdXPCiRicqDA8k4JfnXfi"
},
"to": { "blockId": "bmaKTUXkT2cc3wtKfK7ra71" },
"id": "benDCcLMUWNvi6Fg6CXE9H"
},
"6Tax9rw7L8kmRn9JRD2Mrg": {
"from": {
"blockId": "bmaKTUXkT2cc3wtKfK7ra71",
"stepId": "spHxPWbSqkVZW9gqH86ovC5"
},
"to": { "blockId": "bnt8fM5Wgc8gBg2iSmUcfJu" },
"id": "6Tax9rw7L8kmRn9JRD2Mrg"
}
},
"allIds": ["benDCcLMUWNvi6Fg6CXE9H", "6Tax9rw7L8kmRn9JRD2Mrg"]
},
"theme": {
"general": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,70 +1,70 @@
{
"id": "typebot4",
"createdAt": "2022-01-17T14:37:01.826Z",
"updatedAt": "2022-01-17T14:37:01.826Z",
"id": "ckyltevlb0559z31an8cmkyrp",
"createdAt": "2022-01-19T17:24:34.031Z",
"updatedAt": "2022-01-19T17:24:34.031Z",
"name": "My typebot",
"ownerId": "user2",
"ownerId": "ckyltekzq0533z31ad8opmacz",
"publishedTypebotId": null,
"folderId": null,
"blocks": {
"byId": {
"bec5A5bLwenmZpCJc8FRaM": {
"id": "bec5A5bLwenmZpCJc8FRaM",
"kPupUcEn7TcBGKHUpgK2Q5": {
"id": "kPupUcEn7TcBGKHUpgK2Q5",
"title": "Start",
"stepIds": ["uDMB7a2ucg17WGvbQJjeRn"],
"stepIds": ["nP5oWm7PxigMupyWpPLq24"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"bcyiT7P6E99YnHKnpxs4Yux": {
"id": "bcyiT7P6E99YnHKnpxs4Yux",
"bi4J5fv9DFn1zPSqGf8eRht": {
"id": "bi4J5fv9DFn1zPSqGf8eRht",
"title": "Block #2",
"stepIds": ["step1"],
"graphCoordinates": { "x": 411, "y": 108 }
"graphCoordinates": { "x": 104, "y": 201 },
"stepIds": ["scH9qdXwFfAScoavj6UzQNT"]
},
"bgpNxHtBBXWrP1QMe2A8hZ9": {
"id": "bgpNxHtBBXWrP1QMe2A8hZ9",
"bwqGhUsa2SKaxXSrKtapVc8": {
"id": "bwqGhUsa2SKaxXSrKtapVc8",
"title": "Block #3",
"stepIds": ["sj72oDhJEe4N92KTt64GKWs"],
"graphCoordinates": { "x": 1, "y": 236 }
"graphCoordinates": { "x": 458, "y": 292 },
"stepIds": ["shZdc8Qw48domEbS7vLW5eN"]
},
"buwMV9tx2EFcMbRkNTELt3J": {
"id": "buwMV9tx2EFcMbRkNTELt3J",
"bqmgS9hLUu2RA2oxVv7hMka": {
"id": "bqmgS9hLUu2RA2oxVv7hMka",
"title": "Block #4",
"graphCoordinates": { "x": 441, "y": 330 },
"stepIds": ["s2R2bk7qfSRVgTyRmPhVw7p"]
"graphCoordinates": { "x": 102, "y": 432 },
"stepIds": ["s4z6G3MGAyhXChU9jakQWer"]
}
},
"allIds": [
"bec5A5bLwenmZpCJc8FRaM",
"bcyiT7P6E99YnHKnpxs4Yux",
"bgpNxHtBBXWrP1QMe2A8hZ9",
"buwMV9tx2EFcMbRkNTELt3J"
"kPupUcEn7TcBGKHUpgK2Q5",
"bi4J5fv9DFn1zPSqGf8eRht",
"bwqGhUsa2SKaxXSrKtapVc8",
"bqmgS9hLUu2RA2oxVv7hMka"
]
},
"steps": {
"byId": {
"step1": {
"id": "step1",
"type": "Google Sheets",
"blockId": "bcyiT7P6E99YnHKnpxs4Yux",
"target": { "blockId": "buwMV9tx2EFcMbRkNTELt3J" }
},
"uDMB7a2ucg17WGvbQJjeRn": {
"id": "uDMB7a2ucg17WGvbQJjeRn",
"nP5oWm7PxigMupyWpPLq24": {
"id": "nP5oWm7PxigMupyWpPLq24",
"type": "start",
"label": "Start",
"target": { "blockId": "bgpNxHtBBXWrP1QMe2A8hZ9" },
"blockId": "bec5A5bLwenmZpCJc8FRaM"
"blockId": "kPupUcEn7TcBGKHUpgK2Q5",
"edgeId": "kCLXGLpiM2F6pn4wFYc1f5"
},
"sj72oDhJEe4N92KTt64GKWs": {
"id": "sj72oDhJEe4N92KTt64GKWs",
"scH9qdXwFfAScoavj6UzQNT": {
"id": "scH9qdXwFfAScoavj6UzQNT",
"blockId": "bi4J5fv9DFn1zPSqGf8eRht",
"type": "email input",
"target": { "blockId": "bcyiT7P6E99YnHKnpxs4Yux" },
"blockId": "bgpNxHtBBXWrP1QMe2A8hZ9",
"options": { "variableId": "8H3aQsNji2Gyfpp3RPozNN" }
"options": { "variableId": "ifXp66N1meAtoUDcbqWxuD" },
"edgeId": "7Czn5hJFUfpkRGtwGnKxtt"
},
"s2R2bk7qfSRVgTyRmPhVw7p": {
"id": "s2R2bk7qfSRVgTyRmPhVw7p",
"blockId": "buwMV9tx2EFcMbRkNTELt3J",
"shZdc8Qw48domEbS7vLW5eN": {
"id": "shZdc8Qw48domEbS7vLW5eN",
"blockId": "bwqGhUsa2SKaxXSrKtapVc8",
"type": "Google Sheets",
"edgeId": "eMhGokwHMDRDrynSvpiRje"
},
"s4z6G3MGAyhXChU9jakQWer": {
"id": "s4z6G3MGAyhXChU9jakQWer",
"blockId": "bqmgS9hLUu2RA2oxVv7hMka",
"type": "text",
"content": {
"html": "<div>Your name is: {{First name}} {{Last name}}</div>",
@ -81,21 +81,54 @@
}
},
"allIds": [
"uDMB7a2ucg17WGvbQJjeRn",
"step1",
"sj72oDhJEe4N92KTt64GKWs",
"s2R2bk7qfSRVgTyRmPhVw7p"
"nP5oWm7PxigMupyWpPLq24",
"scH9qdXwFfAScoavj6UzQNT",
"shZdc8Qw48domEbS7vLW5eN",
"s4z6G3MGAyhXChU9jakQWer"
]
},
"choiceItems": { "byId": {}, "allIds": [] },
"variables": {
"byId": {
"8H3aQsNji2Gyfpp3RPozNN": {
"id": "8H3aQsNji2Gyfpp3RPozNN",
"ifXp66N1meAtoUDcbqWxuD": {
"id": "ifXp66N1meAtoUDcbqWxuD",
"name": "Email"
}
},
"allIds": ["8H3aQsNji2Gyfpp3RPozNN"]
"allIds": ["ifXp66N1meAtoUDcbqWxuD"]
},
"edges": {
"byId": {
"kCLXGLpiM2F6pn4wFYc1f5": {
"from": {
"blockId": "kPupUcEn7TcBGKHUpgK2Q5",
"stepId": "nP5oWm7PxigMupyWpPLq24"
},
"to": { "blockId": "bi4J5fv9DFn1zPSqGf8eRht" },
"id": "kCLXGLpiM2F6pn4wFYc1f5"
},
"7Czn5hJFUfpkRGtwGnKxtt": {
"from": {
"blockId": "bi4J5fv9DFn1zPSqGf8eRht",
"stepId": "scH9qdXwFfAScoavj6UzQNT"
},
"to": { "blockId": "bwqGhUsa2SKaxXSrKtapVc8" },
"id": "7Czn5hJFUfpkRGtwGnKxtt"
},
"eMhGokwHMDRDrynSvpiRje": {
"from": {
"blockId": "bwqGhUsa2SKaxXSrKtapVc8",
"stepId": "shZdc8Qw48domEbS7vLW5eN"
},
"to": { "blockId": "bqmgS9hLUu2RA2oxVv7hMka" },
"id": "eMhGokwHMDRDrynSvpiRje"
}
},
"allIds": [
"kCLXGLpiM2F6pn4wFYc1f5",
"7Czn5hJFUfpkRGtwGnKxtt",
"eMhGokwHMDRDrynSvpiRje"
]
},
"theme": {
"general": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -1,110 +1,71 @@
{
"id": "typebot4",
"createdAt": "2022-01-14T14:23:36.576Z",
"updatedAt": "2022-01-14T14:23:36.576Z",
"id": "ckylsd52p0114z31aobllswmu",
"createdAt": "2022-01-19T16:55:13.393Z",
"updatedAt": "2022-01-19T16:55:13.393Z",
"name": "My typebot",
"ownerId": "ckye5hs3e1801em1a0eodjj3f",
"ownerId": "ckylsbdf60088z31ayqytest6",
"publishedTypebotId": null,
"folderId": null,
"blocks": {
"byId": {
"1YV9MuAa6dd6eNxC5BipDZ": {
"id": "1YV9MuAa6dd6eNxC5BipDZ",
"2x83WHtEBkiv7pk7KgqJwZ": {
"id": "2x83WHtEBkiv7pk7KgqJwZ",
"title": "Start",
"stepIds": ["3EzaqYRLFqFQFbCj2gQP3q"],
"stepIds": ["1A76iZBgXG7hvkG2koCxe4"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"bnmeD9SVeGPhF4qvwKfxE8R": {
"id": "bnmeD9SVeGPhF4qvwKfxE8R",
"bwga7RwqQWbowdHph27DM1N": {
"id": "bwga7RwqQWbowdHph27DM1N",
"title": "Block #2",
"stepIds": ["condition1", "condition2"],
"graphCoordinates": { "x": 194, "y": 228 }
"graphCoordinates": { "x": 78, "y": 224 },
"stepIds": ["srwUKaUFFmehppJ2ZDqp4xG", "sxvzuo48GHi3AcAfmiFyYC1"]
},
"b3EaF53FGbQH5MhbBPUjDjb": {
"id": "b3EaF53FGbQH5MhbBPUjDjb",
"bu8whx817bJBG37FQrtD5dD": {
"id": "bu8whx817bJBG37FQrtD5dD",
"title": "Block #3",
"graphCoordinates": { "x": 430, "y": 287 },
"stepIds": ["ituVWW1AvQeVdFHTwsiVao", "5SLc4whZooZVUfr1bmTNSC"]
},
"b59jwmEdwZUvJszV394x44u": {
"id": "b59jwmEdwZUvJszV394x44u",
"title": "Block #4",
"graphCoordinates": { "x": 375, "y": -39 },
"stepIds": ["siBqadjM6AJXf25Ct4413dM", "sqTSo2heZ5vdfzJjNZfYUK5"]
"graphCoordinates": { "x": 844, "y": 185 },
"stepIds": ["sm1YcKTL9cQMCGywzo1wyBB"]
},
"b2Wjyg4MsqB5xhQYQPbPYkc": {
"id": "b2Wjyg4MsqB5xhQYQPbPYkc",
"title": "Block #4",
"graphCoordinates": { "x": 712, "y": 186 },
"stepIds": ["srFH8bxpJZuShgr2hmz4uhx"]
},
"b4KB24EywCVt3zX3opqMvYS": {
"id": "b4KB24EywCVt3zX3opqMvYS",
"baVF9HqhuSnLDZqY9eRPpcp": {
"id": "baVF9HqhuSnLDZqY9eRPpcp",
"title": "Block #5",
"graphCoordinates": { "x": 700, "y": 340 },
"stepIds": ["ssBZF1FgMDYZJTbmTzb8Uks"]
"graphCoordinates": { "x": 841, "y": 356 },
"stepIds": ["sb3o6J8Fybg6u8KuayKviJq"]
},
"bppe1zzyayc8ub14ozqJEXb": {
"id": "bppe1zzyayc8ub14ozqJEXb",
"b9aEH46RHuZWTdQwZJ6KBWR": {
"id": "b9aEH46RHuZWTdQwZJ6KBWR",
"title": "Block #6",
"graphCoordinates": { "x": 713, "y": 491 },
"stepIds": ["scQmWL2qGp1oXnEYdqjLcDv"]
"graphCoordinates": { "x": 839, "y": 523 },
"stepIds": ["scKogEJSTq4kPeHRhwTTjit"]
}
},
"allIds": [
"1YV9MuAa6dd6eNxC5BipDZ",
"bnmeD9SVeGPhF4qvwKfxE8R",
"b3EaF53FGbQH5MhbBPUjDjb",
"b2Wjyg4MsqB5xhQYQPbPYkc",
"b4KB24EywCVt3zX3opqMvYS",
"bppe1zzyayc8ub14ozqJEXb"
"2x83WHtEBkiv7pk7KgqJwZ",
"bwga7RwqQWbowdHph27DM1N",
"bu8whx817bJBG37FQrtD5dD",
"b59jwmEdwZUvJszV394x44u",
"baVF9HqhuSnLDZqY9eRPpcp",
"b9aEH46RHuZWTdQwZJ6KBWR"
]
},
"steps": {
"byId": {
"3EzaqYRLFqFQFbCj2gQP3q": {
"id": "3EzaqYRLFqFQFbCj2gQP3q",
"1A76iZBgXG7hvkG2koCxe4": {
"id": "1A76iZBgXG7hvkG2koCxe4",
"type": "start",
"label": "Start",
"target": { "blockId": "b3EaF53FGbQH5MhbBPUjDjb" },
"blockId": "1YV9MuAa6dd6eNxC5BipDZ"
"blockId": "2x83WHtEBkiv7pk7KgqJwZ",
"edgeId": "jjNy2hYgrQgPS9EBMKA7MH"
},
"condition1": {
"id": "condition1",
"type": "Condition",
"blockId": "bnmeD9SVeGPhF4qvwKfxE8R",
"trueTarget": { "blockId": "b2Wjyg4MsqB5xhQYQPbPYkc" },
"options": {
"comparisons": {
"byId": { "comparison1": { "comparisonOperator": "Equal to" } },
"allIds": ["comparison1"]
},
"logicalOperator": "AND"
}
},
"condition2": {
"id": "condition2",
"blockId": "bnmeD9SVeGPhF4qvwKfxE8R",
"type": "Condition",
"trueTarget": { "blockId": "b4KB24EywCVt3zX3opqMvYS" },
"falseTarget": { "blockId": "bppe1zzyayc8ub14ozqJEXb" },
"options": {
"comparisons": {
"byId": { "comparison1": { "comparisonOperator": "Equal to" } },
"allIds": ["comparison1"]
},
"logicalOperator": "AND"
}
},
"sqTSo2heZ5vdfzJjNZfYUK5": {
"id": "sqTSo2heZ5vdfzJjNZfYUK5",
"blockId": "b3EaF53FGbQH5MhbBPUjDjb",
"type": "number input",
"options": {
"variableId": "icVxLRv1sQnPyNwRX9cjK9",
"min": 0,
"max": 150,
"labels": { "placeholder": "Type your age..." }
},
"target": { "blockId": "bnmeD9SVeGPhF4qvwKfxE8R" }
},
"siBqadjM6AJXf25Ct4413dM": {
"id": "siBqadjM6AJXf25Ct4413dM",
"blockId": "b3EaF53FGbQH5MhbBPUjDjb",
"srwUKaUFFmehppJ2ZDqp4xG": {
"id": "srwUKaUFFmehppJ2ZDqp4xG",
"blockId": "bwga7RwqQWbowdHph27DM1N",
"type": "text",
"content": {
"html": "<div>How old are you?</div>",
@ -114,39 +75,78 @@
"plainText": "How old are you?"
}
},
"srFH8bxpJZuShgr2hmz4uhx": {
"id": "srFH8bxpJZuShgr2hmz4uhx",
"blockId": "b2Wjyg4MsqB5xhQYQPbPYkc",
"sxvzuo48GHi3AcAfmiFyYC1": {
"id": "sxvzuo48GHi3AcAfmiFyYC1",
"blockId": "bwga7RwqQWbowdHph27DM1N",
"type": "number input",
"options": { "variableId": "dEz689uVm8AxUM8TrbQd2t" },
"edgeId": "7mcWaWohM9zGtLX8ZSnqFy"
},
"ituVWW1AvQeVdFHTwsiVao": {
"id": "ituVWW1AvQeVdFHTwsiVao",
"blockId": "bu8whx817bJBG37FQrtD5dD",
"type": "Condition",
"options": {
"comparisons": {
"byId": {
"ituVWW1AvQeVdFHTwsiVao": {
"id": "ituVWW1AvQeVdFHTwsiVao",
"comparisonOperator": "Equal to",
"variableId": "ixdebNyG6gYgmowpkioQtG"
}
},
"allIds": ["ituVWW1AvQeVdFHTwsiVao"]
},
"logicalOperator": "AND"
},
"trueEdgeId": "iBPsFyBsPv6Rbdfo2QdJyi"
},
"5SLc4whZooZVUfr1bmTNSC": {
"id": "5SLc4whZooZVUfr1bmTNSC",
"blockId": "bu8whx817bJBG37FQrtD5dD",
"type": "Condition",
"options": {
"comparisons": {
"byId": {
"5SLc4whZooZVUfr1bmTNSC": {
"id": "5SLc4whZooZVUfr1bmTNSC",
"comparisonOperator": "Equal to"
}
},
"allIds": ["5SLc4whZooZVUfr1bmTNSC"]
},
"logicalOperator": "AND"
},
"trueEdgeId": "354PJ2jD5U3J2APqLsPJrp",
"falseEdgeId": "94bmeCLigEUUpWYw2xsAVB"
},
"sm1YcKTL9cQMCGywzo1wyBB": {
"id": "sm1YcKTL9cQMCGywzo1wyBB",
"blockId": "b59jwmEdwZUvJszV394x44u",
"type": "text",
"content": {
"html": "<div>Wow you are older than 80</div>",
"html": "<div>You are older than 80</div>",
"richText": [
{
"type": "p",
"children": [{ "text": "Wow you are older than 80" }]
}
{ "type": "p", "children": [{ "text": "You are older than 80" }] }
],
"plainText": "Wow you are older than 80"
"plainText": "You are older than 80"
}
},
"ssBZF1FgMDYZJTbmTzb8Uks": {
"id": "ssBZF1FgMDYZJTbmTzb8Uks",
"blockId": "b4KB24EywCVt3zX3opqMvYS",
"sb3o6J8Fybg6u8KuayKviJq": {
"id": "sb3o6J8Fybg6u8KuayKviJq",
"blockId": "baVF9HqhuSnLDZqY9eRPpcp",
"type": "text",
"content": {
"html": "<div>Wow you are older than 20</div>",
"html": "<div>You are older than 20</div>",
"richText": [
{
"type": "p",
"children": [{ "text": "Wow you are older than 20" }]
}
{ "type": "p", "children": [{ "text": "You are older than 20" }] }
],
"plainText": "Wow you are older than 20"
"plainText": "You are older than 20"
}
},
"scQmWL2qGp1oXnEYdqjLcDv": {
"id": "scQmWL2qGp1oXnEYdqjLcDv",
"blockId": "bppe1zzyayc8ub14ozqJEXb",
"scKogEJSTq4kPeHRhwTTjit": {
"id": "scKogEJSTq4kPeHRhwTTjit",
"blockId": "b9aEH46RHuZWTdQwZJ6KBWR",
"type": "text",
"content": {
"html": "<div>You are younger than 20</div>",
@ -158,25 +158,80 @@
}
},
"allIds": [
"3EzaqYRLFqFQFbCj2gQP3q",
"condition1",
"condition2",
"sqTSo2heZ5vdfzJjNZfYUK5",
"siBqadjM6AJXf25Ct4413dM",
"srFH8bxpJZuShgr2hmz4uhx",
"ssBZF1FgMDYZJTbmTzb8Uks",
"scQmWL2qGp1oXnEYdqjLcDv"
"1A76iZBgXG7hvkG2koCxe4",
"srwUKaUFFmehppJ2ZDqp4xG",
"sxvzuo48GHi3AcAfmiFyYC1",
"ituVWW1AvQeVdFHTwsiVao",
"5SLc4whZooZVUfr1bmTNSC",
"sxvzuo48GHi3AcAfmiFyYC1",
"sm1YcKTL9cQMCGywzo1wyBB",
"sb3o6J8Fybg6u8KuayKviJq",
"scKogEJSTq4kPeHRhwTTjit"
]
},
"choiceItems": { "byId": {}, "allIds": [] },
"variables": {
"byId": {
"icVxLRv1sQnPyNwRX9cjK9": {
"id": "icVxLRv1sQnPyNwRX9cjK9",
"dEz689uVm8AxUM8TrbQd2t": {
"id": "dEz689uVm8AxUM8TrbQd2t",
"name": "Age"
}
},
"allIds": ["icVxLRv1sQnPyNwRX9cjK9"]
"allIds": ["dEz689uVm8AxUM8TrbQd2t"]
},
"edges": {
"byId": {
"jjNy2hYgrQgPS9EBMKA7MH": {
"from": {
"blockId": "2x83WHtEBkiv7pk7KgqJwZ",
"stepId": "1A76iZBgXG7hvkG2koCxe4"
},
"to": { "blockId": "bwga7RwqQWbowdHph27DM1N" },
"id": "jjNy2hYgrQgPS9EBMKA7MH"
},
"iBPsFyBsPv6Rbdfo2QdJyi": {
"from": {
"blockId": "bu8whx817bJBG37FQrtD5dD",
"stepId": "ituVWW1AvQeVdFHTwsiVao",
"conditionType": "true"
},
"to": { "blockId": "b59jwmEdwZUvJszV394x44u" },
"id": "iBPsFyBsPv6Rbdfo2QdJyi"
},
"354PJ2jD5U3J2APqLsPJrp": {
"from": {
"blockId": "bu8whx817bJBG37FQrtD5dD",
"stepId": "5SLc4whZooZVUfr1bmTNSC",
"conditionType": "true"
},
"to": { "blockId": "baVF9HqhuSnLDZqY9eRPpcp" },
"id": "354PJ2jD5U3J2APqLsPJrp"
},
"94bmeCLigEUUpWYw2xsAVB": {
"from": {
"blockId": "bu8whx817bJBG37FQrtD5dD",
"stepId": "5SLc4whZooZVUfr1bmTNSC",
"conditionType": "false"
},
"to": { "blockId": "b9aEH46RHuZWTdQwZJ6KBWR" },
"id": "94bmeCLigEUUpWYw2xsAVB"
},
"7mcWaWohM9zGtLX8ZSnqFy": {
"from": {
"blockId": "bwga7RwqQWbowdHph27DM1N",
"stepId": "sxvzuo48GHi3AcAfmiFyYC1"
},
"to": { "blockId": "bu8whx817bJBG37FQrtD5dD" },
"id": "7mcWaWohM9zGtLX8ZSnqFy"
}
},
"allIds": [
"jjNy2hYgrQgPS9EBMKA7MH",
"iBPsFyBsPv6Rbdfo2QdJyi",
"354PJ2jD5U3J2APqLsPJrp",
"94bmeCLigEUUpWYw2xsAVB",
"7mcWaWohM9zGtLX8ZSnqFy"
]
},
"theme": {
"general": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -1,121 +1,144 @@
{
"id": "typebot4",
"createdAt": "2022-01-13T08:10:06.705Z",
"updatedAt": "2022-01-13T08:10:06.705Z",
"id": "ckylrr3qh0030fn1a3nszzxiu",
"createdAt": "2022-01-19T16:38:05.225Z",
"updatedAt": "2022-01-19T16:38:05.225Z",
"name": "My typebot",
"ownerId": "ckybcurfh1612li1a62gqojvj",
"ownerId": "ckylrpsmt0006fn1ah956d0z1",
"publishedTypebotId": null,
"folderId": null,
"blocks": {
"byId": {
"eXpkU5dMsjRPeY3WverF45": {
"id": "eXpkU5dMsjRPeY3WverF45",
"kmUzhRFzSKjkaipYNcku9S": {
"id": "kmUzhRFzSKjkaipYNcku9S",
"title": "Start",
"stepIds": ["vWwzs6EmChn2PJcJQMr2gT"],
"stepIds": ["6XgP3JoCh7Y4M8GCX9DKym"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"b87iNgPqGvKrybzHcTD4Sze": {
"id": "b87iNgPqGvKrybzHcTD4Sze",
"title": "Block #4",
"stepIds": ["s8a2ASVaM3PoaD3y9amHrtT", "sc7FNu4BUCmmhmP14hk2Hij"],
"graphCoordinates": { "x": 584, "y": 389 }
},
"bg22czQixaBBY8M8LJ9wEm1": {
"id": "bg22czQixaBBY8M8LJ9wEm1",
"title": "Block #3",
"stepIds": ["set-var-1", "set-var-2"],
"graphCoordinates": { "x": 581, "y": 97 }
},
"bvGtcdRjZtbbaimKyUD3NAW": {
"id": "bvGtcdRjZtbbaimKyUD3NAW",
"bwWRAaX5m6NZyZ9jjpXmWSb": {
"id": "bwWRAaX5m6NZyZ9jjpXmWSb",
"title": "Block #2",
"stepIds": ["sgFoKA5Y4MXBkqFzCGCWGeg", "number-step"],
"graphCoordinates": { "x": 190, "y": 222 }
"graphCoordinates": { "x": -21, "y": 221 },
"stepIds": ["sqMVMXeRYp4inLcRqej2Wac", "s8n3nJajsBaYqrFeRYVvcf6"]
},
"baUyUnNBxZzPe1z5PqE4NkD": {
"id": "baUyUnNBxZzPe1z5PqE4NkD",
"title": "Block #3",
"graphCoordinates": { "x": 375, "y": 280 },
"stepIds": ["shfL5ueQDuj2RPcJPWZGArT", "sugJ6xN3jFys1CjWfsxGhiJ"]
},
"bwkKNpJmAFCCLbZSnPnnLnR": {
"id": "bwkKNpJmAFCCLbZSnPnnLnR",
"title": "Block #4",
"graphCoordinates": { "x": 421, "y": 42 },
"stepIds": ["shR7ae3iNEvB6arCSu7wVFF"]
}
},
"allIds": [
"eXpkU5dMsjRPeY3WverF45",
"bvGtcdRjZtbbaimKyUD3NAW",
"bg22czQixaBBY8M8LJ9wEm1",
"b87iNgPqGvKrybzHcTD4Sze"
"kmUzhRFzSKjkaipYNcku9S",
"bwWRAaX5m6NZyZ9jjpXmWSb",
"baUyUnNBxZzPe1z5PqE4NkD",
"bwkKNpJmAFCCLbZSnPnnLnR"
]
},
"steps": {
"byId": {
"vWwzs6EmChn2PJcJQMr2gT": {
"id": "vWwzs6EmChn2PJcJQMr2gT",
"6XgP3JoCh7Y4M8GCX9DKym": {
"id": "6XgP3JoCh7Y4M8GCX9DKym",
"type": "start",
"label": "Start",
"target": { "blockId": "bvGtcdRjZtbbaimKyUD3NAW" },
"blockId": "eXpkU5dMsjRPeY3WverF45"
"blockId": "kmUzhRFzSKjkaipYNcku9S",
"edgeId": "ahfJ4fUuvxX2dcBMk876tf"
},
"set-var-2": {
"id": "set-var-2",
"type": "Set variable",
"target": { "blockId": "b87iNgPqGvKrybzHcTD4Sze" },
"blockId": "bg22czQixaBBY8M8LJ9wEm1"
},
"s8a2ASVaM3PoaD3y9amHrtT": {
"id": "s8a2ASVaM3PoaD3y9amHrtT",
"type": "text",
"blockId": "b87iNgPqGvKrybzHcTD4Sze",
"content": {
"html": "<div>Total: {{Total}}</div>",
"richText": [
{ "type": "p", "children": [{ "text": "Total: {{Total}}" }] }
],
"plainText": "Total: {{Total}}"
}
},
"sgFoKA5Y4MXBkqFzCGCWGeg": {
"id": "sgFoKA5Y4MXBkqFzCGCWGeg",
"type": "text",
"blockId": "bvGtcdRjZtbbaimKyUD3NAW",
"content": {
"html": "<div>1000 * ?</div>",
"richText": [{ "type": "p", "children": [{ "text": "1000 * ?" }] }],
"plainText": "1000 * ?"
}
},
"number-step": {
"id": "number-step",
"s8n3nJajsBaYqrFeRYVvcf6": {
"id": "s8n3nJajsBaYqrFeRYVvcf6",
"blockId": "bwWRAaX5m6NZyZ9jjpXmWSb",
"type": "number input",
"target": { "blockId": "bg22czQixaBBY8M8LJ9wEm1" },
"blockId": "bvGtcdRjZtbbaimKyUD3NAW"
"edgeId": "dcJedLC7qsLtsmm1wbiFFc"
},
"set-var-1": {
"id": "set-var-1",
"blockId": "bg22czQixaBBY8M8LJ9wEm1",
"sqMVMXeRYp4inLcRqej2Wac": {
"id": "sqMVMXeRYp4inLcRqej2Wac",
"blockId": "bwWRAaX5m6NZyZ9jjpXmWSb",
"type": "text",
"content": {
"html": "<div>How old are you?</div>",
"richText": [
{ "type": "p", "children": [{ "text": "How old are you?" }] }
],
"plainText": "How old are you?"
}
},
"shfL5ueQDuj2RPcJPWZGArT": {
"id": "shfL5ueQDuj2RPcJPWZGArT",
"blockId": "baUyUnNBxZzPe1z5PqE4NkD",
"type": "Set variable"
},
"sc7FNu4BUCmmhmP14hk2Hij": {
"id": "sc7FNu4BUCmmhmP14hk2Hij",
"blockId": "b87iNgPqGvKrybzHcTD4Sze",
"sugJ6xN3jFys1CjWfsxGhiJ": {
"id": "sugJ6xN3jFys1CjWfsxGhiJ",
"blockId": "baUyUnNBxZzPe1z5PqE4NkD",
"type": "Set variable",
"edgeId": "sA5gvCVVBVYdGsdeSGF5ei"
},
"shR7ae3iNEvB6arCSu7wVFF": {
"id": "shR7ae3iNEvB6arCSu7wVFF",
"blockId": "bwkKNpJmAFCCLbZSnPnnLnR",
"type": "text",
"content": {
"html": "<div>Custom var: {{Custom var}}</div>",
"html": "<div>Total: {{Total}}</div><div>Custom var: {{Custom var}}</div>",
"richText": [
{ "type": "p", "children": [{ "text": "Total: {{Total}}" }] },
{
"type": "p",
"children": [{ "text": "Custom var: {{Custom var}}" }]
}
],
"plainText": "Custom var: {{Custom var}}"
"plainText": "Total: {{Total}}Custom var: {{Custom var}}"
}
}
},
"allIds": [
"vWwzs6EmChn2PJcJQMr2gT",
"s8a2ASVaM3PoaD3y9amHrtT",
"set-var-2",
"sgFoKA5Y4MXBkqFzCGCWGeg",
"number-step",
"set-var-1",
"sc7FNu4BUCmmhmP14hk2Hij"
"6XgP3JoCh7Y4M8GCX9DKym",
"s8n3nJajsBaYqrFeRYVvcf6",
"sqMVMXeRYp4inLcRqej2Wac",
"shfL5ueQDuj2RPcJPWZGArT",
"sugJ6xN3jFys1CjWfsxGhiJ",
"shR7ae3iNEvB6arCSu7wVFF"
]
},
"choiceItems": { "byId": {}, "allIds": [] },
"variables": { "byId": {}, "allIds": [] },
"edges": {
"byId": {
"ahfJ4fUuvxX2dcBMk876tf": {
"from": {
"blockId": "kmUzhRFzSKjkaipYNcku9S",
"stepId": "6XgP3JoCh7Y4M8GCX9DKym"
},
"to": { "blockId": "bwWRAaX5m6NZyZ9jjpXmWSb" },
"id": "ahfJ4fUuvxX2dcBMk876tf"
},
"dcJedLC7qsLtsmm1wbiFFc": {
"from": {
"blockId": "bwWRAaX5m6NZyZ9jjpXmWSb",
"stepId": "s8n3nJajsBaYqrFeRYVvcf6"
},
"to": { "blockId": "baUyUnNBxZzPe1z5PqE4NkD" },
"id": "dcJedLC7qsLtsmm1wbiFFc"
},
"sA5gvCVVBVYdGsdeSGF5ei": {
"from": {
"blockId": "baUyUnNBxZzPe1z5PqE4NkD",
"stepId": "sugJ6xN3jFys1CjWfsxGhiJ"
},
"to": { "blockId": "bwkKNpJmAFCCLbZSnPnnLnR" },
"id": "sA5gvCVVBVYdGsdeSGF5ei"
}
},
"allIds": [
"ahfJ4fUuvxX2dcBMk876tf",
"dcJedLC7qsLtsmm1wbiFFc",
"sA5gvCVVBVYdGsdeSGF5ei"
]
},
"theme": {
"general": {
"font": "Open Sans",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,70 +1,70 @@
{
"id": "typebot4",
"createdAt": "2022-01-12T08:20:11.572Z",
"updatedAt": "2022-01-12T08:20:11.572Z",
"id": "ckylsr69q0240z31afjhedyxo",
"createdAt": "2022-01-19T17:06:08.126Z",
"updatedAt": "2022-01-19T17:06:08.126Z",
"name": "My typebot",
"ownerId": "ckyb9vs110792li1az8bve32o",
"ownerId": "ckylsr4fi0220z31apbinpy9d",
"publishedTypebotId": null,
"folderId": null,
"blocks": {
"byId": {
"iE81k3ViAne3mfPEWeZJcq": {
"id": "iE81k3ViAne3mfPEWeZJcq",
"weeBMMXxNKwEonMfDX8Z5k": {
"id": "weeBMMXxNKwEonMfDX8Z5k",
"title": "Start",
"stepIds": ["fLNNAAtRkrw7GG5iqCCdYx"],
"stepIds": ["nEXiHesKXRQJhQbaWfbDVH"],
"graphCoordinates": { "x": 0, "y": 0 }
},
"bcs6TWbHd9inLRDyGZaX7SD": {
"id": "bcs6TWbHd9inLRDyGZaX7SD",
"bg2MBdkf6y7g6WsbqAP3eAT": {
"id": "bg2MBdkf6y7g6WsbqAP3eAT",
"title": "Block #2",
"graphCoordinates": { "x": 362, "y": 202 },
"stepIds": ["sdExhY2LZ6VZ69s2WFmBcZg"]
"graphCoordinates": { "x": 120, "y": 221 },
"stepIds": ["sqzMjp1Ba4jTL3A6iJehC6C"]
},
"bgjJd4KTrHSy2GD7AfCPECR": {
"id": "bgjJd4KTrHSy2GD7AfCPECR",
"bj5BE1yKPzFFhvRk6cMnmsQ": {
"id": "bj5BE1yKPzFFhvRk6cMnmsQ",
"title": "Block #3",
"graphCoordinates": { "x": 846, "y": 186 },
"stepIds": ["sgwvBXuNbZ16vvJK7rAA8Gw"]
"graphCoordinates": { "x": 529, "y": 130 },
"stepIds": ["s8zPdEj96z8EoJG2zBqgoE8"]
},
"bkLYB73fk4GePgqDLnUHy1t": {
"id": "bkLYB73fk4GePgqDLnUHy1t",
"bdET8zLFQbwpTaAmi4wmezE": {
"id": "bdET8zLFQbwpTaAmi4wmezE",
"title": "Block #4",
"graphCoordinates": { "x": 851, "y": 498 },
"stepIds": ["sbE2QKjYNXwrBexFgYrkMcn"]
"graphCoordinates": { "x": 538, "y": 386 },
"stepIds": ["sjZ28izS5e3VjNynFKT2F7E"]
}
},
"allIds": [
"iE81k3ViAne3mfPEWeZJcq",
"bcs6TWbHd9inLRDyGZaX7SD",
"bgjJd4KTrHSy2GD7AfCPECR",
"bkLYB73fk4GePgqDLnUHy1t"
"weeBMMXxNKwEonMfDX8Z5k",
"bg2MBdkf6y7g6WsbqAP3eAT",
"bj5BE1yKPzFFhvRk6cMnmsQ",
"bdET8zLFQbwpTaAmi4wmezE"
]
},
"steps": {
"byId": {
"fLNNAAtRkrw7GG5iqCCdYx": {
"id": "fLNNAAtRkrw7GG5iqCCdYx",
"nEXiHesKXRQJhQbaWfbDVH": {
"id": "nEXiHesKXRQJhQbaWfbDVH",
"type": "start",
"label": "Start",
"blockId": "iE81k3ViAne3mfPEWeZJcq",
"target": { "blockId": "bcs6TWbHd9inLRDyGZaX7SD" }
"blockId": "weeBMMXxNKwEonMfDX8Z5k",
"edgeId": "uh95dDpiiZdYxpPFsUqZEg"
},
"sdExhY2LZ6VZ69s2WFmBcZg": {
"id": "sdExhY2LZ6VZ69s2WFmBcZg",
"blockId": "bcs6TWbHd9inLRDyGZaX7SD",
"sqzMjp1Ba4jTL3A6iJehC6C": {
"id": "sqzMjp1Ba4jTL3A6iJehC6C",
"blockId": "bg2MBdkf6y7g6WsbqAP3eAT",
"type": "choice input",
"options": {
"itemIds": [
"rFcixXR7vVXDeUuWajc7gR",
"1Ksnvcvtpn358jX5rtEfKa",
"8PLMwgYdaEzG6FAvCo46L8"
"bWrsg18ucP9cdtFKhzgHbF",
"p7Z57shv7p79KiwAtdi8Y3",
"wjMRa2GBBnME9bEiNi6XgP"
]
},
"target": { "blockId": "bkLYB73fk4GePgqDLnUHy1t" }
"edgeId": "asT5shwJqDQ67qPuydR4gy"
},
"sgwvBXuNbZ16vvJK7rAA8Gw": {
"id": "sgwvBXuNbZ16vvJK7rAA8Gw",
"blockId": "bgjJd4KTrHSy2GD7AfCPECR",
"s8zPdEj96z8EoJG2zBqgoE8": {
"id": "s8zPdEj96z8EoJG2zBqgoE8",
"blockId": "bj5BE1yKPzFFhvRk6cMnmsQ",
"type": "text",
"content": {
"html": "<div>I love burgers!</div>",
@ -74,9 +74,9 @@
"plainText": "I love burgers!"
}
},
"sbE2QKjYNXwrBexFgYrkMcn": {
"id": "sbE2QKjYNXwrBexFgYrkMcn",
"blockId": "bkLYB73fk4GePgqDLnUHy1t",
"sjZ28izS5e3VjNynFKT2F7E": {
"id": "sjZ28izS5e3VjNynFKT2F7E",
"blockId": "bdET8zLFQbwpTaAmi4wmezE",
"type": "text",
"content": {
"html": "<div>Cool!</div>",
@ -86,35 +86,70 @@
}
},
"allIds": [
"fLNNAAtRkrw7GG5iqCCdYx",
"sdExhY2LZ6VZ69s2WFmBcZg",
"sgwvBXuNbZ16vvJK7rAA8Gw",
"sbE2QKjYNXwrBexFgYrkMcn"
"nEXiHesKXRQJhQbaWfbDVH",
"sqzMjp1Ba4jTL3A6iJehC6C",
"s8zPdEj96z8EoJG2zBqgoE8",
"sjZ28izS5e3VjNynFKT2F7E"
]
},
"choiceItems": {
"byId": {
"rFcixXR7vVXDeUuWajc7gR": {
"id": "rFcixXR7vVXDeUuWajc7gR",
"stepId": "sdExhY2LZ6VZ69s2WFmBcZg",
"bWrsg18ucP9cdtFKhzgHbF": {
"id": "bWrsg18ucP9cdtFKhzgHbF",
"stepId": "sqzMjp1Ba4jTL3A6iJehC6C",
"content": "Burgers",
"target": { "blockId": "bgjJd4KTrHSy2GD7AfCPECR" }
"edgeId": "jfR6AUWt9b4dhjnUHXB179"
},
"1Ksnvcvtpn358jX5rtEfKa": {
"id": "1Ksnvcvtpn358jX5rtEfKa",
"stepId": "sdExhY2LZ6VZ69s2WFmBcZg",
"p7Z57shv7p79KiwAtdi8Y3": {
"id": "p7Z57shv7p79KiwAtdi8Y3",
"stepId": "sqzMjp1Ba4jTL3A6iJehC6C",
"content": "Hot dogs"
},
"8PLMwgYdaEzG6FAvCo46L8": {
"id": "8PLMwgYdaEzG6FAvCo46L8",
"stepId": "sdExhY2LZ6VZ69s2WFmBcZg",
"wjMRa2GBBnME9bEiNi6XgP": {
"id": "wjMRa2GBBnME9bEiNi6XgP",
"stepId": "sqzMjp1Ba4jTL3A6iJehC6C",
"content": "Carpaccio"
}
},
"allIds": [
"rFcixXR7vVXDeUuWajc7gR",
"1Ksnvcvtpn358jX5rtEfKa",
"8PLMwgYdaEzG6FAvCo46L8"
"bWrsg18ucP9cdtFKhzgHbF",
"p7Z57shv7p79KiwAtdi8Y3",
"wjMRa2GBBnME9bEiNi6XgP"
]
},
"variables": { "byId": {}, "allIds": [] },
"edges": {
"byId": {
"uh95dDpiiZdYxpPFsUqZEg": {
"from": {
"blockId": "weeBMMXxNKwEonMfDX8Z5k",
"stepId": "nEXiHesKXRQJhQbaWfbDVH"
},
"to": { "blockId": "bg2MBdkf6y7g6WsbqAP3eAT" },
"id": "uh95dDpiiZdYxpPFsUqZEg"
},
"jfR6AUWt9b4dhjnUHXB179": {
"from": {
"blockId": "bg2MBdkf6y7g6WsbqAP3eAT",
"stepId": "sqzMjp1Ba4jTL3A6iJehC6C",
"nodeId": "bWrsg18ucP9cdtFKhzgHbF"
},
"to": { "blockId": "bj5BE1yKPzFFhvRk6cMnmsQ" },
"id": "jfR6AUWt9b4dhjnUHXB179"
},
"asT5shwJqDQ67qPuydR4gy": {
"from": {
"blockId": "bg2MBdkf6y7g6WsbqAP3eAT",
"stepId": "sqzMjp1Ba4jTL3A6iJehC6C"
},
"to": { "blockId": "bdET8zLFQbwpTaAmi4wmezE" },
"id": "asT5shwJqDQ67qPuydR4gy"
}
},
"allIds": [
"uh95dDpiiZdYxpPFsUqZEg",
"jfR6AUWt9b4dhjnUHXB179",
"asT5shwJqDQ67qPuydR4gy"
]
},
"theme": {
@ -126,6 +161,5 @@
"settings": {
"typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
},
"variables": { "byId": {}, "allIds": [] },
"publicId": null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -148,7 +148,10 @@ const parseTypebotToPublicTypebot = (
publicId: typebot.publicId,
choiceItems: typebot.choiceItems,
variables: typebot.variables,
edges: typebot.edges,
})
export const loadRawTypebotInDatabase = (typebot: Typebot) =>
prisma.typebot.create({ data: { ...typebot, ownerId: userIds[1] } as any })
prisma.typebot.create({
data: { ...typebot, id: 'typebot4', ownerId: userIds[1] } as any,
})

View File

@ -64,7 +64,7 @@ export const parseTestTypebot = ({
type: 'start',
blockId: 'block0',
label: 'Start',
target: { blockId: 'block1' },
edgeId: 'edge1',
},
...steps.byId,
},
@ -75,6 +75,16 @@ export const parseTestTypebot = ({
publishedTypebotId: null,
updatedAt: new Date(),
variables: { byId: {}, allIds: [] },
edges: {
byId: {
edge1: {
id: 'edge1',
from: { blockId: 'block0', stepId: 'step0' },
to: { blockId: 'block1' },
},
},
allIds: ['edge1'],
},
}
}

View File

@ -82,6 +82,11 @@ Cypress.Commands.add(
}
)
Cypress.Commands.add('createVariable', (name: string) => {
cy.findByTestId('variables-input').type(name)
cy.findByRole('menuitem', { name: `Create "${name}"` }).click()
})
const getDocumentScroll = () => {
if (document.scrollingElement) {
const { scrollTop, scrollLeft } = document.scrollingElement

View File

@ -21,6 +21,7 @@ declare global {
signOut(): Chainable<any>
signIn(email: string): Chainable<any>
loadTypebotFixtureInDatabase(path: string): Chainable<any>
createVariable(name: string): Chainable<any>
mouseMoveBy(
x: number,
y: number,

View File

@ -1,261 +0,0 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { preventUserFromRefreshing } from 'cypress/plugins/utils'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Text input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.TEXT })
cy.signOut()
})
afterEach(() => {
cy.window().then((win) => {
win.removeEventListener('beforeunload', preventUserFromRefreshing)
})
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.should('have.attr', 'type')
.should('equal', 'text')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your name...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
getIframeBody().findByPlaceholderText('Your name...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Long text?' }).check({ force: true })
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByTestId('textarea').should('exist')
})
})
describe('Number input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.NUMBER })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.should('have.attr', 'type')
.should('equal', 'number')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your name...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('spinbutton', { name: 'Min:' }).type('0')
cy.findByRole('spinbutton', { name: 'Max:' }).type('100')
cy.findByRole('spinbutton', { name: 'Step:' }).type('10')
cy.findByRole('button', { name: 'Restart' }).click()
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
getIframeBody()
.findByPlaceholderText('Your name...')
.should('exist')
.type('-1{enter}')
.clear()
.type('150{enter}')
getIframeBody().findByRole('button', { name: 'Go' })
cy.findByTestId('step-step1').click({ force: true })
})
})
describe('Email input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.EMAIL })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your email...')
.should('have.attr', 'type')
.should('equal', 'email')
getIframeBody().findByRole('button', { name: 'Send' })
getIframeBody().findByPlaceholderText('Type your email...').should('exist')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your email...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByTestId('step-step1').should('contain.text', 'Your email...')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Your email...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
})
})
describe('URL input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.URL })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your URL...')
.should('have.attr', 'type')
.should('eq', 'url')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your URL...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByTestId('step-step1').should('contain.text', 'Your URL...')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Your URL...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
})
})
describe('Date input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.DATE })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByTestId('from-date')
.should('have.attr', 'type')
.should('eq', 'date')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Is range?' }).check({ force: true })
cy.findByRole('textbox', { name: 'From label:' }).clear().type('Previous:')
cy.findByRole('textbox', { name: 'To label:' }).clear().type('After:')
cy.findByRole('checkbox', { name: 'With time?' }).check({ force: true })
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody()
.findByTestId('from-date')
.should('have.attr', 'type')
.should('eq', 'datetime-local')
getIframeBody()
.findByTestId('to-date')
.should('have.attr', 'type')
.should('eq', 'datetime-local')
getIframeBody().findByRole('button', { name: 'Go' })
})
})
describe('Phone number input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.PHONE })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Your phone number...')
.should('have.attr', 'type')
.should('eq', 'tel')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('+33 XX XX XX XX')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody()
.findByPlaceholderText('+33 XX XX XX XX')
.type('+33 6 73 18 45 36')
getIframeBody()
.findByRole('img')
.should('have.attr', 'alt')
.should('eq', 'France')
getIframeBody().findByRole('button', { name: 'Go' }).click()
getIframeBody().findByText('+33673184536').should('exist')
})
})
describe('Button input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.CHOICE })
cy.signOut()
})
it('Can edit choice items', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByDisplayValue('Click to edit').type('Item 1{enter}')
cy.findByText('Item 1').trigger('mouseover')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 2{enter}')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 3{enter}')
cy.findByText('Item 2').rightclick()
cy.findByRole('menuitem', { name: 'Delete' }).click()
cy.findByText('Item 2').should('not.exist')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('button', { name: 'Item 3' }).click()
getIframeBody().findByRole('button', { name: 'Item 3' }).should('not.exist')
getIframeBody().findByText('Item 3')
cy.findByRole('button', { name: 'Close' }).click()
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Multiple choice?' }).check({
force: true,
})
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.wait(200)
cy.findByTestId('step-step1').click({ force: true })
cy.findByText('Item 1').trigger('mouseover')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 2{enter}')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('checkbox', { name: 'Item 3' }).click()
getIframeBody().findByRole('checkbox', { name: 'Item 1' }).click()
getIframeBody().findByRole('button', { name: 'Go' }).click()
getIframeBody().findByText('Item 3, Item 1').should('exist')
})
it('Single choice targets should work', () => {
cy.loadTypebotFixtureInDatabase('typebots/singleChoiceTarget.json')
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot4/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('button', { name: 'Burgers' }).click()
getIframeBody().findByText('I love burgers!').should('exist')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByRole('button', { name: 'Carpaccio' }).click()
getIframeBody().findByText('Cool!').should('exist')
})
})

View File

@ -0,0 +1,58 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Button input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.CHOICE })
cy.signOut()
})
it('Can edit choice items', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByDisplayValue('Click to edit').type('Item 1{enter}')
cy.findByText('Item 1').trigger('mouseover')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 2{enter}')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 3{enter}')
cy.findByText('Item 2').rightclick()
cy.findByRole('menuitem', { name: 'Delete' }).click()
cy.findByText('Item 2').should('not.exist')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('button', { name: 'Item 3' }).click()
getIframeBody().findByRole('button', { name: 'Item 3' }).should('not.exist')
getIframeBody().findByText('Item 3')
cy.findByRole('button', { name: 'Close' }).click()
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Multiple choice?' }).check({
force: true,
})
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.wait(200)
cy.findByTestId('step-step1').click({ force: true })
cy.findByText('Item 1').trigger('mouseover')
cy.findByRole('button', { name: 'Add item' }).click()
cy.findByDisplayValue('Click to edit').type('Item 2{enter}')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('checkbox', { name: 'Item 3' }).click()
getIframeBody().findByRole('checkbox', { name: 'Item 1' }).click()
getIframeBody().findByRole('button', { name: 'Go' }).click()
getIframeBody().findByText('Item 3, Item 1').should('exist')
})
it('Single choice targets should work', () => {
cy.loadTypebotFixtureInDatabase('typebots/singleChoiceTarget.json')
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot4/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByRole('button', { name: 'Burgers' }).click()
getIframeBody().findByText('I love burgers!').should('exist')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByRole('button', { name: 'Carpaccio' }).click()
getIframeBody().findByText('Cool!').should('exist')
})
})

View File

@ -0,0 +1,38 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Date input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.DATE })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByTestId('from-date')
.should('have.attr', 'type')
.should('eq', 'date')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Is range?' }).check({ force: true })
cy.findByRole('textbox', { name: 'From label:' }).clear().type('Previous:')
cy.findByRole('textbox', { name: 'To label:' }).clear().type('After:')
cy.findByRole('checkbox', { name: 'With time?' }).check({ force: true })
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody()
.findByTestId('from-date')
.should('have.attr', 'type')
.should('eq', 'datetime-local')
getIframeBody()
.findByTestId('to-date')
.should('have.attr', 'type')
.should('eq', 'datetime-local')
getIframeBody().findByRole('button', { name: 'Go' })
})
})

View File

@ -0,0 +1,33 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Email input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.EMAIL })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your email...')
.should('have.attr', 'type')
.should('equal', 'email')
getIframeBody().findByRole('button', { name: 'Send' })
getIframeBody().findByPlaceholderText('Type your email...').should('exist')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your email...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByTestId('step-step1').should('contain.text', 'Your email...')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Your email...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
})
})

View File

@ -0,0 +1,40 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Number input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.NUMBER })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.should('have.attr', 'type')
.should('equal', 'number')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your name...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('spinbutton', { name: 'Min:' }).type('0')
cy.findByRole('spinbutton', { name: 'Max:' }).type('100')
cy.findByRole('spinbutton', { name: 'Step:' }).type('10')
cy.findByRole('button', { name: 'Restart' }).click()
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
getIframeBody()
.findByPlaceholderText('Your name...')
.should('exist')
.type('-1{enter}')
.clear()
.type('150{enter}')
getIframeBody().findByRole('button', { name: 'Go' })
cy.findByTestId('step-step1').click({ force: true })
})
})

View File

@ -0,0 +1,37 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Phone number input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.PHONE })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Your phone number...')
.should('have.attr', 'type')
.should('eq', 'tel')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('+33 XX XX XX XX')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody()
.findByPlaceholderText('+33 XX XX XX XX')
.type('+33 6 73 18 45 36')
getIframeBody()
.findByRole('img')
.should('have.attr', 'alt')
.should('eq', 'France')
getIframeBody().findByRole('button', { name: 'Go' }).click()
getIframeBody().findByText('+33673184536').should('exist')
})
})

View File

@ -0,0 +1,42 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { preventUserFromRefreshing } from 'cypress/plugins/utils'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('Text input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.TEXT })
cy.signOut()
})
afterEach(() => {
cy.window().then((win) => {
win.removeEventListener('beforeunload', preventUserFromRefreshing)
})
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.should('have.attr', 'type')
.should('equal', 'text')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your name...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByRole('button', { name: 'Restart' }).click()
cy.findByTestId('step-step1').should('contain.text', 'Your name...')
getIframeBody().findByPlaceholderText('Your name...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('checkbox', { name: 'Long text?' }).check({ force: true })
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByTestId('textarea').should('exist')
})
})

View File

@ -0,0 +1,31 @@
import { createTypebotWithStep } from 'cypress/plugins/data'
import { getIframeBody } from 'cypress/support'
import { InputStepType } from 'models'
describe('URL input', () => {
beforeEach(() => {
cy.task('seed')
createTypebotWithStep({ type: InputStepType.URL })
cy.signOut()
})
it('options should work', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot3/edit')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your URL...')
.should('have.attr', 'type')
.should('eq', 'url')
getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled')
cy.findByTestId('step-step1').click({ force: true })
cy.findByRole('textbox', { name: 'Placeholder:' })
.clear()
.type('Your URL...')
cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go')
cy.findByTestId('step-step1').should('contain.text', 'Your URL...')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Your URL...').should('exist')
getIframeBody().findByRole('button', { name: 'Go' })
})
})

View File

@ -121,7 +121,7 @@ describe('Google sheets', () => {
})
const fillInSpreadsheetInfo = () => {
cy.findByTestId('step-step1').click()
cy.findByText('Configure...').click()
cy.findByRole('button', { name: 'Select an account' }).click()
cy.findByRole('menuitem', { name: 'test2@gmail.com' }).click()

View File

@ -1,44 +1,6 @@
import { preventUserFromRefreshing } from 'cypress/plugins/utils'
import { getIframeBody } from 'cypress/support'
describe('Set variables', () => {
beforeEach(() => {
cy.task('seed')
cy.signOut()
})
afterEach(() => {
cy.window().then((win) => {
win.removeEventListener('beforeunload', preventUserFromRefreshing)
})
})
it('options should work', () => {
cy.loadTypebotFixtureInDatabase('typebots/logic/setVariable.json')
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot4/edit')
cy.findByTestId('step-number-step').click()
createNewVar('Num')
cy.findByTestId('step-set-var-1').click()
createNewVar('Total')
cy.findByRole('textbox', { name: 'Value / Expression:' }).type(
'1000 * {{Num}}',
{ parseSpecialCharSequences: false }
)
cy.findByTestId('step-set-var-2').click()
createNewVar('Custom var')
cy.findByRole('textbox', { name: 'Value / Expression:' }).type(
'Custom value'
)
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.type('365{enter}')
getIframeBody().findByText('Total: 365000').should('exist')
getIframeBody().findByText('Custom var: Custom value')
})
})
describe('Condition step', () => {
beforeEach(() => {
cy.task('seed')
@ -56,7 +18,7 @@ describe('Condition step', () => {
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot4/edit')
cy.findByTestId('step-condition1').click()
cy.findAllByText('Equal to').first().click()
cy.findByTestId('variables-input').click()
cy.findByRole('menuitem', { name: 'Age' }).click()
@ -71,7 +33,7 @@ describe('Condition step', () => {
cy.findByRole('menuitem', { name: 'Less than' }).click()
cy.findAllByPlaceholderText('Type a value...').last().type('100')
cy.findByTestId('step-condition2').click()
cy.findAllByText('Equal to').last().click()
cy.findByTestId('variables-input').click()
cy.findByRole('menuitem', { name: 'Age' }).click()
@ -80,20 +42,21 @@ describe('Condition step', () => {
cy.findByPlaceholderText('Type a value...').type('20')
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody().findByPlaceholderText('Type your age...').type('15{enter}')
getIframeBody()
.findByPlaceholderText('Type your answer...')
.type('15{enter}')
getIframeBody().findByText('You are younger than 20').should('exist')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Type your age...').type('45{enter}')
getIframeBody().findByText('Wow you are older than 20').should('exist')
getIframeBody()
.findByPlaceholderText('Type your answer...')
.type('45{enter}')
getIframeBody().findByText('You are older than 20').should('exist')
cy.findByRole('button', { name: 'Restart' }).click()
getIframeBody().findByPlaceholderText('Type your age...').type('90{enter}')
getIframeBody().findByText('Wow you are older than 80').should('exist')
getIframeBody()
.findByPlaceholderText('Type your answer...')
.type('90{enter}')
getIframeBody().findByText('You are older than 80').should('exist')
})
})
const createNewVar = (name: string) => {
cy.findByTestId('variables-input').type(name)
cy.findByRole('menuitem', { name: `Create "${name}"` }).click()
}

View File

@ -0,0 +1,40 @@
import { preventUserFromRefreshing } from 'cypress/plugins/utils'
import { getIframeBody } from 'cypress/support'
describe('Set variables', () => {
beforeEach(() => {
cy.task('seed')
cy.signOut()
})
afterEach(() => {
cy.window().then((win) => {
win.removeEventListener('beforeunload', preventUserFromRefreshing)
})
})
it('options should work', () => {
cy.loadTypebotFixtureInDatabase('typebots/logic/setVariable.json')
cy.signIn('test2@gmail.com')
cy.visit('/typebots/typebot4/edit')
cy.findByText('Type your answer...').click()
cy.createVariable('Num')
cy.findAllByText('Click to edit...').first().click()
cy.createVariable('Total')
cy.findByRole('textbox', { name: 'Value / Expression:' }).type(
'1000 * {{Num}}',
{ parseSpecialCharSequences: false }
)
cy.findAllByText('Click to edit...').last().click()
cy.createVariable('Custom var')
cy.findByRole('textbox', { name: 'Value / Expression:' }).type(
'Custom value'
)
cy.findByRole('button', { name: 'Preview' }).click()
getIframeBody()
.findByPlaceholderText('Type your answer...')
.type('365{enter}')
getIframeBody().findByText('Total: 365000').should('exist')
getIframeBody().findByText('Custom var: Custom value')
})
})

View File

@ -1,9 +1,6 @@
import { Coordinates } from '@dnd-kit/core/dist/types'
import { Block, Step, Table, Target, Typebot } from 'models'
import {
AnchorsPositionProps,
EdgeType,
} from 'components/board/graph/Edges/Edge'
import { Block, Edge, Table, Target, Typebot } from 'models'
import { AnchorsPositionProps } from 'components/board/graph/Edges/Edge'
import {
stubLength,
blockWidth,
@ -13,7 +10,6 @@ import {
} from 'contexts/GraphContext'
import { roundCorners } from 'svg-round-corners'
import { headerHeight } from 'components/shared/TypebotHeader'
import { isConditionStep } from 'utils'
export const computeDropOffPath = (
sourcePosition: Coordinates,
@ -285,10 +281,10 @@ export const computeEdgePathToMouse = ({
export const getEndpointTopOffset = (
graphPosition: Coordinates,
endpoints: Table<Endpoint>,
id?: string
endpointId?: string
): number => {
if (!id) return 0
const endpointRef = endpoints.byId[id]?.ref
if (!endpointId) return 0
const endpointRef = endpoints.byId[endpointId]?.ref
if (!endpointRef) return 0
return (
8 +
@ -298,17 +294,5 @@ export const getEndpointTopOffset = (
)
}
export const getTarget = (step: Step, edgeType: EdgeType) => {
if (!step) return
switch (edgeType) {
case EdgeType.STEP:
case EdgeType.CHOICE_ITEM:
return step.target
case EdgeType.CONDITION_TRUE:
if (!isConditionStep(step)) return
return step.trueTarget
case EdgeType.CONDITION_FALSE:
if (!isConditionStep(step)) return
return step.falseTarget
}
}
export const getSourceEndpointId = (edge?: Edge) =>
edge?.from.nodeId ?? edge?.from.stepId + `${edge?.from.conditionType ?? ''}`

View File

@ -18,6 +18,7 @@ export const parseTypebotToPublicTypebot = (
publicId: typebot.publicId,
choiceItems: typebot.choiceItems,
variables: typebot.variables,
edges: typebot.edges,
})
export const createPublishedTypebot = async (

View File

@ -234,6 +234,7 @@ export const parseNewTypebot = ({
steps: { byId: { [startStepId]: startStep }, allIds: [startStepId] },
choiceItems: { byId: {}, allIds: [] },
variables: { byId: {}, allIds: [] },
edges: { byId: {}, allIds: [] },
theme,
settings,
}