feat(editor): ✨ Add Zapier step
This commit is contained in:
15
apps/builder/assets/logos/ZapierLogo.tsx
Normal file
15
apps/builder/assets/logos/ZapierLogo.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Icon, IconProps } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
export const ZapierLogo = (props: IconProps) => (
|
||||||
|
<Icon
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M159.999 128.056a76.55 76.55 0 0 1-4.915 27.024 76.745 76.745 0 0 1-27.032 4.923h-.108c-9.508-.012-18.618-1.75-27.024-4.919A76.557 76.557 0 0 1 96 128.056v-.112a76.598 76.598 0 0 1 4.91-27.02A76.492 76.492 0 0 1 127.945 96h.108a76.475 76.475 0 0 1 27.032 4.923 76.51 76.51 0 0 1 4.915 27.02v.112zm94.223-21.389h-74.716l52.829-52.833a128.518 128.518 0 0 0-13.828-16.349v-.004a129 129 0 0 0-16.345-13.816l-52.833 52.833V1.782A128.606 128.606 0 0 0 128.064 0h-.132c-7.248.004-14.347.62-21.265 1.782v74.716L53.834 23.665A127.82 127.82 0 0 0 37.497 37.49l-.028.02A128.803 128.803 0 0 0 23.66 53.834l52.837 52.833H1.782S0 120.7 0 127.956v.088c0 7.256.615 14.367 1.782 21.289h74.716l-52.837 52.833a128.91 128.91 0 0 0 30.173 30.173l52.833-52.837v74.72a129.3 129.3 0 0 0 21.24 1.778h.181a129.15 129.15 0 0 0 21.24-1.778v-74.72l52.838 52.837a128.994 128.994 0 0 0 16.341-13.82l.012-.012a129.245 129.245 0 0 0 13.816-16.341l-52.837-52.833h74.724c1.163-6.91 1.77-14 1.778-21.24v-.186c-.008-7.24-.615-14.33-1.778-21.24z"
|
||||||
|
fill="#FF4A00"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
@@ -14,3 +14,4 @@ export * from './WordpressLogo'
|
|||||||
export * from './WixLogo'
|
export * from './WixLogo'
|
||||||
export * from './GoogleLogo'
|
export * from './GoogleLogo'
|
||||||
export * from './FacebookLogo'
|
export * from './FacebookLogo'
|
||||||
|
export * from './ZapierLogo'
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
TextIcon,
|
TextIcon,
|
||||||
WebhookIcon,
|
WebhookIcon,
|
||||||
} from 'assets/icons'
|
} from 'assets/icons'
|
||||||
import { GoogleAnalyticsLogo, GoogleSheetsLogo } from 'assets/logos'
|
import { GoogleAnalyticsLogo, GoogleSheetsLogo, ZapierLogo } from 'assets/logos'
|
||||||
import {
|
import {
|
||||||
BubbleStepType,
|
BubbleStepType,
|
||||||
InputStepType,
|
InputStepType,
|
||||||
@@ -63,6 +63,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
|
|||||||
return <GoogleAnalyticsLogo {...props} />
|
return <GoogleAnalyticsLogo {...props} />
|
||||||
case IntegrationStepType.WEBHOOK:
|
case IntegrationStepType.WEBHOOK:
|
||||||
return <WebhookIcon {...props} />
|
return <WebhookIcon {...props} />
|
||||||
|
case IntegrationStepType.ZAPIER:
|
||||||
|
return <ZapierLogo {...props} />
|
||||||
case IntegrationStepType.EMAIL:
|
case IntegrationStepType.EMAIL:
|
||||||
return <SendEmailIcon {...props} />
|
return <SendEmailIcon {...props} />
|
||||||
case 'start':
|
case 'start':
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export const StepTypeLabel = ({ type }: Props) => {
|
|||||||
)
|
)
|
||||||
case IntegrationStepType.WEBHOOK:
|
case IntegrationStepType.WEBHOOK:
|
||||||
return <Text>Webhook</Text>
|
return <Text>Webhook</Text>
|
||||||
|
case IntegrationStepType.ZAPIER:
|
||||||
|
return <Text>Zapier</Text>
|
||||||
case IntegrationStepType.EMAIL:
|
case IntegrationStepType.EMAIL:
|
||||||
return <Text>Email</Text>
|
return <Text>Email</Text>
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { RedirectSettings } from './bodies/RedirectSettings'
|
|||||||
import { SendEmailSettings } from './bodies/SendEmailSettings/SendEmailSettings'
|
import { SendEmailSettings } from './bodies/SendEmailSettings/SendEmailSettings'
|
||||||
import { SetVariableSettings } from './bodies/SetVariableSettings'
|
import { SetVariableSettings } from './bodies/SetVariableSettings'
|
||||||
import { WebhookSettings } from './bodies/WebhookSettings'
|
import { WebhookSettings } from './bodies/WebhookSettings'
|
||||||
|
import { ZapierSettings } from './bodies/ZapierSettings'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
step: Exclude<Step, TextBubbleStep>
|
step: Exclude<Step, TextBubbleStep>
|
||||||
@@ -199,6 +200,9 @@ export const StepSettings = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case IntegrationStepType.ZAPIER: {
|
||||||
|
return <ZapierSettings step={step} />
|
||||||
|
}
|
||||||
case IntegrationStepType.WEBHOOK: {
|
case IntegrationStepType.WEBHOOK: {
|
||||||
return (
|
return (
|
||||||
<WebhookSettings
|
<WebhookSettings
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AlertIcon,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Link,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
} from '@chakra-ui/react'
|
||||||
|
import { ExternalLinkIcon } from 'assets/icons'
|
||||||
|
import { ZapierStep } from 'models'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
step: ZapierStep
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapierSettings = ({ step }: Props) => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Alert
|
||||||
|
status={step.webhook.url ? 'success' : 'info'}
|
||||||
|
bgColor={step.webhook.url ? undefined : 'blue.50'}
|
||||||
|
rounded="md"
|
||||||
|
>
|
||||||
|
<AlertIcon />
|
||||||
|
{step.webhook.url ? (
|
||||||
|
<>Your zap is correctly configured 🚀</>
|
||||||
|
) : (
|
||||||
|
<Stack>
|
||||||
|
<Text>Head up to Zapier to configure this step:</Text>
|
||||||
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href="https://zapier.com/apps/typebot/integrations"
|
||||||
|
isExternal
|
||||||
|
colorScheme="blue"
|
||||||
|
>
|
||||||
|
<Text mr="2">Zapier</Text> <ExternalLinkIcon />
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Alert>
|
||||||
|
{step.webhook.url && <Input value={step.webhook.url} isDisabled />}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import { ConfigureContent } from './contents/ConfigureContent'
|
|||||||
import { ImageBubbleContent } from './contents/ImageBubbleContent'
|
import { ImageBubbleContent } from './contents/ImageBubbleContent'
|
||||||
import { PlaceholderContent } from './contents/PlaceholderContent'
|
import { PlaceholderContent } from './contents/PlaceholderContent'
|
||||||
import { SendEmailContent } from './contents/SendEmailContent'
|
import { SendEmailContent } from './contents/SendEmailContent'
|
||||||
|
import { ZapierContent } from './contents/ZapierContent'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
step: Step | StartStep
|
step: Step | StartStep
|
||||||
@@ -102,6 +103,9 @@ export const StepNodeContent = ({ step, indices }: Props) => {
|
|||||||
case IntegrationStepType.WEBHOOK: {
|
case IntegrationStepType.WEBHOOK: {
|
||||||
return <WebhookContent step={step} />
|
return <WebhookContent step={step} />
|
||||||
}
|
}
|
||||||
|
case IntegrationStepType.ZAPIER: {
|
||||||
|
return <ZapierContent step={step} />
|
||||||
|
}
|
||||||
case IntegrationStepType.EMAIL: {
|
case IntegrationStepType.EMAIL: {
|
||||||
return <SendEmailContent step={step} />
|
return <SendEmailContent step={step} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Text } from '@chakra-ui/react'
|
||||||
|
import { ZapierStep } from 'models'
|
||||||
|
import { isNotDefined } from 'utils'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
step: ZapierStep
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ZapierContent = ({ step }: Props) => {
|
||||||
|
if (isNotDefined(step.webhook.body))
|
||||||
|
return <Text color="gray.500">Configure...</Text>
|
||||||
|
return (
|
||||||
|
<Text isTruncated pr="6">
|
||||||
|
{step.webhook.url ? 'Enabled' : 'Disabled'}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -218,6 +218,7 @@ const parseDefaultStepOptions = (type: StepWithOptionsType): StepOptions => {
|
|||||||
return defaultGoogleSheetsOptions
|
return defaultGoogleSheetsOptions
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
return defaultGoogleAnalyticsOptions
|
return defaultGoogleAnalyticsOptions
|
||||||
|
case IntegrationStepType.ZAPIER:
|
||||||
case IntegrationStepType.WEBHOOK:
|
case IntegrationStepType.WEBHOOK:
|
||||||
return defaultWebhookOptions
|
return defaultWebhookOptions
|
||||||
case IntegrationStepType.EMAIL:
|
case IntegrationStepType.EMAIL:
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { Prisma } from 'db'
|
import { Prisma } from 'db'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { HttpMethod, IntegrationStepType, Typebot } from 'models'
|
import { HttpMethod, Typebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
import { authenticateUser } from 'services/api/utils'
|
||||||
import { methodNotAllowed } from 'utils'
|
import { isWebhookStep, methodNotAllowed } from 'utils'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
if (req.method === 'PATCH') {
|
if (req.method === 'PATCH') {
|
||||||
@@ -46,7 +46,7 @@ const addUrlToWebhookStep = (
|
|||||||
...b,
|
...b,
|
||||||
steps: b.steps.map((s) => {
|
steps: b.steps.map((s) => {
|
||||||
if (s.id === stepId) {
|
if (s.id === stepId) {
|
||||||
if (s.type !== IntegrationStepType.WEBHOOK) throw new Error()
|
if (!isWebhookStep(s)) throw new Error()
|
||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
webhook: {
|
webhook: {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { Prisma } from 'db'
|
import { Prisma } from 'db'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { IntegrationStepType, Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
import { authenticateUser } from 'services/api/utils'
|
||||||
import { omit } from 'services/utils'
|
import { omit } from 'services/utils'
|
||||||
import { methodNotAllowed } from 'utils'
|
import { isWebhookStep, methodNotAllowed } from 'utils'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
@@ -44,7 +44,7 @@ const removeUrlFromWebhookStep = (
|
|||||||
...b,
|
...b,
|
||||||
steps: b.steps.map((s) => {
|
steps: b.steps.map((s) => {
|
||||||
if (s.id === stepId) {
|
if (s.id === stepId) {
|
||||||
if (s.type !== IntegrationStepType.WEBHOOK) throw new Error()
|
if (!isWebhookStep(s)) throw new Error()
|
||||||
return { ...s, webhook: omit(s.webhook, 'url') }
|
return { ...s, webhook: omit(s.webhook, 'url') }
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { Block, IntegrationStepType } from 'models'
|
import { Block } from 'models'
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { authenticateUser } from 'services/api/utils'
|
import { authenticateUser } from 'services/api/utils'
|
||||||
import { methodNotAllowed } from 'utils'
|
import { isWebhookStep, methodNotAllowed } from 'utils'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
@@ -18,7 +18,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
{ blockId: string; id: string; name: string }[]
|
{ blockId: string; id: string; name: string }[]
|
||||||
>((emptyWebhookSteps, block) => {
|
>((emptyWebhookSteps, block) => {
|
||||||
const steps = block.steps.filter(
|
const steps = block.steps.filter(
|
||||||
(step) => step.type === IntegrationStepType.WEBHOOK && !step.webhook.url
|
(step) => isWebhookStep(step) && !step.webhook.url
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
...emptyWebhookSteps,
|
...emptyWebhookSteps,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
WebhookStep,
|
WebhookStep,
|
||||||
SendEmailStep,
|
SendEmailStep,
|
||||||
PublicBlock,
|
PublicBlock,
|
||||||
|
ZapierStep,
|
||||||
} from 'models'
|
} from 'models'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { parseAnswers, sendRequest } from 'utils'
|
import { parseAnswers, sendRequest } from 'utils'
|
||||||
@@ -46,6 +47,7 @@ export const executeIntegration = ({
|
|||||||
return executeGoogleSheetIntegration(step, context)
|
return executeGoogleSheetIntegration(step, context)
|
||||||
case IntegrationStepType.GOOGLE_ANALYTICS:
|
case IntegrationStepType.GOOGLE_ANALYTICS:
|
||||||
return executeGoogleAnalyticsIntegration(step, context)
|
return executeGoogleAnalyticsIntegration(step, context)
|
||||||
|
case IntegrationStepType.ZAPIER:
|
||||||
case IntegrationStepType.WEBHOOK:
|
case IntegrationStepType.WEBHOOK:
|
||||||
return executeWebhook(step, context)
|
return executeWebhook(step, context)
|
||||||
case IntegrationStepType.EMAIL:
|
case IntegrationStepType.EMAIL:
|
||||||
@@ -156,7 +158,7 @@ const parseCellValues = (
|
|||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
const executeWebhook = async (
|
const executeWebhook = async (
|
||||||
step: WebhookStep,
|
step: WebhookStep | ZapierStep,
|
||||||
{
|
{
|
||||||
blockId,
|
blockId,
|
||||||
stepId,
|
stepId,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type IntegrationStep =
|
|||||||
| GoogleAnalyticsStep
|
| GoogleAnalyticsStep
|
||||||
| WebhookStep
|
| WebhookStep
|
||||||
| SendEmailStep
|
| SendEmailStep
|
||||||
|
| ZapierStep
|
||||||
|
|
||||||
export type IntegrationStepOptions =
|
export type IntegrationStepOptions =
|
||||||
| GoogleSheetsOptions
|
| GoogleSheetsOptions
|
||||||
@@ -17,6 +18,7 @@ export enum IntegrationStepType {
|
|||||||
GOOGLE_ANALYTICS = 'Google Analytics',
|
GOOGLE_ANALYTICS = 'Google Analytics',
|
||||||
WEBHOOK = 'Webhook',
|
WEBHOOK = 'Webhook',
|
||||||
EMAIL = 'Email',
|
EMAIL = 'Email',
|
||||||
|
ZAPIER = 'Zapier',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GoogleSheetsStep = StepBase & {
|
export type GoogleSheetsStep = StepBase & {
|
||||||
@@ -35,6 +37,10 @@ export type WebhookStep = StepBase & {
|
|||||||
webhook: Webhook
|
webhook: Webhook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ZapierStep = Omit<WebhookStep, 'type'> & {
|
||||||
|
type: IntegrationStepType.ZAPIER
|
||||||
|
}
|
||||||
|
|
||||||
export type SendEmailStep = StepBase & {
|
export type SendEmailStep = StepBase & {
|
||||||
type: IntegrationStepType.EMAIL
|
type: IntegrationStepType.EMAIL
|
||||||
options: SendEmailOptions
|
options: SendEmailOptions
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const isIntegrationStep = (
|
|||||||
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
(Object.values(IntegrationStepType) as string[]).includes(step.type)
|
||||||
|
|
||||||
export const isWebhookStep = (step: Step | PublicStep): step is WebhookStep =>
|
export const isWebhookStep = (step: Step | PublicStep): step is WebhookStep =>
|
||||||
step.type === IntegrationStepType.WEBHOOK
|
'webhook' in step
|
||||||
|
|
||||||
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
||||||
(Object.values(BubbleStepType) as string[]).includes(type)
|
(Object.values(BubbleStepType) as string[]).includes(type)
|
||||||
@@ -114,7 +114,11 @@ export const stepTypeHasOption = (
|
|||||||
|
|
||||||
export const stepTypeHasWebhook = (
|
export const stepTypeHasWebhook = (
|
||||||
type: StepType
|
type: StepType
|
||||||
): type is IntegrationStepType.WEBHOOK => type === IntegrationStepType.WEBHOOK
|
): type is IntegrationStepType.WEBHOOK =>
|
||||||
|
Object.values([
|
||||||
|
IntegrationStepType.WEBHOOK,
|
||||||
|
IntegrationStepType.ZAPIER,
|
||||||
|
] as string[]).includes(type)
|
||||||
|
|
||||||
export const stepTypeHasItems = (
|
export const stepTypeHasItems = (
|
||||||
type: StepType
|
type: StepType
|
||||||
|
|||||||
Reference in New Issue
Block a user