From 8a350eee6c93abec6bce40351a0b4ef0b854defe Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Wed, 2 Feb 2022 08:05:02 +0100 Subject: [PATCH] =?UTF-8?q?refactor(editor):=20=E2=99=BB=EF=B8=8F=20Undo?= =?UTF-8?q?=20/=20Redo=20buttons=20+=20structure=20refacto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yet another huge refacto... While implementing undo and redo features I understood that I updated the stored typebot too many times (i.e. on each key input) so I had to rethink it entirely. I also moved around some files. --- apps/builder/assets/icons.tsx | 14 ++ .../analytics/graph/AnalyticsGraph.tsx | 59 ------- .../analytics/graph/Edges/DropOffEdge.tsx | 19 --- .../components/analytics/graph/Edges/Edge.tsx | 88 ---------- .../analytics/graph/Edges/Edges.tsx | 58 ------- .../components/analytics/graph/Edges/index.ts | 1 - .../analytics/graph/blocks/BlockNode.tsx | 62 ------- .../analytics/graph/blocks/StepsList.tsx | 24 --- .../StepNode/ChoiceInputStepNode/index.tsx | 1 - .../StepNode/ContentPopover/index.tsx | 1 - .../StepNodeContent/StepNodeContent.tsx | 118 -------------- .../BlockNode/StepNode/TextEditor/index.tsx | 1 - .../board/graph/BlockNode/StepNode/index.tsx | 2 - .../components/board/graph/Edges/Edge.tsx | 100 ------------ .../{board => editor}/BoardMenuButton.tsx | 0 .../StepsSideBar/StepCard.tsx | 0 .../StepsSideBar/StepIcon.tsx | 0 .../StepsSideBar/StepSideBar.tsx | 26 ++- .../StepsSideBar/StepTypeLabel.tsx | 0 .../{board => editor}/StepsSideBar/index.tsx | 0 .../preview/PreviewDrawer.tsx | 0 apps/builder/components/shared/CodeEditor.tsx | 11 +- .../builder/components/shared/ContextMenu.tsx | 2 + .../Graph}/Edges/DrawingEdge.tsx | 35 ++-- .../components/shared/Graph/Edges/Edge.tsx | 87 ++++++++++ .../graph => shared/Graph}/Edges/Edges.tsx | 0 .../graph => shared/Graph}/Edges/index.tsx | 0 .../Graph/Endpoints}/SourceEndpoint.tsx | 15 +- .../Graph/Endpoints}/TargetEndpoint.tsx | 0 .../shared/Graph/Endpoints/index.tsx | 2 + .../{board/graph => shared/Graph}/Graph.tsx | 35 +++- .../Graph/Nodes}/BlockNode/BlockNode.tsx | 51 ++++-- .../Nodes}/BlockNode/BlockNodeContextMenu.tsx | 0 .../Graph/Nodes}/BlockNode/index.tsx | 0 .../Graph/Nodes/ButtonNode/ButtonNode.tsx} | 19 +-- .../ButtonNode/ButtonNodeContextMenu.tsx} | 2 +- .../Nodes/ButtonNode/ButtonNodeOverlay.tsx} | 7 +- .../Nodes/ButtonNode/ButtonNodesList.tsx} | 12 +- .../shared/Graph/Nodes/ButtonNode/index.tsx | 1 + .../Graph/Nodes/DropOffNode.tsx} | 6 +- .../MediaBubblePopoverContent.tsx} | 27 +--- .../VideoUploadContent.tsx | 0 .../MediaBubblePopoverContent/index.tsx | 1 + .../SettingsPopoverContent/SettingsModal.tsx | 0 .../SettingsPopoverContent.tsx | 72 +++++---- .../bodies/ChoiceInputSettingsBody.tsx | 0 .../ConditionSettingsBody/ComparisonsItem.tsx | 0 .../ConditonSettingsBody.tsx | 0 .../bodies/ConditionSettingsBody/index.tsx | 0 .../bodies/DateInputSettingsBody.tsx | 0 .../bodies/EmailInputSettingsBody.tsx | 0 .../bodies/GoogleAnalyticsSettings.tsx | 0 .../CellWithValueStack.tsx | 0 .../CellWithVariableIdStack.tsx | 0 .../GoogleSheetsSettingsBody.tsx | 52 ++++-- .../SheetsDropdown.tsx | 0 .../SpreadsheetDropdown.tsx | 0 .../bodies/GoogleSheetsSettingsBody/index.tsx | 0 .../bodies/NumberInputSettingsBody.tsx | 0 .../bodies/PhoneNumberSettingsBody.tsx | 0 .../bodies/RedirectSettings.tsx | 0 .../bodies/SetVariableSettings.tsx} | 5 +- .../bodies/TextInputSettingsBody.tsx | 0 .../bodies/UrlInputSettingsBody.tsx | 0 .../bodies/WebhookSettings/KeyValueInputs.tsx | 0 .../WebhookSettings/ResponseMappingInputs.tsx | 0 .../WebhookSettings/VariableForTestInputs.tsx | 0 .../WebhookSettings/WebhookSettings.tsx | 49 +++--- .../bodies/WebhookSettings/index.tsx | 0 .../SettingsPopoverContent/bodies/index.ts | 0 .../StepNode/SettingsPopoverContent/index.ts | 0 .../Graph/Nodes}/StepNode/StepNode.tsx | 151 ++++++++++++----- .../StepNodeContent/StepNodeContent.tsx | 104 ++++++++++++ .../contents/ConditionContent.tsx} | 4 +- .../contents/ConfigureContent.tsx | 10 ++ .../contents/ImageBubbleContent.tsx | 16 ++ .../contents/PlaceholderContent.tsx | 8 + .../contents/SetVariableContent.tsx} | 2 +- .../contents/TextBubbleContent.tsx} | 2 +- .../contents/VideoBubbleContent.tsx} | 2 +- .../contents}/WebhookContent.tsx | 0 .../contents/WithVariableContent.tsx} | 2 +- .../StepNodeContent/contents/index.tsx | 6 + .../Nodes}/StepNode/StepNodeContent/index.tsx | 0 .../Nodes}/StepNode/StepNodeContextMenu.tsx | 0 .../Graph/Nodes}/StepNode/StepNodeOverlay.tsx | 2 +- .../Graph/Nodes/StepNode/StepNodesList.tsx} | 23 +-- .../TextBubbleEditor/TextBubbleEditor.tsx} | 22 +-- .../StepNode/TextBubbleEditor}/ToolBar.tsx | 0 .../Nodes/StepNode/TextBubbleEditor/index.tsx | 1 + .../shared/Graph/Nodes/StepNode/index.tsx | 1 + .../builder/components/shared/Graph/index.tsx | 1 + apps/builder/components/shared/TableList.tsx | 7 +- .../TypebotHeader/EditableTypebotName.tsx | 2 +- .../shared/TypebotHeader/TypebotHeader.tsx | 44 +++-- .../components/shared/VariableSearchInput.tsx | 4 +- .../contexts/AnalyticsGraphProvider.tsx | 62 ------- apps/builder/contexts/GraphContext.tsx | 50 +++++- .../TypebotContext/TypebotContext.tsx | 152 +++++++++++------- .../contexts/TypebotContext/actions/blocks.ts | 65 +++++--- .../TypebotContext/actions/choiceItems.ts | 48 +++--- .../contexts/TypebotContext/actions/edges.ts | 96 ++++++----- .../contexts/TypebotContext/actions/steps.ts | 89 ++++++---- .../TypebotContext/actions/variables.ts | 38 +++-- .../TypebotContext/actions/webhooks.ts | 33 ++-- apps/builder/contexts/UserContext.tsx | 8 +- .../board => layouts/editor}/Board.tsx | 13 +- .../layouts/results/AnalyticsContent.tsx | 19 +-- apps/builder/libs/kbar.ts | 17 +- apps/builder/package.json | 1 + apps/builder/pages/_app.tsx | 18 ++- .../pages/typebots/[typebotId]/edit.tsx | 16 +- apps/builder/playwright.config.ts | 1 + .../typebots/integrations/webhook.json | 143 ++++++++-------- .../typebots/integrations/webhookPreview.json | 2 +- .../playwright/fixtures/typebots/theme.json | 2 +- apps/builder/playwright/global-setup.ts | 17 +- apps/builder/playwright/services/database.ts | 36 ++--- apps/builder/playwright/tests/account.spec.ts | 1 + .../playwright/tests/bubbles/image.spec.ts | 9 +- .../playwright/tests/bubbles/text.spec.ts | 4 +- .../playwright/tests/bubbles/video.spec.ts | 9 +- .../playwright/tests/dashboard.spec.ts | 16 +- apps/builder/playwright/tests/editor.spec.ts | 28 ++++ .../playwright/tests/inputs/buttons.spec.ts | 4 +- .../playwright/tests/inputs/date.spec.ts | 4 +- .../playwright/tests/inputs/email.spec.ts | 4 +- .../playwright/tests/inputs/number.spec.ts | 4 +- .../playwright/tests/inputs/phone.spec.ts | 4 +- .../playwright/tests/inputs/text.spec.ts | 4 +- .../playwright/tests/inputs/url.spec.ts | 4 +- .../integrations/googleAnalytics.spec.ts | 4 +- .../tests/integrations/googleSheets.spec.ts | 24 +-- .../tests/integrations/webhook.spec.ts | 10 +- .../playwright/tests/logic/condition.spec.ts | 7 +- .../playwright/tests/logic/redirect.spec.ts | 3 +- .../tests/logic/setVariable.spec.ts | 5 +- apps/builder/playwright/tests/results.spec.ts | 3 +- .../builder/playwright/tests/settings.spec.ts | 7 +- apps/builder/playwright/tests/theme.spec.ts | 5 +- apps/builder/services/graph.ts | 70 ++++---- apps/builder/services/publicTypebot.tsx | 2 +- apps/builder/services/typebots.ts | 22 +-- apps/builder/services/utils/index.ts | 2 + apps/builder/services/utils/useUndo.ts | 127 +++++++++++++++ apps/builder/services/{ => utils}/utils.ts | 0 .../bot-engine/src/services/integration.ts | 4 +- packages/bot-engine/src/services/logic.ts | 4 +- .../models/src/typebot/steps/integration.ts | 52 +++--- packages/models/src/typebot/typebot.ts | 2 +- packages/models/src/utils.ts | 2 + packages/utils/src/utils.ts | 4 + yarn.lock | 5 + 153 files changed, 1512 insertions(+), 1352 deletions(-) delete mode 100644 apps/builder/components/analytics/graph/AnalyticsGraph.tsx delete mode 100644 apps/builder/components/analytics/graph/Edges/DropOffEdge.tsx delete mode 100644 apps/builder/components/analytics/graph/Edges/Edge.tsx delete mode 100644 apps/builder/components/analytics/graph/Edges/Edges.tsx delete mode 100644 apps/builder/components/analytics/graph/Edges/index.ts delete mode 100644 apps/builder/components/analytics/graph/blocks/BlockNode.tsx delete mode 100644 apps/builder/components/analytics/graph/blocks/StepsList.tsx delete mode 100644 apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/index.tsx delete mode 100644 apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/index.tsx delete mode 100644 apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx delete mode 100644 apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/index.tsx delete mode 100644 apps/builder/components/board/graph/BlockNode/StepNode/index.tsx delete mode 100644 apps/builder/components/board/graph/Edges/Edge.tsx rename apps/builder/components/{board => editor}/BoardMenuButton.tsx (100%) rename apps/builder/components/{board => editor}/StepsSideBar/StepCard.tsx (100%) rename apps/builder/components/{board => editor}/StepsSideBar/StepIcon.tsx (100%) rename apps/builder/components/{board => editor}/StepsSideBar/StepSideBar.tsx (90%) rename apps/builder/components/{board => editor}/StepsSideBar/StepTypeLabel.tsx (100%) rename apps/builder/components/{board => editor}/StepsSideBar/index.tsx (100%) rename apps/builder/components/{board => editor}/preview/PreviewDrawer.tsx (100%) rename apps/builder/components/{board/graph => shared/Graph}/Edges/DrawingEdge.tsx (81%) create mode 100644 apps/builder/components/shared/Graph/Edges/Edge.tsx rename apps/builder/components/{board/graph => shared/Graph}/Edges/Edges.tsx (100%) rename apps/builder/components/{board/graph => shared/Graph}/Edges/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode/StepNode => shared/Graph/Endpoints}/SourceEndpoint.tsx (67%) rename apps/builder/components/{board/graph/BlockNode/StepNode => shared/Graph/Endpoints}/TargetEndpoint.tsx (100%) create mode 100644 apps/builder/components/shared/Graph/Endpoints/index.tsx rename apps/builder/components/{board/graph => shared/Graph}/Graph.tsx (78%) rename apps/builder/components/{board/graph => shared/Graph/Nodes}/BlockNode/BlockNode.tsx (70%) rename apps/builder/components/{board/graph => shared/Graph/Nodes}/BlockNode/BlockNodeContextMenu.tsx (100%) rename apps/builder/components/{board/graph => shared/Graph/Nodes}/BlockNode/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNode.tsx => shared/Graph/Nodes/ButtonNode/ButtonNode.tsx} (91%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNodeContextMenu.tsx => shared/Graph/Nodes/ButtonNode/ButtonNodeContextMenu.tsx} (84%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNodeOverlay.tsx => shared/Graph/Nodes/ButtonNode/ButtonNodeOverlay.tsx} (76%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemsList.tsx => shared/Graph/Nodes/ButtonNode/ButtonNodesList.tsx} (93%) create mode 100644 apps/builder/components/shared/Graph/Nodes/ButtonNode/index.tsx rename apps/builder/components/{analytics/graph/blocks/DropOffBlock.tsx => shared/Graph/Nodes/DropOffNode.tsx} (92%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ContentPopover/ContentPopover.tsx => shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/MediaBubblePopoverContent.tsx} (57%) rename apps/builder/components/{board/graph/BlockNode/StepNode/ContentPopover => shared/Graph/Nodes/StepNode/MediaBubblePopoverContent}/VideoUploadContent.tsx (100%) create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/index.tsx rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/SettingsModal.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx (70%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleAnalyticsSettings.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx (81%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/RedirectSettings.tsx (100%) rename apps/builder/components/{board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx => shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/SetVariableSettings.tsx} (93%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx (84%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/bodies/index.ts (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/SettingsPopoverContent/index.ts (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/StepNode.tsx (60%) create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/StepNodeContent.tsx rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx => shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConditionContent.tsx} (92%) create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConfigureContent.tsx create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ImageBubbleContent.tsx create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/PlaceholderContent.tsx rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx => shared/Graph/Nodes/StepNode/StepNodeContent/contents/SetVariableContent.tsx} (86%) rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent/TextBubbleNodeContent.tsx => shared/Graph/Nodes/StepNode/StepNodeContent/contents/TextBubbleContent.tsx} (91%) rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent/VideoStepNodeContent.tsx => shared/Graph/Nodes/StepNode/StepNodeContent/contents/VideoBubbleContent.tsx} (94%) rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent => shared/Graph/Nodes/StepNode/StepNodeContent/contents}/WebhookContent.tsx (100%) rename apps/builder/components/{board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContentWithVariable.tsx => shared/Graph/Nodes/StepNode/StepNodeContent/contents/WithVariableContent.tsx} (89%) create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/index.tsx rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/StepNodeContent/index.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/StepNodeContextMenu.tsx (100%) rename apps/builder/components/{board/graph/BlockNode => shared/Graph/Nodes}/StepNode/StepNodeOverlay.tsx (89%) rename apps/builder/components/{board/graph/BlockNode/StepsList.tsx => shared/Graph/Nodes/StepNode/StepNodesList.tsx} (86%) rename apps/builder/components/{board/graph/BlockNode/StepNode/TextEditor/TextEditor.tsx => shared/Graph/Nodes/StepNode/TextBubbleEditor/TextBubbleEditor.tsx} (94%) rename apps/builder/components/{board/graph/BlockNode/StepNode/TextEditor => shared/Graph/Nodes/StepNode/TextBubbleEditor}/ToolBar.tsx (100%) create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/index.tsx create mode 100644 apps/builder/components/shared/Graph/Nodes/StepNode/index.tsx create mode 100644 apps/builder/components/shared/Graph/index.tsx delete mode 100644 apps/builder/contexts/AnalyticsGraphProvider.tsx rename apps/builder/{components/board => layouts/editor}/Board.tsx (62%) create mode 100644 apps/builder/playwright/tests/editor.spec.ts create mode 100644 apps/builder/services/utils/index.ts create mode 100644 apps/builder/services/utils/useUndo.ts rename apps/builder/services/{ => utils}/utils.ts (100%) diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx index ae6959dd0..56de365d5 100644 --- a/apps/builder/assets/icons.tsx +++ b/apps/builder/assets/icons.tsx @@ -311,3 +311,17 @@ export const UnlockedIcon = (props: IconProps) => ( ) + +export const UndoIcon = (props: IconProps) => ( + + + + +) + +export const RedoIcon = (props: IconProps) => ( + + + + +) diff --git a/apps/builder/components/analytics/graph/AnalyticsGraph.tsx b/apps/builder/components/analytics/graph/AnalyticsGraph.tsx deleted file mode 100644 index 7b52a6cc5..000000000 --- a/apps/builder/components/analytics/graph/AnalyticsGraph.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Flex, FlexProps, useEventListener } from '@chakra-ui/react' -import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider' -import React, { useMemo, useRef } from 'react' -import { AnswersCount } from 'services/analytics' -import { BlockNode } from './blocks/BlockNode' -import { Edges } from './Edges' - -const AnalyticsGraph = ({ - answersCounts, - ...props -}: { answersCounts?: AnswersCount[] } & FlexProps) => { - const { typebot, graphPosition, setGraphPosition } = useAnalyticsGraph() - const graphContainerRef = useRef(null) - const transform = useMemo( - () => - `translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`, - [graphPosition] - ) - - const handleMouseWheel = (e: WheelEvent) => { - e.preventDefault() - const isPinchingTrackpad = e.ctrlKey - if (isPinchingTrackpad) { - const scale = graphPosition.scale - e.deltaY * 0.01 - if (scale <= 0.2 || scale >= 1) return - setGraphPosition({ - ...graphPosition, - scale, - }) - } else - setGraphPosition({ - ...graphPosition, - x: graphPosition.x - e.deltaX, - y: graphPosition.y - e.deltaY, - }) - } - useEventListener('wheel', handleMouseWheel, graphContainerRef.current) - - if (!typebot) return <> - return ( - - - - {typebot.blocks.allIds.map((blockId) => ( - - ))} - - - ) -} - -export default AnalyticsGraph diff --git a/apps/builder/components/analytics/graph/Edges/DropOffEdge.tsx b/apps/builder/components/analytics/graph/Edges/DropOffEdge.tsx deleted file mode 100644 index 430568d4a..000000000 --- a/apps/builder/components/analytics/graph/Edges/DropOffEdge.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider' -import React, { useMemo } from 'react' -import { computeDropOffPath } from 'services/graph' - -type Props = { - blockId: string -} -export const DropOffEdge = ({ blockId }: Props) => { - const { typebot } = useAnalyticsGraph() - - const path = useMemo(() => { - if (!typebot) return - const block = typebot.blocks.byId[blockId] - if (!block) return '' - return computeDropOffPath(block.graphCoordinates, block.stepIds.length - 1) - }, [blockId, typebot]) - - return -} diff --git a/apps/builder/components/analytics/graph/Edges/Edge.tsx b/apps/builder/components/analytics/graph/Edges/Edge.tsx deleted file mode 100644 index 147887212..000000000 --- a/apps/builder/components/analytics/graph/Edges/Edge.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider' -import { useGraph } from 'contexts/GraphContext' -import React, { useEffect, useMemo, useState } from 'react' -import { - getAnchorsPosition, - computeEdgePath, - getEndpointTopOffset, - getSourceEndpointId, -} from 'services/graph' - -type Props = { edgeId: string } - -export const Edge = ({ edgeId }: Props) => { - const { typebot } = useAnalyticsGraph() - const edge = typebot?.edges.byId[edgeId] - const { sourceEndpoints, targetEndpoints, graphPosition } = useGraph() - const [sourceTop, setSourceTop] = useState( - getEndpointTopOffset( - graphPosition, - sourceEndpoints, - getSourceEndpointId(edge) - ) - ) - const [targetTop, setTargetTop] = useState( - getEndpointTopOffset(graphPosition, sourceEndpoints, edge?.to.stepId) - ) - - useEffect(() => { - const newSourceTop = getEndpointTopOffset( - graphPosition, - sourceEndpoints, - getSourceEndpointId(edge) - ) - const sensibilityThreshold = 10 - const newSourceTopIsTooClose = - sourceTop < newSourceTop + sensibilityThreshold && - sourceTop > newSourceTop - sensibilityThreshold - if (newSourceTopIsTooClose) return - setSourceTop(newSourceTop) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [graphPosition]) - - useEffect(() => { - const newTargetTop = getEndpointTopOffset( - graphPosition, - targetEndpoints, - edge?.to.stepId - ) - const sensibilityThreshold = 10 - const newSourceTopIsTooClose = - targetTop < newTargetTop + sensibilityThreshold && - targetTop > newTargetTop - sensibilityThreshold - if (newSourceTopIsTooClose) return - setTargetTop(newTargetTop) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [graphPosition]) - - const { sourceBlock, targetBlock } = useMemo(() => { - if (!typebot || !edge) return {} - const targetBlock = typebot.blocks.byId[edge.to.blockId] - const sourceBlock = typebot.blocks.byId[edge.from.blockId] - return { - sourceBlock, - targetBlock, - } - }, [edge, typebot]) - - const path = useMemo(() => { - if (!sourceBlock || !targetBlock) return `` - const anchorsPosition = getAnchorsPosition({ - sourceBlock, - targetBlock, - sourceTop, - targetTop, - }) - return computeEdgePath(anchorsPosition) - }, [sourceBlock, sourceTop, targetBlock, targetTop]) - - return ( - - ) -} diff --git a/apps/builder/components/analytics/graph/Edges/Edges.tsx b/apps/builder/components/analytics/graph/Edges/Edges.tsx deleted file mode 100644 index ecbe94f4e..000000000 --- a/apps/builder/components/analytics/graph/Edges/Edges.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { chakra } from '@chakra-ui/system' -import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider' -import React from 'react' -import { AnswersCount } from 'services/analytics' -import { DropOffBlock } from '../blocks/DropOffBlock' -import { DropOffEdge } from './DropOffEdge' -import { Edge } from './Edge' - -type Props = { answersCounts?: AnswersCount[] } - -export const Edges = ({ answersCounts }: Props) => { - const { typebot } = useAnalyticsGraph() - - return ( - <> - - {typebot?.edges.allIds.map((edgeId) => ( - - ))} - - - - {answersCounts?.map((answersCount) => ( - - ))} - - {answersCounts?.map((answersCount) => ( - - ))} - - ) -} diff --git a/apps/builder/components/analytics/graph/Edges/index.ts b/apps/builder/components/analytics/graph/Edges/index.ts deleted file mode 100644 index 1f83b5a43..000000000 --- a/apps/builder/components/analytics/graph/Edges/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Edges } from './Edges' diff --git a/apps/builder/components/analytics/graph/blocks/BlockNode.tsx b/apps/builder/components/analytics/graph/blocks/BlockNode.tsx deleted file mode 100644 index 3b7c16906..000000000 --- a/apps/builder/components/analytics/graph/blocks/BlockNode.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { - Editable, - EditableInput, - EditablePreview, - Stack, - useEventListener, -} from '@chakra-ui/react' -import React, { useState } from 'react' -import { useAnalyticsGraph } from 'contexts/AnalyticsGraphProvider' -import { StepsList } from './StepsList' -import { Block } from 'models' - -type Props = { - block: Block -} - -export const BlockNode = ({ block }: Props) => { - const { updateBlockPosition } = useAnalyticsGraph() - const [isMouseDown, setIsMouseDown] = useState(false) - - const handleMouseDown = () => { - setIsMouseDown(true) - } - const handleMouseUp = () => { - setIsMouseDown(false) - } - - const handleMouseMove = (event: MouseEvent) => { - if (!isMouseDown) return - const { movementX, movementY } = event - - updateBlockPosition(block.id, { - x: block.graphCoordinates.x + movementX, - y: block.graphCoordinates.y + movementY, - }) - } - - useEventListener('mousemove', handleMouseMove) - - return ( - - - - - - - - ) -} diff --git a/apps/builder/components/analytics/graph/blocks/StepsList.tsx b/apps/builder/components/analytics/graph/blocks/StepsList.tsx deleted file mode 100644 index 30e094253..000000000 --- a/apps/builder/components/analytics/graph/blocks/StepsList.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Flex, Stack } from '@chakra-ui/react' -import { StepNodeOverlay } from 'components/board/graph/BlockNode/StepNode' -import { useTypebot } from 'contexts/TypebotContext/TypebotContext' - -export const StepsList = ({ stepIds }: { stepIds: string[] }) => { - const { typebot } = useTypebot() - return ( - - - {typebot && - stepIds.map((stepId) => ( - - - - - ))} - - ) -} diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/index.tsx deleted file mode 100644 index 6f3594fa7..000000000 --- a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ChoiceItemsList as ChoiceInputStepNodeContent } from './ChoiceItemsList' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/index.tsx deleted file mode 100644 index b17d69e50..000000000 --- a/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ContentPopover } from './ContentPopover' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx deleted file mode 100644 index e9d011985..000000000 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Box, Image, Text } from '@chakra-ui/react' -import { - Step, - StartStep, - BubbleStepType, - InputStepType, - LogicStepType, - IntegrationStepType, -} from 'models' -import { isInputStep } from 'utils' -import { ChoiceItemsList } from '../ChoiceInputStepNode/ChoiceItemsList' -import { ConditionNodeContent } from './ConditionNodeContent' -import { SetVariableNodeContent } from './SetVariableNodeContent' -import { StepNodeContentWithVariable } from './StepNodeContentWithVariable' -import { TextBubbleNodeContent } from './TextBubbleNodeContent' -import { VideoStepNodeContent } from './VideoStepNodeContent' -import { WebhookContent } from './WebhookContent' - -type Props = { - step: Step | StartStep - isConnectable?: boolean -} -export const StepNodeContent = ({ step }: Props) => { - if (isInputStep(step) && step.options.variableId) { - return - } - switch (step.type) { - case BubbleStepType.TEXT: { - return - } - case BubbleStepType.IMAGE: { - return !step.content?.url ? ( - Click to edit... - ) : ( - - Step image - - ) - } - case BubbleStepType.VIDEO: { - return - } - case InputStepType.TEXT: { - return ( - - {step.options?.labels?.placeholder ?? 'Type your answer...'} - - ) - } - case InputStepType.NUMBER: { - return ( - - {step.options?.labels?.placeholder ?? 'Type your answer...'} - - ) - } - case InputStepType.EMAIL: { - return ( - - {step.options?.labels?.placeholder ?? 'Type your email...'} - - ) - } - case InputStepType.URL: { - return ( - - {step.options?.labels?.placeholder ?? 'Type your URL...'} - - ) - } - case InputStepType.DATE: { - return Pick a date... - } - case InputStepType.PHONE: { - return ( - - {step.options?.labels?.placeholder ?? 'Your phone number...'} - - ) - } - case InputStepType.CHOICE: { - return - } - case LogicStepType.SET_VARIABLE: { - return - } - case LogicStepType.CONDITION: { - return - } - case LogicStepType.REDIRECT: { - if (!step.options.url) return Configure... - return Redirect to {step.options?.url} - } - case IntegrationStepType.GOOGLE_SHEETS: { - if (!step.options) return Configure... - return {step.options?.action} - } - case IntegrationStepType.GOOGLE_ANALYTICS: { - if (!step.options || !step.options.action) - return Configure... - return Track "{step.options?.action}" - } - case IntegrationStepType.WEBHOOK: { - return - } - case 'start': { - return {step.label} - } - default: { - return No input - } - } -} diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/index.tsx deleted file mode 100644 index 74fb5325a..000000000 --- a/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { TextEditor } from './TextEditor' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/index.tsx deleted file mode 100644 index 2284b1b24..000000000 --- a/apps/builder/components/board/graph/BlockNode/StepNode/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { StepNode } from './StepNode' -export { StepNodeOverlay } from './StepNodeOverlay' diff --git a/apps/builder/components/board/graph/Edges/Edge.tsx b/apps/builder/components/board/graph/Edges/Edge.tsx deleted file mode 100644 index 997974cf7..000000000 --- a/apps/builder/components/board/graph/Edges/Edge.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Coordinates, useGraph } from 'contexts/GraphContext' -import { useTypebot } from 'contexts/TypebotContext/TypebotContext' -import React, { useEffect, useMemo, useState } from 'react' -import { - getAnchorsPosition, - computeEdgePath, - getEndpointTopOffset, - getSourceEndpointId, -} from 'services/graph' - -export type AnchorsPositionProps = { - sourcePosition: Coordinates - targetPosition: Coordinates - sourceType: 'right' | 'left' - totalSegments: number -} - -export const Edge = ({ edgeId }: { edgeId: string }) => { - const { typebot } = useTypebot() - const { previewingEdgeId, sourceEndpoints, targetEndpoints, graphPosition } = - useGraph() - const edge = useMemo( - () => typebot?.edges.byId[edgeId], - [edgeId, typebot?.edges.byId] - ) - const isPreviewing = previewingEdgeId === edgeId - const [sourceTop, setSourceTop] = useState( - getEndpointTopOffset( - graphPosition, - sourceEndpoints, - getSourceEndpointId(edge) - ) - ) - const [targetTop, setTargetTop] = useState( - getEndpointTopOffset(graphPosition, targetEndpoints, edge?.to.stepId) - ) - - useEffect(() => { - const newSourceTop = getEndpointTopOffset( - graphPosition, - sourceEndpoints, - getSourceEndpointId(edge) - ) - const sensibilityThreshold = 10 - const newSourceTopIsTooClose = - sourceTop < newSourceTop + sensibilityThreshold && - sourceTop > newSourceTop - sensibilityThreshold - if (newSourceTopIsTooClose) return - setSourceTop(newSourceTop) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [typebot?.blocks, typebot?.steps, graphPosition, sourceEndpoints]) - - useEffect(() => { - if (!edge) return - const newTargetTop = getEndpointTopOffset( - graphPosition, - targetEndpoints, - edge?.to.stepId - ) - const sensibilityThreshold = 10 - const newSourceTopIsTooClose = - targetTop < newTargetTop + sensibilityThreshold && - targetTop > newTargetTop - sensibilityThreshold - if (newSourceTopIsTooClose) return - setTargetTop(newTargetTop) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [typebot?.blocks, typebot?.steps, graphPosition, targetEndpoints]) - - const { sourceBlock, targetBlock } = useMemo(() => { - if (!typebot || !edge?.from.stepId) return {} - const sourceBlock = typebot.blocks.byId[edge.from.blockId] - const targetBlock = typebot.blocks.byId[edge.to.blockId] - return { - sourceBlock, - targetBlock, - } - }, [edge?.from.blockId, edge?.from.stepId, edge?.to.blockId, typebot]) - - const path = useMemo(() => { - if (!sourceBlock || !targetBlock) return `` - const anchorsPosition = getAnchorsPosition({ - sourceBlock, - targetBlock, - sourceTop, - targetTop, - }) - return computeEdgePath(anchorsPosition) - }, [sourceBlock, sourceTop, targetBlock, targetTop]) - - if (sourceTop === 0) return <> - return ( - - ) -} diff --git a/apps/builder/components/board/BoardMenuButton.tsx b/apps/builder/components/editor/BoardMenuButton.tsx similarity index 100% rename from apps/builder/components/board/BoardMenuButton.tsx rename to apps/builder/components/editor/BoardMenuButton.tsx diff --git a/apps/builder/components/board/StepsSideBar/StepCard.tsx b/apps/builder/components/editor/StepsSideBar/StepCard.tsx similarity index 100% rename from apps/builder/components/board/StepsSideBar/StepCard.tsx rename to apps/builder/components/editor/StepsSideBar/StepCard.tsx diff --git a/apps/builder/components/board/StepsSideBar/StepIcon.tsx b/apps/builder/components/editor/StepsSideBar/StepIcon.tsx similarity index 100% rename from apps/builder/components/board/StepsSideBar/StepIcon.tsx rename to apps/builder/components/editor/StepsSideBar/StepIcon.tsx diff --git a/apps/builder/components/board/StepsSideBar/StepSideBar.tsx b/apps/builder/components/editor/StepsSideBar/StepSideBar.tsx similarity index 90% rename from apps/builder/components/board/StepsSideBar/StepSideBar.tsx rename to apps/builder/components/editor/StepsSideBar/StepSideBar.tsx index f619fd54f..4482e6504 100644 --- a/apps/builder/components/board/StepsSideBar/StepSideBar.tsx +++ b/apps/builder/components/editor/StepsSideBar/StepSideBar.tsx @@ -1,6 +1,5 @@ import { Stack, - Input, Text, SimpleGrid, useEventListener, @@ -98,20 +97,17 @@ export const StepsSideBar = () => { spacing={6} userSelect="none" > - - - - : } - aria-label={isLocked ? 'Unlock' : 'Lock'} - size="sm" - variant="outline" - onClick={handleLockClick} - /> - - - - + + + : } + aria-label={isLocked ? 'Unlock' : 'Lock'} + size="sm" + variant="outline" + onClick={handleLockClick} + /> + + diff --git a/apps/builder/components/board/StepsSideBar/StepTypeLabel.tsx b/apps/builder/components/editor/StepsSideBar/StepTypeLabel.tsx similarity index 100% rename from apps/builder/components/board/StepsSideBar/StepTypeLabel.tsx rename to apps/builder/components/editor/StepsSideBar/StepTypeLabel.tsx diff --git a/apps/builder/components/board/StepsSideBar/index.tsx b/apps/builder/components/editor/StepsSideBar/index.tsx similarity index 100% rename from apps/builder/components/board/StepsSideBar/index.tsx rename to apps/builder/components/editor/StepsSideBar/index.tsx diff --git a/apps/builder/components/board/preview/PreviewDrawer.tsx b/apps/builder/components/editor/preview/PreviewDrawer.tsx similarity index 100% rename from apps/builder/components/board/preview/PreviewDrawer.tsx rename to apps/builder/components/editor/preview/PreviewDrawer.tsx diff --git a/apps/builder/components/shared/CodeEditor.tsx b/apps/builder/components/shared/CodeEditor.tsx index 7d95292f2..deed92364 100644 --- a/apps/builder/components/shared/CodeEditor.tsx +++ b/apps/builder/components/shared/CodeEditor.tsx @@ -2,7 +2,7 @@ import { Box, BoxProps } from '@chakra-ui/react' import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup' import { json } from '@codemirror/lang-json' import { css } from '@codemirror/lang-css' -import { useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' type Props = { value: string @@ -19,6 +19,7 @@ export const CodeEditor = ({ }: Props & Omit) => { const editorContainer = useRef(null) const editorView = useRef(null) + const [plainTextValue, setPlainTextValue] = useState(value) useEffect(() => { if (!editorView.current || !isReadOnly) return @@ -28,11 +29,17 @@ export const CodeEditor = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]) + useEffect(() => { + if (!onChange || plainTextValue === value) return + onChange(plainTextValue) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [plainTextValue]) + useEffect(() => { if (!editorContainer.current) return const updateListenerExtension = EditorView.updateListener.of((update) => { if (update.docChanged && onChange) - onChange(update.state.doc.toJSON().join(' ')) + setPlainTextValue(update.state.doc.toJSON().join(' ')) }) const extensions = [ updateListenerExtension, diff --git a/apps/builder/components/shared/ContextMenu.tsx b/apps/builder/components/shared/ContextMenu.tsx index 0e58dfb40..8f563aa32 100644 --- a/apps/builder/components/shared/ContextMenu.tsx +++ b/apps/builder/components/shared/ContextMenu.tsx @@ -25,6 +25,7 @@ export interface ContextMenuProps { menuProps?: MenuProps portalProps?: PortalProps menuButtonProps?: MenuButtonProps + isDisabled?: boolean } export function ContextMenu( @@ -56,6 +57,7 @@ export function ContextMenu( useEventListener( 'contextmenu', (e) => { + if (props.isDisabled) return if (e.currentTarget === targetRef.current) { e.preventDefault() e.stopPropagation() diff --git a/apps/builder/components/board/graph/Edges/DrawingEdge.tsx b/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx similarity index 81% rename from apps/builder/components/board/graph/Edges/DrawingEdge.tsx rename to apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx index fd9bf414a..28cf0787f 100644 --- a/apps/builder/components/board/graph/Edges/DrawingEdge.tsx +++ b/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx @@ -18,6 +18,7 @@ export const DrawingEdge = () => { connectingIds, sourceEndpoints, targetEndpoints, + blocksCoordinates, } = useGraph() const { typebot, createEdge } = useTypebot() const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) @@ -33,7 +34,7 @@ export const DrawingEdge = () => { return getEndpointTopOffset( graphPosition, sourceEndpoints, - connectingIds.source.nodeId ?? + connectingIds.source.buttonId ?? connectingIds.source.stepId + (connectingIds.source.conditionType ?? '') ) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -50,22 +51,38 @@ export const DrawingEdge = () => { }, [graphPosition, targetEndpoints, connectingIds]) const path = useMemo(() => { - if (!sourceBlock || !typebot || !connectingIds) return `` + if ( + !sourceBlock || + !typebot || + !connectingIds || + !blocksCoordinates || + !sourceTop + ) + return `` return connectingIds?.target - ? computeConnectingEdgePath( - connectingIds as Omit & { target: Target }, - sourceBlock, + ? computeConnectingEdgePath({ + connectingIds: connectingIds as Omit & { + target: Target + }, sourceTop, targetTop, - typebot - ) + blocksCoordinates, + }) : computeEdgePathToMouse({ - blockPosition: sourceBlock.graphCoordinates, + blockPosition: blocksCoordinates.byId[sourceBlock.id], mousePosition, sourceTop, }) - }, [sourceBlock, typebot, connectingIds, sourceTop, targetTop, mousePosition]) + }, [ + sourceBlock, + typebot, + connectingIds, + blocksCoordinates, + sourceTop, + targetTop, + mousePosition, + ]) const handleMouseMove = (e: MouseEvent) => { setMousePosition({ diff --git a/apps/builder/components/shared/Graph/Edges/Edge.tsx b/apps/builder/components/shared/Graph/Edges/Edge.tsx new file mode 100644 index 000000000..3c68e54df --- /dev/null +++ b/apps/builder/components/shared/Graph/Edges/Edge.tsx @@ -0,0 +1,87 @@ +import { Coordinates, useGraph } from 'contexts/GraphContext' +import { useTypebot } from 'contexts/TypebotContext/TypebotContext' +import React, { useMemo } from 'react' +import { + getAnchorsPosition, + computeEdgePath, + getEndpointTopOffset, + getSourceEndpointId, +} from 'services/graph' + +export type AnchorsPositionProps = { + sourcePosition: Coordinates + targetPosition: Coordinates + sourceType: 'right' | 'left' + totalSegments: number +} + +export const Edge = ({ edgeId }: { edgeId: string }) => { + const { typebot } = useTypebot() + const { + previewingEdgeId, + sourceEndpoints, + targetEndpoints, + graphPosition, + blocksCoordinates, + } = useGraph() + const edge = useMemo( + () => typebot?.edges.byId[edgeId], + [edgeId, typebot?.edges.byId] + ) + const isPreviewing = previewingEdgeId === edgeId + + const sourceBlock = edge && typebot?.blocks.byId[edge.from.blockId] + const targetBlock = edge && typebot?.blocks.byId[edge.to.blockId] + + const sourceBlockCoordinates = + sourceBlock && blocksCoordinates?.byId[sourceBlock.id] + const targetBlockCoordinates = + targetBlock && blocksCoordinates?.byId[targetBlock.id] + + const sourceTop = useMemo( + () => + getEndpointTopOffset( + graphPosition, + sourceEndpoints, + getSourceEndpointId(edge) + ), + + // eslint-disable-next-line react-hooks/exhaustive-deps + [edge, graphPosition, sourceEndpoints, sourceBlockCoordinates?.y] + ) + const targetTop = useMemo( + () => getEndpointTopOffset(graphPosition, targetEndpoints, edge?.to.stepId), + // eslint-disable-next-line react-hooks/exhaustive-deps + [graphPosition, targetEndpoints, edge?.to.stepId, targetBlockCoordinates?.y] + ) + + const path = useMemo(() => { + if (!sourceBlockCoordinates || !targetBlockCoordinates || !sourceTop) + return `` + const anchorsPosition = getAnchorsPosition({ + sourceBlockCoordinates, + targetBlockCoordinates, + sourceTop, + targetTop, + }) + return computeEdgePath(anchorsPosition) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + sourceBlockCoordinates?.x, + sourceBlockCoordinates?.y, + targetBlockCoordinates?.x, + targetBlockCoordinates?.y, + sourceTop, + ]) + + if (sourceTop === 0) return <> + return ( + + ) +} diff --git a/apps/builder/components/board/graph/Edges/Edges.tsx b/apps/builder/components/shared/Graph/Edges/Edges.tsx similarity index 100% rename from apps/builder/components/board/graph/Edges/Edges.tsx rename to apps/builder/components/shared/Graph/Edges/Edges.tsx diff --git a/apps/builder/components/board/graph/Edges/index.tsx b/apps/builder/components/shared/Graph/Edges/index.tsx similarity index 100% rename from apps/builder/components/board/graph/Edges/index.tsx rename to apps/builder/components/shared/Graph/Edges/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SourceEndpoint.tsx b/apps/builder/components/shared/Graph/Endpoints/SourceEndpoint.tsx similarity index 67% rename from apps/builder/components/board/graph/BlockNode/StepNode/SourceEndpoint.tsx rename to apps/builder/components/shared/Graph/Endpoints/SourceEndpoint.tsx index ccb3dc00d..655fa008a 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SourceEndpoint.tsx +++ b/apps/builder/components/shared/Graph/Endpoints/SourceEndpoint.tsx @@ -1,7 +1,7 @@ import { Box, BoxProps, Flex } from '@chakra-ui/react' import { useGraph } from 'contexts/GraphContext' import { Source } from 'models' -import React, { MouseEvent, useEffect, useRef } from 'react' +import React, { MouseEvent, useEffect, useRef, useState } from 'react' export const SourceEndpoint = ({ source, @@ -9,7 +9,8 @@ export const SourceEndpoint = ({ }: BoxProps & { source: Source }) => { - const { setConnectingIds, addSourceEndpoint: addEndpoint } = useGraph() + const [ranOnce, setRanOnce] = useState(false) + const { setConnectingIds, addSourceEndpoint, blocksCoordinates } = useGraph() const ref = useRef(null) const handleMouseDown = (e: MouseEvent) => { @@ -18,15 +19,17 @@ export const SourceEndpoint = ({ } useEffect(() => { - if (!ref.current) return - const id = source.nodeId ?? source.stepId + (source.conditionType ?? '') - addEndpoint({ + if (ranOnce || !ref.current) return + const id = source.buttonId ?? source.stepId + (source.conditionType ?? '') + addSourceEndpoint({ id, ref, }) + setRanOnce(true) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ref]) + }, [ref.current]) + if (!blocksCoordinates) return <> return ( { +export const Graph = ({ + answersCounts, + ...props +}: { answersCounts?: AnswersCount[] } & FlexProps) => { const { draggedStepType, setDraggedStepType, draggedStep, setDraggedStep } = useStepDnd() const graphContainerRef = useRef(null) const editorContainerRef = useRef(null) const { createBlock, typebot } = useTypebot() - const { graphPosition, setGraphPosition, setOpenedStepId } = useGraph() + const { + graphPosition, + setGraphPosition, + setOpenedStepId, + updateBlockCoordinates, + } = useGraph() const transform = useMemo( () => `translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`, @@ -48,9 +59,15 @@ const Graph = ({ ...props }: FlexProps) => { const handleMouseUp = (e: MouseEvent) => { if (!draggedStep && !draggedStepType) return - createBlock({ + const coordinates = { x: e.clientX - graphPosition.x - blockWidth / 3, y: e.clientY - graphPosition.y - 20 - headerHeight, + } + const id = generate() + updateBlockCoordinates(id, coordinates) + createBlock({ + id, + ...coordinates, step: draggedStep ?? (draggedStepType as DraggableStepType), }) setDraggedStep(undefined) @@ -79,13 +96,17 @@ const Graph = ({ ...props }: FlexProps) => { }} > - {props.children} {typebot.blocks.allIds.map((blockId) => ( ))} + {answersCounts?.map((answersCount) => ( + + ))} ) } - -export default Graph diff --git a/apps/builder/components/board/graph/BlockNode/BlockNode.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx similarity index 70% rename from apps/builder/components/board/graph/BlockNode/BlockNode.tsx rename to apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx index 6c4415db6..91f9611fb 100644 --- a/apps/builder/components/board/graph/BlockNode/BlockNode.tsx +++ b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx @@ -9,18 +9,26 @@ import React, { useEffect, useMemo, useState } from 'react' import { Block } from 'models' import { useGraph } from 'contexts/GraphContext' import { useStepDnd } from 'contexts/StepDndContext' -import { StepsList } from './StepsList' -import { isDefined } from 'utils' +import { StepNodesList } from '../StepNode/StepNodesList' +import { isNotDefined } from 'utils' import { useTypebot } from 'contexts/TypebotContext/TypebotContext' import { ContextMenu } from 'components/shared/ContextMenu' import { BlockNodeContextMenu } from './BlockNodeContextMenu' +import { useDebounce } from 'use-debounce' type Props = { block: Block } export const BlockNode = ({ block }: Props) => { - const { connectingIds, setConnectingIds, previewingEdgeId } = useGraph() + const { + connectingIds, + setConnectingIds, + previewingEdgeId, + blocksCoordinates, + updateBlockCoordinates, + isReadOnly, + } = useGraph() const { typebot, updateBlock } = useTypebot() const { setMouseOverBlockId } = useStepDnd() const { draggedStep, draggedStepType } = useStepDnd() @@ -32,10 +40,26 @@ export const BlockNode = ({ block }: Props) => { return edge?.to.blockId === block.id || edge?.from.blockId === block.id }, [block.id, previewingEdgeId, typebot?.edges.byId]) + const blockCoordinates = useMemo( + () => blocksCoordinates?.byId[block.id], + [block.id, blocksCoordinates?.byId] + ) + const [debouncedBlockPosition] = useDebounce(blockCoordinates, 100) + useEffect(() => { + if (!debouncedBlockPosition || isReadOnly) return + if ( + debouncedBlockPosition?.x === block.graphCoordinates.x && + debouncedBlockPosition.y === block.graphCoordinates.y + ) + return + updateBlock(block.id, { graphCoordinates: debouncedBlockPosition }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedBlockPosition]) + useEffect(() => { setIsConnecting( connectingIds?.target?.blockId === block.id && - !isDefined(connectingIds.target?.stepId) + isNotDefined(connectingIds.target?.stepId) ) }, [block.id, connectingIds]) @@ -53,11 +77,10 @@ export const BlockNode = ({ block }: Props) => { if (!isMouseDown) return const { movementX, movementY } = event - updateBlock(block.id, { - graphCoordinates: { - x: block.graphCoordinates.x + movementX, - y: block.graphCoordinates.y + movementY, - }, + if (!blockCoordinates) return + updateBlockCoordinates(block.id, { + x: blockCoordinates.x + movementX, + y: blockCoordinates.y + movementY, }) } @@ -77,6 +100,7 @@ export const BlockNode = ({ block }: Props) => { return ( renderMenu={() => } + isDisabled={isReadOnly} > {(ref, isOpened) => ( { transition="border 300ms, box-shadow 200ms" pos="absolute" style={{ - transform: `translate(${block.graphCoordinates.x}px, ${block.graphCoordinates.y}px)`, + transform: `translate(${blockCoordinates?.x ?? 0}px, ${ + blockCoordinates?.y ?? 0 + }px)`, }} onMouseDown={handleMouseDown} onMouseEnter={handleMouseEnter} @@ -106,6 +132,7 @@ export const BlockNode = ({ block }: Props) => { defaultValue={block.title} onSubmit={handleTitleSubmit} fontWeight="semibold" + isDisabled={isReadOnly} > { /> - {typebot && } + {typebot && ( + + )} )} diff --git a/apps/builder/components/board/graph/BlockNode/BlockNodeContextMenu.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/BlockNodeContextMenu.tsx rename to apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNodeContextMenu.tsx diff --git a/apps/builder/components/board/graph/BlockNode/index.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/index.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/index.tsx rename to apps/builder/components/shared/Graph/Nodes/BlockNode/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNode.tsx b/apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNode.tsx similarity index 91% rename from apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNode.tsx rename to apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNode.tsx index 8eea2a5e4..51e063a19 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNode.tsx +++ b/apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNode.tsx @@ -13,11 +13,11 @@ import { Coordinates } from 'contexts/GraphContext' import { useTypebot } from 'contexts/TypebotContext' import { ChoiceInputStep, ChoiceItem } from 'models' import React, { useState } from 'react' -import { isDefined, isSingleChoiceInput } from 'utils' -import { SourceEndpoint } from '../SourceEndpoint' -import { ChoiceItemNodeContextMenu } from './ChoiceItemNodeContextMenu' +import { isNotDefined, isSingleChoiceInput } from 'utils' +import { SourceEndpoint } from '../../Endpoints/SourceEndpoint' +import { ButtonNodeContextMenu } from './ButtonNodeContextMenu' -type ChoiceItemNodeProps = { +type Props = { item: ChoiceItem onMouseMoveBottomOfElement?: () => void onMouseMoveTopOfElement?: () => void @@ -27,12 +27,12 @@ type ChoiceItemNodeProps = { ) => void } -export const ChoiceItemNode = ({ +export const ButtonNode = ({ item, onMouseDown, onMouseMoveBottomOfElement, onMouseMoveTopOfElement, -}: ChoiceItemNodeProps) => { +}: Props) => { const { deleteChoiceItem, updateChoiceItem, typebot, createChoiceItem } = useTypebot() const [mouseDownEvent, setMouseDownEvent] = @@ -88,9 +88,10 @@ export const ChoiceItemNode = ({ const handleMouseEnter = () => setIsMouseOver(true) const handleMouseLeave = () => setIsMouseOver(false) + return ( - renderMenu={() => } + renderMenu={() => } > {(ref, isOpened) => ( { +export const ButtonNodeContextMenu = ({ itemId }: { itemId: string }) => { const { deleteChoiceItem } = useTypebot() const handleDeleteClick = () => deleteChoiceItem(itemId) diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNodeOverlay.tsx b/apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNodeOverlay.tsx similarity index 76% rename from apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNodeOverlay.tsx rename to apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNodeOverlay.tsx index 79f69e453..dafff71ae 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/ChoiceInputStepNode/ChoiceItemNodeOverlay.tsx +++ b/apps/builder/components/shared/Graph/Nodes/ButtonNode/ButtonNodeOverlay.tsx @@ -2,14 +2,11 @@ import { Flex, FlexProps } from '@chakra-ui/react' import { ChoiceItem } from 'models' import React from 'react' -type ChoiceItemNodeOverlayProps = { +type Props = { item: ChoiceItem } & FlexProps -export const ChoiceItemNodeOverlay = ({ - item, - ...props -}: ChoiceItemNodeOverlayProps) => { +export const ButtonNodeOverlay = ({ item, ...props }: Props) => { return ( { +export const ButtonNodesList = ({ step }: ChoiceItemsListProps) => { const { typebot, createChoiceItem } = useTypebot() const { draggedChoiceItem, @@ -90,7 +90,7 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => { {step.options.itemIds.map((itemId, idx) => ( {typebot?.choiceItems.byId[itemId] && ( - handleMouseOnTopOfStep(idx)} onMouseMoveBottomOfElement={() => { @@ -138,7 +138,7 @@ export const ChoiceItemsList = ({ step }: ChoiceItemsListProps) => { {draggedChoiceItem && draggedChoiceItem.stepId === step.id && ( - { - const { typebot } = useAnalyticsGraph() +export const DropOffNode = ({ answersCounts, blockId }: Props) => { + const { typebot } = useTypebot() const totalAnswers = useMemo( () => answersCounts.find((a) => a.blockId === blockId)?.totalAnswers, diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/ContentPopover.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/MediaBubblePopoverContent.tsx similarity index 57% rename from apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/ContentPopover.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/MediaBubblePopoverContent.tsx index c009182de..764c007c7 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/ContentPopover.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/MediaBubblePopoverContent.tsx @@ -5,23 +5,21 @@ import { PopoverBody, } from '@chakra-ui/react' import { ImageUploadContent } from 'components/shared/ImageUploadContent' -import { useTypebot } from 'contexts/TypebotContext' import { BubbleStep, + BubbleStepContent, BubbleStepType, - ImageBubbleStep, TextBubbleStep, - VideoBubbleContent, - VideoBubbleStep, } from 'models' import { useRef } from 'react' import { VideoUploadContent } from './VideoUploadContent' type Props = { step: Exclude + onContentChange: (content: BubbleStepContent) => void } -export const ContentPopover = ({ step }: Props) => { +export const MediaBubblePopoverContent = (props: Props) => { const ref = useRef(null) const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation() @@ -30,37 +28,28 @@ export const ContentPopover = ({ step }: Props) => { - + ) } -export const StepContent = ({ step }: Props) => { - const { updateStep } = useTypebot() - - const handleContentChange = (url: string) => - updateStep(step.id, { content: { url } } as Partial) - - const handleVideoContentChange = (content: VideoBubbleContent) => - updateStep(step.id, { content } as Partial) +export const MediaBubbleContent = ({ step, onContentChange }: Props) => { + const handleImageUrlChange = (url: string) => onContentChange({ url }) switch (step.type) { case BubbleStepType.IMAGE: { return ( ) } case BubbleStepType.VIDEO: { return ( - + ) } } diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/VideoUploadContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/VideoUploadContent.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/ContentPopover/VideoUploadContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/VideoUploadContent.tsx diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/index.tsx new file mode 100644 index 000000000..017af2eea --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/MediaBubblePopoverContent/index.tsx @@ -0,0 +1 @@ +export { MediaBubblePopoverContent } from './MediaBubblePopoverContent' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsModal.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/SettingsModal.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsModal.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/SettingsModal.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx similarity index 70% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx index a494fc59f..d20d275ef 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx @@ -7,9 +7,7 @@ import { IconButton, } from '@chakra-ui/react' import { ExpandIcon } from 'assets/icons' -import { useTypebot } from 'contexts/TypebotContext/TypebotContext' import { - InputStep, InputStepType, IntegrationStepType, LogicStepType, @@ -17,7 +15,6 @@ import { StepOptions, TextBubbleStep, Webhook, - WebhookStep, } from 'models' import { useRef } from 'react' import { @@ -33,15 +30,19 @@ import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings' import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody' import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody' import { RedirectSettings } from './bodies/RedirectSettings' -import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody' +import { SetVariableSettings } from './bodies/SetVariableSettings' import { WebhookSettings } from './bodies/WebhookSettings' type Props = { step: Exclude + webhook?: Webhook onExpandClick: () => void + onOptionsChange: (options: StepOptions) => void + onWebhookChange: (updates: Partial) => void + onTestRequestClick: () => void } -export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => { +export const SettingsPopoverContent = ({ onExpandClick, ...props }: Props) => { const ref = useRef(null) const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation() @@ -60,7 +61,7 @@ export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => { ref={ref} shadow="lg" > - + { ) } -export const StepSettings = ({ step }: { step: Step }) => { - const { updateStep, updateWebhook, typebot } = useTypebot() - const handleOptionsChange = (options: StepOptions) => { - updateStep(step.id, { options } as Partial) - } - - const handleWebhookChange = (webhook: Partial) => { - const webhookId = (step as WebhookStep).options?.webhookId - if (!webhookId) return - updateWebhook(webhookId, webhook) - } - +export const StepSettings = ({ + step, + webhook, + onOptionsChange, + onWebhookChange, + onTestRequestClick, +}: { + step: Step + webhook?: Webhook + onOptionsChange: (options: StepOptions) => void + onWebhookChange: (updates: Partial) => void + onTestRequestClick: () => void +}) => { switch (step.type) { case InputStepType.TEXT: { return ( ) } @@ -101,7 +103,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -109,7 +111,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -117,7 +119,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -125,7 +127,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -133,7 +135,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -141,15 +143,15 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } case LogicStepType.SET_VARIABLE: { return ( - ) } @@ -157,7 +159,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -165,7 +167,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } @@ -173,7 +175,7 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) @@ -182,18 +184,18 @@ export const StepSettings = ({ step }: { step: Step }) => { return ( ) } case IntegrationStepType.WEBHOOK: { return ( ) } diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/index.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/index.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleAnalyticsSettings.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleAnalyticsSettings.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleAnalyticsSettings.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleAnalyticsSettings.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx similarity index 81% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx index 065b21a8a..d4ebf90d8 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx @@ -6,9 +6,13 @@ import { useTypebot } from 'contexts/TypebotContext' import { CredentialsType } from 'db' import { Cell, + defaultTable, ExtractingCell, GoogleSheetsAction, + GoogleSheetsGetOptions, + GoogleSheetsInsertRowOptions, GoogleSheetsOptions, + GoogleSheetsUpdateRowOptions, Table, } from 'models' import React, { useMemo } from 'react' @@ -24,7 +28,7 @@ import { CellWithValueStack } from './CellWithValueStack' import { CellWithVariableIdStack } from './CellWithVariableIdStack' type Props = { - options?: GoogleSheetsOptions + options: GoogleSheetsOptions onOptionsChange: (options: GoogleSheetsOptions) => void stepId: string } @@ -49,8 +53,35 @@ export const GoogleSheetsSettingsBody = ({ onOptionsChange({ ...options, spreadsheetId }) const handleSheetIdChange = (sheetId: string) => onOptionsChange({ ...options, sheetId }) - const handleActionChange = (action: GoogleSheetsAction) => - onOptionsChange({ ...options, action }) + + const handleActionChange = (action: GoogleSheetsAction) => { + switch (action) { + case GoogleSheetsAction.GET: { + const newOptions: GoogleSheetsGetOptions = { + ...options, + action, + cellsToExtract: defaultTable, + } + return onOptionsChange({ ...newOptions }) + } + case GoogleSheetsAction.INSERT_ROW: { + const newOptions: GoogleSheetsInsertRowOptions = { + ...options, + action, + cellsToInsert: defaultTable, + } + return onOptionsChange({ ...newOptions }) + } + case GoogleSheetsAction.UPDATE_ROW: { + const newOptions: GoogleSheetsUpdateRowOptions = { + ...options, + action, + cellsToUpsert: defaultTable, + } + return onOptionsChange({ ...newOptions }) + } + } + } const handleCreateNewClick = async () => { if (hasUnsavedChanges) { @@ -94,14 +125,14 @@ export const GoogleSheetsSettingsBody = ({ <> - currentItem={options.action} + currentItem={'action' in options ? options.action : undefined} onItemSelect={handleActionChange} items={Object.values(GoogleSheetsAction)} placeholder="Select an operation" /> )} - {sheet && options?.action && ( + {sheet && 'action' in options && ( void }) => { @@ -149,7 +183,7 @@ const ActionOptions = ({ case GoogleSheetsAction.INSERT_ROW: return ( - initialItems={options.cellsToInsert ?? { byId: {}, allIds: [] }} + initialItems={options.cellsToInsert} onItemsChange={handleInsertColumnsChange} Item={UpdatingCellItem} addLabel="Add a value" @@ -167,7 +201,7 @@ const ActionOptions = ({ /> Cells to update - initialItems={options.cellsToUpsert ?? { byId: {}, allIds: [] }} + initialItems={options.cellsToUpsert} onItemsChange={handleUpsertColumnsChange} Item={UpdatingCellItem} addLabel="Add a value" @@ -186,7 +220,7 @@ const ActionOptions = ({ /> Cells to extract - initialItems={options.cellsToExtract ?? { byId: {}, allIds: [] }} + initialItems={options.cellsToExtract} onItemsChange={handleExtractingCellsChange} Item={ExtractingCellItem} addLabel="Add a value" diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/index.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/index.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/RedirectSettings.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/RedirectSettings.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/RedirectSettings.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/RedirectSettings.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/SetVariableSettings.tsx similarity index 93% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/SetVariableSettings.tsx index 9503988b8..3266f1dce 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/SetVariableSettings.tsx @@ -9,10 +9,7 @@ type Props = { onOptionsChange: (options: SetVariableOptions) => void } -export const SetVariableSettingsBody = ({ - options, - onOptionsChange, -}: Props) => { +export const SetVariableSettings = ({ options, onOptionsChange }: Props) => { const handleVariableChange = (variable?: Variable) => onOptionsChange({ ...options, variableId: variable?.id }) const handleExpressionChange = (expressionToEvaluate: string) => diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx similarity index 84% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx index 4b38afd29..ba47544af 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useMemo, useState } from 'react' import { Accordion, AccordionButton, @@ -22,7 +22,6 @@ import { ResponseVariableMapping, } from 'models' import { DropdownList } from 'components/shared/DropdownList' -import { generate } from 'short-uuid' import { TableList, TableListItemProps } from 'components/shared/TableList' import { CodeEditor } from 'components/shared/CodeEditor' import { @@ -35,19 +34,22 @@ import { VariableForTestInputs } from './VariableForTestInputs' import { DataVariableInputs } from './ResponseMappingInputs' type Props = { + webhook: Webhook options?: WebhookOptions - webhook?: Webhook onOptionsChange: (options: WebhookOptions) => void - onWebhookChange: (webhook: Partial) => void + onWebhookChange: (updates: Partial) => void + onTestRequestClick: () => void } export const WebhookSettings = ({ options, - webhook, onOptionsChange, + webhook, onWebhookChange, + onTestRequestClick, }: Props) => { - const { createWebhook, typebot, save } = useTypebot() + const { typebot, save } = useTypebot() + const [isTestResponseLoading, setIsTestResponseLoading] = useState(false) const [testResponse, setTestResponse] = useState() const [responseKeys, setResponseKeys] = useState([]) @@ -56,18 +58,9 @@ export const WebhookSettings = ({ status: 'error', }) - useEffect(() => { - if (options?.webhookId) return - const webhookId = generate() - createWebhook({ id: webhookId }) - onOptionsChange({ ...options, webhookId }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - const handleUrlChange = (url?: string) => onWebhookChange({ url }) - const handleMethodChange = (method?: HttpMethod) => - onWebhookChange({ method }) + const handleMethodChange = (method: HttpMethod) => onWebhookChange({ method }) const handleQueryParamsChange = (queryParams: Table) => onWebhookChange({ queryParams }) @@ -78,14 +71,16 @@ export const WebhookSettings = ({ const handleBodyChange = (body: string) => onWebhookChange({ body }) const handleVariablesChange = (variablesForTest: Table) => - onOptionsChange({ ...options, variablesForTest }) + options && onOptionsChange({ ...options, variablesForTest }) const handleResponseMappingChange = ( responseVariableMapping: Table - ) => onOptionsChange({ ...options, responseVariableMapping }) + ) => options && onOptionsChange({ ...options, responseVariableMapping }) const handleTestRequestClick = async () => { if (!typebot || !webhook) return + setIsTestResponseLoading(true) + onTestRequestClick() await save() const { data, error } = await executeWebhook( typebot.id, @@ -98,6 +93,7 @@ export const WebhookSettings = ({ if (error) return toast({ title: error.name, description: error.message }) setTestResponse(JSON.stringify(data, undefined, 2)) setResponseKeys(getDeepKeys(data)) + setIsTestResponseLoading(false) } const ResponseMappingInputs = useMemo( @@ -111,14 +107,14 @@ export const WebhookSettings = ({ - currentItem={webhook?.method ?? HttpMethod.GET} + currentItem={webhook.method} onItemSelect={handleMethodChange} items={Object.values(HttpMethod)} /> @@ -130,7 +126,7 @@ export const WebhookSettings = ({ - initialItems={webhook?.queryParams ?? { byId: {}, allIds: [] }} + initialItems={webhook.queryParams} onItemsChange={handleQueryParamsChange} Item={QueryParamsInputs} addLabel="Add a param" @@ -144,7 +140,7 @@ export const WebhookSettings = ({ - initialItems={webhook?.headers ?? { byId: {}, allIds: [] }} + initialItems={webhook.headers} onItemsChange={handleHeadersChange} Item={HeadersInputs} addLabel="Add a value" @@ -158,7 +154,7 @@ export const WebhookSettings = ({ @@ -181,7 +177,11 @@ export const WebhookSettings = ({ - {testResponse && ( @@ -201,6 +201,7 @@ export const WebhookSettings = ({ } onItemsChange={handleResponseMappingChange} Item={ResponseMappingInputs} + addLabel="Add an entry" /> diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/index.ts b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/index.ts similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/index.ts rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/bodies/index.ts diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/index.ts b/apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/index.ts similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/index.ts rename to apps/builder/components/shared/Graph/Nodes/StepNode/SettingsPopoverContent/index.ts diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNode.tsx similarity index 60% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNode.tsx index 9eb90ef43..3d3313b8c 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNode.tsx @@ -7,23 +7,31 @@ import { useEventListener, } from '@chakra-ui/react' import React, { useEffect, useState } from 'react' -import { BubbleStep, DraggableStep, Step, TextBubbleStep } from 'models' +import { + BubbleStep, + BubbleStepContent, + DraggableStep, + Step, + StepOptions, + TextBubbleStep, + Webhook, +} from 'models' import { Coordinates, useGraph } from 'contexts/GraphContext' -import { StepIcon } from 'components/board/StepsSideBar/StepIcon' -import { isBubbleStep, isTextBubbleStep } from 'utils' -import { TextEditor } from './TextEditor/TextEditor' +import { StepIcon } from 'components/editor/StepsSideBar/StepIcon' +import { isBubbleStep, isTextBubbleStep, isWebhookStep } from 'utils' import { StepNodeContent } from './StepNodeContent/StepNodeContent' import { useTypebot } from 'contexts/TypebotContext' import { ContextMenu } from 'components/shared/ContextMenu' import { SettingsPopoverContent } from './SettingsPopoverContent' import { StepNodeContextMenu } from './StepNodeContextMenu' -import { SourceEndpoint } from './SourceEndpoint' +import { SourceEndpoint } from '../../Endpoints/SourceEndpoint' import { hasDefaultConnector } from 'services/typebots' -import { TargetEndpoint } from './TargetEndpoint' import { useRouter } from 'next/router' import { SettingsModal } from './SettingsPopoverContent/SettingsModal' import { StepSettings } from './SettingsPopoverContent/SettingsPopoverContent' -import { ContentPopover } from './ContentPopover' +import { TextBubbleEditor } from './TextBubbleEditor' +import { TargetEndpoint } from '../../Endpoints' +import { MediaBubblePopoverContent } from './MediaBubblePopoverContent' export const StepNode = ({ step, @@ -42,10 +50,26 @@ export const StepNode = ({ ) => void }) => { const { query } = useRouter() - const { setConnectingIds, connectingIds, openedStepId, setOpenedStepId } = - useGraph() - const { detachStepFromBlock } = useTypebot() + const { + setConnectingIds, + connectingIds, + openedStepId, + setOpenedStepId, + blocksCoordinates, + } = useGraph() + const { detachStepFromBlock, updateStep, typebot, updateWebhook } = + useTypebot() + const [localStep, setLocalStep] = useState(step) + const [localWebhook, setLocalWebhook] = useState( + isWebhookStep(step) + ? typebot?.webhooks.byId[step.options.webhookId ?? ''] + : undefined + ) const [isConnecting, setIsConnecting] = useState(false) + const [isPopoverOpened, setIsPopoverOpened] = useState( + openedStepId === step.id + ) + const [mouseDownEvent, setMouseDownEvent] = useState<{ absolute: Coordinates; relative: Coordinates }>() const [isEditing, setIsEditing] = useState( @@ -57,6 +81,10 @@ export const StepNode = ({ onClose: onModalClose, } = useDisclosure() + useEffect(() => { + setLocalStep(step) + }, [step]) + useEffect(() => { if (query.stepId?.toString() === step.id) setOpenedStepId(step.id) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -69,6 +97,11 @@ export const StepNode = ({ ) }, [connectingIds, step.blockId, step.id]) + const handleModalClose = () => { + updateStep(localStep.id, { ...localStep }) + onModalClose() + } + const handleMouseEnter = () => { if (connectingIds?.target) setConnectingIds({ @@ -116,7 +149,6 @@ export const StepNode = ({ onMouseDown && (event.movementX > 0 || event.movementY > 0) if (isMovingAndIsMouseDown && step.type !== 'start') { - console.log(step) onMouseDown(mouseDownEvent, step) detachStepFromBlock(step.id) setMouseDownEvent(undefined) @@ -142,10 +174,33 @@ export const StepNode = ({ onModalOpen() } - return isEditing && isTextBubbleStep(step) ? ( - { + updateStep(localStep.id, { ...localStep }) + if (localWebhook) updateWebhook(localWebhook.id, { ...localWebhook }) + } + + const handleOptionsChange = (options: StepOptions) => { + setLocalStep({ ...localStep, options } as Step) + } + + const handleContentChange = (content: BubbleStepContent) => + setLocalStep({ ...localStep, content } as Step) + + const handleWebhookChange = (updates: Partial) => { + if (!localWebhook) return + setLocalWebhook({ ...localWebhook, ...updates }) + } + + useEffect(() => { + if (isPopoverOpened && openedStepId !== step.id) updateOptions() + setIsPopoverOpened(openedStepId === step.id) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [openedStepId]) + + return isEditing && isTextBubbleStep(localStep) ? ( + ) : ( @@ -153,7 +208,12 @@ export const StepNode = ({ renderMenu={() => } > {(ref, isOpened) => ( - + - + - {isConnectable && hasDefaultConnector(step) && ( - - )} + {blocksCoordinates && + isConnectable && + hasDefaultConnector(localStep) && ( + + )} - {hasSettingsPopover(step) && ( + {hasSettingsPopover(localStep) && ( )} - {hasContentPopover(step) && } - - + {isMediaBubbleStep(localStep) && ( + + )} + + )} @@ -224,7 +301,7 @@ export const StepNode = ({ const hasSettingsPopover = (step: Step): step is Exclude => !isBubbleStep(step) -const hasContentPopover = ( +const isMediaBubbleStep = ( step: Step ): step is Exclude => isBubbleStep(step) && !isTextBubbleStep(step) diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/StepNodeContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/StepNodeContent.tsx new file mode 100644 index 000000000..7c2df0738 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/StepNodeContent.tsx @@ -0,0 +1,104 @@ +import { Text } from '@chakra-ui/react' +import { + Step, + StartStep, + BubbleStepType, + InputStepType, + LogicStepType, + IntegrationStepType, +} from 'models' +import { isInputStep } from 'utils' +import { ButtonNodesList } from '../../ButtonNode' +import { + ConditionContent, + SetVariableContent, + TextBubbleContent, + VideoBubbleContent, + WebhookContent, + WithVariableContent, +} from './contents' +import { ConfigureContent } from './contents/ConfigureContent' +import { ImageBubbleContent } from './contents/ImageBubbleContent' +import { PlaceholderContent } from './contents/PlaceholderContent' + +type Props = { + step: Step | StartStep + isConnectable?: boolean +} +export const StepNodeContent = ({ step }: Props) => { + if (isInputStep(step) && step.options.variableId) { + return + } + switch (step.type) { + case BubbleStepType.TEXT: { + return + } + case BubbleStepType.IMAGE: { + return + } + case BubbleStepType.VIDEO: { + return + } + case InputStepType.TEXT: + case InputStepType.NUMBER: + case InputStepType.EMAIL: + case InputStepType.URL: + case InputStepType.PHONE: { + return ( + + ) + } + case InputStepType.DATE: { + return Pick a date... + } + case InputStepType.CHOICE: { + return + } + case LogicStepType.SET_VARIABLE: { + return + } + case LogicStepType.CONDITION: { + return + } + case LogicStepType.REDIRECT: { + return ( + + ) + } + case IntegrationStepType.GOOGLE_SHEETS: { + return ( + + ) + } + case IntegrationStepType.GOOGLE_ANALYTICS: { + return ( + + ) + } + case IntegrationStepType.WEBHOOK: { + return + } + case 'start': { + return Start + } + default: { + return No input + } + } +} diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConditionContent.tsx similarity index 92% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConditionContent.tsx index c73f50ee4..379e6e34a 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConditionContent.tsx @@ -1,9 +1,9 @@ import { Flex, Stack, HStack, Tag, Text } from '@chakra-ui/react' import { useTypebot } from 'contexts/TypebotContext' import { ConditionStep } from 'models' -import { SourceEndpoint } from '../SourceEndpoint' +import { SourceEndpoint } from '../../../../Endpoints/SourceEndpoint' -export const ConditionNodeContent = ({ step }: { step: ConditionStep }) => { +export const ConditionContent = ({ step }: { step: ConditionStep }) => { const { typebot } = useTypebot() return ( diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConfigureContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConfigureContent.tsx new file mode 100644 index 000000000..6731e2e90 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ConfigureContent.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import { Text } from '@chakra-ui/react' + +type Props = { label?: string } + +export const ConfigureContent = ({ label }: Props) => ( + + {label ?? 'Configure...'} + +) diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ImageBubbleContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ImageBubbleContent.tsx new file mode 100644 index 000000000..dfba91413 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/ImageBubbleContent.tsx @@ -0,0 +1,16 @@ +import { Box, Text, Image } from '@chakra-ui/react' +import { ImageBubbleStep } from 'models' + +export const ImageBubbleContent = ({ step }: { step: ImageBubbleStep }) => + !step.content?.url ? ( + Click to edit... + ) : ( + + Step image + + ) diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/PlaceholderContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/PlaceholderContent.tsx new file mode 100644 index 000000000..096f77133 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/PlaceholderContent.tsx @@ -0,0 +1,8 @@ +import React from 'react' +import { Text } from '@chakra-ui/react' + +type Props = { placeholder: string } + +export const PlaceholderContent = ({ placeholder }: Props) => ( + {placeholder} +) diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/SetVariableContent.tsx similarity index 86% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/SetVariableContent.tsx index e42460f54..a42313722 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/SetVariableContent.tsx @@ -2,7 +2,7 @@ import { Text } from '@chakra-ui/react' import { useTypebot } from 'contexts/TypebotContext' import { SetVariableStep } from 'models' -export const SetVariableNodeContent = ({ step }: { step: SetVariableStep }) => { +export const SetVariableContent = ({ step }: { step: SetVariableStep }) => { const { typebot } = useTypebot() const variableName = typebot?.variables.byId[step.options?.variableId ?? '']?.name ?? '' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/TextBubbleNodeContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/TextBubbleContent.tsx similarity index 91% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/TextBubbleNodeContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/TextBubbleContent.tsx index 1c78d412a..8448326c4 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/TextBubbleNodeContent.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/TextBubbleContent.tsx @@ -8,7 +8,7 @@ type Props = { step: TextBubbleStep } -export const TextBubbleNodeContent = ({ step }: Props) => { +export const TextBubbleContent = ({ step }: Props) => { const { typebot } = useTypebot() return ( { +export const VideoBubbleContent = ({ step }: { step: VideoBubbleStep }) => { if (!step.content?.url || !step.content.type) return Click to edit... switch (step.content.type) { diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/WebhookContent.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/WebhookContent.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/WebhookContent.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/WebhookContent.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContentWithVariable.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/WithVariableContent.tsx similarity index 89% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContentWithVariable.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/WithVariableContent.tsx index 477e03628..96292d8cf 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContentWithVariable.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/WithVariableContent.tsx @@ -7,7 +7,7 @@ type Props = { step: InputStep } -export const StepNodeContentWithVariable = ({ step }: Props) => { +export const WithVariableContent = ({ step }: Props) => { const { typebot } = useTypebot() const variableName = typebot?.variables.byId[step.options.variableId as string].name diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/index.tsx new file mode 100644 index 000000000..8e745336f --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/contents/index.tsx @@ -0,0 +1,6 @@ +export * from './ConditionContent' +export * from './SetVariableContent' +export * from './WithVariableContent' +export * from './VideoBubbleContent' +export * from './WebhookContent' +export * from './TextBubbleContent' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/index.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/index.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContent/index.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContextMenu.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContextMenu.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeContextMenu.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeOverlay.tsx similarity index 89% rename from apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeOverlay.tsx index 1b979ef41..5d0178039 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodeOverlay.tsx @@ -1,6 +1,6 @@ import { StackProps, HStack } from '@chakra-ui/react' import { StartStep, Step } from 'models' -import { StepIcon } from 'components/board/StepsSideBar/StepIcon' +import { StepIcon } from 'components/editor/StepsSideBar/StepIcon' import { StepNodeContent } from './StepNodeContent/StepNodeContent' export const StepNodeOverlay = ({ diff --git a/apps/builder/components/board/graph/BlockNode/StepsList.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx similarity index 86% rename from apps/builder/components/board/graph/BlockNode/StepsList.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx index d40d723d0..5cf84dc22 100644 --- a/apps/builder/components/board/graph/BlockNode/StepsList.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx @@ -1,12 +1,13 @@ import { useEventListener, Stack, Flex, Portal } from '@chakra-ui/react' import { DraggableStep } from 'models' import { useStepDnd } from 'contexts/StepDndContext' -import { Coordinates } from 'contexts/GraphContext' +import { Coordinates, useGraph } from 'contexts/GraphContext' import { useMemo, useState } from 'react' -import { StepNode, StepNodeOverlay } from './StepNode' import { useTypebot } from 'contexts/TypebotContext' +import { StepNode } from './StepNode' +import { StepNodeOverlay } from './StepNodeOverlay' -export const StepsList = ({ +export const StepNodesList = ({ blockId, stepIds, }: { @@ -22,6 +23,7 @@ export const StepsList = ({ setMouseOverBlockId, } = useStepDnd() const { typebot, createStep } = useTypebot() + const { isReadOnly } = useGraph() const [expandedPlaceholderIndex, setExpandedPlaceholderIndex] = useState< number | undefined >() @@ -73,18 +75,19 @@ export const StepsList = ({ { absolute, relative }: { absolute: Coordinates; relative: Coordinates }, step: DraggableStep ) => { + if (isReadOnly) return setPosition(absolute) setRelativeCoordinates(relative) setMouseOverBlockId(blockId) setDraggedStep(step) } - const handleMouseOnTopOfStep = (stepIndex: number) => { + const handleMouseOnTopOfStep = (stepIndex: number) => () => { if (!draggedStep && !draggedStepType) return setExpandedPlaceholderIndex(stepIndex === 0 ? 0 : stepIndex) } - const handleMouseOnBottomOfStep = (stepIndex: number) => { + const handleMouseOnBottomOfStep = (stepIndex: number) => () => { if (!draggedStep && !draggedStepType) return setExpandedPlaceholderIndex(stepIndex + 1) } @@ -112,12 +115,10 @@ export const StepsList = ({ handleMouseOnTopOfStep(idx)} - onMouseMoveBottomOfElement={() => { - handleMouseOnBottomOfStep(idx) - }} + step={typebot.steps.byId[stepId]} + isConnectable={!isReadOnly && stepIds.length - 1 === idx} + onMouseMoveTopOfElement={handleMouseOnTopOfStep(idx)} + onMouseMoveBottomOfElement={handleMouseOnBottomOfStep(idx)} onMouseDown={handleStepMouseDown} /> void } -export const TextEditor = ({ - initialValue, - stepId, - onClose, -}: TextEditorProps) => { +export const TextBubbleEditor = ({ initialValue, stepId, onClose }: Props) => { const randomEditorId = useMemo(() => Math.random().toString(), []) const editor = useMemo( () => @@ -41,17 +37,14 @@ export const TextEditor = ({ const [isVariableDropdownOpen, setIsVariableDropdownOpen] = useState(false) const textEditorRef = useRef(null) + useOutsideClick({ ref: textEditorRef, - handler: onClose, - }) - - useEffect(() => { - return () => { + handler: () => { save(value) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]) + onClose() + }, + }) useEffect(() => { if (!isVariableDropdownOpen) return @@ -104,7 +97,6 @@ export const TextEditor = ({ const handleChangeEditorContent = (val: unknown[]) => { setValue(val) - save(val) setIsVariableDropdownOpen(false) } return ( diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/ToolBar.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/ToolBar.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/ToolBar.tsx rename to apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/ToolBar.tsx diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/index.tsx new file mode 100644 index 000000000..b62e6dd38 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/TextBubbleEditor/index.tsx @@ -0,0 +1 @@ +export { TextBubbleEditor } from './TextBubbleEditor' diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/index.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/index.tsx new file mode 100644 index 000000000..2b201c4e6 --- /dev/null +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/index.tsx @@ -0,0 +1 @@ +export { StepNodesList } from './StepNodesList' diff --git a/apps/builder/components/shared/Graph/index.tsx b/apps/builder/components/shared/Graph/index.tsx new file mode 100644 index 000000000..8c2482439 --- /dev/null +++ b/apps/builder/components/shared/Graph/index.tsx @@ -0,0 +1 @@ +export { Graph } from './Graph' diff --git a/apps/builder/components/shared/TableList.tsx b/apps/builder/components/shared/TableList.tsx index cd7018a9e..4942eaa95 100644 --- a/apps/builder/components/shared/TableList.tsx +++ b/apps/builder/components/shared/TableList.tsx @@ -1,5 +1,6 @@ import { Box, Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react' import { TrashIcon, PlusIcon } from 'assets/icons' +import { deepEqual } from 'fast-equals' import { Draft } from 'immer' import { Table } from 'models' import React, { useEffect, useState } from 'react' @@ -31,11 +32,7 @@ export const TableList = ({ const [showDeleteId, setShowDeleteId] = useState() useEffect(() => { - if (items.allIds.length === 0) createItem() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - useEffect(() => { + if (deepEqual(items, initialItems)) return onItemsChange(items) // eslint-disable-next-line react-hooks/exhaustive-deps }, [items]) diff --git a/apps/builder/components/shared/TypebotHeader/EditableTypebotName.tsx b/apps/builder/components/shared/TypebotHeader/EditableTypebotName.tsx index 0681bcd1e..6de54893a 100644 --- a/apps/builder/components/shared/TypebotHeader/EditableTypebotName.tsx +++ b/apps/builder/components/shared/TypebotHeader/EditableTypebotName.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@chakra-ui/tooltip' import React from 'react' type EditableProps = { - name?: string + name: string onNewName: (newName: string) => void } export const EditableTypebotName = ({ name, onNewName }: EditableProps) => { diff --git a/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx b/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx index 0d31b107a..2964f7877 100644 --- a/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx +++ b/apps/builder/components/shared/TypebotHeader/TypebotHeader.tsx @@ -1,5 +1,5 @@ -import { Flex, HStack, Button, IconButton } from '@chakra-ui/react' -import { ChevronLeftIcon } from 'assets/icons' +import { Flex, HStack, Button, IconButton, Tooltip } from '@chakra-ui/react' +import { ChevronLeftIcon, RedoIcon, UndoIcon } from 'assets/icons' import { NextChakraLink } from 'components/nextChakra/NextChakraLink' import { RightPanel, useEditor } from 'contexts/EditorContext' import { useTypebot } from 'contexts/TypebotContext/TypebotContext' @@ -13,10 +13,12 @@ export const headerHeight = 56 export const TypebotHeader = () => { const router = useRouter() - const { typebot, updateTypebot, save } = useTypebot() + const { typebot, updateTypebot, save, undo, redo, canUndo, canRedo } = + useTypebot() const { setRightPanel } = useEditor() - const handleBackClick = () => { + const handleBackClick = async () => { + await save() router.push({ pathname: `/typebots`, query: { ...router.query, typebotId: [] }, @@ -85,18 +87,38 @@ export const TypebotHeader = () => { - + } - mr={2} onClick={handleBackClick} /> - - + {typebot?.name && ( + + )} + + } + size="sm" + aria-label="Undo" + onClick={undo} + isDisabled={!canUndo} + /> + + + + } + size="sm" + aria-label="Redo" + onClick={redo} + isDisabled={!canRedo} + /> + + diff --git a/apps/builder/components/shared/VariableSearchInput.tsx b/apps/builder/components/shared/VariableSearchInput.tsx index 2f01e887f..c72c3a637 100644 --- a/apps/builder/components/shared/VariableSearchInput.tsx +++ b/apps/builder/components/shared/VariableSearchInput.tsx @@ -16,7 +16,7 @@ import { Variable } from 'models' import React, { useState, useRef, ChangeEvent, useMemo, useEffect } from 'react' import { generate } from 'short-uuid' import { useDebounce } from 'use-debounce' -import { isDefined } from 'utils' +import { isNotDefined } from 'utils' type Props = { initialVariableId?: string @@ -131,7 +131,7 @@ export const VariableSearchInput = ({ shadow="lg" > {(inputValue?.length ?? 0) > 0 && - !isDefined(variables.find((v) => v.name === inputValue)) && ( + isNotDefined(variables.find((v) => v.name === inputValue)) && (