diff --git a/apps/builder/playwright/fixtures/typebots/inputs/variableButton.json b/apps/builder/playwright/fixtures/typebots/inputs/variableButton.json new file mode 100644 index 000000000..aebb8dcd2 --- /dev/null +++ b/apps/builder/playwright/fixtures/typebots/inputs/variableButton.json @@ -0,0 +1,132 @@ +{ + "id": "cl4fr6kca0000p11abjka8lvd", + "createdAt": "2022-06-15T15:33:43.930Z", + "updatedAt": "2022-06-15T15:36:44.821Z", + "icon": null, + "name": "My typebot", + "publishedTypebotId": null, + "folderId": null, + "groups": [ + { + "id": "block0", + "title": "Group #0", + "blocks": [ + { + "id": "block0", + "type": "start", + "label": "Start", + "groupId": "block0", + "outgoingEdgeId": "edge1" + } + ], + "graphCoordinates": { "x": 0, "y": 0 } + }, + { + "id": "block1", + "title": "Group #1", + "blocks": [ + { + "id": "cl4fr6rgf0000396ml1ai0t8v", + "type": "Set variable", + "groupId": "block1", + "options": { + "variableId": "vcl4fr8f8l000b396m6gsbnrmd", + "expressionToEvaluate": "Variable item" + } + }, + { + "id": "block1", + "type": "choice input", + "items": [ + { + "id": "choice1", + "type": 0, + "blockId": "block1", + "content": "Item 1" + }, + { + "id": "cl4fr7e6i0003396mkh7mol65", + "type": 0, + "blockId": "block1", + "content": "{{Item 2}}", + "outgoingEdgeId": "cl4fr80900009396my6euvunj" + }, + { + "id": "cl4fr7lr90004396mh9vw8wnq", + "type": 0, + "blockId": "block1", + "content": "Item 3" + } + ], + "groupId": "block1", + "options": { "buttonLabel": "Send", "isMultipleChoice": false } + } + ], + "graphCoordinates": { "x": 199, "y": 210 } + }, + { + "id": "cl4fr7wsv0007396m7xgbeymx", + "title": "Group #2", + "blocks": [ + { + "id": "cl4fr7wsv0008396mf9oi9lvi", + "type": "text", + "content": { + "html": "
Ok great!
", + "richText": [ + { "type": "p", "children": [{ "text": "Ok great!" }] } + ], + "plainText": "Ok great!" + }, + "groupId": "cl4fr7wsv0007396m7xgbeymx" + } + ], + "graphCoordinates": { "x": 603, "y": 195 } + } + ], + "variables": [{ "id": "vcl4fr8f8l000b396m6gsbnrmd", "name": "Item 2" }], + "edges": [ + { + "id": "edge1", + "to": { "groupId": "block1" }, + "from": { "blockId": "block0", "groupId": "block0" } + }, + { + "id": "cl4fr80900009396my6euvunj", + "to": { "groupId": "cl4fr7wsv0007396m7xgbeymx" }, + "from": { + "itemId": "cl4fr7e6i0003396mkh7mol65", + "blockId": "block1", + "groupId": "block1" + } + } + ], + "theme": { + "chat": { + "inputs": { + "color": "#303235", + "backgroundColor": "#FFFFFF", + "placeholderColor": "#9095A0" + }, + "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" }, + "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" }, + "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" } + }, + "general": { "font": "Open Sans", "background": { "type": "None" } } + }, + "settings": { + "general": { + "isBrandingEnabled": true, + "isInputPrefillEnabled": true, + "isHideQueryParamsEnabled": true, + "isNewResultOnRefreshEnabled": false + }, + "metadata": { + "description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form." + }, + "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 } + }, + "publicId": null, + "customDomain": null, + "workspaceId": "proWorkspace" +} diff --git a/apps/builder/playwright/tests/inputs/buttons.spec.ts b/apps/builder/playwright/tests/inputs/buttons.spec.ts index 7cf252cae..94e287740 100644 --- a/apps/builder/playwright/tests/inputs/buttons.spec.ts +++ b/apps/builder/playwright/tests/inputs/buttons.spec.ts @@ -1,11 +1,13 @@ import test, { expect } from '@playwright/test' import { createTypebots, + importTypebotInDatabase, parseDefaultGroupWithBlock, } from '../../services/database' import { defaultChoiceInputOptions, InputBlockType, ItemType } from 'models' import { typebotViewer } from '../../services/selectorUtils' import cuid from 'cuid' +import path from 'path' test.describe.parallel('Buttons input block', () => { test('can edit button items', async ({ page }) => { @@ -67,3 +69,30 @@ test.describe.parallel('Buttons input block', () => { ).toBeVisible() }) }) + +test('Variable buttons should work', async ({ page }) => { + const typebotId = cuid() + await importTypebotInDatabase( + path.join(__dirname, '../../fixtures/typebots/inputs/variableButton.json'), + { + id: typebotId, + } + ) + + await page.goto(`/typebots/${typebotId}/edit`) + await page.click('text=Preview') + await typebotViewer(page).locator('text=Variable item').click() + await expect(typebotViewer(page).locator('text=Variable item')).toBeVisible() + await expect(typebotViewer(page).locator('text=Ok great!')).toBeVisible() + await page.click('text="Item 1"') + await page.fill('input[value="Item 1"]', '{{Item 2}}') + await page.click('[data-testid="block1-icon"]') + await page.click('text=Multiple choice?') + await page.click('text="Restart"') + await typebotViewer(page).locator('text="Variable item" >> nth=0').click() + await typebotViewer(page).locator('text="Variable item" >> nth=1').click() + await typebotViewer(page).locator('text="Send"').click() + await expect( + typebotViewer(page).locator('text="Variable item, Variable item"') + ).toBeVisible() +}) diff --git a/packages/bot-engine/src/components/ChatGroup/ChatBlock/InputChatBlock.tsx b/packages/bot-engine/src/components/ChatGroup/ChatBlock/InputChatBlock.tsx index 450035504..8f0835092 100644 --- a/packages/bot-engine/src/components/ChatGroup/ChatBlock/InputChatBlock.tsx +++ b/packages/bot-engine/src/components/ChatGroup/ChatBlock/InputChatBlock.tsx @@ -13,7 +13,11 @@ import { PaymentForm } from './inputs/PaymentForm' import { RatingForm } from './inputs/RatingForm' import { FileUploadForm } from './inputs/FileUploadForm' -export type InputSubmitContent = { label?: string; value: string } +export type InputSubmitContent = { + label?: string + value: string + itemId?: string +} export const InputChatBlock = ({ block, @@ -40,7 +44,7 @@ export const InputChatBlock = ({ ? variableId && typebot.variables.find(byId(variableId))?.value : undefined - const handleSubmit = async ({ label, value }: InputSubmitContent) => { + const handleSubmit = async ({ label, value, itemId }: InputSubmitContent) => { setAnswer(label ?? value) const isRetry = !isInputValid(value, block.type) if (!isRetry && addAnswer) @@ -50,7 +54,7 @@ export const InputChatBlock = ({ content: value, variableId: variableId ?? null, }) - if (!isEditting) onTransitionEnd({ label, value }, isRetry) + if (!isEditting) onTransitionEnd({ label, value, itemId }, isRetry) setIsEditting(false) } diff --git a/packages/bot-engine/src/components/ChatGroup/ChatBlock/inputs/ChoiceForm.tsx b/packages/bot-engine/src/components/ChatGroup/ChatBlock/inputs/ChoiceForm.tsx index 13722cb62..1ca5475e1 100644 --- a/packages/bot-engine/src/components/ChatGroup/ChatBlock/inputs/ChoiceForm.tsx +++ b/packages/bot-engine/src/components/ChatGroup/ChatBlock/inputs/ChoiceForm.tsx @@ -1,6 +1,8 @@ import { useAnswers } from 'contexts/AnswersContext' +import { useTypebot } from 'contexts/TypebotContext' import { ChoiceInputBlock } from 'models' import React, { useState } from 'react' +import { parseVariables } from 'services/variable' import { InputSubmitContent } from '../InputChatBlock' import { SendButton } from './SendButton' @@ -10,13 +12,20 @@ type ChoiceFormProps = { } export const ChoiceForm = ({ block, onSubmit }: ChoiceFormProps) => { + const { + typebot: { variables }, + } = useTypebot() const { resultValues } = useAnswers() const [selectedIndices, setSelectedIndices] = useState([]) const handleClick = (itemIndex: number) => (e: React.MouseEvent) => { e.preventDefault() if (block.options?.isMultipleChoice) toggleSelectedItemIndex(itemIndex) - else onSubmit({ value: block.items[itemIndex].content ?? '' }) + else + onSubmit({ + value: parseVariables(variables)(block.items[itemIndex].content), + itemId: block.items[itemIndex].id, + }) } const toggleSelectedItemIndex = (itemIndex: number) => { @@ -32,7 +41,9 @@ export const ChoiceForm = ({ block, onSubmit }: ChoiceFormProps) => { const handleSubmit = () => onSubmit({ value: selectedIndices - .map((itemIndex) => block.items[itemIndex].content) + .map((itemIndex) => + parseVariables(variables)(block.items[itemIndex].content) + ) .join(', '), }) @@ -59,7 +70,7 @@ export const ChoiceForm = ({ block, onSubmit }: ChoiceFormProps) => { data-testid="button" data-itemid={item.id} > - {item.content} + {parseVariables(variables)(item.content)} {isUniqueFirstButton && ( diff --git a/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx b/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx index 9fad6a9f7..61be91f9b 100644 --- a/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx +++ b/packages/bot-engine/src/components/ChatGroup/ChatGroup.tsx @@ -10,6 +10,7 @@ import { isInputBlock, isIntegrationBlock, isLogicBlock, + byId, } from 'utils' import { executeLogic } from 'services/logic' import { executeIntegration } from 'services/integration' @@ -187,7 +188,7 @@ export const ChatGroup = ({ isChoiceInput(currentBlock) && !currentBlock.options.isMultipleChoice if (isSingleChoiceBlock) { const nextEdgeId = currentBlock.items.find( - (i) => i.content === answerContent?.value + byId(answerContent?.itemId) )?.outgoingEdgeId if (nextEdgeId) return onGroupEnd({ edgeId: nextEdgeId }) }