refactor: ♻️ Rename step to block
This commit is contained in:
@ -1,24 +0,0 @@
|
||||
import { BubbleStep, BubbleStepType } from 'models'
|
||||
import React from 'react'
|
||||
import { EmbedBubble } from './EmbedBubble'
|
||||
import { ImageBubble } from './ImageBubble'
|
||||
import { TextBubble } from './TextBubble'
|
||||
import { VideoBubble } from './VideoBubble'
|
||||
|
||||
type Props = {
|
||||
step: BubbleStep
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
export const HostBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
switch (step.type) {
|
||||
case BubbleStepType.TEXT:
|
||||
return <TextBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.IMAGE:
|
||||
return <ImageBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.VIDEO:
|
||||
return <VideoBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleStepType.EMBED:
|
||||
return <EmbedBubble step={step} onTransitionEnd={onTransitionEnd} />
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { InputChatStep } from './InputChatStep'
|
@ -1 +0,0 @@
|
||||
export { ChatBlock } from './ChatBlock'
|
@ -20,13 +20,13 @@ export const AvatarSideContainer = forwardRef(
|
||||
const [avatarTopOffset, setAvatarTopOffset] = useState(0)
|
||||
|
||||
const refreshTopOffset = () => {
|
||||
if (!scrollingSideBlockRef.current || !avatarContainer.current) return
|
||||
const { height } = scrollingSideBlockRef.current.getBoundingClientRect()
|
||||
if (!scrollingSideGroupRef.current || !avatarContainer.current) return
|
||||
const { height } = scrollingSideGroupRef.current.getBoundingClientRect()
|
||||
const { height: avatarHeight } =
|
||||
avatarContainer.current.getBoundingClientRect()
|
||||
setAvatarTopOffset(height - avatarHeight)
|
||||
}
|
||||
const scrollingSideBlockRef = useRef<HTMLDivElement>(null)
|
||||
const scrollingSideGroupRef = useRef<HTMLDivElement>(null)
|
||||
const avatarContainer = useRef<HTMLDivElement>(null)
|
||||
useImperativeHandle(ref, () => ({
|
||||
refreshTopOffset,
|
||||
@ -45,7 +45,7 @@ export const AvatarSideContainer = forwardRef(
|
||||
return (
|
||||
<div
|
||||
className="flex w-6 xs:w-10 mr-2 mb-2 flex-shrink-0 items-center relative typebot-avatar-container "
|
||||
ref={scrollingSideBlockRef}
|
||||
ref={scrollingSideGroupRef}
|
||||
>
|
||||
<CSSTransition
|
||||
classNames="bubble"
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useAnswers } from '../../../contexts/AnswersContext'
|
||||
import { InputStep, InputStepType } from 'models'
|
||||
import { InputBlock, InputBlockType } from 'models'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { TextForm } from './inputs/TextForm'
|
||||
import { byId } from 'utils'
|
||||
@ -12,13 +12,13 @@ import { isInputValid } from 'services/inputs'
|
||||
import { PaymentForm } from './inputs/PaymentForm'
|
||||
import { RatingForm } from './inputs/RatingForm'
|
||||
|
||||
export const InputChatStep = ({
|
||||
step,
|
||||
export const InputChatBlock = ({
|
||||
block,
|
||||
hasAvatar,
|
||||
hasGuestAvatar,
|
||||
onTransitionEnd,
|
||||
}: {
|
||||
step: InputStep
|
||||
block: InputBlock
|
||||
hasGuestAvatar: boolean
|
||||
hasAvatar: boolean
|
||||
onTransitionEnd: (answerContent?: string, isRetry?: boolean) => void
|
||||
@ -28,7 +28,7 @@ export const InputChatStep = ({
|
||||
const [answer, setAnswer] = useState<string>()
|
||||
const [isEditting, setIsEditting] = useState(false)
|
||||
|
||||
const { variableId } = step.options
|
||||
const { variableId } = block.options
|
||||
const defaultValue =
|
||||
typebot.settings.general.isInputPrefillEnabled ?? true
|
||||
? variableId && typebot.variables.find(byId(variableId))?.value
|
||||
@ -36,11 +36,11 @@ export const InputChatStep = ({
|
||||
|
||||
const handleSubmit = async (content: string) => {
|
||||
setAnswer(content)
|
||||
const isRetry = !isInputValid(content, step.type)
|
||||
const isRetry = !isInputValid(content, block.type)
|
||||
if (!isRetry && addAnswer)
|
||||
await addAnswer({
|
||||
stepId: step.id,
|
||||
blockId: step.blockId,
|
||||
blockId: block.id,
|
||||
groupId: block.groupId,
|
||||
content,
|
||||
variableId: variableId ?? null,
|
||||
})
|
||||
@ -71,7 +71,7 @@ export const InputChatStep = ({
|
||||
<div className="flex w-6 xs:w-10 h-6 xs:h-10 mr-2 mb-2 mt-1 flex-shrink-0 items-center" />
|
||||
)}
|
||||
<Input
|
||||
step={step}
|
||||
block={block}
|
||||
onSubmit={handleSubmit}
|
||||
defaultValue={defaultValue?.toString()}
|
||||
hasGuestAvatar={hasGuestAvatar}
|
||||
@ -81,42 +81,42 @@ export const InputChatStep = ({
|
||||
}
|
||||
|
||||
const Input = ({
|
||||
step,
|
||||
block,
|
||||
onSubmit,
|
||||
defaultValue,
|
||||
hasGuestAvatar,
|
||||
}: {
|
||||
step: InputStep
|
||||
block: InputBlock
|
||||
onSubmit: (value: string) => void
|
||||
defaultValue?: string
|
||||
hasGuestAvatar: boolean
|
||||
}) => {
|
||||
switch (step.type) {
|
||||
case InputStepType.TEXT:
|
||||
case InputStepType.NUMBER:
|
||||
case InputStepType.EMAIL:
|
||||
case InputStepType.URL:
|
||||
case InputStepType.PHONE:
|
||||
switch (block.type) {
|
||||
case InputBlockType.TEXT:
|
||||
case InputBlockType.NUMBER:
|
||||
case InputBlockType.EMAIL:
|
||||
case InputBlockType.URL:
|
||||
case InputBlockType.PHONE:
|
||||
return (
|
||||
<TextForm
|
||||
step={step}
|
||||
block={block}
|
||||
onSubmit={onSubmit}
|
||||
defaultValue={defaultValue}
|
||||
hasGuestAvatar={hasGuestAvatar}
|
||||
/>
|
||||
)
|
||||
case InputStepType.DATE:
|
||||
return <DateForm options={step.options} onSubmit={onSubmit} />
|
||||
case InputStepType.CHOICE:
|
||||
return <ChoiceForm step={step} onSubmit={onSubmit} />
|
||||
case InputStepType.PAYMENT:
|
||||
case InputBlockType.DATE:
|
||||
return <DateForm options={block.options} onSubmit={onSubmit} />
|
||||
case InputBlockType.CHOICE:
|
||||
return <ChoiceForm block={block} onSubmit={onSubmit} />
|
||||
case InputBlockType.PAYMENT:
|
||||
return (
|
||||
<PaymentForm
|
||||
options={step.options}
|
||||
onSuccess={() => onSubmit(step.options.labels.success ?? 'Success')}
|
||||
options={block.options}
|
||||
onSuccess={() => onSubmit(block.options.labels.success ?? 'Success')}
|
||||
/>
|
||||
)
|
||||
case InputStepType.RATING:
|
||||
return <RatingForm step={step} onSubmit={onSubmit} />
|
||||
case InputBlockType.RATING:
|
||||
return <RatingForm block={block} onSubmit={onSubmit} />
|
||||
}
|
||||
}
|
@ -1,24 +1,24 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { EmbedBubbleStep } from 'models'
|
||||
import { EmbedBubbleBlock } from 'models'
|
||||
import { TypingContent } from './TypingContent'
|
||||
import { parseVariables } from 'services/variable'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
|
||||
type Props = {
|
||||
step: EmbedBubbleStep
|
||||
block: EmbedBubbleBlock
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
export const showAnimationDuration = 400
|
||||
|
||||
export const EmbedBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
export const EmbedBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
const url = useMemo(
|
||||
() => parseVariables(typebot.variables)(step.content?.url),
|
||||
[step.content?.url, typebot.variables]
|
||||
() => parseVariables(typebot.variables)(block.content?.url),
|
||||
[block.content?.url, typebot.variables]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -65,7 +65,7 @@ export const EmbedBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
(isTyping ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
style={{
|
||||
height: isTyping ? '2rem' : step.content.height,
|
||||
height: isTyping ? '2rem' : block.content.height,
|
||||
borderRadius: '15px',
|
||||
}}
|
||||
/>
|
@ -0,0 +1,24 @@
|
||||
import { BubbleBlock, BubbleBlockType } from 'models'
|
||||
import React from 'react'
|
||||
import { EmbedBubble } from './EmbedBubble'
|
||||
import { ImageBubble } from './ImageBubble'
|
||||
import { TextBubble } from './TextBubble'
|
||||
import { VideoBubble } from './VideoBubble'
|
||||
|
||||
type Props = {
|
||||
block: BubbleBlock
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
export const HostBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
switch (block.type) {
|
||||
case BubbleBlockType.TEXT:
|
||||
return <TextBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleBlockType.IMAGE:
|
||||
return <ImageBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleBlockType.VIDEO:
|
||||
return <VideoBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
case BubbleBlockType.EMBED:
|
||||
return <EmbedBubble block={block} onTransitionEnd={onTransitionEnd} />
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ImageBubbleStep } from 'models'
|
||||
import { ImageBubbleBlock } from 'models'
|
||||
import { TypingContent } from './TypingContent'
|
||||
import { parseVariables } from 'services/variable'
|
||||
|
||||
type Props = {
|
||||
step: ImageBubbleStep
|
||||
block: ImageBubbleBlock
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
@ -13,15 +13,15 @@ export const showAnimationDuration = 400
|
||||
|
||||
export const mediaLoadingFallbackTimeout = 5000
|
||||
|
||||
export const ImageBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
export const ImageBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const image = useRef<HTMLImageElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
const url = useMemo(
|
||||
() => parseVariables(typebot.variables)(step.content?.url),
|
||||
[step.content?.url, typebot.variables]
|
||||
() => parseVariables(typebot.variables)(block.content?.url),
|
||||
[block.content?.url, typebot.variables]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
@ -1,12 +1,12 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { BubbleStepType, TextBubbleStep } from 'models'
|
||||
import { BubbleBlockType, TextBubbleBlock } from 'models'
|
||||
import { computeTypingTimeout } from 'services/chat'
|
||||
import { TypingContent } from './TypingContent'
|
||||
import { parseVariables } from 'services/variable'
|
||||
|
||||
type Props = {
|
||||
step: TextBubbleStep
|
||||
block: TextBubbleBlock
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
@ -18,18 +18,18 @@ const defaultTypingEmulation = {
|
||||
maxDelay: 1.5,
|
||||
}
|
||||
|
||||
export const TextBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
export const TextBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
const [content] = useState(
|
||||
parseVariables(typebot.variables)(step.content.html)
|
||||
parseVariables(typebot.variables)(block.content.html)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const typingTimeout = computeTypingTimeout(
|
||||
step.content.plainText,
|
||||
block.content.plainText,
|
||||
typebot.settings?.typingEmulation ?? defaultTypingEmulation
|
||||
)
|
||||
setTimeout(() => {
|
||||
@ -59,7 +59,7 @@ export const TextBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
>
|
||||
{isTyping ? <TypingContent /> : <></>}
|
||||
</div>
|
||||
{step.type === BubbleStepType.TEXT && (
|
||||
{block.type === BubbleBlockType.TEXT && (
|
||||
<p
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
@ -4,13 +4,13 @@ import {
|
||||
Variable,
|
||||
VideoBubbleContent,
|
||||
VideoBubbleContentType,
|
||||
VideoBubbleStep,
|
||||
VideoBubbleBlock,
|
||||
} from 'models'
|
||||
import { TypingContent } from './TypingContent'
|
||||
import { parseVariables } from 'services/variable'
|
||||
|
||||
type Props = {
|
||||
step: VideoBubbleStep
|
||||
block: VideoBubbleBlock
|
||||
onTransitionEnd: () => void
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export const showAnimationDuration = 400
|
||||
|
||||
export const mediaLoadingFallbackTimeout = 5000
|
||||
|
||||
export const VideoBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
export const VideoBubble = ({ block, onTransitionEnd }: Props) => {
|
||||
const { typebot } = useTypebot()
|
||||
const messageContainer = useRef<HTMLDivElement | null>(null)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
@ -56,7 +56,7 @@ export const VideoBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
{isTyping ? <TypingContent /> : <></>}
|
||||
</div>
|
||||
<VideoContent
|
||||
content={step.content}
|
||||
content={block.content}
|
||||
isTyping={isTyping}
|
||||
variables={typebot.variables}
|
||||
/>
|
@ -0,0 +1 @@
|
||||
export { InputChatBlock } from './InputChatBlock'
|
@ -1,21 +1,21 @@
|
||||
import { useAnswers } from 'contexts/AnswersContext'
|
||||
import { ChoiceInputStep } from 'models'
|
||||
import { ChoiceInputBlock } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { SendButton } from './SendButton'
|
||||
|
||||
type ChoiceFormProps = {
|
||||
step: ChoiceInputStep
|
||||
block: ChoiceInputBlock
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
export const ChoiceForm = ({ block, onSubmit }: ChoiceFormProps) => {
|
||||
const { resultValues } = useAnswers()
|
||||
const [selectedIndices, setSelectedIndices] = useState<number[]>([])
|
||||
|
||||
const handleClick = (itemIndex: number) => (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (step.options?.isMultipleChoice) toggleSelectedItemIndex(itemIndex)
|
||||
else onSubmit(step.items[itemIndex].content ?? '')
|
||||
if (block.options?.isMultipleChoice) toggleSelectedItemIndex(itemIndex)
|
||||
else onSubmit(block.items[itemIndex].content ?? '')
|
||||
}
|
||||
|
||||
const toggleSelectedItemIndex = (itemIndex: number) => {
|
||||
@ -31,25 +31,27 @@ export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
const handleSubmit = () =>
|
||||
onSubmit(
|
||||
selectedIndices
|
||||
.map((itemIndex) => step.items[itemIndex].content)
|
||||
.map((itemIndex) => block.items[itemIndex].content)
|
||||
.join(', ')
|
||||
)
|
||||
|
||||
const isUniqueFirstButton =
|
||||
resultValues && resultValues.answers.length === 0 && step.items.length === 1
|
||||
resultValues &&
|
||||
resultValues.answers.length === 0 &&
|
||||
block.items.length === 1
|
||||
|
||||
return (
|
||||
<form className="flex flex-col" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-wrap">
|
||||
{step.items.map((item, idx) => (
|
||||
{block.items.map((item, idx) => (
|
||||
<span key={item.id} className="relative inline-flex mr-2 mb-2">
|
||||
<button
|
||||
role={step.options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
role={block.options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(idx)}
|
||||
className={
|
||||
'py-2 px-4 text-left font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
||||
(selectedIndices.includes(idx) ||
|
||||
!step.options?.isMultipleChoice
|
||||
!block.options?.isMultipleChoice
|
||||
? ''
|
||||
: 'selectable')
|
||||
}
|
||||
@ -69,7 +71,10 @@ export const ChoiceForm = ({ step, onSubmit }: ChoiceFormProps) => {
|
||||
</div>
|
||||
<div className="flex">
|
||||
{selectedIndices.length > 0 && (
|
||||
<SendButton label={step.options?.buttonLabel ?? 'Send'} disableIcon />
|
||||
<SendButton
|
||||
label={block.options?.buttonLabel ?? 'Send'}
|
||||
disableIcon
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
@ -1,14 +1,14 @@
|
||||
import { RatingInputOptions, RatingInputStep } from 'models'
|
||||
import { RatingInputOptions, RatingInputBlock } from 'models'
|
||||
import React, { FormEvent, useRef, useState } from 'react'
|
||||
import { isDefined, isEmpty, isNotDefined } from 'utils'
|
||||
import { SendButton } from './SendButton'
|
||||
|
||||
type Props = {
|
||||
step: RatingInputStep
|
||||
block: RatingInputBlock
|
||||
onSubmit: (value: string) => void
|
||||
}
|
||||
|
||||
export const RatingForm = ({ step, onSubmit }: Props) => {
|
||||
export const RatingForm = ({ block, onSubmit }: Props) => {
|
||||
const [rating, setRating] = useState<number>()
|
||||
const scaleElement = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
@ -24,9 +24,9 @@ export const RatingForm = ({ step, onSubmit }: Props) => {
|
||||
<form className="flex flex-col" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col" ref={scaleElement}>
|
||||
<div className="flex">
|
||||
{Array.from(Array(step.options.length)).map((_, idx) => (
|
||||
{Array.from(Array(block.options.length)).map((_, idx) => (
|
||||
<RatingButton
|
||||
{...step.options}
|
||||
{...block.options}
|
||||
key={idx}
|
||||
rating={rating}
|
||||
idx={idx + 1}
|
||||
@ -36,15 +36,15 @@ export const RatingForm = ({ step, onSubmit }: Props) => {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mr-2 mb-2">
|
||||
{<span className="text-sm w-full ">{step.options.labels.left}</span>}
|
||||
{!isEmpty(step.options.labels.middle) && (
|
||||
{<span className="text-sm w-full ">{block.options.labels.left}</span>}
|
||||
{!isEmpty(block.options.labels.middle) && (
|
||||
<span className="text-sm w-full text-center">
|
||||
{step.options.labels.middle}
|
||||
{block.options.labels.middle}
|
||||
</span>
|
||||
)}
|
||||
{
|
||||
<span className="text-sm w-full text-right">
|
||||
{step.options.labels.right}
|
||||
{block.options.labels.right}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@ -53,7 +53,7 @@ export const RatingForm = ({ step, onSubmit }: Props) => {
|
||||
<div className="flex justify-end mr-2">
|
||||
{isDefined(rating) && (
|
||||
<SendButton
|
||||
label={step.options?.labels.button ?? 'Send'}
|
||||
label={block.options?.labels.button ?? 'Send'}
|
||||
disableIcon
|
||||
/>
|
||||
)}
|
@ -1,39 +1,39 @@
|
||||
import {
|
||||
EmailInputStep,
|
||||
InputStepType,
|
||||
NumberInputStep,
|
||||
PhoneNumberInputStep,
|
||||
TextInputStep,
|
||||
UrlInputStep,
|
||||
EmailInputBlock,
|
||||
InputBlockType,
|
||||
NumberInputBlock,
|
||||
PhoneNumberInputBlock,
|
||||
TextInputBlock,
|
||||
UrlInputBlock,
|
||||
} from 'models'
|
||||
import React, { FormEvent, useState } from 'react'
|
||||
import { SendButton } from '../SendButton'
|
||||
import { TextInput } from './TextInputContent'
|
||||
|
||||
type TextFormProps = {
|
||||
step:
|
||||
| TextInputStep
|
||||
| EmailInputStep
|
||||
| NumberInputStep
|
||||
| UrlInputStep
|
||||
| PhoneNumberInputStep
|
||||
block:
|
||||
| TextInputBlock
|
||||
| EmailInputBlock
|
||||
| NumberInputBlock
|
||||
| UrlInputBlock
|
||||
| PhoneNumberInputBlock
|
||||
onSubmit: (value: string) => void
|
||||
defaultValue?: string
|
||||
hasGuestAvatar: boolean
|
||||
}
|
||||
|
||||
export const TextForm = ({
|
||||
step,
|
||||
block,
|
||||
onSubmit,
|
||||
defaultValue,
|
||||
hasGuestAvatar,
|
||||
}: TextFormProps) => {
|
||||
const [inputValue, setInputValue] = useState(defaultValue ?? '')
|
||||
|
||||
const isLongText = step.type === InputStepType.TEXT && step.options?.isLong
|
||||
const isLongText = block.type === InputBlockType.TEXT && block.options?.isLong
|
||||
|
||||
const handleChange = (inputValue: string) => {
|
||||
if (step.type === InputStepType.URL && !inputValue.startsWith('https://'))
|
||||
if (block.type === InputBlockType.URL && !inputValue.startsWith('https://'))
|
||||
return inputValue === 'https:/'
|
||||
? undefined
|
||||
: setInputValue(`https://${inputValue}`)
|
||||
@ -57,9 +57,9 @@ export const TextForm = ({
|
||||
maxWidth: isLongText ? undefined : '350px',
|
||||
}}
|
||||
>
|
||||
<TextInput step={step} onChange={handleChange} value={inputValue} />
|
||||
<TextInput block={block} onChange={handleChange} value={inputValue} />
|
||||
<SendButton
|
||||
label={step.options?.labels?.button ?? 'Send'}
|
||||
label={block.options?.labels?.button ?? 'Send'}
|
||||
isDisabled={inputValue === ''}
|
||||
className="my-2 ml-2"
|
||||
/>
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
TextInputStep,
|
||||
EmailInputStep,
|
||||
NumberInputStep,
|
||||
InputStepType,
|
||||
UrlInputStep,
|
||||
PhoneNumberInputStep,
|
||||
TextInputBlock,
|
||||
EmailInputBlock,
|
||||
NumberInputBlock,
|
||||
InputBlockType,
|
||||
UrlInputBlock,
|
||||
PhoneNumberInputBlock,
|
||||
} from 'models'
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
@ -16,17 +16,17 @@ import React, {
|
||||
import PhoneInput, { Value, Country } from 'react-phone-number-input'
|
||||
|
||||
type TextInputProps = {
|
||||
step:
|
||||
| TextInputStep
|
||||
| EmailInputStep
|
||||
| NumberInputStep
|
||||
| UrlInputStep
|
||||
| PhoneNumberInputStep
|
||||
block:
|
||||
| TextInputBlock
|
||||
| EmailInputBlock
|
||||
| NumberInputBlock
|
||||
| UrlInputBlock
|
||||
| PhoneNumberInputBlock
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
export const TextInput = ({ block, value, onChange }: TextInputProps) => {
|
||||
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@ -42,14 +42,14 @@ export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
onChange(value as string)
|
||||
}
|
||||
|
||||
switch (step.type) {
|
||||
case InputStepType.TEXT: {
|
||||
return step.options?.isLong ? (
|
||||
switch (block.type) {
|
||||
case InputBlockType.TEXT: {
|
||||
return block.options?.isLong ? (
|
||||
<LongTextInput
|
||||
ref={inputRef as unknown as RefObject<HTMLTextAreaElement>}
|
||||
value={value}
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
@ -58,7 +58,7 @@ export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
}
|
||||
name="typebot-short-text"
|
||||
onChange={handleInputChange}
|
||||
@ -66,13 +66,13 @@ export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.EMAIL: {
|
||||
case InputBlockType.EMAIL: {
|
||||
return (
|
||||
<ShortTextInput
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Type your email...'
|
||||
block.options?.labels?.placeholder ?? 'Type your email...'
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
type="email"
|
||||
@ -80,36 +80,36 @@ export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.NUMBER: {
|
||||
case InputBlockType.NUMBER: {
|
||||
return (
|
||||
<ShortTextInput
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
type="number"
|
||||
style={{ appearance: 'auto' }}
|
||||
min={step.options?.min}
|
||||
max={step.options?.max}
|
||||
step={step.options?.step ?? 'any'}
|
||||
min={block.options?.min}
|
||||
max={block.options?.max}
|
||||
step={block.options?.step ?? 'any'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.URL: {
|
||||
case InputBlockType.URL: {
|
||||
return (
|
||||
<ShortTextInput
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
placeholder={step.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||
placeholder={block.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||
onChange={handleInputChange}
|
||||
type="url"
|
||||
autoComplete="url"
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputStepType.PHONE: {
|
||||
case InputBlockType.PHONE: {
|
||||
return (
|
||||
<PhoneInput
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -117,9 +117,9 @@ export const TextInput = ({ step, value, onChange }: TextInputProps) => {
|
||||
value={value}
|
||||
onChange={handlePhoneNumberChange}
|
||||
placeholder={
|
||||
step.options.labels.placeholder ?? 'Your phone number...'
|
||||
block.options.labels.placeholder ?? 'Your phone number...'
|
||||
}
|
||||
defaultCountry={step.options.defaultCountryCode as Country}
|
||||
defaultCountry={block.options.defaultCountryCode as Country}
|
||||
/>
|
||||
)
|
||||
}
|
@ -3,37 +3,37 @@ import { TransitionGroup, CSSTransition } from 'react-transition-group'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { LinkedTypebot, useTypebot } from '../../contexts/TypebotContext'
|
||||
import {
|
||||
isBubbleStep,
|
||||
isBubbleStepType,
|
||||
isBubbleBlock,
|
||||
isBubbleBlockType,
|
||||
isChoiceInput,
|
||||
isDefined,
|
||||
isInputStep,
|
||||
isIntegrationStep,
|
||||
isLogicStep,
|
||||
isInputBlock,
|
||||
isIntegrationBlock,
|
||||
isLogicBlock,
|
||||
} from 'utils'
|
||||
import { executeLogic } from 'services/logic'
|
||||
import { executeIntegration } from 'services/integration'
|
||||
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
|
||||
import { parseRetryBlock, blockCanBeRetried } from 'services/inputs'
|
||||
import { parseVariables } from '../../services/variable'
|
||||
import { useAnswers } from 'contexts/AnswersContext'
|
||||
import {
|
||||
BubbleStep,
|
||||
InputStep,
|
||||
LogicStepType,
|
||||
BubbleBlock,
|
||||
InputBlock,
|
||||
LogicBlockType,
|
||||
PublicTypebot,
|
||||
Step,
|
||||
Block,
|
||||
} from 'models'
|
||||
import { HostBubble } from './ChatStep/bubbles/HostBubble'
|
||||
import { InputChatStep } from './ChatStep/InputChatStep'
|
||||
import { getLastChatStepType } from '../../services/chat'
|
||||
import { HostBubble } from './ChatBlock/bubbles/HostBubble'
|
||||
import { getLastChatBlockType } from '../../services/chat'
|
||||
import { useChat } from 'contexts/ChatContext'
|
||||
import { InputChatBlock } from './ChatBlock'
|
||||
|
||||
type ChatBlockProps = {
|
||||
steps: Step[]
|
||||
startStepIndex: number
|
||||
blockTitle: string
|
||||
type ChatGroupProps = {
|
||||
blocks: Block[]
|
||||
startBlockIndex: number
|
||||
groupTitle: string
|
||||
keepShowingHostAvatar: boolean
|
||||
onBlockEnd: ({
|
||||
onGroupEnd: ({
|
||||
edgeId,
|
||||
updatedTypebot,
|
||||
}: {
|
||||
@ -42,15 +42,15 @@ type ChatBlockProps = {
|
||||
}) => void
|
||||
}
|
||||
|
||||
type ChatDisplayChunk = { bubbles: BubbleStep[]; input?: InputStep }
|
||||
type ChatDisplayChunk = { bubbles: BubbleBlock[]; input?: InputBlock }
|
||||
|
||||
export const ChatBlock = ({
|
||||
steps,
|
||||
startStepIndex,
|
||||
blockTitle,
|
||||
onBlockEnd,
|
||||
export const ChatGroup = ({
|
||||
blocks,
|
||||
startBlockIndex,
|
||||
groupTitle,
|
||||
onGroupEnd,
|
||||
keepShowingHostAvatar,
|
||||
}: ChatBlockProps) => {
|
||||
}: ChatGroupProps) => {
|
||||
const {
|
||||
currentTypebotId,
|
||||
typebot,
|
||||
@ -66,55 +66,57 @@ export const ChatBlock = ({
|
||||
} = useTypebot()
|
||||
const { resultValues, updateVariables, resultId } = useAnswers()
|
||||
const { scroll } = useChat()
|
||||
const [processedSteps, setProcessedSteps] = useState<Step[]>([])
|
||||
const [processedBlocks, setProcessedBlocks] = useState<Block[]>([])
|
||||
const [displayedChunks, setDisplayedChunks] = useState<ChatDisplayChunk[]>([])
|
||||
|
||||
const insertStepInStack = (nextStep: Step) => {
|
||||
setProcessedSteps([...processedSteps, nextStep])
|
||||
if (isBubbleStep(nextStep)) {
|
||||
const lastStepType = getLastChatStepType(processedSteps)
|
||||
lastStepType && isBubbleStepType(lastStepType)
|
||||
const insertBlockInStack = (nextBlock: Block) => {
|
||||
setProcessedBlocks([...processedBlocks, nextBlock])
|
||||
if (isBubbleBlock(nextBlock)) {
|
||||
const lastBlockType = getLastChatBlockType(processedBlocks)
|
||||
lastBlockType && isBubbleBlockType(lastBlockType)
|
||||
? setDisplayedChunks(
|
||||
displayedChunks.map((c, idx) =>
|
||||
idx === displayedChunks.length - 1
|
||||
? { bubbles: [...c.bubbles, nextStep] }
|
||||
? { bubbles: [...c.bubbles, nextBlock] }
|
||||
: c
|
||||
)
|
||||
)
|
||||
: setDisplayedChunks([...displayedChunks, { bubbles: [nextStep] }])
|
||||
: setDisplayedChunks([...displayedChunks, { bubbles: [nextBlock] }])
|
||||
}
|
||||
if (isInputStep(nextStep)) {
|
||||
if (isInputBlock(nextBlock)) {
|
||||
displayedChunks.length === 0 ||
|
||||
isDefined(displayedChunks[displayedChunks.length - 1].input)
|
||||
? setDisplayedChunks([
|
||||
...displayedChunks,
|
||||
{ bubbles: [], input: nextStep },
|
||||
{ bubbles: [], input: nextBlock },
|
||||
])
|
||||
: setDisplayedChunks(
|
||||
displayedChunks.map((c, idx) =>
|
||||
idx === displayedChunks.length - 1 ? { ...c, input: nextStep } : c
|
||||
idx === displayedChunks.length - 1
|
||||
? { ...c, input: nextBlock }
|
||||
: c
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const nextStep = steps[startStepIndex]
|
||||
if (nextStep) insertStepInStack(nextStep)
|
||||
const nextBlock = blocks[startBlockIndex]
|
||||
if (nextBlock) insertBlockInStack(nextBlock)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
scroll()
|
||||
onNewStepDisplayed()
|
||||
onNewBlockDisplayed()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [processedSteps])
|
||||
}, [processedBlocks])
|
||||
|
||||
const onNewStepDisplayed = async () => {
|
||||
const currentStep = [...processedSteps].pop()
|
||||
if (!currentStep) return
|
||||
if (isLogicStep(currentStep)) {
|
||||
const { nextEdgeId, linkedTypebot } = await executeLogic(currentStep, {
|
||||
const onNewBlockDisplayed = async () => {
|
||||
const currentBlock = [...processedBlocks].pop()
|
||||
if (!currentBlock) return
|
||||
if (isLogicBlock(currentBlock)) {
|
||||
const { nextEdgeId, linkedTypebot } = await executeLogic(currentBlock, {
|
||||
isPreview,
|
||||
apiHost,
|
||||
typebot,
|
||||
@ -129,72 +131,75 @@ export const ChatBlock = ({
|
||||
currentTypebotId,
|
||||
})
|
||||
const isRedirecting =
|
||||
currentStep.type === LogicStepType.REDIRECT &&
|
||||
currentStep.options.isNewTab === false
|
||||
currentBlock.type === LogicBlockType.REDIRECT &&
|
||||
currentBlock.options.isNewTab === false
|
||||
if (isRedirecting) return
|
||||
nextEdgeId
|
||||
? onBlockEnd({ edgeId: nextEdgeId, updatedTypebot: linkedTypebot })
|
||||
: displayNextStep()
|
||||
? onGroupEnd({ edgeId: nextEdgeId, updatedTypebot: linkedTypebot })
|
||||
: displayNextBlock()
|
||||
}
|
||||
if (isIntegrationStep(currentStep)) {
|
||||
if (isIntegrationBlock(currentBlock)) {
|
||||
const nextEdgeId = await executeIntegration({
|
||||
step: currentStep,
|
||||
block: currentBlock,
|
||||
context: {
|
||||
apiHost,
|
||||
typebotId: currentTypebotId,
|
||||
blockId: currentStep.blockId,
|
||||
stepId: currentStep.id,
|
||||
groupId: currentBlock.groupId,
|
||||
blockId: currentBlock.id,
|
||||
variables: typebot.variables,
|
||||
isPreview,
|
||||
updateVariableValue,
|
||||
updateVariables,
|
||||
resultValues,
|
||||
blocks: typebot.blocks,
|
||||
groups: typebot.groups,
|
||||
onNewLog,
|
||||
resultId,
|
||||
},
|
||||
})
|
||||
nextEdgeId ? onBlockEnd({ edgeId: nextEdgeId }) : displayNextStep()
|
||||
nextEdgeId ? onGroupEnd({ edgeId: nextEdgeId }) : displayNextBlock()
|
||||
}
|
||||
if (currentStep.type === 'start')
|
||||
onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
|
||||
if (currentBlock.type === 'start')
|
||||
onGroupEnd({ edgeId: currentBlock.outgoingEdgeId })
|
||||
}
|
||||
|
||||
const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
|
||||
const displayNextBlock = (answerContent?: string, isRetry?: boolean) => {
|
||||
scroll()
|
||||
const currentStep = [...processedSteps].pop()
|
||||
if (currentStep) {
|
||||
if (isRetry && stepCanBeRetried(currentStep))
|
||||
return insertStepInStack(
|
||||
parseRetryStep(currentStep, typebot.variables, createEdge)
|
||||
const currentBlock = [...processedBlocks].pop()
|
||||
if (currentBlock) {
|
||||
if (isRetry && blockCanBeRetried(currentBlock))
|
||||
return insertBlockInStack(
|
||||
parseRetryBlock(currentBlock, typebot.variables, createEdge)
|
||||
)
|
||||
if (
|
||||
isInputStep(currentStep) &&
|
||||
currentStep.options?.variableId &&
|
||||
isInputBlock(currentBlock) &&
|
||||
currentBlock.options?.variableId &&
|
||||
answerContent
|
||||
) {
|
||||
updateVariableValue(currentStep.options.variableId, answerContent)
|
||||
updateVariableValue(currentBlock.options.variableId, answerContent)
|
||||
}
|
||||
const isSingleChoiceStep =
|
||||
isChoiceInput(currentStep) && !currentStep.options.isMultipleChoice
|
||||
if (isSingleChoiceStep) {
|
||||
const nextEdgeId = currentStep.items.find(
|
||||
const isSingleChoiceBlock =
|
||||
isChoiceInput(currentBlock) && !currentBlock.options.isMultipleChoice
|
||||
if (isSingleChoiceBlock) {
|
||||
const nextEdgeId = currentBlock.items.find(
|
||||
(i) => i.content === answerContent
|
||||
)?.outgoingEdgeId
|
||||
if (nextEdgeId) return onBlockEnd({ edgeId: nextEdgeId })
|
||||
if (nextEdgeId) return onGroupEnd({ edgeId: nextEdgeId })
|
||||
}
|
||||
|
||||
if (currentStep?.outgoingEdgeId || processedSteps.length === steps.length)
|
||||
return onBlockEnd({ edgeId: currentStep.outgoingEdgeId })
|
||||
if (
|
||||
currentBlock?.outgoingEdgeId ||
|
||||
processedBlocks.length === blocks.length
|
||||
)
|
||||
return onGroupEnd({ edgeId: currentBlock.outgoingEdgeId })
|
||||
}
|
||||
const nextStep = steps[processedSteps.length + startStepIndex]
|
||||
nextStep ? insertStepInStack(nextStep) : onBlockEnd({})
|
||||
const nextBlock = blocks[processedBlocks.length + startBlockIndex]
|
||||
nextBlock ? insertBlockInStack(nextBlock) : onGroupEnd({})
|
||||
}
|
||||
|
||||
const avatarSrc = typebot.theme.chat.hostAvatar?.url
|
||||
|
||||
return (
|
||||
<div className="flex w-full" data-block-name={blockTitle}>
|
||||
<div className="flex w-full" data-group-name={groupTitle}>
|
||||
<div className="flex flex-col w-full min-w-0">
|
||||
{displayedChunks.map((chunk, idx) => (
|
||||
<ChatChunks
|
||||
@ -205,7 +210,7 @@ export const ChatBlock = ({
|
||||
src: avatarSrc && parseVariables(typebot.variables)(avatarSrc),
|
||||
}}
|
||||
hasGuestAvatar={typebot.theme.chat.guestAvatar?.isEnabled ?? false}
|
||||
onDisplayNextStep={displayNextStep}
|
||||
onDisplayNextBlock={displayNextBlock}
|
||||
keepShowingHostAvatar={keepShowingHostAvatar}
|
||||
/>
|
||||
))}
|
||||
@ -219,14 +224,14 @@ type Props = {
|
||||
hostAvatar: { isEnabled: boolean; src?: string }
|
||||
hasGuestAvatar: boolean
|
||||
keepShowingHostAvatar: boolean
|
||||
onDisplayNextStep: (answerContent?: string, isRetry?: boolean) => void
|
||||
onDisplayNextBlock: (answerContent?: string, isRetry?: boolean) => void
|
||||
}
|
||||
const ChatChunks = ({
|
||||
displayChunk: { bubbles, input },
|
||||
hostAvatar,
|
||||
hasGuestAvatar,
|
||||
keepShowingHostAvatar,
|
||||
onDisplayNextStep,
|
||||
onDisplayNextBlock,
|
||||
}: Props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const avatarSideContainerRef = useRef<any>()
|
||||
@ -253,17 +258,17 @@ const ChatChunks = ({
|
||||
style={{ marginRight: hasGuestAvatar ? '50px' : '0.5rem' }}
|
||||
>
|
||||
<TransitionGroup>
|
||||
{bubbles.map((step) => (
|
||||
{bubbles.map((block) => (
|
||||
<CSSTransition
|
||||
key={step.id}
|
||||
key={block.id}
|
||||
classNames="bubble"
|
||||
timeout={500}
|
||||
unmountOnExit
|
||||
>
|
||||
<HostBubble
|
||||
step={step}
|
||||
block={block}
|
||||
onTransitionEnd={() => {
|
||||
onDisplayNextStep()
|
||||
onDisplayNextBlock()
|
||||
refreshTopOffset()
|
||||
}}
|
||||
/>
|
||||
@ -279,9 +284,9 @@ const ChatChunks = ({
|
||||
in={isDefined(input)}
|
||||
>
|
||||
{input && (
|
||||
<InputChatStep
|
||||
step={input}
|
||||
onTransitionEnd={onDisplayNextStep}
|
||||
<InputChatBlock
|
||||
block={input}
|
||||
onTransitionEnd={onDisplayNextBlock}
|
||||
hasAvatar={hostAvatar.isEnabled}
|
||||
hasGuestAvatar={hasGuestAvatar}
|
||||
/>
|
1
packages/bot-engine/src/components/ChatGroup/index.tsx
Normal file
1
packages/bot-engine/src/components/ChatGroup/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { ChatGroup } from './ChatGroup'
|
@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { ChatBlock } from './ChatBlock/ChatBlock'
|
||||
import { ChatGroup } from './ChatGroup'
|
||||
import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
import { useAnswers } from '../contexts/AnswersContext'
|
||||
import { Block, Edge, PublicTypebot, Theme, VariableWithValue } from 'models'
|
||||
import { Group, Edge, PublicTypebot, Theme, VariableWithValue } from 'models'
|
||||
import { byId, isNotDefined } from 'utils'
|
||||
import { animateScroll as scroll } from 'react-scroll'
|
||||
import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
|
||||
@ -13,15 +13,15 @@ import { ChatContext } from 'contexts/ChatContext'
|
||||
type Props = {
|
||||
theme: Theme
|
||||
predefinedVariables?: { [key: string]: string | undefined }
|
||||
startBlockId?: string
|
||||
onNewBlockVisible: (edge: Edge) => void
|
||||
startGroupId?: string
|
||||
onNewGroupVisible: (edge: Edge) => void
|
||||
onCompleted: () => void
|
||||
}
|
||||
export const ConversationContainer = ({
|
||||
theme,
|
||||
predefinedVariables,
|
||||
startBlockId,
|
||||
onNewBlockVisible,
|
||||
startGroupId,
|
||||
onNewGroupVisible,
|
||||
onCompleted,
|
||||
}: Props) => {
|
||||
const {
|
||||
@ -31,34 +31,34 @@ export const ConversationContainer = ({
|
||||
popEdgeIdFromLinkedTypebotQueue,
|
||||
} = useTypebot()
|
||||
const { document: frameDocument } = useFrame()
|
||||
const [displayedBlocks, setDisplayedBlocks] = useState<
|
||||
{ block: Block; startStepIndex: number }[]
|
||||
const [displayedGroups, setDisplayedGroups] = useState<
|
||||
{ group: Group; startBlockIndex: number }[]
|
||||
>([])
|
||||
const { updateVariables } = useAnswers()
|
||||
const bottomAnchor = useRef<HTMLDivElement | null>(null)
|
||||
const scrollableContainer = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const displayNextBlock = ({
|
||||
const displayNextGroup = ({
|
||||
edgeId,
|
||||
updatedTypebot,
|
||||
blockId,
|
||||
groupId,
|
||||
}: {
|
||||
edgeId?: string
|
||||
blockId?: string
|
||||
groupId?: string
|
||||
updatedTypebot?: PublicTypebot | LinkedTypebot
|
||||
}) => {
|
||||
const currentTypebot = updatedTypebot ?? typebot
|
||||
if (blockId) {
|
||||
const nextBlock = currentTypebot.blocks.find(byId(blockId))
|
||||
if (!nextBlock) return
|
||||
onNewBlockVisible({
|
||||
if (groupId) {
|
||||
const nextGroup = currentTypebot.groups.find(byId(groupId))
|
||||
if (!nextGroup) return
|
||||
onNewGroupVisible({
|
||||
id: 'edgeId',
|
||||
from: { blockId: 'block', stepId: 'step' },
|
||||
to: { blockId },
|
||||
from: { groupId: 'block', blockId: 'block' },
|
||||
to: { groupId },
|
||||
})
|
||||
return setDisplayedBlocks([
|
||||
...displayedBlocks,
|
||||
{ block: nextBlock, startStepIndex: 0 },
|
||||
return setDisplayedGroups([
|
||||
...displayedGroups,
|
||||
{ group: nextGroup, startBlockIndex: 0 },
|
||||
])
|
||||
}
|
||||
const nextEdge = currentTypebot.edges.find(byId(edgeId))
|
||||
@ -66,30 +66,30 @@ export const ConversationContainer = ({
|
||||
if (linkedBotQueue.length > 0) {
|
||||
const nextEdgeId = linkedBotQueue[0].edgeId
|
||||
popEdgeIdFromLinkedTypebotQueue()
|
||||
displayNextBlock({ edgeId: nextEdgeId })
|
||||
displayNextGroup({ edgeId: nextEdgeId })
|
||||
}
|
||||
return onCompleted()
|
||||
}
|
||||
const nextBlock = currentTypebot.blocks.find(byId(nextEdge.to.blockId))
|
||||
if (!nextBlock) return onCompleted()
|
||||
const startStepIndex = nextEdge.to.stepId
|
||||
? nextBlock.steps.findIndex(byId(nextEdge.to.stepId))
|
||||
const nextGroup = currentTypebot.groups.find(byId(nextEdge.to.groupId))
|
||||
if (!nextGroup) return onCompleted()
|
||||
const startBlockIndex = nextEdge.to.blockId
|
||||
? nextGroup.blocks.findIndex(byId(nextEdge.to.blockId))
|
||||
: 0
|
||||
onNewBlockVisible(nextEdge)
|
||||
setDisplayedBlocks([
|
||||
...displayedBlocks,
|
||||
{ block: nextBlock, startStepIndex },
|
||||
onNewGroupVisible(nextEdge)
|
||||
setDisplayedGroups([
|
||||
...displayedGroups,
|
||||
{ group: nextGroup, startBlockIndex },
|
||||
])
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prefilledVariables = injectPredefinedVariables(predefinedVariables)
|
||||
updateVariables(prefilledVariables)
|
||||
displayNextBlock({
|
||||
edgeId: startBlockId
|
||||
displayNextGroup({
|
||||
edgeId: startGroupId
|
||||
? undefined
|
||||
: typebot.blocks[0].steps[0].outgoingEdgeId,
|
||||
blockId: startBlockId,
|
||||
: typebot.groups[0].blocks[0].outgoingEdgeId,
|
||||
groupId: startGroupId,
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@ -131,14 +131,14 @@ export const ConversationContainer = ({
|
||||
className="overflow-y-scroll w-full lg:w-3/4 min-h-full rounded lg:px-5 px-3 pt-10 relative scrollable-container typebot-chat-view"
|
||||
>
|
||||
<ChatContext onScroll={autoScrollToBottom}>
|
||||
{displayedBlocks.map((displayedBlock, idx) => (
|
||||
<ChatBlock
|
||||
key={displayedBlock.block.id + idx}
|
||||
steps={displayedBlock.block.steps}
|
||||
startStepIndex={displayedBlock.startStepIndex}
|
||||
onBlockEnd={displayNextBlock}
|
||||
blockTitle={displayedBlock.block.title}
|
||||
keepShowingHostAvatar={idx === displayedBlocks.length - 1}
|
||||
{displayedGroups.map((displayedGroup, idx) => (
|
||||
<ChatGroup
|
||||
key={displayedGroup.group.id + idx}
|
||||
blocks={displayedGroup.group.blocks}
|
||||
startBlockIndex={displayedGroup.startBlockIndex}
|
||||
onGroupEnd={displayNextGroup}
|
||||
groupTitle={displayedGroup.group.title}
|
||||
keepShowingHostAvatar={idx === displayedGroups.length - 1}
|
||||
/>
|
||||
))}
|
||||
</ChatContext>
|
||||
|
@ -30,8 +30,8 @@ export type TypebotViewerProps = {
|
||||
style?: CSSProperties
|
||||
predefinedVariables?: { [key: string]: string | undefined }
|
||||
resultId?: string
|
||||
startBlockId?: string
|
||||
onNewBlockVisible?: (edge: Edge) => void
|
||||
startGroupId?: string
|
||||
onNewGroupVisible?: (edge: Edge) => void
|
||||
onNewAnswer?: (answer: Answer) => Promise<void>
|
||||
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
|
||||
onCompleted?: () => void
|
||||
@ -44,10 +44,10 @@ export const TypebotViewer = ({
|
||||
isPreview = false,
|
||||
style,
|
||||
resultId,
|
||||
startBlockId,
|
||||
startGroupId,
|
||||
predefinedVariables,
|
||||
onNewLog,
|
||||
onNewBlockVisible,
|
||||
onNewGroupVisible,
|
||||
onNewAnswer,
|
||||
onCompleted,
|
||||
onVariablesUpdated,
|
||||
@ -59,8 +59,8 @@ export const TypebotViewer = ({
|
||||
: 'transparent',
|
||||
[typebot?.theme?.general?.background]
|
||||
)
|
||||
const handleNewBlockVisible = (edge: Edge) =>
|
||||
onNewBlockVisible && onNewBlockVisible(edge)
|
||||
const handleNewGroupVisible = (edge: Edge) =>
|
||||
onNewGroupVisible && onNewGroupVisible(edge)
|
||||
|
||||
const handleNewAnswer = (answer: Answer) => onNewAnswer && onNewAnswer(answer)
|
||||
|
||||
@ -115,10 +115,10 @@ export const TypebotViewer = ({
|
||||
<div className="flex w-full h-full justify-center">
|
||||
<ConversationContainer
|
||||
theme={typebot.theme}
|
||||
onNewBlockVisible={handleNewBlockVisible}
|
||||
onNewGroupVisible={handleNewGroupVisible}
|
||||
onCompleted={handleCompleted}
|
||||
predefinedVariables={predefinedVariables}
|
||||
startBlockId={startBlockId}
|
||||
startGroupId={startGroupId}
|
||||
/>
|
||||
</div>
|
||||
{typebot.settings.general.isBrandingEnabled && <LiteBadge />}
|
||||
|
@ -10,7 +10,7 @@ import React, {
|
||||
|
||||
export type LinkedTypebot = Pick<
|
||||
PublicTypebot | Typebot,
|
||||
'id' | 'blocks' | 'variables' | 'edges'
|
||||
'id' | 'groups' | 'variables' | 'edges'
|
||||
>
|
||||
|
||||
export type LinkedTypebotQueue = {
|
||||
@ -86,14 +86,14 @@ export const TypebotContext = ({
|
||||
const injectLinkedTypebot = (typebot: Typebot | PublicTypebot) => {
|
||||
const typebotToInject = {
|
||||
id: 'typebotId' in typebot ? typebot.typebotId : typebot.id,
|
||||
blocks: typebot.blocks,
|
||||
groups: typebot.groups,
|
||||
edges: typebot.edges,
|
||||
variables: typebot.variables,
|
||||
}
|
||||
setLinkedTypebots((typebots) => [...typebots, typebotToInject])
|
||||
const updatedTypebot = {
|
||||
...localTypebot,
|
||||
blocks: [...localTypebot.blocks, ...typebotToInject.blocks],
|
||||
groups: [...localTypebot.groups, ...typebotToInject.groups],
|
||||
variables: [...localTypebot.variables, ...typebotToInject.variables],
|
||||
edges: [...localTypebot.edges, ...typebotToInject.edges],
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
Step,
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
Block,
|
||||
TypingEmulation,
|
||||
} from 'models'
|
||||
import { isBubbleStep, isInputStep } from 'utils'
|
||||
import { isBubbleBlock, isInputBlock } from 'utils'
|
||||
|
||||
export const computeTypingTimeout = (
|
||||
bubbleContent: string,
|
||||
@ -23,11 +23,11 @@ export const computeTypingTimeout = (
|
||||
return typingTimeout
|
||||
}
|
||||
|
||||
export const getLastChatStepType = (
|
||||
steps: Step[]
|
||||
): BubbleStepType | InputStepType | undefined => {
|
||||
const displayedSteps = steps.filter(
|
||||
(s) => isBubbleStep(s) || isInputStep(s)
|
||||
) as (BubbleStep | InputStep)[]
|
||||
return displayedSteps.pop()?.type
|
||||
export const getLastChatBlockType = (
|
||||
blocks: Block[]
|
||||
): BubbleBlockType | InputBlockType | undefined => {
|
||||
const displayedBlocks = blocks.filter(
|
||||
(s) => isBubbleBlock(s) || isInputBlock(s)
|
||||
) as (BubbleBlock | InputBlock)[]
|
||||
return displayedBlocks.pop()?.type
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
Edge,
|
||||
EmailInputStep,
|
||||
InputStepType,
|
||||
PhoneNumberInputStep,
|
||||
Step,
|
||||
UrlInputStep,
|
||||
EmailInputBlock,
|
||||
InputBlockType,
|
||||
PhoneNumberInputBlock,
|
||||
Block,
|
||||
UrlInputBlock,
|
||||
Variable,
|
||||
} from 'models'
|
||||
import { isPossiblePhoneNumber } from 'react-phone-number-input'
|
||||
import { isInputStep } from 'utils'
|
||||
import { isInputBlock } from 'utils'
|
||||
import { parseVariables } from './variable'
|
||||
|
||||
const emailRegex =
|
||||
@ -20,41 +20,41 @@ const urlRegex =
|
||||
|
||||
export const isInputValid = (
|
||||
inputValue: string,
|
||||
type: InputStepType
|
||||
type: InputBlockType
|
||||
): boolean => {
|
||||
switch (type) {
|
||||
case InputStepType.EMAIL:
|
||||
case InputBlockType.EMAIL:
|
||||
return emailRegex.test(inputValue)
|
||||
case InputStepType.PHONE:
|
||||
case InputBlockType.PHONE:
|
||||
return isPossiblePhoneNumber(inputValue)
|
||||
case InputStepType.URL:
|
||||
case InputBlockType.URL:
|
||||
return urlRegex.test(inputValue)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const stepCanBeRetried = (
|
||||
step: Step
|
||||
): step is EmailInputStep | UrlInputStep | PhoneNumberInputStep =>
|
||||
isInputStep(step) && 'retryMessageContent' in step.options
|
||||
export const blockCanBeRetried = (
|
||||
block: Block
|
||||
): block is EmailInputBlock | UrlInputBlock | PhoneNumberInputBlock =>
|
||||
isInputBlock(block) && 'retryMessageContent' in block.options
|
||||
|
||||
export const parseRetryStep = (
|
||||
step: EmailInputStep | UrlInputStep | PhoneNumberInputStep,
|
||||
export const parseRetryBlock = (
|
||||
block: EmailInputBlock | UrlInputBlock | PhoneNumberInputBlock,
|
||||
variables: Variable[],
|
||||
createEdge: (edge: Edge) => void
|
||||
): BubbleStep => {
|
||||
const content = parseVariables(variables)(step.options.retryMessageContent)
|
||||
const newStepId = step.id + Math.random() * 1000
|
||||
): BubbleBlock => {
|
||||
const content = parseVariables(variables)(block.options.retryMessageContent)
|
||||
const newBlockId = block.id + Math.random() * 1000
|
||||
const newEdge: Edge = {
|
||||
id: (Math.random() * 1000).toString(),
|
||||
from: { stepId: newStepId, blockId: step.blockId },
|
||||
to: { blockId: step.blockId, stepId: step.id },
|
||||
from: { blockId: newBlockId, groupId: block.groupId },
|
||||
to: { groupId: block.groupId, blockId: block.id },
|
||||
}
|
||||
createEdge(newEdge)
|
||||
return {
|
||||
blockId: step.blockId,
|
||||
id: newStepId,
|
||||
type: BubbleStepType.TEXT,
|
||||
groupId: block.groupId,
|
||||
id: newBlockId,
|
||||
type: BubbleBlockType.TEXT,
|
||||
content: {
|
||||
html: `<div>${content}</div>`,
|
||||
richText: [],
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { Log } from 'db'
|
||||
import {
|
||||
IntegrationStep,
|
||||
IntegrationStepType,
|
||||
GoogleSheetsStep,
|
||||
IntegrationBlock,
|
||||
IntegrationBlockType,
|
||||
GoogleSheetsBlock,
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
Variable,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
Cell,
|
||||
GoogleSheetsGetOptions,
|
||||
GoogleAnalyticsStep,
|
||||
WebhookStep,
|
||||
SendEmailStep,
|
||||
ZapierStep,
|
||||
GoogleAnalyticsBlock,
|
||||
WebhookBlock,
|
||||
SendEmailBlock,
|
||||
ZapierBlock,
|
||||
ResultValues,
|
||||
Block,
|
||||
Group,
|
||||
VariableWithValue,
|
||||
MakeComStep,
|
||||
PabblyConnectStep,
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
} from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import { byId, sendRequest } from 'utils'
|
||||
@ -27,12 +27,12 @@ import { parseVariables, parseVariablesInObject } from './variable'
|
||||
type IntegrationContext = {
|
||||
apiHost: string
|
||||
typebotId: string
|
||||
groupId: string
|
||||
blockId: string
|
||||
stepId: string
|
||||
isPreview: boolean
|
||||
variables: Variable[]
|
||||
resultValues: ResultValues
|
||||
blocks: Block[]
|
||||
groups: Group[]
|
||||
resultId?: string
|
||||
updateVariables: (variables: VariableWithValue[]) => void
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
@ -40,55 +40,55 @@ type IntegrationContext = {
|
||||
}
|
||||
|
||||
export const executeIntegration = ({
|
||||
step,
|
||||
block,
|
||||
context,
|
||||
}: {
|
||||
step: IntegrationStep
|
||||
block: IntegrationBlock
|
||||
context: IntegrationContext
|
||||
}): Promise<string | undefined> => {
|
||||
switch (step.type) {
|
||||
case IntegrationStepType.GOOGLE_SHEETS:
|
||||
return executeGoogleSheetIntegration(step, context)
|
||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(step, context)
|
||||
case IntegrationStepType.ZAPIER:
|
||||
case IntegrationStepType.MAKE_COM:
|
||||
case IntegrationStepType.PABBLY_CONNECT:
|
||||
case IntegrationStepType.WEBHOOK:
|
||||
return executeWebhook(step, context)
|
||||
case IntegrationStepType.EMAIL:
|
||||
return sendEmail(step, context)
|
||||
switch (block.type) {
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return executeGoogleSheetIntegration(block, context)
|
||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||
return executeGoogleAnalyticsIntegration(block, context)
|
||||
case IntegrationBlockType.ZAPIER:
|
||||
case IntegrationBlockType.MAKE_COM:
|
||||
case IntegrationBlockType.PABBLY_CONNECT:
|
||||
case IntegrationBlockType.WEBHOOK:
|
||||
return executeWebhook(block, context)
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return sendEmail(block, context)
|
||||
}
|
||||
}
|
||||
|
||||
export const executeGoogleAnalyticsIntegration = async (
|
||||
step: GoogleAnalyticsStep,
|
||||
block: GoogleAnalyticsBlock,
|
||||
{ variables }: IntegrationContext
|
||||
) => {
|
||||
if (!step.options?.trackingId) return step.outgoingEdgeId
|
||||
if (!block.options?.trackingId) return block.outgoingEdgeId
|
||||
const { default: initGoogleAnalytics } = await import('../../lib/gtag')
|
||||
await initGoogleAnalytics(step.options.trackingId)
|
||||
sendGaEvent(parseVariablesInObject(step.options, variables))
|
||||
return step.outgoingEdgeId
|
||||
await initGoogleAnalytics(block.options.trackingId)
|
||||
sendGaEvent(parseVariablesInObject(block.options, variables))
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeGoogleSheetIntegration = async (
|
||||
step: GoogleSheetsStep,
|
||||
block: GoogleSheetsBlock,
|
||||
context: IntegrationContext
|
||||
) => {
|
||||
if (!('action' in step.options)) return step.outgoingEdgeId
|
||||
switch (step.options.action) {
|
||||
if (!('action' in block.options)) return block.outgoingEdgeId
|
||||
switch (block.options.action) {
|
||||
case GoogleSheetsAction.INSERT_ROW:
|
||||
await insertRowInGoogleSheets(step.options, context)
|
||||
await insertRowInGoogleSheets(block.options, context)
|
||||
break
|
||||
case GoogleSheetsAction.UPDATE_ROW:
|
||||
await updateRowInGoogleSheets(step.options, context)
|
||||
await updateRowInGoogleSheets(block.options, context)
|
||||
break
|
||||
case GoogleSheetsAction.GET:
|
||||
await getRowFromGoogleSheets(step.options, context)
|
||||
await getRowFromGoogleSheets(block.options, context)
|
||||
break
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const insertRowInGoogleSheets = async (
|
||||
@ -216,10 +216,9 @@ const parseCellValues = (
|
||||
}, {})
|
||||
|
||||
const executeWebhook = async (
|
||||
step: WebhookStep | ZapierStep | MakeComStep | PabblyConnectStep,
|
||||
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock,
|
||||
{
|
||||
blockId,
|
||||
stepId,
|
||||
variables,
|
||||
updateVariableValue,
|
||||
updateVariables,
|
||||
@ -232,7 +231,7 @@ const executeWebhook = async (
|
||||
) => {
|
||||
const params = stringify({ resultId })
|
||||
const { data, error } = await sendRequest({
|
||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/steps/${stepId}/executeWebhook?${params}`,
|
||||
url: `${apiHost}/api/typebots/${typebotId}/blocks/${blockId}/executeWebhook?${params}`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
variables,
|
||||
@ -252,7 +251,7 @@ const executeWebhook = async (
|
||||
: 'Webhook successfuly executed',
|
||||
details: JSON.stringify(error ?? data, null, 2).substring(0, 1000),
|
||||
})
|
||||
const newVariables = step.options.responseVariableMapping.reduce<
|
||||
const newVariables = block.options.responseVariableMapping.reduce<
|
||||
VariableWithValue[]
|
||||
>((newVariables, varMapping) => {
|
||||
if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables
|
||||
@ -271,11 +270,11 @@ const executeWebhook = async (
|
||||
}
|
||||
}, [])
|
||||
updateVariables(newVariables)
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const sendEmail = async (
|
||||
step: SendEmailStep,
|
||||
block: SendEmailBlock,
|
||||
{ variables, apiHost, isPreview, onNewLog, resultId }: IntegrationContext
|
||||
) => {
|
||||
if (isPreview) {
|
||||
@ -284,9 +283,9 @@ const sendEmail = async (
|
||||
description: 'Emails are not sent in preview mode',
|
||||
details: null,
|
||||
})
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
const { options } = step
|
||||
const { options } = block
|
||||
const replyTo = parseVariables(variables)(options.replyTo)
|
||||
const { error } = await sendRequest({
|
||||
url: `${apiHost}/api/integrations/email?resultId=${resultId}`,
|
||||
@ -304,7 +303,7 @@ const sendEmail = async (
|
||||
onNewLog(
|
||||
parseLog(error, 'Succesfully sent an email', 'Failed to send an email')
|
||||
)
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const parseLog = (
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { LinkedTypebot } from 'contexts/TypebotContext'
|
||||
import { Log } from 'db'
|
||||
import {
|
||||
LogicStep,
|
||||
LogicStepType,
|
||||
LogicBlock,
|
||||
LogicBlockType,
|
||||
LogicalOperator,
|
||||
ConditionStep,
|
||||
ConditionBlock,
|
||||
Variable,
|
||||
ComparisonOperators,
|
||||
SetVariableStep,
|
||||
RedirectStep,
|
||||
SetVariableBlock,
|
||||
RedirectBlock,
|
||||
Comparison,
|
||||
CodeStep,
|
||||
TypebotLinkStep,
|
||||
CodeBlock,
|
||||
TypebotLinkBlock,
|
||||
PublicTypebot,
|
||||
Typebot,
|
||||
Edge,
|
||||
@ -42,51 +42,53 @@ type LogicContext = {
|
||||
}
|
||||
|
||||
export const executeLogic = async (
|
||||
step: LogicStep,
|
||||
block: LogicBlock,
|
||||
context: LogicContext
|
||||
): Promise<{
|
||||
nextEdgeId?: EdgeId
|
||||
linkedTypebot?: PublicTypebot | LinkedTypebot
|
||||
}> => {
|
||||
switch (step.type) {
|
||||
case LogicStepType.SET_VARIABLE:
|
||||
return { nextEdgeId: executeSetVariable(step, context) }
|
||||
case LogicStepType.CONDITION:
|
||||
return { nextEdgeId: executeCondition(step, context) }
|
||||
case LogicStepType.REDIRECT:
|
||||
return { nextEdgeId: executeRedirect(step, context) }
|
||||
case LogicStepType.CODE:
|
||||
return { nextEdgeId: await executeCode(step, context) }
|
||||
case LogicStepType.TYPEBOT_LINK:
|
||||
return await executeTypebotLink(step, context)
|
||||
switch (block.type) {
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return { nextEdgeId: executeSetVariable(block, context) }
|
||||
case LogicBlockType.CONDITION:
|
||||
return { nextEdgeId: executeCondition(block, context) }
|
||||
case LogicBlockType.REDIRECT:
|
||||
return { nextEdgeId: executeRedirect(block, context) }
|
||||
case LogicBlockType.CODE:
|
||||
return { nextEdgeId: await executeCode(block, context) }
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return await executeTypebotLink(block, context)
|
||||
}
|
||||
}
|
||||
|
||||
const executeSetVariable = (
|
||||
step: SetVariableStep,
|
||||
block: SetVariableBlock,
|
||||
{ typebot: { variables }, updateVariableValue, updateVariables }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.variableId) return step.outgoingEdgeId
|
||||
const evaluatedExpression = step.options.expressionToEvaluate
|
||||
? evaluateExpression(variables)(step.options.expressionToEvaluate)
|
||||
if (!block.options?.variableId) return block.outgoingEdgeId
|
||||
const evaluatedExpression = block.options.expressionToEvaluate
|
||||
? evaluateExpression(variables)(block.options.expressionToEvaluate)
|
||||
: undefined
|
||||
const existingVariable = variables.find(byId(step.options.variableId))
|
||||
if (!existingVariable) return step.outgoingEdgeId
|
||||
const existingVariable = variables.find(byId(block.options.variableId))
|
||||
if (!existingVariable) return block.outgoingEdgeId
|
||||
updateVariableValue(existingVariable.id, evaluatedExpression)
|
||||
updateVariables([{ ...existingVariable, value: evaluatedExpression }])
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCondition = (
|
||||
step: ConditionStep,
|
||||
block: ConditionBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
const { content } = step.items[0]
|
||||
const { content } = block.items[0]
|
||||
const isConditionPassed =
|
||||
content.logicalOperator === LogicalOperator.AND
|
||||
? content.comparisons.every(executeComparison(variables))
|
||||
: content.comparisons.some(executeComparison(variables))
|
||||
return isConditionPassed ? step.items[0].outgoingEdgeId : step.outgoingEdgeId
|
||||
return isConditionPassed
|
||||
? block.items[0].outgoingEdgeId
|
||||
: block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeComparison =
|
||||
@ -122,14 +124,14 @@ const executeComparison =
|
||||
}
|
||||
|
||||
const executeRedirect = (
|
||||
step: RedirectStep,
|
||||
block: RedirectBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
): EdgeId | undefined => {
|
||||
if (!step.options?.url) return step.outgoingEdgeId
|
||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(step.options.url))
|
||||
if (!block.options?.url) return block.outgoingEdgeId
|
||||
const formattedUrl = sanitizeUrl(parseVariables(variables)(block.options.url))
|
||||
const isEmbedded = window.parent && window.location !== window.top?.location
|
||||
if (isEmbedded) {
|
||||
if (!step.options.isNewTab)
|
||||
if (!block.options.isNewTab)
|
||||
return ((window.top as Window).location.href = formattedUrl)
|
||||
|
||||
try {
|
||||
@ -145,30 +147,30 @@ const executeRedirect = (
|
||||
)
|
||||
}
|
||||
} else {
|
||||
window.open(formattedUrl, step.options.isNewTab ? '_blank' : '_self')
|
||||
window.open(formattedUrl, block.options.isNewTab ? '_blank' : '_self')
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeCode = async (
|
||||
step: CodeStep,
|
||||
block: CodeBlock,
|
||||
{ typebot: { variables } }: LogicContext
|
||||
) => {
|
||||
if (!step.options.content) return
|
||||
if (!block.options.content) return
|
||||
const func = Function(
|
||||
...variables.map((v) => v.id),
|
||||
parseVariables(variables, { fieldToParse: 'id' })(step.options.content)
|
||||
parseVariables(variables, { fieldToParse: 'id' })(block.options.content)
|
||||
)
|
||||
try {
|
||||
await func(...variables.map((v) => v.value))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return step.outgoingEdgeId
|
||||
return block.outgoingEdgeId
|
||||
}
|
||||
|
||||
const executeTypebotLink = async (
|
||||
step: TypebotLinkStep,
|
||||
block: TypebotLinkBlock,
|
||||
context: LogicContext
|
||||
): Promise<{
|
||||
nextEdgeId?: EdgeId
|
||||
@ -184,10 +186,10 @@ const executeTypebotLink = async (
|
||||
currentTypebotId,
|
||||
} = context
|
||||
const linkedTypebot = (
|
||||
step.options.typebotId === 'current'
|
||||
block.options.typebotId === 'current'
|
||||
? typebot
|
||||
: [typebot, ...linkedTypebots].find(byId(step.options.typebotId)) ??
|
||||
(await fetchAndInjectTypebot(step, context))
|
||||
: [typebot, ...linkedTypebots].find(byId(block.options.typebotId)) ??
|
||||
(await fetchAndInjectTypebot(block, context))
|
||||
) as PublicTypebot | LinkedTypebot | undefined
|
||||
if (!linkedTypebot) {
|
||||
onNewLog({
|
||||
@ -195,26 +197,26 @@ const executeTypebotLink = async (
|
||||
description: 'Failed to link typebot',
|
||||
details: '',
|
||||
})
|
||||
return { nextEdgeId: step.outgoingEdgeId }
|
||||
return { nextEdgeId: block.outgoingEdgeId }
|
||||
}
|
||||
if (step.outgoingEdgeId)
|
||||
if (block.outgoingEdgeId)
|
||||
pushEdgeIdInLinkedTypebotQueue({
|
||||
edgeId: step.outgoingEdgeId,
|
||||
edgeId: block.outgoingEdgeId,
|
||||
typebotId: currentTypebotId,
|
||||
})
|
||||
setCurrentTypebotId(
|
||||
'typebotId' in linkedTypebot ? linkedTypebot.typebotId : linkedTypebot.id
|
||||
)
|
||||
const nextBlockId =
|
||||
step.options.blockId ??
|
||||
linkedTypebot.blocks.find((b) => b.steps.some((s) => s.type === 'start'))
|
||||
const nextGroupId =
|
||||
block.options.groupId ??
|
||||
linkedTypebot.groups.find((b) => b.blocks.some((s) => s.type === 'start'))
|
||||
?.id
|
||||
if (!nextBlockId) return { nextEdgeId: step.outgoingEdgeId }
|
||||
if (!nextGroupId) return { nextEdgeId: block.outgoingEdgeId }
|
||||
const newEdge: Edge = {
|
||||
id: (Math.random() * 1000).toString(),
|
||||
from: { stepId: '', blockId: '' },
|
||||
from: { blockId: '', groupId: '' },
|
||||
to: {
|
||||
blockId: nextBlockId,
|
||||
groupId: nextGroupId,
|
||||
},
|
||||
}
|
||||
createEdge(newEdge)
|
||||
@ -228,15 +230,15 @@ const executeTypebotLink = async (
|
||||
}
|
||||
|
||||
const fetchAndInjectTypebot = async (
|
||||
step: TypebotLinkStep,
|
||||
block: TypebotLinkBlock,
|
||||
{ apiHost, injectLinkedTypebot, isPreview }: LogicContext
|
||||
): Promise<LinkedTypebot | undefined> => {
|
||||
const { data, error } = isPreview
|
||||
? await sendRequest<{ typebot: Typebot }>(
|
||||
`/api/typebots/${step.options.typebotId}`
|
||||
`/api/typebots/${block.options.typebotId}`
|
||||
)
|
||||
: await sendRequest<{ typebot: PublicTypebot }>(
|
||||
`${apiHost}/api/publicTypebots/${step.options.typebotId}`
|
||||
`${apiHost}/api/publicTypebots/${block.options.typebotId}`
|
||||
)
|
||||
if (!data || error) return
|
||||
return injectLinkedTypebot(data.typebot)
|
||||
|
@ -0,0 +1,33 @@
|
||||
DROP INDEX "Answer_resultId_blockId_stepId_key";
|
||||
|
||||
ALTER TABLE "Answer"
|
||||
RENAME COLUMN "stepId" TO "groupId";
|
||||
|
||||
ALTER TABLE "PublicTypebot"
|
||||
RENAME COLUMN "blocks" TO "groups";
|
||||
|
||||
ALTER TABLE "PublicTypebot"
|
||||
ALTER COLUMN groups TYPE JSONB USING to_json(groups);
|
||||
|
||||
ALTER TABLE "PublicTypebot"
|
||||
ALTER COLUMN edges TYPE JSONB USING to_json(edges);
|
||||
|
||||
ALTER TABLE "Typebot"
|
||||
RENAME COLUMN "blocks" TO "groups";
|
||||
|
||||
ALTER TABLE "Typebot"
|
||||
ALTER COLUMN groups TYPE JSONB USING to_json(groups);
|
||||
|
||||
ALTER TABLE "Typebot"
|
||||
ALTER COLUMN edges TYPE JSONB USING to_json(edges);
|
||||
|
||||
UPDATE "Typebot" t
|
||||
SET groups = REPLACE(REPLACE(REPLACE(t.groups::text, '"blockId":', '"groupId":'), '"steps":', '"blocks":'), '"stepId":', '"blockId":')::jsonb,
|
||||
edges = REPLACE(REPLACE(t.edges::text, '"blockId":', '"groupId":'), '"stepId":', '"blockId":')::jsonb;
|
||||
|
||||
UPDATE "PublicTypebot" t
|
||||
SET groups = REPLACE(REPLACE(REPLACE(t.groups::text, '"blockId":', '"groupId":'), '"steps":', '"blocks":'), '"stepId":', '"blockId":')::jsonb,
|
||||
edges = REPLACE(REPLACE(t.edges::text, '"blockId":', '"groupId":'), '"stepId":', '"blockId":')::jsonb;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Answer_resultId_blockId_groupId_key" ON "Answer"("resultId", "blockId", "groupId");
|
@ -169,9 +169,9 @@ model Typebot {
|
||||
results Result[]
|
||||
folderId String?
|
||||
folder DashboardFolder? @relation(fields: [folderId], references: [id])
|
||||
blocks Json[]
|
||||
groups Json
|
||||
variables Json[]
|
||||
edges Json[]
|
||||
edges Json
|
||||
theme Json
|
||||
settings Json
|
||||
publicId String? @unique
|
||||
@ -215,9 +215,9 @@ model PublicTypebot {
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
typebotId String @unique
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
blocks Json[]
|
||||
groups Json
|
||||
variables Json[]
|
||||
edges Json[]
|
||||
edges Json
|
||||
theme Json
|
||||
settings Json
|
||||
}
|
||||
@ -248,12 +248,12 @@ model Answer {
|
||||
createdAt DateTime @default(now())
|
||||
resultId String
|
||||
result Result @relation(fields: [resultId], references: [id], onDelete: Cascade)
|
||||
stepId String
|
||||
blockId String
|
||||
blockId String
|
||||
groupId String
|
||||
variableId String?
|
||||
content String
|
||||
|
||||
@@unique([resultId, blockId, stepId])
|
||||
@@unique([resultId, blockId, groupId])
|
||||
}
|
||||
|
||||
model Coupon {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Block, Edge, Settings, Theme, Variable } from './typebot'
|
||||
import { Group, Edge, Settings, Theme, Variable } from './typebot'
|
||||
import { PublicTypebot as PublicTypebotFromPrisma } from 'db'
|
||||
|
||||
export type PublicTypebot = Omit<
|
||||
PublicTypebotFromPrisma,
|
||||
| 'blocks'
|
||||
| 'groups'
|
||||
| 'theme'
|
||||
| 'settings'
|
||||
| 'variables'
|
||||
@ -11,7 +11,7 @@ export type PublicTypebot = Omit<
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
> & {
|
||||
blocks: Block[]
|
||||
groups: Group[]
|
||||
variables: Variable[]
|
||||
edges: Edge[]
|
||||
theme: Theme
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Result as ResultFromPrisma } from 'db'
|
||||
import { Answer, VariableWithValue } from '.'
|
||||
import { InputStepType } from './typebot/steps/shared'
|
||||
import { InputBlockType } from './typebot/blocks/shared'
|
||||
|
||||
export type Result = Omit<ResultFromPrisma, 'createdAt' | 'variables'> & {
|
||||
createdAt: string
|
||||
@ -16,8 +16,8 @@ export type ResultValues = Pick<
|
||||
|
||||
export type ResultHeaderCell = {
|
||||
label: string
|
||||
stepId?: string
|
||||
stepType?: InputStepType
|
||||
blockId?: string
|
||||
blockType?: InputBlockType
|
||||
isLong?: boolean
|
||||
variableId?: string
|
||||
}
|
||||
|
78
packages/models/src/typebot/blocks/blocks.ts
Normal file
78
packages/models/src/typebot/blocks/blocks.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
InputBlockOptions,
|
||||
IntegrationBlockOptions,
|
||||
Item,
|
||||
LogicBlockOptions,
|
||||
} from '.'
|
||||
import { BubbleBlock, bubbleBlockSchema } from './bubble'
|
||||
import { InputBlock, inputBlockSchema } from './input'
|
||||
import { IntegrationBlock, integrationBlockSchema } from './integration'
|
||||
import { ConditionBlock, LogicBlock, logicBlockSchema } from './logic'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
InputBlockType,
|
||||
IntegrationBlockType,
|
||||
LogicBlockType,
|
||||
blockBaseSchema,
|
||||
} from './shared'
|
||||
|
||||
export type DraggableBlock =
|
||||
| BubbleBlock
|
||||
| InputBlock
|
||||
| LogicBlock
|
||||
| IntegrationBlock
|
||||
|
||||
export type BlockType =
|
||||
| 'start'
|
||||
| BubbleBlockType
|
||||
| InputBlockType
|
||||
| LogicBlockType
|
||||
| IntegrationBlockType
|
||||
|
||||
export type DraggableBlockType =
|
||||
| BubbleBlockType
|
||||
| InputBlockType
|
||||
| LogicBlockType
|
||||
| IntegrationBlockType
|
||||
|
||||
export type BlockWithOptions =
|
||||
| InputBlock
|
||||
| Exclude<LogicBlock, ConditionBlock>
|
||||
| IntegrationBlock
|
||||
|
||||
export type BlockWithOptionsType =
|
||||
| InputBlockType
|
||||
| Exclude<LogicBlockType, LogicBlockType.CONDITION>
|
||||
| IntegrationBlockType
|
||||
|
||||
export type BlockOptions =
|
||||
| InputBlockOptions
|
||||
| LogicBlockOptions
|
||||
| IntegrationBlockOptions
|
||||
|
||||
export type BlockWithItems = Omit<Block, 'items'> & { items: Item[] }
|
||||
|
||||
export type BlockBase = z.infer<typeof blockBaseSchema>
|
||||
|
||||
const startBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.literal('start'),
|
||||
label: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type StartBlock = z.infer<typeof startBlockSchema>
|
||||
|
||||
export type BlockIndices = {
|
||||
groupIndex: number
|
||||
blockIndex: number
|
||||
}
|
||||
|
||||
export const blockSchema = startBlockSchema
|
||||
.or(bubbleBlockSchema)
|
||||
.or(inputBlockSchema)
|
||||
.or(logicBlockSchema)
|
||||
.or(integrationBlockSchema)
|
||||
|
||||
export type Block = z.infer<typeof blockSchema>
|
18
packages/models/src/typebot/blocks/bubble/bubble.ts
Normal file
18
packages/models/src/typebot/blocks/bubble/bubble.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
import { embedBubbleContentSchema, embedBubbleBlockSchema } from './embed'
|
||||
import { imageBubbleContentSchema, imageBubbleBlockSchema } from './image'
|
||||
import { textBubbleContentSchema, textBubbleBlockSchema } from './text'
|
||||
import { videoBubbleContentSchema, videoBubbleBlockSchema } from './video'
|
||||
|
||||
export const bubbleBlockContentSchema = textBubbleContentSchema
|
||||
.or(imageBubbleContentSchema)
|
||||
.or(videoBubbleContentSchema)
|
||||
.or(embedBubbleContentSchema)
|
||||
|
||||
export const bubbleBlockSchema = textBubbleBlockSchema
|
||||
.or(imageBubbleBlockSchema)
|
||||
.or(videoBubbleBlockSchema)
|
||||
.or(embedBubbleBlockSchema)
|
||||
|
||||
export type BubbleBlock = z.infer<typeof bubbleBlockSchema>
|
||||
export type BubbleBlockContent = z.infer<typeof bubbleBlockContentSchema>
|
@ -1,4 +1,4 @@
|
||||
import { stepBaseSchema, BubbleStepType } from '../shared'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const embedBubbleContentSchema = z.object({
|
||||
@ -6,14 +6,14 @@ export const embedBubbleContentSchema = z.object({
|
||||
height: z.number(),
|
||||
})
|
||||
|
||||
export const embedBubbleStepSchema = stepBaseSchema.and(
|
||||
export const embedBubbleBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([BubbleStepType.EMBED]),
|
||||
type: z.enum([BubbleBlockType.EMBED]),
|
||||
content: embedBubbleContentSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultEmbedBubbleContent: EmbedBubbleContent = { height: 400 }
|
||||
|
||||
export type EmbedBubbleStep = z.infer<typeof embedBubbleStepSchema>
|
||||
export type EmbedBubbleBlock = z.infer<typeof embedBubbleBlockSchema>
|
||||
export type EmbedBubbleContent = z.infer<typeof embedBubbleContentSchema>
|
@ -1,18 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
import { stepBaseSchema, BubbleStepType } from '../shared'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
|
||||
export const imageBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
||||
})
|
||||
|
||||
export const imageBubbleStepSchema = stepBaseSchema.and(
|
||||
export const imageBubbleBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([BubbleStepType.IMAGE]),
|
||||
type: z.enum([BubbleBlockType.IMAGE]),
|
||||
content: imageBubbleContentSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultImageBubbleContent: ImageBubbleContent = {}
|
||||
|
||||
export type ImageBubbleStep = z.infer<typeof imageBubbleStepSchema>
|
||||
export type ImageBubbleBlock = z.infer<typeof imageBubbleBlockSchema>
|
||||
export type ImageBubbleContent = z.infer<typeof imageBubbleContentSchema>
|
@ -1,4 +1,4 @@
|
||||
import { stepBaseSchema, BubbleStepType } from '../shared'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
|
||||
export const defaultTextBubbleContent: TextBubbleContent = {
|
||||
@ -15,11 +15,11 @@ export const textBubbleContentSchema = z.object({
|
||||
|
||||
export type TextBubbleContent = z.infer<typeof textBubbleContentSchema>
|
||||
|
||||
export const textBubbleStepSchema = stepBaseSchema.and(
|
||||
export const textBubbleBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([BubbleStepType.TEXT]),
|
||||
type: z.enum([BubbleBlockType.TEXT]),
|
||||
content: textBubbleContentSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export type TextBubbleStep = z.infer<typeof textBubbleStepSchema>
|
||||
export type TextBubbleBlock = z.infer<typeof textBubbleBlockSchema>
|
@ -1,4 +1,4 @@
|
||||
import { stepBaseSchema, BubbleStepType } from '../shared'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
|
||||
export enum VideoBubbleContentType {
|
||||
@ -13,14 +13,14 @@ export const videoBubbleContentSchema = z.object({
|
||||
type: z.nativeEnum(VideoBubbleContentType).optional(),
|
||||
})
|
||||
|
||||
export const videoBubbleStepSchema = stepBaseSchema.and(
|
||||
export const videoBubbleBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([BubbleStepType.VIDEO]),
|
||||
type: z.enum([BubbleBlockType.VIDEO]),
|
||||
content: videoBubbleContentSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultVideoBubbleContent: VideoBubbleContent = {}
|
||||
|
||||
export type VideoBubbleStep = z.infer<typeof videoBubbleStepSchema>
|
||||
export type VideoBubbleBlock = z.infer<typeof videoBubbleBlockSchema>
|
||||
export type VideoBubbleContent = z.infer<typeof videoBubbleContentSchema>
|
@ -1,4 +1,4 @@
|
||||
export * from './steps'
|
||||
export * from './blocks'
|
||||
export * from './bubble'
|
||||
export * from './input'
|
||||
export * from './logic'
|
@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
stepBaseSchema,
|
||||
InputStepType,
|
||||
blockBaseSchema,
|
||||
InputBlockType,
|
||||
defaultButtonLabel,
|
||||
optionBaseSchema,
|
||||
itemBaseSchema,
|
||||
@ -20,9 +20,9 @@ export const defaultChoiceInputOptions: ChoiceInputOptions = {
|
||||
isMultipleChoice: false,
|
||||
}
|
||||
|
||||
export const choiceInputSchema = stepBaseSchema.and(
|
||||
export const choiceInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.CHOICE]),
|
||||
type: z.enum([InputBlockType.CHOICE]),
|
||||
items: z.array(z.any()),
|
||||
options: choiceInputOptionsSchema,
|
||||
})
|
||||
@ -36,5 +36,5 @@ export const buttonItemSchema = itemBaseSchema.and(
|
||||
)
|
||||
|
||||
export type ButtonItem = z.infer<typeof buttonItemSchema>
|
||||
export type ChoiceInputStep = z.infer<typeof choiceInputSchema>
|
||||
export type ChoiceInputBlock = z.infer<typeof choiceInputSchema>
|
||||
export type ChoiceInputOptions = z.infer<typeof choiceInputOptionsSchema>
|
@ -1,7 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
stepBaseSchema,
|
||||
InputStepType,
|
||||
blockBaseSchema,
|
||||
InputBlockType,
|
||||
defaultButtonLabel,
|
||||
optionBaseSchema,
|
||||
} from '../shared'
|
||||
@ -18,9 +18,9 @@ export const dateInputOptionsSchema = optionBaseSchema.and(
|
||||
})
|
||||
)
|
||||
|
||||
export const dateInputSchema = stepBaseSchema.and(
|
||||
export const dateInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.DATE]),
|
||||
type: z.enum([InputBlockType.DATE]),
|
||||
options: dateInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -31,5 +31,5 @@ export const defaultDateInputOptions: DateInputOptions = {
|
||||
labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' },
|
||||
}
|
||||
|
||||
export type DateInputStep = z.infer<typeof dateInputSchema>
|
||||
export type DateInputBlock = z.infer<typeof dateInputSchema>
|
||||
export type DateInputOptions = z.infer<typeof dateInputOptionsSchema>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
@ -15,9 +15,9 @@ export const emailInputOptionsSchema = optionBaseSchema
|
||||
})
|
||||
)
|
||||
|
||||
export const emailInputSchema = stepBaseSchema.and(
|
||||
export const emailInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.EMAIL]),
|
||||
type: z.enum([InputBlockType.EMAIL]),
|
||||
options: emailInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -31,5 +31,5 @@ export const defaultEmailInputOptions: EmailInputOptions = {
|
||||
"This email doesn't seem to be valid. Can you type it again?",
|
||||
}
|
||||
|
||||
export type EmailInputStep = z.infer<typeof emailInputSchema>
|
||||
export type EmailInputBlock = z.infer<typeof emailInputSchema>
|
||||
export type EmailInputOptions = z.infer<typeof emailInputOptionsSchema>
|
@ -7,15 +7,15 @@ import { numberInputOptionsSchema, numberInputSchema } from './number'
|
||||
import { paymentInputOptionsSchema, paymentInputSchema } from './payment'
|
||||
import {
|
||||
phoneNumberInputOptionsSchema,
|
||||
phoneNumberInputStepSchema,
|
||||
phoneNumberInputBlockSchema,
|
||||
} from './phone'
|
||||
import { ratingInputOptionsSchema, ratingInputStepSchema } from './rating'
|
||||
import { ratingInputOptionsSchema, ratingInputBlockSchema } from './rating'
|
||||
import { textInputOptionsSchema, textInputSchema } from './text'
|
||||
import { urlInputOptionsSchema, urlInputSchema } from './url'
|
||||
|
||||
export type OptionBase = z.infer<typeof optionBaseSchema>
|
||||
|
||||
export const inputStepOptionsSchema = textInputOptionsSchema
|
||||
export const inputBlockOptionsSchema = textInputOptionsSchema
|
||||
.or(choiceInputOptionsSchema)
|
||||
.or(emailInputOptionsSchema)
|
||||
.or(numberInputOptionsSchema)
|
||||
@ -25,15 +25,15 @@ export const inputStepOptionsSchema = textInputOptionsSchema
|
||||
.or(paymentInputOptionsSchema)
|
||||
.or(ratingInputOptionsSchema)
|
||||
|
||||
export const inputStepSchema = textInputSchema
|
||||
export const inputBlockSchema = textInputSchema
|
||||
.or(numberInputSchema)
|
||||
.or(emailInputSchema)
|
||||
.or(urlInputSchema)
|
||||
.or(dateInputSchema)
|
||||
.or(phoneNumberInputStepSchema)
|
||||
.or(phoneNumberInputBlockSchema)
|
||||
.or(choiceInputSchema)
|
||||
.or(paymentInputSchema)
|
||||
.or(ratingInputStepSchema)
|
||||
.or(ratingInputBlockSchema)
|
||||
|
||||
export type InputStep = z.infer<typeof inputStepSchema>
|
||||
export type InputStepOptions = z.infer<typeof inputStepOptionsSchema>
|
||||
export type InputBlock = z.infer<typeof inputBlockSchema>
|
||||
export type InputBlockOptions = z.infer<typeof inputBlockOptionsSchema>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
@ -17,9 +17,9 @@ export const numberInputOptionsSchema = optionBaseSchema
|
||||
})
|
||||
)
|
||||
|
||||
export const numberInputSchema = stepBaseSchema.and(
|
||||
export const numberInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.NUMBER]),
|
||||
type: z.enum([InputBlockType.NUMBER]),
|
||||
options: numberInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -28,5 +28,5 @@ export const defaultNumberInputOptions: NumberInputOptions = {
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type a number...' },
|
||||
}
|
||||
|
||||
export type NumberInputStep = z.infer<typeof numberInputSchema>
|
||||
export type NumberInputBlock = z.infer<typeof numberInputSchema>
|
||||
export type NumberInputOptions = z.infer<typeof numberInputOptionsSchema>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { InputStepType, optionBaseSchema, stepBaseSchema } from '../shared'
|
||||
import { InputBlockType, optionBaseSchema, blockBaseSchema } from '../shared'
|
||||
|
||||
export type CreditCardDetails = {
|
||||
number: string
|
||||
@ -32,9 +32,9 @@ export const paymentInputOptionsSchema = optionBaseSchema.and(
|
||||
})
|
||||
)
|
||||
|
||||
export const paymentInputSchema = stepBaseSchema.and(
|
||||
export const paymentInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.PAYMENT]),
|
||||
type: z.enum([InputBlockType.PAYMENT]),
|
||||
options: paymentInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -45,5 +45,5 @@ export const defaultPaymentInputOptions: PaymentInputOptions = {
|
||||
currency: 'USD',
|
||||
}
|
||||
|
||||
export type PaymentInputStep = z.infer<typeof paymentInputSchema>
|
||||
export type PaymentInputBlock = z.infer<typeof paymentInputSchema>
|
||||
export type PaymentInputOptions = z.infer<typeof paymentInputOptionsSchema>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
@ -16,9 +16,9 @@ export const phoneNumberInputOptionsSchema = optionBaseSchema
|
||||
})
|
||||
)
|
||||
|
||||
export const phoneNumberInputStepSchema = stepBaseSchema.and(
|
||||
export const phoneNumberInputBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.PHONE]),
|
||||
type: z.enum([InputBlockType.PHONE]),
|
||||
options: phoneNumberInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -32,7 +32,7 @@ export const defaultPhoneInputOptions: PhoneNumberInputOptions = {
|
||||
"This phone number doesn't seem to be valid. Can you type it again?",
|
||||
}
|
||||
|
||||
export type PhoneNumberInputStep = z.infer<typeof phoneNumberInputStepSchema>
|
||||
export type PhoneNumberInputBlock = z.infer<typeof phoneNumberInputBlockSchema>
|
||||
export type PhoneNumberInputOptions = z.infer<
|
||||
typeof phoneNumberInputOptionsSchema
|
||||
>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
|
||||
export const defaultRatingInputOptions: RatingInputOptions = {
|
||||
@ -30,12 +30,12 @@ export const ratingInputOptionsSchema = optionBaseSchema.and(
|
||||
})
|
||||
)
|
||||
|
||||
export const ratingInputStepSchema = stepBaseSchema.and(
|
||||
export const ratingInputBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.literal(InputStepType.RATING),
|
||||
type: z.literal(InputBlockType.RATING),
|
||||
options: ratingInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export type RatingInputStep = z.infer<typeof ratingInputStepSchema>
|
||||
export type RatingInputBlock = z.infer<typeof ratingInputBlockSchema>
|
||||
export type RatingInputOptions = z.infer<typeof ratingInputOptionsSchema>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
|
||||
export const textInputOptionsBaseSchema = z.object({
|
||||
@ -26,12 +26,12 @@ export const defaultTextInputOptions: TextInputOptions = {
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type your answer...' },
|
||||
}
|
||||
|
||||
export const textInputSchema = stepBaseSchema.and(
|
||||
export const textInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.TEXT]),
|
||||
type: z.enum([InputBlockType.TEXT]),
|
||||
options: textInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export type TextInputStep = z.infer<typeof textInputSchema>
|
||||
export type TextInputBlock = z.infer<typeof textInputSchema>
|
||||
export type TextInputOptions = z.infer<typeof textInputOptionsSchema>
|
@ -1,9 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputStepType,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
stepBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
@ -15,9 +15,9 @@ export const urlInputOptionsSchema = optionBaseSchema
|
||||
})
|
||||
)
|
||||
|
||||
export const urlInputSchema = stepBaseSchema.and(
|
||||
export const urlInputSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([InputStepType.URL]),
|
||||
type: z.enum([InputBlockType.URL]),
|
||||
options: urlInputOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -31,5 +31,5 @@ export const defaultUrlInputOptions: UrlInputOptions = {
|
||||
"This URL doesn't seem to be valid. Can you type it again?",
|
||||
}
|
||||
|
||||
export type UrlInputStep = z.infer<typeof urlInputSchema>
|
||||
export type UrlInputBlock = z.infer<typeof urlInputSchema>
|
||||
export type UrlInputOptions = z.infer<typeof urlInputOptionsSchema>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const googleAnalyticsOptionsSchema = z.object({
|
||||
trackingId: z.string().optional(),
|
||||
@ -9,16 +9,16 @@ export const googleAnalyticsOptionsSchema = z.object({
|
||||
value: z.number().optional(),
|
||||
})
|
||||
|
||||
export const googleAnalyticsStepSchema = stepBaseSchema.and(
|
||||
export const googleAnalyticsBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.GOOGLE_ANALYTICS]),
|
||||
type: z.enum([IntegrationBlockType.GOOGLE_ANALYTICS]),
|
||||
options: googleAnalyticsOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {}
|
||||
|
||||
export type GoogleAnalyticsStep = z.infer<typeof googleAnalyticsStepSchema>
|
||||
export type GoogleAnalyticsBlock = z.infer<typeof googleAnalyticsBlockSchema>
|
||||
export type GoogleAnalyticsOptions = z.infer<
|
||||
typeof googleAnalyticsOptionsSchema
|
||||
>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export enum GoogleSheetsAction {
|
||||
GET = 'Get data from sheet',
|
||||
@ -53,16 +53,16 @@ export const googleSheetsOptionsSchema = googleSheetsOptionsBaseSchema
|
||||
.or(googleSheetsInsertRowOptionsSchema)
|
||||
.or(googleSheetsUpdateRowOptionsSchema)
|
||||
|
||||
export const googleSheetsStepSchema = stepBaseSchema.and(
|
||||
export const googleSheetsBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.GOOGLE_SHEETS]),
|
||||
type: z.enum([IntegrationBlockType.GOOGLE_SHEETS]),
|
||||
options: googleSheetsOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {}
|
||||
|
||||
export type GoogleSheetsStep = z.infer<typeof googleSheetsStepSchema>
|
||||
export type GoogleSheetsBlock = z.infer<typeof googleSheetsBlockSchema>
|
||||
export type GoogleSheetsOptions = z.infer<typeof googleSheetsOptionsSchema>
|
||||
export type GoogleSheetsOptionsBase = z.infer<
|
||||
typeof googleSheetsOptionsBaseSchema
|
@ -0,0 +1,32 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
googleAnalyticsOptionsSchema,
|
||||
googleAnalyticsBlockSchema,
|
||||
} from './googleAnalytics'
|
||||
import {
|
||||
googleSheetsOptionsSchema,
|
||||
googleSheetsBlockSchema,
|
||||
} from './googleSheets'
|
||||
import { makeComBlockSchema } from './makeCom'
|
||||
import { pabblyConnectBlockSchema } from './pabblyConnect'
|
||||
import { sendEmailOptionsSchema, sendEmailBlockSchema } from './sendEmail'
|
||||
import { webhookOptionsSchema, webhookBlockSchema } from './webhook'
|
||||
import { zapierBlockSchema } from './zapier'
|
||||
|
||||
const integrationBlockOptionsSchema = googleSheetsOptionsSchema
|
||||
.or(googleAnalyticsOptionsSchema)
|
||||
.or(webhookOptionsSchema)
|
||||
.or(sendEmailOptionsSchema)
|
||||
|
||||
export const integrationBlockSchema = googleSheetsBlockSchema
|
||||
.or(googleAnalyticsBlockSchema)
|
||||
.or(webhookBlockSchema)
|
||||
.or(sendEmailBlockSchema)
|
||||
.or(zapierBlockSchema)
|
||||
.or(makeComBlockSchema)
|
||||
.or(pabblyConnectBlockSchema)
|
||||
|
||||
export type IntegrationBlock = z.infer<typeof integrationBlockSchema>
|
||||
export type IntegrationBlockOptions = z.infer<
|
||||
typeof integrationBlockOptionsSchema
|
||||
>
|
13
packages/models/src/typebot/blocks/integration/makeCom.ts
Normal file
13
packages/models/src/typebot/blocks/integration/makeCom.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const makeComBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationBlockType.MAKE_COM]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type MakeComBlock = z.infer<typeof makeComBlockSchema>
|
@ -0,0 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const pabblyConnectBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationBlockType.PABBLY_CONNECT]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type PabblyConnectBlock = z.infer<typeof pabblyConnectBlockSchema>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const sendEmailOptionsSchema = z.object({
|
||||
credentialsId: z.string(),
|
||||
@ -11,9 +11,9 @@ export const sendEmailOptionsSchema = z.object({
|
||||
bcc: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const sendEmailStepSchema = stepBaseSchema.and(
|
||||
export const sendEmailBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.EMAIL]),
|
||||
type: z.enum([IntegrationBlockType.EMAIL]),
|
||||
options: sendEmailOptionsSchema,
|
||||
})
|
||||
)
|
||||
@ -23,5 +23,5 @@ export const defaultSendEmailOptions: SendEmailOptions = {
|
||||
recipients: [],
|
||||
}
|
||||
|
||||
export type SendEmailStep = z.infer<typeof sendEmailStepSchema>
|
||||
export type SendEmailBlock = z.infer<typeof sendEmailBlockSchema>
|
||||
export type SendEmailOptions = z.infer<typeof sendEmailOptionsSchema>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
const variableForTestSchema = z.object({
|
||||
id: z.string(),
|
||||
@ -20,9 +20,9 @@ export const webhookOptionsSchema = z.object({
|
||||
isCustomBody: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const webhookStepSchema = stepBaseSchema.and(
|
||||
export const webhookBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.WEBHOOK]),
|
||||
type: z.enum([IntegrationBlockType.WEBHOOK]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
@ -35,7 +35,7 @@ export const defaultWebhookOptions: Omit<WebhookOptions, 'webhookId'> = {
|
||||
isCustomBody: false,
|
||||
}
|
||||
|
||||
export type WebhookStep = z.infer<typeof webhookStepSchema>
|
||||
export type WebhookBlock = z.infer<typeof webhookBlockSchema>
|
||||
export type WebhookOptions = z.infer<typeof webhookOptionsSchema>
|
||||
export type ResponseVariableMapping = z.infer<
|
||||
typeof responseVariableMappingSchema
|
13
packages/models/src/typebot/blocks/integration/zapier.ts
Normal file
13
packages/models/src/typebot/blocks/integration/zapier.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationBlockType, blockBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const zapierBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationBlockType.ZAPIER]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type ZapierBlock = z.infer<typeof zapierBlockSchema>
|
@ -5,7 +5,7 @@ import { conditionItemSchema } from './logic'
|
||||
|
||||
export type ItemIndices = {
|
||||
blockIndex: number
|
||||
stepIndex: number
|
||||
groupIndex: number
|
||||
itemIndex: number
|
||||
}
|
||||
const itemScema = buttonItemSchema.or(conditionItemSchema)
|
@ -1,19 +1,19 @@
|
||||
import { z } from 'zod'
|
||||
import { LogicStepType, stepBaseSchema } from '../shared'
|
||||
import { LogicBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const codeOptionsSchema = z.object({
|
||||
name: z.string(),
|
||||
content: z.string().optional(),
|
||||
})
|
||||
|
||||
export const codeStepSchema = stepBaseSchema.and(
|
||||
export const codeBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([LogicStepType.CODE]),
|
||||
type: z.enum([LogicBlockType.CODE]),
|
||||
options: codeOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' }
|
||||
|
||||
export type CodeStep = z.infer<typeof codeStepSchema>
|
||||
export type CodeBlock = z.infer<typeof codeBlockSchema>
|
||||
export type CodeOptions = z.infer<typeof codeOptionsSchema>
|
@ -2,8 +2,8 @@ import { z } from 'zod'
|
||||
import {
|
||||
itemBaseSchema,
|
||||
ItemType,
|
||||
LogicStepType,
|
||||
stepBaseSchema,
|
||||
LogicBlockType,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
|
||||
export enum LogicalOperator {
|
||||
@ -39,9 +39,9 @@ export const conditionItemSchema = itemBaseSchema.and(
|
||||
})
|
||||
)
|
||||
|
||||
export const conditionStepSchema = stepBaseSchema.and(
|
||||
export const conditionBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([LogicStepType.CONDITION]),
|
||||
type: z.enum([LogicBlockType.CONDITION]),
|
||||
items: z.array(conditionItemSchema),
|
||||
options: z.object({}),
|
||||
})
|
||||
@ -54,5 +54,5 @@ export const defaultConditionContent: ConditionContent = {
|
||||
|
||||
export type ConditionItem = z.infer<typeof conditionItemSchema>
|
||||
export type Comparison = z.infer<typeof comparisonSchema>
|
||||
export type ConditionStep = z.infer<typeof conditionStepSchema>
|
||||
export type ConditionBlock = z.infer<typeof conditionBlockSchema>
|
||||
export type ConditionContent = z.infer<typeof conditionContentSchema>
|
20
packages/models/src/typebot/blocks/logic/logic.ts
Normal file
20
packages/models/src/typebot/blocks/logic/logic.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { z } from 'zod'
|
||||
import { codeOptionsSchema, codeBlockSchema } from './code'
|
||||
import { conditionBlockSchema } from './condition'
|
||||
import { redirectOptionsSchema, redirectBlockSchema } from './redirect'
|
||||
import { setVariableOptionsSchema, setVariableBlockSchema } from './setVariable'
|
||||
import { typebotLinkOptionsSchema, typebotLinkBlockSchema } from './typebotLink'
|
||||
|
||||
const logicBlockOptionsSchema = codeOptionsSchema
|
||||
.or(redirectOptionsSchema)
|
||||
.or(setVariableOptionsSchema)
|
||||
.or(typebotLinkOptionsSchema)
|
||||
|
||||
export const logicBlockSchema = codeBlockSchema
|
||||
.or(conditionBlockSchema)
|
||||
.or(redirectBlockSchema)
|
||||
.or(typebotLinkBlockSchema)
|
||||
.or(setVariableBlockSchema)
|
||||
|
||||
export type LogicBlock = z.infer<typeof logicBlockSchema>
|
||||
export type LogicBlockOptions = z.infer<typeof logicBlockOptionsSchema>
|
@ -1,19 +1,19 @@
|
||||
import { z } from 'zod'
|
||||
import { LogicStepType, stepBaseSchema } from '../shared'
|
||||
import { LogicBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const redirectOptionsSchema = z.object({
|
||||
url: z.string().optional(),
|
||||
isNewTab: z.boolean(),
|
||||
})
|
||||
|
||||
export const redirectStepSchema = stepBaseSchema.and(
|
||||
export const redirectBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([LogicStepType.REDIRECT]),
|
||||
type: z.enum([LogicBlockType.REDIRECT]),
|
||||
options: redirectOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }
|
||||
|
||||
export type RedirectStep = z.infer<typeof redirectStepSchema>
|
||||
export type RedirectBlock = z.infer<typeof redirectBlockSchema>
|
||||
export type RedirectOptions = z.infer<typeof redirectOptionsSchema>
|
@ -1,5 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { LogicStepType, stepBaseSchema } from '../shared'
|
||||
import { LogicBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const setVariableOptionsSchema = z.object({
|
||||
variableId: z.string().optional(),
|
||||
@ -7,14 +7,14 @@ export const setVariableOptionsSchema = z.object({
|
||||
isCode: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const setVariableStepSchema = stepBaseSchema.and(
|
||||
export const setVariableBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([LogicStepType.SET_VARIABLE]),
|
||||
type: z.enum([LogicBlockType.SET_VARIABLE]),
|
||||
options: setVariableOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultSetVariablesOptions: SetVariableOptions = {}
|
||||
|
||||
export type SetVariableStep = z.infer<typeof setVariableStepSchema>
|
||||
export type SetVariableBlock = z.infer<typeof setVariableBlockSchema>
|
||||
export type SetVariableOptions = z.infer<typeof setVariableOptionsSchema>
|
@ -1,19 +1,19 @@
|
||||
import { z } from 'zod'
|
||||
import { LogicStepType, stepBaseSchema } from '../shared'
|
||||
import { LogicBlockType, blockBaseSchema } from '../shared'
|
||||
|
||||
export const typebotLinkOptionsSchema = z.object({
|
||||
typebotId: z.string().optional(),
|
||||
blockId: z.string().optional(),
|
||||
groupId: z.string().optional(),
|
||||
})
|
||||
|
||||
export const typebotLinkStepSchema = stepBaseSchema.and(
|
||||
export const typebotLinkBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([LogicStepType.TYPEBOT_LINK]),
|
||||
type: z.enum([LogicBlockType.TYPEBOT_LINK]),
|
||||
options: typebotLinkOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultTypebotLinkOptions: TypebotLinkOptions = {}
|
||||
|
||||
export type TypebotLinkStep = z.infer<typeof typebotLinkStepSchema>
|
||||
export type TypebotLinkBlock = z.infer<typeof typebotLinkBlockSchema>
|
||||
export type TypebotLinkOptions = z.infer<typeof typebotLinkOptionsSchema>
|
@ -1,8 +1,8 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const stepBaseSchema = z.object({
|
||||
export const blockBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
blockId: z.string(),
|
||||
groupId: z.string(),
|
||||
outgoingEdgeId: z.string().optional(),
|
||||
})
|
||||
|
||||
@ -14,7 +14,7 @@ export const optionBaseSchema = z.object({
|
||||
|
||||
export const itemBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
stepId: z.string(),
|
||||
blockId: z.string(),
|
||||
outgoingEdgeId: z.string().optional(),
|
||||
})
|
||||
|
||||
@ -23,14 +23,14 @@ export enum ItemType {
|
||||
CONDITION,
|
||||
}
|
||||
|
||||
export enum BubbleStepType {
|
||||
export enum BubbleBlockType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
EMBED = 'embed',
|
||||
}
|
||||
|
||||
export enum InputStepType {
|
||||
export enum InputBlockType {
|
||||
TEXT = 'text input',
|
||||
NUMBER = 'number input',
|
||||
EMAIL = 'email input',
|
||||
@ -42,7 +42,7 @@ export enum InputStepType {
|
||||
RATING = 'rating input',
|
||||
}
|
||||
|
||||
export enum LogicStepType {
|
||||
export enum LogicBlockType {
|
||||
SET_VARIABLE = 'Set variable',
|
||||
CONDITION = 'Condition',
|
||||
REDIRECT = 'Redirect',
|
||||
@ -50,7 +50,7 @@ export enum LogicStepType {
|
||||
TYPEBOT_LINK = 'Typebot link',
|
||||
}
|
||||
|
||||
export enum IntegrationStepType {
|
||||
export enum IntegrationBlockType {
|
||||
GOOGLE_SHEETS = 'Google Sheets',
|
||||
GOOGLE_ANALYTICS = 'Google Analytics',
|
||||
WEBHOOK = 'Webhook',
|
@ -1,5 +1,5 @@
|
||||
export * from './typebot'
|
||||
export * from './steps'
|
||||
export * from './blocks'
|
||||
export * from './theme'
|
||||
export * from './settings'
|
||||
export * from './variable'
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import { embedBubbleContentSchema, embedBubbleStepSchema } from './embed'
|
||||
import { imageBubbleContentSchema, imageBubbleStepSchema } from './image'
|
||||
import { textBubbleContentSchema, textBubbleStepSchema } from './text'
|
||||
import { videoBubbleContentSchema, videoBubbleStepSchema } from './video'
|
||||
|
||||
export const bubbleStepContentSchema = textBubbleContentSchema
|
||||
.or(imageBubbleContentSchema)
|
||||
.or(videoBubbleContentSchema)
|
||||
.or(embedBubbleContentSchema)
|
||||
|
||||
export const bubbleStepSchema = textBubbleStepSchema
|
||||
.or(imageBubbleStepSchema)
|
||||
.or(videoBubbleStepSchema)
|
||||
.or(embedBubbleStepSchema)
|
||||
|
||||
export type BubbleStep = z.infer<typeof bubbleStepSchema>
|
||||
export type BubbleStepContent = z.infer<typeof bubbleStepContentSchema>
|
@ -1,32 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
googleAnalyticsOptionsSchema,
|
||||
googleAnalyticsStepSchema,
|
||||
} from './googleAnalytics'
|
||||
import {
|
||||
googleSheetsOptionsSchema,
|
||||
googleSheetsStepSchema,
|
||||
} from './googleSheets'
|
||||
import { makeComStepSchema } from './makeCom'
|
||||
import { pabblyConnectStepSchema } from './pabblyConnect'
|
||||
import { sendEmailOptionsSchema, sendEmailStepSchema } from './sendEmail'
|
||||
import { webhookOptionsSchema, webhookStepSchema } from './webhook'
|
||||
import { zapierStepSchema } from './zapier'
|
||||
|
||||
const integrationStepOptionsSchema = googleSheetsOptionsSchema
|
||||
.or(googleAnalyticsOptionsSchema)
|
||||
.or(webhookOptionsSchema)
|
||||
.or(sendEmailOptionsSchema)
|
||||
|
||||
export const integrationStepSchema = googleSheetsStepSchema
|
||||
.or(googleAnalyticsStepSchema)
|
||||
.or(webhookStepSchema)
|
||||
.or(sendEmailStepSchema)
|
||||
.or(zapierStepSchema)
|
||||
.or(makeComStepSchema)
|
||||
.or(pabblyConnectStepSchema)
|
||||
|
||||
export type IntegrationStep = z.infer<typeof integrationStepSchema>
|
||||
export type IntegrationStepOptions = z.infer<
|
||||
typeof integrationStepOptionsSchema
|
||||
>
|
@ -1,13 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const makeComStepSchema = stepBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.MAKE_COM]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type MakeComStep = z.infer<typeof makeComStepSchema>
|
@ -1,13 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const pabblyConnectStepSchema = stepBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.PABBLY_CONNECT]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type PabblyConnectStep = z.infer<typeof pabblyConnectStepSchema>
|
@ -1,13 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import { IntegrationStepType, stepBaseSchema } from '../shared'
|
||||
import { webhookOptionsSchema } from './webhook'
|
||||
|
||||
export const zapierStepSchema = stepBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationStepType.ZAPIER]),
|
||||
options: webhookOptionsSchema,
|
||||
webhookId: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type ZapierStep = z.infer<typeof zapierStepSchema>
|
@ -1,20 +0,0 @@
|
||||
import { z } from 'zod'
|
||||
import { codeOptionsSchema, codeStepSchema } from './code'
|
||||
import { conditionStepSchema } from './condition'
|
||||
import { redirectOptionsSchema, redirectStepSchema } from './redirect'
|
||||
import { setVariableOptionsSchema, setVariableStepSchema } from './setVariable'
|
||||
import { typebotLinkOptionsSchema, typebotLinkStepSchema } from './typebotLink'
|
||||
|
||||
const logicStepOptionsSchema = codeOptionsSchema
|
||||
.or(redirectOptionsSchema)
|
||||
.or(setVariableOptionsSchema)
|
||||
.or(typebotLinkOptionsSchema)
|
||||
|
||||
export const logicStepSchema = codeStepSchema
|
||||
.or(conditionStepSchema)
|
||||
.or(redirectStepSchema)
|
||||
.or(typebotLinkStepSchema)
|
||||
.or(setVariableStepSchema)
|
||||
|
||||
export type LogicStep = z.infer<typeof logicStepSchema>
|
||||
export type LogicStepOptions = z.infer<typeof logicStepOptionsSchema>
|
@ -1,74 +0,0 @@
|
||||
import {
|
||||
InputStepOptions,
|
||||
IntegrationStepOptions,
|
||||
Item,
|
||||
LogicStepOptions,
|
||||
} from '.'
|
||||
import { BubbleStep, bubbleStepSchema } from './bubble'
|
||||
import { InputStep, inputStepSchema } from './input'
|
||||
import { IntegrationStep, integrationStepSchema } from './integration'
|
||||
import { ConditionStep, LogicStep, logicStepSchema } from './logic'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
BubbleStepType,
|
||||
InputStepType,
|
||||
IntegrationStepType,
|
||||
LogicStepType,
|
||||
stepBaseSchema,
|
||||
} from './shared'
|
||||
|
||||
export type DraggableStep = BubbleStep | InputStep | LogicStep | IntegrationStep
|
||||
|
||||
export type StepType =
|
||||
| 'start'
|
||||
| BubbleStepType
|
||||
| InputStepType
|
||||
| LogicStepType
|
||||
| IntegrationStepType
|
||||
|
||||
export type DraggableStepType =
|
||||
| BubbleStepType
|
||||
| InputStepType
|
||||
| LogicStepType
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepWithOptions =
|
||||
| InputStep
|
||||
| Exclude<LogicStep, ConditionStep>
|
||||
| IntegrationStep
|
||||
|
||||
export type StepWithOptionsType =
|
||||
| InputStepType
|
||||
| Exclude<LogicStepType, LogicStepType.CONDITION>
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepOptions =
|
||||
| InputStepOptions
|
||||
| LogicStepOptions
|
||||
| IntegrationStepOptions
|
||||
|
||||
export type StepWithItems = Omit<Step, 'items'> & { items: Item[] }
|
||||
|
||||
export type StepBase = z.infer<typeof stepBaseSchema>
|
||||
|
||||
const startStepSchema = stepBaseSchema.and(
|
||||
z.object({
|
||||
type: z.literal('start'),
|
||||
label: z.string(),
|
||||
})
|
||||
)
|
||||
|
||||
export type StartStep = z.infer<typeof startStepSchema>
|
||||
|
||||
export type StepIndices = {
|
||||
blockIndex: number
|
||||
stepIndex: number
|
||||
}
|
||||
|
||||
export const stepSchema = startStepSchema
|
||||
.or(bubbleStepSchema)
|
||||
.or(inputStepSchema)
|
||||
.or(logicStepSchema)
|
||||
.or(integrationStepSchema)
|
||||
|
||||
export type Step = z.infer<typeof stepSchema>
|
@ -1,28 +1,28 @@
|
||||
import { z } from 'zod'
|
||||
import { settingsSchema } from './settings'
|
||||
import { stepSchema } from './steps'
|
||||
import { blockSchema } from './blocks'
|
||||
import { themeSchema } from './theme'
|
||||
import { variableSchema } from './variable'
|
||||
|
||||
const blockSchema = z.object({
|
||||
const groupSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
graphCoordinates: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
}),
|
||||
steps: z.array(stepSchema),
|
||||
blocks: z.array(blockSchema),
|
||||
})
|
||||
|
||||
const sourceSchema = z.object({
|
||||
groupId: z.string(),
|
||||
blockId: z.string(),
|
||||
stepId: z.string(),
|
||||
itemId: z.string().optional(),
|
||||
})
|
||||
|
||||
const targetSchema = z.object({
|
||||
blockId: z.string(),
|
||||
stepId: z.string().optional(),
|
||||
groupId: z.string(),
|
||||
blockId: z.string().optional(),
|
||||
})
|
||||
|
||||
const edgeSchema = z.object({
|
||||
@ -32,9 +32,10 @@ const edgeSchema = z.object({
|
||||
})
|
||||
|
||||
const typebotSchema = z.object({
|
||||
version: z.enum(['2']).optional(),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
blocks: z.array(blockSchema),
|
||||
groups: z.array(groupSchema),
|
||||
edges: z.array(edgeSchema),
|
||||
variables: z.array(variableSchema),
|
||||
theme: themeSchema,
|
||||
@ -53,4 +54,4 @@ export type Typebot = z.infer<typeof typebotSchema>
|
||||
export type Target = z.infer<typeof targetSchema>
|
||||
export type Source = z.infer<typeof sourceSchema>
|
||||
export type Edge = z.infer<typeof edgeSchema>
|
||||
export type Block = z.infer<typeof blockSchema>
|
||||
export type Group = z.infer<typeof groupSchema>
|
||||
|
@ -1,82 +1,82 @@
|
||||
import {
|
||||
Block,
|
||||
Group,
|
||||
Variable,
|
||||
InputStep,
|
||||
InputBlock,
|
||||
ResultHeaderCell,
|
||||
ResultWithAnswers,
|
||||
Answer,
|
||||
VariableWithValue,
|
||||
} from 'models'
|
||||
import { isInputStep, isDefined, byId } from './utils'
|
||||
import { isInputBlock, isDefined, byId } from './utils'
|
||||
|
||||
export const parseResultHeader = ({
|
||||
blocks,
|
||||
groups,
|
||||
variables,
|
||||
}: {
|
||||
blocks: Block[]
|
||||
groups: Group[]
|
||||
variables: Variable[]
|
||||
}): ResultHeaderCell[] => {
|
||||
const parsedBlocks = parseInputsResultHeader({ blocks, variables })
|
||||
const parsedGroups = parseInputsResultHeader({ groups, variables })
|
||||
return [
|
||||
{ label: 'Submitted at' },
|
||||
...parsedBlocks,
|
||||
...parseVariablesHeaders(variables, parsedBlocks),
|
||||
...parsedGroups,
|
||||
...parseVariablesHeaders(variables, parsedGroups),
|
||||
]
|
||||
}
|
||||
|
||||
const parseInputsResultHeader = ({
|
||||
blocks,
|
||||
groups,
|
||||
variables,
|
||||
}: {
|
||||
blocks: Block[]
|
||||
groups: Group[]
|
||||
variables: Variable[]
|
||||
}): ResultHeaderCell[] =>
|
||||
(
|
||||
blocks
|
||||
.flatMap((b) =>
|
||||
b.steps.map((s) => ({
|
||||
groups
|
||||
.flatMap((g) =>
|
||||
g.blocks.map((s) => ({
|
||||
...s,
|
||||
blockTitle: b.title,
|
||||
blockTitle: g.title,
|
||||
}))
|
||||
)
|
||||
.filter((step) => isInputStep(step)) as (InputStep & {
|
||||
.filter((block) => isInputBlock(block)) as (InputBlock & {
|
||||
blockTitle: string
|
||||
})[]
|
||||
).reduce<ResultHeaderCell[]>((headers, inputStep) => {
|
||||
).reduce<ResultHeaderCell[]>((headers, inputBlock) => {
|
||||
if (
|
||||
headers.find(
|
||||
(h) =>
|
||||
isDefined(h.variableId) &&
|
||||
h.variableId ===
|
||||
variables.find(byId(inputStep.options.variableId))?.id
|
||||
variables.find(byId(inputBlock.options.variableId))?.id
|
||||
)
|
||||
)
|
||||
return headers
|
||||
const matchedVariableName =
|
||||
inputStep.options.variableId &&
|
||||
variables.find(byId(inputStep.options.variableId))?.name
|
||||
inputBlock.options.variableId &&
|
||||
variables.find(byId(inputBlock.options.variableId))?.name
|
||||
|
||||
let label = matchedVariableName ?? inputStep.blockTitle
|
||||
let label = matchedVariableName ?? inputBlock.blockTitle
|
||||
const totalPrevious = headers.filter((h) => h.label.includes(label)).length
|
||||
if (totalPrevious > 0) label = label + ` (${totalPrevious})`
|
||||
return [
|
||||
...headers,
|
||||
{
|
||||
stepType: inputStep.type,
|
||||
stepId: inputStep.id,
|
||||
variableId: inputStep.options.variableId,
|
||||
blockType: inputBlock.type,
|
||||
blockId: inputBlock.id,
|
||||
variableId: inputBlock.options.variableId,
|
||||
label,
|
||||
isLong: 'isLong' in inputStep.options && inputStep.options.isLong,
|
||||
isLong: 'isLong' in inputBlock.options && inputBlock.options.isLong,
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const parseVariablesHeaders = (
|
||||
variables: Variable[],
|
||||
stepResultHeader: ResultHeaderCell[]
|
||||
blockResultHeader: ResultHeaderCell[]
|
||||
) =>
|
||||
variables.reduce<ResultHeaderCell[]>((headers, v) => {
|
||||
if (stepResultHeader.find((h) => h.variableId === v.id)) return headers
|
||||
if (blockResultHeader.find((h) => h.variableId === v.id)) return headers
|
||||
return [
|
||||
...headers,
|
||||
{
|
||||
@ -87,7 +87,7 @@ const parseVariablesHeaders = (
|
||||
}, [])
|
||||
|
||||
export const parseAnswers =
|
||||
({ blocks, variables }: { blocks: Block[]; variables: Variable[] }) =>
|
||||
({ groups, variables }: { groups: Group[]; variables: Variable[] }) =>
|
||||
({
|
||||
createdAt,
|
||||
answers,
|
||||
@ -95,7 +95,7 @@ export const parseAnswers =
|
||||
}: Pick<ResultWithAnswers, 'createdAt' | 'answers' | 'variables'>): {
|
||||
[key: string]: string
|
||||
} => {
|
||||
const header = parseResultHeader({ blocks, variables })
|
||||
const header = parseResultHeader({ groups, variables })
|
||||
return {
|
||||
submittedAt: createdAt,
|
||||
...[...answers, ...resultVariables].reduce<{
|
||||
@ -106,7 +106,7 @@ export const parseAnswers =
|
||||
const key = answer.variableId
|
||||
? header.find((cell) => cell.variableId === answer.variableId)
|
||||
?.label
|
||||
: header.find((cell) => cell.stepId === answer.stepId)?.label
|
||||
: header.find((cell) => cell.blockId === answer.blockId)?.label
|
||||
if (!key) return o
|
||||
return {
|
||||
...o,
|
||||
|
15
packages/utils/src/typebotConversions.ts
Normal file
15
packages/utils/src/typebotConversions.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Typebot } from 'models'
|
||||
|
||||
export const convertTypebotToV2 = (typebot: any): Typebot => {
|
||||
const newTypebot = JSON.parse(
|
||||
JSON.stringify(typebot)
|
||||
.replace(/\"blocks\":/g, '"groups":')
|
||||
.replace(/\"steps\":/g, '"blocks":')
|
||||
.replace(/\"blockId\":/g, '"groupId":')
|
||||
.replace(/\"blockId\":/g, '"blockId":')
|
||||
)
|
||||
return {
|
||||
version: '2',
|
||||
...newTypebot,
|
||||
}
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
import {
|
||||
BubbleStep,
|
||||
BubbleStepType,
|
||||
ChoiceInputStep,
|
||||
ConditionStep,
|
||||
InputStep,
|
||||
InputStepType,
|
||||
IntegrationStep,
|
||||
IntegrationStepType,
|
||||
LogicStep,
|
||||
LogicStepType,
|
||||
Step,
|
||||
TextInputStep,
|
||||
TextBubbleStep,
|
||||
WebhookStep,
|
||||
StepType,
|
||||
StepWithOptionsType,
|
||||
ImageBubbleStep,
|
||||
VideoBubbleStep,
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
ChoiceInputBlock,
|
||||
ConditionBlock,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
IntegrationBlock,
|
||||
IntegrationBlockType,
|
||||
LogicBlock,
|
||||
LogicBlockType,
|
||||
Block,
|
||||
TextInputBlock,
|
||||
TextBubbleBlock,
|
||||
WebhookBlock,
|
||||
BlockType,
|
||||
BlockWithOptionsType,
|
||||
ImageBubbleBlock,
|
||||
VideoBubbleBlock,
|
||||
} from 'models'
|
||||
|
||||
export const sendRequest = async <ResponseData>(
|
||||
@ -67,78 +67,78 @@ export const isEmpty = (value: string | undefined | null): value is undefined =>
|
||||
export const isNotEmpty = (value: string | undefined | null): value is string =>
|
||||
value !== undefined && value !== null && value !== ''
|
||||
|
||||
export const isInputStep = (step: Step): step is InputStep =>
|
||||
(Object.values(InputStepType) as string[]).includes(step.type)
|
||||
export const isInputBlock = (block: Block): block is InputBlock =>
|
||||
(Object.values(InputBlockType) as string[]).includes(block.type)
|
||||
|
||||
export const isBubbleStep = (step: Step): step is BubbleStep =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(step.type)
|
||||
export const isBubbleBlock = (block: Block): block is BubbleBlock =>
|
||||
(Object.values(BubbleBlockType) as string[]).includes(block.type)
|
||||
|
||||
export const isLogicStep = (step: Step): step is LogicStep =>
|
||||
(Object.values(LogicStepType) as string[]).includes(step.type)
|
||||
export const isLogicBlock = (block: Block): block is LogicBlock =>
|
||||
(Object.values(LogicBlockType) as string[]).includes(block.type)
|
||||
|
||||
export const isTextBubbleStep = (step: Step): step is TextBubbleStep =>
|
||||
step.type === BubbleStepType.TEXT
|
||||
export const isTextBubbleBlock = (block: Block): block is TextBubbleBlock =>
|
||||
block.type === BubbleBlockType.TEXT
|
||||
|
||||
export const isMediaBubbleStep = (
|
||||
step: Step
|
||||
): step is ImageBubbleStep | VideoBubbleStep =>
|
||||
step.type === BubbleStepType.IMAGE || step.type === BubbleStepType.VIDEO
|
||||
export const isMediaBubbleBlock = (
|
||||
block: Block
|
||||
): block is ImageBubbleBlock | VideoBubbleBlock =>
|
||||
block.type === BubbleBlockType.IMAGE || block.type === BubbleBlockType.VIDEO
|
||||
|
||||
export const isTextInputStep = (step: Step): step is TextInputStep =>
|
||||
step.type === InputStepType.TEXT
|
||||
export const isTextInputBlock = (block: Block): block is TextInputBlock =>
|
||||
block.type === InputBlockType.TEXT
|
||||
|
||||
export const isChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE
|
||||
export const isChoiceInput = (block: Block): block is ChoiceInputBlock =>
|
||||
block.type === InputBlockType.CHOICE
|
||||
|
||||
export const isSingleChoiceInput = (step: Step): step is ChoiceInputStep =>
|
||||
step.type === InputStepType.CHOICE &&
|
||||
'options' in step &&
|
||||
!step.options.isMultipleChoice
|
||||
export const isSingleChoiceInput = (block: Block): block is ChoiceInputBlock =>
|
||||
block.type === InputBlockType.CHOICE &&
|
||||
'options' in block &&
|
||||
!block.options.isMultipleChoice
|
||||
|
||||
export const isConditionStep = (step: Step): step is ConditionStep =>
|
||||
step.type === LogicStepType.CONDITION
|
||||
export const isConditionBlock = (block: Block): block is ConditionBlock =>
|
||||
block.type === LogicBlockType.CONDITION
|
||||
|
||||
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
||||
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
||||
export const isIntegrationBlock = (block: Block): block is IntegrationBlock =>
|
||||
(Object.values(IntegrationBlockType) as string[]).includes(block.type)
|
||||
|
||||
export const isWebhookStep = (step: Step): step is WebhookStep =>
|
||||
export const isWebhookBlock = (block: Block): block is WebhookBlock =>
|
||||
[
|
||||
IntegrationStepType.WEBHOOK,
|
||||
IntegrationStepType.PABBLY_CONNECT,
|
||||
IntegrationStepType.ZAPIER,
|
||||
IntegrationStepType.MAKE_COM,
|
||||
].includes(step.type as IntegrationStepType)
|
||||
IntegrationBlockType.WEBHOOK,
|
||||
IntegrationBlockType.PABBLY_CONNECT,
|
||||
IntegrationBlockType.ZAPIER,
|
||||
IntegrationBlockType.MAKE_COM,
|
||||
].includes(block.type as IntegrationBlockType)
|
||||
|
||||
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(type)
|
||||
export const isBubbleBlockType = (type: BlockType): type is BubbleBlockType =>
|
||||
(Object.values(BubbleBlockType) as string[]).includes(type)
|
||||
|
||||
export const stepTypeHasOption = (
|
||||
type: StepType
|
||||
): type is StepWithOptionsType =>
|
||||
(Object.values(InputStepType) as string[])
|
||||
.concat(Object.values(LogicStepType))
|
||||
.concat(Object.values(IntegrationStepType))
|
||||
export const blockTypeHasOption = (
|
||||
type: BlockType
|
||||
): type is BlockWithOptionsType =>
|
||||
(Object.values(InputBlockType) as string[])
|
||||
.concat(Object.values(LogicBlockType))
|
||||
.concat(Object.values(IntegrationBlockType))
|
||||
.includes(type)
|
||||
|
||||
export const stepTypeHasWebhook = (
|
||||
type: StepType
|
||||
): type is IntegrationStepType.WEBHOOK =>
|
||||
export const blockTypeHasWebhook = (
|
||||
type: BlockType
|
||||
): type is IntegrationBlockType.WEBHOOK =>
|
||||
Object.values([
|
||||
IntegrationStepType.WEBHOOK,
|
||||
IntegrationStepType.ZAPIER,
|
||||
IntegrationStepType.MAKE_COM,
|
||||
IntegrationStepType.PABBLY_CONNECT,
|
||||
IntegrationBlockType.WEBHOOK,
|
||||
IntegrationBlockType.ZAPIER,
|
||||
IntegrationBlockType.MAKE_COM,
|
||||
IntegrationBlockType.PABBLY_CONNECT,
|
||||
] as string[]).includes(type)
|
||||
|
||||
export const stepTypeHasItems = (
|
||||
type: StepType
|
||||
): type is LogicStepType.CONDITION | InputStepType.CHOICE =>
|
||||
type === LogicStepType.CONDITION || type === InputStepType.CHOICE
|
||||
export const blockTypeHasItems = (
|
||||
type: BlockType
|
||||
): type is LogicBlockType.CONDITION | InputBlockType.CHOICE =>
|
||||
type === LogicBlockType.CONDITION || type === InputBlockType.CHOICE
|
||||
|
||||
export const stepHasItems = (
|
||||
step: Step
|
||||
): step is ConditionStep | ChoiceInputStep =>
|
||||
'items' in step && isDefined(step.items)
|
||||
export const blockHasItems = (
|
||||
block: Block
|
||||
): block is ConditionBlock | ChoiceInputBlock =>
|
||||
'items' in block && isDefined(block.items)
|
||||
|
||||
export const byId = (id?: string) => (obj: { id: string }) => obj.id === id
|
||||
|
||||
|
Reference in New Issue
Block a user