2
0

Add conditional choice items

Allows you to conditonnally display an item from the Buttons or the Picture choice input

Closes #546
This commit is contained in:
Baptiste Arnaud
2023-06-05 17:33:43 +02:00
parent acaa1c6223
commit ef0a2d9dc6
18 changed files with 404 additions and 157 deletions

View File

@ -5,12 +5,22 @@ import {
Fade,
IconButton,
Flex,
Popover,
PopoverAnchor,
PopoverArrow,
PopoverBody,
PopoverContent,
Portal,
useColorModeValue,
SlideFade,
} from '@chakra-ui/react'
import { PlusIcon } from '@/components/icons'
import { PlusIcon, SettingsIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { ButtonItem, Item, ItemIndices, ItemType } from '@typebot.io/schemas'
import React, { useRef, useState } from 'react'
import { isNotDefined } from '@typebot.io/lib'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { ButtonsItemSettings } from './ButtonsItemSettings'
type Props = {
item: ButtonItem
@ -20,8 +30,12 @@ type Props = {
export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
const { deleteItem, updateItem, createItem } = useTypebot()
const { openedItemId, setOpenedItemId } = useGraph()
const [itemValue, setItemValue] = useState(item.content ?? 'Click to edit')
const editableRef = useRef<HTMLDivElement | null>(null)
const ref = useRef<HTMLDivElement | null>(null)
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()
const handleInputSubmit = () => {
if (itemValue === '') deleteItem(indices)
@ -45,44 +59,101 @@ export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
)
}
const updateItemSettings = (settings: Omit<ButtonItem, 'content'>) => {
updateItem(indices, { ...item, ...settings })
}
return (
<Flex px={4} py={2} justify="center" w="90%" pos="relative">
<Editable
ref={editableRef}
flex="1"
startWithEditView={isNotDefined(item.content)}
value={itemValue}
onChange={setItemValue}
onSubmit={handleInputSubmit}
onKeyDownCapture={handleKeyPress}
maxW="180px"
>
<EditablePreview
w="full"
color={item.content !== 'Click to edit' ? 'inherit' : 'gray.500'}
cursor="pointer"
/>
<EditableInput />
</Editable>
<Fade
in={isMouseOver}
style={{
position: 'absolute',
bottom: '-15px',
zIndex: 3,
left: '90px',
}}
unmountOnExit
>
<IconButton
aria-label="Add item"
icon={<PlusIcon />}
size="xs"
shadow="md"
colorScheme="gray"
onClick={handlePlusClick}
/>
</Fade>
</Flex>
<Popover
placement="right"
isLazy
isOpen={openedItemId === item.id}
closeOnBlur={false}
>
<PopoverAnchor>
<Flex px={4} py={2} justify="center" w="90%" pos="relative">
<Editable
ref={editableRef}
flex="1"
startWithEditView={isNotDefined(item.content)}
value={itemValue}
onChange={setItemValue}
onSubmit={handleInputSubmit}
onKeyDownCapture={handleKeyPress}
maxW="180px"
>
<EditablePreview
w="full"
color={item.content !== 'Click to edit' ? 'inherit' : 'gray.500'}
cursor="pointer"
/>
<EditableInput />
</Editable>
<HitboxExtension />
<SlideFade
offsetY="0px"
offsetX="-10px"
in={isMouseOver}
style={{
position: 'absolute',
right: '-60px',
zIndex: 3,
}}
unmountOnExit
>
<IconButton
aria-label="Open settings"
icon={<SettingsIcon />}
bgColor={useColorModeValue('white', 'gray.800')}
variant="ghost"
size="sm"
shadow="md"
colorScheme="gray"
onClick={() => setOpenedItemId(item.id)}
/>
</SlideFade>
<Fade
in={isMouseOver}
style={{
position: 'absolute',
bottom: '-15px',
zIndex: 3,
left: '90px',
}}
unmountOnExit
>
<IconButton
aria-label="Add item"
icon={<PlusIcon />}
size="xs"
shadow="md"
colorScheme="gray"
onClick={handlePlusClick}
/>
</Fade>
</Flex>
</PopoverAnchor>
<Portal>
<PopoverContent pos="relative" onMouseDown={handleMouseDown}>
<PopoverArrow />
<PopoverBody
py="6"
overflowY="scroll"
maxH="400px"
shadow="lg"
ref={ref}
>
<ButtonsItemSettings
item={item}
onSettingsChange={updateItemSettings}
/>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
)
}
const HitboxExtension = () => (
<Flex h="full" w="50px" pos="absolute" top="0" right="-70px" />
)

View File

@ -0,0 +1,50 @@
import React from 'react'
import { Stack } from '@chakra-ui/react'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm'
import { ButtonItem, Condition, LogicalOperator } from '@typebot.io/schemas'
type Props = {
item: ButtonItem
onSettingsChange: (updates: Omit<ButtonItem, 'content'>) => void
}
export const ButtonsItemSettings = ({ item, onSettingsChange }: Props) => {
const updateIsDisplayConditionEnabled = (isEnabled: boolean) =>
onSettingsChange({
...item,
displayCondition: {
...item.displayCondition,
isEnabled,
},
})
const updateDisplayCondition = (condition: Condition) =>
onSettingsChange({
...item,
displayCondition: {
...item.displayCondition,
condition,
},
})
return (
<Stack spacing={4}>
<SwitchWithRelatedSettings
label="Display condition"
initialValue={item.displayCondition?.isEnabled ?? false}
onCheckChange={updateIsDisplayConditionEnabled}
>
<ConditionForm
condition={
item.displayCondition?.condition ?? {
comparisons: [],
logicalOperator: LogicalOperator.AND,
}
}
onConditionChange={updateDisplayCondition}
/>
</SwitchWithRelatedSettings>
</Stack>
)
}

View File

@ -11,6 +11,9 @@ import {
Text,
} from '@chakra-ui/react'
import { ImageUploadContent } from '@/components/ImageUploadContent'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm'
import { Condition, LogicalOperator } from '@typebot.io/schemas'
type Props = {
typebotId: string
@ -32,15 +35,35 @@ export const PictureChoiceItemSettings = ({
const updateDescription = (description: string) =>
onItemChange({ ...item, description })
const updateIsDisplayConditionEnabled = (isEnabled: boolean) =>
onItemChange({
...item,
displayCondition: {
...item.displayCondition,
isEnabled,
},
})
const updateDisplayCondition = (condition: Condition) =>
onItemChange({
...item,
displayCondition: {
...item.displayCondition,
condition,
},
})
return (
<Stack>
<Stack spacing={4}>
<HStack>
<Text fontWeight="medium">Image:</Text>
<Popover isLazy>
{({ onClose }) => (
<>
<PopoverTrigger>
<Button size="sm">Pick an image</Button>
<Button size="sm">
{item.pictureSrc ? 'Change image' : 'Pick an image'}
</Button>
</PopoverTrigger>
<PopoverContent p="4" w="500px">
<ImageUploadContent
@ -67,6 +90,21 @@ export const PictureChoiceItemSettings = ({
defaultValue={item.description}
onChange={updateDescription}
/>
<SwitchWithRelatedSettings
label="Display condition"
initialValue={item.displayCondition?.isEnabled ?? false}
onCheckChange={updateIsDisplayConditionEnabled}
>
<ConditionForm
condition={
item.displayCondition?.condition ?? {
comparisons: [],
logicalOperator: LogicalOperator.AND,
}
}
onConditionChange={updateDisplayCondition}
/>
</SwitchWithRelatedSettings>
</Stack>
)
}

View File

@ -0,0 +1,78 @@
import { Stack, Wrap, Tag, Text, useColorModeValue } from '@chakra-ui/react'
import { byId } from '@typebot.io/lib'
import { ComparisonOperators, Condition, Variable } from '@typebot.io/schemas'
type Props = {
condition: Condition
variables: Variable[]
size?: 'xs' | 'sm'
displaySemicolon?: boolean
}
export const ConditionContent = ({
condition,
variables,
size = 'sm',
displaySemicolon,
}: Props) => {
const comparisonValueBg = useColorModeValue('gray.200', 'gray.700')
return (
<Stack>
{condition.comparisons.map((comparison, idx) => {
const variable = variables.find(byId(comparison.variableId))
return (
<Wrap key={comparison.id} spacing={1} noOfLines={1}>
{idx === 0 && <Text fontSize={size}>IF</Text>}
{idx > 0 && (
<Text fontSize={size}>{condition.logicalOperator ?? ''}</Text>
)}
{variable?.name && (
<Tag bgColor="orange.400" color="white" size="sm">
{variable.name}
</Tag>
)}
{comparison.comparisonOperator && (
<Text fontSize={size}>
{parseComparisonOperatorSymbol(comparison.comparisonOperator)}
</Text>
)}
{comparison?.value && (
<Tag bgColor={comparisonValueBg} size="sm">
{comparison.value}
</Tag>
)}
{idx === condition.comparisons.length - 1 && displaySemicolon && (
<Text fontSize={size}>:</Text>
)}
</Wrap>
)
})}
</Stack>
)
}
const parseComparisonOperatorSymbol = (
operator: ComparisonOperators
): string => {
switch (operator) {
case ComparisonOperators.CONTAINS:
return 'contains'
case ComparisonOperators.EQUAL:
return '='
case ComparisonOperators.GREATER:
return '>'
case ComparisonOperators.IS_SET:
return 'is set'
case ComparisonOperators.LESS:
return '<'
case ComparisonOperators.NOT_EQUAL:
return '!='
case ComparisonOperators.ENDS_WITH:
return 'ends with'
case ComparisonOperators.STARTS_WITH:
return 'starts with'
case ComparisonOperators.IS_EMPTY:
return 'is empty'
case ComparisonOperators.NOT_CONTAINS:
return 'not contains'
}
}

View File

@ -1,30 +1,30 @@
import { Flex } from '@chakra-ui/react'
import { DropdownList } from '@/components/DropdownList'
import { Comparison, ConditionItem, LogicalOperator } from '@typebot.io/schemas'
import { Comparison, Condition, LogicalOperator } from '@typebot.io/schemas'
import React from 'react'
import { ComparisonItem } from './ComparisonItem'
import { TableList } from '@/components/TableList'
type Props = {
itemContent: ConditionItem['content']
onItemChange: (updates: Partial<ConditionItem>) => void
condition: Condition
onConditionChange: (newCondition: Condition) => void
}
export const ConditionItemForm = ({ itemContent, onItemChange }: Props) => {
export const ConditionForm = ({ condition, onConditionChange }: Props) => {
const handleComparisonsChange = (comparisons: Comparison[]) =>
onItemChange({ content: { ...itemContent, comparisons } })
onConditionChange({ ...condition, comparisons })
const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) =>
onItemChange({ content: { ...itemContent, logicalOperator } })
onConditionChange({ ...condition, logicalOperator })
return (
<TableList<Comparison>
initialItems={itemContent.comparisons}
initialItems={condition.comparisons}
onItemsChange={handleComparisonsChange}
Item={ComparisonItem}
ComponentBetweenItems={() => (
<Flex justify="center">
<DropdownList
currentItem={itemContent.logicalOperator}
currentItem={condition.logicalOperator}
onItemSelect={handleLogicalOperatorChange}
items={Object.values(LogicalOperator)}
/>

View File

@ -1,9 +1,6 @@
import {
Stack,
Tag,
Text,
Flex,
Wrap,
Fade,
IconButton,
Popover,
@ -12,23 +9,23 @@ import {
PopoverArrow,
PopoverBody,
useEventListener,
useColorModeValue,
PopoverAnchor,
} from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import {
Comparison,
ConditionItem,
ComparisonOperators,
ItemType,
ItemIndices,
Condition,
} from '@typebot.io/schemas'
import React, { useRef } from 'react'
import { byId, isNotDefined } from '@typebot.io/lib'
import { isNotDefined } from '@typebot.io/lib'
import { PlusIcon } from '@/components/icons'
import { ConditionItemForm } from './ConditionItemForm'
import { ConditionForm } from './ConditionForm'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { createId } from '@paralleldrive/cuid2'
import { ConditionContent } from './ConditionContent'
type Props = {
item: ConditionItem
@ -37,7 +34,6 @@ type Props = {
}
export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => {
const comparisonValueBg = useColorModeValue('gray.200', 'gray.700')
const { typebot, createItem, updateItem } = useTypebot()
const { openedItemId, setOpenedItemId } = useGraph()
const ref = useRef<HTMLDivElement | null>(null)
@ -48,8 +44,8 @@ export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => {
setOpenedItemId(item.id)
}
const handleItemChange = (updates: Partial<ConditionItem>) => {
updateItem(indices, { ...item, ...updates })
const updateCondition = (condition: Condition) => {
updateItem(indices, { ...item, content: condition } as ConditionItem)
}
const handlePlusClick = (event: React.MouseEvent) => {
@ -85,37 +81,10 @@ export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => {
comparisonIsEmpty(item.content.comparisons[0]) ? (
<Text color={'gray.500'}>Configure...</Text>
) : (
<Stack maxW="170px">
{item.content.comparisons.map((comparison, idx) => {
const variable = typebot?.variables.find(
byId(comparison.variableId)
)
return (
<Wrap key={comparison.id} spacing={1} noOfLines={1}>
{idx > 0 && (
<Text>{item.content.logicalOperator ?? ''}</Text>
)}
{variable?.name && (
<Tag bgColor="orange.400" color="white">
{variable.name}
</Tag>
)}
{comparison.comparisonOperator && (
<Text fontSize="sm">
{parseComparisonOperatorSymbol(
comparison.comparisonOperator
)}
</Text>
)}
{comparison?.value && (
<Tag bgColor={comparisonValueBg}>
<Text noOfLines={1}>{comparison.value}</Text>
</Tag>
)}
</Wrap>
)
})}
</Stack>
<ConditionContent
condition={item.content}
variables={typebot?.variables ?? []}
/>
)}
<Fade
in={isMouseOver}
@ -148,9 +117,9 @@ export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => {
shadow="lg"
ref={ref}
>
<ConditionItemForm
itemContent={item.content}
onItemChange={handleItemChange}
<ConditionForm
condition={item.content}
onConditionChange={updateCondition}
/>
</PopoverBody>
</PopoverContent>
@ -163,30 +132,3 @@ const comparisonIsEmpty = (comparison: Comparison) =>
isNotDefined(comparison.comparisonOperator) &&
isNotDefined(comparison.value) &&
isNotDefined(comparison.variableId)
const parseComparisonOperatorSymbol = (
operator: ComparisonOperators
): string => {
switch (operator) {
case ComparisonOperators.CONTAINS:
return 'contains'
case ComparisonOperators.EQUAL:
return '='
case ComparisonOperators.GREATER:
return '>'
case ComparisonOperators.IS_SET:
return 'is set'
case ComparisonOperators.LESS:
return '<'
case ComparisonOperators.NOT_EQUAL:
return '!='
case ComparisonOperators.ENDS_WITH:
return 'ends with'
case ComparisonOperators.STARTS_WITH:
return 'starts with'
case ComparisonOperators.IS_EMPTY:
return 'is empty'
case ComparisonOperators.NOT_CONTAINS:
return 'not contains'
}
}

View File

@ -1,4 +1,4 @@
import { Flex, useColorModeValue } from '@chakra-ui/react'
import { Flex, useColorModeValue, Stack } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import {
ChoiceInputBlock,
@ -20,6 +20,7 @@ import {
} from '@/features/graph/providers/GraphDndProvider'
import { useGraph } from '@/features/graph/providers/GraphProvider'
import { setMultipleRefs } from '@/helpers/setMultipleRefs'
import { ConditionContent } from '@/features/blocks/logic/condition/components/ConditionContent'
type Props = {
item: Item
@ -71,12 +72,22 @@ export const ItemNode = ({
renderMenu={() => <ItemNodeContextMenu indices={indices} />}
>
{(ref, isContextMenuOpened) => (
<Flex
<Stack
data-testid="item"
pos="relative"
ref={setMultipleRefs([ref, itemRef])}
w="full"
>
{'displayCondition' in item &&
item.displayCondition?.isEnabled &&
item.displayCondition.condition && (
<ConditionContent
condition={item.displayCondition.condition}
variables={typebot?.variables ?? []}
size="xs"
displaySemicolon
/>
)}
<Flex
align="center"
onMouseEnter={handleMouseEnter}
@ -113,7 +124,7 @@ export const ItemNode = ({
/>
)}
</Flex>
</Flex>
</Stack>
)}
</ContextMenu>
)

View File

@ -0,0 +1,17 @@
import { ChoiceInputBlock, Variable } from '@typebot.io/schemas'
import { executeCondition } from '../../logic/condition/executeCondition'
export const filterChoiceItems =
(variables: Variable[]) =>
(block: ChoiceInputBlock): ChoiceInputBlock => {
const filteredItems = block.items.filter((item) => {
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
return executeCondition(variables)(item.displayCondition.condition)
return true
})
return {
...block,
items: filteredItems,
}
}

View File

@ -8,6 +8,7 @@ import { isDefined } from '@typebot.io/lib'
import { deepParseVariables } from '@/features/variables/deepParseVariable'
import { transformStringVariablesToList } from '@/features/variables/transformVariablesToList'
import { updateVariables } from '@/features/variables/updateVariables'
import { filterChoiceItems } from './filterChoiceItems'
export const injectVariableValuesInButtonsInputBlock =
(state: SessionState) =>
@ -30,7 +31,9 @@ export const injectVariableValuesInButtonsInputBlock =
})),
}
}
return deepParseVariables(state.typebot.variables)(block)
return deepParseVariables(state.typebot.variables)(
filterChoiceItems(state.typebot.variables)(block)
)
}
const getVariableValue =

View File

@ -0,0 +1,17 @@
import { PictureChoiceBlock, Variable } from '@typebot.io/schemas'
import { executeCondition } from '../../logic/condition/executeCondition'
export const filterPictureChoiceItems =
(variables: Variable[]) =>
(block: PictureChoiceBlock): PictureChoiceBlock => {
const filteredItems = block.items.filter((item) => {
if (item.displayCondition?.isEnabled && item.displayCondition?.condition)
return executeCondition(variables)(item.displayCondition.condition)
return true
})
return {
...block,
items: filteredItems,
}
}

View File

@ -6,6 +6,7 @@ import {
} from '@typebot.io/schemas'
import { isDefined } from '@typebot.io/lib'
import { deepParseVariables } from '@/features/variables/deepParseVariable'
import { filterPictureChoiceItems } from './filterPictureChoiceItems'
export const injectVariableValuesInPictureChoiceBlock =
(variables: SessionState['typebot']['variables']) =>
@ -51,5 +52,7 @@ export const injectVariableValuesInPictureChoiceBlock =
})),
}
}
return deepParseVariables(variables)(block)
return deepParseVariables(variables)(
filterPictureChoiceItems(variables)(block)
)
}

View File

@ -1,34 +1,20 @@
import { findUniqueVariableValue } from '@/features/variables/findUniqueVariableValue'
import { isNotDefined, isDefined } from '@typebot.io/lib'
import {
Comparison,
ComparisonOperators,
ConditionBlock,
Condition,
LogicalOperator,
SessionState,
Variable,
} from '@typebot.io/schemas'
import { isNotDefined, isDefined } from '@typebot.io/lib'
import { ExecuteLogicResponse } from '@/features/chat/types'
import { findUniqueVariableValue } from '@/features/variables/findUniqueVariableValue'
import { parseVariables } from '@/features/variables/parseVariables'
import { parseVariables } from 'bot-engine'
export const executeCondition = (
{ typebot: { variables } }: SessionState,
block: ConditionBlock
): ExecuteLogicResponse => {
const passedCondition = block.items.find((item) => {
const { content } = item
const isConditionPassed =
content.logicalOperator === LogicalOperator.AND
? content.comparisons.every(executeComparison(variables))
: content.comparisons.some(executeComparison(variables))
return isConditionPassed
})
return {
outgoingEdgeId: passedCondition
? passedCondition.outgoingEdgeId
: block.outgoingEdgeId,
}
}
export const executeCondition =
(variables: Variable[]) =>
(condition: Condition): boolean =>
condition.logicalOperator === LogicalOperator.AND
? condition.comparisons.every(executeComparison(variables))
: condition.comparisons.some(executeComparison(variables))
const executeComparison =
(variables: Variable[]) =>

View File

@ -0,0 +1,17 @@
import { ConditionBlock, SessionState } from '@typebot.io/schemas'
import { ExecuteLogicResponse } from '@/features/chat/types'
import { executeCondition } from './executeCondition'
export const executeConditionBlock = (
{ typebot: { variables } }: SessionState,
block: ConditionBlock
): ExecuteLogicResponse => {
const passedCondition = block.items.find((item) =>
executeCondition(variables)(item.content)
)
return {
outgoingEdgeId: passedCondition
? passedCondition.outgoingEdgeId
: block.outgoingEdgeId,
}
}

View File

@ -55,7 +55,7 @@ export const executeGroup =
if (isInputBlock(block))
return {
messages,
input: await injectVariablesValueInBlock(newSessionState)(block),
input: await parseInput(newSessionState)(block),
newSessionState: {
...newSessionState,
currentBlock: {
@ -183,7 +183,7 @@ const parseBubbleBlock =
}
}
const injectVariablesValueInBlock =
const parseInput =
(state: SessionState) =>
async (block: InputBlock): Promise<ChatReply['input']> => {
switch (block.type) {

View File

@ -4,7 +4,7 @@ import { ExecuteLogicResponse } from '../types'
import { executeScript } from '@/features/blocks/logic/script/executeScript'
import { executeJumpBlock } from '@/features/blocks/logic/jump/executeJumpBlock'
import { executeRedirect } from '@/features/blocks/logic/redirect/executeRedirect'
import { executeCondition } from '@/features/blocks/logic/condition/executeCondition'
import { executeConditionBlock } from '@/features/blocks/logic/condition/executeConditionBlock'
import { executeSetVariable } from '@/features/blocks/logic/setVariable/executeSetVariable'
import { executeTypebotLink } from '@/features/blocks/logic/typebotLink/executeTypebotLink'
import { executeAbTest } from '@/features/blocks/logic/abTest/executeAbTest'
@ -16,7 +16,7 @@ export const executeLogic =
case LogicBlockType.SET_VARIABLE:
return executeSetVariable(state, block)
case LogicBlockType.CONDITION:
return executeCondition(state, block)
return executeConditionBlock(state, block)
case LogicBlockType.REDIRECT:
return executeRedirect(state, block)
case LogicBlockType.SCRIPT:

View File

@ -4,6 +4,7 @@ import { itemBaseSchema } from '../../items/baseSchemas'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
import { conditionSchema } from '../logic/condition'
export const choiceInputOptionsSchema = optionBaseSchema.merge(
z.object({
@ -26,6 +27,12 @@ export const buttonItemSchema = itemBaseSchema.merge(
z.object({
type: z.literal(ItemType.BUTTON),
content: z.string().optional(),
displayCondition: z
.object({
isEnabled: z.boolean().optional(),
condition: conditionSchema.optional(),
})
.optional(),
})
)

View File

@ -4,6 +4,7 @@ import { itemBaseSchema } from '../../items/baseSchemas'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
import { conditionSchema } from '../logic/condition'
export const pictureChoiceOptionsSchema = optionBaseSchema.merge(
z.object({
@ -28,6 +29,12 @@ export const pictureChoiceItemSchema = itemBaseSchema.merge(
pictureSrc: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
displayCondition: z
.object({
isEnabled: z.boolean().optional(),
condition: conditionSchema.optional(),
})
.optional(),
})
)

View File

@ -29,7 +29,7 @@ const comparisonSchema = z.object({
value: z.string().optional(),
})
const conditionContentSchema = z.object({
export const conditionSchema = z.object({
logicalOperator: z.nativeEnum(LogicalOperator),
comparisons: z.array(comparisonSchema),
})
@ -37,7 +37,7 @@ const conditionContentSchema = z.object({
export const conditionItemSchema = itemBaseSchema.merge(
z.object({
type: z.literal(ItemType.CONDITION),
content: conditionContentSchema,
content: conditionSchema,
})
)
@ -48,7 +48,7 @@ export const conditionBlockSchema = blockBaseSchema.merge(
})
)
export const defaultConditionContent: ConditionContent = {
export const defaultConditionContent: Condition = {
comparisons: [],
logicalOperator: LogicalOperator.AND,
}
@ -56,4 +56,4 @@ export const defaultConditionContent: ConditionContent = {
export type ConditionItem = z.infer<typeof conditionItemSchema>
export type Comparison = z.infer<typeof comparisonSchema>
export type ConditionBlock = z.infer<typeof conditionBlockSchema>
export type ConditionContent = z.infer<typeof conditionContentSchema>
export type Condition = z.infer<typeof conditionSchema>