diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx
index df5a15ce7..2ff549b86 100644
--- a/apps/builder/assets/icons.tsx
+++ b/apps/builder/assets/icons.tsx
@@ -280,3 +280,9 @@ export const FilmIcon = (props: IconProps) => (
)
+
+export const WebhookIcon = (props: IconProps) => (
+
+
+
+)
diff --git a/apps/builder/assets/styles/codeMirror.css b/apps/builder/assets/styles/codeMirror.css
new file mode 100644
index 000000000..0b40e9102
--- /dev/null
+++ b/apps/builder/assets/styles/codeMirror.css
@@ -0,0 +1,9 @@
+.cm-editor {
+ height: 100%;
+ outline: 0px solid transparent !important;
+}
+
+.cm-scroller {
+ border-radius: 5px;
+ border: 1px solid #e5e7eb;
+}
diff --git a/apps/builder/components/board/StepTypesList/StepIcon.tsx b/apps/builder/components/board/StepTypesList/StepIcon.tsx
index e72d59b4f..d496cc116 100644
--- a/apps/builder/components/board/StepTypesList/StepIcon.tsx
+++ b/apps/builder/components/board/StepTypesList/StepIcon.tsx
@@ -14,6 +14,7 @@ import {
NumberIcon,
PhoneIcon,
TextIcon,
+ WebhookIcon,
} from 'assets/icons'
import { GoogleAnalyticsLogo, GoogleSheetsLogo } from 'assets/logos'
import {
@@ -59,6 +60,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
return
case IntegrationStepType.GOOGLE_ANALYTICS:
return
+ case IntegrationStepType.WEBHOOK:
+ return
case 'start':
return
default:
diff --git a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx
index 279e8467c..50038598f 100644
--- a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx
+++ b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx
@@ -49,6 +49,8 @@ export const StepTypeLabel = ({ type }: Props) => {
Analytics
)
+ case IntegrationStepType.WEBHOOK:
+ return Webhook
default:
return <>>
}
diff --git a/apps/builder/components/board/graph/BlockNode/BlockNode.tsx b/apps/builder/components/board/graph/BlockNode/BlockNode.tsx
index a8362fd22..be69bbbc0 100644
--- a/apps/builder/components/board/graph/BlockNode/BlockNode.tsx
+++ b/apps/builder/components/board/graph/BlockNode/BlockNode.tsx
@@ -89,7 +89,7 @@ export const BlockNode = ({ block }: Props) => {
borderColor={
isConnecting || isOpened || isPreviewing ? 'blue.400' : 'white'
}
- minW="300px"
+ w="300px"
transition="border 300ms, box-shadow 200ms"
pos="absolute"
style={{
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx
index ef766ed8c..a494fc59f 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx
@@ -16,6 +16,8 @@ import {
Step,
StepOptions,
TextBubbleStep,
+ Webhook,
+ WebhookStep,
} from 'models'
import { useRef } from 'react'
import {
@@ -32,6 +34,7 @@ import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
import { RedirectSettings } from './bodies/RedirectSettings'
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
+import { WebhookSettings } from './bodies/WebhookSettings'
type Props = {
step: Exclude
@@ -51,7 +54,7 @@ export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => {
{
}
export const StepSettings = ({ step }: { step: Step }) => {
- const { updateStep } = useTypebot()
- const handleOptionsChange = (options: StepOptions) =>
+ 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)
+ }
switch (step.type) {
case InputStepType.TEXT: {
@@ -176,6 +186,17 @@ export const StepSettings = ({ step }: { step: Step }) => {
/>
)
}
+ case IntegrationStepType.WEBHOOK: {
+ return (
+
+ )
+ }
default: {
return <>>
}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx
index 17d24add2..dbcb09122 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ChoiceInputSettingsBody.tsx
@@ -18,8 +18,8 @@ export const ChoiceInputSettingsBody = ({
options && onOptionsChange({ ...options, isMultipleChoice })
const handleButtonLabelChange = (buttonLabel: string) =>
options && onOptionsChange({ ...options, buttonLabel })
- const handleVariableChange = (variable: Variable) =>
- options && onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ options && onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx
new file mode 100644
index 000000000..aaee21757
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsItem.tsx
@@ -0,0 +1,51 @@
+import { Stack } from '@chakra-ui/react'
+import { DropdownList } from 'components/shared/DropdownList'
+import { InputWithVariableButton } from 'components/shared/InputWithVariableButton'
+import { TableListItemProps } from 'components/shared/TableList'
+import { VariableSearchInput } from 'components/shared/VariableSearchInput'
+import { Comparison, Variable, ComparisonOperators } from 'models'
+
+export const ComparisonItem = ({
+ item,
+ onItemChange,
+}: TableListItemProps) => {
+ const handleSelectVariable = (variable?: Variable) => {
+ if (variable?.id === item.variableId) return
+ onItemChange({ ...item, variableId: variable?.id })
+ }
+
+ const handleSelectComparisonOperator = (
+ comparisonOperator: ComparisonOperators
+ ) => {
+ if (comparisonOperator === item.comparisonOperator) return
+ onItemChange({ ...item, comparisonOperator })
+ }
+ const handleChangeValue = (value: string) => {
+ if (value === item.value) return
+ onItemChange({ ...item, value })
+ }
+
+ return (
+
+
+
+ currentItem={item.comparisonOperator}
+ onItemSelect={handleSelectComparisonOperator}
+ items={Object.values(ComparisonOperators)}
+ placeholder="Select an operator"
+ />
+ {item.comparisonOperator !== ComparisonOperators.IS_SET && (
+
+ )}
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsList.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsList.tsx
deleted file mode 100644
index 89ef41a71..000000000
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ComparisonsList.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
-import { PlusIcon, TrashIcon } from 'assets/icons'
-import { DebouncedInput } from 'components/shared/DebouncedInput'
-import { DropdownList } from 'components/shared/DropdownList'
-import { VariableSearchInput } from 'components/shared/VariableSearchInput'
-import {
- Comparison,
- ComparisonOperators,
- LogicalOperator,
- Table,
- Variable,
-} from 'models'
-import React, { useEffect, useState } from 'react'
-import { generate } from 'short-uuid'
-import { useImmer } from 'use-immer'
-
-type Props = {
- initialComparisons: Table
- logicalOperator: LogicalOperator
- onLogicalOperatorChange: (logicalOperator: LogicalOperator) => void
- onComparisonsChange: (comparisons: Table) => void
-}
-
-export const ComparisonsList = ({
- initialComparisons,
- logicalOperator,
- onLogicalOperatorChange,
- onComparisonsChange,
-}: Props) => {
- const [comparisons, setComparisons] = useImmer(initialComparisons)
- const [showDeleteId, setShowDeleteId] = useState()
-
- useEffect(() => {
- onComparisonsChange(comparisons)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [comparisons])
-
- const createComparison = () => {
- setComparisons((comparisons) => {
- const id = generate()
- comparisons.byId[id] = {
- id,
- comparisonOperator: ComparisonOperators.EQUAL,
- }
- comparisons.allIds.push(id)
- })
- }
-
- const updateComparison = (
- comparisonId: string,
- updates: Partial>
- ) =>
- setComparisons((comparisons) => {
- comparisons.byId[comparisonId] = {
- ...comparisons.byId[comparisonId],
- ...updates,
- }
- })
-
- const deleteComparison = (comparisonId: string) => () => {
- setComparisons((comparisons) => {
- delete comparisons.byId[comparisonId]
- const index = comparisons.allIds.indexOf(comparisonId)
- if (index !== -1) comparisons.allIds.splice(index, 1)
- })
- }
-
- const handleVariableSelected =
- (comparisonId: string) => (variable: Variable) => {
- updateComparison(comparisonId, { variableId: variable.id })
- }
-
- const handleComparisonOperatorSelected =
- (comparisonId: string) => (dropdownItem: ComparisonOperators) =>
- updateComparison(comparisonId, {
- comparisonOperator: dropdownItem,
- })
- const handleLogicalOperatorSelected = (dropdownItem: LogicalOperator) =>
- onLogicalOperatorChange(dropdownItem)
-
- const handleValueChange = (comparisonId: string) => (value: string) =>
- updateComparison(comparisonId, { value })
-
- const handleMouseEnter = (comparisonId: string) => () => {
- setShowDeleteId(comparisonId)
- }
-
- const handleMouseLeave = () => setShowDeleteId(undefined)
-
- return (
-
- {comparisons.allIds.map((comparisonId, idx) => (
- <>
- {idx > 0 && (
-
-
- currentItem={logicalOperator}
- onItemSelect={handleLogicalOperatorSelected}
- items={Object.values(LogicalOperator)}
- />
-
- )}
-
-
-
-
- currentItem={comparisons.byId[comparisonId].comparisonOperator}
- onItemSelect={handleComparisonOperatorSelected(comparisonId)}
- items={Object.values(ComparisonOperators)}
- />
- {comparisons.byId[comparisonId].comparisonOperator !==
- ComparisonOperators.IS_SET && (
-
- )}
-
-
- }
- aria-label="Remove comparison"
- onClick={deleteComparison(comparisonId)}
- pos="absolute"
- left="-15px"
- top="-15px"
- size="sm"
- shadow="md"
- />
-
-
- >
- ))}
- }
- onClick={createComparison}
- flexShrink={0}
- colorScheme="blue"
- >
- Add a comparison
-
-
- )
-}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx
index afc8728f8..7bc447bfd 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/ConditionSettingsBody/ConditonSettingsBody.tsx
@@ -1,6 +1,9 @@
+import { Flex } from '@chakra-ui/react'
+import { DropdownList } from 'components/shared/DropdownList'
+import { TableList } from 'components/shared/TableList'
import { Comparison, ConditionOptions, LogicalOperator, Table } from 'models'
import React from 'react'
-import { ComparisonsList } from './ComparisonsList'
+import { ComparisonItem } from './ComparisonsItem'
type ConditionSettingsBodyProps = {
options: ConditionOptions
@@ -17,11 +20,19 @@ export const ConditionSettingsBody = ({
onOptionsChange({ ...options, logicalOperator })
return (
-
+ onItemsChange={handleComparisonsChange}
+ Item={ComparisonItem}
+ ComponentBetweenItems={() => (
+
+
+ currentItem={options.logicalOperator}
+ onItemSelect={handleLogicalOperatorChange}
+ items={Object.values(LogicalOperator)}
+ />
+
+ )}
+ addLabel="Add a comparison"
/>
)
}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx
index 1649f5f49..7973366ab 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx
@@ -24,8 +24,8 @@ export const DateInputSettingsBody = ({
onOptionsChange({ ...options, isRange })
const handleHasTimeChange = (hasTime: boolean) =>
onOptionsChange({ ...options, hasTime })
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx
index 67252c296..25d42d814 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx
@@ -17,8 +17,8 @@ export const EmailInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx
new file mode 100644
index 000000000..13db46c39
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithValueStack.tsx
@@ -0,0 +1,35 @@
+import { Stack } from '@chakra-ui/react'
+import { DropdownList } from 'components/shared/DropdownList'
+import { InputWithVariableButton } from 'components/shared/InputWithVariableButton'
+import { TableListItemProps } from 'components/shared/TableList'
+import { Cell } from 'models'
+
+export const CellWithValueStack = ({
+ item,
+ onItemChange,
+ columns,
+}: TableListItemProps & { columns: string[] }) => {
+ const handleColumnSelect = (column: string) => {
+ if (item.column === column) return
+ onItemChange({ ...item, column })
+ }
+ const handleValueChange = (value: string) => {
+ if (item.value === value) return
+ onItemChange({ ...item, value })
+ }
+ return (
+
+
+ currentItem={item.column}
+ onItemSelect={handleColumnSelect}
+ items={columns}
+ placeholder="Select a column"
+ />
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx
new file mode 100644
index 000000000..289416d8e
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/CellWithVariableIdStack.tsx
@@ -0,0 +1,37 @@
+import { Stack } from '@chakra-ui/react'
+import { DropdownList } from 'components/shared/DropdownList'
+import { TableListItemProps } from 'components/shared/TableList'
+import { VariableSearchInput } from 'components/shared/VariableSearchInput'
+import { ExtractingCell, Variable } from 'models'
+
+export const CellWithVariableIdStack = ({
+ item,
+ onItemChange,
+ columns,
+}: TableListItemProps & { columns: string[] }) => {
+ const handleColumnSelect = (column: string) => {
+ if (item.column === column) return
+ onItemChange({ ...item, column })
+ }
+
+ const handleVariableIdChange = (variable?: Variable) => {
+ if (item.variableId === variable?.id) return
+ onItemChange({ ...item, variableId: variable?.id })
+ }
+
+ return (
+
+
+ currentItem={item.column}
+ onItemSelect={handleColumnSelect}
+ items={columns}
+ placeholder="Select a column"
+ />
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/ExtractCellList.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/ExtractCellList.tsx
deleted file mode 100644
index 2121e50b5..000000000
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/ExtractCellList.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
-import { PlusIcon, TrashIcon } from 'assets/icons'
-import { DropdownList } from 'components/shared/DropdownList'
-import { VariableSearchInput } from 'components/shared/VariableSearchInput'
-import { ExtractingCell, Table, Variable } from 'models'
-import React, { useEffect, useState } from 'react'
-import { Sheet } from 'services/integrations'
-import { generate } from 'short-uuid'
-import { useImmer } from 'use-immer'
-
-type Props = {
- sheet: Sheet
- initialCells?: Table
- onCellsChange: (cells: Table) => void
-}
-
-const id = generate()
-const defaultCells: Table = {
- byId: { [id]: {} },
- allIds: [id],
-}
-
-export const ExtractCellList = ({
- sheet,
- initialCells,
- onCellsChange,
-}: Props) => {
- const [cells, setCells] = useImmer(initialCells ?? defaultCells)
- const [showDeleteId, setShowDeleteId] = useState()
-
- useEffect(() => {
- onCellsChange(cells)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [cells])
-
- const createCell = () => {
- setCells((cells) => {
- const id = generate()
- cells.byId[id] = {}
- cells.allIds.push(id)
- })
- }
-
- const updateCell = (cellId: string, updates: Partial) =>
- setCells((cells) => {
- cells.byId[cellId] = {
- ...cells.byId[cellId],
- ...updates,
- }
- })
-
- const deleteCell = (cellId: string) => () => {
- setCells((cells) => {
- delete cells.byId[cellId]
- const index = cells.allIds.indexOf(cellId)
- if (index !== -1) cells.allIds.splice(index, 1)
- })
- }
-
- const handleMouseEnter = (cellId: string) => () => {
- setShowDeleteId(cellId)
- }
-
- const handleCellChange = (cellId: string) => (cell: ExtractingCell) =>
- updateCell(cellId, cell)
-
- const handleMouseLeave = () => setShowDeleteId(undefined)
-
- return (
-
- {cells.allIds.map((cellId) => (
- <>
-
-
-
- }
- aria-label="Remove cell"
- onClick={deleteCell(cellId)}
- pos="absolute"
- left="-15px"
- top="-15px"
- size="sm"
- shadow="md"
- />
-
-
- >
- ))}
- }
- onClick={createCell}
- flexShrink={0}
- colorScheme="blue"
- >
- Add a value
-
-
- )
-}
-
-export const CellWithVariableIdStack = ({
- cell,
- columns,
- onCellChange,
-}: {
- cell: ExtractingCell
- columns: string[]
- onCellChange: (cell: ExtractingCell) => void
-}) => {
- const handleColumnSelect = (column: string) => {
- onCellChange({ ...cell, column })
- }
- const handleVariableIdChange = (variable: Variable) => {
- onCellChange({ ...cell, variableId: variable.id })
- }
- return (
-
-
- currentItem={cell.column}
- onItemSelect={handleColumnSelect}
- items={columns}
- placeholder="Select a column"
- />
-
-
- )
-}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
index 5e6fd56a3..e25f204e0 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/GoogleSheetsSettingsBody.tsx
@@ -1,6 +1,7 @@
import { Divider, Stack, Text } from '@chakra-ui/react'
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
import { DropdownList } from 'components/shared/DropdownList'
+import { TableList, TableListItemProps } from 'components/shared/TableList'
import { useTypebot } from 'contexts/TypebotContext'
import { CredentialsType } from 'db'
import {
@@ -17,10 +18,10 @@ import {
useSheets,
} from 'services/integrations'
import { isDefined } from 'utils'
-import { ExtractCellList } from './ExtractCellList'
import { SheetsDropdown } from './SheetsDropdown'
import { SpreadsheetsDropdown } from './SpreadsheetDropdown'
-import { CellWithValueStack, UpdateCellList } from './UpdateCellList'
+import { CellWithValueStack } from './CellWithValueStack'
+import { CellWithVariableIdStack } from './CellWithVariableIdStack'
type Props = {
options?: GoogleSheetsOptions
@@ -132,13 +133,26 @@ const ActionOptions = ({
const handleExtractingCellsChange = (cellsToExtract: Table) =>
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
+ const UpdatingCellItem = useMemo(
+ () => (props: TableListItemProps) =>
+ ,
+ [sheet.columns]
+ )
+
+ const ExtractingCellItem = useMemo(
+ () => (props: TableListItemProps) =>
+ ,
+ [sheet.columns]
+ )
+
switch (options.action) {
case GoogleSheetsAction.INSERT_ROW:
return (
-
+ initialItems={options.cellsToInsert}
+ onItemsChange={handleInsertColumnsChange}
+ Item={UpdatingCellItem}
+ addLabel="Add a value"
/>
)
case GoogleSheetsAction.UPDATE_ROW:
@@ -146,15 +160,17 @@ const ActionOptions = ({
Row to select
Cells to update
-
+ initialItems={options.cellsToUpsert}
+ onItemsChange={handleUpsertColumnsChange}
+ Item={UpdatingCellItem}
+ addLabel="Add a value"
/>
)
@@ -163,15 +179,17 @@ const ActionOptions = ({
Row to select
Cells to extract
-
+ 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/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx
index ac2c9c748..7da02a930 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SheetsDropdown.tsx
@@ -29,7 +29,7 @@ export const SheetsDropdown = ({
s.name)}
- onSelectItem={handleSpreadsheetSelect}
+ onValueChange={handleSpreadsheetSelect}
placeholder={isLoading ? 'Loading...' : 'Select the sheet'}
isDisabled={isLoading}
/>
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx
index a6ab8d768..b50218828 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/SpreadsheetDropdown.tsx
@@ -27,7 +27,7 @@ export const SpreadsheetsDropdown = ({
s.name)}
- onSelectItem={handleSpreadsheetSelect}
+ onValueChange={handleSpreadsheetSelect}
placeholder={isLoading ? 'Loading...' : 'Search for spreadsheet'}
isDisabled={isLoading}
/>
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/UpdateCellList.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/UpdateCellList.tsx
deleted file mode 100644
index 28ee2e01e..000000000
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/GoogleSheetsSettingsBody/UpdateCellList.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
-import { PlusIcon, TrashIcon } from 'assets/icons'
-import { DropdownList } from 'components/shared/DropdownList'
-import { InputWithVariableButton } from 'components/shared/InputWithVariableButton'
-import { Cell, Table } from 'models'
-import React, { useEffect, useState } from 'react'
-import { Sheet } from 'services/integrations'
-import { generate } from 'short-uuid'
-import { useImmer } from 'use-immer'
-
-type Props = {
- sheet: Sheet
- initialCells?: Table
- onCellsChange: (cells: Table) => void
-}
-
-const id = generate()
-const defaultCells: Table = {
- byId: { [id]: {} },
- allIds: [id],
-}
-
-export const UpdateCellList = ({
- sheet,
- initialCells,
- onCellsChange,
-}: Props) => {
- const [cells, setCells] = useImmer(initialCells ?? defaultCells)
- const [showDeleteId, setShowDeleteId] = useState()
-
- useEffect(() => {
- onCellsChange(cells)
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [cells])
-
- const createCell = () => {
- setCells((cells) => {
- const id = generate()
- cells.byId[id] = {}
- cells.allIds.push(id)
- })
- }
-
- const updateCell = (cellId: string, updates: Partial) =>
- setCells((cells) => {
- cells.byId[cellId] = {
- ...cells.byId[cellId],
- ...updates,
- }
- })
-
- const deleteCell = (cellId: string) => () => {
- setCells((cells) => {
- delete cells.byId[cellId]
- const index = cells.allIds.indexOf(cellId)
- if (index !== -1) cells.allIds.splice(index, 1)
- })
- }
-
- const handleMouseEnter = (cellId: string) => () => {
- setShowDeleteId(cellId)
- }
-
- const handleCellChange = (cellId: string) => (cell: Cell) =>
- updateCell(cellId, cell)
-
- const handleMouseLeave = () => setShowDeleteId(undefined)
-
- return (
-
- {cells.allIds.map((cellId) => (
- <>
-
-
-
- }
- aria-label="Remove cell"
- onClick={deleteCell(cellId)}
- pos="absolute"
- left="-15px"
- top="-15px"
- size="sm"
- shadow="md"
- />
-
-
- >
- ))}
- }
- onClick={createCell}
- flexShrink={0}
- colorScheme="blue"
- >
- Add a value
-
-
- )
-}
-
-export const CellWithValueStack = ({
- cell,
- columns,
- onCellChange,
-}: {
- cell: Cell
- columns: string[]
- onCellChange: (column: Cell) => void
-}) => {
- const handleColumnSelect = (column: string) => {
- onCellChange({ ...cell, column })
- }
- const handleValueChange = (value: string) => {
- onCellChange({ ...cell, value })
- }
- return (
-
-
- currentItem={cell.column}
- onItemSelect={handleColumnSelect}
- items={columns}
- placeholder="Select a column"
- />
-
-
- )
-}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx
index 404c5cca1..b91350b72 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx
@@ -25,8 +25,8 @@ export const NumberInputSettingsBody = ({
onOptionsChange(removeUndefinedFields({ ...options, max }))
const handleStepChange = (step?: number) =>
onOptionsChange(removeUndefinedFields({ ...options, step }))
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx
index 2961c7ebd..3a9cdd257 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx
@@ -17,8 +17,8 @@ export const PhoneNumberSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx
index 4b4f51a2a..9aed047cc 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/SetVariableSettingsBody.tsx
@@ -13,8 +13,8 @@ export const SetVariableSettingsBody = ({
options,
onOptionsChange,
}: Props) => {
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
const handleExpressionChange = (expressionToEvaluate: string) =>
onOptionsChange({ ...options, expressionToEvaluate })
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx
index 2a8dfb2fc..428f78203 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx
@@ -20,8 +20,8 @@ export const TextInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
const handleLongChange = (isLong: boolean) =>
onOptionsChange({ ...options, isLong })
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx
index 063a5b262..f2c34beee 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx
@@ -17,8 +17,8 @@ export const UrlInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
- const handleVariableChange = (variable: Variable) =>
- onOptionsChange({ ...options, variableId: variable.id })
+ const handleVariableChange = (variable?: Variable) =>
+ onOptionsChange({ ...options, variableId: variable?.id })
return (
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx
new file mode 100644
index 000000000..93d8f88ab
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/KeyValueInputs.tsx
@@ -0,0 +1,62 @@
+import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
+import { InputWithVariableButton } from 'components/shared/InputWithVariableButton'
+import { TableListItemProps } from 'components/shared/TableList'
+import { KeyValue } from 'models'
+
+export const QueryParamsInputs = (props: TableListItemProps) => (
+
+)
+
+export const HeadersInputs = (props: TableListItemProps) => (
+
+)
+
+export const KeyValueInputs = ({
+ id,
+ item,
+ onItemChange,
+ keyPlaceholder,
+ valuePlaceholder,
+}: TableListItemProps & {
+ keyPlaceholder?: string
+ valuePlaceholder?: string
+}) => {
+ const handleKeyChange = (key: string) => {
+ if (key === item.key) return
+ onItemChange({ ...item, key })
+ }
+ const handleValueChange = (value: string) => {
+ if (value === item.value) return
+ onItemChange({ ...item, value })
+ }
+ return (
+
+
+ Key:
+
+
+
+ Value:
+
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx
new file mode 100644
index 000000000..9074ca766
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/ResponseMappingInputs.tsx
@@ -0,0 +1,38 @@
+import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
+import { SearchableDropdown } from 'components/shared/SearchableDropdown'
+import { TableListItemProps } from 'components/shared/TableList'
+import { VariableSearchInput } from 'components/shared/VariableSearchInput'
+import { Variable, ResponseVariableMapping } from 'models'
+
+export const DataVariableInputs = ({
+ item,
+ onItemChange,
+ dataItems,
+}: TableListItemProps & { dataItems: string[] }) => {
+ const handleBodyPathChange = (bodyPath: string) =>
+ onItemChange({ ...item, bodyPath })
+ const handleVariableChange = (variable?: Variable) =>
+ onItemChange({ ...item, variableId: variable?.id })
+
+ return (
+
+
+ Data:
+
+
+
+ Set variable:
+
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx
new file mode 100644
index 000000000..2d11c0dbb
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/VariableForTestInputs.tsx
@@ -0,0 +1,39 @@
+import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
+import { DebouncedInput } from 'components/shared/DebouncedInput'
+import { TableListItemProps } from 'components/shared/TableList'
+import { VariableSearchInput } from 'components/shared/VariableSearchInput'
+import { VariableForTest, Variable } from 'models'
+
+export const VariableForTestInputs = ({
+ id,
+ item,
+ onItemChange,
+}: TableListItemProps) => {
+ const handleVariableSelect = (variable?: Variable) =>
+ onItemChange({ ...item, variableId: variable?.id })
+ const handleValueChange = (value: string) => {
+ if (value === item.value) return
+ onItemChange({ ...item, value })
+ }
+ return (
+
+
+ Variable name:
+
+
+
+ Test value:
+
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx
new file mode 100644
index 000000000..1e48f4673
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/WebhookSettings.tsx
@@ -0,0 +1,204 @@
+import React, { useEffect, useMemo, useState } from 'react'
+import {
+ Accordion,
+ AccordionButton,
+ AccordionIcon,
+ AccordionItem,
+ AccordionPanel,
+ Button,
+ Flex,
+ Stack,
+ useToast,
+} from '@chakra-ui/react'
+import { InputWithVariableButton } from 'components/shared/InputWithVariableButton'
+import { useTypebot } from 'contexts/TypebotContext'
+import {
+ HttpMethod,
+ KeyValue,
+ Table,
+ WebhookOptions,
+ VariableForTest,
+ Webhook,
+ 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 {
+ convertVariableForTestToVariables,
+ executeWebhook,
+ getDeepKeys,
+} from 'services/integrations'
+import { HeadersInputs, QueryParamsInputs } from './KeyValueInputs'
+import { VariableForTestInputs } from './VariableForTestInputs'
+import { DataVariableInputs } from './ResponseMappingInputs'
+
+type Props = {
+ options?: WebhookOptions
+ webhook?: Webhook
+ onOptionsChange: (options: WebhookOptions) => void
+ onWebhookChange: (webhook: Partial) => void
+}
+
+export const WebhookSettings = ({
+ options,
+ webhook,
+ onOptionsChange,
+ onWebhookChange,
+}: Props) => {
+ const { createWebhook, typebot, save } = useTypebot()
+ const [testResponse, setTestResponse] = useState()
+ const [responseKeys, setResponseKeys] = useState([])
+
+ const toast = useToast({
+ position: 'top-right',
+ 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 handleQueryParamsChange = (queryParams: Table) =>
+ onWebhookChange({ queryParams })
+
+ const handleHeadersChange = (headers: Table) =>
+ onWebhookChange({ headers })
+
+ const handleBodyChange = (body: string) => onWebhookChange({ body })
+
+ const handleVariablesChange = (variablesForTest: Table) =>
+ onOptionsChange({ ...options, variablesForTest })
+
+ const handleResponseMappingChange = (
+ responseVariableMapping: Table
+ ) => onOptionsChange({ ...options, responseVariableMapping })
+
+ const handleTestRequestClick = async () => {
+ if (!typebot || !webhook) return
+ await save()
+ const { data, error } = await executeWebhook(
+ typebot.id,
+ webhook.id,
+ convertVariableForTestToVariables(
+ options?.variablesForTest,
+ typebot.variables
+ )
+ )
+ if (error) return toast({ title: error.name, description: error.message })
+ setTestResponse(JSON.stringify(data, undefined, 2))
+ setResponseKeys(getDeepKeys(data))
+ }
+
+ const ResponseMappingInputs = useMemo(
+ () => (props: TableListItemProps) =>
+ ,
+ [responseKeys]
+ )
+
+ return (
+
+
+
+
+ currentItem={webhook?.method ?? HttpMethod.GET}
+ onItemSelect={handleMethodChange}
+ items={Object.values(HttpMethod)}
+ />
+
+
+
+
+
+
+ Query params
+
+
+
+
+ initialItems={webhook?.queryParams}
+ onItemsChange={handleQueryParamsChange}
+ Item={QueryParamsInputs}
+ addLabel="Add a param"
+ />
+
+
+
+
+ Headers
+
+
+
+
+ initialItems={webhook?.headers}
+ onItemsChange={handleHeadersChange}
+ Item={HeadersInputs}
+ addLabel="Add a value"
+ />
+
+
+
+
+ Body
+
+
+
+
+
+
+
+
+ Variable values for test
+
+
+
+
+ initialItems={options?.variablesForTest}
+ onItemsChange={handleVariablesChange}
+ Item={VariableForTestInputs}
+ addLabel="Add an entry"
+ />
+
+
+
+
+ {testResponse && }
+ {(testResponse || options?.responseVariableMapping) && (
+
+
+
+ Save in variables
+
+
+
+
+ initialItems={options?.responseVariableMapping}
+ onItemsChange={handleResponseMappingChange}
+ Item={ResponseMappingInputs}
+ />
+
+
+
+ )}
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx
new file mode 100644
index 000000000..0a2775b8f
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/WebhookSettings/index.tsx
@@ -0,0 +1 @@
+export { WebhookSettings } from './WebhookSettings'
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx
index 489081a7c..f495b8cfd 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx
@@ -13,7 +13,7 @@ import { StepIcon } from 'components/board/StepTypesList/StepIcon'
import { isBubbleStep, isTextBubbleStep } from 'utils'
import { Coordinates } from '@dnd-kit/core/dist/types'
import { TextEditor } from './TextEditor/TextEditor'
-import { StepNodeContent } from './StepNodeContent'
+import { StepNodeContent } from './StepNodeContent/StepNodeContent'
import { useTypebot } from 'contexts/TypebotContext'
import { ContextMenu } from 'components/shared/ContextMenu'
import { SettingsPopoverContent } from './SettingsPopoverContent'
@@ -164,6 +164,7 @@ export const StepNode = ({
cursor={'pointer'}
bgColor="white"
align="flex-start"
+ w="full"
>
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent.tsx
deleted file mode 100644
index 0739718f0..000000000
--- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import { Box, Flex, HStack, Image, Stack, Tag, Text } from '@chakra-ui/react'
-import { useTypebot } from 'contexts/TypebotContext'
-import {
- Step,
- StartStep,
- BubbleStepType,
- InputStepType,
- LogicStepType,
- SetVariableStep,
- ConditionStep,
- IntegrationStepType,
- VideoBubbleStep,
- VideoBubbleContentType,
-} from 'models'
-import { ChoiceItemsList } from './ChoiceInputStepNode/ChoiceItemsList'
-import { SourceEndpoint } from './SourceEndpoint'
-
-type Props = {
- step: Step | StartStep
- isConnectable?: boolean
-}
-export const StepNodeContent = ({ step }: Props) => {
- switch (step.type) {
- case BubbleStepType.TEXT: {
- return (
- Click to edit... | | | | | |
`
- : step.content.html,
- }}
- />
- )
- }
- case BubbleStepType.IMAGE: {
- return !step.content?.url ? (
- Click to edit...
- ) : (
-
-
-
- )
- }
- 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 (
-
- {step.options?.labels?.from ?? '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) 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 'start': {
- return {step.label}
- }
- default: {
- return No input
- }
- }
-}
-
-const SetVariableNodeContent = ({ step }: { step: SetVariableStep }) => {
- const { typebot } = useTypebot()
- const variableName =
- typebot?.variables.byId[step.options?.variableId ?? '']?.name ?? ''
- const expression = step.options?.expressionToEvaluate ?? ''
- return (
-
- {variableName === '' && expression === ''
- ? 'Click to edit...'
- : `${variableName} = ${expression}`}
-
- )
-}
-
-const ConditionNodeContent = ({ step }: { step: ConditionStep }) => {
- const { typebot } = useTypebot()
- return (
-
-
- {step.options?.comparisons.allIds.map((comparisonId, idx) => {
- const comparison = step.options?.comparisons.byId[comparisonId]
- const variable = typebot?.variables.byId[comparison?.variableId ?? '']
- return (
-
- {idx > 0 && {step.options?.logicalOperator ?? ''}}
- {variable?.name && (
- {variable.name}
- )}
- {comparison.comparisonOperator && (
- {comparison?.comparisonOperator}
- )}
- {comparison?.value && (
- {comparison.value}
- )}
-
- )
- })}
-
-
-
-
- )
-}
-
-const VideoStepNodeContent = ({ step }: { step: VideoBubbleStep }) => {
- if (!step.content?.url || !step.content.type)
- return Click to edit...
- switch (step.content.type) {
- case VideoBubbleContentType.URL:
- return (
-
-
-
- )
- case VideoBubbleContentType.VIMEO:
- case VideoBubbleContentType.YOUTUBE: {
- const baseUrl =
- step.content.type === VideoBubbleContentType.VIMEO
- ? 'https://player.vimeo.com/video'
- : 'https://www.youtube.com/embed'
- return (
-
-
-
- )
- }
- }
-}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx
new file mode 100644
index 000000000..5c9ef4bb4
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/ConditionNodeContent.tsx
@@ -0,0 +1,52 @@
+import { Flex, Stack, HStack, Tag, Text } from '@chakra-ui/react'
+import { useTypebot } from 'contexts/TypebotContext'
+import { ConditionStep } from 'models'
+import { SourceEndpoint } from '../SourceEndpoint'
+
+export const ConditionNodeContent = ({ step }: { step: ConditionStep }) => {
+ const { typebot } = useTypebot()
+ return (
+
+
+ {step.options?.comparisons.allIds.map((comparisonId, idx) => {
+ const comparison = step.options?.comparisons.byId[comparisonId]
+ const variable = typebot?.variables.byId[comparison?.variableId ?? '']
+ return (
+
+ {idx > 0 && {step.options?.logicalOperator ?? ''}}
+ {variable?.name && (
+ {variable.name}
+ )}
+ {comparison.comparisonOperator && (
+ {comparison?.comparisonOperator}
+ )}
+ {comparison?.value && (
+ {comparison.value}
+ )}
+
+ )
+ })}
+
+
+
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx
new file mode 100644
index 000000000..e42460f54
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/SetVariableNodeContent.tsx
@@ -0,0 +1,17 @@
+import { Text } from '@chakra-ui/react'
+import { useTypebot } from 'contexts/TypebotContext'
+import { SetVariableStep } from 'models'
+
+export const SetVariableNodeContent = ({ step }: { step: SetVariableStep }) => {
+ const { typebot } = useTypebot()
+ const variableName =
+ typebot?.variables.byId[step.options?.variableId ?? '']?.name ?? ''
+ const expression = step.options?.expressionToEvaluate ?? ''
+ return (
+
+ {variableName === '' && expression === ''
+ ? 'Click to edit...'
+ : `${variableName} = ${expression}`}
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx
new file mode 100644
index 000000000..0c9842682
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/StepNodeContent.tsx
@@ -0,0 +1,128 @@
+import { Box, Flex, Image, Text } from '@chakra-ui/react'
+import {
+ Step,
+ StartStep,
+ BubbleStepType,
+ InputStepType,
+ LogicStepType,
+ IntegrationStepType,
+} from 'models'
+import { ChoiceItemsList } from '../ChoiceInputStepNode/ChoiceItemsList'
+import { ConditionNodeContent } from './ConditionNodeContent'
+import { SetVariableNodeContent } from './SetVariableNodeContent'
+import { VideoStepNodeContent } from './VideoStepNodeContent'
+import { WebhookContent } from './WebhookContent'
+
+type Props = {
+ step: Step | StartStep
+ isConnectable?: boolean
+}
+export const StepNodeContent = ({ step }: Props) => {
+ switch (step.type) {
+ case BubbleStepType.TEXT: {
+ return (
+ Click to edit...`
+ : step.content.html,
+ }}
+ />
+ )
+ }
+ case BubbleStepType.IMAGE: {
+ return !step.content?.url ? (
+ Click to edit...
+ ) : (
+
+
+
+ )
+ }
+ 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 (
+
+ {step.options?.labels?.from ?? '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) 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/StepNodeContent/VideoStepNodeContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/VideoStepNodeContent.tsx
new file mode 100644
index 000000000..4654c4e70
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/VideoStepNodeContent.tsx
@@ -0,0 +1,51 @@
+import { Box, Text } from '@chakra-ui/react'
+import { VideoBubbleStep, VideoBubbleContentType } from 'models'
+
+export const VideoStepNodeContent = ({ step }: { step: VideoBubbleStep }) => {
+ if (!step.content?.url || !step.content.type)
+ return Click to edit...
+ switch (step.content.type) {
+ case VideoBubbleContentType.URL:
+ return (
+
+
+
+ )
+ case VideoBubbleContentType.VIMEO:
+ case VideoBubbleContentType.YOUTUBE: {
+ const baseUrl =
+ step.content.type === VideoBubbleContentType.VIMEO
+ ? 'https://player.vimeo.com/video'
+ : 'https://www.youtube.com/embed'
+ return (
+
+
+
+ )
+ }
+ }
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/WebhookContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/WebhookContent.tsx
new file mode 100644
index 000000000..b025d28d3
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/WebhookContent.tsx
@@ -0,0 +1,22 @@
+import { Text } from '@chakra-ui/react'
+import { useTypebot } from 'contexts/TypebotContext'
+import { WebhookStep } from 'models'
+import { useMemo } from 'react'
+
+type Props = {
+ step: WebhookStep
+}
+
+export const WebhookContent = ({ step }: Props) => {
+ const { typebot } = useTypebot()
+ const webhook = useMemo(
+ () => typebot?.webhooks.byId[step.options?.webhookId ?? ''],
+ [step.options?.webhookId, typebot?.webhooks.byId]
+ )
+ if (!webhook?.url) return Configure...
+ return (
+
+ {webhook.method} {webhook.url}
+
+ )
+}
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/index.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/index.tsx
new file mode 100644
index 000000000..d9ebd7e10
--- /dev/null
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeContent/index.tsx
@@ -0,0 +1 @@
+export { StepNodeContent } from './StepNodeContent'
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx
index c25c3bc85..7532c4b96 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeOverlay.tsx
@@ -1,7 +1,7 @@
import { StackProps, HStack } from '@chakra-ui/react'
import { StartStep, Step } from 'models'
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
-import { StepNodeContent } from './StepNodeContent'
+import { StepNodeContent } from './StepNodeContent/StepNodeContent'
export const StepNodeOverlay = ({
step,
diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/TextEditor.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/TextEditor.tsx
index b349087ed..bd4126d93 100644
--- a/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/TextEditor.tsx
+++ b/apps/builder/components/board/graph/BlockNode/StepNode/TextEditor/TextEditor.tsx
@@ -94,9 +94,9 @@ export const TextEditor = ({
e.stopPropagation()
}
- const handleVariableSelected = (variable: Variable) => {
+ const handleVariableSelected = (variable?: Variable) => {
setIsVariableDropdownOpen(false)
- if (!rememberedSelection.current) return
+ if (!rememberedSelection.current || !variable) return
Transforms.select(editor, rememberedSelection.current)
Transforms.insertText(editor, '{{' + variable.name + '}}')
ReactEditor.focus(editor as unknown as ReactEditor)
diff --git a/apps/builder/components/shared/CodeEditor.tsx b/apps/builder/components/shared/CodeEditor.tsx
new file mode 100644
index 000000000..0145de820
--- /dev/null
+++ b/apps/builder/components/shared/CodeEditor.tsx
@@ -0,0 +1,58 @@
+import { Box, BoxProps } from '@chakra-ui/react'
+import { EditorState, EditorView, basicSetup } from '@codemirror/basic-setup'
+import { json } from '@codemirror/lang-json'
+import { useEffect, useRef } from 'react'
+
+type Props = {
+ value: string
+ onChange?: (value: string) => void
+ isReadOnly?: boolean
+}
+export const CodeEditor = ({
+ value,
+ onChange,
+ isReadOnly = false,
+ ...props
+}: Props & Omit) => {
+ const editorContainer = useRef(null)
+ const editorView = useRef(null)
+
+ useEffect(() => {
+ if (!editorView.current || !isReadOnly) return
+ editorView.current.dispatch({
+ changes: { from: 0, insert: value },
+ })
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [value])
+
+ useEffect(() => {
+ if (!editorContainer.current) return
+ const updateListenerExtension = EditorView.updateListener.of((update) => {
+ if (update.docChanged && onChange)
+ onChange(update.state.doc.toJSON().join(' '))
+ })
+ const editor = new EditorView({
+ state: EditorState.create({
+ extensions: [
+ updateListenerExtension,
+ basicSetup,
+ json(),
+ EditorState.readOnly.of(isReadOnly),
+ ],
+ }),
+ parent: editorContainer.current,
+ })
+ editor.dispatch({
+ changes: { from: 0, insert: value },
+ })
+ editorView.current = editor
+ return () => {
+ editor.destroy()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ return (
+
+ )
+}
diff --git a/apps/builder/components/shared/InputWithVariableButton.tsx b/apps/builder/components/shared/InputWithVariableButton.tsx
index f1f8723aa..4b61cfe11 100644
--- a/apps/builder/components/shared/InputWithVariableButton.tsx
+++ b/apps/builder/components/shared/InputWithVariableButton.tsx
@@ -1,4 +1,5 @@
import {
+ Flex,
HStack,
IconButton,
Input,
@@ -6,6 +7,7 @@ import {
Popover,
PopoverContent,
PopoverTrigger,
+ Tooltip,
} from '@chakra-ui/react'
import { UserIcon } from 'assets/icons'
import { Variable } from 'models'
@@ -29,12 +31,12 @@ export const InputWithVariableButton = ({
const [carretPosition, setCarretPosition] = useState(0)
useEffect(() => {
- onChange(debouncedValue)
+ if (debouncedValue !== initialValue) onChange(debouncedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue])
- const handleVariableSelected = (variable: Variable) => {
- if (!inputRef.current) return
+ const handleVariableSelected = (variable?: Variable) => {
+ if (!inputRef.current || !variable) return
const cursorPosition = carretPosition
const textBeforeCursorPosition = inputRef.current.value.substring(
0,
@@ -67,7 +69,7 @@ export const InputWithVariableButton = ({
setValue(e.target.value)
return (
-
+
- }
- pos="relative"
- ml="2"
- />
+
+
+ }
+ pos="relative"
+ />
+
+
void
+ onValueChange?: (value: string) => void
} & InputProps
+
export const SearchableDropdown = ({
selectedItem,
items,
- onSelectItem,
+ onValueChange,
...inputProps
}: Props) => {
const { onOpen, onClose, isOpen } = useDisclosure()
- const [inputValue, setInputValue] = useState(selectedItem)
+ const [inputValue, setInputValue] = useState(selectedItem ?? '')
+ const [debouncedInputValue] = useDebounce(inputValue, 200)
const [filteredItems, setFilteredItems] = useState([
...items
.filter((item) =>
@@ -52,6 +55,13 @@ export const SearchableDropdown = ({
handler: onClose,
})
+ useEffect(() => {
+ onValueChange &&
+ debouncedInputValue !== selectedItem &&
+ onValueChange(debouncedInputValue)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedInputValue])
+
const onInputChange = (e: ChangeEvent) => {
setInputValue(e.target.value)
if (e.target.value === '') {
@@ -69,7 +79,6 @@ export const SearchableDropdown = ({
const handleItemClick = (item: string) => () => {
setInputValue(item)
- onSelectItem(item)
onClose()
}
diff --git a/apps/builder/components/shared/TableList.tsx b/apps/builder/components/shared/TableList.tsx
new file mode 100644
index 000000000..e9a38e71a
--- /dev/null
+++ b/apps/builder/components/shared/TableList.tsx
@@ -0,0 +1,114 @@
+import { Box, Button, Fade, Flex, IconButton, Stack } from '@chakra-ui/react'
+import { TrashIcon, PlusIcon } from 'assets/icons'
+import { Draft } from 'immer'
+import { Table } from 'models'
+import React, { useEffect, useState } from 'react'
+import { generate } from 'short-uuid'
+import { useImmer } from 'use-immer'
+
+export type TableListItemProps = {
+ id: string
+ item: T
+ onItemChange: (item: T) => void
+}
+
+type Props = {
+ initialItems?: Table
+ onItemsChange: (items: Table) => void
+ addLabel?: string
+ Item: (props: TableListItemProps) => JSX.Element
+ ComponentBetweenItems?: (props: unknown) => JSX.Element
+}
+
+export const TableList = ({
+ initialItems,
+ onItemsChange,
+ addLabel = 'Add',
+ Item,
+ ComponentBetweenItems = () => <>>,
+}: Props) => {
+ const [items, setItems] = useImmer(initialItems ?? { byId: {}, allIds: [] })
+ const [showDeleteId, setShowDeleteId] = useState()
+
+ useEffect(() => {
+ if (items.allIds.length === 0) createItem()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ useEffect(() => {
+ onItemsChange(items)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [items])
+
+ const createItem = () => {
+ setItems((items) => {
+ const id = generate()
+ items.byId[id] = { id } as unknown as Draft
+ items.allIds.push(id)
+ })
+ }
+
+ const updateItem = (itemId: string, updates: Partial) =>
+ setItems((items) => {
+ items.byId[itemId] = {
+ ...items.byId[itemId],
+ ...updates,
+ }
+ })
+
+ const deleteItem = (itemId: string) => () => {
+ setItems((items) => {
+ delete items.byId[itemId]
+ const index = items.allIds.indexOf(itemId)
+ if (index !== -1) items.allIds.splice(index, 1)
+ })
+ }
+
+ const handleMouseEnter = (itemId: string) => () => setShowDeleteId(itemId)
+
+ const handleCellChange = (itemId: string) => (item: T) =>
+ updateItem(itemId, item)
+
+ const handleMouseLeave = () => setShowDeleteId(undefined)
+
+ return (
+
+ {items.allIds.map((itemId, idx) => (
+
+ {idx !== 0 && }
+
+
+
+ }
+ aria-label="Remove cell"
+ onClick={deleteItem(itemId)}
+ pos="absolute"
+ left="-15px"
+ top="-15px"
+ size="sm"
+ shadow="md"
+ />
+
+
+
+ ))}
+ }
+ onClick={createItem}
+ flexShrink={0}
+ colorScheme="blue"
+ >
+ {addLabel}
+
+
+ )
+}
diff --git a/apps/builder/components/shared/VariableSearchInput.tsx b/apps/builder/components/shared/VariableSearchInput.tsx
index 7f542c8b5..5ddea4c07 100644
--- a/apps/builder/components/shared/VariableSearchInput.tsx
+++ b/apps/builder/components/shared/VariableSearchInput.tsx
@@ -15,11 +15,14 @@ import { useTypebot } from 'contexts/TypebotContext'
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'
type Props = {
initialVariableId?: string
- onSelectVariable: (variable: Pick) => void
+ onSelectVariable: (
+ variable: Pick | undefined
+ ) => void
isDefaultOpen?: boolean
} & InputProps
@@ -39,6 +42,7 @@ export const VariableSearchInput = ({
const [inputValue, setInputValue] = useState(
typebot?.variables.byId[initialVariableId ?? '']?.name ?? ''
)
+ const [debouncedInputValue] = useDebounce(inputValue, 200)
const [filteredItems, setFilteredItems] = useState(variables)
const dropdownRef = useRef(null)
const inputRef = useRef(null)
@@ -53,11 +57,18 @@ export const VariableSearchInput = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
+ useEffect(() => {
+ const variable = variables.find((v) => v.name === debouncedInputValue)
+ if (variable) onSelectVariable(variable)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedInputValue])
+
const onInputChange = (e: ChangeEvent) => {
setInputValue(e.target.value)
onOpen()
if (e.target.value === '') {
setFilteredItems([...variables.slice(0, 50)])
+ onSelectVariable(undefined)
return
}
setFilteredItems([
diff --git a/apps/builder/components/theme/GeneralContent/FontSelector/FontSelector.tsx b/apps/builder/components/theme/GeneralContent/FontSelector/FontSelector.tsx
index 2419b3ce2..90efbaafa 100644
--- a/apps/builder/components/theme/GeneralContent/FontSelector/FontSelector.tsx
+++ b/apps/builder/components/theme/GeneralContent/FontSelector/FontSelector.tsx
@@ -27,18 +27,19 @@ export const FontSelector = ({
)
}
+ const handleFontSelected = (nextFont: string) => {
+ if (nextFont == currentFont) return
+ setCurrentFont(nextFont)
+ onSelectFont(nextFont)
+ }
+
return (
Font
{
- if (nextFont !== currentFont) {
- setCurrentFont(nextFont)
- onSelectFont(nextFont)
- }
- }}
+ onValueChange={handleFontSelected}
/>
)
diff --git a/apps/builder/contexts/TypebotContext/TypebotContext.tsx b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
index 7b185ecf1..1abac7612 100644
--- a/apps/builder/contexts/TypebotContext/TypebotContext.tsx
+++ b/apps/builder/contexts/TypebotContext/TypebotContext.tsx
@@ -29,6 +29,7 @@ import { stepsAction, StepsActions } from './actions/steps'
import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
import { variablesAction, VariablesActions } from './actions/variables'
import { edgesAction, EdgesActions } from './actions/edges'
+import { webhooksAction, WebhooksAction } from './actions/webhooks'
type UpdateTypebotPayload = Partial<{
theme: Theme
@@ -52,7 +53,8 @@ const typebotContext = createContext<
StepsActions &
ChoiceItemsActions &
VariablesActions &
- EdgesActions
+ EdgesActions &
+ WebhooksAction
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
>({})
@@ -78,9 +80,11 @@ export const TypebotContext = ({
description: error.message,
}),
})
+
const [localTypebot, setLocalTypebot] = useImmer(
undefined
)
+
const [localPublishedTypebot, setLocalPublishedTypebot] =
useState()
const [isSavingLoading, setIsSavingLoading] = useState(false)
@@ -214,6 +218,7 @@ export const TypebotContext = ({
...choiceItemsAction(setLocalTypebot as Updater),
...variablesAction(setLocalTypebot as Updater),
...edgesAction(setLocalTypebot as Updater),
+ ...webhooksAction(setLocalTypebot as Updater),
}}
>
{children}
diff --git a/apps/builder/contexts/TypebotContext/actions/steps.ts b/apps/builder/contexts/TypebotContext/actions/steps.ts
index 7ce7a848b..61f39d902 100644
--- a/apps/builder/contexts/TypebotContext/actions/steps.ts
+++ b/apps/builder/contexts/TypebotContext/actions/steps.ts
@@ -10,8 +10,9 @@ import { Updater } from 'use-immer'
import { removeEmptyBlocks } from './blocks'
import { WritableDraft } from 'immer/dist/types/types-external'
import { createChoiceItemDraft, deleteChoiceItemDraft } from './choiceItems'
-import { isChoiceInput } from 'utils'
+import { isChoiceInput, isWebhookStep } from 'utils'
import { deleteEdgeDraft } from './edges'
+import { deleteWebhookDraft } from './webhooks'
export type StepsActions = {
createStep: (
@@ -51,6 +52,8 @@ export const stepsAction = (setTypebot: Updater): StepsActions => ({
setTypebot((typebot) => {
const step = typebot.steps.byId[stepId]
if (isChoiceInput(step)) deleteChoiceItemsInsideStep(typebot, step)
+ if (isWebhookStep(step))
+ deleteWebhookDraft(step.options?.webhookId)(typebot)
deleteAssociatedEdges(typebot, stepId)
removeStepIdFromBlock(typebot, stepId)
deleteStepDraft(typebot, stepId)
diff --git a/apps/builder/contexts/TypebotContext/actions/variables.ts b/apps/builder/contexts/TypebotContext/actions/variables.ts
index f4e2fceba..c56996963 100644
--- a/apps/builder/contexts/TypebotContext/actions/variables.ts
+++ b/apps/builder/contexts/TypebotContext/actions/variables.ts
@@ -1,10 +1,9 @@
import { Typebot, Variable } from 'models'
import { Updater } from 'use-immer'
import { WritableDraft } from 'immer/dist/types/types-external'
-import { generate } from 'short-uuid'
export type VariablesActions = {
- createVariable: (variable: Omit | Variable) => void
+ createVariable: (variable: Variable) => void
updateVariable: (
variableId: string,
updates: Partial>
@@ -15,10 +14,10 @@ export type VariablesActions = {
export const variablesAction = (
setTypebot: Updater
): VariablesActions => ({
- createVariable: (variable: Omit | Variable) => {
+ createVariable: (newVariable: Variable) => {
setTypebot((typebot) => {
- const id = createVariableDraft(typebot, variable)
- return id
+ typebot.variables.byId[newVariable.id] = newVariable
+ typebot.variables.allIds.push(newVariable.id)
})
},
updateVariable: (
@@ -46,15 +45,3 @@ export const deleteVariableDraft = (
const index = typebot.variables.allIds.indexOf(variableId)
if (index !== -1) typebot.variables.allIds.splice(index, 1)
}
-
-export const createVariableDraft = (
- typebot: WritableDraft,
- variable: Omit | Variable
-) => {
- const newVariable = {
- ...variable,
- id: 'id' in variable ? variable.id : generate(),
- }
- typebot.variables.byId[newVariable.id] = newVariable
- typebot.variables.allIds.push(newVariable.id)
-}
diff --git a/apps/builder/contexts/TypebotContext/actions/webhooks.ts b/apps/builder/contexts/TypebotContext/actions/webhooks.ts
new file mode 100644
index 000000000..efa164a41
--- /dev/null
+++ b/apps/builder/contexts/TypebotContext/actions/webhooks.ts
@@ -0,0 +1,41 @@
+import { Typebot, Webhook } from 'models'
+import { Updater } from 'use-immer'
+import { WritableDraft } from 'immer/dist/internal'
+
+export type WebhooksAction = {
+ createWebhook: (webook: Webhook) => void
+ updateWebhook: (
+ webhookId: string,
+ updates: Partial>
+ ) => void
+ deleteWebhook: (variableId: string) => void
+}
+
+export const webhooksAction = (
+ setTypebot: Updater
+): WebhooksAction => ({
+ createWebhook: (newWebhook: Webhook) => {
+ setTypebot((typebot) => {
+ typebot.webhooks.byId[newWebhook.id] = newWebhook
+ typebot.webhooks.allIds.push(newWebhook.id)
+ })
+ },
+ updateWebhook: (webhookId: string, updates: Partial>) =>
+ setTypebot((typebot) => {
+ typebot.webhooks.byId[webhookId] = {
+ ...typebot.webhooks.byId[webhookId],
+ ...updates,
+ }
+ }),
+ deleteWebhook: (webhookId: string) => {
+ setTypebot(deleteWebhookDraft(webhookId))
+ },
+})
+
+export const deleteWebhookDraft =
+ (webhookId?: string) => (typebot: WritableDraft) => {
+ if (!webhookId) return
+ delete typebot.webhooks.byId[webhookId]
+ const index = typebot.webhooks.allIds.indexOf(webhookId)
+ if (index !== -1) typebot.webhooks.allIds.splice(index, 1)
+ }
diff --git a/apps/builder/cypress/fixtures/typebots/integrations/googleSheets.json b/apps/builder/cypress/fixtures/typebots/integrations/googleSheets.json
index 2152b438d..a32e52f59 100644
--- a/apps/builder/cypress/fixtures/typebots/integrations/googleSheets.json
+++ b/apps/builder/cypress/fixtures/typebots/integrations/googleSheets.json
@@ -6,6 +6,7 @@
"ownerId": "ckylsz8yy0335z31amvq0jwtt",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"j24wz82YG3rjXMgrmCiTLy": {
diff --git a/apps/builder/cypress/fixtures/typebots/integrations/googleSheetsGet.json b/apps/builder/cypress/fixtures/typebots/integrations/googleSheetsGet.json
index 0bfa40051..9ca1e2cc1 100644
--- a/apps/builder/cypress/fixtures/typebots/integrations/googleSheetsGet.json
+++ b/apps/builder/cypress/fixtures/typebots/integrations/googleSheetsGet.json
@@ -6,6 +6,7 @@
"ownerId": "ckyltekzq0533z31ad8opmacz",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"kPupUcEn7TcBGKHUpgK2Q5": {
diff --git a/apps/builder/cypress/fixtures/typebots/integrations/webhook.json b/apps/builder/cypress/fixtures/typebots/integrations/webhook.json
new file mode 100644
index 000000000..d7ac6c320
--- /dev/null
+++ b/apps/builder/cypress/fixtures/typebots/integrations/webhook.json
@@ -0,0 +1,150 @@
+{
+ "id": "typebot4",
+ "createdAt": "2022-01-21T07:55:14.727Z",
+ "updatedAt": "2022-01-21T07:55:14.727Z",
+ "name": "My typebot",
+ "ownerId": "user2",
+ "publishedTypebotId": null,
+ "folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
+ "blocks": {
+ "byId": {
+ "3kH2sUjVThQDWmqdoKnGk5": {
+ "id": "3kH2sUjVThQDWmqdoKnGk5",
+ "title": "Start",
+ "stepIds": ["oxTsU2C1RX5QHuyY8qjHAM"],
+ "graphCoordinates": { "x": 42, "y": 13 }
+ },
+ "b9mSgu7RKmK4xuiTVQP5Me8": {
+ "id": "b9mSgu7RKmK4xuiTVQP5Me8",
+ "title": "Block #3",
+ "stepIds": ["ssLd2wjExS9qWRur4tZDU1Z"],
+ "graphCoordinates": { "x": 300, "y": 550 }
+ },
+ "bdFW2HHjMoEFmqHtFre9Xi8": {
+ "id": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "title": "Block #2",
+ "stepIds": ["sgkADMK25y9P9V3vjwjBaac", "ssEiEECKSFkA44dGDceHxKw"],
+ "graphCoordinates": { "x": 121, "y": 227 }
+ }
+ },
+ "allIds": [
+ "3kH2sUjVThQDWmqdoKnGk5",
+ "bdFW2HHjMoEFmqHtFre9Xi8",
+ "b9mSgu7RKmK4xuiTVQP5Me8"
+ ]
+ },
+ "steps": {
+ "byId": {
+ "oxTsU2C1RX5QHuyY8qjHAM": {
+ "id": "oxTsU2C1RX5QHuyY8qjHAM",
+ "type": "start",
+ "label": "Start",
+ "edgeId": "25yX9DnQgdafpdAjfAu5Fp",
+ "blockId": "3kH2sUjVThQDWmqdoKnGk5"
+ },
+ "sgkADMK25y9P9V3vjwjBaac": {
+ "id": "sgkADMK25y9P9V3vjwjBaac",
+ "type": "text",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "content": {
+ "html": "Ready?
",
+ "richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }],
+ "plainText": "Ready?"
+ }
+ },
+ "ssEiEECKSFkA44dGDceHxKw": {
+ "id": "ssEiEECKSFkA44dGDceHxKw",
+ "type": "choice input",
+ "edgeId": "oxEEtym3NfDf34NCipzjRQ",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "options": { "itemIds": ["q69Ex7LacPrH9QUMeosRnB"] }
+ },
+ "ssLd2wjExS9qWRur4tZDU1Z": {
+ "id": "ssLd2wjExS9qWRur4tZDU1Z",
+ "type": "Webhook",
+ "blockId": "b9mSgu7RKmK4xuiTVQP5Me8",
+ "options": { "webhookId": "4h4Kk3Q1qGy7gFzpZtWVpU" }
+ }
+ },
+ "allIds": [
+ "oxTsU2C1RX5QHuyY8qjHAM",
+ "sgkADMK25y9P9V3vjwjBaac",
+ "ssEiEECKSFkA44dGDceHxKw",
+ "ssLd2wjExS9qWRur4tZDU1Z"
+ ]
+ },
+ "choiceItems": {
+ "byId": {
+ "q69Ex7LacPrH9QUMeosRnB": {
+ "id": "q69Ex7LacPrH9QUMeosRnB",
+ "stepId": "ssEiEECKSFkA44dGDceHxKw",
+ "content": "Go"
+ }
+ },
+ "allIds": ["q69Ex7LacPrH9QUMeosRnB"]
+ },
+ "variables": {
+ "byId": {
+ "oASkBtoLqkYNqeakcjZH4L": {
+ "id": "oASkBtoLqkYNqeakcjZH4L",
+ "name": "secret 1"
+ },
+ "4tvkRmf32wiTsXrYoqyhfr": {
+ "id": "4tvkRmf32wiTsXrYoqyhfr",
+ "name": "secret 2"
+ },
+ "jEg1FvkCU5S5owNAxXFsHL": {
+ "id": "jEg1FvkCU5S5owNAxXFsHL",
+ "name": "secret 3"
+ },
+ "rEoE1ehHzgx8X3d3UPGDHg": {
+ "id": "rEoE1ehHzgx8X3d3UPGDHg",
+ "name": "secret 4"
+ }
+ },
+ "allIds": [
+ "oASkBtoLqkYNqeakcjZH4L",
+ "4tvkRmf32wiTsXrYoqyhfr",
+ "jEg1FvkCU5S5owNAxXFsHL",
+ "rEoE1ehHzgx8X3d3UPGDHg"
+ ]
+ },
+ "webhooks": {
+ "byId": {
+ "4h4Kk3Q1qGy7gFzpZtWVpU": { "id": "4h4Kk3Q1qGy7gFzpZtWVpU", "url": "" }
+ },
+ "allIds": ["4h4Kk3Q1qGy7gFzpZtWVpU"]
+ },
+ "edges": {
+ "byId": {
+ "25yX9DnQgdafpdAjfAu5Fp": {
+ "id": "25yX9DnQgdafpdAjfAu5Fp",
+ "to": { "blockId": "bdFW2HHjMoEFmqHtFre9Xi8" },
+ "from": {
+ "stepId": "oxTsU2C1RX5QHuyY8qjHAM",
+ "blockId": "3kH2sUjVThQDWmqdoKnGk5"
+ }
+ },
+ "oxEEtym3NfDf34NCipzjRQ": {
+ "id": "oxEEtym3NfDf34NCipzjRQ",
+ "to": { "blockId": "b9mSgu7RKmK4xuiTVQP5Me8" },
+ "from": {
+ "stepId": "ssEiEECKSFkA44dGDceHxKw",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8"
+ }
+ }
+ },
+ "allIds": ["25yX9DnQgdafpdAjfAu5Fp", "oxEEtym3NfDf34NCipzjRQ"]
+ },
+ "theme": {
+ "general": {
+ "font": "Open Sans",
+ "background": { "type": "None", "content": "#ffffff" }
+ }
+ },
+ "settings": {
+ "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ },
+ "publicId": null
+}
diff --git a/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.json b/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.json
new file mode 100644
index 000000000..631a48a17
--- /dev/null
+++ b/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.json
@@ -0,0 +1,264 @@
+{
+ "id": "typebot4",
+ "createdAt": "2022-01-21T07:55:14.727Z",
+ "updatedAt": "2022-01-21T07:55:14.727Z",
+ "name": "My typebot",
+ "ownerId": "user2",
+ "publishedTypebotId": null,
+ "folderId": null,
+ "blocks": {
+ "byId": {
+ "3kH2sUjVThQDWmqdoKnGk5": {
+ "id": "3kH2sUjVThQDWmqdoKnGk5",
+ "title": "Start",
+ "stepIds": ["oxTsU2C1RX5QHuyY8qjHAM"],
+ "graphCoordinates": { "x": 42, "y": 13 }
+ },
+ "b9mSgu7RKmK4xuiTVQP5Me8": {
+ "id": "b9mSgu7RKmK4xuiTVQP5Me8",
+ "title": "Block #3",
+ "stepIds": ["ssLd2wjExS9qWRur4tZDU1Z"],
+ "graphCoordinates": { "x": 300, "y": 550 }
+ },
+ "bdFW2HHjMoEFmqHtFre9Xi8": {
+ "id": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "title": "Block #2",
+ "stepIds": ["sgkADMK25y9P9V3vjwjBaac", "ssEiEECKSFkA44dGDceHxKw"],
+ "graphCoordinates": { "x": 121, "y": 227 }
+ },
+ "bmz4rc8r19H2C6b7soxzby4": {
+ "id": "bmz4rc8r19H2C6b7soxzby4",
+ "title": "Block #4",
+ "graphCoordinates": { "x": 632, "y": 279 },
+ "stepIds": ["sgTWsRM1qF2YoYLuGo3Z3pU"]
+ }
+ },
+ "allIds": [
+ "3kH2sUjVThQDWmqdoKnGk5",
+ "bdFW2HHjMoEFmqHtFre9Xi8",
+ "b9mSgu7RKmK4xuiTVQP5Me8",
+ "bmz4rc8r19H2C6b7soxzby4"
+ ]
+ },
+ "steps": {
+ "byId": {
+ "oxTsU2C1RX5QHuyY8qjHAM": {
+ "id": "oxTsU2C1RX5QHuyY8qjHAM",
+ "type": "start",
+ "label": "Start",
+ "edgeId": "25yX9DnQgdafpdAjfAu5Fp",
+ "blockId": "3kH2sUjVThQDWmqdoKnGk5"
+ },
+ "sgkADMK25y9P9V3vjwjBaac": {
+ "id": "sgkADMK25y9P9V3vjwjBaac",
+ "type": "text",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "content": {
+ "html": "Ready?
",
+ "richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }],
+ "plainText": "Ready?"
+ }
+ },
+ "ssEiEECKSFkA44dGDceHxKw": {
+ "id": "ssEiEECKSFkA44dGDceHxKw",
+ "type": "choice input",
+ "edgeId": "oxEEtym3NfDf34NCipzjRQ",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8",
+ "options": { "itemIds": ["q69Ex7LacPrH9QUMeosRnB"] }
+ },
+ "ssLd2wjExS9qWRur4tZDU1Z": {
+ "id": "ssLd2wjExS9qWRur4tZDU1Z",
+ "type": "Webhook",
+ "blockId": "b9mSgu7RKmK4xuiTVQP5Me8",
+ "options": {
+ "webhookId": "4h4Kk3Q1qGy7gFzpZtWVpU",
+ "variablesForTest": {
+ "byId": {
+ "6pMn1xm1y3xWVSdJetMAJH": {
+ "id": "6pMn1xm1y3xWVSdJetMAJH",
+ "variableId": "oASkBtoLqkYNqeakcjZH4L",
+ "value": "secret1"
+ },
+ "ettAiB75uoFWnJyPS7gn5k": {
+ "id": "ettAiB75uoFWnJyPS7gn5k",
+ "variableId": "4tvkRmf32wiTsXrYoqyhfr",
+ "value": "secret2"
+ },
+ "kKpD3Q4YvFQ7CGWiZxJF4s": {
+ "id": "kKpD3Q4YvFQ7CGWiZxJF4s",
+ "variableId": "jEg1FvkCU5S5owNAxXFsHL",
+ "value": "secret3"
+ },
+ "xjUC5Q3msXCw9fwqpNdoSx": {
+ "id": "xjUC5Q3msXCw9fwqpNdoSx",
+ "variableId": "rEoE1ehHzgx8X3d3UPGDHg",
+ "value": "secret4"
+ }
+ },
+ "allIds": [
+ "6pMn1xm1y3xWVSdJetMAJH",
+ "ettAiB75uoFWnJyPS7gn5k",
+ "kKpD3Q4YvFQ7CGWiZxJF4s",
+ "xjUC5Q3msXCw9fwqpNdoSx"
+ ]
+ },
+ "responseVariableMapping": {
+ "byId": {
+ "o53h6M1sgHJfDTY5C3YEaT": {
+ "id": "o53h6M1sgHJfDTY5C3YEaT",
+ "bodyPath": "data[0].name",
+ "variableId": "4kVx5uf8W1XP6WsfJEvt8v"
+ }
+ },
+ "allIds": ["o53h6M1sgHJfDTY5C3YEaT"]
+ }
+ },
+ "edgeId": "81SjKnxuUgrPmXvvJJihHM"
+ },
+ "sgTWsRM1qF2YoYLuGo3Z3pU": {
+ "id": "sgTWsRM1qF2YoYLuGo3Z3pU",
+ "blockId": "bmz4rc8r19H2C6b7soxzby4",
+ "type": "text",
+ "content": {
+ "html": "His name is {{Name}}
",
+ "richText": [
+ { "type": "p", "children": [{ "text": "His name is {{Name}}" }] }
+ ],
+ "plainText": "His name is {{Name}}"
+ }
+ }
+ },
+ "allIds": [
+ "oxTsU2C1RX5QHuyY8qjHAM",
+ "sgkADMK25y9P9V3vjwjBaac",
+ "ssEiEECKSFkA44dGDceHxKw",
+ "ssLd2wjExS9qWRur4tZDU1Z",
+ "sgTWsRM1qF2YoYLuGo3Z3pU"
+ ]
+ },
+ "choiceItems": {
+ "byId": {
+ "q69Ex7LacPrH9QUMeosRnB": {
+ "id": "q69Ex7LacPrH9QUMeosRnB",
+ "stepId": "ssEiEECKSFkA44dGDceHxKw",
+ "content": "Go"
+ }
+ },
+ "allIds": ["q69Ex7LacPrH9QUMeosRnB"]
+ },
+ "webhooks": { "byId": {}, "allIds": [] },
+ "variables": {
+ "byId": {
+ "4tvkRmf32wiTsXrYoqyhfr": {
+ "id": "4tvkRmf32wiTsXrYoqyhfr",
+ "name": "secret 2",
+ "value": "secret2"
+ },
+ "jEg1FvkCU5S5owNAxXFsHL": {
+ "id": "jEg1FvkCU5S5owNAxXFsHL",
+ "name": "secret 3",
+ "value": "secret3"
+ },
+ "oASkBtoLqkYNqeakcjZH4L": {
+ "id": "oASkBtoLqkYNqeakcjZH4L",
+ "name": "secret 1",
+ "value": "secret1"
+ },
+ "rEoE1ehHzgx8X3d3UPGDHg": {
+ "id": "rEoE1ehHzgx8X3d3UPGDHg",
+ "name": "secret 4",
+ "value": "secret4"
+ },
+ "4kVx5uf8W1XP6WsfJEvt8v": {
+ "id": "4kVx5uf8W1XP6WsfJEvt8v",
+ "name": "Name"
+ }
+ },
+ "allIds": [
+ "oASkBtoLqkYNqeakcjZH4L",
+ "4tvkRmf32wiTsXrYoqyhfr",
+ "jEg1FvkCU5S5owNAxXFsHL",
+ "rEoE1ehHzgx8X3d3UPGDHg",
+ "4kVx5uf8W1XP6WsfJEvt8v"
+ ]
+ },
+ "webhooks": {
+ "byId": {
+ "4h4Kk3Q1qGy7gFzpZtWVpU": {
+ "id": "4h4Kk3Q1qGy7gFzpZtWVpU",
+ "url": "http://localhost:3000/api/mock/webhook",
+ "queryParams": {
+ "byId": {
+ "hwGB11cA7RaYnaqH7gYyuQ": {
+ "id": "hwGB11cA7RaYnaqH7gYyuQ",
+ "key": "firstParam",
+ "value": "{{secret 1}}"
+ },
+ "6ux2FZjhNc4vfqNUDuCkxn": {
+ "id": "6ux2FZjhNc4vfqNUDuCkxn",
+ "key": "secondParam",
+ "value": "{{secret 2}}"
+ }
+ },
+ "allIds": ["hwGB11cA7RaYnaqH7gYyuQ", "6ux2FZjhNc4vfqNUDuCkxn"]
+ },
+ "headers": {
+ "byId": {
+ "ayTB2cFRKMo6oH9t9KS8SA": {
+ "id": "ayTB2cFRKMo6oH9t9KS8SA",
+ "key": "Custom-Typebot",
+ "value": "{{secret 3}}"
+ }
+ },
+ "allIds": ["ayTB2cFRKMo6oH9t9KS8SA"]
+ },
+ "method": "POST",
+ "body": "{ \"customField\": \"{{secret 4}}\" }"
+ }
+ },
+ "allIds": ["4h4Kk3Q1qGy7gFzpZtWVpU"]
+ },
+ "edges": {
+ "byId": {
+ "25yX9DnQgdafpdAjfAu5Fp": {
+ "id": "25yX9DnQgdafpdAjfAu5Fp",
+ "to": { "blockId": "bdFW2HHjMoEFmqHtFre9Xi8" },
+ "from": {
+ "stepId": "oxTsU2C1RX5QHuyY8qjHAM",
+ "blockId": "3kH2sUjVThQDWmqdoKnGk5"
+ }
+ },
+ "oxEEtym3NfDf34NCipzjRQ": {
+ "id": "oxEEtym3NfDf34NCipzjRQ",
+ "to": { "blockId": "b9mSgu7RKmK4xuiTVQP5Me8" },
+ "from": {
+ "stepId": "ssEiEECKSFkA44dGDceHxKw",
+ "blockId": "bdFW2HHjMoEFmqHtFre9Xi8"
+ }
+ },
+ "81SjKnxuUgrPmXvvJJihHM": {
+ "from": {
+ "blockId": "b9mSgu7RKmK4xuiTVQP5Me8",
+ "stepId": "ssLd2wjExS9qWRur4tZDU1Z"
+ },
+ "to": { "blockId": "bmz4rc8r19H2C6b7soxzby4" },
+ "id": "81SjKnxuUgrPmXvvJJihHM"
+ }
+ },
+ "allIds": [
+ "25yX9DnQgdafpdAjfAu5Fp",
+ "oxEEtym3NfDf34NCipzjRQ",
+ "81SjKnxuUgrPmXvvJJihHM"
+ ]
+ },
+ "theme": {
+ "general": {
+ "font": "Open Sans",
+ "background": { "type": "None", "content": "#ffffff" }
+ }
+ },
+ "settings": {
+ "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ },
+ "publicId": null
+}
diff --git a/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.png b/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.png
new file mode 100644
index 000000000..8b98e2c9e
Binary files /dev/null and b/apps/builder/cypress/fixtures/typebots/integrations/webhookPreview.png differ
diff --git a/apps/builder/cypress/fixtures/typebots/logic/condition.json b/apps/builder/cypress/fixtures/typebots/logic/condition.json
index 77c120389..e2d3be1a7 100644
--- a/apps/builder/cypress/fixtures/typebots/logic/condition.json
+++ b/apps/builder/cypress/fixtures/typebots/logic/condition.json
@@ -6,6 +6,7 @@
"ownerId": "ckylsbdf60088z31ayqytest6",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"2x83WHtEBkiv7pk7KgqJwZ": {
diff --git a/apps/builder/cypress/fixtures/typebots/logic/redirect.json b/apps/builder/cypress/fixtures/typebots/logic/redirect.json
index 858f65f97..bb50d7c4e 100644
--- a/apps/builder/cypress/fixtures/typebots/logic/redirect.json
+++ b/apps/builder/cypress/fixtures/typebots/logic/redirect.json
@@ -6,6 +6,7 @@
"ownerId": "ckymkff1100362z1a85juyoa8",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"bsVJfEW7EZrUnAi9s5ev17": {
diff --git a/apps/builder/cypress/fixtures/typebots/logic/setVariable.json b/apps/builder/cypress/fixtures/typebots/logic/setVariable.json
index bb00661a7..f253e4a0f 100644
--- a/apps/builder/cypress/fixtures/typebots/logic/setVariable.json
+++ b/apps/builder/cypress/fixtures/typebots/logic/setVariable.json
@@ -6,6 +6,7 @@
"ownerId": "ckylrpsmt0006fn1ah956d0z1",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"kmUzhRFzSKjkaipYNcku9S": {
diff --git a/apps/builder/cypress/fixtures/typebots/singleChoiceTarget.json b/apps/builder/cypress/fixtures/typebots/singleChoiceTarget.json
index 063971f6e..b8724261d 100644
--- a/apps/builder/cypress/fixtures/typebots/singleChoiceTarget.json
+++ b/apps/builder/cypress/fixtures/typebots/singleChoiceTarget.json
@@ -6,6 +6,7 @@
"ownerId": "ckylsr4fi0220z31apbinpy9d",
"publishedTypebotId": null,
"folderId": null,
+ "webhooks": { "byId": {}, "allIds": [] },
"blocks": {
"byId": {
"weeBMMXxNKwEonMfDX8Z5k": {
diff --git a/apps/builder/cypress/plugins/utils.ts b/apps/builder/cypress/plugins/utils.ts
index 28cf09843..1493df390 100644
--- a/apps/builder/cypress/plugins/utils.ts
+++ b/apps/builder/cypress/plugins/utils.ts
@@ -75,6 +75,7 @@ export const parseTestTypebot = ({
publishedTypebotId: null,
updatedAt: new Date(),
variables: { byId: {}, allIds: [] },
+ webhooks: { byId: {}, allIds: [] },
edges: {
byId: {
edge1: {
diff --git a/apps/builder/cypress/tests/bubbles/image.ts b/apps/builder/cypress/tests/bubbles/image.ts
index 2de598416..622c5cb60 100644
--- a/apps/builder/cypress/tests/bubbles/image.ts
+++ b/apps/builder/cypress/tests/bubbles/image.ts
@@ -52,7 +52,7 @@ describe('Image bubbles', () => {
.should('include', unsplashImageSrc)
})
- it.only('should import giphy gifs correctly', () => {
+ it('should import giphy gifs correctly', () => {
cy.findByRole('button', { name: 'Giphy' }).click()
cy.findAllByRole('img').eq(3).click()
cy.findAllByRole('img')
diff --git a/apps/builder/cypress/tests/integrations/webhooks.ts b/apps/builder/cypress/tests/integrations/webhooks.ts
new file mode 100644
index 000000000..8319aa2dd
--- /dev/null
+++ b/apps/builder/cypress/tests/integrations/webhooks.ts
@@ -0,0 +1,96 @@
+import { preventUserFromRefreshing } from 'cypress/plugins/utils'
+import { getIframeBody } from 'cypress/support'
+
+describe('Webhook step', () => {
+ beforeEach(() => {
+ cy.task('seed')
+ cy.signOut()
+ })
+
+ afterEach(() => {
+ cy.window().then((win) => {
+ win.removeEventListener('beforeunload', preventUserFromRefreshing)
+ })
+ })
+
+ describe('Configuration', () => {
+ it('configuration is working', () => {
+ cy.loadTypebotFixtureInDatabase('typebots/integrations/webhook.json')
+ cy.signIn('test2@gmail.com')
+ cy.visit('/typebots/typebot4/edit')
+ cy.findByText('Configure...').click()
+ cy.findByRole('button', { name: 'GET' }).click()
+ cy.findByRole('menuitem', { name: 'POST' }).click({ force: true })
+ cy.findByPlaceholderText('Your Webhook URL...').type(
+ `${Cypress.env('SITE_NAME')}/api/mock/webhook`
+ )
+
+ cy.findByRole('button', { name: 'Query params' }).click()
+ cy.findByRole('textbox', { name: 'Key:' }).type('firstParam')
+ cy.findByRole('textbox', { name: 'Value:' }).type('{{secret 1}}', {
+ parseSpecialCharSequences: false,
+ })
+ cy.findByRole('button', { name: 'Add a param' }).click()
+ cy.findAllByRole('textbox', { name: 'Key:' }).last().type('secondParam')
+ cy.findAllByRole('textbox', { name: 'Value:' })
+ .last()
+ .type('{{secret 2}}', {
+ parseSpecialCharSequences: false,
+ })
+
+ cy.findByRole('button', { name: 'Headers' }).click()
+ cy.findAllByRole('textbox', { name: 'Key:' })
+ .last()
+ .type('Custom-Typebot')
+ cy.findAllByRole('textbox', { name: 'Value:' })
+ .last()
+ .type('{{secret 3}}', {
+ parseSpecialCharSequences: false,
+ })
+
+ cy.findByRole('button', { name: 'Body' }).click()
+ cy.findByTestId('code-editor').type('{ "customField": "{{secret 4}}" }', {
+ parseSpecialCharSequences: false,
+ waitForAnimations: false,
+ })
+
+ cy.findByRole('button', { name: 'Variable values for test' }).click()
+ addTestVariable('secret 1', 'secret1')
+ cy.findByRole('button', { name: 'Add an entry' }).click()
+ addTestVariable('secret 2', 'secret2')
+ cy.findByRole('button', { name: 'Add an entry' }).click()
+ addTestVariable('secret 3', 'secret3')
+ cy.findByRole('button', { name: 'Add an entry' }).click()
+ addTestVariable('secret 4', 'secret4')
+
+ cy.findByRole('button', { name: 'Test the request' }).click()
+
+ cy.findAllByTestId('code-editor')
+ .should('have.length', 2)
+ .last()
+ .should('contain.text', '"statusCode": 200')
+
+ cy.findByRole('button', { name: 'Save in variables' }).click()
+ cy.findByPlaceholderText('Select the data').click()
+ cy.findByRole('menuitem', { name: 'data[0].name' }).click()
+ })
+ })
+ describe('Preview', () => {
+ it('should correctly send the request', () => {
+ cy.loadTypebotFixtureInDatabase(
+ 'typebots/integrations/webhookPreview.json'
+ )
+ cy.signIn('test2@gmail.com')
+ cy.visit('/typebots/typebot4/edit')
+ cy.findByRole('button', { name: 'Preview' }).click()
+ getIframeBody().findByRole('button', { name: 'Go' }).click()
+ getIframeBody().findByText('His name is John').should('exist')
+ })
+ })
+})
+
+const addTestVariable = (name: string, value: string) => {
+ cy.findAllByTestId('variables-input').last().click()
+ cy.findByRole('menuitem', { name }).click()
+ cy.findAllByRole('textbox', { name: 'Test value:' }).last().type(value)
+}
diff --git a/apps/builder/cypress/tests/logic/condition.ts b/apps/builder/cypress/tests/logic/condition.ts
index 768d31981..4a7bec8c9 100644
--- a/apps/builder/cypress/tests/logic/condition.ts
+++ b/apps/builder/cypress/tests/logic/condition.ts
@@ -22,14 +22,14 @@ describe('Condition step', () => {
cy.findByTestId('variables-input').click()
cy.findByRole('menuitem', { name: 'Age' }).click()
- cy.findByRole('button', { name: 'Equal to' }).click()
+ cy.findByRole('button', { name: 'Select an operator' }).click()
cy.findByRole('menuitem', { name: 'Greater than' }).click()
cy.findByPlaceholderText('Type a value...').type('80')
cy.findByRole('button', { name: 'Add a comparison' }).click()
cy.findAllByTestId('variables-input').last().click()
cy.findByRole('menuitem', { name: 'Age' }).click()
- cy.findByRole('button', { name: 'Equal to' }).click()
+ cy.findByRole('button', { name: 'Select an operator' }).click()
cy.findByRole('menuitem', { name: 'Less than' }).click()
cy.findAllByPlaceholderText('Type a value...').last().type('100')
@@ -37,7 +37,7 @@ describe('Condition step', () => {
cy.findByTestId('variables-input').click()
cy.findByRole('menuitem', { name: 'Age' }).click()
- cy.findByRole('button', { name: 'Equal to' }).click()
+ cy.findByRole('button', { name: 'Select an operator' }).click()
cy.findByRole('menuitem', { name: 'Greater than' }).click()
cy.findByPlaceholderText('Type a value...').type('20')
diff --git a/apps/builder/package.json b/apps/builder/package.json
index 0aacc739a..90a536f5e 100644
--- a/apps/builder/package.json
+++ b/apps/builder/package.json
@@ -12,6 +12,9 @@
"dependencies": {
"@chakra-ui/css-reset": "^1.1.1",
"@chakra-ui/react": "^1.7.4",
+ "@codemirror/basic-setup": "^0.19.1",
+ "@codemirror/lang-json": "^0.19.1",
+ "@codemirror/text": "^0.19.6",
"@dnd-kit/core": "^4.0.3",
"@dnd-kit/sortable": "^5.1.0",
"@emotion/react": "^11.7.1",
@@ -36,6 +39,7 @@
"framer-motion": "^4",
"google-auth-library": "^7.11.0",
"google-spreadsheet": "^3.2.0",
+ "got": "^12.0.1",
"htmlparser2": "^7.2.0",
"immer": "^9.0.7",
"js-video-url-parser": "^0.5.1",
diff --git a/apps/builder/pages/_app.tsx b/apps/builder/pages/_app.tsx
index 12ac50f66..a252566d7 100644
--- a/apps/builder/pages/_app.tsx
+++ b/apps/builder/pages/_app.tsx
@@ -8,6 +8,7 @@ import 'assets/styles/routerProgressBar.css'
import 'assets/styles/plate.css'
import 'focus-visible/dist/focus-visible'
import 'assets/styles/submissionsTable.css'
+import 'assets/styles/codeMirror.css'
const App = ({ Component, pageProps }: AppProps) => {
useRouterProgressBar()
diff --git a/apps/builder/pages/api/mock/webhook.ts b/apps/builder/pages/api/mock/webhook.ts
new file mode 100644
index 000000000..2d5e398cb
--- /dev/null
+++ b/apps/builder/pages/api/mock/webhook.ts
@@ -0,0 +1,26 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import { methodNotAllowed } from 'utils'
+
+const handler = (req: NextApiRequest, res: NextApiResponse) => {
+ if (req.method === 'POST') {
+ const firstParam = req.query.firstParam.toString()
+ const secondParam = req.query.secondParam.toString()
+ const customHeader = req.headers['custom-typebot']
+ const { body } = req
+ if (
+ body.customField === 'secret4' &&
+ customHeader === 'secret3' &&
+ secondParam === 'secret2' &&
+ firstParam === 'secret1'
+ ) {
+ return res.status(200).send([
+ { name: 'John', age: 21 },
+ { name: 'Tim', age: 52 },
+ ])
+ }
+ return res.status(400).send({ message: 'Bad request' })
+ }
+ return methodNotAllowed(res)
+}
+
+export default handler
diff --git a/apps/builder/pages/api/typebots/[typebotId]/webhooks/[id]/execute.ts b/apps/builder/pages/api/typebots/[typebotId]/webhooks/[id]/execute.ts
new file mode 100644
index 000000000..1353d66d0
--- /dev/null
+++ b/apps/builder/pages/api/typebots/[typebotId]/webhooks/[id]/execute.ts
@@ -0,0 +1,104 @@
+import prisma from 'libs/prisma'
+import {
+ KeyValue,
+ Table,
+ Typebot,
+ Variable,
+ Webhook,
+ WebhookResponse,
+} from 'models'
+import { parseVariables } from 'bot-engine'
+import { NextApiRequest, NextApiResponse } from 'next'
+import got, { Method, Headers, HTTPError } from 'got'
+import { methodNotAllowed } from 'utils'
+import { stringify } from 'qs'
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) => {
+ if (req.method === 'POST') {
+ const typebotId = req.query.typebotId.toString()
+ const webhookId = req.query.id.toString()
+ const variables = JSON.parse(req.body).variables as Table
+ const typebot = await prisma.typebot.findUnique({
+ where: { id: typebotId },
+ })
+ const webhook = (typebot as Typebot).webhooks.byId[webhookId]
+ const result = await executeWebhook(webhook, variables)
+ return res.status(200).send(result)
+ }
+ return methodNotAllowed(res)
+}
+
+const executeWebhook = async (
+ webhook: Webhook,
+ variables: Table
+): Promise => {
+ if (!webhook.url || !webhook.method)
+ return {
+ statusCode: 400,
+ data: { message: `Webhook doesn't have url or method` },
+ }
+ const headers = convertKeyValueTableToObject(webhook.headers, variables) as
+ | Headers
+ | undefined
+ const queryParams = stringify(
+ convertKeyValueTableToObject(webhook.queryParams, variables)
+ )
+ const contentType = headers ? headers['Content-Type'] : undefined
+ try {
+ const response = await got(
+ parseVariables({ text: webhook.url + `?${queryParams}`, variables }),
+ {
+ method: webhook.method as Method,
+ headers,
+ json:
+ contentType !== 'x-www-form-urlencoded' && webhook.body
+ ? JSON.parse(parseVariables({ text: webhook.body, variables }))
+ : undefined,
+ form:
+ contentType === 'x-www-form-urlencoded' && webhook.body
+ ? JSON.parse(parseVariables({ text: webhook.body, variables }))
+ : undefined,
+ }
+ )
+ return {
+ statusCode: response.statusCode,
+ data: parseBody(response.body),
+ }
+ } catch (error) {
+ if (error instanceof HTTPError) {
+ return {
+ statusCode: error.response.statusCode,
+ data: parseBody(error.response.body as string),
+ }
+ }
+ return {
+ statusCode: 500,
+ data: { message: `Error from Typebot server: ${error}` },
+ }
+ }
+}
+
+const parseBody = (body: string) => {
+ try {
+ return JSON.parse(body)
+ } catch (err) {
+ return body
+ }
+}
+
+const convertKeyValueTableToObject = (
+ keyValues: Table | undefined,
+ variables: Table
+) => {
+ if (!keyValues) return
+ return keyValues.allIds.reduce((object, id) => {
+ const item = keyValues.byId[id]
+ if (!item.key) return {}
+ return {
+ ...object,
+ [item.key]: parseVariables({ text: item.value, variables }),
+ }
+ }, {})
+}
+
+export default handler
diff --git a/apps/builder/services/integrations.ts b/apps/builder/services/integrations.ts
index cf3c85c73..6028e58f0 100644
--- a/apps/builder/services/integrations.ts
+++ b/apps/builder/services/integrations.ts
@@ -2,6 +2,7 @@ import { sendRequest } from 'utils'
import { stringify } from 'qs'
import useSWR from 'swr'
import { fetcher } from './utils'
+import { Table, Variable, VariableForTest, WebhookResponse } from 'models'
export const getGoogleSheetsConsentScreenUrl = (
redirectUrl: string,
@@ -64,3 +65,68 @@ export const useSheets = ({
mutate,
}
}
+
+export const executeWebhook = (
+ typebotId: string,
+ webhookId: string,
+ variables: Table
+) =>
+ sendRequest({
+ url: `/api/typebots/${typebotId}/webhooks/${webhookId}/execute`,
+ method: 'POST',
+ body: {
+ variables,
+ },
+ })
+
+export const convertVariableForTestToVariables = (
+ variablesForTest: Table | undefined,
+ variables: Table
+): Table => {
+ if (!variablesForTest) return { byId: {}, allIds: [] }
+ return {
+ byId: {
+ ...variables.byId,
+ ...variablesForTest.allIds.reduce((obj, id) => {
+ const variableForTest = variablesForTest.byId[id]
+ if (!variableForTest.variableId) return {}
+ const variable = variables.byId[variableForTest.variableId ?? '']
+ return {
+ ...obj,
+ [variableForTest.variableId]: {
+ ...variable,
+ value: variableForTest.value,
+ },
+ }
+ }, {}),
+ },
+ allIds: variables.allIds,
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const getDeepKeys = (obj: any): string[] => {
+ let keys: string[] = []
+ for (const key in obj) {
+ if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
+ const subkeys = getDeepKeys(obj[key])
+ keys = keys.concat(
+ subkeys.map(function (subkey) {
+ return key + '.' + subkey
+ })
+ )
+ } else if (Array.isArray(obj[key])) {
+ for (let i = 0; i < obj[key].length; i++) {
+ const subkeys = getDeepKeys(obj[key][i])
+ keys = keys.concat(
+ subkeys.map(function (subkey) {
+ return key + '[' + i + ']' + '.' + subkey
+ })
+ )
+ }
+ } else {
+ keys.push(key)
+ }
+ }
+ return keys
+}
diff --git a/apps/builder/services/typebots.ts b/apps/builder/services/typebots.ts
index c0637b015..da5dbc522 100644
--- a/apps/builder/services/typebots.ts
+++ b/apps/builder/services/typebots.ts
@@ -198,7 +198,10 @@ export const parseNewTypebot = ({
ownerId: string
folderId: string | null
name: string
-}): Partial => {
+}): Omit<
+ Typebot,
+ 'createdAt' | 'updatedAt' | 'id' | 'publishedTypebotId' | 'publicId'
+> => {
const startBlockId = shortId.generate()
const startStepId = shortId.generate()
const startStep: StartStep = {
@@ -235,6 +238,7 @@ export const parseNewTypebot = ({
choiceItems: { byId: {}, allIds: [] },
variables: { byId: {}, allIds: [] },
edges: { byId: {}, allIds: [] },
+ webhooks: { byId: {}, allIds: [] },
theme,
settings,
}
diff --git a/packages/bot-engine/rollup.config.js b/packages/bot-engine/rollup.config.js
index 2bf4cdd3f..9a97683df 100644
--- a/packages/bot-engine/rollup.config.js
+++ b/packages/bot-engine/rollup.config.js
@@ -44,7 +44,7 @@ export default [
],
},
{
- input: 'dist/esm/types/index.d.ts',
+ input: 'dist/esm/types/src/index.d.ts',
output: [{ file: 'dist/index.d.ts', format: 'esm' }],
plugins: [dts()],
external: [/\.css$/],
diff --git a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx
index 7bc9ee151..77836fcb6 100644
--- a/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx
+++ b/packages/bot-engine/src/components/ChatBlock/ChatBlock.tsx
@@ -55,6 +55,7 @@ export const ChatBlock = ({
}
if (isIntegrationStep(currentStep)) {
const nextEdgeId = await executeIntegration(
+ typebot.typebotId,
currentStep,
typebot.variables,
updateVariableValue
diff --git a/packages/bot-engine/src/index.ts b/packages/bot-engine/src/index.ts
index c089e42a3..bb2e5ffe8 100644
--- a/packages/bot-engine/src/index.ts
+++ b/packages/bot-engine/src/index.ts
@@ -1,3 +1,3 @@
export * from './components/TypebotViewer'
-
+export { parseVariables } from './services/variable'
export * from 'util'
diff --git a/packages/bot-engine/src/services/integration.ts b/packages/bot-engine/src/services/integration.ts
index 6ec985817..5b28d2f90 100644
--- a/packages/bot-engine/src/services/integration.ts
+++ b/packages/bot-engine/src/services/integration.ts
@@ -10,13 +10,18 @@ import {
Cell,
GoogleSheetsGetOptions,
GoogleAnalyticsStep,
+ Webhook,
+ WebhookStep,
} from 'models'
import { stringify } from 'qs'
import { sendRequest } from 'utils'
import { sendGaEvent } from '../../lib/gtag'
import { parseVariables, parseVariablesInObject } from './variable'
+const safeEval = eval
+
export const executeIntegration = (
+ typebotId: string,
step: IntegrationStep,
variables: Table,
updateVariableValue: (variableId: string, value: string) => void
@@ -26,6 +31,8 @@ export const executeIntegration = (
return executeGoogleSheetIntegration(step, variables, updateVariableValue)
case IntegrationStepType.GOOGLE_ANALYTICS:
return executeGoogleAnalyticsIntegration(step, variables)
+ case IntegrationStepType.WEBHOOK:
+ return executeWebhook(typebotId, step, variables, updateVariableValue)
}
}
@@ -142,3 +149,26 @@ const parseCellValues = (
[cell.column]: parseVariables({ text: cell.value, variables }),
}
}, {})
+
+const executeWebhook = async (
+ typebotId: string,
+ step: WebhookStep,
+ variables: Table,
+ updateVariableValue: (variableId: string, value: string) => void
+) => {
+ if (!step.options?.webhookId) return step.edgeId
+ const { data, error } = await sendRequest({
+ url: `http://localhost:3000/api/typebots/${typebotId}/webhooks/${step.options?.webhookId}/execute`,
+ method: 'POST',
+ body: {
+ variables,
+ },
+ })
+ console.error(error)
+ step.options.responseVariableMapping?.allIds.forEach((varMappingId) => {
+ const varMapping = step.options?.responseVariableMapping?.byId[varMappingId]
+ if (!varMapping?.bodyPath || !varMapping.variableId) return
+ const value = safeEval(`(${JSON.stringify(data)}).${varMapping?.bodyPath}`)
+ updateVariableValue(varMapping.variableId, value)
+ })
+}
diff --git a/packages/db/package.json b/packages/db/package.json
index 9e80b3f89..0934f73fe 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -5,12 +5,12 @@
"main": "./index.tsx",
"types": "./index.tsx",
"devDependencies": {
- "prisma": "^3.7.0",
+ "prisma": "^3.8.1",
"ts-node": "^10.4.0",
- "typescript": "^4.5.4"
+ "typescript": "^4.5.5"
},
"dependencies": {
- "@prisma/client": "^3.7.0"
+ "@prisma/client": "^3.8.1"
},
"scripts": {
"dev": "yarn prisma db push && BROWSER=none yarn prisma studio",
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index abba8730b..85b0a5219 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -110,6 +110,7 @@ model Typebot {
steps Json
choiceItems Json
variables Json
+ webhooks Json
edges Json
theme Json
settings Json
diff --git a/packages/models/src/typebot/steps/integration.ts b/packages/models/src/typebot/steps/integration.ts
index 663b04d6f..b25b5e6dd 100644
--- a/packages/models/src/typebot/steps/integration.ts
+++ b/packages/models/src/typebot/steps/integration.ts
@@ -1,15 +1,20 @@
import { StepBase } from '.'
import { Table } from '../..'
-export type IntegrationStep = GoogleSheetsStep | GoogleAnalyticsStep
+export type IntegrationStep =
+ | GoogleSheetsStep
+ | GoogleAnalyticsStep
+ | WebhookStep
export type IntegrationStepOptions =
| GoogleSheetsOptions
| GoogleAnalyticsOptions
+ | WebhookOptions
export enum IntegrationStepType {
GOOGLE_SHEETS = 'Google Sheets',
GOOGLE_ANALYTICS = 'Google Analytics',
+ WEBHOOK = 'Webhook',
}
export type GoogleSheetsStep = StepBase & {
@@ -22,6 +27,11 @@ export type GoogleAnalyticsStep = StepBase & {
options?: GoogleAnalyticsOptions
}
+export type WebhookStep = StepBase & {
+ type: IntegrationStepType.WEBHOOK
+ options?: WebhookOptions
+}
+
export type GoogleAnalyticsOptions = {
trackingId?: string
category?: string
@@ -66,3 +76,40 @@ export type GoogleSheetsUpdateRowOptions = GoogleSheetsOptionsBase & {
referenceCell?: Cell
cellsToUpsert?: Table
}
+
+export type ResponseVariableMapping = { bodyPath?: string; variableId?: string }
+
+export type WebhookOptions = {
+ webhookId?: string
+ variablesForTest?: Table
+ responseVariableMapping?: Table
+}
+
+export enum HttpMethod {
+ POST = 'POST',
+ GET = 'GET',
+ PUT = 'PUT',
+ DELETE = 'DELETE',
+ PATCH = 'PATCH',
+ HEAD = 'HEAD',
+ CONNECT = 'CONNECT',
+ OPTIONS = 'OPTIONS',
+ TRACE = 'TRACE',
+}
+
+export type KeyValue = { key?: string; value?: string }
+export type VariableForTest = { variableId?: string; value?: string }
+
+export type Webhook = {
+ id: string
+ url?: string
+ method?: HttpMethod
+ queryParams?: Table
+ headers?: Table
+ body?: string
+}
+
+export type WebhookResponse = {
+ statusCode: number
+ data?: unknown
+}
diff --git a/packages/models/src/typebot/typebot.ts b/packages/models/src/typebot/typebot.ts
index 8dc54dc70..b55ba53d7 100644
--- a/packages/models/src/typebot/typebot.ts
+++ b/packages/models/src/typebot/typebot.ts
@@ -5,16 +5,24 @@ import { Settings } from './settings'
import { Step } from './steps/steps'
import { Theme } from './theme'
import { Variable } from './variable'
+import { Webhook } from '.'
export type Typebot = Omit<
TypebotFromPrisma,
- 'blocks' | 'theme' | 'settings' | 'steps' | 'choiceItems' | 'variables'
+ | 'blocks'
+ | 'theme'
+ | 'settings'
+ | 'steps'
+ | 'choiceItems'
+ | 'variables'
+ | 'webhooks'
> & {
blocks: Table
steps: Table
choiceItems: Table
variables: Table
edges: Table
+ webhooks: Table
theme: Theme
settings: Settings
}
diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts
index 57c5d98b9..1d17b747b 100644
--- a/packages/utils/src/utils.ts
+++ b/packages/utils/src/utils.ts
@@ -13,6 +13,7 @@ import {
Table,
TextInputStep,
TextBubbleStep,
+ WebhookStep,
} from 'models'
export const sendRequest = async ({
@@ -74,3 +75,6 @@ export const isConditionStep = (step: Step): step is ConditionStep =>
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
(Object.values(IntegrationStepType) as string[]).includes(step.type)
+
+export const isWebhookStep = (step: Step): step is WebhookStep =>
+ step.type === IntegrationStepType.WEBHOOK
diff --git a/yarn.lock b/yarn.lock
index 0ea600deb..e8a5e8f91 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -726,6 +726,221 @@
dependencies:
"@chakra-ui/utils" "1.9.1"
+"@codemirror/autocomplete@^0.19.0":
+ version "0.19.12"
+ resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-0.19.12.tgz#4c9e4487b45e6877807e4f16c1fffd5e7639ae52"
+ integrity sha512-zUQYo5gMdv7vhxlKoAY/vnNCGzlE9AU7+P649v3ovpQpoFdo3U1Nt01EJqFb4Sfaw6l1U/Elc9Iksd1lDy+MVw==
+ dependencies:
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/state" "^0.19.4"
+ "@codemirror/text" "^0.19.2"
+ "@codemirror/tooltip" "^0.19.12"
+ "@codemirror/view" "^0.19.0"
+ "@lezer/common" "^0.15.0"
+
+"@codemirror/basic-setup@^0.19.1":
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/@codemirror/basic-setup/-/basic-setup-0.19.1.tgz#17b27d02f15c628eb62a85d01e3e1b1958933eb4"
+ integrity sha512-gLjD7YgZU/we6BzS/ecCmD3viw83dsgv5ZUaSydYbYx9X4w4w9RqYnckcJ+0GDyHfNr5Jtfv2Z5ZtFQnBj0UDA==
+ dependencies:
+ "@codemirror/autocomplete" "^0.19.0"
+ "@codemirror/closebrackets" "^0.19.0"
+ "@codemirror/commands" "^0.19.0"
+ "@codemirror/comment" "^0.19.0"
+ "@codemirror/fold" "^0.19.0"
+ "@codemirror/gutter" "^0.19.0"
+ "@codemirror/highlight" "^0.19.0"
+ "@codemirror/history" "^0.19.0"
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/lint" "^0.19.0"
+ "@codemirror/matchbrackets" "^0.19.0"
+ "@codemirror/rectangular-selection" "^0.19.0"
+ "@codemirror/search" "^0.19.0"
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.31"
+
+"@codemirror/closebrackets@^0.19.0":
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/@codemirror/closebrackets/-/closebrackets-0.19.0.tgz#69fdcee85779d638a00a42becd9f53a33a26d77f"
+ integrity sha512-dFWX5OEVYWRNtGaifSbwIAlymnRRjxWMiMbffbAjF7p0zfGHDbdGkiT56q3Xud63h5/tQdSo5dK1iyNTzHz5vg==
+ dependencies:
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/rangeset" "^0.19.0"
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/text" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/commands@^0.19.0":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@codemirror/commands/-/commands-0.19.7.tgz#b55ccc7f3c1ad4cb0ec422d50c93568dbb05cc55"
+ integrity sha512-Mwh064xnuDbFw+9KJAi2Hmg8Va+YqQzgn6e/94/bSJavY3uuIBPr4vJp6pFEa1qPp40qs5/XJ01ty/0G3uLewA==
+ dependencies:
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/matchbrackets" "^0.19.0"
+ "@codemirror/state" "^0.19.2"
+ "@codemirror/text" "^0.19.0"
+ "@codemirror/view" "^0.19.22"
+ "@lezer/common" "^0.15.0"
+
+"@codemirror/comment@^0.19.0":
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/@codemirror/comment/-/comment-0.19.0.tgz#4f23497924e9346898c2e0123011acc535a0bea6"
+ integrity sha512-3hqAd0548fxqOBm4khFMcXVIivX8p0bSlbAuZJ6PNoUn/0wXhxkxowPp0FmFzU2+y37Z+ZQF5cRB5EREWPRIiQ==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/text" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/fold@^0.19.0":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/fold/-/fold-0.19.2.tgz#9d4e0c0f9f3bb2fcded7d82bea14ce39310e8e2a"
+ integrity sha512-FLi6RBhHPBnSbKZEu1S98z+VYSP5678cMdYVqhR58OWWTkEiLRVPeCTj8FhRKNL9B8Gx+lBQhGq3uwr3KtSs8w==
+ dependencies:
+ "@codemirror/gutter" "^0.19.0"
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/rangeset" "^0.19.0"
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.22"
+
+"@codemirror/gutter@^0.19.0", "@codemirror/gutter@^0.19.4":
+ version "0.19.9"
+ resolved "https://registry.yarnpkg.com/@codemirror/gutter/-/gutter-0.19.9.tgz#bbb69f4d49570d9c1b3f3df5d134980c516cd42b"
+ integrity sha512-PFrtmilahin1g6uL27aG5tM/rqR9DZzZYZsIrCXA5Uc2OFTFqx4owuhoU9hqfYxHp5ovfvBwQ+txFzqS4vog6Q==
+ dependencies:
+ "@codemirror/rangeset" "^0.19.0"
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.23"
+
+"@codemirror/highlight@^0.19.0":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.19.7.tgz#91a0c9994c759f5f153861e3aae74ff9e7c7c35b"
+ integrity sha512-3W32hBCY0pbbv/xidismw+RDMKuIag+fo4kZIbD7WoRj+Ttcaxjf+vP6RttRHXLaaqbWh031lTeON8kMlDhMYw==
+ dependencies:
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/rangeset" "^0.19.0"
+ "@codemirror/state" "^0.19.3"
+ "@codemirror/view" "^0.19.0"
+ "@lezer/common" "^0.15.0"
+ style-mod "^4.0.0"
+
+"@codemirror/history@^0.19.0":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@codemirror/history/-/history-0.19.2.tgz#25e3fda755f77ac1223a6ae6e9d7899f5919265e"
+ integrity sha512-unhP4t3N2smzmHoo/Yio6ueWi+il8gm9VKrvi6wlcdGH5fOfVDNkmjHQ495SiR+EdOG35+3iNebSPYww0vN7ow==
+ dependencies:
+ "@codemirror/state" "^0.19.2"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/lang-json@^0.19.1":
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/@codemirror/lang-json/-/lang-json-0.19.1.tgz#616588d1422529965243c10af6c44ad0b9134fb0"
+ integrity sha512-66K5TT9HO0ODtpjY+3Ub6t3r0OB1d27P+Kl5oygk4tDavHUBpsyHTJRFw/CdeRM2VwjbpBfctGm/cTrSthFDZg==
+ dependencies:
+ "@codemirror/highlight" "^0.19.0"
+ "@codemirror/language" "^0.19.0"
+ "@lezer/json" "^0.15.0"
+
+"@codemirror/language@^0.19.0":
+ version "0.19.7"
+ resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-0.19.7.tgz#9eef8e827692d93a701b18db9d46a42be34ecca6"
+ integrity sha512-pNNUtYWMIMG0lUSKyUXJr8U0rFiCKsKFXbA2Oj17PC+S1FY99hV0z1vcntW67ekAIZw9DMEUQnLsKBuIbAUX7Q==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/text" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+ "@lezer/common" "^0.15.5"
+ "@lezer/lr" "^0.15.0"
+
+"@codemirror/lint@^0.19.0":
+ version "0.19.3"
+ resolved "https://registry.yarnpkg.com/@codemirror/lint/-/lint-0.19.3.tgz#84101d0967fea8df114a8f0f79965c22ccd3b3cc"
+ integrity sha512-+c39s05ybD2NjghxkPFsUbH/qBL0cdzKmtHbzUm0RVspeL2OiP7uHYJ6J5+Qr9RjMIPWzcqSauRqxfmCrctUfg==
+ dependencies:
+ "@codemirror/gutter" "^0.19.4"
+ "@codemirror/panel" "^0.19.0"
+ "@codemirror/rangeset" "^0.19.1"
+ "@codemirror/state" "^0.19.4"
+ "@codemirror/tooltip" "^0.19.5"
+ "@codemirror/view" "^0.19.0"
+ crelt "^1.0.5"
+
+"@codemirror/matchbrackets@^0.19.0":
+ version "0.19.3"
+ resolved "https://registry.yarnpkg.com/@codemirror/matchbrackets/-/matchbrackets-0.19.3.tgz#1f430ada6fa21af2205280ff344ef57bb95dd3cb"
+ integrity sha512-ljkrBxaLgh8jesroUiBa57pdEwqJamxkukXrJpL9LdyFZVJaF+9TldhztRaMsMZO1XnCSSHQ9sg32iuHo7Sc2g==
+ dependencies:
+ "@codemirror/language" "^0.19.0"
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+ "@lezer/common" "^0.15.0"
+
+"@codemirror/panel@^0.19.0":
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/@codemirror/panel/-/panel-0.19.1.tgz#bf77d27b962cf16357139e50864d0eb69d634441"
+ integrity sha512-sYeOCMA3KRYxZYJYn5PNlt9yNsjy3zTNTrbYSfVgjgL9QomIVgOJWPO5hZ2sTN8lufO6lw0vTBsIPL9MSidmBg==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/rangeset@^0.19.0", "@codemirror/rangeset@^0.19.1", "@codemirror/rangeset@^0.19.5":
+ version "0.19.6"
+ resolved "https://registry.yarnpkg.com/@codemirror/rangeset/-/rangeset-0.19.6.tgz#2562850cb4ce7dd30088f4d13a13860b67e7d384"
+ integrity sha512-wYtgGnW2Jtrh2nj7vpcBoEZib+jfyilrLN6w7YMTzzSRN8xXhYRorOUg4VQIa1JwFcMQrjSCkIdqXsDqOX1cYg==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+
+"@codemirror/rectangular-selection@^0.19.0":
+ version "0.19.1"
+ resolved "https://registry.yarnpkg.com/@codemirror/rectangular-selection/-/rectangular-selection-0.19.1.tgz#5a88ece4fb68ce5682539497db8a64fc015aae63"
+ integrity sha512-9ElnqOg3mpZIWe0prPRd1SZ48Q9QB3bR8Aocq8UtjboJSUG8ABhRrbuTZMW/rMqpBPSjVpCe9xkCCkEQMYQVmw==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/text" "^0.19.4"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/search@^0.19.0":
+ version "0.19.5"
+ resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-0.19.5.tgz#cae88292a6b4a6d6e6a8b6218fe62355cf7f6055"
+ integrity sha512-9kbtCBpMDlzcj7AptMRBx9BZpC5wz+/tG8nIe4vdpOszP08ZY2AcxN5nlhCoUSZu+pd0b6fYiwjLXOf000rRpw==
+ dependencies:
+ "@codemirror/panel" "^0.19.0"
+ "@codemirror/rangeset" "^0.19.0"
+ "@codemirror/state" "^0.19.3"
+ "@codemirror/text" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+ crelt "^1.0.5"
+
+"@codemirror/state@^0.19.0", "@codemirror/state@^0.19.2", "@codemirror/state@^0.19.3", "@codemirror/state@^0.19.4":
+ version "0.19.6"
+ resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-0.19.6.tgz#d631f041d39ce41b7891b099fca26cb1fdb9763e"
+ integrity sha512-sqIQZE9VqwQj7D4c2oz9mfLhlT1ElAzGB5lO1lE33BPyrdNy1cJyCIOecT4cn4VeJOFrnjOeu+IftZ3zqdFETw==
+ dependencies:
+ "@codemirror/text" "^0.19.0"
+
+"@codemirror/text@^0.19.0", "@codemirror/text@^0.19.2", "@codemirror/text@^0.19.4", "@codemirror/text@^0.19.6":
+ version "0.19.6"
+ resolved "https://registry.yarnpkg.com/@codemirror/text/-/text-0.19.6.tgz#9adcbd8137f69b75518eacd30ddb16fd67bbac45"
+ integrity sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==
+
+"@codemirror/tooltip@^0.19.12", "@codemirror/tooltip@^0.19.5":
+ version "0.19.13"
+ resolved "https://registry.yarnpkg.com/@codemirror/tooltip/-/tooltip-0.19.13.tgz#4af381aead14d9d7091258bdd6a5de7dc5f56915"
+ integrity sha512-7vgvjQjwFQ9hPejw2s+w3UR1XAYjQ5M0F9HRwutXkZHP1tBFV7LnNJ3xBD7F9SR9kAh8WgdL3BpUsEwX1aqoQg==
+ dependencies:
+ "@codemirror/state" "^0.19.0"
+ "@codemirror/view" "^0.19.0"
+
+"@codemirror/view@^0.19.0", "@codemirror/view@^0.19.22", "@codemirror/view@^0.19.23", "@codemirror/view@^0.19.31":
+ version "0.19.40"
+ resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-0.19.40.tgz#1be9cac1725568b7fba2252658a6f255b29339eb"
+ integrity sha512-0CQV99+/nIKTVVbDs0XjW4Rkp8TobzJBXRaUHF6mOroVjuIBBcolE1eAGVEU5LrCS44C798jiP4r/HhLDNS+rw==
+ dependencies:
+ "@codemirror/rangeset" "^0.19.5"
+ "@codemirror/state" "^0.19.3"
+ "@codemirror/text" "^0.19.0"
+ style-mod "^4.0.0"
+ w3c-keyname "^2.2.4"
+
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
@@ -1118,6 +1333,25 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+"@lezer/common@^0.15.0", "@lezer/common@^0.15.5":
+ version "0.15.11"
+ resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.11.tgz#965b5067036305f12e8a3efc344076850be1d3a8"
+ integrity sha512-vv0nSdIaVCRcJ8rPuDdsrNVfBOYe/4Szr/LhF929XyDmBndLDuWiCCHooGlGlJfzELyO608AyDhVsuX/ZG36NA==
+
+"@lezer/json@^0.15.0":
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/@lezer/json/-/json-0.15.0.tgz#b96c1161eb8514e05f4eaaec95c68376e76e539f"
+ integrity sha512-OsMjjBkTkeQ15iMCu5U1OiBubRC4V9Wm03zdIlUgNZ20aUPx5DWDRqUc5wG41JXVSj7Lxmo+idlFCfBBdxB8sw==
+ dependencies:
+ "@lezer/lr" "^0.15.0"
+
+"@lezer/lr@^0.15.0":
+ version "0.15.7"
+ resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.7.tgz#e7e6a8a106c6474b4586a66a3e60ba85f1faf368"
+ integrity sha512-rmUukgyKSm6xzXO4cK5hkpX3+ZTHF+bHDkEuhofAVUTS3J23YytUxGWsrDwBVvGbhvxW87kheb2mRYHRwKacDQ==
+ dependencies:
+ "@lezer/common" "^0.15.0"
+
"@napi-rs/triples@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c"
@@ -1296,22 +1530,22 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
-"@prisma/client@^3.7.0":
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.7.0.tgz#9cafc105f12635c95e9b7e7b18e8fbf52cf3f18a"
- integrity sha512-fUJMvBOX5C7JPc0e3CJD6Gbelbu4dMJB4ScYpiht8HMUnRShw20ULOipTopjNtl6ekHQJ4muI7pXlQxWS9nMbw==
+"@prisma/client@^3.8.1":
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.8.1.tgz#c11eda8e84760867552ffde4de7b48fb2cf1e1c0"
+ integrity sha512-NxD1Xbkx1eT1mxSwo1RwZe665mqBETs0VxohuwNfFIxMqcp0g6d4TgugPxwZ4Jb4e5wCu8mQ9quMedhNWIWcZQ==
dependencies:
- "@prisma/engines-version" "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
+ "@prisma/engines-version" "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
-"@prisma/engines-version@3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f":
- version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
- resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#055f36ac8b06c301332c14963cd0d6c795942c90"
- integrity sha512-+qx2b+HK7BKF4VCa0LZ/t1QCXsu6SmvhUQyJkOD2aPpmOzket4fEnSKQZSB0i5tl7rwCDsvAiSeK8o7rf+yvwg==
+"@prisma/engines-version@3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f":
+ version "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f.tgz#4c8d9744b5e54650a8ba5fde0a711399d6adba24"
+ integrity sha512-G2JH6yWt6ixGKmsRmVgaQYahfwMopim0u/XLIZUo2o/mZ5jdu7+BL+2V5lZr7XiG1axhyrpvlyqE/c0OgYSl3g==
-"@prisma/engines@3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f":
- version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
- resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#12f28d5b78519fbd84c89a5bdff457ff5095e7a2"
- integrity sha512-W549ub5NlgexNhR8EFstA/UwAWq3Zq0w9aNkraqsozVCt2CsX+lK4TK7IW5OZVSnxHwRjrgEAt3r9yPy8nZQRg==
+"@prisma/engines@3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f":
+ version "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f.tgz#4479099b99f6a082ce5843ee7208943ccedd127f"
+ integrity sha512-bHYubuItSN/DGYo36aDu7xJiJmK52JOSHs4MK+KbceAtwS20BCWadRgtpQ3iZ2EXfN/B1T0iCXlNraaNwnpU2w==
"@reach/alert@0.13.2":
version "0.13.2"
@@ -1416,11 +1650,23 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
+"@sindresorhus/is@^4.2.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.3.0.tgz#344fd9bf808a84567ba563d00cc54b2f428dbab1"
+ integrity sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==
+
"@socket.io/component-emitter@~3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz#8863915676f837d9dad7b76f50cb500c1e9422e9"
integrity sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==
+"@szmarczak/http-timer@^5.0.1":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a"
+ integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==
+ dependencies:
+ defer-to-connect "^2.0.1"
+
"@testing-library/cypress@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-8.0.2.tgz#b13f0ff2424dec4368b6670dfbfb7e43af8eefc9"
@@ -1494,6 +1740,16 @@
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
+"@types/cacheable-request@^6.0.2":
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9"
+ integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==
+ dependencies:
+ "@types/http-cache-semantics" "*"
+ "@types/keyv" "*"
+ "@types/node" "*"
+ "@types/responselike" "*"
+
"@types/engine.io@*":
version "3.1.7"
resolved "https://registry.yarnpkg.com/@types/engine.io/-/engine.io-3.1.7.tgz#86e541a5dc52fb7e97735383564a6ae4cfe2e8f5"
@@ -1516,6 +1772,11 @@
resolved "https://registry.yarnpkg.com/@types/google-spreadsheet/-/google-spreadsheet-3.1.5.tgz#2bdc6f9f5372551e0506cb6ef3f562adcf44fc2e"
integrity sha512-7N+mDtZ1pmya2RRFPPl4KYc2TRgiqCNBLUZfyrKfER+u751JgCO+C24/LzF70UmUm/zhHUbzRZ5mtfaxekQ1ZQ==
+"@types/http-cache-semantics@*":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812"
+ integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==
+
"@types/is-hotkey@^0.1.1":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.7.tgz#30ec6d4234895230b576728ef77e70a52962f3b3"
@@ -1555,6 +1816,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
+"@types/keyv@*":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41"
+ integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
+ dependencies:
+ "@types/node" "*"
+
"@types/lodash.mergewith@4.6.6":
version "4.6.6"
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
@@ -1673,6 +1941,13 @@
dependencies:
"@types/node" "*"
+"@types/responselike@*", "@types/responselike@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
+ integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
+ dependencies:
+ "@types/node" "*"
+
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@@ -2541,6 +2816,24 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+cacheable-lookup@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz#65c0e51721bb7f9f2cb513aed6da4a1b93ad7dc8"
+ integrity sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==
+
+cacheable-request@^7.0.2:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27"
+ integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^4.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^6.0.1"
+ responselike "^2.0.0"
+
cachedir@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
@@ -2702,6 +2995,13 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
+clone-response@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
+ integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
+ dependencies:
+ mimic-response "^1.0.0"
+
clsx@1.1.1, clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
@@ -2944,6 +3244,11 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+crelt@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94"
+ integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==
+
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -3234,6 +3539,13 @@ debug@^3.1.0, debug@^3.2.7:
dependencies:
ms "^2.1.1"
+decompress-response@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
+ integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
+ dependencies:
+ mimic-response "^3.1.0"
+
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -3244,6 +3556,11 @@ deepmerge@^4.2.2:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+defer-to-connect@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587"
+ integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==
+
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -4109,6 +4426,11 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
+form-data-encoder@1.7.1:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96"
+ integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==
+
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -4227,6 +4549,11 @@ get-stream@^5.0.0, get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
+get-stream@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
get-symbol-description@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
@@ -4381,6 +4708,25 @@ googleapis-common@^5.0.1:
url-template "^2.0.8"
uuid "^8.0.0"
+got@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-12.0.1.tgz#78747f1c5bc7069bbd739636ed8b70c7f2140a39"
+ integrity sha512-1Zhoh+lDej3t7Ks1BP/Jufn+rNqdiHQgUOcTxHzg2Dao1LQfp5S4Iq0T3iBxN4Zdo7QqCJL+WJUNzDX6rCP2Ew==
+ dependencies:
+ "@sindresorhus/is" "^4.2.0"
+ "@szmarczak/http-timer" "^5.0.1"
+ "@types/cacheable-request" "^6.0.2"
+ "@types/responselike" "^1.0.0"
+ cacheable-lookup "^6.0.4"
+ cacheable-request "^7.0.2"
+ decompress-response "^6.0.0"
+ form-data-encoder "1.7.1"
+ get-stream "^6.0.1"
+ http2-wrapper "^2.1.9"
+ lowercase-keys "^3.0.0"
+ p-cancelable "^3.0.0"
+ responselike "^2.0.0"
+
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
version "4.2.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
@@ -4487,6 +4833,11 @@ htmlparser2@^7.2.0:
domutils "^2.8.0"
entities "^3.0.1"
+http-cache-semantics@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
+ integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
+
http-errors@1.6.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
@@ -4517,6 +4868,14 @@ http-signature@~1.3.6:
jsprim "^2.0.2"
sshpk "^1.14.1"
+http2-wrapper@^2.1.9:
+ version "2.1.10"
+ resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.1.10.tgz#307cd0cee2564723692ad34c2d570d12f10e83be"
+ integrity sha512-QHgsdYkieKp+6JbXP25P+tepqiHYd+FVnDwXpxi/BlUcoIB0nsmTOymTNvETuTO+pDuwcSklPE72VR3DqV+Haw==
+ dependencies:
+ quick-lru "^5.1.1"
+ resolve-alpn "^1.2.0"
+
https-browserify@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@@ -5002,6 +5361,11 @@ json-bigint@^1.0.0:
dependencies:
bignumber.js "^9.0.0"
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -5099,6 +5463,13 @@ kbar@^0.1.0-beta.24:
react-virtual "^2.8.2"
tiny-invariant "^1.2.0"
+keyv@^4.0.0:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.5.tgz#bb12b467aba372fab2a44d4420c00d3c4ebd484c"
+ integrity sha512-531pkGLqV3BMg0eDqqJFI0R1mkK1Nm5xIP2mM6keP5P8WfFtCkg2IOwplTUmlGoTgIg9yQYZ/kdihhz89XH3vA==
+ dependencies:
+ json-buffer "3.0.1"
+
language-subtag-registry@~0.3.2:
version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
@@ -5272,6 +5643,16 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lowercase-keys@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
+ integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
+
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@@ -5393,6 +5774,16 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+mimic-response@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mimic-response@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
+ integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -5792,6 +6183,11 @@ otplib@^12.0.1:
"@otplib/preset-default" "^12.0.1"
"@otplib/preset-v11" "^12.0.1"
+p-cancelable@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
+ integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
+
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -6386,12 +6782,12 @@ pretty-format@^3.8.0:
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
-prisma@^3.7.0:
- version "3.7.0"
- resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.7.0.tgz#9c73eeb2f16f767fdf523d0f4cc4c749734d62e2"
- integrity sha512-pzgc95msPLcCHqOli7Hnabu/GRfSGSUWl5s2P6N13T/rgMB+NNeKbxCmzQiZT2yLOeLEPivV6YrW1oeQIwJxcg==
+prisma@^3.8.1:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.8.1.tgz#44395cef7cbb1ea86216cb84ee02f856c08a7873"
+ integrity sha512-Q8zHwS9m70TaD7qI8u+8hTAmiTpK+IpvRYF3Rgb/OeWGQJOMgZCFFvNCiSfoLEQ95wilK7ctW3KOpc9AuYnRUA==
dependencies:
- "@prisma/engines" "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
+ "@prisma/engines" "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
process-nextick-args@~2.0.0:
version "2.0.1"
@@ -6842,6 +7238,11 @@ resize-observer-polyfill@^1.5.1:
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
+resolve-alpn@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
+ integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -6869,6 +7270,13 @@ resolve@^2.0.0-next.3:
is-core-module "^2.2.0"
path-parse "^1.0.6"
+responselike@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
+ integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
+ dependencies:
+ lowercase-keys "^2.0.0"
+
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -7477,6 +7885,11 @@ style-inject@^0.3.0:
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
integrity sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==
+style-mod@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.0.0.tgz#97e7c2d68b592975f2ca7a63d0dd6fcacfe35a01"
+ integrity sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==
+
style-value-types@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
@@ -7925,6 +8338,11 @@ typescript@^4.5.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
+typescript@^4.5.5:
+ version "4.5.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
+ integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
+
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
@@ -8074,6 +8492,11 @@ vm-browserify@1.1.2:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+w3c-keyname@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.4.tgz#4ade6916f6290224cdbd1db8ac49eab03d0eef6b"
+ integrity sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==
+
warning@^4.0.2, warning@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|