⚡ (webhook) Enable advanced config for Zapier and Make.com
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { defaultWebhookAttributes, MakeComBlock, Webhook } from 'models'
|
||||
import { useEffect } from 'react'
|
||||
import { MakeComBlock } from 'models'
|
||||
import { byId, isNotDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
@ -9,22 +8,9 @@ type Props = {
|
||||
}
|
||||
|
||||
export const MakeComContent = ({ block }: Props) => {
|
||||
const { webhooks, typebot, updateWebhook } = useTypebot()
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot) return
|
||||
if (!webhook) {
|
||||
const { webhookId } = block
|
||||
const newWebhook = {
|
||||
id: webhookId,
|
||||
...defaultWebhookAttributes,
|
||||
typebotId: typebot.id,
|
||||
} as Webhook
|
||||
updateWebhook(webhookId, newWebhook)
|
||||
}
|
||||
}, [block, typebot, updateWebhook, webhook])
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
|
@ -1,31 +1,68 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Button,
|
||||
Input,
|
||||
Link,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { MakeComBlock } from 'models'
|
||||
import React from 'react'
|
||||
import { byId } from 'utils'
|
||||
import { MakeComBlock, Webhook, WebhookOptions } from 'models'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { byId, env } from 'utils'
|
||||
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
const debounceWebhookTimeout = 2000
|
||||
|
||||
type Props = {
|
||||
block: MakeComBlock
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
}
|
||||
|
||||
export const MakeComSettings = ({ block }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
export const MakeComSettings = ({
|
||||
block: { webhookId, id: blockId, options },
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const { webhooks, updateWebhook } = useTypebot()
|
||||
const webhook = webhooks.find(byId(webhookId))
|
||||
|
||||
const [localWebhook, _setLocalWebhook] = useState(webhook)
|
||||
const updateWebhookDebounced = useDebouncedCallback(
|
||||
async (newLocalWebhook) => {
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
},
|
||||
env('E2E_TEST') === 'true' ? 0 : debounceWebhookTimeout
|
||||
)
|
||||
|
||||
const setLocalWebhook = useCallback(
|
||||
(newLocalWebhook: Webhook) => {
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
updateWebhookDebounced(newLocalWebhook)
|
||||
},
|
||||
[updateWebhookDebounced]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!localWebhook ||
|
||||
localWebhook.url ||
|
||||
!webhook?.url ||
|
||||
webhook.url === localWebhook.url
|
||||
)
|
||||
return
|
||||
setLocalWebhook({
|
||||
...localWebhook,
|
||||
url: webhook?.url,
|
||||
})
|
||||
}, [webhook, localWebhook, setLocalWebhook])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
updateWebhookDebounced.flush()
|
||||
},
|
||||
[updateWebhookDebounced]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={webhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{webhook?.url ? (
|
||||
{localWebhook?.url ? (
|
||||
<>Your scenario is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
@ -41,7 +78,15 @@ export const MakeComSettings = ({ block }: Props) => {
|
||||
</Stack>
|
||||
)}
|
||||
</Alert>
|
||||
{webhook?.url && <Input value={webhook?.url} isDisabled />}
|
||||
{localWebhook && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { defaultWebhookAttributes, PabblyConnectBlock, Webhook } from 'models'
|
||||
import { useEffect } from 'react'
|
||||
import { PabblyConnectBlock } from 'models'
|
||||
import { byId, isNotDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
@ -9,22 +8,9 @@ type Props = {
|
||||
}
|
||||
|
||||
export const PabblyConnectContent = ({ block }: Props) => {
|
||||
const { webhooks, typebot, updateWebhook } = useTypebot()
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot) return
|
||||
if (!webhook) {
|
||||
const { webhookId } = block
|
||||
const newWebhook = {
|
||||
id: webhookId,
|
||||
...defaultWebhookAttributes,
|
||||
typebotId: typebot.id,
|
||||
} as Webhook
|
||||
updateWebhook(webhookId, newWebhook)
|
||||
}
|
||||
}, [block, typebot, updateWebhook, webhook])
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
|
@ -0,0 +1,92 @@
|
||||
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { PabblyConnectBlock, Webhook, WebhookOptions } from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { byId, env } from 'utils'
|
||||
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
|
||||
const debounceWebhookTimeout = 2000
|
||||
|
||||
type Props = {
|
||||
block: PabblyConnectBlock
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
}
|
||||
|
||||
export const PabblyConnectSettings = ({
|
||||
block: { webhookId, id: blockId, options },
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const { webhooks, updateWebhook } = useTypebot()
|
||||
|
||||
const [localWebhook, _setLocalWebhook] = useState(
|
||||
webhooks.find(byId(webhookId))
|
||||
)
|
||||
|
||||
const updateWebhookDebounced = useDebouncedCallback(
|
||||
async (newLocalWebhook) => {
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
},
|
||||
env('E2E_TEST') === 'true' ? 0 : debounceWebhookTimeout
|
||||
)
|
||||
|
||||
const setLocalWebhook = (newLocalWebhook: Webhook) => {
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
updateWebhookDebounced(newLocalWebhook)
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
updateWebhookDebounced.flush()
|
||||
},
|
||||
[updateWebhookDebounced]
|
||||
)
|
||||
|
||||
const handleUrlChange = (url: string) =>
|
||||
localWebhook &&
|
||||
setLocalWebhook({
|
||||
...localWebhook,
|
||||
url,
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{localWebhook?.url ? (
|
||||
<>Your scenario is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
<Text>Head up to Pabbly Connect to get the webhook URL:</Text>
|
||||
<Button
|
||||
as={Link}
|
||||
href="https://www.pabbly.com/connect/integrations/typebot/"
|
||||
isExternal
|
||||
colorScheme="blue"
|
||||
>
|
||||
<Text mr="2">Pabbly.com</Text> <ExternalLinkIcon />
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</Alert>
|
||||
<TextInput
|
||||
placeholder="Paste webhook URL..."
|
||||
defaultValue={localWebhook?.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
{localWebhook && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,232 @@
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { TableList, TableListItemProps } from '@/components/TableList'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import {
|
||||
Stack,
|
||||
HStack,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionPanel,
|
||||
Button,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
HttpMethod,
|
||||
KeyValue,
|
||||
VariableForTest,
|
||||
ResponseVariableMapping,
|
||||
WebhookOptions,
|
||||
Webhook,
|
||||
} from 'models'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { executeWebhook } from '../queries/executeWebhookQuery'
|
||||
import { convertVariablesForTestToVariables } from '../utils/convertVariablesForTestToVariables'
|
||||
import { getDeepKeys } from '../utils/getDeepKeys'
|
||||
import {
|
||||
QueryParamsInputs,
|
||||
HeadersInputs,
|
||||
} from './WebhookSettings/KeyValueInputs'
|
||||
import { DataVariableInputs } from './WebhookSettings/ResponseMappingInputs'
|
||||
import { VariableForTestInputs } from './WebhookSettings/VariableForTestInputs'
|
||||
|
||||
type Props = {
|
||||
blockId: string
|
||||
webhook: Webhook
|
||||
options: WebhookOptions
|
||||
onWebhookChange: (webhook: Webhook) => void
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
}
|
||||
|
||||
export const WebhookAdvancedConfigForm = ({
|
||||
blockId,
|
||||
webhook,
|
||||
options,
|
||||
onWebhookChange,
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const { typebot, save, updateWebhook } = useTypebot()
|
||||
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
|
||||
const [testResponse, setTestResponse] = useState<string>()
|
||||
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||
const { showToast } = useToast()
|
||||
|
||||
const handleMethodChange = (method: HttpMethod) =>
|
||||
onWebhookChange({ ...webhook, method })
|
||||
|
||||
const handleQueryParamsChange = (queryParams: KeyValue[]) =>
|
||||
onWebhookChange({ ...webhook, queryParams })
|
||||
|
||||
const handleHeadersChange = (headers: KeyValue[]) =>
|
||||
onWebhookChange({ ...webhook, headers })
|
||||
|
||||
const handleBodyChange = (body: string) =>
|
||||
onWebhookChange({ ...webhook, body })
|
||||
|
||||
const handleVariablesChange = (variablesForTest: VariableForTest[]) =>
|
||||
onOptionsChange({ ...options, variablesForTest })
|
||||
|
||||
const handleResponseMappingChange = (
|
||||
responseVariableMapping: ResponseVariableMapping[]
|
||||
) => onOptionsChange({ ...options, responseVariableMapping })
|
||||
|
||||
const handleAdvancedConfigChange = (isAdvancedConfig: boolean) =>
|
||||
onOptionsChange({ ...options, isAdvancedConfig })
|
||||
|
||||
const handleBodyFormStateChange = (isCustomBody: boolean) =>
|
||||
onOptionsChange({ ...options, isCustomBody })
|
||||
|
||||
const handleTestRequestClick = async () => {
|
||||
if (!typebot || !webhook) return
|
||||
setIsTestResponseLoading(true)
|
||||
await Promise.all([updateWebhook(webhook.id, webhook), save()])
|
||||
const { data, error } = await executeWebhook(
|
||||
typebot.id,
|
||||
convertVariablesForTestToVariables(
|
||||
options.variablesForTest,
|
||||
typebot.variables
|
||||
),
|
||||
{ blockId }
|
||||
)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
setTestResponse(JSON.stringify(data, undefined, 2))
|
||||
setResponseKeys(getDeepKeys(data))
|
||||
setIsTestResponseLoading(false)
|
||||
}
|
||||
|
||||
const ResponseMappingInputs = useMemo(
|
||||
() =>
|
||||
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
||||
return <DataVariableInputs {...props} dataItems={responseKeys} />
|
||||
},
|
||||
[responseKeys]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwitchWithLabel
|
||||
label="Advanced configuration"
|
||||
initialValue={options.isAdvancedConfig ?? true}
|
||||
onCheckChange={handleAdvancedConfigChange}
|
||||
/>
|
||||
{(options.isAdvancedConfig ?? true) && (
|
||||
<>
|
||||
<HStack justify="space-between">
|
||||
<Text>Method:</Text>
|
||||
<DropdownList<HttpMethod>
|
||||
currentItem={webhook.method as HttpMethod}
|
||||
onItemSelect={handleMethodChange}
|
||||
items={Object.values(HttpMethod)}
|
||||
/>
|
||||
</HStack>
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Query params
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={webhook.queryParams}
|
||||
onItemsChange={handleQueryParamsChange}
|
||||
Item={QueryParamsInputs}
|
||||
addLabel="Add a param"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Headers
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={webhook.headers}
|
||||
onItemsChange={handleHeadersChange}
|
||||
Item={HeadersInputs}
|
||||
addLabel="Add a value"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Body
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<SwitchWithLabel
|
||||
label="Custom body"
|
||||
initialValue={options.isCustomBody ?? true}
|
||||
onCheckChange={handleBodyFormStateChange}
|
||||
/>
|
||||
{(options.isCustomBody ?? true) && (
|
||||
<CodeEditor
|
||||
defaultValue={webhook.body ?? ''}
|
||||
lang="json"
|
||||
onChange={handleBodyChange}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Variable values for test
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<VariableForTest>
|
||||
initialItems={
|
||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||
}
|
||||
onItemsChange={handleVariablesChange}
|
||||
Item={VariableForTestInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
{webhook.url && (
|
||||
<Button
|
||||
onClick={handleTestRequestClick}
|
||||
colorScheme="blue"
|
||||
isLoading={isTestResponseLoading}
|
||||
>
|
||||
Test the request
|
||||
</Button>
|
||||
)}
|
||||
{testResponse && (
|
||||
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
||||
)}
|
||||
{(testResponse || options?.responseVariableMapping.length > 0) && (
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Save in variables
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<ResponseVariableMapping>
|
||||
initialItems={options.responseVariableMapping}
|
||||
onItemsChange={handleResponseMappingChange}
|
||||
Item={ResponseMappingInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,70 +1,24 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Button,
|
||||
HStack,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Link,
|
||||
} from '@chakra-ui/react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Spinner, Stack } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import {
|
||||
HttpMethod,
|
||||
KeyValue,
|
||||
WebhookOptions,
|
||||
VariableForTest,
|
||||
ResponseVariableMapping,
|
||||
WebhookBlock,
|
||||
MakeComBlock,
|
||||
PabblyConnectBlock,
|
||||
Webhook,
|
||||
} from 'models'
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||
import { HeadersInputs, QueryParamsInputs } from './KeyValueInputs'
|
||||
import { VariableForTestInputs } from './VariableForTestInputs'
|
||||
import { DataVariableInputs } from './ResponseMappingInputs'
|
||||
import { WebhookOptions, Webhook, WebhookBlock } from 'models'
|
||||
import { byId, env } from 'utils'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { useToast } from '@/hooks/useToast'
|
||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||
import { TableListItemProps, TableList } from '@/components/TableList'
|
||||
import { executeWebhook } from '../../queries/executeWebhookQuery'
|
||||
import { getDeepKeys } from '../../utils/getDeepKeys'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { convertVariablesForTestToVariables } from '../../utils/convertVariablesForTestToVariables'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { WebhookAdvancedConfigForm } from '../WebhookAdvancedConfigForm'
|
||||
|
||||
const debounceWebhookTimeout = 2000
|
||||
|
||||
type Provider = {
|
||||
name: 'Pabbly Connect'
|
||||
url: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
block: WebhookBlock | MakeComBlock | PabblyConnectBlock
|
||||
block: WebhookBlock
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
provider?: Provider
|
||||
}
|
||||
|
||||
export const WebhookSettings = ({
|
||||
block: { options, id: blockId, webhookId },
|
||||
block: { webhookId, id: blockId, options },
|
||||
onOptionsChange,
|
||||
provider,
|
||||
}: Props) => {
|
||||
const { typebot, save, webhooks, updateWebhook } = useTypebot()
|
||||
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
|
||||
const [testResponse, setTestResponse] = useState<string>()
|
||||
const [responseKeys, setResponseKeys] = useState<string[]>([])
|
||||
const { showToast } = useToast()
|
||||
const { webhooks, updateWebhook } = useTypebot()
|
||||
const [localWebhook, _setLocalWebhook] = useState(
|
||||
webhooks.find(byId(webhookId))
|
||||
)
|
||||
@ -90,200 +44,23 @@ export const WebhookSettings = ({
|
||||
const handleUrlChange = (url?: string) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, url: url ?? null })
|
||||
|
||||
const handleMethodChange = (method: HttpMethod) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, method })
|
||||
|
||||
const handleQueryParamsChange = (queryParams: KeyValue[]) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, queryParams })
|
||||
|
||||
const handleHeadersChange = (headers: KeyValue[]) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, headers })
|
||||
|
||||
const handleBodyChange = (body: string) =>
|
||||
localWebhook && setLocalWebhook({ ...localWebhook, body })
|
||||
|
||||
const handleVariablesChange = (variablesForTest: VariableForTest[]) =>
|
||||
onOptionsChange({ ...options, variablesForTest })
|
||||
|
||||
const handleResponseMappingChange = (
|
||||
responseVariableMapping: ResponseVariableMapping[]
|
||||
) => onOptionsChange({ ...options, responseVariableMapping })
|
||||
|
||||
const handleAdvancedConfigChange = (isAdvancedConfig: boolean) =>
|
||||
onOptionsChange({ ...options, isAdvancedConfig })
|
||||
|
||||
const handleBodyFormStateChange = (isCustomBody: boolean) =>
|
||||
onOptionsChange({ ...options, isCustomBody })
|
||||
|
||||
const handleTestRequestClick = async () => {
|
||||
if (!typebot || !localWebhook) return
|
||||
setIsTestResponseLoading(true)
|
||||
await Promise.all([updateWebhook(localWebhook.id, localWebhook), save()])
|
||||
const { data, error } = await executeWebhook(
|
||||
typebot.id,
|
||||
convertVariablesForTestToVariables(
|
||||
options.variablesForTest,
|
||||
typebot.variables
|
||||
),
|
||||
{ blockId }
|
||||
)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
setTestResponse(JSON.stringify(data, undefined, 2))
|
||||
setResponseKeys(getDeepKeys(data))
|
||||
setIsTestResponseLoading(false)
|
||||
}
|
||||
|
||||
const ResponseMappingInputs = useMemo(
|
||||
() =>
|
||||
function Component(props: TableListItemProps<ResponseVariableMapping>) {
|
||||
return <DataVariableInputs {...props} dataItems={responseKeys} />
|
||||
},
|
||||
[responseKeys]
|
||||
)
|
||||
|
||||
if (!localWebhook) return <Spinner />
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
{provider && (
|
||||
<Alert status={'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
<Stack>
|
||||
<Text>Head up to {provider.name} to configure this block:</Text>
|
||||
<Button as={Link} href={provider.url} isExternal colorScheme="blue">
|
||||
<Text mr="2">{provider.name}</Text> <ExternalLinkIcon />
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
<TextInput
|
||||
placeholder="Paste webhook URL..."
|
||||
defaultValue={localWebhook.url ?? ''}
|
||||
onChange={handleUrlChange}
|
||||
debounceTimeout={0}
|
||||
withVariableButton={!provider}
|
||||
/>
|
||||
<SwitchWithLabel
|
||||
label="Advanced configuration"
|
||||
initialValue={options.isAdvancedConfig ?? true}
|
||||
onCheckChange={handleAdvancedConfigChange}
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
{(options.isAdvancedConfig ?? true) && (
|
||||
<Stack>
|
||||
<HStack justify="space-between">
|
||||
<Text>Method:</Text>
|
||||
<DropdownList<HttpMethod>
|
||||
currentItem={localWebhook.method as HttpMethod}
|
||||
onItemSelect={handleMethodChange}
|
||||
items={Object.values(HttpMethod)}
|
||||
/>
|
||||
</HStack>
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Query params
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.queryParams}
|
||||
onItemsChange={handleQueryParamsChange}
|
||||
Item={QueryParamsInputs}
|
||||
addLabel="Add a param"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Headers
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<KeyValue>
|
||||
initialItems={localWebhook.headers}
|
||||
onItemsChange={handleHeadersChange}
|
||||
Item={HeadersInputs}
|
||||
addLabel="Add a value"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Body
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<SwitchWithLabel
|
||||
label="Custom body"
|
||||
initialValue={options.isCustomBody ?? true}
|
||||
onCheckChange={handleBodyFormStateChange}
|
||||
/>
|
||||
{(options.isCustomBody ?? true) && (
|
||||
<CodeEditor
|
||||
defaultValue={localWebhook.body ?? ''}
|
||||
lang="json"
|
||||
onChange={handleBodyChange}
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Variable values for test
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<VariableForTest>
|
||||
initialItems={
|
||||
options?.variablesForTest ?? { byId: {}, allIds: [] }
|
||||
}
|
||||
onItemsChange={handleVariablesChange}
|
||||
Item={VariableForTestInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack>
|
||||
{localWebhook.url && (
|
||||
<Button
|
||||
onClick={handleTestRequestClick}
|
||||
colorScheme="blue"
|
||||
isLoading={isTestResponseLoading}
|
||||
>
|
||||
Test the request
|
||||
</Button>
|
||||
)}
|
||||
{testResponse && (
|
||||
<CodeEditor isReadOnly lang="json" value={testResponse} />
|
||||
)}
|
||||
{(testResponse || options?.responseVariableMapping.length > 0) && (
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Save in variables
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel py={4} as={Stack} spacing="6">
|
||||
<TableList<ResponseVariableMapping>
|
||||
initialItems={options.responseVariableMapping}
|
||||
onItemsChange={handleResponseMappingChange}
|
||||
Item={ResponseMappingInputs}
|
||||
addLabel="Add an entry"
|
||||
debounceTimeout={0}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { defaultWebhookAttributes, Webhook, ZapierBlock } from 'models'
|
||||
import { useEffect } from 'react'
|
||||
import { ZapierBlock } from 'models'
|
||||
import { byId, isNotDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
@ -9,22 +8,9 @@ type Props = {
|
||||
}
|
||||
|
||||
export const ZapierContent = ({ block }: Props) => {
|
||||
const { webhooks, typebot, updateWebhook } = useTypebot()
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
|
||||
useEffect(() => {
|
||||
if (!typebot) return
|
||||
if (!webhook) {
|
||||
const { webhookId } = block
|
||||
const newWebhook = {
|
||||
id: webhookId,
|
||||
...defaultWebhookAttributes,
|
||||
typebotId: typebot.id,
|
||||
} as Webhook
|
||||
updateWebhook(webhookId, newWebhook)
|
||||
}
|
||||
}, [block, typebot, updateWebhook, webhook])
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
|
@ -1,31 +1,69 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Button,
|
||||
Input,
|
||||
Link,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import { Alert, AlertIcon, Button, Link, Stack, Text } from '@chakra-ui/react'
|
||||
import { ExternalLinkIcon } from '@/components/icons'
|
||||
import { useTypebot } from '@/features/editor'
|
||||
import { ZapierBlock } from 'models'
|
||||
import React from 'react'
|
||||
import { byId } from 'utils'
|
||||
import { Webhook, WebhookOptions, ZapierBlock } from 'models'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { byId, env } from 'utils'
|
||||
import { WebhookAdvancedConfigForm } from '../../webhook/components/WebhookAdvancedConfigForm'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
const debounceWebhookTimeout = 2000
|
||||
|
||||
type Props = {
|
||||
block: ZapierBlock
|
||||
onOptionsChange: (options: WebhookOptions) => void
|
||||
}
|
||||
|
||||
export const ZapierSettings = ({ block }: Props) => {
|
||||
const { webhooks } = useTypebot()
|
||||
const webhook = webhooks.find(byId(block.webhookId))
|
||||
export const ZapierSettings = ({
|
||||
block: { webhookId, id: blockId, options },
|
||||
onOptionsChange,
|
||||
}: Props) => {
|
||||
const { webhooks, updateWebhook } = useTypebot()
|
||||
const webhook = webhooks.find(byId(webhookId))
|
||||
|
||||
const [localWebhook, _setLocalWebhook] = useState(webhook)
|
||||
|
||||
const updateWebhookDebounced = useDebouncedCallback(
|
||||
async (newLocalWebhook) => {
|
||||
await updateWebhook(newLocalWebhook.id, newLocalWebhook)
|
||||
},
|
||||
env('E2E_TEST') === 'true' ? 0 : debounceWebhookTimeout
|
||||
)
|
||||
|
||||
const setLocalWebhook = useCallback(
|
||||
(newLocalWebhook: Webhook) => {
|
||||
_setLocalWebhook(newLocalWebhook)
|
||||
updateWebhookDebounced(newLocalWebhook)
|
||||
},
|
||||
[updateWebhookDebounced]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!localWebhook ||
|
||||
localWebhook.url ||
|
||||
!webhook?.url ||
|
||||
webhook.url === localWebhook.url
|
||||
)
|
||||
return
|
||||
setLocalWebhook({
|
||||
...localWebhook,
|
||||
url: webhook?.url,
|
||||
})
|
||||
}, [webhook, localWebhook, setLocalWebhook])
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
updateWebhookDebounced.flush()
|
||||
},
|
||||
[updateWebhookDebounced]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Alert status={webhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<Alert status={localWebhook?.url ? 'success' : 'info'} rounded="md">
|
||||
<AlertIcon />
|
||||
{webhook?.url ? (
|
||||
{localWebhook?.url ? (
|
||||
<>Your zap is correctly configured 🚀</>
|
||||
) : (
|
||||
<Stack>
|
||||
@ -41,7 +79,15 @@ export const ZapierSettings = ({ block }: Props) => {
|
||||
</Stack>
|
||||
)}
|
||||
</Alert>
|
||||
{webhook?.url && <Input value={webhook?.url} isDisabled />}
|
||||
{localWebhook && (
|
||||
<WebhookAdvancedConfigForm
|
||||
blockId={blockId}
|
||||
webhook={localWebhook}
|
||||
options={options}
|
||||
onWebhookChange={setLocalWebhook}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
@ -38,11 +38,12 @@ import { SetVariableSettings } from '@/features/blocks/logic/setVariable'
|
||||
import { TypebotLinkForm } from '@/features/blocks/logic/typebotLink'
|
||||
import { ButtonsBlockSettings } from '@/features/blocks/inputs/buttons'
|
||||
import { ChatwootSettingsForm } from '@/features/blocks/integrations/chatwoot'
|
||||
import { MakeComSettings } from '@/features/blocks/integrations/makeCom'
|
||||
import { HelpDocButton } from './HelpDocButton'
|
||||
import { WaitSettings } from '@/features/blocks/logic/wait/components/WaitSettings'
|
||||
import { ScriptSettings } from '@/features/blocks/logic/script/components/ScriptSettings'
|
||||
import { JumpSettings } from '@/features/blocks/logic/jump/components/JumpSettings'
|
||||
import { MakeComSettings } from '@/features/blocks/integrations/makeCom/components/MakeComSettings'
|
||||
import { PabblyConnectSettings } from '@/features/blocks/integrations/pabbly/components/PabblyConnectSettings'
|
||||
|
||||
type Props = {
|
||||
block: BlockWithOptions
|
||||
@ -248,20 +249,20 @@ export const BlockSettings = ({
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.ZAPIER: {
|
||||
return <ZapierSettings block={block} />
|
||||
return (
|
||||
<ZapierSettings block={block} onOptionsChange={handleOptionsChange} />
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.MAKE_COM: {
|
||||
return <MakeComSettings block={block} />
|
||||
return (
|
||||
<MakeComSettings block={block} onOptionsChange={handleOptionsChange} />
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.PABBLY_CONNECT: {
|
||||
return (
|
||||
<WebhookSettings
|
||||
<PabblyConnectSettings
|
||||
block={block}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
provider={{
|
||||
name: 'Pabbly Connect',
|
||||
url: 'https://www.pabbly.com/connect/integrations/typebot/',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user