feat(integration): ✨ Add webhooks
This commit is contained in:
@ -280,3 +280,9 @@ export const FilmIcon = (props: IconProps) => (
|
|||||||
<line x1="17" y1="7" x2="22" y2="7"></line>
|
<line x1="17" y1="7" x2="22" y2="7"></line>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const WebhookIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
9
apps/builder/assets/styles/codeMirror.css
Normal file
9
apps/builder/assets/styles/codeMirror.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
outline: 0px solid transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-scroller {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
}
|
@ -14,6 +14,7 @@ import {
|
|||||||
NumberIcon,
|
NumberIcon,
|
||||||
PhoneIcon,
|
PhoneIcon,
|
||||||
TextIcon,
|
TextIcon,
|
||||||
|
WebhookIcon,
|
||||||
} from 'assets/icons'
|
} from 'assets/icons'
|
||||||
import { GoogleAnalyticsLogo, GoogleSheetsLogo } from 'assets/logos'
|
import { GoogleAnalyticsLogo, GoogleSheetsLogo } from 'assets/logos'
|
||||||
import {
|
import {
|
||||||
@ -59,6 +60,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
|
|||||||
return <GoogleSheetsLogo {...props} />
|
return <GoogleSheetsLogo {...props} />
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
return <GoogleAnalyticsLogo {...props} />
|
return <GoogleAnalyticsLogo {...props} />
|
||||||
|
case IntegrationStepType.WEBHOOK:
|
||||||
|
return <WebhookIcon />
|
||||||
case 'start':
|
case 'start':
|
||||||
return <FlagIcon {...props} />
|
return <FlagIcon {...props} />
|
||||||
default:
|
default:
|
||||||
|
@ -49,6 +49,8 @@ export const StepTypeLabel = ({ type }: Props) => {
|
|||||||
<Text>Analytics</Text>
|
<Text>Analytics</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
case IntegrationStepType.WEBHOOK:
|
||||||
|
return <Text>Webhook</Text>
|
||||||
default:
|
default:
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ export const BlockNode = ({ block }: Props) => {
|
|||||||
borderColor={
|
borderColor={
|
||||||
isConnecting || isOpened || isPreviewing ? 'blue.400' : 'white'
|
isConnecting || isOpened || isPreviewing ? 'blue.400' : 'white'
|
||||||
}
|
}
|
||||||
minW="300px"
|
w="300px"
|
||||||
transition="border 300ms, box-shadow 200ms"
|
transition="border 300ms, box-shadow 200ms"
|
||||||
pos="absolute"
|
pos="absolute"
|
||||||
style={{
|
style={{
|
||||||
|
@ -16,6 +16,8 @@ import {
|
|||||||
Step,
|
Step,
|
||||||
StepOptions,
|
StepOptions,
|
||||||
TextBubbleStep,
|
TextBubbleStep,
|
||||||
|
Webhook,
|
||||||
|
WebhookStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
@ -32,6 +34,7 @@ import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
|||||||
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
||||||
import { RedirectSettings } from './bodies/RedirectSettings'
|
import { RedirectSettings } from './bodies/RedirectSettings'
|
||||||
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
|
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'
|
||||||
|
import { WebhookSettings } from './bodies/WebhookSettings'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
step: Exclude<Step, TextBubbleStep>
|
step: Exclude<Step, TextBubbleStep>
|
||||||
@ -51,7 +54,7 @@ export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => {
|
|||||||
<PopoverContent onMouseDown={handleMouseDown} pos="relative">
|
<PopoverContent onMouseDown={handleMouseDown} pos="relative">
|
||||||
<PopoverArrow />
|
<PopoverArrow />
|
||||||
<PopoverBody
|
<PopoverBody
|
||||||
p="6"
|
py="6"
|
||||||
overflowY="scroll"
|
overflowY="scroll"
|
||||||
maxH="400px"
|
maxH="400px"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -74,9 +77,16 @@ export const SettingsPopoverContent = ({ step, onExpandClick }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const StepSettings = ({ step }: { step: Step }) => {
|
export const StepSettings = ({ step }: { step: Step }) => {
|
||||||
const { updateStep } = useTypebot()
|
const { updateStep, updateWebhook, typebot } = useTypebot()
|
||||||
const handleOptionsChange = (options: StepOptions) =>
|
const handleOptionsChange = (options: StepOptions) => {
|
||||||
updateStep(step.id, { options } as Partial<InputStep>)
|
updateStep(step.id, { options } as Partial<InputStep>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWebhookChange = (webhook: Partial<Webhook>) => {
|
||||||
|
const webhookId = (step as WebhookStep).options?.webhookId
|
||||||
|
if (!webhookId) return
|
||||||
|
updateWebhook(webhookId, webhook)
|
||||||
|
}
|
||||||
|
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case InputStepType.TEXT: {
|
case InputStepType.TEXT: {
|
||||||
@ -176,6 +186,17 @@ export const StepSettings = ({ step }: { step: Step }) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case IntegrationStepType.WEBHOOK: {
|
||||||
|
return (
|
||||||
|
<WebhookSettings
|
||||||
|
key={step.options?.webhookId}
|
||||||
|
options={step.options}
|
||||||
|
webhook={typebot?.webhooks.byId[step.options?.webhookId ?? '']}
|
||||||
|
onOptionsChange={handleOptionsChange}
|
||||||
|
onWebhookChange={handleWebhookChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ export const ChoiceInputSettingsBody = ({
|
|||||||
options && onOptionsChange({ ...options, isMultipleChoice })
|
options && onOptionsChange({ ...options, isMultipleChoice })
|
||||||
const handleButtonLabelChange = (buttonLabel: string) =>
|
const handleButtonLabelChange = (buttonLabel: string) =>
|
||||||
options && onOptionsChange({ ...options, buttonLabel })
|
options && onOptionsChange({ ...options, buttonLabel })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
options && onOptionsChange({ ...options, variableId: variable.id })
|
options && onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -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<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
|
||||||
|
delay={100}
|
||||||
|
initialValue={item.value ?? ''}
|
||||||
|
onChange={handleChangeValue}
|
||||||
|
placeholder="Type a value..."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
@ -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<Comparison>
|
|
||||||
logicalOperator: LogicalOperator
|
|
||||||
onLogicalOperatorChange: (logicalOperator: LogicalOperator) => void
|
|
||||||
onComparisonsChange: (comparisons: Table<Comparison>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ComparisonsList = ({
|
|
||||||
initialComparisons,
|
|
||||||
logicalOperator,
|
|
||||||
onLogicalOperatorChange,
|
|
||||||
onComparisonsChange,
|
|
||||||
}: Props) => {
|
|
||||||
const [comparisons, setComparisons] = useImmer(initialComparisons)
|
|
||||||
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
|
|
||||||
|
|
||||||
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<Omit<Comparison, 'id'>>
|
|
||||||
) =>
|
|
||||||
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 (
|
|
||||||
<Stack spacing="4" py="4">
|
|
||||||
{comparisons.allIds.map((comparisonId, idx) => (
|
|
||||||
<>
|
|
||||||
{idx > 0 && (
|
|
||||||
<Flex justify="center">
|
|
||||||
<DropdownList<LogicalOperator>
|
|
||||||
currentItem={logicalOperator}
|
|
||||||
onItemSelect={handleLogicalOperatorSelected}
|
|
||||||
items={Object.values(LogicalOperator)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<Flex
|
|
||||||
pos="relative"
|
|
||||||
onMouseEnter={handleMouseEnter(comparisonId)}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
key={comparisonId}
|
|
||||||
p="4"
|
|
||||||
rounded="md"
|
|
||||||
flex="1"
|
|
||||||
borderWidth="1px"
|
|
||||||
>
|
|
||||||
<VariableSearchInput
|
|
||||||
initialVariableId={comparisons.byId[comparisonId].variableId}
|
|
||||||
onSelectVariable={handleVariableSelected(comparisonId)}
|
|
||||||
placeholder="Search for a variable"
|
|
||||||
/>
|
|
||||||
<DropdownList<ComparisonOperators>
|
|
||||||
currentItem={comparisons.byId[comparisonId].comparisonOperator}
|
|
||||||
onItemSelect={handleComparisonOperatorSelected(comparisonId)}
|
|
||||||
items={Object.values(ComparisonOperators)}
|
|
||||||
/>
|
|
||||||
{comparisons.byId[comparisonId].comparisonOperator !==
|
|
||||||
ComparisonOperators.IS_SET && (
|
|
||||||
<DebouncedInput
|
|
||||||
delay={100}
|
|
||||||
initialValue={comparisons.byId[comparisonId].value ?? ''}
|
|
||||||
onChange={handleValueChange(comparisonId)}
|
|
||||||
placeholder="Type a value..."
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
<Fade in={showDeleteId === comparisonId}>
|
|
||||||
<IconButton
|
|
||||||
icon={<TrashIcon />}
|
|
||||||
aria-label="Remove comparison"
|
|
||||||
onClick={deleteComparison(comparisonId)}
|
|
||||||
pos="absolute"
|
|
||||||
left="-15px"
|
|
||||||
top="-15px"
|
|
||||||
size="sm"
|
|
||||||
shadow="md"
|
|
||||||
/>
|
|
||||||
</Fade>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
leftIcon={<PlusIcon />}
|
|
||||||
onClick={createComparison}
|
|
||||||
flexShrink={0}
|
|
||||||
colorScheme="blue"
|
|
||||||
>
|
|
||||||
Add a comparison
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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 { Comparison, ConditionOptions, LogicalOperator, Table } from 'models'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { ComparisonsList } from './ComparisonsList'
|
import { ComparisonItem } from './ComparisonsItem'
|
||||||
|
|
||||||
type ConditionSettingsBodyProps = {
|
type ConditionSettingsBodyProps = {
|
||||||
options: ConditionOptions
|
options: ConditionOptions
|
||||||
@ -17,11 +20,19 @@ export const ConditionSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, logicalOperator })
|
onOptionsChange({ ...options, logicalOperator })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComparisonsList
|
<TableList<Comparison>
|
||||||
initialComparisons={options.comparisons}
|
onItemsChange={handleComparisonsChange}
|
||||||
logicalOperator={options.logicalOperator ?? LogicalOperator.AND}
|
Item={ComparisonItem}
|
||||||
onLogicalOperatorChange={handleLogicalOperatorChange}
|
ComponentBetweenItems={() => (
|
||||||
onComparisonsChange={handleComparisonsChange}
|
<Flex justify="center">
|
||||||
|
<DropdownList<LogicalOperator>
|
||||||
|
currentItem={options.logicalOperator}
|
||||||
|
onItemSelect={handleLogicalOperatorChange}
|
||||||
|
items={Object.values(LogicalOperator)}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
addLabel="Add a comparison"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ export const DateInputSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, isRange })
|
onOptionsChange({ ...options, isRange })
|
||||||
const handleHasTimeChange = (hasTime: boolean) =>
|
const handleHasTimeChange = (hasTime: boolean) =>
|
||||||
onOptionsChange({ ...options, hasTime })
|
onOptionsChange({ ...options, hasTime })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -17,8 +17,8 @@ export const EmailInputSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
||||||
const handleButtonLabelChange = (button: string) =>
|
const handleButtonLabelChange = (button: string) =>
|
||||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -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<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>
|
||||||
|
)
|
||||||
|
}
|
@ -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<ExtractingCell>
|
|
||||||
onCellsChange: (cells: Table<ExtractingCell>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = generate()
|
|
||||||
const defaultCells: Table<ExtractingCell> = {
|
|
||||||
byId: { [id]: {} },
|
|
||||||
allIds: [id],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExtractCellList = ({
|
|
||||||
sheet,
|
|
||||||
initialCells,
|
|
||||||
onCellsChange,
|
|
||||||
}: Props) => {
|
|
||||||
const [cells, setCells] = useImmer(initialCells ?? defaultCells)
|
|
||||||
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
|
|
||||||
|
|
||||||
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<ExtractingCell>) =>
|
|
||||||
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 (
|
|
||||||
<Stack spacing="4">
|
|
||||||
{cells.allIds.map((cellId) => (
|
|
||||||
<>
|
|
||||||
<Flex
|
|
||||||
pos="relative"
|
|
||||||
onMouseEnter={handleMouseEnter(cellId)}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<CellWithVariableIdStack
|
|
||||||
key={cellId}
|
|
||||||
cell={cells.byId[cellId]}
|
|
||||||
columns={sheet.columns}
|
|
||||||
onCellChange={handleCellChange(cellId)}
|
|
||||||
/>
|
|
||||||
<Fade in={showDeleteId === cellId}>
|
|
||||||
<IconButton
|
|
||||||
icon={<TrashIcon />}
|
|
||||||
aria-label="Remove cell"
|
|
||||||
onClick={deleteCell(cellId)}
|
|
||||||
pos="absolute"
|
|
||||||
left="-15px"
|
|
||||||
top="-15px"
|
|
||||||
size="sm"
|
|
||||||
shadow="md"
|
|
||||||
/>
|
|
||||||
</Fade>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
leftIcon={<PlusIcon />}
|
|
||||||
onClick={createCell}
|
|
||||||
flexShrink={0}
|
|
||||||
colorScheme="blue"
|
|
||||||
>
|
|
||||||
Add a value
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
|
||||||
<DropdownList<string>
|
|
||||||
currentItem={cell.column}
|
|
||||||
onItemSelect={handleColumnSelect}
|
|
||||||
items={columns}
|
|
||||||
placeholder="Select a column"
|
|
||||||
/>
|
|
||||||
<VariableSearchInput
|
|
||||||
initialVariableId={cell.variableId}
|
|
||||||
onSelectVariable={handleVariableIdChange}
|
|
||||||
placeholder="Select a variable"
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
import { Divider, Stack, Text } from '@chakra-ui/react'
|
import { Divider, Stack, Text } from '@chakra-ui/react'
|
||||||
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
||||||
import { DropdownList } from 'components/shared/DropdownList'
|
import { DropdownList } from 'components/shared/DropdownList'
|
||||||
|
import { TableList, TableListItemProps } from 'components/shared/TableList'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { CredentialsType } from 'db'
|
import { CredentialsType } from 'db'
|
||||||
import {
|
import {
|
||||||
@ -17,10 +18,10 @@ import {
|
|||||||
useSheets,
|
useSheets,
|
||||||
} from 'services/integrations'
|
} from 'services/integrations'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined } from 'utils'
|
||||||
import { ExtractCellList } from './ExtractCellList'
|
|
||||||
import { SheetsDropdown } from './SheetsDropdown'
|
import { SheetsDropdown } from './SheetsDropdown'
|
||||||
import { SpreadsheetsDropdown } from './SpreadsheetDropdown'
|
import { SpreadsheetsDropdown } from './SpreadsheetDropdown'
|
||||||
import { CellWithValueStack, UpdateCellList } from './UpdateCellList'
|
import { CellWithValueStack } from './CellWithValueStack'
|
||||||
|
import { CellWithVariableIdStack } from './CellWithVariableIdStack'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options?: GoogleSheetsOptions
|
options?: GoogleSheetsOptions
|
||||||
@ -132,13 +133,26 @@ const ActionOptions = ({
|
|||||||
const handleExtractingCellsChange = (cellsToExtract: Table<ExtractingCell>) =>
|
const handleExtractingCellsChange = (cellsToExtract: Table<ExtractingCell>) =>
|
||||||
onOptionsChange({ ...options, cellsToExtract } as GoogleSheetsOptions)
|
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) {
|
switch (options.action) {
|
||||||
case GoogleSheetsAction.INSERT_ROW:
|
case GoogleSheetsAction.INSERT_ROW:
|
||||||
return (
|
return (
|
||||||
<UpdateCellList
|
<TableList<Cell>
|
||||||
initialCells={options.cellsToInsert}
|
initialItems={options.cellsToInsert}
|
||||||
sheet={sheet}
|
onItemsChange={handleInsertColumnsChange}
|
||||||
onCellsChange={handleInsertColumnsChange}
|
Item={UpdatingCellItem}
|
||||||
|
addLabel="Add a value"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case GoogleSheetsAction.UPDATE_ROW:
|
case GoogleSheetsAction.UPDATE_ROW:
|
||||||
@ -146,15 +160,17 @@ const ActionOptions = ({
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Text>Row to select</Text>
|
<Text>Row to select</Text>
|
||||||
<CellWithValueStack
|
<CellWithValueStack
|
||||||
cell={options.referenceCell ?? {}}
|
id={'reference'}
|
||||||
columns={sheet.columns}
|
columns={sheet.columns}
|
||||||
onCellChange={handleReferenceCellChange}
|
item={options.referenceCell ?? {}}
|
||||||
|
onItemChange={handleReferenceCellChange}
|
||||||
/>
|
/>
|
||||||
<Text>Cells to update</Text>
|
<Text>Cells to update</Text>
|
||||||
<UpdateCellList
|
<TableList<Cell>
|
||||||
initialCells={options.cellsToUpsert}
|
initialItems={options.cellsToUpsert}
|
||||||
sheet={sheet}
|
onItemsChange={handleUpsertColumnsChange}
|
||||||
onCellsChange={handleUpsertColumnsChange}
|
Item={UpdatingCellItem}
|
||||||
|
addLabel="Add a value"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
@ -163,15 +179,17 @@ const ActionOptions = ({
|
|||||||
<Stack>
|
<Stack>
|
||||||
<Text>Row to select</Text>
|
<Text>Row to select</Text>
|
||||||
<CellWithValueStack
|
<CellWithValueStack
|
||||||
cell={options.referenceCell ?? {}}
|
id={'reference'}
|
||||||
columns={sheet.columns}
|
columns={sheet.columns}
|
||||||
onCellChange={handleReferenceCellChange}
|
item={options.referenceCell ?? {}}
|
||||||
|
onItemChange={handleReferenceCellChange}
|
||||||
/>
|
/>
|
||||||
<Text>Cells to extract</Text>
|
<Text>Cells to extract</Text>
|
||||||
<ExtractCellList
|
<TableList<ExtractingCell>
|
||||||
initialCells={options.cellsToExtract}
|
initialItems={options.cellsToExtract}
|
||||||
sheet={sheet}
|
onItemsChange={handleExtractingCellsChange}
|
||||||
onCellsChange={handleExtractingCellsChange}
|
Item={ExtractingCellItem}
|
||||||
|
addLabel="Add a value"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,7 @@ export const SheetsDropdown = ({
|
|||||||
<SearchableDropdown
|
<SearchableDropdown
|
||||||
selectedItem={currentSheet?.name}
|
selectedItem={currentSheet?.name}
|
||||||
items={(sheets ?? []).map((s) => s.name)}
|
items={(sheets ?? []).map((s) => s.name)}
|
||||||
onSelectItem={handleSpreadsheetSelect}
|
onValueChange={handleSpreadsheetSelect}
|
||||||
placeholder={isLoading ? 'Loading...' : 'Select the sheet'}
|
placeholder={isLoading ? 'Loading...' : 'Select the sheet'}
|
||||||
isDisabled={isLoading}
|
isDisabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
@ -27,7 +27,7 @@ export const SpreadsheetsDropdown = ({
|
|||||||
<SearchableDropdown
|
<SearchableDropdown
|
||||||
selectedItem={currentSpreadsheet?.name}
|
selectedItem={currentSpreadsheet?.name}
|
||||||
items={(spreadsheets ?? []).map((s) => s.name)}
|
items={(spreadsheets ?? []).map((s) => s.name)}
|
||||||
onSelectItem={handleSpreadsheetSelect}
|
onValueChange={handleSpreadsheetSelect}
|
||||||
placeholder={isLoading ? 'Loading...' : 'Search for spreadsheet'}
|
placeholder={isLoading ? 'Loading...' : 'Search for spreadsheet'}
|
||||||
isDisabled={isLoading}
|
isDisabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
@ -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<Cell>
|
|
||||||
onCellsChange: (cells: Table<Cell>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = generate()
|
|
||||||
const defaultCells: Table<Cell> = {
|
|
||||||
byId: { [id]: {} },
|
|
||||||
allIds: [id],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UpdateCellList = ({
|
|
||||||
sheet,
|
|
||||||
initialCells,
|
|
||||||
onCellsChange,
|
|
||||||
}: Props) => {
|
|
||||||
const [cells, setCells] = useImmer(initialCells ?? defaultCells)
|
|
||||||
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
|
|
||||||
|
|
||||||
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<Cell>) =>
|
|
||||||
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 (
|
|
||||||
<Stack spacing="4">
|
|
||||||
{cells.allIds.map((cellId) => (
|
|
||||||
<>
|
|
||||||
<Flex
|
|
||||||
pos="relative"
|
|
||||||
onMouseEnter={handleMouseEnter(cellId)}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<CellWithValueStack
|
|
||||||
key={cellId}
|
|
||||||
cell={cells.byId[cellId]}
|
|
||||||
columns={sheet.columns}
|
|
||||||
onCellChange={handleCellChange(cellId)}
|
|
||||||
/>
|
|
||||||
<Fade in={showDeleteId === cellId}>
|
|
||||||
<IconButton
|
|
||||||
icon={<TrashIcon />}
|
|
||||||
aria-label="Remove cell"
|
|
||||||
onClick={deleteCell(cellId)}
|
|
||||||
pos="absolute"
|
|
||||||
left="-15px"
|
|
||||||
top="-15px"
|
|
||||||
size="sm"
|
|
||||||
shadow="md"
|
|
||||||
/>
|
|
||||||
</Fade>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
leftIcon={<PlusIcon />}
|
|
||||||
onClick={createCell}
|
|
||||||
flexShrink={0}
|
|
||||||
colorScheme="blue"
|
|
||||||
>
|
|
||||||
Add a value
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Stack p="4" rounded="md" flex="1" borderWidth="1px">
|
|
||||||
<DropdownList<string>
|
|
||||||
currentItem={cell.column}
|
|
||||||
onItemSelect={handleColumnSelect}
|
|
||||||
items={columns}
|
|
||||||
placeholder="Select a column"
|
|
||||||
/>
|
|
||||||
<InputWithVariableButton
|
|
||||||
initialValue={cell.value ?? ''}
|
|
||||||
onChange={handleValueChange}
|
|
||||||
placeholder="Type a value..."
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
@ -25,8 +25,8 @@ export const NumberInputSettingsBody = ({
|
|||||||
onOptionsChange(removeUndefinedFields({ ...options, max }))
|
onOptionsChange(removeUndefinedFields({ ...options, max }))
|
||||||
const handleStepChange = (step?: number) =>
|
const handleStepChange = (step?: number) =>
|
||||||
onOptionsChange(removeUndefinedFields({ ...options, step }))
|
onOptionsChange(removeUndefinedFields({ ...options, step }))
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -17,8 +17,8 @@ export const PhoneNumberSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
||||||
const handleButtonLabelChange = (button: string) =>
|
const handleButtonLabelChange = (button: string) =>
|
||||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -13,8 +13,8 @@ export const SetVariableSettingsBody = ({
|
|||||||
options,
|
options,
|
||||||
onOptionsChange,
|
onOptionsChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
const handleExpressionChange = (expressionToEvaluate: string) =>
|
const handleExpressionChange = (expressionToEvaluate: string) =>
|
||||||
onOptionsChange({ ...options, expressionToEvaluate })
|
onOptionsChange({ ...options, expressionToEvaluate })
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ export const TextInputSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||||
const handleLongChange = (isLong: boolean) =>
|
const handleLongChange = (isLong: boolean) =>
|
||||||
onOptionsChange({ ...options, isLong })
|
onOptionsChange({ ...options, isLong })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -17,8 +17,8 @@ export const UrlInputSettingsBody = ({
|
|||||||
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
|
||||||
const handleButtonLabelChange = (button: string) =>
|
const handleButtonLabelChange = (button: string) =>
|
||||||
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
|
||||||
const handleVariableChange = (variable: Variable) =>
|
const handleVariableChange = (variable?: Variable) =>
|
||||||
onOptionsChange({ ...options, variableId: variable.id })
|
onOptionsChange({ ...options, variableId: variable?.id })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
|
@ -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<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,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<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}
|
||||||
|
delay={100}
|
||||||
|
initialValue={item.value ?? ''}
|
||||||
|
onChange={handleValueChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
@ -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<Webhook>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WebhookSettings = ({
|
||||||
|
options,
|
||||||
|
webhook,
|
||||||
|
onOptionsChange,
|
||||||
|
onWebhookChange,
|
||||||
|
}: Props) => {
|
||||||
|
const { createWebhook, typebot, save } = useTypebot()
|
||||||
|
const [testResponse, setTestResponse] = useState<string>()
|
||||||
|
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||||
|
|
||||||
|
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<KeyValue>) =>
|
||||||
|
onWebhookChange({ queryParams })
|
||||||
|
|
||||||
|
const handleHeadersChange = (headers: Table<KeyValue>) =>
|
||||||
|
onWebhookChange({ headers })
|
||||||
|
|
||||||
|
const handleBodyChange = (body: string) => onWebhookChange({ body })
|
||||||
|
|
||||||
|
const handleVariablesChange = (variablesForTest: Table<VariableForTest>) =>
|
||||||
|
onOptionsChange({ ...options, variablesForTest })
|
||||||
|
|
||||||
|
const handleResponseMappingChange = (
|
||||||
|
responseVariableMapping: Table<ResponseVariableMapping>
|
||||||
|
) => 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<ResponseVariableMapping>) =>
|
||||||
|
<DataVariableInputs {...props} dataItems={responseKeys} />,
|
||||||
|
[responseKeys]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Stack>
|
||||||
|
<Flex>
|
||||||
|
<DropdownList<HttpMethod>
|
||||||
|
currentItem={webhook?.method ?? HttpMethod.GET}
|
||||||
|
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={webhook?.body ?? ''}
|
||||||
|
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}
|
||||||
|
onItemsChange={handleVariablesChange}
|
||||||
|
Item={VariableForTestInputs}
|
||||||
|
addLabel="Add an entry"
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
<Button onClick={handleTestRequestClick} colorScheme="blue">
|
||||||
|
Test the request
|
||||||
|
</Button>
|
||||||
|
{testResponse && <CodeEditor isReadOnly 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}
|
||||||
|
onItemsChange={handleResponseMappingChange}
|
||||||
|
Item={ResponseMappingInputs}
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { WebhookSettings } from './WebhookSettings'
|
@ -13,7 +13,7 @@ import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
|||||||
import { isBubbleStep, isTextBubbleStep } from 'utils'
|
import { isBubbleStep, isTextBubbleStep } from 'utils'
|
||||||
import { Coordinates } from '@dnd-kit/core/dist/types'
|
import { Coordinates } from '@dnd-kit/core/dist/types'
|
||||||
import { TextEditor } from './TextEditor/TextEditor'
|
import { TextEditor } from './TextEditor/TextEditor'
|
||||||
import { StepNodeContent } from './StepNodeContent'
|
import { StepNodeContent } from './StepNodeContent/StepNodeContent'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||||
import { SettingsPopoverContent } from './SettingsPopoverContent'
|
import { SettingsPopoverContent } from './SettingsPopoverContent'
|
||||||
@ -164,6 +164,7 @@ export const StepNode = ({
|
|||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
bgColor="white"
|
bgColor="white"
|
||||||
align="flex-start"
|
align="flex-start"
|
||||||
|
w="full"
|
||||||
>
|
>
|
||||||
<StepIcon type={step.type} mt="1" />
|
<StepIcon type={step.type} mt="1" />
|
||||||
<StepNodeContent step={step} />
|
<StepNodeContent step={step} />
|
||||||
|
@ -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 (
|
|
||||||
<Flex
|
|
||||||
flexDir={'column'}
|
|
||||||
opacity={step.content.html === '' ? '0.5' : '1'}
|
|
||||||
className="slate-html-container"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html:
|
|
||||||
step.content.html === ''
|
|
||||||
? `<p>Click to edit...</p>`
|
|
||||||
: step.content.html,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case BubbleStepType.IMAGE: {
|
|
||||||
return !step.content?.url ? (
|
|
||||||
<Text color={'gray.500'}>Click to edit...</Text>
|
|
||||||
) : (
|
|
||||||
<Box w="full">
|
|
||||||
<Image
|
|
||||||
src={step.content?.url}
|
|
||||||
alt="Step image"
|
|
||||||
rounded="md"
|
|
||||||
objectFit="cover"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case BubbleStepType.VIDEO: {
|
|
||||||
return <VideoStepNodeContent step={step} />
|
|
||||||
}
|
|
||||||
case InputStepType.TEXT: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.NUMBER: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.EMAIL: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.placeholder ?? 'Type your email...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.URL: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.placeholder ?? 'Type your URL...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.DATE: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.from ?? 'Pick a date...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.PHONE: {
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{step.options?.labels?.placeholder ?? 'Your phone number...'}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case InputStepType.CHOICE: {
|
|
||||||
return <ChoiceItemsList step={step} />
|
|
||||||
}
|
|
||||||
case LogicStepType.SET_VARIABLE: {
|
|
||||||
return <SetVariableNodeContent step={step} />
|
|
||||||
}
|
|
||||||
case LogicStepType.CONDITION: {
|
|
||||||
return <ConditionNodeContent step={step} />
|
|
||||||
}
|
|
||||||
case LogicStepType.REDIRECT: {
|
|
||||||
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
|
||||||
return <Text isTruncated>Redirect to {step.options?.url}</Text>
|
|
||||||
}
|
|
||||||
case IntegrationStepType.GOOGLE_SHEETS: {
|
|
||||||
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
|
||||||
return <Text>{step.options?.action}</Text>
|
|
||||||
}
|
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS: {
|
|
||||||
if (!step.options || !step.options.action)
|
|
||||||
return <Text color={'gray.500'}>Configure...</Text>
|
|
||||||
return <Text>Track "{step.options?.action}"</Text>
|
|
||||||
}
|
|
||||||
case 'start': {
|
|
||||||
return <Text>{step.label}</Text>
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return <Text>No input</Text>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SetVariableNodeContent = ({ step }: { step: SetVariableStep }) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
const variableName =
|
|
||||||
typebot?.variables.byId[step.options?.variableId ?? '']?.name ?? ''
|
|
||||||
const expression = step.options?.expressionToEvaluate ?? ''
|
|
||||||
return (
|
|
||||||
<Text color={'gray.500'}>
|
|
||||||
{variableName === '' && expression === ''
|
|
||||||
? 'Click to edit...'
|
|
||||||
: `${variableName} = ${expression}`}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConditionNodeContent = ({ step }: { step: ConditionStep }) => {
|
|
||||||
const { typebot } = useTypebot()
|
|
||||||
return (
|
|
||||||
<Flex>
|
|
||||||
<Stack color={'gray.500'}>
|
|
||||||
{step.options?.comparisons.allIds.map((comparisonId, idx) => {
|
|
||||||
const comparison = step.options?.comparisons.byId[comparisonId]
|
|
||||||
const variable = typebot?.variables.byId[comparison?.variableId ?? '']
|
|
||||||
return (
|
|
||||||
<HStack key={comparisonId} spacing={1}>
|
|
||||||
{idx > 0 && <Text>{step.options?.logicalOperator ?? ''}</Text>}
|
|
||||||
{variable?.name && (
|
|
||||||
<Tag bgColor="orange.400">{variable.name}</Tag>
|
|
||||||
)}
|
|
||||||
{comparison.comparisonOperator && (
|
|
||||||
<Text>{comparison?.comparisonOperator}</Text>
|
|
||||||
)}
|
|
||||||
{comparison?.value && (
|
|
||||||
<Tag bgColor={'green.400'}>{comparison.value}</Tag>
|
|
||||||
)}
|
|
||||||
</HStack>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Stack>
|
|
||||||
<SourceEndpoint
|
|
||||||
source={{
|
|
||||||
blockId: step.blockId,
|
|
||||||
stepId: step.id,
|
|
||||||
conditionType: 'true',
|
|
||||||
}}
|
|
||||||
pos="absolute"
|
|
||||||
top="7px"
|
|
||||||
right="15px"
|
|
||||||
/>
|
|
||||||
<SourceEndpoint
|
|
||||||
source={{
|
|
||||||
blockId: step.blockId,
|
|
||||||
stepId: step.id,
|
|
||||||
conditionType: 'false',
|
|
||||||
}}
|
|
||||||
pos="absolute"
|
|
||||||
bottom="7px"
|
|
||||||
right="15px"
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const VideoStepNodeContent = ({ step }: { step: VideoBubbleStep }) => {
|
|
||||||
if (!step.content?.url || !step.content.type)
|
|
||||||
return <Text color="gray.500">Click to edit...</Text>
|
|
||||||
switch (step.content.type) {
|
|
||||||
case VideoBubbleContentType.URL:
|
|
||||||
return (
|
|
||||||
<Box w="full" h="120px" pos="relative">
|
|
||||||
<video
|
|
||||||
key={step.content.url}
|
|
||||||
controls
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: '0',
|
|
||||||
borderRadius: '10px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<source src={step.content.url} />
|
|
||||||
</video>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
case VideoBubbleContentType.VIMEO:
|
|
||||||
case VideoBubbleContentType.YOUTUBE: {
|
|
||||||
const baseUrl =
|
|
||||||
step.content.type === VideoBubbleContentType.VIMEO
|
|
||||||
? 'https://player.vimeo.com/video'
|
|
||||||
: 'https://www.youtube.com/embed'
|
|
||||||
return (
|
|
||||||
<Box w="full" h="120px" pos="relative">
|
|
||||||
<iframe
|
|
||||||
src={`${baseUrl}/${step.content.id}`}
|
|
||||||
allowFullScreen
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
left: '0',
|
|
||||||
top: '0',
|
|
||||||
borderRadius: '10px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 (
|
||||||
|
<Flex>
|
||||||
|
<Stack color={'gray.500'}>
|
||||||
|
{step.options?.comparisons.allIds.map((comparisonId, idx) => {
|
||||||
|
const comparison = step.options?.comparisons.byId[comparisonId]
|
||||||
|
const variable = typebot?.variables.byId[comparison?.variableId ?? '']
|
||||||
|
return (
|
||||||
|
<HStack key={comparisonId} spacing={1}>
|
||||||
|
{idx > 0 && <Text>{step.options?.logicalOperator ?? ''}</Text>}
|
||||||
|
{variable?.name && (
|
||||||
|
<Tag bgColor="orange.400">{variable.name}</Tag>
|
||||||
|
)}
|
||||||
|
{comparison.comparisonOperator && (
|
||||||
|
<Text>{comparison?.comparisonOperator}</Text>
|
||||||
|
)}
|
||||||
|
{comparison?.value && (
|
||||||
|
<Tag bgColor={'green.400'}>{comparison.value}</Tag>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Stack>
|
||||||
|
<SourceEndpoint
|
||||||
|
source={{
|
||||||
|
blockId: step.blockId,
|
||||||
|
stepId: step.id,
|
||||||
|
conditionType: 'true',
|
||||||
|
}}
|
||||||
|
pos="absolute"
|
||||||
|
top="7px"
|
||||||
|
right="15px"
|
||||||
|
/>
|
||||||
|
<SourceEndpoint
|
||||||
|
source={{
|
||||||
|
blockId: step.blockId,
|
||||||
|
stepId: step.id,
|
||||||
|
conditionType: 'false',
|
||||||
|
}}
|
||||||
|
pos="absolute"
|
||||||
|
bottom="7px"
|
||||||
|
right="15px"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
@ -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 (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{variableName === '' && expression === ''
|
||||||
|
? 'Click to edit...'
|
||||||
|
: `${variableName} = ${expression}`}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
@ -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 (
|
||||||
|
<Flex
|
||||||
|
flexDir={'column'}
|
||||||
|
opacity={step.content.html === '' ? '0.5' : '1'}
|
||||||
|
className="slate-html-container"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html:
|
||||||
|
step.content.html === ''
|
||||||
|
? `<p>Click to edit...</p>`
|
||||||
|
: step.content.html,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case BubbleStepType.IMAGE: {
|
||||||
|
return !step.content?.url ? (
|
||||||
|
<Text color={'gray.500'}>Click to edit...</Text>
|
||||||
|
) : (
|
||||||
|
<Box w="full">
|
||||||
|
<Image
|
||||||
|
src={step.content?.url}
|
||||||
|
alt="Step image"
|
||||||
|
rounded="md"
|
||||||
|
objectFit="cover"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case BubbleStepType.VIDEO: {
|
||||||
|
return <VideoStepNodeContent step={step} />
|
||||||
|
}
|
||||||
|
case InputStepType.TEXT: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.NUMBER: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.placeholder ?? 'Type your answer...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.EMAIL: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.placeholder ?? 'Type your email...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.URL: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.placeholder ?? 'Type your URL...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.DATE: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.from ?? 'Pick a date...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.PHONE: {
|
||||||
|
return (
|
||||||
|
<Text color={'gray.500'}>
|
||||||
|
{step.options?.labels?.placeholder ?? 'Your phone number...'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case InputStepType.CHOICE: {
|
||||||
|
return <ChoiceItemsList step={step} />
|
||||||
|
}
|
||||||
|
case LogicStepType.SET_VARIABLE: {
|
||||||
|
return <SetVariableNodeContent step={step} />
|
||||||
|
}
|
||||||
|
case LogicStepType.CONDITION: {
|
||||||
|
return <ConditionNodeContent step={step} />
|
||||||
|
}
|
||||||
|
case LogicStepType.REDIRECT: {
|
||||||
|
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
||||||
|
return <Text isTruncated>Redirect to {step.options?.url}</Text>
|
||||||
|
}
|
||||||
|
case IntegrationStepType.GOOGLE_SHEETS: {
|
||||||
|
if (!step.options) return <Text color={'gray.500'}>Configure...</Text>
|
||||||
|
return <Text>{step.options?.action}</Text>
|
||||||
|
}
|
||||||
|
case IntegrationStepType.GOOGLE_ANALYTICS: {
|
||||||
|
if (!step.options || !step.options.action)
|
||||||
|
return <Text color={'gray.500'}>Configure...</Text>
|
||||||
|
return <Text>Track "{step.options?.action}"</Text>
|
||||||
|
}
|
||||||
|
case IntegrationStepType.WEBHOOK: {
|
||||||
|
return <WebhookContent step={step} />
|
||||||
|
}
|
||||||
|
case 'start': {
|
||||||
|
return <Text>{step.label}</Text>
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return <Text>No input</Text>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <Text color="gray.500">Click to edit...</Text>
|
||||||
|
switch (step.content.type) {
|
||||||
|
case VideoBubbleContentType.URL:
|
||||||
|
return (
|
||||||
|
<Box w="full" h="120px" pos="relative">
|
||||||
|
<video
|
||||||
|
key={step.content.url}
|
||||||
|
controls
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '0',
|
||||||
|
borderRadius: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<source src={step.content.url} />
|
||||||
|
</video>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
case VideoBubbleContentType.VIMEO:
|
||||||
|
case VideoBubbleContentType.YOUTUBE: {
|
||||||
|
const baseUrl =
|
||||||
|
step.content.type === VideoBubbleContentType.VIMEO
|
||||||
|
? 'https://player.vimeo.com/video'
|
||||||
|
: 'https://www.youtube.com/embed'
|
||||||
|
return (
|
||||||
|
<Box w="full" h="120px" pos="relative">
|
||||||
|
<iframe
|
||||||
|
src={`${baseUrl}/${step.content.id}`}
|
||||||
|
allowFullScreen
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '0',
|
||||||
|
borderRadius: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <Text color="gray.500">Configure...</Text>
|
||||||
|
return (
|
||||||
|
<Text isTruncated pr="6">
|
||||||
|
{webhook.method} {webhook.url}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { StepNodeContent } from './StepNodeContent'
|
@ -1,7 +1,7 @@
|
|||||||
import { StackProps, HStack } from '@chakra-ui/react'
|
import { StackProps, HStack } from '@chakra-ui/react'
|
||||||
import { StartStep, Step } from 'models'
|
import { StartStep, Step } from 'models'
|
||||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||||
import { StepNodeContent } from './StepNodeContent'
|
import { StepNodeContent } from './StepNodeContent/StepNodeContent'
|
||||||
|
|
||||||
export const StepNodeOverlay = ({
|
export const StepNodeOverlay = ({
|
||||||
step,
|
step,
|
||||||
|
@ -94,9 +94,9 @@ export const TextEditor = ({
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVariableSelected = (variable: Variable) => {
|
const handleVariableSelected = (variable?: Variable) => {
|
||||||
setIsVariableDropdownOpen(false)
|
setIsVariableDropdownOpen(false)
|
||||||
if (!rememberedSelection.current) return
|
if (!rememberedSelection.current || !variable) return
|
||||||
Transforms.select(editor, rememberedSelection.current)
|
Transforms.select(editor, rememberedSelection.current)
|
||||||
Transforms.insertText(editor, '{{' + variable.name + '}}')
|
Transforms.insertText(editor, '{{' + variable.name + '}}')
|
||||||
ReactEditor.focus(editor as unknown as ReactEditor)
|
ReactEditor.focus(editor as unknown as ReactEditor)
|
||||||
|
58
apps/builder/components/shared/CodeEditor.tsx
Normal file
58
apps/builder/components/shared/CodeEditor.tsx
Normal file
@ -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<BoxProps, 'onChange'>) => {
|
||||||
|
const editorContainer = useRef<HTMLDivElement | null>(null)
|
||||||
|
const editorView = useRef<EditorView | null>(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 (
|
||||||
|
<Box ref={editorContainer} h="200px" data-testid="code-editor" {...props} />
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Flex,
|
||||||
HStack,
|
HStack,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
@ -6,6 +7,7 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
|
Tooltip,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { UserIcon } from 'assets/icons'
|
import { UserIcon } from 'assets/icons'
|
||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
@ -29,12 +31,12 @@ export const InputWithVariableButton = ({
|
|||||||
const [carretPosition, setCarretPosition] = useState<number>(0)
|
const [carretPosition, setCarretPosition] = useState<number>(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(debouncedValue)
|
if (debouncedValue !== initialValue) onChange(debouncedValue)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [debouncedValue])
|
}, [debouncedValue])
|
||||||
|
|
||||||
const handleVariableSelected = (variable: Variable) => {
|
const handleVariableSelected = (variable?: Variable) => {
|
||||||
if (!inputRef.current) return
|
if (!inputRef.current || !variable) return
|
||||||
const cursorPosition = carretPosition
|
const cursorPosition = carretPosition
|
||||||
const textBeforeCursorPosition = inputRef.current.value.substring(
|
const textBeforeCursorPosition = inputRef.current.value.substring(
|
||||||
0,
|
0,
|
||||||
@ -67,7 +69,7 @@ export const InputWithVariableButton = ({
|
|||||||
setValue(e.target.value)
|
setValue(e.target.value)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack>
|
<HStack spacing={0}>
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
@ -79,12 +81,15 @@ export const InputWithVariableButton = ({
|
|||||||
/>
|
/>
|
||||||
<Popover matchWidth isLazy>
|
<Popover matchWidth isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<IconButton
|
<Flex>
|
||||||
aria-label="Insert a variable"
|
<Tooltip label="Insert a variable">
|
||||||
icon={<UserIcon />}
|
<IconButton
|
||||||
pos="relative"
|
aria-label="Insert a variable"
|
||||||
ml="2"
|
icon={<UserIcon />}
|
||||||
/>
|
pos="relative"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent w="full">
|
<PopoverContent w="full">
|
||||||
<VariableSearchInput
|
<VariableSearchInput
|
||||||
|
@ -11,20 +11,23 @@ import {
|
|||||||
InputProps,
|
InputProps,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useState, useRef, useEffect, ChangeEvent } from 'react'
|
import { useState, useRef, useEffect, ChangeEvent } from 'react'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedItem?: string
|
selectedItem?: string
|
||||||
items: string[]
|
items: string[]
|
||||||
onSelectItem: (value: string) => void
|
onValueChange?: (value: string) => void
|
||||||
} & InputProps
|
} & InputProps
|
||||||
|
|
||||||
export const SearchableDropdown = ({
|
export const SearchableDropdown = ({
|
||||||
selectedItem,
|
selectedItem,
|
||||||
items,
|
items,
|
||||||
onSelectItem,
|
onValueChange,
|
||||||
...inputProps
|
...inputProps
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { onOpen, onClose, isOpen } = useDisclosure()
|
const { onOpen, onClose, isOpen } = useDisclosure()
|
||||||
const [inputValue, setInputValue] = useState(selectedItem)
|
const [inputValue, setInputValue] = useState(selectedItem ?? '')
|
||||||
|
const [debouncedInputValue] = useDebounce(inputValue, 200)
|
||||||
const [filteredItems, setFilteredItems] = useState([
|
const [filteredItems, setFilteredItems] = useState([
|
||||||
...items
|
...items
|
||||||
.filter((item) =>
|
.filter((item) =>
|
||||||
@ -52,6 +55,13 @@ export const SearchableDropdown = ({
|
|||||||
handler: onClose,
|
handler: onClose,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onValueChange &&
|
||||||
|
debouncedInputValue !== selectedItem &&
|
||||||
|
onValueChange(debouncedInputValue)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [debouncedInputValue])
|
||||||
|
|
||||||
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputValue(e.target.value)
|
setInputValue(e.target.value)
|
||||||
if (e.target.value === '') {
|
if (e.target.value === '') {
|
||||||
@ -69,7 +79,6 @@ export const SearchableDropdown = ({
|
|||||||
|
|
||||||
const handleItemClick = (item: string) => () => {
|
const handleItemClick = (item: string) => () => {
|
||||||
setInputValue(item)
|
setInputValue(item)
|
||||||
onSelectItem(item)
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
114
apps/builder/components/shared/TableList.tsx
Normal file
114
apps/builder/components/shared/TableList.tsx
Normal file
@ -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<T> = {
|
||||||
|
id: string
|
||||||
|
item: T
|
||||||
|
onItemChange: (item: T) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props<T> = {
|
||||||
|
initialItems?: Table<T>
|
||||||
|
onItemsChange: (items: Table<T>) => void
|
||||||
|
addLabel?: string
|
||||||
|
Item: (props: TableListItemProps<T>) => JSX.Element
|
||||||
|
ComponentBetweenItems?: (props: unknown) => JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TableList = <T,>({
|
||||||
|
initialItems,
|
||||||
|
onItemsChange,
|
||||||
|
addLabel = 'Add',
|
||||||
|
Item,
|
||||||
|
ComponentBetweenItems = () => <></>,
|
||||||
|
}: Props<T>) => {
|
||||||
|
const [items, setItems] = useImmer(initialItems ?? { byId: {}, allIds: [] })
|
||||||
|
const [showDeleteId, setShowDeleteId] = useState<string | undefined>()
|
||||||
|
|
||||||
|
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<T>
|
||||||
|
items.allIds.push(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateItem = (itemId: string, updates: Partial<T>) =>
|
||||||
|
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 (
|
||||||
|
<Stack spacing="4">
|
||||||
|
{items.allIds.map((itemId, idx) => (
|
||||||
|
<Box key={itemId}>
|
||||||
|
{idx !== 0 && <ComponentBetweenItems />}
|
||||||
|
<Flex
|
||||||
|
pos="relative"
|
||||||
|
onMouseEnter={handleMouseEnter(itemId)}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<Item
|
||||||
|
id={itemId}
|
||||||
|
item={items.byId[itemId]}
|
||||||
|
onItemChange={handleCellChange(itemId)}
|
||||||
|
/>
|
||||||
|
<Fade in={showDeleteId === itemId}>
|
||||||
|
<IconButton
|
||||||
|
icon={<TrashIcon />}
|
||||||
|
aria-label="Remove cell"
|
||||||
|
onClick={deleteItem(itemId)}
|
||||||
|
pos="absolute"
|
||||||
|
left="-15px"
|
||||||
|
top="-15px"
|
||||||
|
size="sm"
|
||||||
|
shadow="md"
|
||||||
|
/>
|
||||||
|
</Fade>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
leftIcon={<PlusIcon />}
|
||||||
|
onClick={createItem}
|
||||||
|
flexShrink={0}
|
||||||
|
colorScheme="blue"
|
||||||
|
>
|
||||||
|
{addLabel}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
@ -15,11 +15,14 @@ import { useTypebot } from 'contexts/TypebotContext'
|
|||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import React, { useState, useRef, ChangeEvent, useMemo, useEffect } from 'react'
|
import React, { useState, useRef, ChangeEvent, useMemo, useEffect } from 'react'
|
||||||
import { generate } from 'short-uuid'
|
import { generate } from 'short-uuid'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
import { isDefined } from 'utils'
|
import { isDefined } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialVariableId?: string
|
initialVariableId?: string
|
||||||
onSelectVariable: (variable: Pick<Variable, 'id' | 'name'>) => void
|
onSelectVariable: (
|
||||||
|
variable: Pick<Variable, 'id' | 'name'> | undefined
|
||||||
|
) => void
|
||||||
isDefaultOpen?: boolean
|
isDefaultOpen?: boolean
|
||||||
} & InputProps
|
} & InputProps
|
||||||
|
|
||||||
@ -39,6 +42,7 @@ export const VariableSearchInput = ({
|
|||||||
const [inputValue, setInputValue] = useState(
|
const [inputValue, setInputValue] = useState(
|
||||||
typebot?.variables.byId[initialVariableId ?? '']?.name ?? ''
|
typebot?.variables.byId[initialVariableId ?? '']?.name ?? ''
|
||||||
)
|
)
|
||||||
|
const [debouncedInputValue] = useDebounce(inputValue, 200)
|
||||||
const [filteredItems, setFilteredItems] = useState<Variable[]>(variables)
|
const [filteredItems, setFilteredItems] = useState<Variable[]>(variables)
|
||||||
const dropdownRef = useRef(null)
|
const dropdownRef = useRef(null)
|
||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
@ -53,11 +57,18 @@ export const VariableSearchInput = ({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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<HTMLInputElement>) => {
|
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputValue(e.target.value)
|
setInputValue(e.target.value)
|
||||||
onOpen()
|
onOpen()
|
||||||
if (e.target.value === '') {
|
if (e.target.value === '') {
|
||||||
setFilteredItems([...variables.slice(0, 50)])
|
setFilteredItems([...variables.slice(0, 50)])
|
||||||
|
onSelectVariable(undefined)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setFilteredItems([
|
setFilteredItems([
|
||||||
|
@ -27,18 +27,19 @@ export const FontSelector = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFontSelected = (nextFont: string) => {
|
||||||
|
if (nextFont == currentFont) return
|
||||||
|
setCurrentFont(nextFont)
|
||||||
|
onSelectFont(nextFont)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Text>Font</Text>
|
<Text>Font</Text>
|
||||||
<SearchableDropdown
|
<SearchableDropdown
|
||||||
selectedItem={activeFont}
|
selectedItem={activeFont}
|
||||||
items={googleFonts}
|
items={googleFonts}
|
||||||
onSelectItem={(nextFont) => {
|
onValueChange={handleFontSelected}
|
||||||
if (nextFont !== currentFont) {
|
|
||||||
setCurrentFont(nextFont)
|
|
||||||
onSelectFont(nextFont)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
@ -29,6 +29,7 @@ import { stepsAction, StepsActions } from './actions/steps'
|
|||||||
import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
|
import { choiceItemsAction, ChoiceItemsActions } from './actions/choiceItems'
|
||||||
import { variablesAction, VariablesActions } from './actions/variables'
|
import { variablesAction, VariablesActions } from './actions/variables'
|
||||||
import { edgesAction, EdgesActions } from './actions/edges'
|
import { edgesAction, EdgesActions } from './actions/edges'
|
||||||
|
import { webhooksAction, WebhooksAction } from './actions/webhooks'
|
||||||
|
|
||||||
type UpdateTypebotPayload = Partial<{
|
type UpdateTypebotPayload = Partial<{
|
||||||
theme: Theme
|
theme: Theme
|
||||||
@ -52,7 +53,8 @@ const typebotContext = createContext<
|
|||||||
StepsActions &
|
StepsActions &
|
||||||
ChoiceItemsActions &
|
ChoiceItemsActions &
|
||||||
VariablesActions &
|
VariablesActions &
|
||||||
EdgesActions
|
EdgesActions &
|
||||||
|
WebhooksAction
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
>({})
|
>({})
|
||||||
@ -78,9 +80,11 @@ export const TypebotContext = ({
|
|||||||
description: error.message,
|
description: error.message,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const [localTypebot, setLocalTypebot] = useImmer<Typebot | undefined>(
|
const [localTypebot, setLocalTypebot] = useImmer<Typebot | undefined>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
const [localPublishedTypebot, setLocalPublishedTypebot] =
|
const [localPublishedTypebot, setLocalPublishedTypebot] =
|
||||||
useState<PublicTypebot>()
|
useState<PublicTypebot>()
|
||||||
const [isSavingLoading, setIsSavingLoading] = useState(false)
|
const [isSavingLoading, setIsSavingLoading] = useState(false)
|
||||||
@ -214,6 +218,7 @@ export const TypebotContext = ({
|
|||||||
...choiceItemsAction(setLocalTypebot as Updater<Typebot>),
|
...choiceItemsAction(setLocalTypebot as Updater<Typebot>),
|
||||||
...variablesAction(setLocalTypebot as Updater<Typebot>),
|
...variablesAction(setLocalTypebot as Updater<Typebot>),
|
||||||
...edgesAction(setLocalTypebot as Updater<Typebot>),
|
...edgesAction(setLocalTypebot as Updater<Typebot>),
|
||||||
|
...webhooksAction(setLocalTypebot as Updater<Typebot>),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -10,8 +10,9 @@ import { Updater } from 'use-immer'
|
|||||||
import { removeEmptyBlocks } from './blocks'
|
import { removeEmptyBlocks } from './blocks'
|
||||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||||
import { createChoiceItemDraft, deleteChoiceItemDraft } from './choiceItems'
|
import { createChoiceItemDraft, deleteChoiceItemDraft } from './choiceItems'
|
||||||
import { isChoiceInput } from 'utils'
|
import { isChoiceInput, isWebhookStep } from 'utils'
|
||||||
import { deleteEdgeDraft } from './edges'
|
import { deleteEdgeDraft } from './edges'
|
||||||
|
import { deleteWebhookDraft } from './webhooks'
|
||||||
|
|
||||||
export type StepsActions = {
|
export type StepsActions = {
|
||||||
createStep: (
|
createStep: (
|
||||||
@ -51,6 +52,8 @@ export const stepsAction = (setTypebot: Updater<Typebot>): StepsActions => ({
|
|||||||
setTypebot((typebot) => {
|
setTypebot((typebot) => {
|
||||||
const step = typebot.steps.byId[stepId]
|
const step = typebot.steps.byId[stepId]
|
||||||
if (isChoiceInput(step)) deleteChoiceItemsInsideStep(typebot, step)
|
if (isChoiceInput(step)) deleteChoiceItemsInsideStep(typebot, step)
|
||||||
|
if (isWebhookStep(step))
|
||||||
|
deleteWebhookDraft(step.options?.webhookId)(typebot)
|
||||||
deleteAssociatedEdges(typebot, stepId)
|
deleteAssociatedEdges(typebot, stepId)
|
||||||
removeStepIdFromBlock(typebot, stepId)
|
removeStepIdFromBlock(typebot, stepId)
|
||||||
deleteStepDraft(typebot, stepId)
|
deleteStepDraft(typebot, stepId)
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { Typebot, Variable } from 'models'
|
import { Typebot, Variable } from 'models'
|
||||||
import { Updater } from 'use-immer'
|
import { Updater } from 'use-immer'
|
||||||
import { WritableDraft } from 'immer/dist/types/types-external'
|
import { WritableDraft } from 'immer/dist/types/types-external'
|
||||||
import { generate } from 'short-uuid'
|
|
||||||
|
|
||||||
export type VariablesActions = {
|
export type VariablesActions = {
|
||||||
createVariable: (variable: Omit<Variable, 'id'> | Variable) => void
|
createVariable: (variable: Variable) => void
|
||||||
updateVariable: (
|
updateVariable: (
|
||||||
variableId: string,
|
variableId: string,
|
||||||
updates: Partial<Omit<Variable, 'id'>>
|
updates: Partial<Omit<Variable, 'id'>>
|
||||||
@ -15,10 +14,10 @@ export type VariablesActions = {
|
|||||||
export const variablesAction = (
|
export const variablesAction = (
|
||||||
setTypebot: Updater<Typebot>
|
setTypebot: Updater<Typebot>
|
||||||
): VariablesActions => ({
|
): VariablesActions => ({
|
||||||
createVariable: (variable: Omit<Variable, 'id'> | Variable) => {
|
createVariable: (newVariable: Variable) => {
|
||||||
setTypebot((typebot) => {
|
setTypebot((typebot) => {
|
||||||
const id = createVariableDraft(typebot, variable)
|
typebot.variables.byId[newVariable.id] = newVariable
|
||||||
return id
|
typebot.variables.allIds.push(newVariable.id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
updateVariable: (
|
updateVariable: (
|
||||||
@ -46,15 +45,3 @@ export const deleteVariableDraft = (
|
|||||||
const index = typebot.variables.allIds.indexOf(variableId)
|
const index = typebot.variables.allIds.indexOf(variableId)
|
||||||
if (index !== -1) typebot.variables.allIds.splice(index, 1)
|
if (index !== -1) typebot.variables.allIds.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createVariableDraft = (
|
|
||||||
typebot: WritableDraft<Typebot>,
|
|
||||||
variable: Omit<Variable, 'id'> | Variable
|
|
||||||
) => {
|
|
||||||
const newVariable = {
|
|
||||||
...variable,
|
|
||||||
id: 'id' in variable ? variable.id : generate(),
|
|
||||||
}
|
|
||||||
typebot.variables.byId[newVariable.id] = newVariable
|
|
||||||
typebot.variables.allIds.push(newVariable.id)
|
|
||||||
}
|
|
||||||
|
41
apps/builder/contexts/TypebotContext/actions/webhooks.ts
Normal file
41
apps/builder/contexts/TypebotContext/actions/webhooks.ts
Normal file
@ -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<Omit<Webhook, 'id'>>
|
||||||
|
) => void
|
||||||
|
deleteWebhook: (variableId: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const webhooksAction = (
|
||||||
|
setTypebot: Updater<Typebot>
|
||||||
|
): WebhooksAction => ({
|
||||||
|
createWebhook: (newWebhook: Webhook) => {
|
||||||
|
setTypebot((typebot) => {
|
||||||
|
typebot.webhooks.byId[newWebhook.id] = newWebhook
|
||||||
|
typebot.webhooks.allIds.push(newWebhook.id)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateWebhook: (webhookId: string, updates: Partial<Omit<Webhook, 'id'>>) =>
|
||||||
|
setTypebot((typebot) => {
|
||||||
|
typebot.webhooks.byId[webhookId] = {
|
||||||
|
...typebot.webhooks.byId[webhookId],
|
||||||
|
...updates,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deleteWebhook: (webhookId: string) => {
|
||||||
|
setTypebot(deleteWebhookDraft(webhookId))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteWebhookDraft =
|
||||||
|
(webhookId?: string) => (typebot: WritableDraft<Typebot>) => {
|
||||||
|
if (!webhookId) return
|
||||||
|
delete typebot.webhooks.byId[webhookId]
|
||||||
|
const index = typebot.webhooks.allIds.indexOf(webhookId)
|
||||||
|
if (index !== -1) typebot.webhooks.allIds.splice(index, 1)
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckylsz8yy0335z31amvq0jwtt",
|
"ownerId": "ckylsz8yy0335z31amvq0jwtt",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"j24wz82YG3rjXMgrmCiTLy": {
|
"j24wz82YG3rjXMgrmCiTLy": {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckyltekzq0533z31ad8opmacz",
|
"ownerId": "ckyltekzq0533z31ad8opmacz",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"kPupUcEn7TcBGKHUpgK2Q5": {
|
"kPupUcEn7TcBGKHUpgK2Q5": {
|
||||||
|
150
apps/builder/cypress/fixtures/typebots/integrations/webhook.json
Normal file
150
apps/builder/cypress/fixtures/typebots/integrations/webhook.json
Normal file
@ -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": "<div>Ready?</div>",
|
||||||
|
"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
|
||||||
|
}
|
@ -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": "<div>Ready?</div>",
|
||||||
|
"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": "<div>His name is {{Name}}</div>",
|
||||||
|
"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
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 283 KiB |
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckylsbdf60088z31ayqytest6",
|
"ownerId": "ckylsbdf60088z31ayqytest6",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"2x83WHtEBkiv7pk7KgqJwZ": {
|
"2x83WHtEBkiv7pk7KgqJwZ": {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckymkff1100362z1a85juyoa8",
|
"ownerId": "ckymkff1100362z1a85juyoa8",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"bsVJfEW7EZrUnAi9s5ev17": {
|
"bsVJfEW7EZrUnAi9s5ev17": {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckylrpsmt0006fn1ah956d0z1",
|
"ownerId": "ckylrpsmt0006fn1ah956d0z1",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"kmUzhRFzSKjkaipYNcku9S": {
|
"kmUzhRFzSKjkaipYNcku9S": {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"ownerId": "ckylsr4fi0220z31apbinpy9d",
|
"ownerId": "ckylsr4fi0220z31apbinpy9d",
|
||||||
"publishedTypebotId": null,
|
"publishedTypebotId": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
|
"webhooks": { "byId": {}, "allIds": [] },
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"byId": {
|
"byId": {
|
||||||
"weeBMMXxNKwEonMfDX8Z5k": {
|
"weeBMMXxNKwEonMfDX8Z5k": {
|
||||||
|
@ -75,6 +75,7 @@ export const parseTestTypebot = ({
|
|||||||
publishedTypebotId: null,
|
publishedTypebotId: null,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
variables: { byId: {}, allIds: [] },
|
variables: { byId: {}, allIds: [] },
|
||||||
|
webhooks: { byId: {}, allIds: [] },
|
||||||
edges: {
|
edges: {
|
||||||
byId: {
|
byId: {
|
||||||
edge1: {
|
edge1: {
|
||||||
|
@ -52,7 +52,7 @@ describe('Image bubbles', () => {
|
|||||||
.should('include', unsplashImageSrc)
|
.should('include', unsplashImageSrc)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.only('should import giphy gifs correctly', () => {
|
it('should import giphy gifs correctly', () => {
|
||||||
cy.findByRole('button', { name: 'Giphy' }).click()
|
cy.findByRole('button', { name: 'Giphy' }).click()
|
||||||
cy.findAllByRole('img').eq(3).click()
|
cy.findAllByRole('img').eq(3).click()
|
||||||
cy.findAllByRole('img')
|
cy.findAllByRole('img')
|
||||||
|
96
apps/builder/cypress/tests/integrations/webhooks.ts
Normal file
96
apps/builder/cypress/tests/integrations/webhooks.ts
Normal file
@ -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)
|
||||||
|
}
|
@ -22,14 +22,14 @@ describe('Condition step', () => {
|
|||||||
|
|
||||||
cy.findByTestId('variables-input').click()
|
cy.findByTestId('variables-input').click()
|
||||||
cy.findByRole('menuitem', { name: 'Age' }).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.findByRole('menuitem', { name: 'Greater than' }).click()
|
||||||
cy.findByPlaceholderText('Type a value...').type('80')
|
cy.findByPlaceholderText('Type a value...').type('80')
|
||||||
|
|
||||||
cy.findByRole('button', { name: 'Add a comparison' }).click()
|
cy.findByRole('button', { name: 'Add a comparison' }).click()
|
||||||
cy.findAllByTestId('variables-input').last().click()
|
cy.findAllByTestId('variables-input').last().click()
|
||||||
cy.findByRole('menuitem', { name: 'Age' }).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.findByRole('menuitem', { name: 'Less than' }).click()
|
||||||
cy.findAllByPlaceholderText('Type a value...').last().type('100')
|
cy.findAllByPlaceholderText('Type a value...').last().type('100')
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ describe('Condition step', () => {
|
|||||||
|
|
||||||
cy.findByTestId('variables-input').click()
|
cy.findByTestId('variables-input').click()
|
||||||
cy.findByRole('menuitem', { name: 'Age' }).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.findByRole('menuitem', { name: 'Greater than' }).click()
|
||||||
cy.findByPlaceholderText('Type a value...').type('20')
|
cy.findByPlaceholderText('Type a value...').type('20')
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/css-reset": "^1.1.1",
|
"@chakra-ui/css-reset": "^1.1.1",
|
||||||
"@chakra-ui/react": "^1.7.4",
|
"@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/core": "^4.0.3",
|
||||||
"@dnd-kit/sortable": "^5.1.0",
|
"@dnd-kit/sortable": "^5.1.0",
|
||||||
"@emotion/react": "^11.7.1",
|
"@emotion/react": "^11.7.1",
|
||||||
@ -36,6 +39,7 @@
|
|||||||
"framer-motion": "^4",
|
"framer-motion": "^4",
|
||||||
"google-auth-library": "^7.11.0",
|
"google-auth-library": "^7.11.0",
|
||||||
"google-spreadsheet": "^3.2.0",
|
"google-spreadsheet": "^3.2.0",
|
||||||
|
"got": "^12.0.1",
|
||||||
"htmlparser2": "^7.2.0",
|
"htmlparser2": "^7.2.0",
|
||||||
"immer": "^9.0.7",
|
"immer": "^9.0.7",
|
||||||
"js-video-url-parser": "^0.5.1",
|
"js-video-url-parser": "^0.5.1",
|
||||||
|
@ -8,6 +8,7 @@ import 'assets/styles/routerProgressBar.css'
|
|||||||
import 'assets/styles/plate.css'
|
import 'assets/styles/plate.css'
|
||||||
import 'focus-visible/dist/focus-visible'
|
import 'focus-visible/dist/focus-visible'
|
||||||
import 'assets/styles/submissionsTable.css'
|
import 'assets/styles/submissionsTable.css'
|
||||||
|
import 'assets/styles/codeMirror.css'
|
||||||
|
|
||||||
const App = ({ Component, pageProps }: AppProps) => {
|
const App = ({ Component, pageProps }: AppProps) => {
|
||||||
useRouterProgressBar()
|
useRouterProgressBar()
|
||||||
|
26
apps/builder/pages/api/mock/webhook.ts
Normal file
26
apps/builder/pages/api/mock/webhook.ts
Normal file
@ -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
|
@ -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<Variable>
|
||||||
|
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<Variable>
|
||||||
|
): Promise<WebhookResponse> => {
|
||||||
|
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<KeyValue> | undefined,
|
||||||
|
variables: Table<Variable>
|
||||||
|
) => {
|
||||||
|
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
|
@ -2,6 +2,7 @@ import { sendRequest } from 'utils'
|
|||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from './utils'
|
import { fetcher } from './utils'
|
||||||
|
import { Table, Variable, VariableForTest, WebhookResponse } from 'models'
|
||||||
|
|
||||||
export const getGoogleSheetsConsentScreenUrl = (
|
export const getGoogleSheetsConsentScreenUrl = (
|
||||||
redirectUrl: string,
|
redirectUrl: string,
|
||||||
@ -64,3 +65,68 @@ export const useSheets = ({
|
|||||||
mutate,
|
mutate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const executeWebhook = (
|
||||||
|
typebotId: string,
|
||||||
|
webhookId: string,
|
||||||
|
variables: Table<Variable>
|
||||||
|
) =>
|
||||||
|
sendRequest<WebhookResponse>({
|
||||||
|
url: `/api/typebots/${typebotId}/webhooks/${webhookId}/execute`,
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
variables,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const convertVariableForTestToVariables = (
|
||||||
|
variablesForTest: Table<VariableForTest> | undefined,
|
||||||
|
variables: Table<Variable>
|
||||||
|
): Table<Variable> => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -198,7 +198,10 @@ export const parseNewTypebot = ({
|
|||||||
ownerId: string
|
ownerId: string
|
||||||
folderId: string | null
|
folderId: string | null
|
||||||
name: string
|
name: string
|
||||||
}): Partial<Typebot> => {
|
}): Omit<
|
||||||
|
Typebot,
|
||||||
|
'createdAt' | 'updatedAt' | 'id' | 'publishedTypebotId' | 'publicId'
|
||||||
|
> => {
|
||||||
const startBlockId = shortId.generate()
|
const startBlockId = shortId.generate()
|
||||||
const startStepId = shortId.generate()
|
const startStepId = shortId.generate()
|
||||||
const startStep: StartStep = {
|
const startStep: StartStep = {
|
||||||
@ -235,6 +238,7 @@ export const parseNewTypebot = ({
|
|||||||
choiceItems: { byId: {}, allIds: [] },
|
choiceItems: { byId: {}, allIds: [] },
|
||||||
variables: { byId: {}, allIds: [] },
|
variables: { byId: {}, allIds: [] },
|
||||||
edges: { byId: {}, allIds: [] },
|
edges: { byId: {}, allIds: [] },
|
||||||
|
webhooks: { byId: {}, allIds: [] },
|
||||||
theme,
|
theme,
|
||||||
settings,
|
settings,
|
||||||
}
|
}
|
||||||
|
@ -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' }],
|
output: [{ file: 'dist/index.d.ts', format: 'esm' }],
|
||||||
plugins: [dts()],
|
plugins: [dts()],
|
||||||
external: [/\.css$/],
|
external: [/\.css$/],
|
||||||
|
@ -55,6 +55,7 @@ export const ChatBlock = ({
|
|||||||
}
|
}
|
||||||
if (isIntegrationStep(currentStep)) {
|
if (isIntegrationStep(currentStep)) {
|
||||||
const nextEdgeId = await executeIntegration(
|
const nextEdgeId = await executeIntegration(
|
||||||
|
typebot.typebotId,
|
||||||
currentStep,
|
currentStep,
|
||||||
typebot.variables,
|
typebot.variables,
|
||||||
updateVariableValue
|
updateVariableValue
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export * from './components/TypebotViewer'
|
export * from './components/TypebotViewer'
|
||||||
|
export { parseVariables } from './services/variable'
|
||||||
export * from 'util'
|
export * from 'util'
|
||||||
|
@ -10,13 +10,18 @@ import {
|
|||||||
Cell,
|
Cell,
|
||||||
GoogleSheetsGetOptions,
|
GoogleSheetsGetOptions,
|
||||||
GoogleAnalyticsStep,
|
GoogleAnalyticsStep,
|
||||||
|
Webhook,
|
||||||
|
WebhookStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { sendRequest } from 'utils'
|
import { sendRequest } from 'utils'
|
||||||
import { sendGaEvent } from '../../lib/gtag'
|
import { sendGaEvent } from '../../lib/gtag'
|
||||||
import { parseVariables, parseVariablesInObject } from './variable'
|
import { parseVariables, parseVariablesInObject } from './variable'
|
||||||
|
|
||||||
|
const safeEval = eval
|
||||||
|
|
||||||
export const executeIntegration = (
|
export const executeIntegration = (
|
||||||
|
typebotId: string,
|
||||||
step: IntegrationStep,
|
step: IntegrationStep,
|
||||||
variables: Table<Variable>,
|
variables: Table<Variable>,
|
||||||
updateVariableValue: (variableId: string, value: string) => void
|
updateVariableValue: (variableId: string, value: string) => void
|
||||||
@ -26,6 +31,8 @@ export const executeIntegration = (
|
|||||||
return executeGoogleSheetIntegration(step, variables, updateVariableValue)
|
return executeGoogleSheetIntegration(step, variables, updateVariableValue)
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
return executeGoogleAnalyticsIntegration(step, variables)
|
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 }),
|
[cell.column]: parseVariables({ text: cell.value, variables }),
|
||||||
}
|
}
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
|
const executeWebhook = async (
|
||||||
|
typebotId: string,
|
||||||
|
step: WebhookStep,
|
||||||
|
variables: Table<Variable>,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
"main": "./index.tsx",
|
"main": "./index.tsx",
|
||||||
"types": "./index.tsx",
|
"types": "./index.tsx",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prisma": "^3.7.0",
|
"prisma": "^3.8.1",
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^3.7.0"
|
"@prisma/client": "^3.8.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "yarn prisma db push && BROWSER=none yarn prisma studio",
|
"dev": "yarn prisma db push && BROWSER=none yarn prisma studio",
|
||||||
|
@ -110,6 +110,7 @@ model Typebot {
|
|||||||
steps Json
|
steps Json
|
||||||
choiceItems Json
|
choiceItems Json
|
||||||
variables Json
|
variables Json
|
||||||
|
webhooks Json
|
||||||
edges Json
|
edges Json
|
||||||
theme Json
|
theme Json
|
||||||
settings Json
|
settings Json
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import { StepBase } from '.'
|
import { StepBase } from '.'
|
||||||
import { Table } from '../..'
|
import { Table } from '../..'
|
||||||
|
|
||||||
export type IntegrationStep = GoogleSheetsStep | GoogleAnalyticsStep
|
export type IntegrationStep =
|
||||||
|
| GoogleSheetsStep
|
||||||
|
| GoogleAnalyticsStep
|
||||||
|
| WebhookStep
|
||||||
|
|
||||||
export type IntegrationStepOptions =
|
export type IntegrationStepOptions =
|
||||||
| GoogleSheetsOptions
|
| GoogleSheetsOptions
|
||||||
| GoogleAnalyticsOptions
|
| GoogleAnalyticsOptions
|
||||||
|
| WebhookOptions
|
||||||
|
|
||||||
export enum IntegrationStepType {
|
export enum IntegrationStepType {
|
||||||
GOOGLE_SHEETS = 'Google Sheets',
|
GOOGLE_SHEETS = 'Google Sheets',
|
||||||
GOOGLE_ANALYTICS = 'Google Analytics',
|
GOOGLE_ANALYTICS = 'Google Analytics',
|
||||||
|
WEBHOOK = 'Webhook',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GoogleSheetsStep = StepBase & {
|
export type GoogleSheetsStep = StepBase & {
|
||||||
@ -22,6 +27,11 @@ export type GoogleAnalyticsStep = StepBase & {
|
|||||||
options?: GoogleAnalyticsOptions
|
options?: GoogleAnalyticsOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WebhookStep = StepBase & {
|
||||||
|
type: IntegrationStepType.WEBHOOK
|
||||||
|
options?: WebhookOptions
|
||||||
|
}
|
||||||
|
|
||||||
export type GoogleAnalyticsOptions = {
|
export type GoogleAnalyticsOptions = {
|
||||||
trackingId?: string
|
trackingId?: string
|
||||||
category?: string
|
category?: string
|
||||||
@ -66,3 +76,40 @@ export type GoogleSheetsUpdateRowOptions = GoogleSheetsOptionsBase & {
|
|||||||
referenceCell?: Cell
|
referenceCell?: Cell
|
||||||
cellsToUpsert?: Table<Cell>
|
cellsToUpsert?: Table<Cell>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResponseVariableMapping = { bodyPath?: string; variableId?: string }
|
||||||
|
|
||||||
|
export type WebhookOptions = {
|
||||||
|
webhookId?: string
|
||||||
|
variablesForTest?: Table<VariableForTest>
|
||||||
|
responseVariableMapping?: Table<ResponseVariableMapping>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<KeyValue>
|
||||||
|
headers?: Table<KeyValue>
|
||||||
|
body?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WebhookResponse = {
|
||||||
|
statusCode: number
|
||||||
|
data?: unknown
|
||||||
|
}
|
||||||
|
@ -5,16 +5,24 @@ import { Settings } from './settings'
|
|||||||
import { Step } from './steps/steps'
|
import { Step } from './steps/steps'
|
||||||
import { Theme } from './theme'
|
import { Theme } from './theme'
|
||||||
import { Variable } from './variable'
|
import { Variable } from './variable'
|
||||||
|
import { Webhook } from '.'
|
||||||
|
|
||||||
export type Typebot = Omit<
|
export type Typebot = Omit<
|
||||||
TypebotFromPrisma,
|
TypebotFromPrisma,
|
||||||
'blocks' | 'theme' | 'settings' | 'steps' | 'choiceItems' | 'variables'
|
| 'blocks'
|
||||||
|
| 'theme'
|
||||||
|
| 'settings'
|
||||||
|
| 'steps'
|
||||||
|
| 'choiceItems'
|
||||||
|
| 'variables'
|
||||||
|
| 'webhooks'
|
||||||
> & {
|
> & {
|
||||||
blocks: Table<Block>
|
blocks: Table<Block>
|
||||||
steps: Table<Step>
|
steps: Table<Step>
|
||||||
choiceItems: Table<ChoiceItem>
|
choiceItems: Table<ChoiceItem>
|
||||||
variables: Table<Variable>
|
variables: Table<Variable>
|
||||||
edges: Table<Edge>
|
edges: Table<Edge>
|
||||||
|
webhooks: Table<Webhook>
|
||||||
theme: Theme
|
theme: Theme
|
||||||
settings: Settings
|
settings: Settings
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
TextInputStep,
|
TextInputStep,
|
||||||
TextBubbleStep,
|
TextBubbleStep,
|
||||||
|
WebhookStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
|
|
||||||
export const sendRequest = async <ResponseData>({
|
export const sendRequest = async <ResponseData>({
|
||||||
@ -74,3 +75,6 @@ export const isConditionStep = (step: Step): step is ConditionStep =>
|
|||||||
|
|
||||||
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
||||||
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
||||||
|
|
||||||
|
export const isWebhookStep = (step: Step): step is WebhookStep =>
|
||||||
|
step.type === IntegrationStepType.WEBHOOK
|
||||||
|
459
yarn.lock
459
yarn.lock
@ -726,6 +726,221 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@chakra-ui/utils" "1.9.1"
|
"@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":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
version "0.8.0"
|
version "0.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
|
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"
|
"@types/yargs" "^15.0.0"
|
||||||
chalk "^4.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":
|
"@napi-rs/triples@1.0.3":
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c"
|
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"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
|
||||||
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
|
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
|
||||||
|
|
||||||
"@prisma/client@^3.7.0":
|
"@prisma/client@^3.8.1":
|
||||||
version "3.7.0"
|
version "3.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.7.0.tgz#9cafc105f12635c95e9b7e7b18e8fbf52cf3f18a"
|
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.8.1.tgz#c11eda8e84760867552ffde4de7b48fb2cf1e1c0"
|
||||||
integrity sha512-fUJMvBOX5C7JPc0e3CJD6Gbelbu4dMJB4ScYpiht8HMUnRShw20ULOipTopjNtl6ekHQJ4muI7pXlQxWS9nMbw==
|
integrity sha512-NxD1Xbkx1eT1mxSwo1RwZe665mqBETs0VxohuwNfFIxMqcp0g6d4TgugPxwZ4Jb4e5wCu8mQ9quMedhNWIWcZQ==
|
||||||
dependencies:
|
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":
|
"@prisma/engines-version@3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f":
|
||||||
version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
version "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#055f36ac8b06c301332c14963cd0d6c795942c90"
|
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f.tgz#4c8d9744b5e54650a8ba5fde0a711399d6adba24"
|
||||||
integrity sha512-+qx2b+HK7BKF4VCa0LZ/t1QCXsu6SmvhUQyJkOD2aPpmOzket4fEnSKQZSB0i5tl7rwCDsvAiSeK8o7rf+yvwg==
|
integrity sha512-G2JH6yWt6ixGKmsRmVgaQYahfwMopim0u/XLIZUo2o/mZ5jdu7+BL+2V5lZr7XiG1axhyrpvlyqE/c0OgYSl3g==
|
||||||
|
|
||||||
"@prisma/engines@3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f":
|
"@prisma/engines@3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f":
|
||||||
version "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
version "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f.tgz#12f28d5b78519fbd84c89a5bdff457ff5095e7a2"
|
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f.tgz#4479099b99f6a082ce5843ee7208943ccedd127f"
|
||||||
integrity sha512-W549ub5NlgexNhR8EFstA/UwAWq3Zq0w9aNkraqsozVCt2CsX+lK4TK7IW5OZVSnxHwRjrgEAt3r9yPy8nZQRg==
|
integrity sha512-bHYubuItSN/DGYo36aDu7xJiJmK52JOSHs4MK+KbceAtwS20BCWadRgtpQ3iZ2EXfN/B1T0iCXlNraaNwnpU2w==
|
||||||
|
|
||||||
"@reach/alert@0.13.2":
|
"@reach/alert@0.13.2":
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
|
||||||
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
|
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":
|
"@socket.io/component-emitter@~3.0.0":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz#8863915676f837d9dad7b76f50cb500c1e9422e9"
|
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz#8863915676f837d9dad7b76f50cb500c1e9422e9"
|
||||||
integrity sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==
|
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":
|
"@testing-library/cypress@^8.0.2":
|
||||||
version "8.0.2"
|
version "8.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-8.0.2.tgz#b13f0ff2424dec4368b6670dfbfb7e43af8eefc9"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
||||||
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
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@*":
|
"@types/engine.io@*":
|
||||||
version "3.1.7"
|
version "3.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/engine.io/-/engine.io-3.1.7.tgz#86e541a5dc52fb7e97735383564a6ae4cfe2e8f5"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/google-spreadsheet/-/google-spreadsheet-3.1.5.tgz#2bdc6f9f5372551e0506cb6ef3f562adcf44fc2e"
|
||||||
integrity sha512-7N+mDtZ1pmya2RRFPPl4KYc2TRgiqCNBLUZfyrKfER+u751JgCO+C24/LzF70UmUm/zhHUbzRZ5mtfaxekQ1ZQ==
|
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":
|
"@types/is-hotkey@^0.1.1":
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/is-hotkey/-/is-hotkey-0.1.7.tgz#30ec6d4234895230b576728ef77e70a52962f3b3"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
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":
|
"@types/lodash.mergewith@4.6.6":
|
||||||
version "4.6.6"
|
version "4.6.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
|
resolved "https://registry.yarnpkg.com/@types/lodash.mergewith/-/lodash.mergewith-4.6.6.tgz#c4698f5b214a433ff35cb2c75ee6ec7f99d79f10"
|
||||||
@ -1673,6 +1941,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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@*":
|
"@types/scheduler@*":
|
||||||
version "0.16.2"
|
version "0.16.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
|
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"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
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:
|
cachedir@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
|
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"
|
slice-ansi "^3.0.0"
|
||||||
string-width "^4.2.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:
|
clsx@1.1.1, clsx@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
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"
|
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
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:
|
cross-spawn@^6.0.5:
|
||||||
version "6.0.5"
|
version "6.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
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:
|
dependencies:
|
||||||
ms "^2.1.1"
|
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:
|
deep-is@^0.1.3:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
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"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
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:
|
define-properties@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
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"
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
|
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:
|
form-data@~2.3.2:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
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:
|
dependencies:
|
||||||
pump "^3.0.0"
|
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:
|
get-symbol-description@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
|
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"
|
url-template "^2.0.8"
|
||||||
uuid "^8.0.0"
|
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:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
||||||
version "4.2.9"
|
version "4.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
|
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"
|
domutils "^2.8.0"
|
||||||
entities "^3.0.1"
|
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:
|
http-errors@1.6.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
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"
|
jsprim "^2.0.2"
|
||||||
sshpk "^1.14.1"
|
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:
|
https-browserify@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||||
@ -5002,6 +5361,11 @@ json-bigint@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
bignumber.js "^9.0.0"
|
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:
|
json-parse-better-errors@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
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"
|
react-virtual "^2.8.2"
|
||||||
tiny-invariant "^1.2.0"
|
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:
|
language-subtag-registry@~0.3.2:
|
||||||
version "0.3.21"
|
version "0.3.21"
|
||||||
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
|
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:
|
dependencies:
|
||||||
js-tokens "^3.0.0 || ^4.0.0"
|
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:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
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"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
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:
|
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
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-default" "^12.0.1"
|
||||||
"@otplib/preset-v11" "^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:
|
p-finally@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
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"
|
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||||
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
|
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
|
||||||
|
|
||||||
prisma@^3.7.0:
|
prisma@^3.8.1:
|
||||||
version "3.7.0"
|
version "3.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.7.0.tgz#9c73eeb2f16f767fdf523d0f4cc4c749734d62e2"
|
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.8.1.tgz#44395cef7cbb1ea86216cb84ee02f856c08a7873"
|
||||||
integrity sha512-pzgc95msPLcCHqOli7Hnabu/GRfSGSUWl5s2P6N13T/rgMB+NNeKbxCmzQiZT2yLOeLEPivV6YrW1oeQIwJxcg==
|
integrity sha512-Q8zHwS9m70TaD7qI8u+8hTAmiTpK+IpvRYF3Rgb/OeWGQJOMgZCFFvNCiSfoLEQ95wilK7ctW3KOpc9AuYnRUA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines" "3.7.0-31.8746e055198f517658c08a0c426c7eec87f5a85f"
|
"@prisma/engines" "3.8.0-43.34df67547cf5598f5a6cd3eb45f14ee70c3fb86f"
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
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"
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
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:
|
resolve-from@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
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"
|
is-core-module "^2.2.0"
|
||||||
path-parse "^1.0.6"
|
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:
|
restore-cursor@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
|
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"
|
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
|
||||||
integrity sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==
|
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:
|
style-value-types@4.1.4:
|
||||||
version "4.1.4"
|
version "4.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
|
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"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
|
||||||
integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
|
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:
|
unbox-primitive@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
|
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"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
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:
|
warning@^4.0.2, warning@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
|
Reference in New Issue
Block a user