From ef0a2d9dc61042281ee486ef5e9bb8d711f92e30 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Mon, 5 Jun 2023 17:33:43 +0200 Subject: [PATCH] :sparkles: Add conditional choice items Allows you to conditonnally display an item from the Buttons or the Picture choice input Closes #546 --- .../buttons/components/ButtonsItemNode.tsx | 149 +++++++++++++----- .../components/ButtonsItemSettings.tsx | 50 ++++++ .../components/PictureChoiceItemSettings.tsx | 42 ++++- .../condition/components/ConditionContent.tsx | 78 +++++++++ ...onditionItemForm.tsx => ConditionForm.tsx} | 16 +- .../components/ConditionItemNode.tsx | 84 ++-------- .../graph/components/nodes/item/ItemNode.tsx | 17 +- .../inputs/buttons/filterChoiceItems.ts | 17 ++ ...injectVariableValuesInButtonsInputBlock.ts | 5 +- .../pictureChoice/filterPictureChoiceItems.ts | 17 ++ ...njectVariableValuesInPictureChoiceBlock.ts | 5 +- .../logic/condition/executeCondition.ts | 34 ++-- .../logic/condition/executeConditionBlock.ts | 17 ++ .../src/features/chat/helpers/executeGroup.ts | 4 +- .../src/features/chat/helpers/executeLogic.ts | 4 +- .../schemas/features/blocks/inputs/choice.ts | 7 + .../features/blocks/inputs/pictureChoice.ts | 7 + .../features/blocks/logic/condition.ts | 8 +- 18 files changed, 404 insertions(+), 157 deletions(-) create mode 100644 apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemSettings.tsx create mode 100644 apps/builder/src/features/blocks/logic/condition/components/ConditionContent.tsx rename apps/builder/src/features/blocks/logic/condition/components/{ConditionItemForm.tsx => ConditionForm.tsx} (61%) create mode 100644 apps/viewer/src/features/blocks/inputs/buttons/filterChoiceItems.ts create mode 100644 apps/viewer/src/features/blocks/inputs/pictureChoice/filterPictureChoiceItems.ts create mode 100644 apps/viewer/src/features/blocks/logic/condition/executeConditionBlock.ts diff --git a/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemNode.tsx b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemNode.tsx index d03589da0..a9fffceca 100644 --- a/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemNode.tsx +++ b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemNode.tsx @@ -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(null) + const ref = useRef(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) => { + updateItem(indices, { ...item, ...settings }) + } + return ( - - - - - - - } - size="xs" - shadow="md" - colorScheme="gray" - onClick={handlePlusClick} - /> - - + + + + + + + + + + } + bgColor={useColorModeValue('white', 'gray.800')} + variant="ghost" + size="sm" + shadow="md" + colorScheme="gray" + onClick={() => setOpenedItemId(item.id)} + /> + + + } + size="xs" + shadow="md" + colorScheme="gray" + onClick={handlePlusClick} + /> + + + + + + + + + + + + ) } + +const HitboxExtension = () => ( + +) diff --git a/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemSettings.tsx b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemSettings.tsx new file mode 100644 index 000000000..33bffec97 --- /dev/null +++ b/apps/builder/src/features/blocks/inputs/buttons/components/ButtonsItemSettings.tsx @@ -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) => 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 ( + + + + + + ) +} diff --git a/apps/builder/src/features/blocks/inputs/pictureChoice/components/PictureChoiceItemSettings.tsx b/apps/builder/src/features/blocks/inputs/pictureChoice/components/PictureChoiceItemSettings.tsx index 2e5f675dd..33d9527ef 100644 --- a/apps/builder/src/features/blocks/inputs/pictureChoice/components/PictureChoiceItemSettings.tsx +++ b/apps/builder/src/features/blocks/inputs/pictureChoice/components/PictureChoiceItemSettings.tsx @@ -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 ( - + Image: {({ onClose }) => ( <> - + + + + ) } diff --git a/apps/builder/src/features/blocks/logic/condition/components/ConditionContent.tsx b/apps/builder/src/features/blocks/logic/condition/components/ConditionContent.tsx new file mode 100644 index 000000000..5c035d69d --- /dev/null +++ b/apps/builder/src/features/blocks/logic/condition/components/ConditionContent.tsx @@ -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 ( + + {condition.comparisons.map((comparison, idx) => { + const variable = variables.find(byId(comparison.variableId)) + return ( + + {idx === 0 && IF} + {idx > 0 && ( + {condition.logicalOperator ?? ''} + )} + {variable?.name && ( + + {variable.name} + + )} + {comparison.comparisonOperator && ( + + {parseComparisonOperatorSymbol(comparison.comparisonOperator)} + + )} + {comparison?.value && ( + + {comparison.value} + + )} + {idx === condition.comparisons.length - 1 && displaySemicolon && ( + : + )} + + ) + })} + + ) +} + +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' + } +} diff --git a/apps/builder/src/features/blocks/logic/condition/components/ConditionItemForm.tsx b/apps/builder/src/features/blocks/logic/condition/components/ConditionForm.tsx similarity index 61% rename from apps/builder/src/features/blocks/logic/condition/components/ConditionItemForm.tsx rename to apps/builder/src/features/blocks/logic/condition/components/ConditionForm.tsx index b27842018..841dd2b44 100644 --- a/apps/builder/src/features/blocks/logic/condition/components/ConditionItemForm.tsx +++ b/apps/builder/src/features/blocks/logic/condition/components/ConditionForm.tsx @@ -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) => 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 ( - initialItems={itemContent.comparisons} + initialItems={condition.comparisons} onItemsChange={handleComparisonsChange} Item={ComparisonItem} ComponentBetweenItems={() => ( diff --git a/apps/builder/src/features/blocks/logic/condition/components/ConditionItemNode.tsx b/apps/builder/src/features/blocks/logic/condition/components/ConditionItemNode.tsx index 6bf8428d5..801a3ca84 100644 --- a/apps/builder/src/features/blocks/logic/condition/components/ConditionItemNode.tsx +++ b/apps/builder/src/features/blocks/logic/condition/components/ConditionItemNode.tsx @@ -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(null) @@ -48,8 +44,8 @@ export const ConditionItemNode = ({ item, isMouseOver, indices }: Props) => { setOpenedItemId(item.id) } - const handleItemChange = (updates: Partial) => { - 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]) ? ( Configure... ) : ( - - {item.content.comparisons.map((comparison, idx) => { - const variable = typebot?.variables.find( - byId(comparison.variableId) - ) - return ( - - {idx > 0 && ( - {item.content.logicalOperator ?? ''} - )} - {variable?.name && ( - - {variable.name} - - )} - {comparison.comparisonOperator && ( - - {parseComparisonOperatorSymbol( - comparison.comparisonOperator - )} - - )} - {comparison?.value && ( - - {comparison.value} - - )} - - ) - })} - + )} { shadow="lg" ref={ref} > - @@ -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' - } -} diff --git a/apps/builder/src/features/graph/components/nodes/item/ItemNode.tsx b/apps/builder/src/features/graph/components/nodes/item/ItemNode.tsx index a91a169ec..6ae820492 100644 --- a/apps/builder/src/features/graph/components/nodes/item/ItemNode.tsx +++ b/apps/builder/src/features/graph/components/nodes/item/ItemNode.tsx @@ -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={() => } > {(ref, isContextMenuOpened) => ( - + {'displayCondition' in item && + item.displayCondition?.isEnabled && + item.displayCondition.condition && ( + + )} )} - + )} ) diff --git a/apps/viewer/src/features/blocks/inputs/buttons/filterChoiceItems.ts b/apps/viewer/src/features/blocks/inputs/buttons/filterChoiceItems.ts new file mode 100644 index 000000000..7ad20984f --- /dev/null +++ b/apps/viewer/src/features/blocks/inputs/buttons/filterChoiceItems.ts @@ -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, + } + } diff --git a/apps/viewer/src/features/blocks/inputs/buttons/injectVariableValuesInButtonsInputBlock.ts b/apps/viewer/src/features/blocks/inputs/buttons/injectVariableValuesInButtonsInputBlock.ts index 976317c72..74aff85db 100644 --- a/apps/viewer/src/features/blocks/inputs/buttons/injectVariableValuesInButtonsInputBlock.ts +++ b/apps/viewer/src/features/blocks/inputs/buttons/injectVariableValuesInButtonsInputBlock.ts @@ -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 = diff --git a/apps/viewer/src/features/blocks/inputs/pictureChoice/filterPictureChoiceItems.ts b/apps/viewer/src/features/blocks/inputs/pictureChoice/filterPictureChoiceItems.ts new file mode 100644 index 000000000..704761a99 --- /dev/null +++ b/apps/viewer/src/features/blocks/inputs/pictureChoice/filterPictureChoiceItems.ts @@ -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, + } + } diff --git a/apps/viewer/src/features/blocks/inputs/pictureChoice/injectVariableValuesInPictureChoiceBlock.ts b/apps/viewer/src/features/blocks/inputs/pictureChoice/injectVariableValuesInPictureChoiceBlock.ts index b051b284a..00341935f 100644 --- a/apps/viewer/src/features/blocks/inputs/pictureChoice/injectVariableValuesInPictureChoiceBlock.ts +++ b/apps/viewer/src/features/blocks/inputs/pictureChoice/injectVariableValuesInPictureChoiceBlock.ts @@ -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) + ) } diff --git a/apps/viewer/src/features/blocks/logic/condition/executeCondition.ts b/apps/viewer/src/features/blocks/logic/condition/executeCondition.ts index e7009ce19..f022f785d 100644 --- a/apps/viewer/src/features/blocks/logic/condition/executeCondition.ts +++ b/apps/viewer/src/features/blocks/logic/condition/executeCondition.ts @@ -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[]) => diff --git a/apps/viewer/src/features/blocks/logic/condition/executeConditionBlock.ts b/apps/viewer/src/features/blocks/logic/condition/executeConditionBlock.ts new file mode 100644 index 000000000..b42aa835e --- /dev/null +++ b/apps/viewer/src/features/blocks/logic/condition/executeConditionBlock.ts @@ -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, + } +} diff --git a/apps/viewer/src/features/chat/helpers/executeGroup.ts b/apps/viewer/src/features/chat/helpers/executeGroup.ts index 0e538e482..80efd5b43 100644 --- a/apps/viewer/src/features/chat/helpers/executeGroup.ts +++ b/apps/viewer/src/features/chat/helpers/executeGroup.ts @@ -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 => { switch (block.type) { diff --git a/apps/viewer/src/features/chat/helpers/executeLogic.ts b/apps/viewer/src/features/chat/helpers/executeLogic.ts index 261fc6b20..818f93ad2 100644 --- a/apps/viewer/src/features/chat/helpers/executeLogic.ts +++ b/apps/viewer/src/features/chat/helpers/executeLogic.ts @@ -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: diff --git a/packages/schemas/features/blocks/inputs/choice.ts b/packages/schemas/features/blocks/inputs/choice.ts index 6f4387fc4..355d7d5c0 100644 --- a/packages/schemas/features/blocks/inputs/choice.ts +++ b/packages/schemas/features/blocks/inputs/choice.ts @@ -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(), }) ) diff --git a/packages/schemas/features/blocks/inputs/pictureChoice.ts b/packages/schemas/features/blocks/inputs/pictureChoice.ts index 03da07c6a..2a74c154a 100644 --- a/packages/schemas/features/blocks/inputs/pictureChoice.ts +++ b/packages/schemas/features/blocks/inputs/pictureChoice.ts @@ -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(), }) ) diff --git a/packages/schemas/features/blocks/logic/condition.ts b/packages/schemas/features/blocks/logic/condition.ts index e1ee26cbc..ffb9e0489 100644 --- a/packages/schemas/features/blocks/logic/condition.ts +++ b/packages/schemas/features/blocks/logic/condition.ts @@ -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 export type Comparison = z.infer export type ConditionBlock = z.infer -export type ConditionContent = z.infer +export type Condition = z.infer