2
0

(webhook) Enable advanced config for Zapier and Make.com

This commit is contained in:
Baptiste Arnaud
2023-03-06 10:42:17 +01:00
parent 5bda556200
commit c1a636b965
9 changed files with 479 additions and 328 deletions

View File

@ -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 (

View File

@ -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>
)
}

View File

@ -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 (

View File

@ -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>
)
}

View File

@ -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>
)}
</>
)
}

View File

@ -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>
)
}

View File

@ -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 (

View File

@ -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>
)
}

View File

@ -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/',
}}
/>
)
}