Skip to content

Commit

Permalink
feat(editor): ✨ Restore published version button
Browse files Browse the repository at this point in the history
Had to migrate webhooks into a standalone table
  • Loading branch information
baptisteArno committed Mar 1, 2022
1 parent 0df719d commit e17a1a0
Show file tree
Hide file tree
Showing 46 changed files with 578 additions and 348 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
StepOptions,
TextBubbleStep,
Webhook,
WebhookStep,
} from 'models'
import { useRef } from 'react'
import {
Expand Down Expand Up @@ -95,11 +94,6 @@ export const StepSettings = ({
const handleOptionsChange = (options: StepOptions) => {
onStepChange({ options } as Partial<Step>)
}
const handleWebhookChange = (updates: Partial<Webhook>) => {
onStepChange({
webhook: { ...(step as WebhookStep).webhook, ...updates },
} as Partial<Step>)
}
const handleItemChange = (updates: Partial<ConditionItem>) => {
onStepChange({
items: [{ ...(step as ConditionStep).items[0], ...updates }],
Expand Down Expand Up @@ -208,7 +202,6 @@ export const StepSettings = ({
<WebhookSettings
step={step}
onOptionsChange={handleOptionsChange}
onWebhookChange={handleWebhookChange}
onTestRequestClick={onTestRequestClick}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import {
Sheet,
useSheets,
} from 'services/integrations'
import { isDefined } from 'utils'
import { isDefined, omit } from 'utils'
import { SheetsDropdown } from './SheetsDropdown'
import { SpreadsheetsDropdown } from './SpreadsheetDropdown'
import { CellWithValueStack } from './CellWithValueStack'
import { CellWithVariableIdStack } from './CellWithVariableIdStack'
import { omit } from 'services/utils'
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'

type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react'
import React, { useEffect, useMemo, useState } from 'react'
import {
Accordion,
AccordionButton,
Expand All @@ -7,6 +7,7 @@ import {
AccordionPanel,
Button,
Flex,
Spinner,
Stack,
useToast,
} from '@chakra-ui/react'
Expand All @@ -17,9 +18,10 @@ import {
KeyValue,
WebhookOptions,
VariableForTest,
Webhook,
ResponseVariableMapping,
WebhookStep,
defaultWebhookAttributes,
Webhook,
} from 'models'
import { DropdownList } from 'components/shared/DropdownList'
import { TableList, TableListItemProps } from 'components/shared/TableList'
Expand All @@ -32,21 +34,21 @@ import {
import { HeadersInputs, QueryParamsInputs } from './KeyValueInputs'
import { VariableForTestInputs } from './VariableForTestInputs'
import { DataVariableInputs } from './ResponseMappingInputs'
import { byId } from 'utils'
import { deepEqual } from 'fast-equals'

type Props = {
step: WebhookStep
onOptionsChange: (options: WebhookOptions) => void
onWebhookChange: (updates: Partial<Webhook>) => void
onTestRequestClick: () => void
}

export const WebhookSettings = ({
step: { webhook, options, blockId, id: stepId },
step: { options, blockId, id: stepId, webhookId },
onOptionsChange,
onWebhookChange,
onTestRequestClick,
}: Props) => {
const { typebot, save } = useTypebot()
const { typebot, save, webhooks, updateWebhook } = useTypebot()
const [isTestResponseLoading, setIsTestResponseLoading] = useState(false)
const [testResponse, setTestResponse] = useState<string>()
const [responseKeys, setResponseKeys] = useState<string[]>([])
Expand All @@ -55,18 +57,52 @@ export const WebhookSettings = ({
position: 'top-right',
status: 'error',
})
const [localWebhook, setLocalWebhook] = useState(
webhooks.find(byId(webhookId))
)

useEffect(() => {
const incomingWebhook = webhooks.find(byId(webhookId))
if (deepEqual(incomingWebhook, localWebhook)) return
setLocalWebhook(incomingWebhook)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [webhooks])

const handleUrlChange = (url?: string) => onWebhookChange({ url })
useEffect(() => {
if (!typebot) return
if (!localWebhook) {
const newWebhook = {
id: webhookId,
...defaultWebhookAttributes,
typebotId: typebot.id,
} as Webhook
updateWebhook(webhookId, newWebhook)
}

return () => {
setLocalWebhook((localWebhook) => {
if (!localWebhook) return
updateWebhook(webhookId, localWebhook).then()
return localWebhook
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const handleMethodChange = (method: HttpMethod) => onWebhookChange({ method })
const handleUrlChange = (url?: string) =>
localWebhook && setLocalWebhook({ ...localWebhook, url: url ?? null })

const handleMethodChange = (method: HttpMethod) =>
localWebhook && setLocalWebhook({ ...localWebhook, method })

const handleQueryParamsChange = (queryParams: KeyValue[]) =>
onWebhookChange({ queryParams })
localWebhook && setLocalWebhook({ ...localWebhook, queryParams })

const handleHeadersChange = (headers: KeyValue[]) =>
onWebhookChange({ headers })
localWebhook && setLocalWebhook({ ...localWebhook, headers })

const handleBodyChange = (body: string) => onWebhookChange({ body })
const handleBodyChange = (body: string) =>
localWebhook && setLocalWebhook({ ...localWebhook, body })

const handleVariablesChange = (variablesForTest: VariableForTest[]) =>
onOptionsChange({ ...options, variablesForTest })
Expand All @@ -76,10 +112,10 @@ export const WebhookSettings = ({
) => onOptionsChange({ ...options, responseVariableMapping })

const handleTestRequestClick = async () => {
if (!typebot) return
if (!typebot || !localWebhook) return
setIsTestResponseLoading(true)
onTestRequestClick()
await save()
await Promise.all([updateWebhook(localWebhook.id, localWebhook), save()])
const { data, error } = await executeWebhook(
typebot.id,
convertVariableForTestToVariables(
Expand All @@ -100,19 +136,20 @@ export const WebhookSettings = ({
[responseKeys]
)

if (!localWebhook) return <Spinner />
return (
<Stack spacing={4}>
<Stack>
<Flex>
<DropdownList<HttpMethod>
currentItem={webhook.method}
currentItem={localWebhook.method as HttpMethod}
onItemSelect={handleMethodChange}
items={Object.values(HttpMethod)}
/>
</Flex>
<InputWithVariableButton
placeholder="Your Webhook URL..."
initialValue={webhook.url ?? ''}
initialValue={localWebhook.url ?? ''}
onChange={handleUrlChange}
/>
</Stack>
Expand All @@ -124,7 +161,7 @@ export const WebhookSettings = ({
</AccordionButton>
<AccordionPanel pb={4} as={Stack} spacing="6">
<TableList<KeyValue>
initialItems={webhook.queryParams}
initialItems={localWebhook.queryParams}
onItemsChange={handleQueryParamsChange}
Item={QueryParamsInputs}
addLabel="Add a param"
Expand All @@ -138,7 +175,7 @@ export const WebhookSettings = ({
</AccordionButton>
<AccordionPanel pb={4} as={Stack} spacing="6">
<TableList<KeyValue>
initialItems={webhook.headers}
initialItems={localWebhook.headers}
onItemsChange={handleHeadersChange}
Item={HeadersInputs}
addLabel="Add a value"
Expand All @@ -152,7 +189,7 @@ export const WebhookSettings = ({
</AccordionButton>
<AccordionPanel pb={4} as={Stack} spacing="6">
<CodeEditor
value={webhook.body ?? ''}
value={localWebhook.body ?? ''}
lang="json"
onChange={handleBodyChange}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,27 @@ import {
Text,
} from '@chakra-ui/react'
import { ExternalLinkIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext'
import { ZapierStep } from 'models'
import React from 'react'
import { byId } from 'utils'

type Props = {
step: ZapierStep
}

export const ZapierSettings = ({ step }: Props) => {
const { webhooks } = useTypebot()
const webhook = webhooks.find(byId(step.webhookId))
return (
<Stack spacing={4}>
<Alert
status={step.webhook.url ? 'success' : 'info'}
bgColor={step.webhook.url ? undefined : 'blue.50'}
status={webhook?.url ? 'success' : 'info'}
bgColor={webhook?.url ? undefined : 'blue.50'}
rounded="md"
>
<AlertIcon />
{step.webhook.url ? (
{webhook?.url ? (
<>Your zap is correctly configured 🚀</>
) : (
<Stack>
Expand All @@ -40,7 +44,7 @@ export const ZapierSettings = ({ step }: Props) => {
</Stack>
)}
</Alert>
{step.webhook.url && <Input value={step.webhook.url} isDisabled />}
{webhook?.url && <Input value={webhook?.url} isDisabled />}
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Text } from '@chakra-ui/react'
import { useTypebot } from 'contexts/TypebotContext'
import { WebhookStep } from 'models'
import { byId } from 'utils'

type Props = {
step: WebhookStep
}

export const WebhookContent = ({ step: { webhook } }: Props) => {
export const WebhookContent = ({ step: { webhookId } }: Props) => {
const { webhooks } = useTypebot()
const webhook = webhooks.find(byId(webhookId))

if (!webhook?.url) return <Text color="gray.500">Configure...</Text>
return (
<Text isTruncated pr="6">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Text } from '@chakra-ui/react'
import { useTypebot } from 'contexts/TypebotContext'
import { ZapierStep } from 'models'
import { isNotDefined } from 'utils'
import { byId, isNotDefined } from 'utils'

type Props = {
step: ZapierStep
}

export const ZapierContent = ({ step }: Props) => {
if (isNotDefined(step.webhook.body))
export const ZapierContent = ({ step: { webhookId } }: Props) => {
const { webhooks } = useTypebot()
const webhook = webhooks.find(byId(webhookId))

if (isNotDefined(webhook?.body))
return <Text color="gray.500">Configure...</Text>
return (
<Text isTruncated pr="6">
{step.webhook.url ? 'Enabled' : 'Disabled'}
{webhook?.url ? 'Enabled' : 'Disabled'}
</Text>
)
}
79 changes: 68 additions & 11 deletions apps/builder/components/shared/buttons/PublishButton.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,75 @@
import { Button } from '@chakra-ui/react'
import {
Button,
HStack,
IconButton,
Stack,
Tooltip,
Text,
Menu,
MenuButton,
MenuList,
MenuItem,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from 'assets/icons'
import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { timeSince } from 'services/utils'
import { isNotDefined } from 'utils'

export const PublishButton = () => {
const { isPublishing, isPublished, publishTypebot } = useTypebot()
const {
isPublishing,
isPublished,
publishTypebot,
publishedTypebot,
restorePublishedTypebot,
} = useTypebot()

return (
<Button
ml={2}
colorScheme="blue"
isLoading={isPublishing}
isDisabled={isPublished}
onClick={publishTypebot}
>
{isPublished ? 'Published' : 'Publish'}
</Button>
<HStack spacing="1px">
<Tooltip
borderRadius="md"
hasArrow
placement="bottom-end"
label={
<Stack>
<Text>There are non published changes.</Text>
<Text fontStyle="italic">
Published version from{' '}
{publishedTypebot &&
timeSince(publishedTypebot.updatedAt.toString())}{' '}
ago
</Text>
</Stack>
}
isDisabled={isNotDefined(publishedTypebot)}
>
<Button
colorScheme="blue"
isLoading={isPublishing}
isDisabled={isPublished}
onClick={publishTypebot}
borderRightRadius={publishedTypebot && !isPublished ? 0 : undefined}
>
{isPublished ? 'Published' : 'Publish'}
</Button>
</Tooltip>

{publishedTypebot && !isPublished && (
<Menu>
<MenuButton
as={IconButton}
colorScheme="blue"
borderLeftRadius={0}
icon={<ChevronLeftIcon transform="rotate(-90deg)" />}
aria-label="Show published version"
/>
<MenuList>
<MenuItem onClick={restorePublishedTypebot}>
Restore published version
</MenuItem>
</MenuList>
</Menu>
)}
</HStack>
)
}
Loading

2 comments on commit e17a1a0

@vercel
Copy link

@vercel vercel bot commented on e17a1a0 Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on e17a1a0 Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

app.typebot.io
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app

Please sign in to comment.