refactor(editor): ♻️ Undo / Redo buttons + structure refacto
Yet another huge refacto... While implementing undo and redo features I understood that I updated the stored typebot too many times (i.e. on each key input) so I had to rethink it entirely. I also moved around some files.
This commit is contained in:
@ -0,0 +1,55 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { ChoiceInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type ChoiceInputSettingsBodyProps = {
|
||||
options?: ChoiceInputOptions
|
||||
onOptionsChange: (options: ChoiceInputOptions) => void
|
||||
}
|
||||
|
||||
export const ChoiceInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: ChoiceInputSettingsBodyProps) => {
|
||||
const handleIsMultipleChange = (isMultipleChoice: boolean) =>
|
||||
options && onOptionsChange({ ...options, isMultipleChoice })
|
||||
const handleButtonLabelChange = (buttonLabel: string) =>
|
||||
options && onOptionsChange({ ...options, buttonLabel })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
options && onOptionsChange({ ...options, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<SwitchWithLabel
|
||||
id={'is-multiple'}
|
||||
label={'Multiple choice?'}
|
||||
initialValue={options?.isMultipleChoice ?? false}
|
||||
onCheckChange={handleIsMultipleChange}
|
||||
/>
|
||||
{options?.isMultipleChoice && (
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options?.buttonLabel ?? 'Send'}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options?.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { DropdownList } from 'components/shared/DropdownList'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton/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<Comparison>) => {
|
||||
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 (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<VariableSearchInput
|
||||
initialVariableId={item.variableId}
|
||||
onSelectVariable={handleSelectVariable}
|
||||
placeholder="Search for a variable"
|
||||
/>
|
||||
<DropdownList<ComparisonOperators>
|
||||
currentItem={item.comparisonOperator}
|
||||
onItemSelect={handleSelectComparisonOperator}
|
||||
items={Object.values(ComparisonOperators)}
|
||||
placeholder="Select an operator"
|
||||
/>
|
||||
{item.comparisonOperator !== ComparisonOperators.IS_SET && (
|
||||
<InputWithVariableButton
|
||||
initialValue={item.value ?? ''}
|
||||
onChange={handleChangeValue}
|
||||
placeholder="Type a value..."
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
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 { ComparisonItem } from './ComparisonsItem'
|
||||
|
||||
type ConditionSettingsBodyProps = {
|
||||
options: ConditionOptions
|
||||
onOptionsChange: (options: ConditionOptions) => void
|
||||
}
|
||||
|
||||
export const ConditionSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: ConditionSettingsBodyProps) => {
|
||||
const handleComparisonsChange = (comparisons: Table<Comparison>) =>
|
||||
onOptionsChange({ ...options, comparisons })
|
||||
const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) =>
|
||||
onOptionsChange({ ...options, logicalOperator })
|
||||
|
||||
return (
|
||||
<TableList<Comparison>
|
||||
initialItems={options.comparisons}
|
||||
onItemsChange={handleComparisonsChange}
|
||||
Item={ComparisonItem}
|
||||
ComponentBetweenItems={() => (
|
||||
<Flex justify="center">
|
||||
<DropdownList<LogicalOperator>
|
||||
currentItem={options.logicalOperator}
|
||||
onItemSelect={handleLogicalOperatorChange}
|
||||
items={Object.values(LogicalOperator)}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
addLabel="Add a comparison"
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { ConditionSettingsBody } from './ConditonSettingsBody'
|
@ -0,0 +1,89 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { DateInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type DateInputSettingsBodyProps = {
|
||||
options: DateInputOptions
|
||||
onOptionsChange: (options: DateInputOptions) => void
|
||||
}
|
||||
|
||||
export const DateInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: DateInputSettingsBodyProps) => {
|
||||
const handleFromChange = (from: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options?.labels, from } })
|
||||
const handleToChange = (to: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options?.labels, to } })
|
||||
const handleButtonLabelChange = (button: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||
const handleIsRangeChange = (isRange: boolean) =>
|
||||
onOptionsChange({ ...options, isRange })
|
||||
const handleHasTimeChange = (hasTime: boolean) =>
|
||||
onOptionsChange({ ...options, hasTime })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onOptionsChange({ ...options, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<SwitchWithLabel
|
||||
id="is-range"
|
||||
label={'Is range?'}
|
||||
initialValue={options.isRange}
|
||||
onCheckChange={handleIsRangeChange}
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
id="with-time"
|
||||
label={'With time?'}
|
||||
initialValue={options.isRange}
|
||||
onCheckChange={handleHasTimeChange}
|
||||
/>
|
||||
{options.isRange && (
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="from">
|
||||
From label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="from"
|
||||
initialValue={options.labels.from}
|
||||
onChange={handleFromChange}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{options?.isRange && (
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="to">
|
||||
To label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="to"
|
||||
initialValue={options.labels.to}
|
||||
onChange={handleToChange}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { EmailInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type EmailInputSettingsBodyProps = {
|
||||
options: EmailInputOptions
|
||||
onOptionsChange: (options: EmailInputOptions) => void
|
||||
}
|
||||
|
||||
export const EmailInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: EmailInputSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
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 })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options.labels.placeholder}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
FormLabel,
|
||||
Stack,
|
||||
Tag,
|
||||
} from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton/InputWithVariableButton'
|
||||
import { GoogleAnalyticsOptions } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options?: GoogleAnalyticsOptions
|
||||
onOptionsChange: (options: GoogleAnalyticsOptions) => void
|
||||
}
|
||||
|
||||
export const GoogleAnalyticsSettings = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const handleTrackingIdChange = (trackingId: string) =>
|
||||
onOptionsChange({ ...options, trackingId })
|
||||
|
||||
const handleCategoryChange = (category: string) =>
|
||||
onOptionsChange({ ...options, category })
|
||||
|
||||
const handleActionChange = (action: string) =>
|
||||
onOptionsChange({ ...options, action })
|
||||
|
||||
const handleLabelChange = (label: string) =>
|
||||
onOptionsChange({ ...options, label })
|
||||
|
||||
const handleValueChange = (value?: string) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
value: value ? parseFloat(value) : undefined,
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="tracking-id">
|
||||
Tracking ID:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="tracking-id"
|
||||
initialValue={options?.trackingId ?? ''}
|
||||
placeholder="G-123456..."
|
||||
onChange={handleTrackingIdChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="category">
|
||||
Event category:
|
||||
</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id="category"
|
||||
initialValue={options?.category ?? ''}
|
||||
placeholder="Example: Typebot"
|
||||
onChange={handleCategoryChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="action">
|
||||
Event action:
|
||||
</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id="action"
|
||||
initialValue={options?.action ?? ''}
|
||||
placeholder="Example: Submit email"
|
||||
onChange={handleActionChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
<Box flex="1" textAlign="left">
|
||||
Advanced
|
||||
</Box>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="label">
|
||||
Event label <Tag>Optional</Tag>:
|
||||
</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id="label"
|
||||
initialValue={options?.label ?? ''}
|
||||
placeholder="Example: Campaign Z"
|
||||
onChange={handleLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="value">
|
||||
Event value <Tag>Optional</Tag>:
|
||||
</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id="value"
|
||||
initialValue={options?.value?.toString() ?? ''}
|
||||
placeholder="Example: 0"
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
</Stack>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { DropdownList } from 'components/shared/DropdownList'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton/InputWithVariableButton'
|
||||
import { TableListItemProps } from 'components/shared/TableList'
|
||||
import { Cell } from 'models'
|
||||
|
||||
export const CellWithValueStack = ({
|
||||
item,
|
||||
onItemChange,
|
||||
columns,
|
||||
}: TableListItemProps<Cell> & { 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 (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<DropdownList<string>
|
||||
currentItem={item.column}
|
||||
onItemSelect={handleColumnSelect}
|
||||
items={columns}
|
||||
placeholder="Select a column"
|
||||
/>
|
||||
<InputWithVariableButton
|
||||
initialValue={item.value ?? ''}
|
||||
onChange={handleValueChange}
|
||||
placeholder="Type a value..."
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -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<ExtractingCell> & { 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 (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<DropdownList<string>
|
||||
currentItem={item.column}
|
||||
onItemSelect={handleColumnSelect}
|
||||
items={columns}
|
||||
placeholder="Select a column"
|
||||
/>
|
||||
<VariableSearchInput
|
||||
initialVariableId={item.variableId}
|
||||
onSelectVariable={handleVariableIdChange}
|
||||
placeholder="Select a variable"
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
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 {
|
||||
Cell,
|
||||
defaultTable,
|
||||
ExtractingCell,
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsGetOptions,
|
||||
GoogleSheetsInsertRowOptions,
|
||||
GoogleSheetsOptions,
|
||||
GoogleSheetsUpdateRowOptions,
|
||||
Table,
|
||||
} from 'models'
|
||||
import React, { useMemo } from 'react'
|
||||
import {
|
||||
getGoogleSheetsConsentScreenUrl,
|
||||
Sheet,
|
||||
useSheets,
|
||||
} from 'services/integrations'
|
||||
import { isDefined } from 'utils'
|
||||
import { SheetsDropdown } from './SheetsDropdown'
|
||||
import { SpreadsheetsDropdown } from './SpreadsheetDropdown'
|
||||
import { CellWithValueStack } from './CellWithValueStack'
|
||||
import { CellWithVariableIdStack } from './CellWithVariableIdStack'
|
||||
|
||||
type Props = {
|
||||
options: GoogleSheetsOptions
|
||||
onOptionsChange: (options: GoogleSheetsOptions) => void
|
||||
stepId: string
|
||||
}
|
||||
|
||||
export const GoogleSheetsSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
stepId,
|
||||
}: Props) => {
|
||||
const { save, hasUnsavedChanges } = useTypebot()
|
||||
const { sheets, isLoading } = useSheets({
|
||||
credentialsId: options?.credentialsId,
|
||||
spreadsheetId: options?.spreadsheetId,
|
||||
})
|
||||
const sheet = useMemo(
|
||||
() => sheets?.find((s) => s.id === options?.sheetId),
|
||||
[sheets, options?.sheetId]
|
||||
)
|
||||
const handleCredentialsIdChange = (credentialsId: string) =>
|
||||
onOptionsChange({ ...options, credentialsId })
|
||||
const handleSpreadsheetIdChange = (spreadsheetId: string) =>
|
||||
onOptionsChange({ ...options, spreadsheetId })
|
||||
const handleSheetIdChange = (sheetId: string) =>
|
||||
onOptionsChange({ ...options, sheetId })
|
||||
|
||||
const handleActionChange = (action: GoogleSheetsAction) => {
|
||||
switch (action) {
|
||||
case GoogleSheetsAction.GET: {
|
||||
const newOptions: GoogleSheetsGetOptions = {
|
||||
...options,
|
||||
action,
|
||||
cellsToExtract: defaultTable,
|
||||
}
|
||||
return onOptionsChange({ ...newOptions })
|
||||
}
|
||||
case GoogleSheetsAction.INSERT_ROW: {
|
||||
const newOptions: GoogleSheetsInsertRowOptions = {
|
||||
...options,
|
||||
action,
|
||||
cellsToInsert: defaultTable,
|
||||
}
|
||||
return onOptionsChange({ ...newOptions })
|
||||
}
|
||||
case GoogleSheetsAction.UPDATE_ROW: {
|
||||
const newOptions: GoogleSheetsUpdateRowOptions = {
|
||||
...options,
|
||||
action,
|
||||
cellsToUpsert: defaultTable,
|
||||
}
|
||||
return onOptionsChange({ ...newOptions })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateNewClick = async () => {
|
||||
if (hasUnsavedChanges) {
|
||||
const errorToastId = await save()
|
||||
if (errorToastId) return
|
||||
}
|
||||
const linkElement = document.createElement('a')
|
||||
linkElement.href = getGoogleSheetsConsentScreenUrl(
|
||||
window.location.href,
|
||||
stepId
|
||||
)
|
||||
linkElement.click()
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<CredentialsDropdown
|
||||
type={CredentialsType.GOOGLE_SHEETS}
|
||||
currentCredentialsId={options?.credentialsId}
|
||||
onCredentialsSelect={handleCredentialsIdChange}
|
||||
onCreateNewClick={handleCreateNewClick}
|
||||
/>
|
||||
{options?.credentialsId && (
|
||||
<SpreadsheetsDropdown
|
||||
credentialsId={options.credentialsId}
|
||||
spreadsheetId={options.spreadsheetId}
|
||||
onSelectSpreadsheetId={handleSpreadsheetIdChange}
|
||||
/>
|
||||
)}
|
||||
{options?.spreadsheetId && options.credentialsId && (
|
||||
<SheetsDropdown
|
||||
sheets={sheets ?? []}
|
||||
isLoading={isLoading}
|
||||
sheetId={options.sheetId}
|
||||
onSelectSheetId={handleSheetIdChange}
|
||||
/>
|
||||
)}
|
||||
{options?.spreadsheetId &&
|
||||
options.credentialsId &&
|
||||
isDefined(options.sheetId) && (
|
||||
<>
|
||||
<Divider />
|
||||
<DropdownList<GoogleSheetsAction>
|
||||
currentItem={'action' in options ? options.action : undefined}
|
||||
onItemSelect={handleActionChange}
|
||||
items={Object.values(GoogleSheetsAction)}
|
||||
placeholder="Select an operation"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{sheet && 'action' in options && (
|
||||
<ActionOptions
|
||||
options={options}
|
||||
sheet={sheet}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const ActionOptions = ({
|
||||
options,
|
||||
sheet,
|
||||
onOptionsChange,
|
||||
}: {
|
||||
options:
|
||||
| GoogleSheetsGetOptions
|
||||
| GoogleSheetsInsertRowOptions
|
||||
| GoogleSheetsUpdateRowOptions
|
||||
sheet: Sheet
|
||||
onOptionsChange: (options: GoogleSheetsOptions) => void
|
||||
}) => {
|
||||
const handleInsertColumnsChange = (cellsToInsert: Table<Cell>) =>
|
||||
onOptionsChange({ ...options, cellsToInsert } as GoogleSheetsOptions)
|
||||
|
||||
const handleUpsertColumnsChange = (cellsToUpsert: Table<Cell>) =>
|
||||
onOptionsChange({ ...options, cellsToUpsert } as GoogleSheetsOptions)
|
||||
|
||||
const handleReferenceCellChange = (referenceCell: Cell) =>
|
||||
onOptionsChange({ ...options, referenceCell } as GoogleSheetsOptions)
|
||||
|
||||
const handleExtractingCellsChange = (cellsToExtract: Table<ExtractingCell>) =>
|
||||
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
|
||||
|
||||
const UpdatingCellItem = useMemo(
|
||||
() => (props: TableListItemProps<Cell>) =>
|
||||
<CellWithValueStack {...props} columns={sheet.columns} />,
|
||||
[sheet.columns]
|
||||
)
|
||||
|
||||
const ExtractingCellItem = useMemo(
|
||||
() => (props: TableListItemProps<ExtractingCell>) =>
|
||||
<CellWithVariableIdStack {...props} columns={sheet.columns} />,
|
||||
[sheet.columns]
|
||||
)
|
||||
|
||||
switch (options.action) {
|
||||
case GoogleSheetsAction.INSERT_ROW:
|
||||
return (
|
||||
<TableList<Cell>
|
||||
initialItems={options.cellsToInsert}
|
||||
onItemsChange={handleInsertColumnsChange}
|
||||
Item={UpdatingCellItem}
|
||||
addLabel="Add a value"
|
||||
/>
|
||||
)
|
||||
case GoogleSheetsAction.UPDATE_ROW:
|
||||
return (
|
||||
<Stack>
|
||||
<Text>Row to select</Text>
|
||||
<CellWithValueStack
|
||||
id={'reference'}
|
||||
columns={sheet.columns}
|
||||
item={options.referenceCell ?? {}}
|
||||
onItemChange={handleReferenceCellChange}
|
||||
/>
|
||||
<Text>Cells to update</Text>
|
||||
<TableList<Cell>
|
||||
initialItems={options.cellsToUpsert}
|
||||
onItemsChange={handleUpsertColumnsChange}
|
||||
Item={UpdatingCellItem}
|
||||
addLabel="Add a value"
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
case GoogleSheetsAction.GET:
|
||||
return (
|
||||
<Stack>
|
||||
<Text>Row to select</Text>
|
||||
<CellWithValueStack
|
||||
id={'reference'}
|
||||
columns={sheet.columns}
|
||||
item={options.referenceCell ?? {}}
|
||||
onItemChange={handleReferenceCellChange}
|
||||
/>
|
||||
<Text>Cells to extract</Text>
|
||||
<TableList<ExtractingCell>
|
||||
initialItems={options.cellsToExtract}
|
||||
onItemsChange={handleExtractingCellsChange}
|
||||
Item={ExtractingCellItem}
|
||||
addLabel="Add a value"
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { SearchableDropdown } from 'components/shared/SearchableDropdown'
|
||||
import { useMemo } from 'react'
|
||||
import { Sheet } from 'services/integrations'
|
||||
import { isDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
sheets: Sheet[]
|
||||
isLoading: boolean
|
||||
sheetId?: string
|
||||
onSelectSheetId: (id: string) => void
|
||||
}
|
||||
|
||||
export const SheetsDropdown = ({
|
||||
sheets,
|
||||
isLoading,
|
||||
sheetId,
|
||||
onSelectSheetId,
|
||||
}: Props) => {
|
||||
const currentSheet = useMemo(
|
||||
() => sheets?.find((s) => s.id === sheetId),
|
||||
[sheetId, sheets]
|
||||
)
|
||||
|
||||
const handleSpreadsheetSelect = (name: string) => {
|
||||
const id = sheets?.find((s) => s.name === name)?.id
|
||||
if (isDefined(id)) onSelectSheetId(id)
|
||||
}
|
||||
return (
|
||||
<SearchableDropdown
|
||||
selectedItem={currentSheet?.name}
|
||||
items={(sheets ?? []).map((s) => s.name)}
|
||||
onValueChange={handleSpreadsheetSelect}
|
||||
placeholder={'Select the sheet'}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { SearchableDropdown } from 'components/shared/SearchableDropdown'
|
||||
import { useMemo } from 'react'
|
||||
import { useSpreadsheets } from 'services/integrations'
|
||||
|
||||
type Props = {
|
||||
credentialsId: string
|
||||
spreadsheetId?: string
|
||||
onSelectSpreadsheetId: (id: string) => void
|
||||
}
|
||||
|
||||
export const SpreadsheetsDropdown = ({
|
||||
credentialsId,
|
||||
spreadsheetId,
|
||||
onSelectSpreadsheetId,
|
||||
}: Props) => {
|
||||
const { spreadsheets, isLoading } = useSpreadsheets({ credentialsId })
|
||||
const currentSpreadsheet = useMemo(
|
||||
() => spreadsheets?.find((s) => s.id === spreadsheetId),
|
||||
[spreadsheetId, spreadsheets]
|
||||
)
|
||||
|
||||
const handleSpreadsheetSelect = (name: string) => {
|
||||
const id = spreadsheets?.find((s) => s.name === name)?.id
|
||||
if (id) onSelectSpreadsheetId(id)
|
||||
}
|
||||
return (
|
||||
<SearchableDropdown
|
||||
selectedItem={currentSpreadsheet?.name}
|
||||
items={(spreadsheets ?? []).map((s) => s.name)}
|
||||
onValueChange={handleSpreadsheetSelect}
|
||||
placeholder={'Search for spreadsheet'}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { GoogleSheetsSettingsBody } from './GoogleSheetsSettingsBody'
|
@ -0,0 +1,94 @@
|
||||
import { FormLabel, HStack, Stack } from '@chakra-ui/react'
|
||||
import { SmartNumberInput } from 'components/shared/SmartNumberInput'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { NumberInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
import { removeUndefinedFields } from 'services/utils'
|
||||
|
||||
type NumberInputSettingsBodyProps = {
|
||||
options: NumberInputOptions
|
||||
onOptionsChange: (options: NumberInputOptions) => void
|
||||
}
|
||||
|
||||
export const NumberInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: NumberInputSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, placeholder } })
|
||||
const handleButtonLabelChange = (button: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, button } })
|
||||
const handleMinChange = (min?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, min }))
|
||||
const handleMaxChange = (max?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, max }))
|
||||
const handleStepChange = (step?: number) =>
|
||||
onOptionsChange(removeUndefinedFields({ ...options, step }))
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onOptionsChange({ ...options, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options.labels.placeholder}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options?.labels?.button ?? 'Send'}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="min">
|
||||
Min:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="min"
|
||||
value={options.min}
|
||||
onValueChange={handleMinChange}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="max">
|
||||
Max:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="max"
|
||||
value={options.max}
|
||||
onValueChange={handleMaxChange}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" htmlFor="step">
|
||||
Step:
|
||||
</FormLabel>
|
||||
<SmartNumberInput
|
||||
id="step"
|
||||
value={options.step}
|
||||
onValueChange={handleStepChange}
|
||||
/>
|
||||
</HStack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { EmailInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type PhoneNumberSettingsBodyProps = {
|
||||
options: EmailInputOptions
|
||||
onOptionsChange: (options: EmailInputOptions) => void
|
||||
}
|
||||
|
||||
export const PhoneNumberSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: PhoneNumberSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
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 })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options.labels.placeholder}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
|
||||
import { RedirectOptions } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options: RedirectOptions
|
||||
onOptionsChange: (options: RedirectOptions) => void
|
||||
}
|
||||
|
||||
export const RedirectSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const handleUrlChange = (url?: string) => onOptionsChange({ ...options, url })
|
||||
|
||||
const handleIsNewTabChange = (isNewTab: boolean) =>
|
||||
onOptionsChange({ ...options, isNewTab })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="tracking-id">
|
||||
Url:
|
||||
</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id="tracking-id"
|
||||
initialValue={options.url ?? ''}
|
||||
placeholder="Type a URL..."
|
||||
onChange={handleUrlChange}
|
||||
/>
|
||||
</Stack>
|
||||
<SwitchWithLabel
|
||||
id="new-tab"
|
||||
label="Open in new tab?"
|
||||
initialValue={options.isNewTab}
|
||||
onCheckChange={handleIsNewTabChange}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedTextarea } from 'components/shared/DebouncedTextarea'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { SetVariableOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options: SetVariableOptions
|
||||
onOptionsChange: (options: SetVariableOptions) => void
|
||||
}
|
||||
|
||||
export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onOptionsChange({ ...options, variableId: variable?.id })
|
||||
const handleExpressionChange = (expressionToEvaluate: string) =>
|
||||
onOptionsChange({ ...options, expressionToEvaluate })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable-search">
|
||||
Search or create variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
onSelectVariable={handleVariableChange}
|
||||
initialVariableId={options.variableId}
|
||||
id="variable-search"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="expression">
|
||||
Value / Expression:
|
||||
</FormLabel>
|
||||
<DebouncedTextarea
|
||||
id="expression"
|
||||
initialValue={options.expressionToEvaluate ?? ''}
|
||||
onChange={handleExpressionChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { TextInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type TextInputSettingsBodyProps = {
|
||||
options: TextInputOptions
|
||||
onOptionsChange: (options: TextInputOptions) => void
|
||||
}
|
||||
|
||||
export const TextInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: TextInputSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, placeholder } })
|
||||
const handleButtonLabelChange = (button: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, button } })
|
||||
const handleLongChange = (isLong: boolean) =>
|
||||
onOptionsChange({ ...options, isLong })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onOptionsChange({ ...options, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<SwitchWithLabel
|
||||
id="switch"
|
||||
label="Long text?"
|
||||
initialValue={options?.isLong ?? false}
|
||||
onCheckChange={handleLongChange}
|
||||
/>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options.labels.placeholder}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { UrlInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type UrlInputSettingsBodyProps = {
|
||||
options: UrlInputOptions
|
||||
onOptionsChange: (options: UrlInputOptions) => void
|
||||
}
|
||||
|
||||
export const UrlInputSettingsBody = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
}: UrlInputSettingsBodyProps) => {
|
||||
const handlePlaceholderChange = (placeholder: string) =>
|
||||
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 })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="placeholder">
|
||||
Placeholder:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="placeholder"
|
||||
initialValue={options.labels.placeholder}
|
||||
onChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<DebouncedInput
|
||||
id="button"
|
||||
initialValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save answer in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { Stack, FormControl, FormLabel } from '@chakra-ui/react'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton/InputWithVariableButton'
|
||||
import { TableListItemProps } from 'components/shared/TableList'
|
||||
import { KeyValue } from 'models'
|
||||
|
||||
export const QueryParamsInputs = (props: TableListItemProps<KeyValue>) => (
|
||||
<KeyValueInputs
|
||||
{...props}
|
||||
keyPlaceholder="e.g. email"
|
||||
valuePlaceholder="e.g. {{Email}}"
|
||||
/>
|
||||
)
|
||||
|
||||
export const HeadersInputs = (props: TableListItemProps<KeyValue>) => (
|
||||
<KeyValueInputs
|
||||
{...props}
|
||||
keyPlaceholder="e.g. Content-Type"
|
||||
valuePlaceholder="e.g. application/json"
|
||||
/>
|
||||
)
|
||||
|
||||
export const KeyValueInputs = ({
|
||||
id,
|
||||
item,
|
||||
onItemChange,
|
||||
keyPlaceholder,
|
||||
valuePlaceholder,
|
||||
}: TableListItemProps<KeyValue> & {
|
||||
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 (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'key' + id}>Key:</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id={'key' + id}
|
||||
initialValue={item.key ?? ''}
|
||||
onChange={handleKeyChange}
|
||||
placeholder={keyPlaceholder}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'value' + id}>Value:</FormLabel>
|
||||
<InputWithVariableButton
|
||||
id={'value' + id}
|
||||
initialValue={item.value ?? ''}
|
||||
onChange={handleValueChange}
|
||||
placeholder={valuePlaceholder}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -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<ResponseVariableMapping> & { dataItems: string[] }) => {
|
||||
const handleBodyPathChange = (bodyPath: string) =>
|
||||
onItemChange({ ...item, bodyPath })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onItemChange({ ...item, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="name">Data:</FormLabel>
|
||||
<SearchableDropdown
|
||||
items={dataItems}
|
||||
value={item.bodyPath}
|
||||
onValueChange={handleBodyPathChange}
|
||||
placeholder="Select the data"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="value">Set variable:</FormLabel>
|
||||
<VariableSearchInput
|
||||
onSelectVariable={handleVariableChange}
|
||||
placeholder="Search for a variable"
|
||||
initialVariableId={item.variableId}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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<VariableForTest>) => {
|
||||
const handleVariableSelect = (variable?: Variable) =>
|
||||
onItemChange({ ...item, variableId: variable?.id })
|
||||
const handleValueChange = (value: string) => {
|
||||
if (value === item.value) return
|
||||
onItemChange({ ...item, value })
|
||||
}
|
||||
return (
|
||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'name' + id}>Variable name:</FormLabel>
|
||||
<VariableSearchInput
|
||||
id={'name' + id}
|
||||
initialVariableId={item.variableId}
|
||||
onSelectVariable={handleVariableSelect}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor={'value' + id}>Test value:</FormLabel>
|
||||
<DebouncedInput
|
||||
id={'value' + id}
|
||||
initialValue={item.value ?? ''}
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Button,
|
||||
Flex,
|
||||
Stack,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton/InputWithVariableButton'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import {
|
||||
HttpMethod,
|
||||
KeyValue,
|
||||
Table,
|
||||
WebhookOptions,
|
||||
VariableForTest,
|
||||
Webhook,
|
||||
ResponseVariableMapping,
|
||||
} from 'models'
|
||||
import { DropdownList } from 'components/shared/DropdownList'
|
||||
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 = {
|
||||
webhook: Webhook
|
||||
options?: WebhookOptions
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
onWebhookChange: (updates: Partial<Webhook>) => void
|
||||
onTestRequestClick: () => void
|
||||
}
|
||||
|
||||
export const WebhookSettings = ({
|
||||
options,
|
||||
onOptionsChange,
|
||||
webhook,
|
||||
onWebhookChange,
|
||||
onTestRequestClick,
|
||||
}: Props) => {
|
||||
const { typebot, save } = useTypebot()
|
||||
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
|
||||
const [testResponse, setTestResponse] = useState<string>()
|
||||
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
const handleUrlChange = (url?: string) => onWebhookChange({ url })
|
||||
|
||||
const handleMethodChange = (method: HttpMethod) => onWebhookChange({ method })
|
||||
|
||||
const handleQueryParamsChange = (queryParams: Table<KeyValue>) =>
|
||||
onWebhookChange({ queryParams })
|
||||
|
||||
const handleHeadersChange = (headers: Table<KeyValue>) =>
|
||||
onWebhookChange({ headers })
|
||||
|
||||
const handleBodyChange = (body: string) => onWebhookChange({ body })
|
||||
|
||||
const handleVariablesChange = (variablesForTest: Table<VariableForTest>) =>
|
||||
options && onOptionsChange({ ...options, variablesForTest })
|
||||
|
||||
const handleResponseMappingChange = (
|
||||
responseVariableMapping: Table<ResponseVariableMapping>
|
||||
) => options && onOptionsChange({ ...options, responseVariableMapping })
|
||||
|
||||
const handleTestRequestClick = async () => {
|
||||
if (!typebot || !webhook) return
|
||||
setIsTestResponseLoading(true)
|
||||
onTestRequestClick()
|
||||
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))
|
||||
setIsTestResponseLoading(false)
|
||||
}
|
||||
|
||||
const ResponseMappingInputs = useMemo(
|
||||
() => (props: TableListItemProps<ResponseVariableMapping>) =>
|
||||
<DataVariableInputs {...props} dataItems={responseKeys} />,
|
||||
[responseKeys]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<Flex>
|
||||
<DropdownList<HttpMethod>
|
||||
currentItem={webhook.method}
|
||||
onItemSelect={handleMethodChange}
|
||||
items={Object.values(HttpMethod)}
|
||||
/>
|
||||
</Flex>
|
||||
<InputWithVariableButton
|
||||
placeholder="Your Webhook URL..."
|
||||
initialValue={webhook.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
/>
|
||||
</Stack>
|
||||
<Accordion allowToggle allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Query params
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={webhook.queryParams}
|
||||
onItemsChange={handleQueryParamsChange}
|
||||
Item={QueryParamsInputs}
|
||||
addLabel="Add a param"
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Headers
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={webhook.headers}
|
||||
onItemsChange={handleHeadersChange}
|
||||
Item={HeadersInputs}
|
||||
addLabel="Add a value"
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Body
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<CodeEditor
|
||||
value={'test'}
|
||||
lang="json"
|
||||
onChange={handleBodyChange}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Variable values for test
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<VariableForTest>
|
||||
initialItems={
|
||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||
}
|
||||
onItemsChange={handleVariablesChange}
|
||||
Item={VariableForTestInputs}
|
||||
addLabel="Add an entry"
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<Button
|
||||
onClick={handleTestRequestClick}
|
||||
colorScheme="blue"
|
||||
isLoading={isTestResponseLoading}
|
||||
>
|
||||
Test the request
|
||||
</Button>
|
||||
{testResponse && (
|
||||
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
||||
)}
|
||||
{(testResponse || options?.responseVariableMapping) && (
|
||||
<Accordion allowToggle allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Save in variables
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="6">
|
||||
<TableList<ResponseVariableMapping>
|
||||
initialItems={
|
||||
options?.responseVariableMapping ?? { byId: {}, allIds: [] }
|
||||
}
|
||||
onItemsChange={handleResponseMappingChange}
|
||||
Item={ResponseMappingInputs}
|
||||
addLabel="Add an entry"
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { WebhookSettings } from './WebhookSettings'
|
@ -0,0 +1,5 @@
|
||||
export * from './DateInputSettingsBody'
|
||||
export * from './EmailInputSettingsBody'
|
||||
export * from './NumberInputSettingsBody'
|
||||
export * from './TextInputSettingsBody'
|
||||
export * from './UrlInputSettingsBody'
|
Reference in New Issue
Block a user