chore(editor): ♻️ Revert tables to arrays
Yet another refacto. I improved many many mechanisms on this one including dnd. It is now end 2 end tested 🎉
This commit is contained in:
@@ -4,7 +4,7 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
|
||||
import { ChatStep } from './ChatStep'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { HostAvatarsContext } from '../../contexts/HostAvatarsContext'
|
||||
import { Step } from 'models'
|
||||
import { PublicStep } from 'models'
|
||||
import { useTypebot } from '../../contexts/TypebotContext'
|
||||
import {
|
||||
isBubbleStep,
|
||||
@@ -14,26 +14,28 @@ import {
|
||||
isLogicStep,
|
||||
} from 'utils'
|
||||
import { executeLogic } from 'services/logic'
|
||||
import { getSingleChoiceTargetId } from 'services/inputs'
|
||||
import { executeIntegration } from 'services/integration'
|
||||
|
||||
type ChatBlockProps = {
|
||||
stepIds: string[]
|
||||
startStepId?: string
|
||||
steps: PublicStep[]
|
||||
startStepIndex: number
|
||||
blockIndex: number
|
||||
onBlockEnd: (edgeId?: string) => void
|
||||
}
|
||||
|
||||
export const ChatBlock = ({
|
||||
stepIds,
|
||||
startStepId,
|
||||
steps,
|
||||
startStepIndex,
|
||||
blockIndex,
|
||||
onBlockEnd,
|
||||
}: ChatBlockProps) => {
|
||||
const { typebot, updateVariableValue } = useTypebot()
|
||||
const [displayedSteps, setDisplayedSteps] = useState<Step[]>([])
|
||||
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
|
||||
|
||||
const currentStepIndex = displayedSteps.length - 1
|
||||
|
||||
useEffect(() => {
|
||||
const nextStep =
|
||||
typebot.steps.byId[startStepId ?? stepIds[displayedSteps.length]]
|
||||
const nextStep = steps[startStepIndex]
|
||||
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@@ -60,6 +62,7 @@ export const ChatBlock = ({
|
||||
typebot.typebotId,
|
||||
currentStep,
|
||||
typebot.variables,
|
||||
{ blockIndex, stepIndex: currentStepIndex },
|
||||
updateVariableValue
|
||||
)
|
||||
nextEdgeId ? onBlockEnd(nextEdgeId) : displayNextStep()
|
||||
@@ -85,18 +88,17 @@ export const ChatBlock = ({
|
||||
}
|
||||
const isSingleChoiceStep =
|
||||
isChoiceInput(currentStep) && !currentStep.options.isMultipleChoice
|
||||
if (isSingleChoiceStep)
|
||||
return onBlockEnd(
|
||||
getSingleChoiceTargetId(
|
||||
currentStep,
|
||||
typebot.choiceItems,
|
||||
answerContent
|
||||
)
|
||||
if (isSingleChoiceStep) {
|
||||
onBlockEnd(
|
||||
currentStep.items.find((i) => i.content === answerContent)
|
||||
?.outgoingEdgeId
|
||||
)
|
||||
if (currentStep?.edgeId || displayedSteps.length === stepIds.length)
|
||||
return onBlockEnd(currentStep.edgeId)
|
||||
}
|
||||
|
||||
if (currentStep?.outgoingEdgeId || displayedSteps.length === steps.length)
|
||||
return onBlockEnd(currentStep.outgoingEdgeId)
|
||||
}
|
||||
const nextStep = typebot.steps.byId[stepIds[displayedSteps.length]]
|
||||
const nextStep = steps[displayedSteps.length]
|
||||
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useAnswers } from '../../../contexts/AnswersContext'
|
||||
import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
|
||||
import { InputStep, InputStepType, Step } from 'models'
|
||||
import { InputStep, InputStepType, PublicStep, Step } from 'models'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { TextForm } from './inputs/TextForm'
|
||||
import { isBubbleStep, isInputStep } from 'utils'
|
||||
@@ -13,7 +13,7 @@ export const ChatStep = ({
|
||||
step,
|
||||
onTransitionEnd,
|
||||
}: {
|
||||
step: Step
|
||||
step: PublicStep
|
||||
onTransitionEnd: (answerContent?: string) => void
|
||||
}) => {
|
||||
const { addAnswer } = useAnswers()
|
||||
@@ -63,6 +63,6 @@ const InputChatStep = ({
|
||||
case InputStepType.DATE:
|
||||
return <DateForm options={step.options} onSubmit={handleSubmit} />
|
||||
case InputStepType.CHOICE:
|
||||
return <ChoiceForm options={step.options} onSubmit={handleSubmit} />
|
||||
return <ChoiceForm step={step} onSubmit={handleSubmit} />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHostAvatars } from 'contexts/HostAvatarsContext'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import {
|
||||
Table,
|
||||
Variable,
|
||||
VideoBubbleContent,
|
||||
VideoBubbleContentType,
|
||||
@@ -83,7 +82,7 @@ const VideoContent = ({
|
||||
}: {
|
||||
content?: VideoBubbleContent
|
||||
isTyping: boolean
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
}) => {
|
||||
const url = useMemo(
|
||||
() => parseVariables({ text: content?.url, variables: variables }),
|
||||
|
||||
@@ -1,65 +1,61 @@
|
||||
import { ChoiceInputOptions } from 'models'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { filterTable } from 'utils'
|
||||
import { useTypebot } from '../../../../contexts/TypebotContext'
|
||||
import { ChoiceInputStep } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { SendButton } from './SendButton'
|
||||
|
||||
type ChoiceFormProps = {
|
||||
options?: ChoiceInputOptions
|
||||
step: ChoiceInputStep
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const ChoiceForm = ({ options, onSubmit }: ChoiceFormProps) => {
|
||||
const { typebot } = useTypebot()
|
||||
const items = useMemo(
|
||||
() => filterTable(options?.itemIds ?? [], typebot.choiceItems),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
)
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
const [selectedIndices, setSelectedIndices] = useState<number[]>([])
|
||||
|
||||
const handleClick = (itemId: string) => (e: React.MouseEvent) => {
|
||||
const handleClick = (itemIndex: number) => (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (options?.isMultipleChoice) toggleSelectedItemId(itemId)
|
||||
else onSubmit(items.byId[itemId].content ?? '')
|
||||
if (step.options?.isMultipleChoice) toggleSelectedItemIndex(itemIndex)
|
||||
else onSubmit(step.items[itemIndex].content ?? '')
|
||||
}
|
||||
|
||||
const toggleSelectedItemId = (itemId: string) => {
|
||||
const existingIndex = selectedIds.indexOf(itemId)
|
||||
const toggleSelectedItemIndex = (itemIndex: number) => {
|
||||
const existingIndex = selectedIndices.indexOf(itemIndex)
|
||||
if (existingIndex !== -1) {
|
||||
selectedIds.splice(existingIndex, 1)
|
||||
setSelectedIds([...selectedIds])
|
||||
selectedIndices.splice(existingIndex, 1)
|
||||
setSelectedIndices([...selectedIndices])
|
||||
} else {
|
||||
setSelectedIds([...selectedIds, itemId])
|
||||
setSelectedIndices([...selectedIndices, itemIndex])
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = () =>
|
||||
onSubmit(selectedIds.map((itemId) => items.byId[itemId].content).join(', '))
|
||||
onSubmit(
|
||||
selectedIndices
|
||||
.map((itemIndex) => step.items[itemIndex].content)
|
||||
.join(', ')
|
||||
)
|
||||
|
||||
return (
|
||||
<form className="flex flex-col" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-wrap">
|
||||
{options?.itemIds.map((itemId) => (
|
||||
{step.items.map((item, idx) => (
|
||||
<button
|
||||
key={itemId}
|
||||
role={options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(itemId)}
|
||||
key={item.id}
|
||||
role={step.options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(idx)}
|
||||
className={
|
||||
'py-2 px-4 font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none mr-2 mb-2 typebot-button ' +
|
||||
(selectedIds.includes(itemId) || !options?.isMultipleChoice
|
||||
(selectedIndices.includes(idx) || !step.options?.isMultipleChoice
|
||||
? 'active'
|
||||
: '')
|
||||
}
|
||||
data-testid="button"
|
||||
>
|
||||
{items.byId[itemId].content}
|
||||
{item.content}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
{selectedIds.length > 0 && (
|
||||
<SendButton label={options?.buttonLabel ?? 'Send'} />
|
||||
{selectedIndices.length > 0 && (
|
||||
<SendButton label={step.options?.buttonLabel ?? 'Send'} />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -5,11 +5,12 @@ import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
import { useAnswers } from '../contexts/AnswersContext'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Answer, Block, PublicTypebot } from 'models'
|
||||
import { Answer, Edge, PublicBlock, PublicTypebot } from 'models'
|
||||
import { byId } from 'utils'
|
||||
|
||||
type Props = {
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisible: (edgeId: string) => void
|
||||
onNewBlockVisible: (edge: Edge) => void
|
||||
onNewAnswer: (answer: Answer) => void
|
||||
onCompleted: () => void
|
||||
}
|
||||
@@ -21,30 +22,29 @@ export const ConversationContainer = ({
|
||||
}: Props) => {
|
||||
const { document: frameDocument } = useFrame()
|
||||
const [displayedBlocks, setDisplayedBlocks] = useState<
|
||||
{ block: Block; startStepId?: string }[]
|
||||
{ block: PublicBlock; startStepIndex: number }[]
|
||||
>([])
|
||||
const [localAnswer, setLocalAnswer] = useState<Answer | undefined>()
|
||||
const { answers } = useAnswers()
|
||||
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const displayNextBlock = (edgeId?: string) => {
|
||||
const edge = typebot.edges.byId[edgeId ?? '']
|
||||
if (!edge) return onCompleted()
|
||||
const nextBlock = {
|
||||
block: typebot.blocks.byId[edge.to.blockId],
|
||||
startStepId: edge.to.stepId,
|
||||
}
|
||||
const nextEdge = typebot.edges.find(byId(edgeId))
|
||||
if (!nextEdge) return onCompleted()
|
||||
const nextBlock = typebot.blocks.find(byId(nextEdge.to.blockId))
|
||||
if (!nextBlock) return onCompleted()
|
||||
onNewBlockVisible(edge.id)
|
||||
setDisplayedBlocks([...displayedBlocks, nextBlock])
|
||||
const startStepIndex = nextEdge.to.stepId
|
||||
? nextBlock.steps.findIndex(byId(nextEdge.to.stepId))
|
||||
: 0
|
||||
onNewBlockVisible(nextEdge)
|
||||
setDisplayedBlocks([
|
||||
...displayedBlocks,
|
||||
{ block: nextBlock, startStepIndex },
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const blocks = typebot.blocks
|
||||
const firstEdgeId =
|
||||
typebot.steps.byId[blocks.byId[blocks.allIds[0]].stepIds[0]].edgeId
|
||||
if (!firstEdgeId) return
|
||||
displayNextBlock(firstEdgeId)
|
||||
displayNextBlock(typebot.blocks[0].steps[0].outgoingEdgeId)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@@ -68,8 +68,9 @@ export const ConversationContainer = ({
|
||||
{displayedBlocks.map((displayedBlock, idx) => (
|
||||
<ChatBlock
|
||||
key={displayedBlock.block.id + idx}
|
||||
stepIds={displayedBlock.block.stepIds}
|
||||
startStepId={displayedBlock.startStepId}
|
||||
steps={displayedBlock.block.steps}
|
||||
startStepIndex={displayedBlock.startStepIndex}
|
||||
blockIndex={idx}
|
||||
onBlockEnd={displayNextBlock}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -10,11 +10,11 @@ import phoneNumberInputStyle from 'react-phone-number-input/style.css'
|
||||
import phoneSyle from '../assets/phone.css'
|
||||
import { ConversationContainer } from './ConversationContainer'
|
||||
import { AnswersContext } from '../contexts/AnswersContext'
|
||||
import { Answer, BackgroundType, PublicTypebot } from 'models'
|
||||
import { Answer, BackgroundType, Edge, PublicTypebot } from 'models'
|
||||
|
||||
export type TypebotViewerProps = {
|
||||
typebot: PublicTypebot
|
||||
onNewBlockVisible?: (edgeId: string) => void
|
||||
onNewBlockVisible?: (edge: Edge) => void
|
||||
onNewAnswer?: (answer: Answer) => void
|
||||
onCompleted?: () => void
|
||||
}
|
||||
@@ -31,8 +31,8 @@ export const TypebotViewer = ({
|
||||
: 'transparent',
|
||||
[typebot?.theme?.general?.background]
|
||||
)
|
||||
const handleNewBlockVisible = (blockId: string) => {
|
||||
if (onNewBlockVisible) onNewBlockVisible(blockId)
|
||||
const handleNewBlockVisible = (edge: Edge) => {
|
||||
if (onNewBlockVisible) onNewBlockVisible(edge)
|
||||
}
|
||||
const handleNewAnswer = (answer: Answer) => {
|
||||
if (onNewAnswer) onNewAnswer(answer)
|
||||
|
||||
@@ -20,13 +20,9 @@ export const TypebotContext = ({
|
||||
const updateVariableValue = (variableId: string, value: string) => {
|
||||
setLocalTypebot((typebot) => ({
|
||||
...typebot,
|
||||
variables: {
|
||||
...typebot.variables,
|
||||
byId: {
|
||||
...typebot.variables.byId,
|
||||
[variableId]: { ...typebot.variables.byId[variableId], value },
|
||||
},
|
||||
},
|
||||
variables: typebot.variables.map((v) =>
|
||||
v.id === variableId ? { ...v, value } : v
|
||||
),
|
||||
}))
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ChoiceInputStep, ChoiceItem, Table } from 'models'
|
||||
|
||||
export const getSingleChoiceTargetId = (
|
||||
currentStep: ChoiceInputStep,
|
||||
choiceItems: Table<ChoiceItem>,
|
||||
answerContent?: string
|
||||
): string | undefined => {
|
||||
const itemId = currentStep.options.itemIds.find(
|
||||
(itemId) => choiceItems.byId[itemId].content === answerContent
|
||||
)
|
||||
if (!itemId) throw new Error('itemId should exist')
|
||||
return choiceItems.byId[itemId].edgeId ?? currentStep.edgeId
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
Variable,
|
||||
Table,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
Cell,
|
||||
GoogleSheetsGetOptions,
|
||||
@@ -19,10 +18,12 @@ import { parseVariables, parseVariablesInObject } from './variable'
|
||||
|
||||
const safeEval = eval
|
||||
|
||||
type Indices = { blockIndex: number; stepIndex: number }
|
||||
export const executeIntegration = (
|
||||
typebotId: string,
|
||||
step: IntegrationStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
indices: Indices,
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
switch (step.type) {
|
||||
@@ -31,13 +32,19 @@ export const executeIntegration = (
|
||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(step, variables)
|
||||
case IntegrationStepType.WEBHOOK:
|
||||
return executeWebhook(typebotId, step, variables, updateVariableValue)
|
||||
return executeWebhook(
|
||||
typebotId,
|
||||
step,
|
||||
variables,
|
||||
indices,
|
||||
updateVariableValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const executeGoogleAnalyticsIntegration = async (
|
||||
step: GoogleAnalyticsStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!step.options?.trackingId) return
|
||||
const { default: initGoogleAnalytics } = await import('../../lib/gtag')
|
||||
@@ -47,10 +54,10 @@ export const executeGoogleAnalyticsIntegration = async (
|
||||
|
||||
const executeGoogleSheetIntegration = async (
|
||||
step: GoogleSheetsStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!('action' in step.options)) return step.edgeId
|
||||
if (!('action' in step.options)) return step.outgoingEdgeId
|
||||
switch (step.options.action) {
|
||||
case GoogleSheetsAction.INSERT_ROW:
|
||||
await insertRowInGoogleSheets(step.options, variables)
|
||||
@@ -62,12 +69,12 @@ const executeGoogleSheetIntegration = async (
|
||||
await getRowFromGoogleSheets(step.options, variables, updateVariableValue)
|
||||
break
|
||||
}
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const insertRowInGoogleSheets = async (
|
||||
options: GoogleSheetsInsertRowOptions,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!options.cellsToInsert) return
|
||||
return sendRequest({
|
||||
@@ -82,7 +89,7 @@ const insertRowInGoogleSheets = async (
|
||||
|
||||
const updateRowInGoogleSheets = async (
|
||||
options: GoogleSheetsUpdateRowOptions,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) => {
|
||||
if (!options.cellsToUpsert || !options.referenceCell) return
|
||||
return sendRequest({
|
||||
@@ -104,7 +111,7 @@ const updateRowInGoogleSheets = async (
|
||||
|
||||
const getRowFromGoogleSheets = async (
|
||||
options: GoogleSheetsGetOptions,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!options.referenceCell || !options.cellsToExtract) return
|
||||
@@ -118,9 +125,7 @@ const getRowFromGoogleSheets = async (
|
||||
variables,
|
||||
}),
|
||||
},
|
||||
columns: options.cellsToExtract.allIds.map(
|
||||
(id) => options.cellsToExtract?.byId[id].column
|
||||
),
|
||||
columns: options.cellsToExtract.map((cell) => cell.column),
|
||||
},
|
||||
{ indices: false }
|
||||
)
|
||||
@@ -129,18 +134,15 @@ const getRowFromGoogleSheets = async (
|
||||
method: 'GET',
|
||||
})
|
||||
if (!data) return
|
||||
options.cellsToExtract.allIds.forEach((cellId) => {
|
||||
const cell = options.cellsToExtract?.byId[cellId]
|
||||
if (!cell) return
|
||||
options.cellsToExtract.forEach((cell) =>
|
||||
updateVariableValue(cell.variableId ?? '', data[cell.column ?? ''])
|
||||
})
|
||||
)
|
||||
}
|
||||
const parseCellValues = (
|
||||
cells: Table<Cell>,
|
||||
variables: Table<Variable>
|
||||
cells: Cell[],
|
||||
variables: Variable[]
|
||||
): { [key: string]: string } =>
|
||||
cells.allIds.reduce((row, id) => {
|
||||
const cell = cells.byId[id]
|
||||
cells.reduce((row, cell) => {
|
||||
return !cell.column || !cell.value
|
||||
? row
|
||||
: {
|
||||
@@ -152,20 +154,21 @@ const parseCellValues = (
|
||||
const executeWebhook = async (
|
||||
typebotId: string,
|
||||
step: WebhookStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
indices: Indices,
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
) => {
|
||||
if (!step.options?.webhookId) return step.edgeId
|
||||
if (!step.webhook) return step.outgoingEdgeId
|
||||
const { blockIndex, stepIndex } = indices
|
||||
const { data, error } = await sendRequest({
|
||||
url: `http://localhost:3000/api/typebots/${typebotId}/webhooks/${step.options?.webhookId}/execute`,
|
||||
url: `http://localhost:3000/api/typebots/${typebotId}/blocks/${blockIndex}/steps/${stepIndex}/executeWebhook`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
},
|
||||
})
|
||||
console.error(error)
|
||||
step.options.responseVariableMapping?.allIds.forEach((varMappingId) => {
|
||||
const varMapping = step.options?.responseVariableMapping?.byId[varMappingId]
|
||||
step.options.responseVariableMapping.forEach((varMapping) => {
|
||||
if (!varMapping?.bodyPath || !varMapping.variableId) return
|
||||
const value = safeEval(`(${JSON.stringify(data)}).${varMapping?.bodyPath}`)
|
||||
updateVariableValue(varMapping.variableId, value)
|
||||
|
||||
@@ -3,20 +3,21 @@ import {
|
||||
LogicStepType,
|
||||
LogicalOperator,
|
||||
ConditionStep,
|
||||
Table,
|
||||
Variable,
|
||||
ComparisonOperators,
|
||||
SetVariableStep,
|
||||
RedirectStep,
|
||||
Comparison,
|
||||
} from 'models'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { sanitizeUrl } from './utils'
|
||||
import { isMathFormula, evaluateExpression, parseVariables } from './variable'
|
||||
|
||||
type EdgeId = string
|
||||
|
||||
export const executeLogic = (
|
||||
step: LogicStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, expression: string) => void
|
||||
): EdgeId | undefined => {
|
||||
switch (step.type) {
|
||||
@@ -31,40 +32,36 @@ export const executeLogic = (
|
||||
|
||||
const executeSetVariable = (
|
||||
step: SetVariableStep,
|
||||
variables: Table<Variable>,
|
||||
variables: Variable[],
|
||||
updateVariableValue: (variableId: string, expression: string) => void
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.variableId || !step.options.expressionToEvaluate)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
const expression = step.options.expressionToEvaluate
|
||||
const evaluatedExpression = isMathFormula(expression)
|
||||
? evaluateExpression(parseVariables({ text: expression, variables }))
|
||||
: expression
|
||||
updateVariableValue(step.options.variableId, evaluatedExpression)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCondition = (
|
||||
step: ConditionStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
): EdgeId | undefined => {
|
||||
const { content } = step.items[0]
|
||||
const isConditionPassed =
|
||||
step.options?.logicalOperator === LogicalOperator.AND
|
||||
? step.options?.comparisons.allIds.every(
|
||||
executeComparison(step, variables)
|
||||
)
|
||||
: step.options?.comparisons.allIds.some(
|
||||
executeComparison(step, variables)
|
||||
)
|
||||
return isConditionPassed ? step.trueEdgeId : step.falseEdgeId
|
||||
content.logicalOperator === LogicalOperator.AND
|
||||
? content.comparisons.every(executeComparison(variables))
|
||||
: content.comparisons.some(executeComparison(variables))
|
||||
return isConditionPassed ? step.items[0].outgoingEdgeId : step.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeComparison =
|
||||
(step: ConditionStep, variables: Table<Variable>) =>
|
||||
(comparisonId: string) => {
|
||||
const comparison = step.options?.comparisons.byId[comparisonId]
|
||||
(variables: Variable[]) => (comparison: Comparison) => {
|
||||
if (!comparison?.variableId) return false
|
||||
const inputValue = variables.byId[comparison.variableId].value ?? ''
|
||||
const inputValue =
|
||||
variables.find((v) => v.id === comparison.variableId)?.value ?? ''
|
||||
const { value } = comparison
|
||||
if (isNotDefined(value)) return false
|
||||
switch (comparison.comparisonOperator) {
|
||||
@@ -91,12 +88,12 @@ const executeComparison =
|
||||
|
||||
const executeRedirect = (
|
||||
step: RedirectStep,
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.url) return step.edgeId
|
||||
if (!step.options?.url) return step.outgoingEdgeId
|
||||
window.open(
|
||||
sanitizeUrl(parseVariables({ text: step.options?.url, variables })),
|
||||
step.options.isNewTab ? '_blank' : '_self'
|
||||
)
|
||||
return step.edgeId
|
||||
return step.outgoingEdgeId
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Table, Variable } from 'models'
|
||||
import { Variable } from 'models'
|
||||
import { isDefined } from 'utils'
|
||||
|
||||
const safeEval = eval
|
||||
@@ -11,16 +11,16 @@ export const parseVariables = ({
|
||||
variables,
|
||||
}: {
|
||||
text?: string
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
}): string => {
|
||||
if (!text || text === '') return ''
|
||||
return text.replace(/\{\{(.*?)\}\}/g, (_, fullVariableString) => {
|
||||
const matchedVarName = fullVariableString.replace(/{{|}}/g, '')
|
||||
const matchedVariableId = variables.allIds.find((variableId) => {
|
||||
const variable = variables.byId[variableId]
|
||||
return matchedVarName === variable.name && isDefined(variable.value)
|
||||
})
|
||||
return variables.byId[matchedVariableId ?? '']?.value ?? ''
|
||||
return (
|
||||
variables.find((v) => {
|
||||
return matchedVarName === v.name && isDefined(v.value)
|
||||
})?.value ?? ''
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ const countDecimals = (value: number) => {
|
||||
|
||||
export const parseVariablesInObject = (
|
||||
object: { [key: string]: string | number },
|
||||
variables: Table<Variable>
|
||||
variables: Variable[]
|
||||
) =>
|
||||
Object.keys(object).reduce((newObj, key) => {
|
||||
const currentValue = object[key]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"baseUrl": "./src"
|
||||
"baseUrl": "./src",
|
||||
"downlevelIteration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,30 +106,25 @@ model Typebot {
|
||||
results Result[]
|
||||
folderId String?
|
||||
folder DashboardFolder? @relation(fields: [folderId], references: [id])
|
||||
blocks Json
|
||||
steps Json
|
||||
choiceItems Json
|
||||
variables Json
|
||||
webhooks Json
|
||||
edges Json
|
||||
blocks Json[]
|
||||
variables Json[]
|
||||
edges Json[]
|
||||
theme Json
|
||||
settings Json
|
||||
publicId String? @unique
|
||||
}
|
||||
|
||||
model PublicTypebot {
|
||||
id String @id @default(cuid())
|
||||
typebotId String @unique
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
blocks Json
|
||||
steps Json
|
||||
choiceItems Json
|
||||
variables Json
|
||||
edges Json
|
||||
theme Json
|
||||
settings Json
|
||||
publicId String? @unique
|
||||
id String @id @default(cuid())
|
||||
typebotId String @unique
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
blocks Json[]
|
||||
variables Json[]
|
||||
edges Json[]
|
||||
theme Json
|
||||
settings Json
|
||||
publicId String? @unique
|
||||
}
|
||||
|
||||
model Result {
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import { Block, Edge, Settings, Step, Theme, Variable } from './typebot'
|
||||
import { PublicTypebot as PublicTypebotFromPrisma } from 'db'
|
||||
import { Block, ChoiceItem, Edge, Settings, Step, Theme } from './typebot'
|
||||
import { Variable } from './typebot/variable'
|
||||
import { Table } from './utils'
|
||||
|
||||
export type PublicTypebot = Omit<
|
||||
PublicTypebotFromPrisma,
|
||||
| 'blocks'
|
||||
| 'startBlock'
|
||||
| 'theme'
|
||||
| 'settings'
|
||||
| 'steps'
|
||||
| 'choiceItems'
|
||||
| 'variables'
|
||||
'blocks' | 'theme' | 'settings' | 'variables' | 'edges'
|
||||
> & {
|
||||
blocks: Table<Block>
|
||||
steps: Table<Step>
|
||||
choiceItems: Table<ChoiceItem>
|
||||
variables: Table<Variable>
|
||||
edges: Table<Edge>
|
||||
blocks: PublicBlock[]
|
||||
variables: Variable[]
|
||||
edges: Edge[]
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
export type PublicBlock = Omit<Block, 'steps'> & { steps: PublicStep[] }
|
||||
export type PublicStep = Omit<Step, 'webhook'> & { webhook?: string }
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from './bubble'
|
||||
export * from './inputs'
|
||||
export * from './logic'
|
||||
export * from './integration'
|
||||
export * from './item'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ItemBase, ItemType } from '.'
|
||||
import { Item } from './item'
|
||||
import { StepBase } from './steps'
|
||||
|
||||
export type InputStep =
|
||||
@@ -62,14 +64,13 @@ export type PhoneNumberInputOptions = OptionBase & InputTextOptionsBase
|
||||
|
||||
export type ChoiceInputStep = StepBase & {
|
||||
type: InputStepType.CHOICE
|
||||
items: ButtonItem[]
|
||||
options: ChoiceInputOptions
|
||||
}
|
||||
|
||||
export type ChoiceItem = {
|
||||
id: string
|
||||
stepId: string
|
||||
export type ButtonItem = ItemBase & {
|
||||
type: ItemType.BUTTON
|
||||
content?: string
|
||||
edgeId?: string
|
||||
}
|
||||
|
||||
type OptionBase = { variableId?: string }
|
||||
@@ -78,7 +79,6 @@ type InputTextOptionsBase = {
|
||||
}
|
||||
|
||||
export type ChoiceInputOptions = OptionBase & {
|
||||
itemIds: string[]
|
||||
isMultipleChoice: boolean
|
||||
buttonLabel: string
|
||||
}
|
||||
@@ -140,5 +140,4 @@ export const defaultPhoneInputOptions: PhoneNumberInputOptions = {
|
||||
export const defaultChoiceInputOptions: ChoiceInputOptions = {
|
||||
buttonLabel: defaultButtonLabel,
|
||||
isMultipleChoice: false,
|
||||
itemIds: [],
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { StepBase } from '.'
|
||||
import { Table } from '../..'
|
||||
|
||||
export type IntegrationStep =
|
||||
| GoogleSheetsStep
|
||||
@@ -30,6 +29,7 @@ export type GoogleAnalyticsStep = StepBase & {
|
||||
export type WebhookStep = StepBase & {
|
||||
type: IntegrationStepType.WEBHOOK
|
||||
options: WebhookOptions
|
||||
webhook: Webhook
|
||||
}
|
||||
|
||||
export type GoogleAnalyticsOptions = {
|
||||
@@ -58,34 +58,41 @@ export type GoogleSheetsOptionsBase = {
|
||||
sheetId?: string
|
||||
}
|
||||
|
||||
export type Cell = { column?: string; value?: string }
|
||||
export type ExtractingCell = { column?: string; variableId?: string }
|
||||
export type Cell = { id: string; column?: string; value?: string }
|
||||
export type ExtractingCell = {
|
||||
id: string
|
||||
column?: string
|
||||
variableId?: string
|
||||
}
|
||||
|
||||
export type GoogleSheetsGetOptions = NonNullable<GoogleSheetsOptionsBase> & {
|
||||
action: GoogleSheetsAction.GET
|
||||
referenceCell?: Cell
|
||||
cellsToExtract: Table<ExtractingCell>
|
||||
cellsToExtract: ExtractingCell[]
|
||||
}
|
||||
|
||||
export type GoogleSheetsInsertRowOptions =
|
||||
NonNullable<GoogleSheetsOptionsBase> & {
|
||||
action: GoogleSheetsAction.INSERT_ROW
|
||||
cellsToInsert: Table<Cell>
|
||||
cellsToInsert: Cell[]
|
||||
}
|
||||
|
||||
export type GoogleSheetsUpdateRowOptions =
|
||||
NonNullable<GoogleSheetsOptionsBase> & {
|
||||
action: GoogleSheetsAction.UPDATE_ROW
|
||||
referenceCell?: Cell
|
||||
cellsToUpsert: Table<Cell>
|
||||
cellsToUpsert: Cell[]
|
||||
}
|
||||
|
||||
export type ResponseVariableMapping = { bodyPath?: string; variableId?: string }
|
||||
export type ResponseVariableMapping = {
|
||||
id: string
|
||||
bodyPath?: string
|
||||
variableId?: string
|
||||
}
|
||||
|
||||
export type WebhookOptions = {
|
||||
webhookId: string
|
||||
variablesForTest: Table<VariableForTest>
|
||||
responseVariableMapping: Table<ResponseVariableMapping>
|
||||
variablesForTest: VariableForTest[]
|
||||
responseVariableMapping: ResponseVariableMapping[]
|
||||
}
|
||||
|
||||
export enum HttpMethod {
|
||||
@@ -100,15 +107,19 @@ export enum HttpMethod {
|
||||
TRACE = 'TRACE',
|
||||
}
|
||||
|
||||
export type KeyValue = { key?: string; value?: string }
|
||||
export type VariableForTest = { variableId?: string; value?: string }
|
||||
export type KeyValue = { id: string; key?: string; value?: string }
|
||||
export type VariableForTest = {
|
||||
id: string
|
||||
variableId?: string
|
||||
value?: string
|
||||
}
|
||||
|
||||
export type Webhook = {
|
||||
id: string
|
||||
url?: string
|
||||
method: HttpMethod
|
||||
queryParams: Table<KeyValue>
|
||||
headers: Table<KeyValue>
|
||||
queryParams: KeyValue[]
|
||||
headers: KeyValue[]
|
||||
body?: string
|
||||
}
|
||||
|
||||
@@ -122,12 +133,12 @@ export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {}
|
||||
export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {}
|
||||
|
||||
export const defaultWebhookOptions: Omit<WebhookOptions, 'webhookId'> = {
|
||||
responseVariableMapping: { byId: {}, allIds: [] },
|
||||
variablesForTest: { byId: {}, allIds: [] },
|
||||
responseVariableMapping: [],
|
||||
variablesForTest: [],
|
||||
}
|
||||
|
||||
export const defaultWebhookAttributes: Omit<Webhook, 'id'> = {
|
||||
method: HttpMethod.GET,
|
||||
headers: { byId: {}, allIds: [] },
|
||||
queryParams: { byId: {}, allIds: [] },
|
||||
headers: [],
|
||||
queryParams: [],
|
||||
}
|
||||
|
||||
20
packages/models/src/typebot/steps/item.ts
Normal file
20
packages/models/src/typebot/steps/item.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ButtonItem, ConditionItem } from '.'
|
||||
|
||||
export type Item = ButtonItem | ConditionItem
|
||||
|
||||
export enum ItemType {
|
||||
BUTTON,
|
||||
CONDITION,
|
||||
}
|
||||
|
||||
export type ItemBase = {
|
||||
id: string
|
||||
stepId: string
|
||||
outgoingEdgeId?: string
|
||||
}
|
||||
|
||||
export type ItemIndices = {
|
||||
blockIndex: number
|
||||
stepIndex: number
|
||||
itemIndex: number
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { StepBase } from '.'
|
||||
import { Table } from '../..'
|
||||
import { ItemType, StepBase } from '.'
|
||||
import { ItemBase } from './item'
|
||||
|
||||
export type LogicStep = SetVariableStep | ConditionStep | RedirectStep
|
||||
|
||||
@@ -9,10 +9,7 @@ export enum LogicStepType {
|
||||
REDIRECT = 'Redirect',
|
||||
}
|
||||
|
||||
export type LogicStepOptions =
|
||||
| SetVariableOptions
|
||||
| ConditionOptions
|
||||
| RedirectOptions
|
||||
export type LogicStepOptions = SetVariableOptions | RedirectOptions
|
||||
|
||||
export type SetVariableStep = StepBase & {
|
||||
type: LogicStepType.SET_VARIABLE
|
||||
@@ -21,9 +18,12 @@ export type SetVariableStep = StepBase & {
|
||||
|
||||
export type ConditionStep = StepBase & {
|
||||
type: LogicStepType.CONDITION
|
||||
options: ConditionOptions
|
||||
trueEdgeId?: string
|
||||
falseEdgeId?: string
|
||||
items: [ConditionItem]
|
||||
}
|
||||
|
||||
export type ConditionItem = ItemBase & {
|
||||
type: ItemType.CONDITION
|
||||
content: ConditionContent
|
||||
}
|
||||
|
||||
export type RedirectStep = StepBase & {
|
||||
@@ -45,8 +45,8 @@ export enum ComparisonOperators {
|
||||
IS_SET = 'Is set',
|
||||
}
|
||||
|
||||
export type ConditionOptions = {
|
||||
comparisons: Table<Comparison>
|
||||
export type ConditionContent = {
|
||||
comparisons: Comparison[]
|
||||
logicalOperator: LogicalOperator
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ export type RedirectOptions = {
|
||||
|
||||
export const defaultSetVariablesOptions: SetVariableOptions = {}
|
||||
|
||||
export const defaultConditionOptions: ConditionOptions = {
|
||||
comparisons: { byId: {}, allIds: [] },
|
||||
export const defaultConditionContent: ConditionContent = {
|
||||
comparisons: [],
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,12 @@ import {
|
||||
InputStepOptions,
|
||||
IntegrationStepOptions,
|
||||
IntegrationStepType,
|
||||
Item,
|
||||
LogicStepOptions,
|
||||
RedirectStep,
|
||||
SetVariableStep,
|
||||
} from '.'
|
||||
import { Edge } from '..'
|
||||
import { BubbleStep, BubbleStepType } from './bubble'
|
||||
import { InputStep, InputStepType } from './inputs'
|
||||
import { IntegrationStep } from './integration'
|
||||
@@ -31,11 +35,16 @@ export type DraggableStepType =
|
||||
| LogicStepType
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepWithOptions = InputStep | LogicStep | IntegrationStep
|
||||
export type StepWithOptions =
|
||||
| InputStep
|
||||
| SetVariableStep
|
||||
| RedirectStep
|
||||
| IntegrationStep
|
||||
|
||||
export type StepWithOptionsType =
|
||||
| InputStepType
|
||||
| LogicStepType
|
||||
| LogicStepType.REDIRECT
|
||||
| LogicStepType.SET_VARIABLE
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepOptions =
|
||||
@@ -43,9 +52,16 @@ export type StepOptions =
|
||||
| LogicStepOptions
|
||||
| IntegrationStepOptions
|
||||
|
||||
export type StepBase = { id: string; blockId: string; edgeId?: string }
|
||||
export type StepWithItems = Omit<Step, 'items'> & { items: Item[] }
|
||||
|
||||
export type StepBase = { id: string; blockId: string; outgoingEdgeId?: string }
|
||||
|
||||
export type StartStep = StepBase & {
|
||||
type: 'start'
|
||||
label: string
|
||||
}
|
||||
|
||||
export type StepIndices = {
|
||||
blockIndex: number
|
||||
stepIndex: number
|
||||
}
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
import { Typebot as TypebotFromPrisma } from 'db'
|
||||
import { ChoiceItem } from './steps/inputs'
|
||||
import { Table } from '../utils'
|
||||
import { Settings } from './settings'
|
||||
import { Step } from './steps/steps'
|
||||
import { Theme } from './theme'
|
||||
import { Variable } from './variable'
|
||||
import { Webhook } from '.'
|
||||
|
||||
export type Typebot = Omit<
|
||||
TypebotFromPrisma,
|
||||
| 'blocks'
|
||||
| 'theme'
|
||||
| 'settings'
|
||||
| 'steps'
|
||||
| 'choiceItems'
|
||||
| 'variables'
|
||||
| 'webhooks'
|
||||
'blocks' | 'theme' | 'settings' | 'variables' | 'edges'
|
||||
> & {
|
||||
blocks: Table<Block>
|
||||
steps: Table<Step>
|
||||
choiceItems: Table<ChoiceItem>
|
||||
variables: Table<Variable>
|
||||
edges: Table<Edge>
|
||||
webhooks: Table<Webhook>
|
||||
blocks: Block[]
|
||||
variables: Variable[]
|
||||
edges: Edge[]
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
}
|
||||
@@ -34,14 +22,13 @@ export type Block = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
stepIds: string[]
|
||||
steps: Step[]
|
||||
}
|
||||
|
||||
export type Source = {
|
||||
blockId: string
|
||||
stepId: string
|
||||
buttonId?: string
|
||||
conditionType?: 'true' | 'false'
|
||||
itemId?: string
|
||||
}
|
||||
export type Target = { blockId: string; stepId?: string }
|
||||
export type Edge = {
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
export type Table<T> = { byId: { [key: string]: T }; allIds: string[] }
|
||||
|
||||
export const defaultTable = { byId: {}, allIds: [] }
|
||||
export type IdMap<T> = { [id: string]: T }
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
LogicStep,
|
||||
LogicStepType,
|
||||
Step,
|
||||
Table,
|
||||
TextInputStep,
|
||||
TextBubbleStep,
|
||||
WebhookStep,
|
||||
StepType,
|
||||
StepWithOptionsType,
|
||||
PublicStep,
|
||||
} from 'models'
|
||||
|
||||
export const sendRequest = async <ResponseData>({
|
||||
@@ -50,39 +50,44 @@ export const isNotDefined = <T>(
|
||||
value: T | undefined | null
|
||||
): value is undefined | null => value === undefined || value === null
|
||||
|
||||
export const filterTable = <T>(ids: string[], table: Table<T>): Table<T> => ({
|
||||
byId: ids.reduce((acc, id) => ({ ...acc, [id]: table.byId[id] }), {}),
|
||||
allIds: ids,
|
||||
})
|
||||
|
||||
export const isInputStep = (step: Step): step is InputStep =>
|
||||
export const isInputStep = (step: Step | PublicStep): step is InputStep =>
|
||||
(Object.values(InputStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isBubbleStep = (step: Step): step is BubbleStep =>
|
||||
export const isBubbleStep = (step: Step | PublicStep): step is BubbleStep =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isLogicStep = (step: Step): step is LogicStep =>
|
||||
export const isLogicStep = (step: Step | PublicStep): step is LogicStep =>
|
||||
(Object.values(LogicStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isTextBubbleStep = (step: Step): step is TextBubbleStep =>
|
||||
step.type === BubbleStepType.TEXT
|
||||
export const isTextBubbleStep = (
|
||||
step: Step | PublicStep
|
||||
): step is TextBubbleStep => step.type === BubbleStepType.TEXT
|
||||
|
||||
export const isTextInputStep = (step: Step): step is TextInputStep =>
|
||||
step.type === InputStepType.TEXT
|
||||
export const isTextInputStep = (
|
||||
step: Step | PublicStep
|
||||
): step is TextInputStep => step.type === InputStepType.TEXT
|
||||
|
||||
export const isChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE
|
||||
export const isChoiceInput = (
|
||||
step: Step | PublicStep
|
||||
): step is ChoiceInputStep => step.type === InputStepType.CHOICE
|
||||
|
||||
export const isSingleChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE && !step.options.isMultipleChoice
|
||||
export const isSingleChoiceInput = (
|
||||
step: Step | PublicStep
|
||||
): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE &&
|
||||
'options' in step &&
|
||||
!step.options.isMultipleChoice
|
||||
|
||||
export const isConditionStep = (step: Step): step is ConditionStep =>
|
||||
step.type === LogicStepType.CONDITION
|
||||
export const isConditionStep = (
|
||||
step: Step | PublicStep
|
||||
): step is ConditionStep => step.type === LogicStepType.CONDITION
|
||||
|
||||
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
||||
export const isIntegrationStep = (
|
||||
step: Step | PublicStep
|
||||
): step is IntegrationStep =>
|
||||
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
||||
|
||||
export const isWebhookStep = (step: Step): step is WebhookStep =>
|
||||
export const isWebhookStep = (step: Step | PublicStep): step is WebhookStep =>
|
||||
step.type === IntegrationStepType.WEBHOOK
|
||||
|
||||
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
||||
@@ -95,3 +100,18 @@ export const stepTypeHasOption = (
|
||||
.concat(Object.values(LogicStepType))
|
||||
.concat(Object.values(IntegrationStepType))
|
||||
.includes(type)
|
||||
|
||||
export const stepTypeHasWebhook = (
|
||||
type: StepType
|
||||
): type is IntegrationStepType.WEBHOOK => type === IntegrationStepType.WEBHOOK
|
||||
|
||||
export const stepTypeHasItems = (
|
||||
type: StepType
|
||||
): type is LogicStepType.CONDITION | InputStepType.CHOICE =>
|
||||
type === LogicStepType.CONDITION || type === InputStepType.CHOICE
|
||||
|
||||
export const stepHasItems = (
|
||||
step: Step
|
||||
): step is ConditionStep | ChoiceInputStep => 'items' in step
|
||||
|
||||
export const byId = (id?: string) => (obj: { id: string }) => obj.id === id
|
||||
|
||||
Reference in New Issue
Block a user