diff --git a/.eslintignore b/.eslintignore
index b512c09d4..3c3629e64 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1 +1 @@
-node_modules
\ No newline at end of file
+node_modules
diff --git a/.prettierignore b/.prettierignore
index 561504a01..c6fdd0e10 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,4 @@
emojiList.json
iconNames.ts
reporters
+.last-run.json
diff --git a/apps/builder/.eslintignore b/apps/builder/.eslintignore
new file mode 100644
index 000000000..ebd9b0fa7
--- /dev/null
+++ b/apps/builder/.eslintignore
@@ -0,0 +1 @@
+src/test/reporters
\ No newline at end of file
diff --git a/apps/builder/package.json b/apps/builder/package.json
index 9b357cfc0..344128904 100644
--- a/apps/builder/package.json
+++ b/apps/builder/package.json
@@ -100,7 +100,7 @@
},
"devDependencies": {
"@chakra-ui/styled-system": "2.9.2",
- "@playwright/test": "1.43.1",
+ "@playwright/test": "1.45.2",
"@typebot.io/billing": "workspace:*",
"@typebot.io/forge": "workspace:*",
"@typebot.io/forge-repository": "workspace:*",
diff --git a/apps/builder/playwright.config.ts b/apps/builder/playwright.config.ts
index 91b2463ed..72fe4e447 100644
--- a/apps/builder/playwright.config.ts
+++ b/apps/builder/playwright.config.ts
@@ -10,13 +10,13 @@ export default defineConfig({
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
},
forbidOnly: !!process.env.CI,
- workers: process.env.CI ? 1 : 3,
- retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : 4,
+ retries: process.env.CI ? 2 : 1,
reporter: [
[process.env.CI ? 'github' : 'list'],
['html', { outputFolder: 'src/test/reporters' }],
],
- maxFailures: process.env.CI ? 10 : undefined,
+ maxFailures: 10,
webServer: process.env.CI
? {
command: 'pnpm run start',
diff --git a/apps/builder/src/components/icons.tsx b/apps/builder/src/components/icons.tsx
index aeeecc9ba..bad82bc2e 100644
--- a/apps/builder/src/components/icons.tsx
+++ b/apps/builder/src/components/icons.tsx
@@ -695,3 +695,11 @@ export const VideoPopoverIcon = (props: IconProps) => (
/>
)
+
+export const WalletIcon = (props: IconProps) => (
+
+
+
+
+
+)
diff --git a/apps/builder/src/components/inputs/NumberInput.tsx b/apps/builder/src/components/inputs/NumberInput.tsx
index 82bb2c688..8b7d7027d 100644
--- a/apps/builder/src/components/inputs/NumberInput.tsx
+++ b/apps/builder/src/components/inputs/NumberInput.tsx
@@ -49,6 +49,7 @@ export const NumberInput = ({
helperText,
...props
}: Props) => {
+ const [isTouched, setIsTouched] = useState(false)
const [value, setValue] = useState(defaultValue?.toString() ?? '')
const onValueChangeDebounced = useDebouncedCallback(
@@ -56,6 +57,11 @@ export const NumberInput = ({
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
)
+ useEffect(() => {
+ if (isTouched || value !== '' || !defaultValue) return
+ setValue(defaultValue?.toString() ?? '')
+ }, [defaultValue, isTouched, value])
+
useEffect(
() => () => {
onValueChangeDebounced.flush()
@@ -64,6 +70,7 @@ export const NumberInput = ({
)
const handleValueChange = (newValue: string) => {
+ if (!isTouched) setIsTouched(true)
if (value.startsWith('{{') && value.endsWith('}}') && newValue !== '')
return
setValue(newValue)
diff --git a/apps/builder/src/components/inputs/SwitchWithLabel.tsx b/apps/builder/src/components/inputs/SwitchWithLabel.tsx
index 4813108a0..da6cb0c64 100644
--- a/apps/builder/src/components/inputs/SwitchWithLabel.tsx
+++ b/apps/builder/src/components/inputs/SwitchWithLabel.tsx
@@ -12,7 +12,7 @@ import { isDefined } from '@typebot.io/lib'
export type SwitchWithLabelProps = {
label: string
- initialValue: boolean
+ initialValue: boolean | undefined
moreInfoContent?: string
onCheckChange?: (isChecked: boolean) => void
justifyContent?: FormControlProps['justifyContent']
diff --git a/apps/builder/src/components/logos/StripeLogo.tsx b/apps/builder/src/components/logos/StripeLogo.tsx
new file mode 100644
index 000000000..880e69fa3
--- /dev/null
+++ b/apps/builder/src/components/logos/StripeLogo.tsx
@@ -0,0 +1,16 @@
+import { Icon, IconProps } from '@chakra-ui/react'
+
+export const StripeLogo = (props: IconProps) => {
+ return (
+
+
+
+
+ )
+}
diff --git a/apps/builder/src/features/blocks/inputs/payment/components/StripeConfigModal.tsx b/apps/builder/src/features/blocks/inputs/payment/components/StripeConfigModal.tsx
index 14931e119..b283f8ea6 100644
--- a/apps/builder/src/features/blocks/inputs/payment/components/StripeConfigModal.tsx
+++ b/apps/builder/src/features/blocks/inputs/payment/components/StripeConfigModal.tsx
@@ -36,6 +36,21 @@ export const StripeConfigModal = ({
onNewCredentials,
onClose,
}: Props) => {
+ return (
+
+
+
+
+ )
+}
+
+export const StripeCreateModalContent = ({
+ onNewCredentials,
+ onClose,
+}: Pick) => {
const { t } = useTranslate()
const { user } = useUser()
const { workspace } = useWorkspace()
@@ -99,7 +114,8 @@ export const StripeConfigModal = ({
test: { ...stripeConfig.test, secretKey },
})
- const createCredentials = async () => {
+ const createCredentials = async (e: React.FormEvent) => {
+ e.preventDefault()
if (!user?.email || !workspace?.id) return
mutate({
credentials: {
@@ -120,16 +136,16 @@ export const StripeConfigModal = ({
},
})
}
+
return (
-
-
-
-
- {t('blocks.inputs.payment.settings.stripeConfig.title.label')}
-
-
+
+
+ {t('blocks.inputs.payment.settings.stripeConfig.title.label')}
+
+
+
-
+
+
)
}
diff --git a/apps/builder/src/features/blocks/inputs/payment/components/UpdateStripeCredentialsModalContent.tsx b/apps/builder/src/features/blocks/inputs/payment/components/UpdateStripeCredentialsModalContent.tsx
new file mode 100644
index 000000000..425d065f9
--- /dev/null
+++ b/apps/builder/src/features/blocks/inputs/payment/components/UpdateStripeCredentialsModalContent.tsx
@@ -0,0 +1,236 @@
+import { TextInput } from '@/components/inputs'
+import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
+import { TextLink } from '@/components/TextLink'
+import { useUser } from '@/features/account/hooks/useUser'
+import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
+import { trpc } from '@/lib/trpc'
+import {
+ Text,
+ ModalContent,
+ ModalHeader,
+ ModalCloseButton,
+ ModalBody,
+ Stack,
+ FormLabel,
+ HStack,
+ FormControl,
+ ModalFooter,
+ Button,
+} from '@chakra-ui/react'
+import { useTranslate } from '@tolgee/react'
+import { isNotEmpty } from '@typebot.io/lib'
+import { StripeCredentials } from '@typebot.io/schemas'
+import { useEffect, useState } from 'react'
+
+type Props = {
+ credentialsId: string
+ onUpdate: () => void
+}
+
+export const UpdateStripeCredentialsModalContent = ({
+ credentialsId,
+ onUpdate,
+}: Props) => {
+ const { t } = useTranslate()
+ const { user } = useUser()
+ const { workspace } = useWorkspace()
+ const [isCreating, setIsCreating] = useState(false)
+ const [stripeConfig, setStripeConfig] = useState<
+ StripeCredentials['data'] & { name: string }
+ >()
+
+ const { data: existingCredentials } =
+ trpc.credentials.getCredentials.useQuery(
+ {
+ credentialsId,
+ workspaceId: workspace!.id,
+ },
+ {
+ enabled: !!workspace?.id,
+ }
+ )
+
+ useEffect(() => {
+ if (!existingCredentials || stripeConfig) return
+ setStripeConfig({
+ name: existingCredentials.name,
+ live: existingCredentials.data.live,
+ test: existingCredentials.data.test,
+ })
+ }, [existingCredentials, stripeConfig])
+
+ const { mutate } = trpc.credentials.updateCredentials.useMutation({
+ onMutate: () => setIsCreating(true),
+ onSettled: () => setIsCreating(false),
+ onSuccess: () => {
+ onUpdate()
+ },
+ })
+
+ const handleNameChange = (name: string) =>
+ stripeConfig &&
+ setStripeConfig({
+ ...stripeConfig,
+ name,
+ })
+
+ const handlePublicKeyChange = (publicKey: string) =>
+ stripeConfig &&
+ setStripeConfig({
+ ...stripeConfig,
+ live: { ...stripeConfig.live, publicKey },
+ })
+
+ const handleSecretKeyChange = (secretKey: string) =>
+ stripeConfig &&
+ setStripeConfig({
+ ...stripeConfig,
+ live: { ...stripeConfig.live, secretKey },
+ })
+
+ const handleTestPublicKeyChange = (publicKey: string) =>
+ stripeConfig &&
+ setStripeConfig({
+ ...stripeConfig,
+ test: { ...stripeConfig.test, publicKey },
+ })
+
+ const handleTestSecretKeyChange = (secretKey: string) =>
+ stripeConfig &&
+ setStripeConfig({
+ ...stripeConfig,
+ test: { ...stripeConfig.test, secretKey },
+ })
+
+ const updateCreds = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!user?.email || !workspace?.id || !stripeConfig) return
+ mutate({
+ credentialsId,
+ credentials: {
+ data: {
+ live: stripeConfig.live,
+ test: {
+ publicKey: isNotEmpty(stripeConfig.test.publicKey)
+ ? stripeConfig.test.publicKey
+ : undefined,
+ secretKey: isNotEmpty(stripeConfig.test.secretKey)
+ ? stripeConfig.test.secretKey
+ : undefined,
+ },
+ },
+ name: stripeConfig.name,
+ type: 'stripe',
+ workspaceId: workspace.id,
+ },
+ })
+ }
+
+ return (
+
+
+ {t('blocks.inputs.payment.settings.stripeConfig.title.label')}
+
+
+
+
+ )
+}
diff --git a/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts b/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts
index 4b3a3a41c..49a8cc2a7 100644
--- a/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts
+++ b/apps/builder/src/features/blocks/inputs/payment/payment.spec.ts
@@ -20,7 +20,8 @@ test.describe('Payment input block', () => {
await page.goto(`/typebots/${typebotId}/edit`)
await page.click('text=Configure...')
- await page.getByRole('button', { name: 'Add Stripe account' }).click()
+ await page.getByRole('button', { name: 'Select Stripe account' }).click()
+ await page.getByRole('menuitem', { name: 'Connect new' }).click()
await page.fill('[placeholder="Typebot"]', 'My Stripe Account')
await page.fill('[placeholder="sk_test_..."]', env.STRIPE_SECRET_KEY ?? '')
await page.fill('[placeholder="sk_live_..."]', env.STRIPE_SECRET_KEY ?? '')
diff --git a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsConnectModal.tsx b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsConnectModal.tsx
index 606a636d5..35d790510 100644
--- a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsConnectModal.tsx
+++ b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsConnectModal.tsx
@@ -21,8 +21,8 @@ import { getGoogleSheetsConsentScreenUrlQuery } from '../queries/getGoogleSheets
type Props = {
isOpen: boolean
- typebotId: string
- blockId: string
+ typebotId?: string
+ blockId?: string
onClose: () => void
}
@@ -32,30 +32,45 @@ export const GoogleSheetConnectModal = ({
isOpen,
onClose,
}: Props) => {
- const { workspace } = useWorkspace()
return (
-
- Connect Spreadsheets
-
-
-
- Make sure to check all the permissions so that the integration works
- as expected:
-
-
-
- Google does not provide more granular permissions than
- "read" or "write" access. That's why it
- states that Typebot can also delete your spreadsheets which it
- won't.
-
-
+
+
+ )
+}
+
+export const GoogleSheetConnectModalContent = ({
+ typebotId,
+ blockId,
+}: {
+ typebotId?: string
+ blockId?: string
+}) => {
+ const { workspace } = useWorkspace()
+
+ return (
+
+ Connect Spreadsheets
+
+
+
+ Make sure to check all the permissions so that the integration works
+ as expected:
+
+
+
+ Google does not provide more granular permissions than
+ "read" or "write" access. That's why it
+ states that Typebot can also delete your spreadsheets which it
+ won't.
+
+
+ {workspace?.id && (
}
@@ -64,19 +79,18 @@ export const GoogleSheetConnectModal = ({
variant="outline"
href={getGoogleSheetsConsentScreenUrlQuery(
window.location.href,
+ workspace.id,
blockId,
- workspace?.id,
typebotId
)}
mx="auto"
>
Continue with Google
-
-
-
-
-
-
+ )}
+
+
+
+
)
}
diff --git a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsLogo.tsx b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsLogo.tsx
index 65eda1a21..db05f2ff1 100644
--- a/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsLogo.tsx
+++ b/apps/builder/src/features/blocks/integrations/googleSheets/components/GoogleSheetsLogo.tsx
@@ -5,44 +5,22 @@ export const GoogleSheetsLogo = (props: IconProps) => (
Sheets-icon
Created with Sketch.
-
-
-
+
+
+
-
-
-
-
+
+
+
+
(
fy="2.71744318%"
r="161.248516%"
gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)"
- id="radialGradient-16"
>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
+
(
diff --git a/apps/builder/src/features/blocks/integrations/googleSheets/queries/getGoogleSheetsConsentScreenUrlQuery.ts b/apps/builder/src/features/blocks/integrations/googleSheets/queries/getGoogleSheetsConsentScreenUrlQuery.ts
index fdcfcc480..39991c172 100644
--- a/apps/builder/src/features/blocks/integrations/googleSheets/queries/getGoogleSheetsConsentScreenUrlQuery.ts
+++ b/apps/builder/src/features/blocks/integrations/googleSheets/queries/getGoogleSheetsConsentScreenUrlQuery.ts
@@ -2,8 +2,8 @@ import { stringify } from 'qs'
export const getGoogleSheetsConsentScreenUrlQuery = (
redirectUrl: string,
- blockId: string,
- workspaceId?: string,
+ workspaceId: string,
+ blockId?: string,
typebotId?: string
) => {
const queryParams = stringify({
diff --git a/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigForm.tsx b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigForm.tsx
index c117025df..ec5ed986a 100644
--- a/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigForm.tsx
+++ b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigForm.tsx
@@ -6,79 +6,93 @@ import { SmtpCredentials } from '@typebot.io/schemas'
import React from 'react'
type Props = {
- config: SmtpCredentials['data']
+ config: SmtpCredentials['data'] | undefined
onConfigChange: (config: SmtpCredentials['data']) => void
}
export const SmtpConfigForm = ({ config, onConfigChange }: Props) => {
const handleFromEmailChange = (email: string) =>
- onConfigChange({ ...config, from: { ...config.from, email } })
+ config && onConfigChange({ ...config, from: { ...config.from, email } })
+
const handleFromNameChange = (name: string) =>
- onConfigChange({ ...config, from: { ...config.from, name } })
- const handleHostChange = (host: string) => onConfigChange({ ...config, host })
+ config && onConfigChange({ ...config, from: { ...config.from, name } })
+
+ const handleHostChange = (host: string) =>
+ config && onConfigChange({ ...config, host })
+
const handleUsernameChange = (username: string) =>
- onConfigChange({ ...config, username })
+ config && onConfigChange({ ...config, username })
+
const handlePasswordChange = (password: string) =>
- onConfigChange({ ...config, password })
+ config && onConfigChange({ ...config, password })
+
const handleTlsCheck = (isTlsEnabled: boolean) =>
- onConfigChange({ ...config, isTlsEnabled })
+ config && onConfigChange({ ...config, isTlsEnabled })
+
const handlePortNumberChange = (port?: number) =>
- isDefined(port) && onConfigChange({ ...config, port })
+ config && isDefined(port) && onConfigChange({ ...config, port })
return (
-
+
)
diff --git a/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigModal.tsx b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigModal.tsx
index 48d3b8e69..b1fad022e 100644
--- a/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigModal.tsx
+++ b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpConfigModal.tsx
@@ -26,9 +26,25 @@ type Props = {
export const SmtpConfigModal = ({
isOpen,
- onNewCredentials,
onClose,
+ onNewCredentials,
}: Props) => {
+ return (
+
+
+ {
+ onNewCredentials(id)
+ onClose()
+ }}
+ />
+
+ )
+}
+
+export const SmtpCreateModalContent = ({
+ onNewCredentials,
+}: Pick) => {
const { user } = useUser()
const { workspace } = useWorkspace()
const [isCreating, setIsCreating] = useState(false)
@@ -53,11 +69,11 @@ export const SmtpConfigModal = ({
onSuccess: (data) => {
refetchCredentials()
onNewCredentials(data.credentialsId)
- onClose()
},
})
- const handleCreateClick = async () => {
+ const handleCreateClick = async (e: React.FormEvent) => {
+ e.preventDefault()
if (!user?.email || !workspace?.id) return
setIsCreating(true)
const { error: testSmtpError } = await testSmtpConfig(
@@ -82,19 +98,18 @@ export const SmtpConfigModal = ({
})
}
return (
-
-
-
- Create SMTP config
-
+
+ Create SMTP config
+
+
-
+
+
)
}
diff --git a/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpUpdateModalContent.tsx b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpUpdateModalContent.tsx
new file mode 100644
index 000000000..00ba690b8
--- /dev/null
+++ b/apps/builder/src/features/blocks/integrations/sendEmail/components/SmtpUpdateModalContent.tsx
@@ -0,0 +1,113 @@
+import {
+ ModalContent,
+ ModalHeader,
+ ModalCloseButton,
+ ModalBody,
+ ModalFooter,
+ Button,
+} from '@chakra-ui/react'
+import { useUser } from '@/features/account/hooks/useUser'
+import React, { useEffect, useState } from 'react'
+import { isNotDefined } from '@typebot.io/lib'
+import { SmtpConfigForm } from './SmtpConfigForm'
+import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
+import { useToast } from '@/hooks/useToast'
+import { testSmtpConfig } from '../queries/testSmtpConfigQuery'
+import { trpc } from '@/lib/trpc'
+import { SmtpCredentials } from '@typebot.io/schemas/features/blocks/integrations/sendEmail/schema'
+
+type Props = {
+ credentialsId: string
+ onUpdate: () => void
+}
+
+export const SmtpUpdateModalContent = ({ credentialsId, onUpdate }: Props) => {
+ const { user } = useUser()
+ const { workspace } = useWorkspace()
+ const [isCreating, setIsCreating] = useState(false)
+ const { showToast } = useToast()
+ const [smtpConfig, setSmtpConfig] = useState()
+
+ const { data: existingCredentials } =
+ trpc.credentials.getCredentials.useQuery(
+ {
+ workspaceId: workspace!.id,
+ credentialsId: credentialsId,
+ },
+ {
+ enabled: !!workspace?.id,
+ }
+ )
+
+ useEffect(() => {
+ if (!existingCredentials || smtpConfig) return
+ setSmtpConfig(existingCredentials.data)
+ }, [existingCredentials, smtpConfig])
+
+ const { mutate } = trpc.credentials.updateCredentials.useMutation({
+ onSettled: () => setIsCreating(false),
+ onError: (err) => {
+ showToast({
+ description: err.message,
+ status: 'error',
+ })
+ },
+ onSuccess: () => {
+ onUpdate()
+ },
+ })
+
+ const handleUpdateClick = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!user?.email || !workspace?.id || !smtpConfig) return
+ setIsCreating(true)
+ const { error: testSmtpError } = await testSmtpConfig(
+ smtpConfig,
+ user.email
+ )
+ if (testSmtpError) {
+ setIsCreating(false)
+ return showToast({
+ title: 'Invalid configuration',
+ description: "We couldn't send the test email with your configuration",
+ })
+ }
+ mutate({
+ credentialsId,
+ credentials: {
+ data: smtpConfig,
+ name: smtpConfig.from.email as string,
+ type: 'smtp',
+ workspaceId: workspace.id,
+ },
+ })
+ }
+ return (
+
+ Update SMTP config
+
+
+
+ )
+}
diff --git a/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts b/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts
index fb8f8bc6e..ca28f83a4 100644
--- a/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts
+++ b/apps/builder/src/features/blocks/integrations/webhook/webhook.spec.ts
@@ -1,98 +1,77 @@
import test, { expect, Page } from '@playwright/test'
-import {
- createWebhook,
- importTypebotInDatabase,
-} from '@typebot.io/playwright/databaseActions'
+import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { getTestAsset } from '@/test/utils/playwright'
import { apiToken } from '@typebot.io/playwright/databaseSetup'
import { env } from '@typebot.io/env'
-import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
+import { omit } from '@typebot.io/lib/utils'
-test.describe('Builder', () => {
- test('easy configuration should work', async ({ page }) => {
- const typebotId = createId()
- await importTypebotInDatabase(
- getTestAsset('typebots/integrations/easyConfigWebhook.json'),
- {
- id: typebotId,
- }
- )
- await createWebhook(typebotId, { method: HttpMethod.POST })
- await page.goto(`/typebots/${typebotId}/edit`)
- await page.click('text=Configure...')
- await page.fill(
- 'input[placeholder="Paste URL..."]',
- `${env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
- )
- await page.click('text=Test the request')
- await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
- `"Group #1": "answer value", "Group #2": "20", "Group #2 (1)": "Yes"`,
- { timeout: 10000 }
- )
- })
+test.describe.configure({ mode: 'parallel' })
- test('its configuration should work', async ({ page }) => {
- const typebotId = createId()
- await importTypebotInDatabase(
- getTestAsset('typebots/integrations/webhook.json'),
- {
- id: typebotId,
- }
- )
- await createWebhook(typebotId)
+test('editor configuration should work', async ({ page }) => {
+ const typebotId = createId()
+ await importTypebotInDatabase(
+ getTestAsset('typebots/integrations/webhook.json'),
+ {
+ id: typebotId,
+ }
+ )
- await page.goto(`/typebots/${typebotId}/edit`)
- await page.click('text=Configure...')
- await page.fill(
- 'input[placeholder="Paste URL..."]',
- `${env.NEXTAUTH_URL}/api/mock/webhook`
- )
- await page.click('text=Advanced configuration')
- await page.getByRole('button', { name: 'GET' }).click()
- await page.click('text=POST')
+ await page.goto(`/typebots/${typebotId}/edit`)
+ await page.click('text=Configure...')
+ await page.fill(
+ 'input[placeholder="Paste URL..."]',
+ `${env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
+ )
+ await page.click('text=Test the request')
+ await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
+ `"Group #1": "Go", "secret 1": "content"`,
+ { timeout: 10000 }
+ )
- await page.click('text=Query params')
- await page.click('text=Add a param')
- await page.fill('input[placeholder="e.g. email"]', 'firstParam')
- await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
+ await page.fill(
+ 'input[placeholder="Paste URL..."]',
+ `${env.NEXTAUTH_URL}/api/mock/webhook`
+ )
+ await page.click('text=Advanced configuration')
- await page.click('text=Add a param')
- await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
- await page.fill(
- 'input[placeholder="e.g. {{Email}}"] >> nth=1',
- '{{secret 2}}'
- )
+ await page.click('text=Query params')
+ await page.click('text=Add a param')
+ await page.fill('input[placeholder="e.g. email"]', 'firstParam')
+ await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
- await page.click('text=Headers')
- await page.waitForTimeout(200)
- await page.getByRole('button', { name: 'Add a value' }).click()
- await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
- await page.fill(
- 'input[placeholder="e.g. application/json"]',
- '{{secret 3}}'
- )
+ await page.click('text=Add a param')
+ await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
+ await page.fill(
+ 'input[placeholder="e.g. {{Email}}"] >> nth=1',
+ '{{secret 2}}'
+ )
- await page.click('text=Body')
- await page.click('text=Custom body')
- await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
+ await page.click('text=Headers')
+ await page.waitForTimeout(200)
+ await page.getByRole('button', { name: 'Add a value' }).click()
+ await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
+ await page.fill('input[placeholder="e.g. application/json"]', '{{secret 3}}')
- await page.click('text=Variable values for test')
- await addTestVariable(page, 'secret 1', 'secret1')
- await addTestVariable(page, 'secret 2', 'secret2')
- await addTestVariable(page, 'secret 3', 'secret3')
- await addTestVariable(page, 'secret 4', 'secret4')
+ await page.click('text=Body')
+ await page.click('text=Custom body')
+ await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
- await page.click('text=Test the request')
- await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
- '"statusCode": 200'
- )
+ await page.click('text=Variable values for test')
+ await addTestVariable(page, 'secret 1', 'secret1')
+ await addTestVariable(page, 'secret 2', 'secret2')
+ await addTestVariable(page, 'secret 3', 'secret3')
+ await addTestVariable(page, 'secret 4', 'secret4')
- await page.click('text=Save in variables')
- await page.click('text=Add an entry >> nth=-1')
- await page.click('input[placeholder="Select the data"]')
- await page.click('text=data.flatMap(item => item.name)')
- })
+ await page.click('text=Test the request')
+ await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
+ '"statusCode": 200'
+ )
+
+ await page.click('text=Save in variables')
+ await page.click('text=Add an entry >> nth=-1')
+ await page.click('input[placeholder="Select the data"]')
+ await page.click('text=data.flatMap(item => item.name)')
})
const addTestVariable = async (page: Page, name: string, value: string) => {
@@ -102,83 +81,70 @@ const addTestVariable = async (page: Page, name: string, value: string) => {
await page.fill('input >> nth=-1', value)
}
-test.describe('API', () => {
- const typebotId = 'webhook-flow'
+test('Webhook API endpoints should work', async ({ request }) => {
+ const typebotId = createId()
+ await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
+ id: typebotId,
+ })
- test.beforeAll(async () => {
- try {
- await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
- id: typebotId,
- })
- await createWebhook(typebotId)
- } catch (err) {
- console.log(err)
+ // GET webhook blocks
+ const getResponse = await request.get(
+ `/api/v1/typebots/${typebotId}/webhookBlocks`,
+ {
+ headers: { Authorization: `Bearer ${apiToken}` },
}
+ )
+ const { webhookBlocks } = await getResponse.json()
+ expect(webhookBlocks).toHaveLength(1)
+ expect(webhookBlocks[0]).toEqual({
+ id: 'webhookBlock',
+ label: 'Webhook > webhookBlock',
+ type: 'Webhook',
})
- test('can get webhook blocks', async ({ request }) => {
- const response = await request.get(
- `/api/v1/typebots/${typebotId}/webhookBlocks`,
- {
- headers: { Authorization: `Bearer ${apiToken}` },
- }
- )
- const { webhookBlocks } = await response.json()
- expect(webhookBlocks).toHaveLength(1)
- expect(webhookBlocks[0]).toEqual({
- id: 'webhookBlock',
- label: 'Webhook > webhookBlock',
- type: 'Webhook',
- })
+ // Subscribe webhook
+ const url = 'https://test.com'
+ const subscribeResponse = await request.post(
+ `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/subscribe`,
+ {
+ headers: {
+ Authorization: `Bearer ${apiToken}`,
+ },
+ data: { url },
+ }
+ )
+ expect(await subscribeResponse.json()).toEqual({
+ id: 'webhookBlock',
+ url,
})
- test('can subscribe webhook', async ({ request }) => {
- const url = 'https://test.com'
- const response = await request.post(
- `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/subscribe`,
- {
- headers: {
- Authorization: `Bearer ${apiToken}`,
- },
- data: { url },
- }
- )
- const body = await response.json()
- expect(body).toEqual({
- id: 'webhookBlock',
- url,
- })
+ // Unsubscribe webhook
+ const unsubResponse = await request.post(
+ `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/unsubscribe`,
+ {
+ headers: { Authorization: `Bearer ${apiToken}` },
+ }
+ )
+ expect(await unsubResponse.json()).toEqual({
+ id: 'webhookBlock',
+ url: null,
})
- test('can unsubscribe webhook', async ({ request }) => {
- const response = await request.post(
- `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/unsubscribe`,
- {
- headers: { Authorization: `Bearer ${apiToken}` },
- }
- )
- const body = await response.json()
- expect(body).toEqual({
- id: 'webhookBlock',
- url: null,
- })
- })
+ // Get sample result
+ const sampleResponse = await request.get(
+ `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/getResultExample`,
+ {
+ headers: { Authorization: `Bearer ${apiToken}` },
+ }
+ )
+ const sample = await sampleResponse.json()
- test('can get a sample result', async ({ request }) => {
- const response = await request.get(
- `/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/getResultExample`,
- {
- headers: { Authorization: `Bearer ${apiToken}` },
- }
- )
- const data = await response.json()
- expect(data.resultExample).toMatchObject({
- message: 'This is a sample result, it has been generated ⬇️',
- Welcome: 'Hi!',
- Email: 'user@email.com',
- Name: 'answer value',
- Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
- 'Additional information': 'answer value',
- })
+ expect(omit(sample.resultExample, 'submittedAt')).toMatchObject({
+ message: 'This is a sample result, it has been generated ⬇️',
+ Welcome: 'Hi!',
+ Email: 'user@email.com',
+ Name: 'answer value',
+ Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
+ 'Additional information': 'answer value',
})
})
diff --git a/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiCredentialsModal.tsx b/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiCredentialsModal.tsx
deleted file mode 100644
index 60c14dcc1..000000000
--- a/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiCredentialsModal.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { TextInput } from '@/components/inputs/TextInput'
-import { TextLink } from '@/components/TextLink'
-import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
-import { useToast } from '@/hooks/useToast'
-import { trpc } from '@/lib/trpc'
-import {
- Modal,
- ModalOverlay,
- ModalContent,
- ModalHeader,
- ModalCloseButton,
- ModalBody,
- Stack,
- ModalFooter,
- Button,
-} from '@chakra-ui/react'
-import React, { useState } from 'react'
-
-const zemanticAIDashboardPage = 'https://zemantic.ai/dashboard/settings'
-
-type Props = {
- isOpen: boolean
- onClose: () => void
- onNewCredentials: (id: string) => void
-}
-
-export const ZemanticAiCredentialsModal = ({
- isOpen,
- onClose,
- onNewCredentials,
-}: Props) => {
- const { workspace } = useWorkspace()
- const { showToast } = useToast()
- const [apiKey, setApiKey] = useState('')
- const [name, setName] = useState('')
-
- const [isCreating, setIsCreating] = useState(false)
-
- const {
- credentials: {
- listCredentials: { refetch: refetchCredentials },
- },
- } = trpc.useContext()
- const { mutate } = trpc.credentials.createCredentials.useMutation({
- onMutate: () => setIsCreating(true),
- onSettled: () => setIsCreating(false),
- onError: (err) => {
- showToast({
- description: err.message,
- status: 'error',
- })
- },
- onSuccess: (data) => {
- refetchCredentials()
- onNewCredentials(data.credentialsId)
- onClose()
- },
- })
-
- const createZemanticAiCredentials = async (e: React.FormEvent) => {
- e.preventDefault()
- if (!workspace) return
- mutate({
- credentials: {
- type: 'zemanticAi',
- workspaceId: workspace.id,
- name,
- data: {
- apiKey,
- },
- },
- })
- }
-
- return (
-
-
-
- Add Zemantic AI account
-
-
-
-
- )
-}
diff --git a/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiSettings.tsx b/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiSettings.tsx
index c6442ecfa..47695d832 100644
--- a/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiSettings.tsx
+++ b/apps/builder/src/features/blocks/integrations/zemanticAi/ZemanticAiSettings.tsx
@@ -1,6 +1,4 @@
import { TextInput, Textarea, NumberInput } from '@/components/inputs'
-import { CredentialsDropdown } from '@/features/credentials/components/CredentialsDropdown'
-import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import {
Accordion,
AccordionButton,
@@ -9,15 +7,12 @@ import {
AccordionPanel,
Stack,
Text,
- useDisclosure,
} from '@chakra-ui/react'
import { isEmpty } from '@typebot.io/lib'
import { ZemanticAiBlock } from '@typebot.io/schemas'
-import { ZemanticAiCredentialsModal } from './ZemanticAiCredentialsModal'
import { ProjectsDropdown } from './ProjectsDropdown'
import { SearchResponseItem } from './SearchResponseItem'
import { TableList } from '@/components/TableList'
-import { createId } from '@paralleldrive/cuid2'
type Props = {
block: ZemanticAiBlock
@@ -28,22 +23,6 @@ export const ZemanticAiSettings = ({
block: { id: blockId, options },
onOptionsChange,
}: Props) => {
- const { workspace } = useWorkspace()
- const { isOpen, onOpen, onClose } = useDisclosure()
-
- const updateCredentialsId = (credentialsId: string | undefined) => {
- onOptionsChange({
- ...options,
- credentialsId,
- responseMapping: [
- {
- id: createId(),
- valueToExtract: 'Summary',
- },
- ],
- })
- }
-
const updateProjectId = (projectId: string | undefined) => {
onOptionsChange({
...options,
@@ -92,23 +71,6 @@ export const ZemanticAiSettings = ({
return (
- {workspace && (
- <>
-
-
- >
- )}
{options?.credentialsId && (
<>
+ i.pick(inputShape)
+ ),
])
.and(z.object({ id: z.string().cuid2().optional() })),
})
@@ -53,7 +53,12 @@ export const createCredentials = authenticatedProcedure
})
)
.mutation(async ({ input: { credentials }, ctx: { user } }) => {
- if (await isNotAvailable(credentials.name, credentials.type))
+ if (
+ await isNotAvailable(
+ credentials.name,
+ credentials.type as Credentials['type']
+ )
+ )
throw new TRPCError({
code: 'CONFLICT',
message: 'Credentials already exist.',
@@ -62,7 +67,7 @@ export const createCredentials = authenticatedProcedure
where: {
id: credentials.workspaceId,
},
- select: { id: true, members: true },
+ select: { id: true, members: { select: { userId: true, role: true } } },
})
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
diff --git a/apps/builder/src/features/credentials/api/deleteCredentials.ts b/apps/builder/src/features/credentials/api/deleteCredentials.ts
index 7d542a9fe..71e0808bb 100644
--- a/apps/builder/src/features/credentials/api/deleteCredentials.ts
+++ b/apps/builder/src/features/credentials/api/deleteCredentials.ts
@@ -30,11 +30,8 @@ export const deleteCredentials = authenticatedProcedure
const workspace = await prisma.workspace.findFirst({
where: {
id: workspaceId,
- members: {
- some: { userId: user.id, role: { in: ['ADMIN', 'MEMBER'] } },
- },
},
- select: { id: true, members: true },
+ select: { id: true, members: { select: { userId: true, role: true } } },
})
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
throw new TRPCError({
diff --git a/apps/builder/src/features/credentials/api/getCredentials.ts b/apps/builder/src/features/credentials/api/getCredentials.ts
new file mode 100644
index 000000000..c40a62cef
--- /dev/null
+++ b/apps/builder/src/features/credentials/api/getCredentials.ts
@@ -0,0 +1,66 @@
+import prisma from '@typebot.io/lib/prisma'
+import { authenticatedProcedure } from '@/helpers/server/trpc'
+import { TRPCError } from '@trpc/server'
+import { z } from 'zod'
+import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
+import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
+
+export const getCredentials = authenticatedProcedure
+ .meta({
+ openapi: {
+ method: 'GET',
+ path: '/v1/credentials/{credentialsId}',
+ protect: true,
+ summary: 'Get credentials data',
+ tags: ['Credentials'],
+ },
+ })
+ .input(
+ z.object({
+ workspaceId: z.string(),
+ credentialsId: z.string(),
+ })
+ )
+ .output(
+ z.object({
+ name: z.string(),
+ data: z.any(),
+ })
+ )
+ .query(async ({ input: { workspaceId, credentialsId }, ctx: { user } }) => {
+ const workspace = await prisma.workspace.findFirst({
+ where: {
+ id: workspaceId,
+ },
+ select: {
+ id: true,
+ members: true,
+ },
+ })
+ if (!workspace || isWriteWorkspaceForbidden(workspace, user))
+ throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
+
+ const credentials = await prisma.credentials.findFirst({
+ where: {
+ id: credentialsId,
+ },
+ select: {
+ data: true,
+ iv: true,
+ name: true,
+ },
+ })
+
+ if (!credentials)
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ message: 'Credentials not found',
+ })
+
+ const credentialsData = await decrypt(credentials.data, credentials.iv)
+
+ return {
+ name: credentials.name,
+ data: credentialsData,
+ }
+ })
diff --git a/apps/builder/src/features/credentials/api/listCredentials.ts b/apps/builder/src/features/credentials/api/listCredentials.ts
index 9db21aa3e..2b11d84d4 100644
--- a/apps/builder/src/features/credentials/api/listCredentials.ts
+++ b/apps/builder/src/features/credentials/api/listCredentials.ts
@@ -1,16 +1,18 @@
import prisma from '@typebot.io/lib/prisma'
import { authenticatedProcedure } from '@/helpers/server/trpc'
import { TRPCError } from '@trpc/server'
-import { openAICredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/openai'
-import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
import { z } from 'zod'
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
-import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp'
-import { zemanticAiCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
-import {
- googleSheetsCredentialsSchema,
- stripeCredentialsSchema,
-} from '@typebot.io/schemas'
+import { credentialsTypeSchema } from '@typebot.io/schemas'
+import { isDefined } from '@udecode/plate-common'
+
+const outputCredentialsSchema = z.array(
+ z.object({
+ id: z.string(),
+ type: credentialsTypeSchema,
+ name: z.string(),
+ })
+)
export const listCredentials = authenticatedProcedure
.meta({
@@ -25,17 +27,12 @@ export const listCredentials = authenticatedProcedure
.input(
z.object({
workspaceId: z.string(),
- type: stripeCredentialsSchema.shape.type
- .or(smtpCredentialsSchema.shape.type)
- .or(googleSheetsCredentialsSchema.shape.type)
- .or(openAICredentialsSchema.shape.type)
- .or(whatsAppCredentialsSchema.shape.type)
- .or(zemanticAiCredentialsSchema.shape.type),
+ type: credentialsTypeSchema.optional(),
})
)
.output(
z.object({
- credentials: z.array(z.object({ id: z.string(), name: z.string() })),
+ credentials: outputCredentialsSchema,
})
)
.query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
@@ -52,6 +49,7 @@ export const listCredentials = authenticatedProcedure
},
select: {
id: true,
+ type: true,
name: true,
},
},
@@ -60,5 +58,11 @@ export const listCredentials = authenticatedProcedure
if (!workspace || isReadWorkspaceFobidden(workspace, user))
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
- return { credentials: workspace.credentials }
+ return {
+ credentials: outputCredentialsSchema.parse(
+ isDefined(type)
+ ? workspace.credentials
+ : workspace.credentials.sort((a, b) => a.type.localeCompare(b.type))
+ ),
+ }
})
diff --git a/apps/builder/src/features/credentials/api/router.ts b/apps/builder/src/features/credentials/api/router.ts
index e52fced93..d1f90d09a 100644
--- a/apps/builder/src/features/credentials/api/router.ts
+++ b/apps/builder/src/features/credentials/api/router.ts
@@ -2,9 +2,13 @@ import { router } from '@/helpers/server/trpc'
import { createCredentials } from './createCredentials'
import { deleteCredentials } from './deleteCredentials'
import { listCredentials } from './listCredentials'
+import { updateCredentials } from './updateCredentials'
+import { getCredentials } from './getCredentials'
export const credentialsRouter = router({
createCredentials,
listCredentials,
+ getCredentials,
deleteCredentials,
+ updateCredentials,
})
diff --git a/apps/builder/src/features/credentials/api/updateCredentials.ts b/apps/builder/src/features/credentials/api/updateCredentials.ts
new file mode 100644
index 000000000..6ddf18fa5
--- /dev/null
+++ b/apps/builder/src/features/credentials/api/updateCredentials.ts
@@ -0,0 +1,81 @@
+import prisma from '@typebot.io/lib/prisma'
+import { authenticatedProcedure } from '@/helpers/server/trpc'
+import { TRPCError } from '@trpc/server'
+import { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
+import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
+import { z } from 'zod'
+import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp'
+import {
+ googleSheetsCredentialsSchema,
+ stripeCredentialsSchema,
+} from '@typebot.io/schemas'
+import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
+import { forgedCredentialsSchemas } from '../../../../../../packages/forge/repository/credentials'
+
+const inputShape = {
+ name: true,
+ data: true,
+ type: true,
+ workspaceId: true,
+} as const
+
+export const updateCredentials = authenticatedProcedure
+ .meta({
+ openapi: {
+ method: 'PATCH',
+ path: '/v1/credentials/{credentialsId}',
+ protect: true,
+ summary: 'Create credentials',
+ tags: ['Credentials'],
+ },
+ })
+ .input(
+ z.object({
+ credentialsId: z.string(),
+ credentials: z.discriminatedUnion('type', [
+ stripeCredentialsSchema.pick(inputShape),
+ smtpCredentialsSchema.pick(inputShape),
+ googleSheetsCredentialsSchema.pick(inputShape),
+ whatsAppCredentialsSchema.pick(inputShape),
+ ...Object.values(forgedCredentialsSchemas).map((i) =>
+ i.pick(inputShape)
+ ),
+ ]),
+ })
+ )
+ .output(
+ z.object({
+ credentialsId: z.string(),
+ })
+ )
+ .mutation(
+ async ({ input: { credentialsId, credentials }, ctx: { user } }) => {
+ const workspace = await prisma.workspace.findFirst({
+ where: {
+ id: credentials.workspaceId,
+ },
+ select: {
+ id: true,
+ members: true,
+ },
+ })
+ if (!workspace || isWriteWorkspaceForbidden(workspace, user))
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ message: 'Workspace not found',
+ })
+
+ const { encryptedData, iv } = await encrypt(credentials.data)
+ const createdCredentials = await prisma.credentials.update({
+ where: {
+ id: credentialsId,
+ },
+ data: {
+ name: credentials.name,
+ data: encryptedData,
+ iv,
+ },
+ })
+ return { credentialsId: createdCredentials.id }
+ }
+ )
diff --git a/apps/builder/src/features/credentials/components/CredentialsCreateModal.tsx b/apps/builder/src/features/credentials/components/CredentialsCreateModal.tsx
new file mode 100644
index 000000000..41e19e0d4
--- /dev/null
+++ b/apps/builder/src/features/credentials/components/CredentialsCreateModal.tsx
@@ -0,0 +1,82 @@
+import { StripeCreateModalContent } from '@/features/blocks/inputs/payment/components/StripeConfigModal'
+import { GoogleSheetConnectModalContent } from '@/features/blocks/integrations/googleSheets/components/GoogleSheetsConnectModal'
+import { SmtpCreateModalContent } from '@/features/blocks/integrations/sendEmail/components/SmtpConfigModal'
+import { CreateForgedCredentialsModalContent } from '@/features/forge/components/credentials/CreateForgedCredentialsModal'
+import { WhatsAppCreateModalContent } from '@/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal'
+import { Modal, ModalOverlay } from '@chakra-ui/react'
+import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
+import { Credentials } from '@typebot.io/schemas/features/credentials'
+
+export const CredentialsCreateModal = ({
+ creatingType,
+ onSubmit,
+ onClose,
+}: {
+ creatingType?: Credentials['type']
+ onClose: () => void
+ onSubmit: () => void
+}) => {
+ return (
+
+
+ {creatingType && (
+
+ )}
+
+ )
+}
+
+const CredentialsCreateModalContent = ({
+ type,
+ onSubmit,
+ onClose,
+}: {
+ type: Credentials['type']
+ onClose: () => void
+ onSubmit: () => void
+}) => {
+ switch (type) {
+ case 'google sheets':
+ return
+ case 'smtp':
+ return
+ case 'stripe':
+ return (
+
+ )
+ case 'whatsApp':
+ return (
+
+ )
+ default:
+ return (
+
+ )
+ }
+}
+
+const parseModalSize = (type?: Credentials['type']) => {
+ switch (type) {
+ case 'whatsApp':
+ return '3xl'
+ default:
+ return 'lg'
+ }
+}
diff --git a/apps/builder/src/features/credentials/components/CredentialsSettingsForm.tsx b/apps/builder/src/features/credentials/components/CredentialsSettingsForm.tsx
new file mode 100644
index 000000000..cc5804060
--- /dev/null
+++ b/apps/builder/src/features/credentials/components/CredentialsSettingsForm.tsx
@@ -0,0 +1,336 @@
+import { trpc } from '@/lib/trpc'
+import {
+ Heading,
+ HStack,
+ Stack,
+ IconButton,
+ Divider,
+ Button,
+ Menu,
+ MenuButton,
+ MenuList,
+ MenuItem,
+ IconProps,
+ TextProps,
+ Popover,
+ PopoverTrigger,
+ PopoverFooter,
+ PopoverArrow,
+ PopoverBody,
+ PopoverContent,
+ Flex,
+ Skeleton,
+ SkeletonCircle,
+} from '@chakra-ui/react'
+import React, { useMemo, useRef, useState } from 'react'
+import { Credentials, credentialsTypes } from '@typebot.io/schemas'
+import { BlockIcon } from '@/features/editor/components/BlockIcon'
+import { BlockLabel } from '@/features/editor/components/BlockLabel'
+import { Text } from '@chakra-ui/react'
+import { WhatsAppLogo } from '@/components/logos/WhatsAppLogo'
+import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
+import { StripeLogo } from '@/components/logos/StripeLogo'
+import { EditIcon, PlusIcon, TrashIcon } from '@/components/icons'
+import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
+import { toast } from 'sonner'
+import { CredentialsCreateModal } from './CredentialsCreateModal'
+import { CredentialsUpdateModal } from './CredentialsUpdateModal'
+
+const nonEditableTypes = ['whatsApp', 'google sheets'] as const
+
+type CredentialsInfo = Pick
+
+export const CredentialsSettingsForm = () => {
+ const [creatingType, setCreatingType] = useState()
+ const [editingCredentials, setEditingCredentials] = useState<{
+ id: string
+ type: Credentials['type']
+ }>()
+ const [deletingCredentialsId, setDeletingCredentialsId] = useState()
+ const { workspace } = useWorkspace()
+ const { data, isLoading, refetch } =
+ trpc.credentials.listCredentials.useQuery(
+ {
+ workspaceId: workspace!.id,
+ },
+ {
+ enabled: !!workspace?.id,
+ }
+ )
+
+ const { mutate: deleteCredentials } =
+ trpc.credentials.deleteCredentials.useMutation({
+ onMutate: ({ credentialsId }) =>
+ setDeletingCredentialsId(credentialsId as string),
+ onSettled: () => {
+ setDeletingCredentialsId(undefined)
+ },
+ onError: (error) => {
+ toast.error(error.message)
+ },
+ onSuccess: () => {
+ refetch()
+ },
+ })
+
+ const credentials = useMemo(
+ () =>
+ data?.credentials ? groupCredentialsByType(data.credentials) : undefined,
+ [data?.credentials]
+ )
+
+ return (
+
+ {
+ refetch()
+ setCreatingType(undefined)
+ }}
+ onClose={() => setCreatingType(undefined)}
+ />
+ {
+ refetch()
+ setEditingCredentials(undefined)
+ }}
+ onClose={() => setEditingCredentials(undefined)}
+ />
+
+ Credentials
+
+
+
+ {credentials && !isLoading ? (
+ (Object.keys(credentials) as Credentials['type'][]).map((type) => (
+
+
+
+
+
+
+ {credentials[type].map((cred) => (
+
+
+ setEditingCredentials({
+ id: cred.id,
+ type: cred.type,
+ })
+ }
+ onDeleteClick={() =>
+ deleteCredentials({
+ workspaceId: workspace!.id,
+ credentialsId: cred.id,
+ })
+ }
+ />
+
+
+ ))}
+
+
+ ))
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+const CredentialsIcon = ({
+ type,
+ ...props
+}: { type: Credentials['type'] } & IconProps) => {
+ switch (type) {
+ case 'google sheets':
+ return
+ case 'smtp':
+ return
+ case 'stripe':
+ return
+ case 'whatsApp':
+ return
+ default:
+ return
+ }
+}
+
+const CredentialsLabel = ({
+ type,
+ ...props
+}: { type: Credentials['type'] } & TextProps) => {
+ switch (type) {
+ case 'google sheets':
+ return (
+
+ Google Sheets
+
+ )
+ case 'smtp':
+ return (
+
+ SMTP
+
+ )
+ case 'stripe':
+ return (
+
+ Stripe
+
+ )
+ case 'whatsApp':
+ return (
+
+ WhatsApp
+
+ )
+ default:
+ return
+ }
+}
+
+const CredentialsItem = ({
+ isDeleting,
+ onEditClick,
+ onDeleteClick,
+ ...cred
+}: Pick & {
+ isDeleting: boolean
+ onEditClick?: () => void
+ onDeleteClick: () => void
+}) => {
+ const initialFocusRef = useRef(null)
+
+ return (
+
+ {cred.name}
+
+ {onEditClick && (
+ }
+ size="xs"
+ onClick={onEditClick}
+ />
+ )}
+
+ {({ onClose }) => (
+ <>
+
+ }
+ size="xs"
+ />
+
+
+
+
+
+
+ Are you sure?
+
+
+ Make sure this credentials is not used in any of your
+ published bot before proceeding.
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ )
+}
+
+const groupCredentialsByType = (
+ credentials: CredentialsInfo[]
+): Record => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const groupedCredentials: any = {}
+ credentials.forEach((cred) => {
+ if (!groupedCredentials[cred.type]) {
+ groupedCredentials[cred.type] = []
+ }
+ groupedCredentials[cred.type].push(cred)
+ })
+ return groupedCredentials
+}
diff --git a/apps/builder/src/features/credentials/components/CredentialsUpdateModal.tsx b/apps/builder/src/features/credentials/components/CredentialsUpdateModal.tsx
new file mode 100644
index 000000000..c2e2d5d83
--- /dev/null
+++ b/apps/builder/src/features/credentials/components/CredentialsUpdateModal.tsx
@@ -0,0 +1,71 @@
+import { UpdateStripeCredentialsModalContent } from '@/features/blocks/inputs/payment/components/UpdateStripeCredentialsModalContent'
+import { SmtpUpdateModalContent } from '@/features/blocks/integrations/sendEmail/components/SmtpUpdateModalContent'
+import { UpdateForgedCredentialsModalContent } from '@/features/forge/components/credentials/UpdateForgedCredentialsModalContent'
+import { Modal, ModalOverlay } from '@chakra-ui/react'
+import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
+import { Credentials } from '@typebot.io/schemas/features/credentials'
+
+export const CredentialsUpdateModal = ({
+ editingCredentials,
+ onSubmit,
+ onClose,
+}: {
+ editingCredentials?: {
+ id: string
+ type: Credentials['type']
+ }
+ onClose: () => void
+ onSubmit: () => void
+}) => {
+ return (
+
+
+ {editingCredentials && (
+
+ )}
+
+ )
+}
+
+const CredentialsUpdateModalContent = ({
+ editingCredentials,
+ onSubmit,
+}: {
+ editingCredentials: {
+ id: string
+ type: Credentials['type']
+ }
+ onSubmit: () => void
+}) => {
+ switch (editingCredentials.type) {
+ case 'google sheets':
+ return null
+ case 'smtp':
+ return (
+
+ )
+ case 'stripe':
+ return (
+
+ )
+ case 'whatsApp':
+ return null
+ default:
+ return (
+
+ )
+ }
+}
diff --git a/apps/builder/src/features/credentials/credentials.spec.ts b/apps/builder/src/features/credentials/credentials.spec.ts
new file mode 100644
index 000000000..6ca113274
--- /dev/null
+++ b/apps/builder/src/features/credentials/credentials.spec.ts
@@ -0,0 +1,39 @@
+import { expect, test } from '@playwright/test'
+
+test('should be able to create, update and delete credentials', async ({
+ page,
+}) => {
+ await page.goto('/typebots')
+ await page.click('text=Settings & Members')
+ await page.click('text=Credentials')
+
+ // Create
+ await page.getByRole('button', { name: 'Create new' }).click()
+ await page.getByRole('menuitem', { name: 'OpenAI' }).click()
+ await page.getByPlaceholder('My account').fill('Typebot')
+ await page.getByPlaceholder('sk-').fill('sk-test')
+ await page.getByRole('button', { name: 'Create' }).click()
+ await expect(page.getByTestId('openai').getByText('Typebot')).toBeVisible()
+
+ // Edit
+ await page.pause()
+ await page.getByTestId('openai').getByRole('button', { name: 'Edit' }).click()
+ await expect(page.getByPlaceholder('My account')).toHaveValue('Typebot')
+ await expect(page.getByPlaceholder('sk-')).toHaveValue('sk-test')
+ await page.getByPlaceholder('sk-').fill('sk-test-2')
+ await page.getByPlaceholder('My account').fill('Typebot 2')
+ await page.getByRole('button', { name: 'Update' }).click()
+ await expect(page.getByTestId('openai').getByText('Typebot 2')).toBeVisible()
+
+ // Delete
+ await page
+ .getByTestId('openai')
+ .getByRole('button', { name: 'Delete' })
+ .click()
+ await page
+ .getByTestId('openai')
+ .getByRole('button', { name: 'Delete' })
+ .nth(1)
+ .click()
+ await expect(page.getByTestId('openai').getByText('Typebot 2')).toBeHidden()
+})
diff --git a/apps/builder/src/features/dashboard/components/DashboardHeader.tsx b/apps/builder/src/features/dashboard/components/DashboardHeader.tsx
index 6f21a88b3..21b62750d 100644
--- a/apps/builder/src/features/dashboard/components/DashboardHeader.tsx
+++ b/apps/builder/src/features/dashboard/components/DashboardHeader.tsx
@@ -10,13 +10,19 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { WorkspaceDropdown } from '@/features/workspace/components/WorkspaceDropdown'
import { WorkspaceSettingsModal } from '@/features/workspace/components/WorkspaceSettingsModal'
import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider'
+import { useRouter } from 'next/router'
export const DashboardHeader = () => {
const { t } = useTranslate()
const { user, logOut } = useUser()
const { workspace, switchWorkspace, createWorkspace } = useWorkspace()
+ const { asPath } = useRouter()
- const { isOpen, onOpen, onClose } = useDisclosure()
+ const isRedirectFromCredentialsCreation = asPath.includes('credentials')
+
+ const { isOpen, onOpen, onClose } = useDisclosure({
+ defaultIsOpen: isRedirectFromCredentialsCreation,
+ })
const handleCreateNewWorkspace = () =>
createWorkspace(user?.name ?? undefined)
@@ -45,6 +51,9 @@ export const DashboardHeader = () => {
onClose={onClose}
user={user}
workspace={workspace}
+ defaultTab={
+ isRedirectFromCredentialsCreation ? 'credentials' : undefined
+ }
/>
)}
diff --git a/apps/builder/src/features/editor/components/BlockIcon.tsx b/apps/builder/src/features/editor/components/BlockIcon.tsx
index e9e3d9f56..ac82fe6e2 100644
--- a/apps/builder/src/features/editor/components/BlockIcon.tsx
+++ b/apps/builder/src/features/editor/components/BlockIcon.tsx
@@ -1,6 +1,5 @@
-import { useColorModeValue } from '@chakra-ui/react'
+import { IconProps, useColorModeValue } from '@chakra-ui/react'
import React from 'react'
-import { FlagIcon, SendEmailIcon, ThunderIcon } from '@/components/icons'
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
import { ScriptIcon } from '@/features/blocks/logic/script/components/ScriptIcon'
import { JumpIcon } from '@/features/blocks/logic/jump/components/JumpIcon'
@@ -40,10 +39,12 @@ import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/consta
import { Block } from '@typebot.io/schemas'
import { OpenAILogo } from '@/features/blocks/integrations/openai/components/OpenAILogo'
import { ForgedBlockIcon } from '@/features/forge/ForgedBlockIcon'
+import { SendEmailIcon } from '@/features/blocks/integrations/sendEmail/components/SendEmailIcon'
+import { FlagIcon, ThunderIcon } from '@/components/icons'
-type BlockIconProps = { type: Block['type']; mt?: string }
+type BlockIconProps = { type: Block['type'] } & IconProps
-export const BlockIcon = ({ type, mt }: BlockIconProps): JSX.Element => {
+export const BlockIcon = ({ type, ...props }: BlockIconProps): JSX.Element => {
const blue = useColorModeValue('blue.500', 'blue.300')
const orange = useColorModeValue('orange.500', 'orange.300')
const purple = useColorModeValue('purple.500', 'purple.300')
@@ -51,78 +52,78 @@ export const BlockIcon = ({ type, mt }: BlockIconProps): JSX.Element => {
switch (type) {
case BubbleBlockType.TEXT:
- return
+ return
case BubbleBlockType.IMAGE:
- return
+ return
case BubbleBlockType.VIDEO:
- return
+ return
case BubbleBlockType.EMBED:
- return
+ return
case BubbleBlockType.AUDIO:
- return
+ return
case InputBlockType.TEXT:
- return
+ return
case InputBlockType.NUMBER:
- return
+ return
case InputBlockType.EMAIL:
- return
+ return
case InputBlockType.URL:
- return
+ return
case InputBlockType.DATE:
- return
+ return
case InputBlockType.PHONE:
- return
+ return
case InputBlockType.CHOICE:
- return
+ return
case InputBlockType.PICTURE_CHOICE:
- return
+ return
case InputBlockType.PAYMENT:
- return
+ return
case InputBlockType.RATING:
- return
+ return
case InputBlockType.FILE:
- return
+ return
case LogicBlockType.SET_VARIABLE:
- return
+ return
case LogicBlockType.CONDITION:
- return
+ return
case LogicBlockType.REDIRECT:
- return
+ return
case LogicBlockType.SCRIPT:
- return
+ return
case LogicBlockType.WAIT:
- return
+ return
case LogicBlockType.JUMP:
- return
+ return
case LogicBlockType.TYPEBOT_LINK:
- return
+ return
case LogicBlockType.AB_TEST:
- return
+ return
case IntegrationBlockType.GOOGLE_SHEETS:
- return
+ return
case IntegrationBlockType.GOOGLE_ANALYTICS:
- return
+ return
case IntegrationBlockType.WEBHOOK:
- return
+ return
case IntegrationBlockType.ZAPIER:
- return
+ return
case IntegrationBlockType.MAKE_COM:
- return
+ return
case IntegrationBlockType.PABBLY_CONNECT:
- return
+ return
case IntegrationBlockType.EMAIL:
- return
+ return
case IntegrationBlockType.CHATWOOT:
- return
+ return
case IntegrationBlockType.PIXEL:
- return
+ return
case IntegrationBlockType.ZEMANTIC_AI:
- return
+ return
case 'start':
- return
+ return
case IntegrationBlockType.OPEN_AI:
- return
+ return
default:
- return
+ return
}
}
diff --git a/apps/builder/src/features/editor/components/BlockLabel.tsx b/apps/builder/src/features/editor/components/BlockLabel.tsx
index 97a3c0564..b648e9045 100644
--- a/apps/builder/src/features/editor/components/BlockLabel.tsx
+++ b/apps/builder/src/features/editor/components/BlockLabel.tsx
@@ -1,4 +1,4 @@
-import { Text } from '@chakra-ui/react'
+import { Text, TextProps } from '@chakra-ui/react'
import React from 'react'
import { useTranslate } from '@tolgee/react'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
@@ -8,98 +8,224 @@ import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/consta
import { Block } from '@typebot.io/schemas'
import { ForgedBlockLabel } from '@/features/forge/ForgedBlockLabel'
-type Props = { type: Block['type'] }
+type Props = { type: Block['type'] } & TextProps
-export const BlockLabel = ({ type }: Props): JSX.Element => {
+export const BlockLabel = ({ type, ...props }: Props): JSX.Element => {
const { t } = useTranslate()
switch (type) {
case 'start':
- return {t('editor.sidebarBlock.start.label')}
+ return (
+
+ {t('editor.sidebarBlock.start.label')}
+
+ )
case BubbleBlockType.TEXT:
case InputBlockType.TEXT:
- return {t('editor.sidebarBlock.text.label')}
+ return (
+
+ {t('editor.sidebarBlock.text.label')}
+
+ )
case BubbleBlockType.IMAGE:
- return {t('editor.sidebarBlock.image.label')}
+ return (
+
+ {t('editor.sidebarBlock.image.label')}
+
+ )
case BubbleBlockType.VIDEO:
- return {t('editor.sidebarBlock.video.label')}
+ return (
+
+ {t('editor.sidebarBlock.video.label')}
+
+ )
case BubbleBlockType.EMBED:
- return {t('editor.sidebarBlock.embed.label')}
+ return (
+
+ {t('editor.sidebarBlock.embed.label')}
+
+ )
case BubbleBlockType.AUDIO:
- return {t('editor.sidebarBlock.audio.label')}
+ return (
+
+ {t('editor.sidebarBlock.audio.label')}
+
+ )
case InputBlockType.NUMBER:
- return {t('editor.sidebarBlock.number.label')}
+ return (
+
+ {t('editor.sidebarBlock.number.label')}
+
+ )
case InputBlockType.EMAIL:
- return {t('editor.sidebarBlock.email.label')}
+ return (
+
+ {t('editor.sidebarBlock.email.label')}
+
+ )
case InputBlockType.URL:
- return {t('editor.sidebarBlock.website.label')}
+ return (
+
+ {t('editor.sidebarBlock.website.label')}
+
+ )
case InputBlockType.DATE:
- return {t('editor.sidebarBlock.date.label')}
+ return (
+
+ {t('editor.sidebarBlock.date.label')}
+
+ )
case InputBlockType.PHONE:
- return {t('editor.sidebarBlock.phone.label')}
+ return (
+
+ {t('editor.sidebarBlock.phone.label')}
+
+ )
case InputBlockType.CHOICE:
- return {t('editor.sidebarBlock.button.label')}
+ return (
+
+ {t('editor.sidebarBlock.button.label')}
+
+ )
case InputBlockType.PICTURE_CHOICE:
return (
- {t('editor.sidebarBlock.picChoice.label')}
+
+ {t('editor.sidebarBlock.picChoice.label')}
+
)
case InputBlockType.PAYMENT:
- return {t('editor.sidebarBlock.payment.label')}
+ return (
+
+ {t('editor.sidebarBlock.payment.label')}
+
+ )
case InputBlockType.RATING:
- return {t('editor.sidebarBlock.rating.label')}
+ return (
+
+ {t('editor.sidebarBlock.rating.label')}
+
+ )
case InputBlockType.FILE:
- return {t('editor.sidebarBlock.file.label')}
+ return (
+
+ {t('editor.sidebarBlock.file.label')}
+
+ )
case LogicBlockType.SET_VARIABLE:
return (
- {t('editor.sidebarBlock.setVariable.label')}
+
+ {t('editor.sidebarBlock.setVariable.label')}
+
)
case LogicBlockType.CONDITION:
return (
- {t('editor.sidebarBlock.condition.label')}
+
+ {t('editor.sidebarBlock.condition.label')}
+
)
case LogicBlockType.REDIRECT:
return (
- {t('editor.sidebarBlock.redirect.label')}
+
+ {t('editor.sidebarBlock.redirect.label')}
+
)
case LogicBlockType.SCRIPT:
- return {t('editor.sidebarBlock.script.label')}
+ return (
+
+ {t('editor.sidebarBlock.script.label')}
+
+ )
case LogicBlockType.TYPEBOT_LINK:
- return {t('editor.sidebarBlock.typebot.label')}
+ return (
+
+ {t('editor.sidebarBlock.typebot.label')}
+
+ )
case LogicBlockType.WAIT:
- return {t('editor.sidebarBlock.wait.label')}
+ return (
+
+ {t('editor.sidebarBlock.wait.label')}
+
+ )
case LogicBlockType.JUMP:
- return {t('editor.sidebarBlock.jump.label')}
+ return (
+
+ {t('editor.sidebarBlock.jump.label')}
+
+ )
case LogicBlockType.AB_TEST:
- return {t('editor.sidebarBlock.abTest.label')}
+ return (
+
+ {t('editor.sidebarBlock.abTest.label')}
+
+ )
case IntegrationBlockType.GOOGLE_SHEETS:
- return {t('editor.sidebarBlock.sheets.label')}
+ return (
+
+ {t('editor.sidebarBlock.sheets.label')}
+
+ )
case IntegrationBlockType.GOOGLE_ANALYTICS:
return (
- {t('editor.sidebarBlock.analytics.label')}
+
+ {t('editor.sidebarBlock.analytics.label')}
+
)
case IntegrationBlockType.WEBHOOK:
- return HTTP request
+ return (
+
+ HTTP request
+
+ )
case IntegrationBlockType.ZAPIER:
- return {t('editor.sidebarBlock.zapier.label')}
+ return (
+
+ {t('editor.sidebarBlock.zapier.label')}
+
+ )
case IntegrationBlockType.MAKE_COM:
- return {t('editor.sidebarBlock.makecom.label')}
+ return (
+
+ {t('editor.sidebarBlock.makecom.label')}
+
+ )
case IntegrationBlockType.PABBLY_CONNECT:
- return {t('editor.sidebarBlock.pabbly.label')}
+ return (
+
+ {t('editor.sidebarBlock.pabbly.label')}
+
+ )
case IntegrationBlockType.EMAIL:
- return {t('editor.sidebarBlock.email.label')}
+ return (
+
+ {t('editor.sidebarBlock.email.label')}
+
+ )
case IntegrationBlockType.CHATWOOT:
return (
- {t('editor.sidebarBlock.chatwoot.label')}
+
+ {t('editor.sidebarBlock.chatwoot.label')}
+
)
case IntegrationBlockType.OPEN_AI:
- return {t('editor.sidebarBlock.openai.label')}
+ return (
+
+ {t('editor.sidebarBlock.openai.label')}
+
+ )
case IntegrationBlockType.PIXEL:
- return {t('editor.sidebarBlock.pixel.label')}
+ return (
+
+ {t('editor.sidebarBlock.pixel.label')}
+
+ )
case IntegrationBlockType.ZEMANTIC_AI:
return (
- {t('editor.sidebarBlock.zemanticAi.label')}
+
+ {t('editor.sidebarBlock.zemanticAi.label')}
+
)
default:
- return
+ return
}
}
diff --git a/apps/builder/src/features/forge/ForgedBlockIcon.tsx b/apps/builder/src/features/forge/ForgedBlockIcon.tsx
index 72f3da0ef..63a2c2939 100644
--- a/apps/builder/src/features/forge/ForgedBlockIcon.tsx
+++ b/apps/builder/src/features/forge/ForgedBlockIcon.tsx
@@ -1,18 +1,27 @@
-import { useColorMode } from '@chakra-ui/react'
+import { IconProps, useColorMode } from '@chakra-ui/react'
import { ForgedBlock } from '@typebot.io/forge-repository/types'
import { useForgedBlock } from './hooks/useForgedBlock'
export const ForgedBlockIcon = ({
type,
- mt,
+ ...props
}: {
type: ForgedBlock['type']
- mt?: string
-}): JSX.Element => {
+} & IconProps): JSX.Element => {
const { colorMode } = useColorMode()
const { blockDef } = useForgedBlock(type)
if (!blockDef) return <>>
if (colorMode === 'dark' && blockDef.DarkLogo)
- return
- return
+ return (
+
+ )
+ return (
+
+ )
}
diff --git a/apps/builder/src/features/forge/ForgedBlockLabel.tsx b/apps/builder/src/features/forge/ForgedBlockLabel.tsx
index 25e43a4e8..6b7f6f328 100644
--- a/apps/builder/src/features/forge/ForgedBlockLabel.tsx
+++ b/apps/builder/src/features/forge/ForgedBlockLabel.tsx
@@ -1,9 +1,16 @@
import { ForgedBlock } from '@typebot.io/forge-repository/types'
import { useForgedBlock } from './hooks/useForgedBlock'
-import { Text } from '@chakra-ui/react'
+import { Text, TextProps } from '@chakra-ui/react'
-export const ForgedBlockLabel = ({ type }: { type: ForgedBlock['type'] }) => {
+export const ForgedBlockLabel = ({
+ type,
+ ...props
+}: { type: ForgedBlock['type'] } & TextProps) => {
const { blockDef } = useForgedBlock(type)
- return {blockDef?.name}
+ return (
+
+ {blockDef?.name}
+
+ )
}
diff --git a/apps/builder/src/features/forge/api/credentials/createCredentials.ts b/apps/builder/src/features/forge/api/credentials/createCredentials.ts
deleted file mode 100644
index bfa542b30..000000000
--- a/apps/builder/src/features/forge/api/credentials/createCredentials.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import prisma from '@typebot.io/lib/prisma'
-import { authenticatedProcedure } from '@/helpers/server/trpc'
-import { TRPCError } from '@trpc/server'
-import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
-import { z } from 'zod'
-import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
-import { forgedCredentialsSchemas } from '@typebot.io/forge-repository/credentials'
-import { isDefined } from '@typebot.io/lib'
-
-const inputShape = {
- data: true,
- type: true,
- workspaceId: true,
- name: true,
-} as const
-
-export const createCredentials = authenticatedProcedure
- .input(
- z.object({
- credentials: z.discriminatedUnion(
- 'type',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- Object.values(forgedCredentialsSchemas)
- .filter(isDefined)
- .map((i) => i.pick(inputShape))
- ),
- })
- )
- .mutation(async ({ input: { credentials }, ctx: { user } }) => {
- const workspace = await prisma.workspace.findFirst({
- where: {
- id: credentials.workspaceId,
- },
- select: { id: true, members: true },
- })
- if (!workspace || isWriteWorkspaceForbidden(workspace, user))
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
-
- const { encryptedData, iv } = await encrypt(credentials.data)
- const createdCredentials = await prisma.credentials.create({
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- data: {
- ...credentials,
- data: encryptedData,
- iv,
- },
- select: {
- id: true,
- },
- })
- return { credentialsId: createdCredentials.id }
- })
diff --git a/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts b/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts
deleted file mode 100644
index 6c0531ec6..000000000
--- a/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import prisma from '@typebot.io/lib/prisma'
-import { authenticatedProcedure } from '@/helpers/server/trpc'
-import { TRPCError } from '@trpc/server'
-import { z } from 'zod'
-import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
-
-export const deleteCredentials = authenticatedProcedure
- .input(
- z.object({
- credentialsId: z.string(),
- workspaceId: z.string(),
- })
- )
- .mutation(
- async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => {
- const workspace = await prisma.workspace.findFirst({
- where: {
- id: workspaceId,
- members: {
- some: { userId: user.id, role: { in: ['ADMIN', 'MEMBER'] } },
- },
- },
- select: { id: true, members: true },
- })
- if (!workspace || isWriteWorkspaceForbidden(workspace, user))
- throw new TRPCError({
- code: 'NOT_FOUND',
- message: 'Workspace not found',
- })
-
- await prisma.credentials.delete({
- where: {
- id: credentialsId,
- },
- })
- return { credentialsId }
- }
- )
diff --git a/apps/builder/src/features/forge/api/credentials/listCredentials.ts b/apps/builder/src/features/forge/api/credentials/listCredentials.ts
deleted file mode 100644
index 7d5b0011d..000000000
--- a/apps/builder/src/features/forge/api/credentials/listCredentials.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import prisma from '@typebot.io/lib/prisma'
-import { authenticatedProcedure } from '@/helpers/server/trpc'
-import { TRPCError } from '@trpc/server'
-import { z } from 'zod'
-import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
-import { forgedBlockIds } from '@typebot.io/forge-repository/constants'
-
-export const listCredentials = authenticatedProcedure
- .input(
- z.object({
- workspaceId: z.string(),
- type: z.enum(forgedBlockIds),
- })
- )
- .query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
- const workspace = await prisma.workspace.findFirst({
- where: {
- id: workspaceId,
- },
- select: {
- id: true,
- members: true,
- credentials: {
- where: {
- type,
- },
- select: {
- id: true,
- name: true,
- },
- },
- },
- })
- if (!workspace || isReadWorkspaceFobidden(workspace, user))
- throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
-
- return { credentials: workspace.credentials }
- })
diff --git a/apps/builder/src/features/forge/api/router.ts b/apps/builder/src/features/forge/api/router.ts
index 1c2bd6c75..21e88ddfc 100644
--- a/apps/builder/src/features/forge/api/router.ts
+++ b/apps/builder/src/features/forge/api/router.ts
@@ -1,12 +1,6 @@
import { router } from '@/helpers/server/trpc'
import { fetchSelectItems } from './fetchSelectItems'
-import { createCredentials } from './credentials/createCredentials'
-import { deleteCredentials } from './credentials/deleteCredentials'
-import { listCredentials } from './credentials/listCredentials'
export const forgeRouter = router({
fetchSelectItems,
- createCredentials,
- listCredentials,
- deleteCredentials,
})
diff --git a/apps/builder/src/features/forge/components/ForgedBlockSettings.tsx b/apps/builder/src/features/forge/components/ForgedBlockSettings.tsx
index e96c3b3f2..587a538a1 100644
--- a/apps/builder/src/features/forge/components/ForgedBlockSettings.tsx
+++ b/apps/builder/src/features/forge/components/ForgedBlockSettings.tsx
@@ -1,7 +1,7 @@
import { Stack, useDisclosure } from '@chakra-ui/react'
import { BlockOptions } from '@typebot.io/schemas'
import { ForgedCredentialsDropdown } from './credentials/ForgedCredentialsDropdown'
-import { ForgedCredentialsModal } from './credentials/ForgedCredentialsModal'
+import { CreateForgedCredentialsModal } from './credentials/CreateForgedCredentialsModal'
import { ZodObjectLayout } from './zodLayouts/ZodObjectLayout'
import { ZodActionDiscriminatedUnion } from './zodLayouts/ZodActionDiscriminatedUnion'
import { useForgedBlock } from '../hooks/useForgedBlock'
@@ -64,7 +64,7 @@ export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
{blockDef.auth && (
<>
- void
onNewCredentials: (id: string) => void
}
-export const ForgedCredentialsModal = ({
+export const CreateForgedCredentialsModal = ({
blockDef,
isOpen,
+ defaultData,
onClose,
onNewCredentials,
}: Props) => {
+ if (!blockDef.auth) return null
+ return (
+
+
+ {
+ onClose()
+ onNewCredentials(id)
+ }}
+ />
+
+ )
+}
+
+export const CreateForgedCredentialsModalContent = ({
+ blockDef,
+ onNewCredentials,
+}: Pick) => {
const { workspace } = useWorkspace()
const { showToast } = useToast()
const [name, setName] = useState('')
@@ -42,7 +66,8 @@ export const ForgedCredentialsModal = ({
listCredentials: { refetch: refetchCredentials },
},
} = trpc.useContext()
- const { mutate } = trpc.forge.createCredentials.useMutation({
+
+ const { mutate } = trpc.credentials.createCredentials.useMutation({
onMutate: () => setIsCreating(true),
onSettled: () => setIsCreating(false),
onError: (err) => {
@@ -54,59 +79,55 @@ export const ForgedCredentialsModal = ({
onSuccess: (data) => {
refetchCredentials()
onNewCredentials(data.credentialsId)
- onClose()
},
})
const createOpenAICredentials = async (e: React.FormEvent) => {
e.preventDefault()
- if (!workspace) return
+ if (!workspace || !blockDef.auth) return
mutate({
credentials: {
- type: blockDef.id,
+ type: blockDef.id as Credentials['type'],
workspaceId: workspace.id,
name,
data,
- },
+ } as Credentials,
})
}
if (!blockDef.auth) return null
return (
-
-
-
- Add {blockDef.auth.name}
-
-
+
+
+
+
+
)
}
diff --git a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx
index 32abaf5a4..fdaca6a71 100644
--- a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx
+++ b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx
@@ -16,6 +16,7 @@ import { trpc } from '@/lib/trpc'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
import { useToast } from '@/hooks/useToast'
+import { Credentials } from '@typebot.io/schemas/features/credentials'
type Props = Omit & {
blockDef: ForgedBlockDefinition
@@ -34,13 +35,14 @@ export const ForgedCredentialsDropdown = ({
const router = useRouter()
const { showToast } = useToast()
const { workspace, currentRole } = useWorkspace()
- const { data, refetch, isLoading } = trpc.forge.listCredentials.useQuery(
- {
- workspaceId: workspace?.id as string,
- type: blockDef.id,
- },
- { enabled: !!workspace?.id }
- )
+ const { data, refetch, isLoading } =
+ trpc.credentials.listCredentials.useQuery(
+ {
+ workspaceId: workspace?.id as string,
+ type: blockDef.id as Credentials['type'],
+ },
+ { enabled: !!workspace?.id }
+ )
const [isDeleting, setIsDeleting] = useState()
const { mutate } = trpc.credentials.deleteCredentials.useMutation({
diff --git a/apps/builder/src/features/forge/components/credentials/UpdateForgedCredentialsModalContent.tsx b/apps/builder/src/features/forge/components/credentials/UpdateForgedCredentialsModalContent.tsx
new file mode 100644
index 000000000..092673d7b
--- /dev/null
+++ b/apps/builder/src/features/forge/components/credentials/UpdateForgedCredentialsModalContent.tsx
@@ -0,0 +1,119 @@
+import { TextInput } from '@/components/inputs/TextInput'
+import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
+import { useToast } from '@/hooks/useToast'
+import { trpc } from '@/lib/trpc'
+import {
+ ModalContent,
+ ModalHeader,
+ ModalCloseButton,
+ ModalBody,
+ Stack,
+ ModalFooter,
+ Button,
+} from '@chakra-ui/react'
+import React, { useEffect, useState } from 'react'
+import { ZodObjectLayout } from '../zodLayouts/ZodObjectLayout'
+import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
+import { Credentials } from '@typebot.io/schemas'
+
+type Props = {
+ credentialsId: string
+ blockDef: ForgedBlockDefinition
+ onUpdate: () => void
+}
+
+export const UpdateForgedCredentialsModalContent = ({
+ credentialsId,
+ blockDef,
+ onUpdate,
+}: Props) => {
+ const { workspace } = useWorkspace()
+ const { showToast } = useToast()
+ const [name, setName] = useState('')
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const [data, setData] = useState()
+
+ const [isUpdating, setIsUpdating] = useState(false)
+
+ const { data: existingCredentials } =
+ trpc.credentials.getCredentials.useQuery(
+ {
+ workspaceId: workspace?.id as string,
+ credentialsId,
+ },
+ {
+ enabled: !!workspace?.id,
+ }
+ )
+
+ useEffect(() => {
+ if (!existingCredentials || data) return
+ setName(existingCredentials.name)
+ setData(existingCredentials.data)
+ }, [data, existingCredentials])
+
+ const { mutate } = trpc.credentials.updateCredentials.useMutation({
+ onMutate: () => setIsUpdating(true),
+ onSettled: () => setIsUpdating(false),
+ onError: (err) => {
+ showToast({
+ description: err.message,
+ status: 'error',
+ })
+ },
+ onSuccess: () => {
+ onUpdate()
+ },
+ })
+
+ const updateCredentials = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!workspace || !blockDef.auth) return
+ mutate({
+ credentialsId,
+ credentials: {
+ type: blockDef.id,
+ workspaceId: workspace.id,
+ name,
+ data,
+ } as Credentials,
+ })
+ }
+
+ if (!blockDef.auth) return null
+ return (
+
+ Update {blockDef.auth.name}
+
+
+
+ )
+}
diff --git a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx
index b7d5840d2..fb0578d9c 100644
--- a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx
+++ b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx
@@ -64,6 +64,21 @@ export const WhatsAppCredentialsModal = ({
onClose,
onNewCredentials,
}: Props) => {
+ return (
+
+
+
+
+ )
+}
+
+export const WhatsAppCreateModalContent = ({
+ onNewCredentials,
+ onClose,
+}: Pick) => {
const { workspace } = useWorkspace()
const { showToast } = useToast()
const { activeStep, goToNext, goToPrevious, setActiveStep } = useSteps({
@@ -226,82 +241,78 @@ export const WhatsAppCredentialsModal = ({
goToNext()
}
-
return (
-
-
-
-
-
- {activeStep > 0 && (
- }
- aria-label={'Go back'}
- variant="ghost"
- onClick={goToPrevious}
- />
- )}
- Add a WhatsApp phone number
-
-
-
-
-
- {steps.map((step, index) => (
-
-
- }
- incomplete={}
- active={}
- />
-
+
+
+
+ {activeStep > 0 && (
+ }
+ aria-label={'Go back'}
+ variant="ghost"
+ onClick={goToPrevious}
+ />
+ )}
+ Add a WhatsApp phone number
+
+
+
+
+
+ {steps.map((step, index) => (
+
+
+ }
+ incomplete={}
+ active={}
+ />
+
-
- {step.title}
-
+
+ {step.title}
+
-
-
- ))}
-
- {activeStep === 0 && }
- {activeStep === 1 && (
-
- )}
- {activeStep === 2 && (
-
- )}
- {activeStep === 3 && (
-
- )}
-
-
-
-
-
-
+
+
+ ))}
+
+ {activeStep === 0 && }
+ {activeStep === 1 && (
+
+ )}
+ {activeStep === 2 && (
+
+ )}
+ {activeStep === 3 && (
+
+ )}
+
+
+
+
+
)
}
diff --git a/apps/builder/src/features/workspace/components/WorkspaceSettingsModal.tsx b/apps/builder/src/features/workspace/components/WorkspaceSettingsModal.tsx
index 9ffc04047..999ffa0ad 100644
--- a/apps/builder/src/features/workspace/components/WorkspaceSettingsModal.tsx
+++ b/apps/builder/src/features/workspace/components/WorkspaceSettingsModal.tsx
@@ -13,6 +13,7 @@ import {
HardDriveIcon,
SettingsIcon,
UsersIcon,
+ WalletIcon,
} from '@/components/icons'
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
import { User, WorkspaceRole } from '@typebot.io/prisma'
@@ -26,11 +27,13 @@ import { MyAccountForm } from '@/features/account/components/MyAccountForm'
import { BillingSettingsLayout } from '@/features/billing/components/BillingSettingsLayout'
import { useTranslate } from '@tolgee/react'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
+import { CredentialsSettingsForm } from '@/features/credentials/components/CredentialsSettingsForm'
type Props = {
isOpen: boolean
user: User
workspace: WorkspaceInApp
+ defaultTab?: SettingsTab
onClose: () => void
}
@@ -40,17 +43,19 @@ type SettingsTab =
| 'workspace-settings'
| 'members'
| 'billing'
+ | 'credentials'
export const WorkspaceSettingsModal = ({
isOpen,
user,
workspace,
+ defaultTab = 'my-account',
onClose,
}: Props) => {
const { t } = useTranslate()
const { ref } = useParentModal()
const { currentRole } = useWorkspace()
- const [selectedTab, setSelectedTab] = useState('my-account')
+ const [selectedTab, setSelectedTab] = useState(defaultTab)
const canEditWorkspace = currentRole === WorkspaceRole.ADMIN
@@ -121,6 +126,18 @@ export const WorkspaceSettingsModal = ({
{t('workspace.settings.modal.menu.settings.label')}
)}
+ {canEditWorkspace && (
+
+ )}
{currentRole !== WorkspaceRole.GUEST && (
case 'billing':
return
+ case 'credentials':
+ return
default:
return null
}
diff --git a/apps/builder/src/pages/api/credentials/google-sheets/callback.ts b/apps/builder/src/pages/api/credentials/google-sheets/callback.ts
index e2123bc17..92da83439 100644
--- a/apps/builder/src/pages/api/credentials/google-sheets/callback.ts
+++ b/apps/builder/src/pages/api/credentials/google-sheets/callback.ts
@@ -16,7 +16,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (!state) return badRequest(res)
const { typebotId, redirectUrl, blockId, workspaceId } = JSON.parse(
Buffer.from(state, 'base64').toString()
- )
+ ) as {
+ redirectUrl: string
+ workspaceId: string
+ typebotId?: string
+ blockId?: string
+ }
if (req.method === 'GET') {
const code = req.query.code as string | undefined
if (!workspaceId) return badRequest(res)
@@ -55,6 +60,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const { id: credentialsId } = await prisma.credentials.create({
data: credentials,
})
+ if (!typebotId) return res.redirect(`${redirectUrl.split('?')[0]}`)
const typebot = await prisma.typebot.findFirst({
where: {
id: typebotId,
diff --git a/apps/builder/src/test/assets/typebots/api.json b/apps/builder/src/test/assets/typebots/api.json
index 8a7900a01..ef3e12436 100644
--- a/apps/builder/src/test/assets/typebots/api.json
+++ b/apps/builder/src/test/assets/typebots/api.json
@@ -1,31 +1,24 @@
{
- "id": "qujHPjZ44xbrHb1hS1d8qC",
- "createdAt": "2022-02-05T06:21:16.522Z",
- "updatedAt": "2022-02-05T06:21:16.522Z",
+ "version": "6",
+ "id": "clyoe5iot0001grw96sdkhsfo",
"name": "My typebot",
- "folderId": null,
- "groups": [
+ "events": [
{
"id": "k6kY6gwRE6noPoYQNGzgUq",
- "blocks": [
- {
- "id": "22HP69iipkLjJDTUcc1AWW",
- "type": "start",
- "label": "Start",
- "groupId": "k6kY6gwRE6noPoYQNGzgUq",
- "outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE"
- }
- ],
- "title": "Start",
- "graphCoordinates": { "x": 0, "y": 0 }
- },
+ "outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE",
+ "graphCoordinates": { "x": 0, "y": 0 },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "kinRXxYop2X4d7F9qt8WNB",
+ "title": "Welcome",
+ "graphCoordinates": { "x": 1, "y": 148 },
"blocks": [
{
"id": "sc1y8VwDabNJgiVTBi4qtif",
"type": "text",
- "groupId": "kinRXxYop2X4d7F9qt8WNB",
"content": {
"richText": [
{
@@ -42,37 +35,27 @@
{
"id": "s7YqZTBeyCa4Hp3wN2j922c",
"type": "image",
- "groupId": "kinRXxYop2X4d7F9qt8WNB",
"content": {
"url": "https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g"
}
},
{
"id": "sbjZWLJGVkHAkDqS4JQeGow",
+ "outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim",
"type": "choice input",
- "items": [
- {
- "id": "hQw2zbp7FDX7XYK9cFpbgC",
- "type": 0,
- "blockId": "sbjZWLJGVkHAkDqS4JQeGow",
- "content": "Hi!"
- }
- ],
- "groupId": "kinRXxYop2X4d7F9qt8WNB",
- "options": { "buttonLabel": "Send", "isMultipleChoice": false },
- "outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim"
+ "items": [{ "id": "hQw2zbp7FDX7XYK9cFpbgC", "content": "Hi!" }],
+ "options": { "isMultipleChoice": false, "buttonLabel": "Send" }
}
- ],
- "title": "Welcome",
- "graphCoordinates": { "x": 1, "y": 148 }
+ ]
},
{
"id": "o4SH1UtKANnW5N5D67oZUz",
+ "title": "Email",
+ "graphCoordinates": { "x": 669, "y": 141 },
"blocks": [
{
"id": "sxeYubYN6XzhAfG7m9Fivhc",
"type": "text",
- "groupId": "o4SH1UtKANnW5N5D67oZUz",
"content": {
"richText": [
{
@@ -85,7 +68,6 @@
{
"id": "scQ5kduafAtfP9T8SHUJnGi",
"type": "text",
- "groupId": "o4SH1UtKANnW5N5D67oZUz",
"content": {
"richText": [
{
@@ -99,25 +81,23 @@
},
{
"id": "snbsad18Bgry8yZ8DZCfdFD",
+ "outgoingEdgeId": "w3MiN1Ct38jT5NykVsgmb5",
"type": "email input",
- "groupId": "o4SH1UtKANnW5N5D67oZUz",
"options": {
- "labels": { "button": "Send", "placeholder": "Type your email..." },
- "variableId": "3VFChNVSCXQ2rXv4DrJ8Ah"
- },
- "outgoingEdgeId": "w3MiN1Ct38jT5NykVsgmb5"
+ "variableId": "3VFChNVSCXQ2rXv4DrJ8Ah",
+ "labels": { "placeholder": "Type your email...", "button": "Send" }
+ }
}
- ],
- "title": "Email",
- "graphCoordinates": { "x": 669, "y": 141 }
+ ]
},
{
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
+ "title": "Name",
+ "graphCoordinates": { "x": 340, "y": 143 },
"blocks": [
{
"id": "sgtE2Sy7cKykac9B223Kq9R",
"type": "text",
- "groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"content": {
"richText": [
{ "type": "p", "children": [{ "text": "What's your name?" }] }
@@ -126,29 +106,27 @@
},
{
"id": "sqEsMo747LTDnY9FjQcEwUv",
+ "outgoingEdgeId": "4tYbERpi5Po4goVgt6rWXg",
"type": "text input",
- "groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
"options": {
- "isLong": false,
"labels": {
- "button": "Send",
- "placeholder": "Type your answer..."
+ "placeholder": "Type your answer...",
+ "button": "Send"
},
- "variableId": "giiLFGw5xXBCHzvp1qAbdX"
- },
- "outgoingEdgeId": "4tYbERpi5Po4goVgt6rWXg"
+ "variableId": "giiLFGw5xXBCHzvp1qAbdX",
+ "isLong": false
+ }
}
- ],
- "title": "Name",
- "graphCoordinates": { "x": 340, "y": 143 }
+ ]
},
{
"id": "fKqRz7iswk7ULaj5PJocZL",
+ "title": "Services",
+ "graphCoordinates": { "x": 1002, "y": 144 },
"blocks": [
{
"id": "su7HceVXWyTCzi2vv3m4QbK",
"type": "text",
- "groupId": "fKqRz7iswk7ULaj5PJocZL",
"content": {
"richText": [
{
@@ -160,48 +138,26 @@
},
{
"id": "s5VQGsVF4hQgziQsXVdwPDW",
+ "outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk",
"type": "choice input",
"items": [
- {
- "id": "fnLCBF4NdraSwcubnBhk8H",
- "type": 0,
- "blockId": "s5VQGsVF4hQgziQsXVdwPDW",
- "content": "Website dev"
- },
- {
- "id": "a782h8ynMouY84QjH7XSnR",
- "type": 0,
- "blockId": "s5VQGsVF4hQgziQsXVdwPDW",
- "content": "Content Marketing"
- },
- {
- "id": "jGvh94zBByvVFpSS3w97zY",
- "type": 0,
- "blockId": "s5VQGsVF4hQgziQsXVdwPDW",
- "content": "Social Media"
- },
- {
- "id": "6PRLbKUezuFmwWtLVbvAQ7",
- "type": 0,
- "blockId": "s5VQGsVF4hQgziQsXVdwPDW",
- "content": "UI / UX Design"
- }
+ { "id": "fnLCBF4NdraSwcubnBhk8H", "content": "Website dev" },
+ { "id": "a782h8ynMouY84QjH7XSnR", "content": "Content Marketing" },
+ { "id": "jGvh94zBByvVFpSS3w97zY", "content": "Social Media" },
+ { "id": "6PRLbKUezuFmwWtLVbvAQ7", "content": "UI / UX Design" }
],
- "groupId": "fKqRz7iswk7ULaj5PJocZL",
- "options": { "buttonLabel": "Send", "isMultipleChoice": true },
- "outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk"
+ "options": { "isMultipleChoice": true, "buttonLabel": "Send" }
}
- ],
- "title": "Services",
- "graphCoordinates": { "x": 1002, "y": 144 }
+ ]
},
{
"id": "7qHBEyCMvKEJryBHzPmHjV",
+ "title": "Additional information",
+ "graphCoordinates": { "x": 1337, "y": 145 },
"blocks": [
{
"id": "sqR8Sz9gW21aUYKtUikq7qZ",
"type": "text",
- "groupId": "7qHBEyCMvKEJryBHzPmHjV",
"content": {
"richText": [
{
@@ -215,33 +171,34 @@
},
{
"id": "sqFy2G3C1mh9p6s3QBdSS5x",
+ "outgoingEdgeId": "sH5nUssG2XQbm6ZidGv9BY",
"type": "text input",
- "groupId": "7qHBEyCMvKEJryBHzPmHjV",
"options": {
- "isLong": true,
- "labels": { "button": "Send", "placeholder": "Type your answer..." }
- },
- "outgoingEdgeId": "sH5nUssG2XQbm6ZidGv9BY"
+ "labels": {
+ "placeholder": "Type your answer...",
+ "button": "Send"
+ },
+ "isLong": true
+ }
}
- ],
- "title": "Additional information",
- "graphCoordinates": { "x": 1337, "y": 145 }
+ ]
},
{
"id": "vF7AD7zSAj7SNvN3gr9N94",
+ "title": "Bye",
+ "graphCoordinates": { "x": 1668, "y": 143 },
"blocks": [
{
"id": "seLegenCgUwMopRFeAefqZ7",
"type": "text",
- "groupId": "vF7AD7zSAj7SNvN3gr9N94",
"content": {
"richText": [{ "type": "p", "children": [{ "text": "Perfect!" }] }]
}
},
{
"id": "s779Q1y51aVaDUJVrFb16vv",
+ "outgoingEdgeId": "fTVo43AG97eKcaTrZf9KyV",
"type": "text",
- "groupId": "vF7AD7zSAj7SNvN3gr9N94",
"content": {
"richText": [
{
@@ -249,110 +206,107 @@
"children": [{ "text": "We'll get back to you at {{Email}}" }]
}
]
- },
- "outgoingEdgeId": "fTVo43AG97eKcaTrZf9KyV"
+ }
}
- ],
- "title": "Bye",
- "graphCoordinates": { "x": 1668, "y": 143 }
+ ]
},
{
"id": "webhookGroup",
- "graphCoordinates": { "x": 1996, "y": 134 },
"title": "Webhook",
+ "graphCoordinates": { "x": 1996, "y": 134 },
"blocks": [
{
"id": "webhookBlock",
- "groupId": "webhookGroup",
"type": "Webhook",
- "options": { "responseVariableMapping": [], "variablesForTest": [] },
- "webhookId": "webhook1"
+ "options": {
+ "variablesForTest": [],
+ "responseVariableMapping": [],
+ "webhook": { "method": "POST" }
+ }
}
]
}
],
- "variables": [
- { "id": "giiLFGw5xXBCHzvp1qAbdX", "name": "Name" },
- { "id": "3VFChNVSCXQ2rXv4DrJ8Ah", "name": "Email" }
- ],
"edges": [
{
"id": "oNvqaqNExdSH2kKEhKZHuE",
- "to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" },
- "from": {
- "blockId": "22HP69iipkLjJDTUcc1AWW",
- "groupId": "k6kY6gwRE6noPoYQNGzgUq"
- }
+ "from": { "eventId": "k6kY6gwRE6noPoYQNGzgUq" },
+ "to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" }
},
{
"id": "i51YhHpk1dtSyduFNf5Wim",
- "to": { "groupId": "q5dAhqSTCaNdiGSJm9B9Rw" },
- "from": {
- "blockId": "sbjZWLJGVkHAkDqS4JQeGow",
- "groupId": "kinRXxYop2X4d7F9qt8WNB"
- }
+ "from": { "blockId": "sbjZWLJGVkHAkDqS4JQeGow" },
+ "to": { "groupId": "q5dAhqSTCaNdiGSJm9B9Rw" }
},
{
"id": "4tYbERpi5Po4goVgt6rWXg",
- "to": { "groupId": "o4SH1UtKANnW5N5D67oZUz" },
- "from": {
- "blockId": "sqEsMo747LTDnY9FjQcEwUv",
- "groupId": "q5dAhqSTCaNdiGSJm9B9Rw"
- }
+ "from": { "blockId": "sqEsMo747LTDnY9FjQcEwUv" },
+ "to": { "groupId": "o4SH1UtKANnW5N5D67oZUz" }
},
{
"id": "w3MiN1Ct38jT5NykVsgmb5",
- "to": { "groupId": "fKqRz7iswk7ULaj5PJocZL" },
- "from": {
- "blockId": "snbsad18Bgry8yZ8DZCfdFD",
- "groupId": "o4SH1UtKANnW5N5D67oZUz"
- }
+ "from": { "blockId": "snbsad18Bgry8yZ8DZCfdFD" },
+ "to": { "groupId": "fKqRz7iswk7ULaj5PJocZL" }
},
{
"id": "ohTRakmcYJ7GdFWRZrWRjk",
- "to": { "groupId": "7qHBEyCMvKEJryBHzPmHjV" },
- "from": {
- "blockId": "s5VQGsVF4hQgziQsXVdwPDW",
- "groupId": "fKqRz7iswk7ULaj5PJocZL"
- }
+ "from": { "blockId": "s5VQGsVF4hQgziQsXVdwPDW" },
+ "to": { "groupId": "7qHBEyCMvKEJryBHzPmHjV" }
},
{
"id": "sH5nUssG2XQbm6ZidGv9BY",
- "to": { "groupId": "vF7AD7zSAj7SNvN3gr9N94" },
- "from": {
- "blockId": "sqFy2G3C1mh9p6s3QBdSS5x",
- "groupId": "7qHBEyCMvKEJryBHzPmHjV"
- }
+ "from": { "blockId": "sqFy2G3C1mh9p6s3QBdSS5x" },
+ "to": { "groupId": "vF7AD7zSAj7SNvN3gr9N94" }
},
{
- "from": {
- "groupId": "vF7AD7zSAj7SNvN3gr9N94",
- "blockId": "s779Q1y51aVaDUJVrFb16vv"
- },
- "to": { "groupId": "webhookGroup" },
- "id": "fTVo43AG97eKcaTrZf9KyV"
+ "id": "fTVo43AG97eKcaTrZf9KyV",
+ "from": { "blockId": "s779Q1y51aVaDUJVrFb16vv" },
+ "to": { "groupId": "webhookGroup" }
+ }
+ ],
+ "variables": [
+ {
+ "id": "giiLFGw5xXBCHzvp1qAbdX",
+ "name": "Name",
+ "isSessionVariable": true
+ },
+ {
+ "id": "3VFChNVSCXQ2rXv4DrJ8Ah",
+ "name": "Email",
+ "isSessionVariable": true
}
],
"theme": {
+ "general": { "font": "Open Sans", "background": { "type": "None" } },
"chat": {
+ "hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
+ "guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
+ "buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
"inputs": {
- "color": "#303235",
"backgroundColor": "#FFFFFF",
+ "color": "#303235",
"placeholderColor": "#9095A0"
- },
- "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
- "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
- "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
- },
- "general": { "font": "Open Sans", "background": { "type": "None" } }
+ }
+ }
},
+ "selectedThemeTemplateId": null,
"settings": {
"general": { "isBrandingEnabled": true },
+ "typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
- },
- "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ }
},
+ "createdAt": "2024-07-16T12:30:05.790Z",
+ "updatedAt": "2024-07-16T12:30:05.790Z",
+ "icon": null,
+ "folderId": null,
"publicId": null,
- "customDomain": null
+ "customDomain": null,
+ "workspaceId": "proWorkspace",
+ "resultsTablePreferences": null,
+ "isArchived": false,
+ "isClosed": false,
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/builder/src/test/assets/typebots/integrations/easyConfigWebhook.json b/apps/builder/src/test/assets/typebots/integrations/easyConfigWebhook.json
index 6efc4bef7..fe3e9337e 100644
--- a/apps/builder/src/test/assets/typebots/integrations/easyConfigWebhook.json
+++ b/apps/builder/src/test/assets/typebots/integrations/easyConfigWebhook.json
@@ -118,8 +118,7 @@
"variablesForTest": [],
"isAdvancedConfig": false,
"isCustomBody": false
- },
- "webhookId": "webhook1"
+ }
}
]
}
diff --git a/apps/builder/src/test/assets/typebots/integrations/webhook.json b/apps/builder/src/test/assets/typebots/integrations/webhook.json
index 34ef81f29..7bd01dfd7 100644
--- a/apps/builder/src/test/assets/typebots/integrations/webhook.json
+++ b/apps/builder/src/test/assets/typebots/integrations/webhook.json
@@ -1,32 +1,23 @@
{
- "id": "ckz8gli9e9842no1afuppdn0z",
- "createdAt": "2022-02-04T13:44:30.386Z",
- "updatedAt": "2022-02-04T13:44:30.386Z",
+ "version": "6",
+ "id": "clyoe6owl0003grw9k9hzc9qs",
"name": "My typebot",
- "folderId": null,
- "groups": [
+ "events": [
{
"id": "p6GeeRXHgwiJeoJRBkKaMJ",
- "blocks": [
- {
- "id": "iDS7jFemUsQ7Sp3eu3xg3w",
- "type": "start",
- "label": "Start",
- "groupId": "p6GeeRXHgwiJeoJRBkKaMJ",
- "outgoingEdgeId": "cyEJPaLU7AchnBSaeWoyiS"
- }
- ],
- "title": "Start",
- "graphCoordinates": { "x": 0, "y": 0 }
- },
+ "outgoingEdgeId": "cyEJPaLU7AchnBSaeWoyiS",
+ "graphCoordinates": { "x": 0, "y": 0 },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "kBneEpKdMYrF65XxUQ5GS7",
- "graphCoordinates": { "x": 260, "y": 186 },
"title": "Group #1",
+ "graphCoordinates": { "x": 260, "y": 186 },
"blocks": [
{
"id": "skSkZ4PNP7m1gYvu9Ew6ngM",
- "groupId": "kBneEpKdMYrF65XxUQ5GS7",
"type": "text",
"content": {
"richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }]
@@ -34,85 +25,89 @@
},
{
"id": "sh6ZVRA3o72y6BEiNKVcoma",
- "groupId": "kBneEpKdMYrF65XxUQ5GS7",
"type": "choice input",
- "options": { "buttonLabel": "Send", "isMultipleChoice": false },
"items": [
{
"id": "rr5mKKBPq73ZrfXZ3uuupz",
- "blockId": "sh6ZVRA3o72y6BEiNKVcoma",
- "type": 0,
- "content": "Go",
- "outgoingEdgeId": "1sLicz8gq2QxytFTwBd8ac"
+ "outgoingEdgeId": "1sLicz8gq2QxytFTwBd8ac",
+ "content": "Go"
}
- ]
+ ],
+ "options": { "isMultipleChoice": false, "buttonLabel": "Send" }
}
]
},
{
"id": "8XnDM1QsqPms4LQHh8q3Jo",
- "graphCoordinates": { "x": 646, "y": 511 },
"title": "Group #2",
+ "graphCoordinates": { "x": 646, "y": 511 },
"blocks": [
{
"id": "soSmiE7zyb3WF77GxFxAjYX",
- "groupId": "8XnDM1QsqPms4LQHh8q3Jo",
"type": "Webhook",
"options": {
- "responseVariableMapping": [],
"variablesForTest": [],
+ "responseVariableMapping": [],
"isAdvancedConfig": false,
- "isCustomBody": false
- },
- "webhookId": "webhook1"
+ "isCustomBody": false,
+ "webhook": { "method": "POST" }
+ }
}
]
}
],
+ "edges": [
+ {
+ "id": "cyEJPaLU7AchnBSaeWoyiS",
+ "from": { "eventId": "p6GeeRXHgwiJeoJRBkKaMJ" },
+ "to": { "groupId": "kBneEpKdMYrF65XxUQ5GS7" }
+ },
+ {
+ "id": "1sLicz8gq2QxytFTwBd8ac",
+ "from": {
+ "blockId": "sh6ZVRA3o72y6BEiNKVcoma",
+ "itemId": "rr5mKKBPq73ZrfXZ3uuupz"
+ },
+ "to": { "groupId": "8XnDM1QsqPms4LQHh8q3Jo" }
+ }
+ ],
"variables": [
{ "id": "var1", "name": "secret 1" },
{ "id": "var2", "name": "secret 2" },
{ "id": "var3", "name": "secret 3" },
{ "id": "var4", "name": "secret 4" }
],
- "edges": [
- {
- "from": {
- "groupId": "p6GeeRXHgwiJeoJRBkKaMJ",
- "blockId": "iDS7jFemUsQ7Sp3eu3xg3w"
- },
- "to": { "groupId": "kBneEpKdMYrF65XxUQ5GS7" },
- "id": "cyEJPaLU7AchnBSaeWoyiS"
- },
- {
- "from": {
- "groupId": "kBneEpKdMYrF65XxUQ5GS7",
- "blockId": "sh6ZVRA3o72y6BEiNKVcoma",
- "itemId": "rr5mKKBPq73ZrfXZ3uuupz"
- },
- "to": { "groupId": "8XnDM1QsqPms4LQHh8q3Jo" },
- "id": "1sLicz8gq2QxytFTwBd8ac"
- }
- ],
"theme": {
+ "general": { "font": "Open Sans", "background": { "type": "None" } },
"chat": {
+ "hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
+ "guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
+ "buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
"inputs": {
- "color": "#303235",
"backgroundColor": "#FFFFFF",
+ "color": "#303235",
"placeholderColor": "#9095A0"
- },
- "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
- "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
- "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
- },
- "general": { "font": "Open Sans", "background": { "type": "None" } }
+ }
+ }
},
+ "selectedThemeTemplateId": null,
"settings": {
"general": { "isBrandingEnabled": true },
+ "typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
- },
- "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ }
},
- "publicId": null
+ "createdAt": "2024-07-16T12:31:00.501Z",
+ "updatedAt": "2024-07-16T12:31:00.501Z",
+ "icon": null,
+ "folderId": null,
+ "publicId": null,
+ "customDomain": null,
+ "workspaceId": "proWorkspace",
+ "resultsTablePreferences": null,
+ "isArchived": false,
+ "isClosed": false,
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/docs/openapi/builder.json b/apps/docs/openapi/builder.json
index d50678994..f6f679ba7 100644
--- a/apps/docs/openapi/builder.json
+++ b/apps/docs/openapi/builder.json
@@ -12880,40 +12880,6 @@
"name"
]
},
- {
- "type": "object",
- "properties": {
- "data": {
- "type": "object",
- "properties": {
- "apiKey": {
- "type": "string"
- }
- },
- "required": [
- "apiKey"
- ]
- },
- "type": {
- "type": "string",
- "enum": [
- "openai"
- ]
- },
- "workspaceId": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- },
- "required": [
- "data",
- "type",
- "workspaceId",
- "name"
- ]
- },
{
"type": "object",
"properties": {
@@ -12961,15 +12927,297 @@
"apiKey": {
"type": "string"
}
- },
- "required": [
- "apiKey"
- ]
+ }
},
"type": {
"type": "string",
"enum": [
- "zemanticAi"
+ "openai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "zemantic-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "chat-node"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiEndpoint": {
+ "type": "string"
+ },
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "dify-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "mistral"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "elevenlabs"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "anthropic"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "together-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "open-router"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "data",
+ "type",
+ "workspaceId",
+ "name"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "baseUrl": {
+ "type": "string"
+ },
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "nocodb"
]
},
"workspaceId": {
@@ -13091,62 +13339,24 @@
"in": "query",
"name": "type",
"schema": {
- "anyOf": [
- {
- "anyOf": [
- {
- "anyOf": [
- {
- "anyOf": [
- {
- "anyOf": [
- {
- "type": "string",
- "enum": [
- "stripe"
- ]
- },
- {
- "type": "string",
- "enum": [
- "smtp"
- ]
- }
- ]
- },
- {
- "type": "string",
- "enum": [
- "google sheets"
- ]
- }
- ]
- },
- {
- "type": "string",
- "enum": [
- "openai"
- ]
- }
- ]
- },
- {
- "type": "string",
- "enum": [
- "whatsApp"
- ]
- }
- ]
- },
- {
- "type": "string",
- "enum": [
- "zemanticAi"
- ]
- }
+ "type": "string",
+ "enum": [
+ "smtp",
+ "google sheets",
+ "stripe",
+ "whatsApp",
+ "openai",
+ "zemantic-ai",
+ "chat-node",
+ "dify-ai",
+ "mistral",
+ "elevenlabs",
+ "anthropic",
+ "together-ai",
+ "open-router",
+ "nocodb"
]
- },
- "required": true
+ }
}
],
"responses": {
@@ -13165,12 +13375,32 @@
"id": {
"type": "string"
},
+ "type": {
+ "type": "string",
+ "enum": [
+ "smtp",
+ "google sheets",
+ "stripe",
+ "whatsApp",
+ "openai",
+ "zemantic-ai",
+ "chat-node",
+ "dify-ai",
+ "mistral",
+ "elevenlabs",
+ "anthropic",
+ "together-ai",
+ "open-router",
+ "nocodb"
+ ]
+ },
"name": {
"type": "string"
}
},
"required": [
"id",
+ "type",
"name"
]
}
@@ -13236,6 +13466,741 @@
}
}
},
+ "/v1/credentials/{credentialsId}": {
+ "get": {
+ "operationId": "credentials-getCredentials",
+ "summary": "Get credentials data",
+ "tags": [
+ "Credentials"
+ ],
+ "security": [
+ {
+ "Authorization": []
+ }
+ ],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "credentialsId",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ },
+ {
+ "in": "query",
+ "name": "workspaceId",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {}
+ },
+ "required": [
+ "name"
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid input data",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.BAD_REQUEST"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization not provided",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.UNAUTHORIZED"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Insufficient access",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.FORBIDDEN"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.NOT_FOUND"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.INTERNAL_SERVER_ERROR"
+ }
+ }
+ }
+ }
+ }
+ },
+ "patch": {
+ "operationId": "credentials-updateCredentials",
+ "summary": "Create credentials",
+ "tags": [
+ "Credentials"
+ ],
+ "security": [
+ {
+ "Authorization": []
+ }
+ ],
+ "parameters": [
+ {
+ "in": "path",
+ "name": "credentialsId",
+ "schema": {
+ "type": "string"
+ },
+ "required": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "credentials": {
+ "oneOf": [
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "live": {
+ "type": "object",
+ "properties": {
+ "secretKey": {
+ "type": "string"
+ },
+ "publicKey": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "secretKey",
+ "publicKey"
+ ]
+ },
+ "test": {
+ "type": "object",
+ "properties": {
+ "secretKey": {
+ "type": "string"
+ },
+ "publicKey": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": [
+ "live",
+ "test"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "stripe"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "host": {
+ "type": "string"
+ },
+ "username": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "isTlsEnabled": {
+ "type": "boolean"
+ },
+ "port": {
+ "type": "number"
+ },
+ "from": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": [
+ "port",
+ "from"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "smtp"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "refresh_token": {
+ "type": "string",
+ "nullable": true
+ },
+ "expiry_date": {
+ "type": "number",
+ "nullable": true
+ },
+ "access_token": {
+ "type": "string",
+ "nullable": true
+ },
+ "token_type": {
+ "type": "string",
+ "nullable": true
+ },
+ "id_token": {
+ "type": "string",
+ "nullable": true
+ },
+ "scope": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "google sheets"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "systemUserAccessToken": {
+ "type": "string"
+ },
+ "phoneNumberId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "systemUserAccessToken",
+ "phoneNumberId"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "whatsApp"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "openai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "zemantic-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "chat-node"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiEndpoint": {
+ "type": "string"
+ },
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "dify-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "mistral"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "elevenlabs"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "anthropic"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "together-ai"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "open-router"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "baseUrl": {
+ "type": "string"
+ },
+ "apiKey": {
+ "type": "string"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "nocodb"
+ ]
+ },
+ "workspaceId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "data",
+ "type",
+ "workspaceId"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "credentials"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Successful response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "credentialsId": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "credentialsId"
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid input data",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.BAD_REQUEST"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization not provided",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.UNAUTHORIZED"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Insufficient access",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.FORBIDDEN"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.NOT_FOUND"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error.INTERNAL_SERVER_ERROR"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/v1/credentials/:credentialsId": {
"delete": {
"operationId": "credentials-deleteCredentials",
diff --git a/apps/viewer/.eslintignore b/apps/viewer/.eslintignore
new file mode 100644
index 000000000..ebd9b0fa7
--- /dev/null
+++ b/apps/viewer/.eslintignore
@@ -0,0 +1 @@
+src/test/reporters
\ No newline at end of file
diff --git a/apps/viewer/package.json b/apps/viewer/package.json
index 5e9194dbc..eb21809c8 100644
--- a/apps/viewer/package.json
+++ b/apps/viewer/package.json
@@ -40,7 +40,7 @@
"devDependencies": {
"@faire/mjml-react": "3.3.0",
"@paralleldrive/cuid2": "2.2.1",
- "@playwright/test": "1.43.1",
+ "@playwright/test": "1.45.2",
"@typebot.io/emails": "workspace:*",
"@typebot.io/env": "workspace:*",
"@typebot.io/forge": "workspace:*",
diff --git a/apps/viewer/playwright.config.ts b/apps/viewer/playwright.config.ts
index 8ba2ccb1c..961411c9b 100644
--- a/apps/viewer/playwright.config.ts
+++ b/apps/viewer/playwright.config.ts
@@ -10,13 +10,13 @@ export default defineConfig({
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
},
forbidOnly: !!process.env.CI,
- workers: process.env.CI ? 1 : 3,
- retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : 4,
+ retries: process.env.CI ? 2 : 1,
reporter: [
[process.env.CI ? 'github' : 'list'],
['html', { outputFolder: 'src/test/reporters' }],
],
- maxFailures: process.env.CI ? 10 : undefined,
+ maxFailures: 10,
webServer: process.env.CI
? {
command: 'pnpm run start',
diff --git a/apps/viewer/src/test/assets/typebots/chat/linkedBot.json b/apps/viewer/src/test/assets/typebots/chat/linkedBot.json
index fa01ccc04..b984836ed 100644
--- a/apps/viewer/src/test/assets/typebots/chat/linkedBot.json
+++ b/apps/viewer/src/test/assets/typebots/chat/linkedBot.json
@@ -1,28 +1,20 @@
{
- "id": "chat-sub-bot",
- "createdAt": "2022-11-24T09:06:52.903Z",
- "updatedAt": "2022-11-24T09:13:16.782Z",
- "icon": "👶",
+ "version": "6",
+ "id": "clyoehfmp0007grw9ubdop6u0",
"name": "Sub bot",
- "folderId": null,
- "groups": [
+ "events": [
{
"id": "clauup2lh0002vs1a5ei32mmi",
- "title": "Start",
- "blocks": [
- {
- "id": "clauup2li0003vs1aas14fwpc",
- "type": "start",
- "label": "Start",
- "groupId": "clauup2lh0002vs1a5ei32mmi",
- "outgoingEdgeId": "clauupl9n001b3b6qdk4czgom"
- }
- ],
- "graphCoordinates": { "x": 0, "y": 0 }
- },
+ "outgoingEdgeId": "clauupl9n001b3b6qdk4czgom",
+ "graphCoordinates": { "x": 0, "y": 0 },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "clauupd6q00183b6qcm8qbz62",
"title": "Group #1",
+ "graphCoordinates": { "x": 375.36328125, "y": 167.2578125 },
"blocks": [
{
"id": "clauupd6q00193b6qhegmlnxj",
@@ -36,69 +28,69 @@
]
}
]
- },
- "groupId": "clauupd6q00183b6qcm8qbz62"
+ }
},
{
"id": "clauupk97001a3b6q2w9qqkec",
"type": "rating input",
- "groupId": "clauupd6q00183b6qcm8qbz62",
"options": {
- "labels": { "button": "Send" },
- "length": 10,
"buttonType": "Numbers",
+ "length": 10,
+ "labels": { "button": "Send" },
"customIcon": { "isEnabled": false }
}
}
- ],
- "graphCoordinates": { "x": 375.36328125, "y": 167.2578125 }
+ ]
}
],
- "variables": [],
"edges": [
{
"id": "clauupl9n001b3b6qdk4czgom",
- "to": { "groupId": "clauupd6q00183b6qcm8qbz62" },
- "from": {
- "blockId": "clauup2li0003vs1aas14fwpc",
- "groupId": "clauup2lh0002vs1a5ei32mmi"
- }
+ "from": { "eventId": "clauup2lh0002vs1a5ei32mmi" },
+ "to": { "groupId": "clauupd6q00183b6qcm8qbz62" }
}
],
+ "variables": [],
"theme": {
+ "general": { "font": "Open Sans", "background": { "type": "None" } },
"chat": {
- "inputs": {
- "color": "#303235",
- "backgroundColor": "#FFFFFF",
- "placeholderColor": "#9095A0"
- },
- "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
"hostAvatar": {
- "url": "https://avatars.githubusercontent.com/u/16015833?v=4",
- "isEnabled": true
+ "isEnabled": true,
+ "url": "https://avatars.githubusercontent.com/u/16015833?v=4"
},
- "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
- "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
- },
- "general": { "font": "Open Sans", "background": { "type": "None" } }
+ "hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
+ "guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
+ "buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
+ "inputs": {
+ "backgroundColor": "#FFFFFF",
+ "color": "#303235",
+ "placeholderColor": "#9095A0"
+ }
+ }
},
+ "selectedThemeTemplateId": null,
"settings": {
"general": {
"isBrandingEnabled": false,
"isInputPrefillEnabled": true,
- "isResultSavingEnabled": true,
"isHideQueryParamsEnabled": true,
"isNewResultOnRefreshEnabled": false
},
+ "typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
- },
- "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ }
},
+ "createdAt": "2024-07-16T12:39:21.697Z",
+ "updatedAt": "2024-07-16T12:39:21.697Z",
+ "icon": "👶",
+ "folderId": null,
"publicId": null,
"customDomain": null,
"workspaceId": "proWorkspace",
"resultsTablePreferences": null,
"isArchived": false,
- "isClosed": false
+ "isClosed": false,
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/viewer/src/test/assets/typebots/chat/main.json b/apps/viewer/src/test/assets/typebots/chat/main.json
index 691777cfd..f916fef81 100644
--- a/apps/viewer/src/test/assets/typebots/chat/main.json
+++ b/apps/viewer/src/test/assets/typebots/chat/main.json
@@ -1,28 +1,20 @@
{
- "id": "clauujawp00011avs2vj97zma",
- "createdAt": "2022-11-24T09:02:23.737Z",
- "updatedAt": "2022-11-24T09:12:57.036Z",
- "icon": "🤖",
+ "version": "6",
+ "id": "clyoegjca0005grw9ek6h984v",
"name": "Complete bot",
- "folderId": null,
- "groups": [
+ "events": [
{
"id": "clauujawn0000vs1a8z6k2k7d",
- "title": "Start",
- "blocks": [
- {
- "id": "clauujawn0001vs1a0mk8docp",
- "type": "start",
- "label": "Start",
- "groupId": "clauujawn0000vs1a8z6k2k7d",
- "outgoingEdgeId": "clauuk4o300083b6q7b2iowv3"
- }
- ],
- "graphCoordinates": { "x": 0, "y": 0 }
- },
+ "outgoingEdgeId": "clauuk4o300083b6q7b2iowv3",
+ "graphCoordinates": { "x": 0, "y": 0 },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "clauujxdc00063b6q42ca20gj",
"title": "Welcome",
+ "graphCoordinates": { "x": 5.81640625, "y": 172.359375 },
"blocks": [
{
"id": "clauujxdd00073b6qpejnkzcy",
@@ -31,8 +23,7 @@
"richText": [
{ "type": "p", "children": [{ "text": "Hi there! 👋" }] }
]
- },
- "groupId": "clauujxdc00063b6q42ca20gj"
+ }
},
{
"id": "clauukaad00093b6q07av51yc",
@@ -44,29 +35,27 @@
"children": [{ "text": "Welcome. What's your name?" }]
}
]
- },
- "groupId": "clauujxdc00063b6q42ca20gj"
+ }
},
{
"id": "clauukip8000a3b6qtzl288tu",
+ "outgoingEdgeId": "clauul0sk000f3b6q2tvy5wfi",
"type": "text input",
- "groupId": "clauujxdc00063b6q42ca20gj",
"options": {
- "isLong": false,
"labels": {
- "button": "Send",
- "placeholder": "Type your answer..."
+ "placeholder": "Type your answer...",
+ "button": "Send"
},
- "variableId": "vclauuklnc000b3b6q7xchq4yf"
- },
- "outgoingEdgeId": "clauul0sk000f3b6q2tvy5wfi"
+ "variableId": "vclauuklnc000b3b6q7xchq4yf",
+ "isLong": false
+ }
}
- ],
- "graphCoordinates": { "x": 5.81640625, "y": 172.359375 }
+ ]
},
{
"id": "clauukoka000c3b6qe6chawis",
"title": "Age",
+ "graphCoordinates": { "x": 361.17578125, "y": 170.10546875 },
"blocks": [
{
"id": "clauukoka000d3b6qxqi38cmk",
@@ -78,16 +67,14 @@
"children": [{ "text": "Nice to meet you {{Name}}" }]
}
]
- },
- "groupId": "clauukoka000c3b6qe6chawis"
+ }
},
{
"id": "clauuku5o000e3b6q90rm30p1",
"type": "image",
"content": {
"url": "https://media2.giphy.com/media/l0MYGb1LuZ3n7dRnO/giphy-downsized.gif?cid=fe3852a3yd2leg4yi8iual3wgyw893zzocuuqlp3wytt802h&rid=giphy-downsized.gif&ct=g"
- },
- "groupId": "clauukoka000c3b6qe6chawis"
+ }
},
{
"id": "clauul4vg000g3b6qr0q2h0uy",
@@ -96,60 +83,56 @@
"richText": [
{ "type": "p", "children": [{ "text": "How old are you?" }] }
]
- },
- "groupId": "clauukoka000c3b6qe6chawis"
+ }
},
{
"id": "clauul90j000h3b6qjfrw9js4",
+ "outgoingEdgeId": "clauum41j000n3b6qpqu12icm",
"type": "number input",
- "groupId": "clauukoka000c3b6qe6chawis",
"options": {
- "labels": { "button": "Send", "placeholder": "Type a number..." },
- "variableId": "vclauulfjk000i3b6qmujooweu"
- },
- "outgoingEdgeId": "clauum41j000n3b6qpqu12icm"
+ "variableId": "vclauulfjk000i3b6qmujooweu",
+ "labels": { "placeholder": "Type a number...", "button": "Send" }
+ }
}
- ],
- "graphCoordinates": { "x": 361.17578125, "y": 170.10546875 }
+ ]
},
{
"id": "clauulhqf000j3b6qm8y5oifc",
"title": "Is major?",
+ "graphCoordinates": { "x": 726.2265625, "y": 240.80078125 },
"blocks": [
{
"id": "clauulhqf000k3b6qsrc1hd74",
+ "outgoingEdgeId": "clauumm5v000t3b6qu62qcft8",
"type": "Condition",
"items": [
{
"id": "clauulhqg000l3b6qaxn4qli5",
- "type": 1,
- "blockId": "clauulhqf000k3b6qsrc1hd74",
+ "outgoingEdgeId": "clauumi0x000q3b6q9bwkqnmr",
"content": {
+ "logicalOperator": "AND",
"comparisons": [
{
"id": "clauuliyn000m3b6q10gwx8ii",
- "value": "21",
"variableId": "vclauulfjk000i3b6qmujooweu",
- "comparisonOperator": "Greater than"
+ "comparisonOperator": "Greater than",
+ "value": "21"
}
- ],
- "logicalOperator": "AND"
- },
- "outgoingEdgeId": "clauumi0x000q3b6q9bwkqnmr"
+ ]
+ }
}
- ],
- "groupId": "clauulhqf000j3b6qm8y5oifc",
- "outgoingEdgeId": "clauumm5v000t3b6qu62qcft8"
+ ]
}
- ],
- "graphCoordinates": { "x": 726.2265625, "y": 240.80078125 }
+ ]
},
{
"id": "clauum8x7000o3b6qx8hqduf8",
"title": "Group #4",
+ "graphCoordinates": { "x": 1073.38671875, "y": 232.25 },
"blocks": [
{
"id": "clauum8x7000p3b6qxjud5hdc",
+ "outgoingEdgeId": "clauuom2y000y3b6qkcjy2ri7",
"type": "text",
"content": {
"richText": [
@@ -158,39 +141,35 @@
"children": [{ "text": "Ok, you are an adult then 😁" }]
}
]
- },
- "groupId": "clauum8x7000o3b6qx8hqduf8",
- "outgoingEdgeId": "clauuom2y000y3b6qkcjy2ri7"
+ }
}
- ],
- "graphCoordinates": { "x": 1073.38671875, "y": 232.25 }
+ ]
},
{
"id": "clauumjq4000r3b6q8l6bi9ra",
"title": "Group #4 copy",
+ "graphCoordinates": { "x": 1073.984375, "y": 408.6875 },
"blocks": [
{
"id": "clauumjq5000s3b6qqjhrklv4",
+ "outgoingEdgeId": "clauuol8t000x3b6qcw1few70",
"type": "text",
"content": {
"richText": [
{ "type": "p", "children": [{ "text": "Oh, you are a kid 😁" }] }
]
- },
- "groupId": "clauumjq4000r3b6q8l6bi9ra",
- "outgoingEdgeId": "clauuol8t000x3b6qcw1few70"
+ }
}
- ],
- "graphCoordinates": { "x": 1073.984375, "y": 408.6875 }
+ ]
},
{
"id": "clauuoekh000u3b6q6zmlx7f9",
"title": "Magic number",
+ "graphCoordinates": { "x": 1465.359375, "y": 299.25390625 },
"blocks": [
{
"id": "clauuoeki000v3b6qvsh7kde1",
"type": "Set variable",
- "groupId": "clauuoekh000u3b6q6zmlx7f9",
"options": {
"variableId": "vclauuohyp000w3b6qbqrs6c6w",
"expressionToEvaluate": "42"
@@ -198,6 +177,7 @@
},
{
"id": "clauuontu000z3b6q3ydx6ao1",
+ "outgoingEdgeId": "clauuq8je001e3b6qksm4j11g",
"type": "text",
"content": {
"richText": [
@@ -206,38 +186,33 @@
"children": [{ "text": "My magic number is {{Magic number}}" }]
}
]
- },
- "groupId": "clauuoekh000u3b6q6zmlx7f9",
- "outgoingEdgeId": "clauuq8je001e3b6qksm4j11g"
+ }
}
- ],
- "graphCoordinates": { "x": 1465.359375, "y": 299.25390625 }
+ ]
},
{
"id": "clauuq2l6001c3b6qpmq3ivwk",
- "graphCoordinates": { "x": 1836.43359375, "y": 295.39453125 },
"title": "Rate the experience",
+ "graphCoordinates": { "x": 1836.43359375, "y": 295.39453125 },
"blocks": [
{
"id": "clauuq2l6001d3b6qyltfcvgb",
- "groupId": "clauuq2l6001c3b6qpmq3ivwk",
+ "outgoingEdgeId": "clauureo3001h3b6qk6epabxq",
"type": "Typebot link",
"options": {
"typebotId": "chat-sub-bot",
"groupId": "clauupd6q00183b6qcm8qbz62"
- },
- "outgoingEdgeId": "clauureo3001h3b6qk6epabxq"
+ }
}
]
},
{
"id": "clauur7od001f3b6qq140oe55",
- "graphCoordinates": { "x": 2201.45703125, "y": 299.1328125 },
"title": "Multiple input in group",
+ "graphCoordinates": { "x": 2201.45703125, "y": 299.1328125 },
"blocks": [
{
"id": "clauur7od001g3b6qkoeij3f7",
- "groupId": "clauur7od001f3b6qq140oe55",
"type": "text",
"content": {
"richText": [
@@ -252,53 +227,39 @@
},
{
"id": "clauurluf001i3b6qjf78puug",
- "groupId": "clauur7od001f3b6qq140oe55",
"type": "email input",
"options": {
- "labels": { "button": "Send", "placeholder": "Type your email..." },
+ "labels": { "placeholder": "Type your email...", "button": "Send" },
"retryMessageContent": "This email doesn't seem to be valid. Can you type it again?"
}
},
{
"id": "clauurokp001j3b6qyrw7boca",
- "groupId": "clauur7od001f3b6qq140oe55",
"type": "url input",
"options": {
- "labels": { "button": "Send", "placeholder": "Type a URL..." },
+ "labels": { "placeholder": "Type a URL...", "button": "Send" },
"retryMessageContent": "This URL doesn't seem to be valid. Can you type it again?"
}
},
{
"id": "clauurs1o001k3b6qgrj0xf59",
- "groupId": "clauur7od001f3b6qq140oe55",
+ "outgoingEdgeId": "clauushy3001p3b6qqnyrxgtb",
"type": "choice input",
- "options": { "buttonLabel": "Send", "isMultipleChoice": false },
"items": [
- {
- "id": "clauurs1o001l3b6qu9hr712h",
- "blockId": "clauurs1o001k3b6qgrj0xf59",
- "type": 0,
- "content": "Yes"
- },
- {
- "id": "clauuru6t001m3b6qp8vkt23l",
- "content": "No",
- "blockId": "clauurs1o001k3b6qgrj0xf59",
- "type": 0
- }
+ { "id": "clauurs1o001l3b6qu9hr712h", "content": "Yes" },
+ { "id": "clauuru6t001m3b6qp8vkt23l", "content": "No" }
],
- "outgoingEdgeId": "clauushy3001p3b6qqnyrxgtb"
+ "options": { "isMultipleChoice": false, "buttonLabel": "Send" }
}
]
},
{
"id": "clauusa9z001n3b6qys3xvz1l",
- "graphCoordinates": { "x": 2558.609375, "y": 297.078125 },
"title": "Get Chuck Norris joke",
+ "graphCoordinates": { "x": 2558.609375, "y": 297.078125 },
"blocks": [
{
"id": "clauusaa0001o3b6qgddldaen",
- "groupId": "clauusa9z001n3b6qys3xvz1l",
"type": "text",
"content": {
"richText": [
@@ -308,7 +269,6 @@
},
{
"id": "clauusrfh001q3b6q7xaapi4h",
- "groupId": "clauusa9z001n3b6qys3xvz1l",
"type": "text",
"content": {
"richText": [
@@ -321,33 +281,31 @@
},
{
"id": "clauut2nq001r3b6qi437ixc7",
- "groupId": "clauusa9z001n3b6qys3xvz1l",
+ "outgoingEdgeId": "clauuwjq2001x3b6qciu53855",
"type": "Webhook",
"options": {
+ "variablesForTest": [],
"responseVariableMapping": [
{
"id": "clauuvvdr001t3b6qqdxzc057",
- "bodyPath": "data.value",
- "variableId": "vclauuwchv001u3b6qepx6e0a9"
+ "variableId": "vclauuwchv001u3b6qepx6e0a9",
+ "bodyPath": "data.value"
}
],
- "variablesForTest": [],
"isAdvancedConfig": true,
- "isCustomBody": false
- },
- "webhookId": "chat-webhook-id",
- "outgoingEdgeId": "clauuwjq2001x3b6qciu53855"
+ "isCustomBody": false,
+ "webhook": { "method": "POST" }
+ }
}
]
},
{
"id": "clauuwhyl001v3b6qarbpiqbv",
- "graphCoordinates": { "x": 2900.9609375, "y": 288.29296875 },
"title": "Display joke",
+ "graphCoordinates": { "x": 2900.9609375, "y": 288.29296875 },
"blocks": [
{
"id": "clauuwhyl001w3b6q7ai0zeyt",
- "groupId": "clauuwhyl001v3b6qarbpiqbv",
"type": "text",
"content": {
"richText": [{ "type": "p", "children": [{ "text": "{{Joke}}" }] }]
@@ -356,137 +314,120 @@
]
}
],
- "variables": [
- { "id": "vclauuklnc000b3b6q7xchq4yf", "name": "Name" },
- { "id": "vclauulfjk000i3b6qmujooweu", "name": "Age" },
- { "id": "vclauuohyp000w3b6qbqrs6c6w", "name": "Magic number" },
- { "id": "vclauuwchv001u3b6qepx6e0a9", "name": "Joke" }
- ],
"edges": [
{
"id": "clauuk4o300083b6q7b2iowv3",
- "to": { "groupId": "clauujxdc00063b6q42ca20gj" },
- "from": {
- "blockId": "clauujawn0001vs1a0mk8docp",
- "groupId": "clauujawn0000vs1a8z6k2k7d"
- }
+ "from": { "eventId": "clauujawn0000vs1a8z6k2k7d" },
+ "to": { "groupId": "clauujxdc00063b6q42ca20gj" }
},
{
"id": "clauul0sk000f3b6q2tvy5wfi",
- "to": { "groupId": "clauukoka000c3b6qe6chawis" },
- "from": {
- "blockId": "clauukip8000a3b6qtzl288tu",
- "groupId": "clauujxdc00063b6q42ca20gj"
- }
+ "from": { "blockId": "clauukip8000a3b6qtzl288tu" },
+ "to": { "groupId": "clauukoka000c3b6qe6chawis" }
},
{
"id": "clauum41j000n3b6qpqu12icm",
- "to": { "groupId": "clauulhqf000j3b6qm8y5oifc" },
- "from": {
- "blockId": "clauul90j000h3b6qjfrw9js4",
- "groupId": "clauukoka000c3b6qe6chawis"
- }
+ "from": { "blockId": "clauul90j000h3b6qjfrw9js4" },
+ "to": { "groupId": "clauulhqf000j3b6qm8y5oifc" }
},
{
"id": "clauumi0x000q3b6q9bwkqnmr",
- "to": { "groupId": "clauum8x7000o3b6qx8hqduf8" },
"from": {
- "itemId": "clauulhqg000l3b6qaxn4qli5",
"blockId": "clauulhqf000k3b6qsrc1hd74",
- "groupId": "clauulhqf000j3b6qm8y5oifc"
- }
+ "itemId": "clauulhqg000l3b6qaxn4qli5"
+ },
+ "to": { "groupId": "clauum8x7000o3b6qx8hqduf8" }
},
{
"id": "clauumm5v000t3b6qu62qcft8",
- "to": { "groupId": "clauumjq4000r3b6q8l6bi9ra" },
- "from": {
- "blockId": "clauulhqf000k3b6qsrc1hd74",
- "groupId": "clauulhqf000j3b6qm8y5oifc"
- }
+ "from": { "blockId": "clauulhqf000k3b6qsrc1hd74" },
+ "to": { "groupId": "clauumjq4000r3b6q8l6bi9ra" }
},
{
"id": "clauuol8t000x3b6qcw1few70",
- "to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" },
- "from": {
- "blockId": "clauumjq5000s3b6qqjhrklv4",
- "groupId": "clauumjq4000r3b6q8l6bi9ra"
- }
+ "from": { "blockId": "clauumjq5000s3b6qqjhrklv4" },
+ "to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" }
},
{
"id": "clauuom2y000y3b6qkcjy2ri7",
- "to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" },
- "from": {
- "blockId": "clauum8x7000p3b6qxjud5hdc",
- "groupId": "clauum8x7000o3b6qx8hqduf8"
- }
+ "from": { "blockId": "clauum8x7000p3b6qxjud5hdc" },
+ "to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" }
},
{
- "from": {
- "groupId": "clauuoekh000u3b6q6zmlx7f9",
- "blockId": "clauuontu000z3b6q3ydx6ao1"
- },
- "to": { "groupId": "clauuq2l6001c3b6qpmq3ivwk" },
- "id": "clauuq8je001e3b6qksm4j11g"
+ "id": "clauuq8je001e3b6qksm4j11g",
+ "from": { "blockId": "clauuontu000z3b6q3ydx6ao1" },
+ "to": { "groupId": "clauuq2l6001c3b6qpmq3ivwk" }
},
{
- "from": {
- "groupId": "clauuq2l6001c3b6qpmq3ivwk",
- "blockId": "clauuq2l6001d3b6qyltfcvgb"
- },
- "to": { "groupId": "clauur7od001f3b6qq140oe55" },
- "id": "clauureo3001h3b6qk6epabxq"
+ "id": "clauureo3001h3b6qk6epabxq",
+ "from": { "blockId": "clauuq2l6001d3b6qyltfcvgb" },
+ "to": { "groupId": "clauur7od001f3b6qq140oe55" }
},
{
- "from": {
- "groupId": "clauur7od001f3b6qq140oe55",
- "blockId": "clauurs1o001k3b6qgrj0xf59"
- },
- "to": { "groupId": "clauusa9z001n3b6qys3xvz1l" },
- "id": "clauushy3001p3b6qqnyrxgtb"
+ "id": "clauushy3001p3b6qqnyrxgtb",
+ "from": { "blockId": "clauurs1o001k3b6qgrj0xf59" },
+ "to": { "groupId": "clauusa9z001n3b6qys3xvz1l" }
},
{
- "from": {
- "groupId": "clauusa9z001n3b6qys3xvz1l",
- "blockId": "clauut2nq001r3b6qi437ixc7"
- },
- "to": { "groupId": "clauuwhyl001v3b6qarbpiqbv" },
- "id": "clauuwjq2001x3b6qciu53855"
+ "id": "clauuwjq2001x3b6qciu53855",
+ "from": { "blockId": "clauut2nq001r3b6qi437ixc7" },
+ "to": { "groupId": "clauuwhyl001v3b6qarbpiqbv" }
}
],
- "theme": {
- "chat": {
- "inputs": {
- "color": "#303235",
- "backgroundColor": "#FFFFFF",
- "placeholderColor": "#9095A0"
- },
- "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
- "hostAvatar": {
- "url": "https://avatars.githubusercontent.com/u/16015833?v=4",
- "isEnabled": true
- },
- "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
- "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
+ "variables": [
+ {
+ "id": "vclauuklnc000b3b6q7xchq4yf",
+ "name": "Name",
+ "isSessionVariable": true
},
- "general": { "font": "Open Sans", "background": { "type": "None" } }
+ {
+ "id": "vclauulfjk000i3b6qmujooweu",
+ "name": "Age",
+ "isSessionVariable": true
+ },
+ { "id": "vclauuohyp000w3b6qbqrs6c6w", "name": "Magic number" },
+ { "id": "vclauuwchv001u3b6qepx6e0a9", "name": "Joke" }
+ ],
+ "theme": {
+ "general": { "font": "Open Sans", "background": { "type": "None" } },
+ "chat": {
+ "hostAvatar": {
+ "isEnabled": true,
+ "url": "https://avatars.githubusercontent.com/u/16015833?v=4"
+ },
+ "hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
+ "guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
+ "buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
+ "inputs": {
+ "backgroundColor": "#FFFFFF",
+ "color": "#303235",
+ "placeholderColor": "#9095A0"
+ }
+ }
},
+ "selectedThemeTemplateId": null,
"settings": {
"general": {
"isBrandingEnabled": false,
"isInputPrefillEnabled": true,
- "isResultSavingEnabled": true,
"isHideQueryParamsEnabled": true,
"isNewResultOnRefreshEnabled": false
},
+ "typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
- },
- "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ }
},
+ "createdAt": "2024-07-16T12:38:39.850Z",
+ "updatedAt": "2024-07-16T12:38:39.850Z",
+ "icon": "🤖",
+ "folderId": null,
"publicId": null,
"customDomain": null,
"workspaceId": "proWorkspace",
"resultsTablePreferences": null,
"isArchived": false,
- "isClosed": false
+ "isClosed": false,
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/viewer/src/test/assets/typebots/chat/startingWithInput.json b/apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
index 2a4522d52..31950014c 100644
--- a/apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
+++ b/apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
@@ -1,22 +1,16 @@
{
- "version": "5",
- "id": "clnbugp6a00011ackz0k3zfkp",
+ "version": "6",
+ "id": "clyoehs240009grw9vcxfw1ku",
"name": "My typebot",
- "groups": [
+ "events": [
{
"id": "k2nokn9v0zyhae0wqcxsbqa7",
- "title": "Start",
+ "outgoingEdgeId": "fj2ga89lctnuwcdsshwtxmhp",
"graphCoordinates": { "x": 0, "y": 0 },
- "blocks": [
- {
- "id": "sx4xmdbosubnxkhcg6x521p1",
- "groupId": "k2nokn9v0zyhae0wqcxsbqa7",
- "outgoingEdgeId": "fj2ga89lctnuwcdsshwtxmhp",
- "type": "start",
- "label": "Start"
- }
- ]
- },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "g8kdars2ahr3cyz2qf1f7w4i",
"title": "Group #1",
@@ -24,7 +18,6 @@
"blocks": [
{
"id": "prh6snup7cbmoxtf5vox8kjw",
- "groupId": "g8kdars2ahr3cyz2qf1f7w4i",
"type": "text input",
"options": {
"labels": {
@@ -36,7 +29,6 @@
},
{
"id": "dpyyb38amnwwl4q461el2uf6",
- "groupId": "g8kdars2ahr3cyz2qf1f7w4i",
"type": "text",
"content": {
"richText": [
@@ -50,10 +42,7 @@
"edges": [
{
"id": "fj2ga89lctnuwcdsshwtxmhp",
- "from": {
- "groupId": "k2nokn9v0zyhae0wqcxsbqa7",
- "blockId": "sx4xmdbosubnxkhcg6x521p1"
- },
+ "from": { "eventId": "k2nokn9v0zyhae0wqcxsbqa7" },
"to": { "groupId": "g8kdars2ahr3cyz2qf1f7w4i" }
}
],
@@ -88,8 +77,8 @@
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
}
},
- "createdAt": "2023-10-04T14:28:55.282Z",
- "updatedAt": "2023-10-04T14:29:11.949Z",
+ "createdAt": "2024-07-16T12:39:37.804Z",
+ "updatedAt": "2024-07-16T12:39:37.804Z",
"icon": null,
"folderId": null,
"publicId": null,
@@ -98,5 +87,6 @@
"resultsTablePreferences": null,
"isArchived": false,
"isClosed": false,
- "whatsAppCredentialsId": null
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/viewer/src/test/assets/typebots/webhook.json b/apps/viewer/src/test/assets/typebots/webhook.json
index 82da6220a..f63c455ec 100644
--- a/apps/viewer/src/test/assets/typebots/webhook.json
+++ b/apps/viewer/src/test/assets/typebots/webhook.json
@@ -1,85 +1,61 @@
{
- "id": "cl9ip9u0l00001ad79a2lzm55",
- "createdAt": "2022-10-21T16:22:07.414Z",
- "updatedAt": "2022-10-21T16:30:57.642Z",
- "icon": null,
+ "version": "6",
+ "id": "clyoep429000dgrw904vfzaez",
"name": "My typebot",
- "folderId": null,
- "version": "4",
- "groups": [
+ "events": [
{
"id": "cl9ip9u0j0000d71a5d98gwni",
- "title": "Start",
- "blocks": [
- {
- "id": "cl9ip9u0j0001d71a44dsd2p1",
- "type": "start",
- "label": "Start",
- "groupId": "cl9ip9u0j0000d71a5d98gwni",
- "outgoingEdgeId": "cl9ipkkb2001b3b6oh3vptq9k"
- }
- ],
- "graphCoordinates": { "x": 0, "y": 0 }
- },
+ "outgoingEdgeId": "cl9ipkkb2001b3b6oh3vptq9k",
+ "graphCoordinates": { "x": 0, "y": 0 },
+ "type": "start"
+ }
+ ],
+ "groups": [
{
"id": "cl9ipa38j00083b6o69e90m4t",
- "graphCoordinates": { "x": 340, "y": 341 },
"title": "Group #1",
+ "graphCoordinates": { "x": 340, "y": 341 },
"blocks": [
{
"id": "cl9ipaaut000a3b6ovrqlec3x",
- "groupId": "cl9ipa38j00083b6o69e90m4t",
"type": "text input",
"options": {
- "isLong": false,
- "labels": { "button": "Send", "placeholder": "Type a name..." },
- "variableId": "vcl9ipajth000c3b6okl97r81j"
+ "labels": { "placeholder": "Type a name...", "button": "Send" },
+ "variableId": "vcl9ipajth000c3b6okl97r81j",
+ "isLong": false
}
},
{
"id": "cl9ipan8f000d3b6oo2ovi3ac",
- "groupId": "cl9ipa38j00083b6o69e90m4t",
"type": "number input",
"options": {
- "labels": { "button": "Send", "placeholder": "Type an age..." },
- "variableId": "vcl9ipaszl000e3b6ousjxuw7b"
+ "variableId": "vcl9ipaszl000e3b6ousjxuw7b",
+ "labels": { "placeholder": "Type an age...", "button": "Send" }
}
},
{
"id": "cl9ipb08n000f3b6ok3mi2p48",
- "groupId": "cl9ipa38j00083b6o69e90m4t",
+ "outgoingEdgeId": "cl9ipcp83000o3b6odsn0a9a1",
"type": "choice input",
- "options": {
- "buttonLabel": "Send",
- "isMultipleChoice": false,
- "variableId": "vcl9ipg4tb00103b6oue08w3nm"
- },
"items": [
- {
- "id": "cl9ipb08n000g3b6okr691uad",
- "blockId": "cl9ipb08n000f3b6ok3mi2p48",
- "type": 0,
- "content": "Male"
- },
- {
- "blockId": "cl9ipb08n000f3b6ok3mi2p48",
- "type": 0,
- "id": "cl9ipb2kk000h3b6oadwtonnz",
- "content": "Female"
- }
+ { "id": "cl9ipb08n000g3b6okr691uad", "content": "Male" },
+ { "id": "cl9ipb2kk000h3b6oadwtonnz", "content": "Female" }
],
- "outgoingEdgeId": "cl9ipcp83000o3b6odsn0a9a1"
+ "options": {
+ "variableId": "vcl9ipg4tb00103b6oue08w3nm",
+ "isMultipleChoice": false,
+ "buttonLabel": "Send"
+ }
}
]
},
{
"id": "cl9ipbcjy000j3b6oqngo7luv",
- "graphCoordinates": { "x": 781, "y": 91 },
"title": "Group #2",
+ "graphCoordinates": { "x": 781, "y": 91 },
"blocks": [
{
"id": "cl9ipbl6l000m3b6o3evn41kv",
- "groupId": "cl9ipbcjy000j3b6oqngo7luv",
"type": "Set variable",
"options": {
"variableId": "vcl9ipbokm000n3b6o06hvarrf",
@@ -88,9 +64,9 @@
},
{
"id": "cl9ipbcjy000k3b6oe8lta5c1",
- "groupId": "cl9ipbcjy000j3b6oqngo7luv",
"type": "Webhook",
"options": {
+ "variablesForTest": [],
"responseVariableMapping": [
{
"id": "cl9ipdspg000p3b6ognbfvmdx",
@@ -98,15 +74,17 @@
"bodyPath": "data"
}
],
- "variablesForTest": [],
"isAdvancedConfig": true,
- "isCustomBody": true
- },
- "webhookId": "full-body-webhook"
+ "isCustomBody": true,
+ "webhook": {
+ "url": "http://localhost:3000/api/mock/webhook-easy-config",
+ "body": "{\n \"name\": \"{{Name}}\",\n \"age\": {{Age}},\n \"gender\": \"{{Gender}}\"\n }"
+ }
+ }
},
{
"id": "cl9ipe5t8000s3b6ocswre500",
- "groupId": "cl9ipbcjy000j3b6oqngo7luv",
+ "outgoingEdgeId": "cl9ipet83000z3b6of6zfqota",
"type": "text",
"content": {
"richText": [
@@ -117,21 +95,20 @@
{ "type": "p", "children": [{ "text": "" }] },
{ "type": "p", "children": [{ "text": "{{Data}}" }] }
]
- },
- "outgoingEdgeId": "cl9ipet83000z3b6of6zfqota"
+ }
}
]
},
{
"id": "cl9ipej6b000u3b6oeaz305l6",
- "graphCoordinates": { "x": 1138, "y": 85 },
"title": "Group #2 copy",
+ "graphCoordinates": { "x": 1138, "y": 85 },
"blocks": [
{
"id": "cl9ipej6c000w3b6otzk247vl",
- "groupId": "cl9ipej6b000u3b6oeaz305l6",
"type": "Webhook",
"options": {
+ "variablesForTest": [],
"responseVariableMapping": [
{
"id": "cl9ipdspg000p3b6ognbfvmdx",
@@ -139,15 +116,16 @@
"bodyPath": "data"
}
],
- "variablesForTest": [],
"isAdvancedConfig": true,
- "isCustomBody": true
- },
- "webhookId": "partial-body-webhook"
+ "isCustomBody": true,
+ "webhook": {
+ "url": "http://localhost:3000/api/mock/webhook-easy-config",
+ "body": "{{Full body}}"
+ }
+ }
},
{
"id": "cl9ipej6c000y3b6oegzkgloq",
- "groupId": "cl9ipej6b000u3b6oeaz305l6",
"type": "text",
"content": {
"richText": [
@@ -164,97 +142,94 @@
},
{
"id": "cl9ipkaer00153b6ov230yuv2",
- "graphCoordinates": { "x": 333, "y": 26 },
"title": "Group #4",
+ "graphCoordinates": { "x": 333, "y": 26 },
"blocks": [
{
"id": "cl9ipkaer00163b6o0ohmmscn",
- "groupId": "cl9ipkaer00153b6ov230yuv2",
"type": "choice input",
- "options": { "buttonLabel": "Send", "isMultipleChoice": false },
"items": [
{
"id": "cl9ipkaer00173b6oxof4zrqn",
- "blockId": "cl9ipkaer00163b6o0ohmmscn",
- "type": 0,
"content": "Send failing webhook"
}
- ]
+ ],
+ "options": { "isMultipleChoice": false, "buttonLabel": "Send" }
},
{
"id": "cl9ipki9u00193b6okmhudo0f",
- "groupId": "cl9ipkaer00153b6ov230yuv2",
+ "outgoingEdgeId": "cl9ipklm0001c3b6oy0a5nbhr",
"type": "Webhook",
"options": {
- "responseVariableMapping": [],
"variablesForTest": [],
+ "responseVariableMapping": [],
"isAdvancedConfig": false,
- "isCustomBody": false
- },
- "webhookId": "failing-webhook",
- "outgoingEdgeId": "cl9ipklm0001c3b6oy0a5nbhr"
+ "isCustomBody": false,
+ "webhook": { "url": "http://localhost:3001/api/mock/fail" }
+ }
}
]
}
],
- "variables": [
- { "id": "vcl9ipajth000c3b6okl97r81j", "name": "Name" },
- { "id": "vcl9ipaszl000e3b6ousjxuw7b", "name": "Age" },
- { "id": "vcl9ipbokm000n3b6o06hvarrf", "name": "Full body" },
- { "id": "vcl9ipdxnj000q3b6oy55th4xb", "name": "Data" },
- { "id": "vcl9ipg4tb00103b6oue08w3nm", "name": "Gender" }
- ],
"edges": [
{
- "from": {
- "groupId": "cl9ipa38j00083b6o69e90m4t",
- "blockId": "cl9ipb08n000f3b6ok3mi2p48"
- },
- "to": { "groupId": "cl9ipbcjy000j3b6oqngo7luv" },
- "id": "cl9ipcp83000o3b6odsn0a9a1"
+ "id": "cl9ipkkb2001b3b6oh3vptq9k",
+ "from": { "eventId": "cl9ip9u0j0000d71a5d98gwni" },
+ "to": { "groupId": "cl9ipkaer00153b6ov230yuv2" }
},
{
- "from": {
- "groupId": "cl9ipbcjy000j3b6oqngo7luv",
- "blockId": "cl9ipe5t8000s3b6ocswre500"
- },
- "to": { "groupId": "cl9ipej6b000u3b6oeaz305l6" },
- "id": "cl9ipet83000z3b6of6zfqota"
+ "id": "cl9ipcp83000o3b6odsn0a9a1",
+ "from": { "blockId": "cl9ipb08n000f3b6ok3mi2p48" },
+ "to": { "groupId": "cl9ipbcjy000j3b6oqngo7luv" }
},
{
- "from": {
- "groupId": "cl9ip9u0j0000d71a5d98gwni",
- "blockId": "cl9ip9u0j0001d71a44dsd2p1"
- },
- "to": { "groupId": "cl9ipkaer00153b6ov230yuv2" },
- "id": "cl9ipkkb2001b3b6oh3vptq9k"
+ "id": "cl9ipet83000z3b6of6zfqota",
+ "from": { "blockId": "cl9ipe5t8000s3b6ocswre500" },
+ "to": { "groupId": "cl9ipej6b000u3b6oeaz305l6" }
},
{
- "from": {
- "groupId": "cl9ipkaer00153b6ov230yuv2",
- "blockId": "cl9ipki9u00193b6okmhudo0f"
- },
- "to": { "groupId": "cl9ipa38j00083b6o69e90m4t" },
- "id": "cl9ipklm0001c3b6oy0a5nbhr"
+ "id": "cl9ipklm0001c3b6oy0a5nbhr",
+ "from": { "blockId": "cl9ipki9u00193b6okmhudo0f" },
+ "to": { "groupId": "cl9ipa38j00083b6o69e90m4t" }
+ }
+ ],
+ "variables": [
+ {
+ "id": "vcl9ipajth000c3b6okl97r81j",
+ "name": "Name",
+ "isSessionVariable": true
+ },
+ {
+ "id": "vcl9ipaszl000e3b6ousjxuw7b",
+ "name": "Age",
+ "isSessionVariable": true
+ },
+ { "id": "vcl9ipbokm000n3b6o06hvarrf", "name": "Full body" },
+ { "id": "vcl9ipdxnj000q3b6oy55th4xb", "name": "Data" },
+ {
+ "id": "vcl9ipg4tb00103b6oue08w3nm",
+ "name": "Gender",
+ "isSessionVariable": true
}
],
"theme": {
+ "general": { "font": "Open Sans", "background": { "type": "None" } },
"chat": {
- "inputs": {
- "color": "#303235",
- "backgroundColor": "#FFFFFF",
- "placeholderColor": "#9095A0"
- },
- "buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
"hostAvatar": {
- "url": "https://avatars.githubusercontent.com/u/16015833?v=4",
- "isEnabled": true
+ "isEnabled": true,
+ "url": "https://avatars.githubusercontent.com/u/16015833?v=4"
},
- "hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
- "guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
- },
- "general": { "font": "Open Sans", "background": { "type": "None" } }
+ "hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
+ "guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
+ "buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
+ "inputs": {
+ "backgroundColor": "#FFFFFF",
+ "color": "#303235",
+ "placeholderColor": "#9095A0"
+ }
+ }
},
+ "selectedThemeTemplateId": null,
"settings": {
"general": {
"isBrandingEnabled": false,
@@ -262,14 +237,21 @@
"isHideQueryParamsEnabled": true,
"isNewResultOnRefreshEnabled": false
},
+ "typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
"metadata": {
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
- },
- "typingEmulation": { "speed": 300, "enabled": true, "maxDelay": 1.5 }
+ }
},
+ "createdAt": "2024-07-16T12:45:19.954Z",
+ "updatedAt": "2024-07-16T12:46:27.462Z",
+ "icon": null,
+ "folderId": null,
"publicId": null,
"customDomain": null,
"workspaceId": "proWorkspace",
+ "resultsTablePreferences": null,
"isArchived": false,
- "isClosed": false
+ "isClosed": false,
+ "whatsAppCredentialsId": null,
+ "riskLevel": null
}
diff --git a/apps/viewer/src/test/chat.spec.ts b/apps/viewer/src/test/chat.spec.ts
index 23e09ffe8..0ef81e78f 100644
--- a/apps/viewer/src/test/chat.spec.ts
+++ b/apps/viewer/src/test/chat.spec.ts
@@ -2,18 +2,30 @@ import { getTestAsset } from '@/test/utils/playwright'
import test, { expect } from '@playwright/test'
import { createId } from '@paralleldrive/cuid2'
import prisma from '@typebot.io/lib/prisma'
-import {
- createWebhook,
- deleteTypebots,
- deleteWebhooks,
- importTypebotInDatabase,
-} from '@typebot.io/playwright/databaseActions'
-import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
+import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
import { StartChatInput, StartPreviewChatInput } from '@typebot.io/schemas'
-test.afterEach(async () => {
- await deleteWebhooks(['chat-webhook-id'])
- await deleteTypebots(['chat-sub-bot', 'starting-with-input'])
+test.describe.configure({ mode: 'parallel' })
+
+test.beforeEach(async () => {
+ try {
+ await importTypebotInDatabase(
+ getTestAsset('typebots/chat/linkedBot.json'),
+ {
+ id: 'chat-sub-bot',
+ publicId: 'chat-sub-bot-public',
+ }
+ )
+ await importTypebotInDatabase(
+ getTestAsset('typebots/chat/startingWithInput.json'),
+ {
+ id: 'starting-with-input',
+ publicId: 'starting-with-input-public',
+ }
+ )
+ } catch {
+ /* empty */
+ }
})
test('API chat execution should work on preview bot', async ({ request }) => {
@@ -23,22 +35,6 @@ test('API chat execution should work on preview bot', async ({ request }) => {
id: typebotId,
publicId,
})
- await importTypebotInDatabase(getTestAsset('typebots/chat/linkedBot.json'), {
- id: 'chat-sub-bot',
- publicId: 'chat-sub-bot-public',
- })
- await importTypebotInDatabase(
- getTestAsset('typebots/chat/startingWithInput.json'),
- {
- id: 'starting-with-input',
- publicId: 'starting-with-input-public',
- }
- )
- await createWebhook(typebotId, {
- id: 'chat-webhook-id',
- method: HttpMethod.GET,
- url: 'https://api.chucknorris.io/jokes/random',
- })
let chatSessionId: string
@@ -104,22 +100,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
id: typebotId,
publicId,
})
- await importTypebotInDatabase(getTestAsset('typebots/chat/linkedBot.json'), {
- id: 'chat-sub-bot',
- publicId: 'chat-sub-bot-public',
- })
- await importTypebotInDatabase(
- getTestAsset('typebots/chat/startingWithInput.json'),
- {
- id: 'starting-with-input',
- publicId: 'starting-with-input-public',
- }
- )
- await createWebhook(typebotId, {
- id: 'chat-webhook-id',
- method: HttpMethod.GET,
- url: 'https://api.chucknorris.io/jokes/random',
- })
+
let chatSessionId: string
await test.step('Start the chat', async () => {
diff --git a/apps/viewer/src/test/webhook.spec.ts b/apps/viewer/src/test/webhook.spec.ts
index ee9d42551..320526263 100644
--- a/apps/viewer/src/test/webhook.spec.ts
+++ b/apps/viewer/src/test/webhook.spec.ts
@@ -1,50 +1,14 @@
import test, { expect } from '@playwright/test'
import { createId } from '@paralleldrive/cuid2'
-import {
- createWebhook,
- importTypebotInDatabase,
-} from '@typebot.io/playwright/databaseActions'
+import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
import { getTestAsset } from '@/test/utils/playwright'
-import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
-const typebotId = createId()
-
-test.beforeEach(async () => {
+test('should execute webhooks properly', async ({ page }) => {
+ const typebotId = createId()
await importTypebotInDatabase(getTestAsset('typebots/webhook.json'), {
id: typebotId,
publicId: `${typebotId}-public`,
})
-
- try {
- await createWebhook(typebotId, {
- id: 'failing-webhook',
- url: 'http://localhost:3001/api/mock/fail',
- method: HttpMethod.POST,
- })
-
- await createWebhook(typebotId, {
- id: 'partial-body-webhook',
- url: 'http://localhost:3000/api/mock/webhook-easy-config',
- method: HttpMethod.POST,
- body: `{
- "name": "{{Name}}",
- "age": {{Age}},
- "gender": "{{Gender}}"
- }`,
- })
-
- await createWebhook(typebotId, {
- id: 'full-body-webhook',
- url: 'http://localhost:3000/api/mock/webhook-easy-config',
- method: HttpMethod.POST,
- body: `{{Full body}}`,
- })
- } catch (err) {
- console.log(err)
- }
-})
-
-test('should execute webhooks properly', async ({ page }) => {
await page.goto(`/${typebotId}-public`)
await page.locator('text=Send failing webhook').click()
await page.locator('[placeholder="Type a name..."]').fill('John')
diff --git a/packages/bot-engine/blocks/integrations/webhook/executeWebhookBlock.ts b/packages/bot-engine/blocks/integrations/webhook/executeWebhookBlock.ts
index 37d6729ea..9116d26d9 100644
--- a/packages/bot-engine/blocks/integrations/webhook/executeWebhookBlock.ts
+++ b/packages/bot-engine/blocks/integrations/webhook/executeWebhookBlock.ts
@@ -335,8 +335,8 @@ export const convertKeyValueTableToObject = (
const value = parseVariables(variables)(item.value)
if (isEmpty(key) || isEmpty(value)) return object
if (object[key] && concatDuplicateInArray) {
- if (Array.isArray(object[key])) object[key].push(value)
- else object[key] = [object[key], value]
+ if (Array.isArray(object[key])) (object[key] as string[]).push(value)
+ else object[key] = [object[key] as string, value]
} else object[key] = value
return object
}, {})
diff --git a/packages/forge/blocks/anthropic/schemas.ts b/packages/forge/blocks/anthropic/schemas.ts
index a074e92f6..049c508f1 100644
--- a/packages/forge/blocks/anthropic/schemas.ts
+++ b/packages/forge/blocks/anthropic/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { anthropicBlock } from '.'
+import { auth } from './auth'
export const anthropicBlockSchema = parseBlockSchema(anthropicBlock)
-export const anthropicCredentialsSchema = parseBlockCredentials(anthropicBlock)
+export const anthropicCredentialsSchema = parseBlockCredentials(
+ anthropicBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/calCom/schemas.ts b/packages/forge/blocks/calCom/schemas.ts
index ec7288b29..7ebb4e1a5 100644
--- a/packages/forge/blocks/calCom/schemas.ts
+++ b/packages/forge/blocks/calCom/schemas.ts
@@ -1,6 +1,5 @@
// Do not edit this file manually
-import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
+import { parseBlockSchema } from '@typebot.io/forge'
import { calComBlock } from '.'
export const calComBlockSchema = parseBlockSchema(calComBlock)
-export const calComCredentialsSchema = parseBlockCredentials(calComBlock)
diff --git a/packages/forge/blocks/chatNode/schemas.ts b/packages/forge/blocks/chatNode/schemas.ts
index 21a1fdb19..010ec191b 100644
--- a/packages/forge/blocks/chatNode/schemas.ts
+++ b/packages/forge/blocks/chatNode/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { chatNodeBlock } from '.'
+import { auth } from './auth'
export const chatNodeBlockSchema = parseBlockSchema(chatNodeBlock)
-export const chatNodeCredentialsSchema = parseBlockCredentials(chatNodeBlock)
+export const chatNodeCredentialsSchema = parseBlockCredentials(
+ chatNodeBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/difyAi/schemas.ts b/packages/forge/blocks/difyAi/schemas.ts
index 912f1b09b..ae95f7af5 100644
--- a/packages/forge/blocks/difyAi/schemas.ts
+++ b/packages/forge/blocks/difyAi/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { difyAiBlock } from '.'
+import { auth } from './auth'
export const difyAiBlockSchema = parseBlockSchema(difyAiBlock)
-export const difyAiCredentialsSchema = parseBlockCredentials(difyAiBlock)
+export const difyAiCredentialsSchema = parseBlockCredentials(
+ difyAiBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/elevenlabs/schemas.ts b/packages/forge/blocks/elevenlabs/schemas.ts
index 886b14af2..41dd19224 100644
--- a/packages/forge/blocks/elevenlabs/schemas.ts
+++ b/packages/forge/blocks/elevenlabs/schemas.ts
@@ -1,7 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { elevenlabsBlock } from '.'
+import { auth } from './auth'
export const elevenlabsBlockSchema = parseBlockSchema(elevenlabsBlock)
-export const elevenlabsCredentialsSchema =
- parseBlockCredentials(elevenlabsBlock)
+export const elevenlabsCredentialsSchema = parseBlockCredentials(
+ elevenlabsBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/mistral/schemas.ts b/packages/forge/blocks/mistral/schemas.ts
index 5cb7159f5..e38a49ed7 100644
--- a/packages/forge/blocks/mistral/schemas.ts
+++ b/packages/forge/blocks/mistral/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { mistralBlock } from '.'
+import { auth } from './auth'
export const mistralBlockSchema = parseBlockSchema(mistralBlock)
-export const mistralCredentialsSchema = parseBlockCredentials(mistralBlock)
+export const mistralCredentialsSchema = parseBlockCredentials(
+ mistralBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/nocodb/schemas.ts b/packages/forge/blocks/nocodb/schemas.ts
index b6510c579..bb4b49ea0 100644
--- a/packages/forge/blocks/nocodb/schemas.ts
+++ b/packages/forge/blocks/nocodb/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { nocodbBlock } from '.'
+import { auth } from './auth'
export const nocodbBlockSchema = parseBlockSchema(nocodbBlock)
-export const nocodbCredentialsSchema = parseBlockCredentials(nocodbBlock)
+export const nocodbCredentialsSchema = parseBlockCredentials(
+ nocodbBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/openRouter/schemas.ts b/packages/forge/blocks/openRouter/schemas.ts
index 4b04875e4..b44b8c0e3 100644
--- a/packages/forge/blocks/openRouter/schemas.ts
+++ b/packages/forge/blocks/openRouter/schemas.ts
@@ -1,7 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { openRouterBlock } from '.'
+import { auth } from './auth'
export const openRouterBlockSchema = parseBlockSchema(openRouterBlock)
-export const openRouterCredentialsSchema =
- parseBlockCredentials(openRouterBlock)
+export const openRouterCredentialsSchema = parseBlockCredentials(
+ openRouterBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/openai/schemas.ts b/packages/forge/blocks/openai/schemas.ts
index 625dcfb94..e430e6edd 100644
--- a/packages/forge/blocks/openai/schemas.ts
+++ b/packages/forge/blocks/openai/schemas.ts
@@ -1,6 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { openAIBlock } from '.'
+import { auth } from './auth'
export const openAIBlockSchema = parseBlockSchema(openAIBlock)
-export const openAICredentialsSchema = parseBlockCredentials(openAIBlock)
+export const openAICredentialsSchema = parseBlockCredentials(
+ openAIBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/qrcode/schemas.ts b/packages/forge/blocks/qrcode/schemas.ts
index 0a9149f26..b1a797549 100644
--- a/packages/forge/blocks/qrcode/schemas.ts
+++ b/packages/forge/blocks/qrcode/schemas.ts
@@ -1,6 +1,5 @@
// Do not edit this file manually
-import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
+import { parseBlockSchema } from '@typebot.io/forge'
import { qrCodeBlock } from '.'
export const qrCodeBlockSchema = parseBlockSchema(qrCodeBlock)
-export const qrCodeCredentialsSchema = parseBlockCredentials(qrCodeBlock)
diff --git a/packages/forge/blocks/togetherAi/schemas.ts b/packages/forge/blocks/togetherAi/schemas.ts
index 3d569cdf1..55ca72a9f 100644
--- a/packages/forge/blocks/togetherAi/schemas.ts
+++ b/packages/forge/blocks/togetherAi/schemas.ts
@@ -1,7 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { togetherAiBlock } from '.'
+import { auth } from './auth'
export const togetherAiBlockSchema = parseBlockSchema(togetherAiBlock)
-export const togetherAiCredentialsSchema =
- parseBlockCredentials(togetherAiBlock)
+export const togetherAiCredentialsSchema = parseBlockCredentials(
+ togetherAiBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/blocks/zemanticAi/schemas.ts b/packages/forge/blocks/zemanticAi/schemas.ts
index 20c16cb40..774b80f07 100644
--- a/packages/forge/blocks/zemanticAi/schemas.ts
+++ b/packages/forge/blocks/zemanticAi/schemas.ts
@@ -1,7 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { zemanticAiBlock } from '.'
+import { auth } from './auth'
export const zemanticAiBlockSchema = parseBlockSchema(zemanticAiBlock)
-export const zemanticAiCredentialsSchema =
- parseBlockCredentials(zemanticAiBlock)
+export const zemanticAiCredentialsSchema = parseBlockCredentials(
+ zemanticAiBlock.id,
+ auth.schema
+)
diff --git a/packages/forge/cli/index.ts b/packages/forge/cli/index.ts
index a30a9e889..99a0ccff3 100644
--- a/packages/forge/cli/index.ts
+++ b/packages/forge/cli/index.ts
@@ -273,6 +273,7 @@ const createSchemasFile = async (
path: string,
{
id,
+ auth,
}: { id: string; name: string; auth: 'apiKey' | 'encryptedData' | 'none' }
) => {
const camelCaseName = camelize(id as string)
@@ -280,11 +281,19 @@ const createSchemasFile = async (
join(path, 'schemas.ts'),
await prettier.format(
`// Do not edit this file manually
-import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
-import { ${camelCaseName}Block } from '.'
+import { ${
+ auth !== 'none' ? 'parseBlockCredentials,' : ''
+ } parseBlockSchema } from '@typebot.io/forge'
+import { ${camelCaseName}Block } from '.'${
+ auth !== 'none' ? `\nimport { auth } from './auth'` : ''
+ }
export const ${camelCaseName}BlockSchema = parseBlockSchema(${camelCaseName}Block)
-export const ${camelCaseName}CredentialsSchema = parseBlockCredentials(${camelCaseName}Block)`,
+${
+ auth !== 'none'
+ ? `export const ${camelCaseName}CredentialsSchema = parseBlockCredentials(${camelCaseName}Block.id, auth.schema)`
+ : ''
+}`,
{ parser: 'typescript', ...prettierRc }
)
)
diff --git a/packages/forge/core/index.ts b/packages/forge/core/index.ts
index cf2ed7e8c..f76a01110 100644
--- a/packages/forge/core/index.ts
+++ b/packages/forge/core/index.ts
@@ -82,22 +82,18 @@ export const parseBlockSchema = <
})
}
-export const parseBlockCredentials = <
- I extends string,
- A extends AuthDefinition,
- O extends z.ZodObject
->(
- blockDefinition: BlockDefinition
+export const parseBlockCredentials = (
+ blockId: I,
+ authSchema: z.ZodObject
) => {
- if (!blockDefinition.auth) return null
return z.object({
id: z.string(),
- type: z.literal(blockDefinition.id),
+ type: z.literal(blockId),
createdAt: z.date(),
workspaceId: z.string(),
name: z.string(),
iv: z.string(),
- data: blockDefinition.auth.schema,
+ data: authSchema,
})
}
diff --git a/packages/forge/repository/credentials.ts b/packages/forge/repository/credentials.ts
index a32d9136d..78c666296 100644
--- a/packages/forge/repository/credentials.ts
+++ b/packages/forge/repository/credentials.ts
@@ -1,7 +1,5 @@
import { anthropicBlock } from '@typebot.io/anthropic-block'
import { anthropicCredentialsSchema } from '@typebot.io/anthropic-block/schemas'
-import { calComBlock } from '@typebot.io/cal-com-block'
-import { calComCredentialsSchema } from '@typebot.io/cal-com-block/schemas'
import { chatNodeBlock } from '@typebot.io/chat-node-block'
import { chatNodeCredentialsSchema } from '@typebot.io/chat-node-block/schemas'
import { difyAiBlock } from '@typebot.io/dify-ai-block'
@@ -14,8 +12,6 @@ import { openRouterBlock } from '@typebot.io/open-router-block'
import { openRouterCredentialsSchema } from '@typebot.io/open-router-block/schemas'
import { openAIBlock } from '@typebot.io/openai-block'
import { openAICredentialsSchema } from '@typebot.io/openai-block/schemas'
-import { qrCodeBlock } from '@typebot.io/qrcode-block'
-import { qrCodeCredentialsSchema } from '@typebot.io/qrcode-block/schemas'
import { togetherAiBlock } from '@typebot.io/together-ai-block'
import { togetherAiCredentialsSchema } from '@typebot.io/together-ai-block/schemas'
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
@@ -26,9 +22,7 @@ import { nocodbCredentialsSchema } from '@typebot.io/nocodb-block/schemas'
export const forgedCredentialsSchemas = {
[openAIBlock.id]: openAICredentialsSchema,
[zemanticAiBlock.id]: zemanticAiCredentialsSchema,
- [calComBlock.id]: calComCredentialsSchema,
[chatNodeBlock.id]: chatNodeCredentialsSchema,
- [qrCodeBlock.id]: qrCodeCredentialsSchema,
[difyAiBlock.id]: difyAiCredentialsSchema,
[mistralBlock.id]: mistralCredentialsSchema,
[elevenlabsBlock.id]: elevenlabsCredentialsSchema,
diff --git a/packages/lib/package.json b/packages/lib/package.json
index 569466e13..c0ebb87ff 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -7,7 +7,7 @@
"types": "./index.ts",
"devDependencies": {
"@paralleldrive/cuid2": "2.2.1",
- "@playwright/test": "1.43.1",
+ "@playwright/test": "1.45.2",
"@typebot.io/env": "workspace:*",
"@typebot.io/prisma": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
@@ -49,4 +49,4 @@
"wildcard-match": "5.1.3",
"zod": "3.22.4"
}
-}
\ No newline at end of file
+}
diff --git a/packages/playwright/databaseActions.ts b/packages/playwright/databaseActions.ts
index 3528c85ef..0df96a067 100644
--- a/packages/playwright/databaseActions.ts
+++ b/packages/playwright/databaseActions.ts
@@ -151,25 +151,6 @@ export const updateUser = (data: Partial) =>
},
})
-export const createWebhook = async (
- typebotId: string,
- webhookProps?: Partial
-) => {
- try {
- await prisma.webhook.delete({ where: { id: 'webhook1' } })
- } catch {}
- return prisma.webhook.create({
- data: {
- method: 'GET',
- typebotId,
- id: 'webhook1',
- ...webhookProps,
- queryParams: webhookProps?.queryParams ?? [],
- headers: webhookProps?.headers ?? [],
- },
- })
-}
-
export const createTypebots = async (partialTypebots: Partial[]) => {
const typebotsWithId = partialTypebots.map((typebot) => {
const typebotId = typebot.id ?? createId()
diff --git a/packages/playwright/package.json b/packages/playwright/package.json
index 7e00ec640..b9a429e78 100644
--- a/packages/playwright/package.json
+++ b/packages/playwright/package.json
@@ -7,7 +7,7 @@
"author": "Baptiste Arnaud",
"license": "AGPL-3.0-or-later",
"dependencies": {
- "@playwright/test": "1.43.1",
+ "@playwright/test": "1.45.2",
"@typebot.io/lib": "workspace:*",
"@typebot.io/prisma": "workspace:*",
"@typebot.io/schemas": "workspace:*",
diff --git a/packages/schemas/features/credentials.ts b/packages/schemas/features/credentials.ts
index a50e057b7..08a9cdae7 100644
--- a/packages/schemas/features/credentials.ts
+++ b/packages/schemas/features/credentials.ts
@@ -3,16 +3,26 @@ import { stripeCredentialsSchema } from './blocks/inputs/payment/schema'
import { googleSheetsCredentialsSchema } from './blocks/integrations/googleSheets/schema'
import { smtpCredentialsSchema } from './blocks/integrations/sendEmail'
import { whatsAppCredentialsSchema } from './whatsapp'
-import { zemanticAiCredentialsSchema } from './blocks'
-import { openAICredentialsSchema } from './blocks/integrations/openai'
+import { forgedCredentialsSchemas } from '@typebot.io/forge-repository/credentials'
-export const credentialsSchema = z.discriminatedUnion('type', [
+const credentialsSchema = z.discriminatedUnion('type', [
smtpCredentialsSchema,
googleSheetsCredentialsSchema,
stripeCredentialsSchema,
- openAICredentialsSchema,
whatsAppCredentialsSchema,
- zemanticAiCredentialsSchema,
+ ...Object.values(forgedCredentialsSchemas),
])
export type Credentials = z.infer
+
+export const credentialsTypes = [
+ 'smtp',
+ 'google sheets',
+ 'stripe',
+ 'whatsApp',
+ ...(Object.keys(forgedCredentialsSchemas) as Array<
+ keyof typeof forgedCredentialsSchemas
+ >),
+] as const
+
+export const credentialsTypeSchema = z.enum(credentialsTypes)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8d6925b70..94eb8d0e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -288,8 +288,8 @@ importers:
specifier: 2.9.2
version: 2.9.2
'@playwright/test':
- specifier: 1.43.1
- version: 1.43.1
+ specifier: 1.45.2
+ version: 1.45.2
'@typebot.io/billing':
specifier: workspace:*
version: link:../../ee/packages/billing
@@ -478,8 +478,8 @@ importers:
specifier: 2.2.1
version: 2.2.1
'@playwright/test':
- specifier: 1.43.1
- version: 1.43.1
+ specifier: 1.45.2
+ version: 1.45.2
'@typebot.io/emails':
specifier: workspace:*
version: link:../../packages/emails
@@ -1785,8 +1785,8 @@ importers:
specifier: 2.2.1
version: 2.2.1
'@playwright/test':
- specifier: 1.43.1
- version: 1.43.1
+ specifier: 1.45.2
+ version: 1.45.2
'@typebot.io/env':
specifier: workspace:*
version: link:../env
@@ -1856,8 +1856,8 @@ importers:
packages/playwright:
dependencies:
'@playwright/test':
- specifier: 1.43.1
- version: 1.43.1
+ specifier: 1.45.2
+ version: 1.45.2
'@typebot.io/env':
specifier: workspace:*
version: link:../env
@@ -4687,9 +4687,9 @@ packages:
resolution: {integrity: sha512-+zk04eXRiaJGaRnJZkCxXbBtBvQDQJXCoxqlXhLY3HzAovXfsBnh6DjXRujPRQQ7GKtT8/tOlyvZ9h6ReM+GLQ==}
engines: {node: '>=16'}
- '@playwright/test@1.43.1':
- resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==}
- engines: {node: '>=16'}
+ '@playwright/test@1.45.2':
+ resolution: {integrity: sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==}
+ engines: {node: '>=18'}
hasBin: true
'@popperjs/core@2.11.8':
@@ -10645,14 +10645,14 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
- playwright-core@1.43.1:
- resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==}
- engines: {node: '>=16'}
+ playwright-core@1.45.2:
+ resolution: {integrity: sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==}
+ engines: {node: '>=18'}
hasBin: true
- playwright@1.43.1:
- resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==}
- engines: {node: '>=16'}
+ playwright@1.45.2:
+ resolution: {integrity: sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==}
+ engines: {node: '>=18'}
hasBin: true
pngjs@5.0.0:
@@ -16357,9 +16357,9 @@ snapshots:
'@planetscale/database@1.8.0': {}
- '@playwright/test@1.43.1':
+ '@playwright/test@1.45.2':
dependencies:
- playwright: 1.43.1
+ playwright: 1.45.2
'@popperjs/core@2.11.8': {}
@@ -24207,11 +24207,11 @@ snapshots:
dependencies:
find-up: 4.1.0
- playwright-core@1.43.1: {}
+ playwright-core@1.45.2: {}
- playwright@1.43.1:
+ playwright@1.45.2:
dependencies:
- playwright-core: 1.43.1
+ playwright-core: 1.45.2
optionalDependencies:
fsevents: 2.3.2