✨ (credentials) Add credentials management menu in workspace settings
Closes #1567
This commit is contained in:
@ -1 +1 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
emojiList.json
|
emojiList.json
|
||||||
iconNames.ts
|
iconNames.ts
|
||||||
reporters
|
reporters
|
||||||
|
.last-run.json
|
||||||
|
1
apps/builder/.eslintignore
Normal file
1
apps/builder/.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/test/reporters
|
@ -100,7 +100,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chakra-ui/styled-system": "2.9.2",
|
"@chakra-ui/styled-system": "2.9.2",
|
||||||
"@playwright/test": "1.43.1",
|
"@playwright/test": "1.45.2",
|
||||||
"@typebot.io/billing": "workspace:*",
|
"@typebot.io/billing": "workspace:*",
|
||||||
"@typebot.io/forge": "workspace:*",
|
"@typebot.io/forge": "workspace:*",
|
||||||
"@typebot.io/forge-repository": "workspace:*",
|
"@typebot.io/forge-repository": "workspace:*",
|
||||||
|
@ -10,13 +10,13 @@ export default defineConfig({
|
|||||||
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
|
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
|
||||||
},
|
},
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
workers: process.env.CI ? 1 : 3,
|
workers: process.env.CI ? 1 : 4,
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 1,
|
||||||
reporter: [
|
reporter: [
|
||||||
[process.env.CI ? 'github' : 'list'],
|
[process.env.CI ? 'github' : 'list'],
|
||||||
['html', { outputFolder: 'src/test/reporters' }],
|
['html', { outputFolder: 'src/test/reporters' }],
|
||||||
],
|
],
|
||||||
maxFailures: process.env.CI ? 10 : undefined,
|
maxFailures: 10,
|
||||||
webServer: process.env.CI
|
webServer: process.env.CI
|
||||||
? {
|
? {
|
||||||
command: 'pnpm run start',
|
command: 'pnpm run start',
|
||||||
|
@ -695,3 +695,11 @@ export const VideoPopoverIcon = (props: IconProps) => (
|
|||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const WalletIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||||
|
<path d="M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2" />
|
||||||
|
<path d="M3 11h3c.8 0 1.6.3 2.1.9l1.1.9c1.6 1.6 4.1 1.6 5.7 0l1.1-.9c.5-.5 1.3-.9 2.1-.9H21" />
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
@ -49,6 +49,7 @@ export const NumberInput = <HasVariable extends boolean>({
|
|||||||
helperText,
|
helperText,
|
||||||
...props
|
...props
|
||||||
}: Props<HasVariable>) => {
|
}: Props<HasVariable>) => {
|
||||||
|
const [isTouched, setIsTouched] = useState(false)
|
||||||
const [value, setValue] = useState(defaultValue?.toString() ?? '')
|
const [value, setValue] = useState(defaultValue?.toString() ?? '')
|
||||||
|
|
||||||
const onValueChangeDebounced = useDebouncedCallback(
|
const onValueChangeDebounced = useDebouncedCallback(
|
||||||
@ -56,6 +57,11 @@ export const NumberInput = <HasVariable extends boolean>({
|
|||||||
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isTouched || value !== '' || !defaultValue) return
|
||||||
|
setValue(defaultValue?.toString() ?? '')
|
||||||
|
}, [defaultValue, isTouched, value])
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
onValueChangeDebounced.flush()
|
onValueChangeDebounced.flush()
|
||||||
@ -64,6 +70,7 @@ export const NumberInput = <HasVariable extends boolean>({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleValueChange = (newValue: string) => {
|
const handleValueChange = (newValue: string) => {
|
||||||
|
if (!isTouched) setIsTouched(true)
|
||||||
if (value.startsWith('{{') && value.endsWith('}}') && newValue !== '')
|
if (value.startsWith('{{') && value.endsWith('}}') && newValue !== '')
|
||||||
return
|
return
|
||||||
setValue(newValue)
|
setValue(newValue)
|
||||||
|
@ -12,7 +12,7 @@ import { isDefined } from '@typebot.io/lib'
|
|||||||
|
|
||||||
export type SwitchWithLabelProps = {
|
export type SwitchWithLabelProps = {
|
||||||
label: string
|
label: string
|
||||||
initialValue: boolean
|
initialValue: boolean | undefined
|
||||||
moreInfoContent?: string
|
moreInfoContent?: string
|
||||||
onCheckChange?: (isChecked: boolean) => void
|
onCheckChange?: (isChecked: boolean) => void
|
||||||
justifyContent?: FormControlProps['justifyContent']
|
justifyContent?: FormControlProps['justifyContent']
|
||||||
|
16
apps/builder/src/components/logos/StripeLogo.tsx
Normal file
16
apps/builder/src/components/logos/StripeLogo.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Icon, IconProps } from '@chakra-ui/react'
|
||||||
|
|
||||||
|
export const StripeLogo = (props: IconProps) => {
|
||||||
|
return (
|
||||||
|
<Icon viewBox="0 0 400 400" {...props}>
|
||||||
|
<path
|
||||||
|
style={{ fillRule: 'evenodd', clipRule: 'evenodd', fill: '#635bff' }}
|
||||||
|
d="M0 0h400v400H0z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M184.4 155.5c0-9.4 7.7-13.1 20.5-13.1 18.4 0 41.6 5.6 60 15.5v-56.8C244.8 93.1 225 90 205 90c-49.1 0-81.7 25.6-81.7 68.4 0 66.7 91.9 56.1 91.9 84.9 0 11.1-9.7 14.7-23.2 14.7-20.1 0-45.7-8.2-66-19.3v57.5c22.5 9.7 45.2 13.8 66 13.8 50.3 0 84.9-24.9 84.9-68.2-.4-72-92.5-59.2-92.5-86.3z"
|
||||||
|
style={{ fillRule: 'evenodd', clipRule: 'evenodd', fill: '#fff' }}
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
}
|
@ -36,6 +36,21 @@ export const StripeConfigModal = ({
|
|||||||
onNewCredentials,
|
onNewCredentials,
|
||||||
onClose,
|
onClose,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<StripeCreateModalContent
|
||||||
|
onNewCredentials={onNewCredentials}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StripeCreateModalContent = ({
|
||||||
|
onNewCredentials,
|
||||||
|
onClose,
|
||||||
|
}: Pick<Props, 'onClose' | 'onNewCredentials'>) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
@ -99,7 +114,8 @@ export const StripeConfigModal = ({
|
|||||||
test: { ...stripeConfig.test, secretKey },
|
test: { ...stripeConfig.test, secretKey },
|
||||||
})
|
})
|
||||||
|
|
||||||
const createCredentials = async () => {
|
const createCredentials = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
if (!user?.email || !workspace?.id) return
|
if (!user?.email || !workspace?.id) return
|
||||||
mutate({
|
mutate({
|
||||||
credentials: {
|
credentials: {
|
||||||
@ -120,16 +136,16 @@ export const StripeConfigModal = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClose}>
|
<ModalContent>
|
||||||
<ModalOverlay />
|
<ModalHeader>
|
||||||
<ModalContent>
|
{t('blocks.inputs.payment.settings.stripeConfig.title.label')}
|
||||||
<ModalHeader>
|
</ModalHeader>
|
||||||
{t('blocks.inputs.payment.settings.stripeConfig.title.label')}
|
<ModalCloseButton />
|
||||||
</ModalHeader>
|
<form onSubmit={createCredentials}>
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Stack as="form" spacing={4}>
|
<Stack spacing={4}>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
label={t(
|
label={t(
|
||||||
@ -208,8 +224,8 @@ export const StripeConfigModal = ({
|
|||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
|
type="submit"
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
onClick={createCredentials}
|
|
||||||
isDisabled={
|
isDisabled={
|
||||||
stripeConfig.live.publicKey === '' ||
|
stripeConfig.live.publicKey === '' ||
|
||||||
stripeConfig.name === '' ||
|
stripeConfig.name === '' ||
|
||||||
@ -220,7 +236,7 @@ export const StripeConfigModal = ({
|
|||||||
{t('connect')}
|
{t('connect')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</form>
|
||||||
</Modal>
|
</ModalContent>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 (
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
{t('blocks.inputs.payment.settings.stripeConfig.title.label')}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<form onSubmit={updateCreds}>
|
||||||
|
<ModalBody>
|
||||||
|
<Stack as="form" spacing={4}>
|
||||||
|
<TextInput
|
||||||
|
isRequired
|
||||||
|
label={t(
|
||||||
|
'blocks.inputs.payment.settings.stripeConfig.accountName.label'
|
||||||
|
)}
|
||||||
|
defaultValue={stripeConfig?.name}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
placeholder="Typebot"
|
||||||
|
withVariableButton={false}
|
||||||
|
debounceTimeout={0}
|
||||||
|
/>
|
||||||
|
<Stack>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
'blocks.inputs.payment.settings.stripeConfig.testKeys.label'
|
||||||
|
)}{' '}
|
||||||
|
<MoreInfoTooltip>
|
||||||
|
{t(
|
||||||
|
'blocks.inputs.payment.settings.stripeConfig.testKeys.infoText.label'
|
||||||
|
)}
|
||||||
|
</MoreInfoTooltip>
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<TextInput
|
||||||
|
onChange={handleTestPublicKeyChange}
|
||||||
|
placeholder="pk_test_..."
|
||||||
|
withVariableButton={false}
|
||||||
|
defaultValue={stripeConfig?.test?.publicKey}
|
||||||
|
debounceTimeout={0}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
onChange={handleTestSecretKeyChange}
|
||||||
|
placeholder="sk_test_..."
|
||||||
|
withVariableButton={false}
|
||||||
|
debounceTimeout={0}
|
||||||
|
defaultValue={stripeConfig?.test?.secretKey}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
'blocks.inputs.payment.settings.stripeConfig.liveKeys.label'
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<HStack>
|
||||||
|
<FormControl>
|
||||||
|
<TextInput
|
||||||
|
onChange={handlePublicKeyChange}
|
||||||
|
placeholder="pk_live_..."
|
||||||
|
withVariableButton={false}
|
||||||
|
defaultValue={stripeConfig?.live?.publicKey}
|
||||||
|
debounceTimeout={0}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<TextInput
|
||||||
|
onChange={handleSecretKeyChange}
|
||||||
|
placeholder="sk_live_..."
|
||||||
|
withVariableButton={false}
|
||||||
|
defaultValue={stripeConfig?.live?.secretKey}
|
||||||
|
debounceTimeout={0}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
({t('blocks.inputs.payment.settings.stripeConfig.findKeys.label')}{' '}
|
||||||
|
<TextLink href="https://dashboard.stripe.com/apikeys" isExternal>
|
||||||
|
{t(
|
||||||
|
'blocks.inputs.payment.settings.stripeConfig.findKeys.here.label'
|
||||||
|
)}
|
||||||
|
</TextLink>
|
||||||
|
)
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
colorScheme="blue"
|
||||||
|
isDisabled={
|
||||||
|
stripeConfig?.live.publicKey === '' ||
|
||||||
|
stripeConfig?.name === '' ||
|
||||||
|
stripeConfig?.live.secretKey === ''
|
||||||
|
}
|
||||||
|
isLoading={isCreating}
|
||||||
|
>
|
||||||
|
{t('connect')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
)
|
||||||
|
}
|
@ -20,7 +20,8 @@ test.describe('Payment input block', () => {
|
|||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Configure...')
|
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="Typebot"]', 'My Stripe Account')
|
||||||
await page.fill('[placeholder="sk_test_..."]', env.STRIPE_SECRET_KEY ?? '')
|
await page.fill('[placeholder="sk_test_..."]', env.STRIPE_SECRET_KEY ?? '')
|
||||||
await page.fill('[placeholder="sk_live_..."]', env.STRIPE_SECRET_KEY ?? '')
|
await page.fill('[placeholder="sk_live_..."]', env.STRIPE_SECRET_KEY ?? '')
|
||||||
|
@ -21,8 +21,8 @@ import { getGoogleSheetsConsentScreenUrlQuery } from '../queries/getGoogleSheets
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
typebotId: string
|
typebotId?: string
|
||||||
blockId: string
|
blockId?: string
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,30 +32,45 @@ export const GoogleSheetConnectModal = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { workspace } = useWorkspace()
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<GoogleSheetConnectModalContent typebotId={typebotId} blockId={blockId} />
|
||||||
<ModalHeader>Connect Spreadsheets</ModalHeader>
|
</Modal>
|
||||||
<ModalCloseButton />
|
)
|
||||||
<ModalBody as={Stack} spacing="6">
|
}
|
||||||
<Text>
|
|
||||||
Make sure to check all the permissions so that the integration works
|
export const GoogleSheetConnectModalContent = ({
|
||||||
as expected:
|
typebotId,
|
||||||
</Text>
|
blockId,
|
||||||
<Image
|
}: {
|
||||||
src="/images/google-spreadsheets-scopes.png"
|
typebotId?: string
|
||||||
alt="Google Spreadsheets checkboxes"
|
blockId?: string
|
||||||
rounded="md"
|
}) => {
|
||||||
/>
|
const { workspace } = useWorkspace()
|
||||||
<AlertInfo>
|
|
||||||
Google does not provide more granular permissions than
|
return (
|
||||||
"read" or "write" access. That's why it
|
<ModalContent>
|
||||||
states that Typebot can also delete your spreadsheets which it
|
<ModalHeader>Connect Spreadsheets</ModalHeader>
|
||||||
won't.
|
<ModalCloseButton />
|
||||||
</AlertInfo>
|
<ModalBody as={Stack} spacing="6">
|
||||||
<Flex>
|
<Text>
|
||||||
|
Make sure to check all the permissions so that the integration works
|
||||||
|
as expected:
|
||||||
|
</Text>
|
||||||
|
<Image
|
||||||
|
src="/images/google-spreadsheets-scopes.png"
|
||||||
|
alt="Google Spreadsheets checkboxes"
|
||||||
|
rounded="md"
|
||||||
|
/>
|
||||||
|
<AlertInfo>
|
||||||
|
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.
|
||||||
|
</AlertInfo>
|
||||||
|
<Flex>
|
||||||
|
{workspace?.id && (
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
leftIcon={<GoogleLogo />}
|
leftIcon={<GoogleLogo />}
|
||||||
@ -64,19 +79,18 @@ export const GoogleSheetConnectModal = ({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
href={getGoogleSheetsConsentScreenUrlQuery(
|
href={getGoogleSheetsConsentScreenUrlQuery(
|
||||||
window.location.href,
|
window.location.href,
|
||||||
|
workspace.id,
|
||||||
blockId,
|
blockId,
|
||||||
workspace?.id,
|
|
||||||
typebotId
|
typebotId
|
||||||
)}
|
)}
|
||||||
mx="auto"
|
mx="auto"
|
||||||
>
|
>
|
||||||
Continue with Google
|
Continue with Google
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
)}
|
||||||
</ModalBody>
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
<ModalFooter />
|
<ModalFooter />
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,44 +5,22 @@ export const GoogleSheetsLogo = (props: IconProps) => (
|
|||||||
<title>Sheets-icon</title>
|
<title>Sheets-icon</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<defs>
|
<defs>
|
||||||
<path
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
id="path-1"
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
|
||||||
id="path-3"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
|
||||||
id="path-5"
|
|
||||||
></path>
|
|
||||||
<linearGradient
|
<linearGradient
|
||||||
x1="50.0053945%"
|
x1="50.0053945%"
|
||||||
y1="8.58610612%"
|
y1="8.58610612%"
|
||||||
x2="50.0053945%"
|
x2="50.0053945%"
|
||||||
y2="100.013939%"
|
y2="100.013939%"
|
||||||
id="linearGradient-7"
|
|
||||||
>
|
>
|
||||||
<stop stopColor="#263238" stopOpacity="0.2" offset="0%"></stop>
|
<stop stopColor="#263238" stopOpacity="0.2" offset="0%"></stop>
|
||||||
<stop stopColor="#263238" stopOpacity="0.02" offset="100%"></stop>
|
<stop stopColor="#263238" stopOpacity="0.02" offset="100%"></stop>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<path
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
id="path-8"
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
></path>
|
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"></path>
|
||||||
<path
|
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
|
||||||
id="path-10"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
|
||||||
id="path-12"
|
|
||||||
></path>
|
|
||||||
<path
|
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
|
||||||
id="path-14"
|
|
||||||
></path>
|
|
||||||
<radialGradient
|
<radialGradient
|
||||||
cx="3.16804688%"
|
cx="3.16804688%"
|
||||||
cy="2.71744318%"
|
cy="2.71744318%"
|
||||||
@ -50,112 +28,101 @@ export const GoogleSheetsLogo = (props: IconProps) => (
|
|||||||
fy="2.71744318%"
|
fy="2.71744318%"
|
||||||
r="161.248516%"
|
r="161.248516%"
|
||||||
gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)"
|
gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)"
|
||||||
id="radialGradient-16"
|
|
||||||
>
|
>
|
||||||
<stop stopColor="#FFFFFF" stopOpacity="0.1" offset="0%"></stop>
|
<stop stopColor="#FFFFFF" stopOpacity="0.1" offset="0%"></stop>
|
||||||
<stop stopColor="#FFFFFF" stopOpacity="0" offset="100%"></stop>
|
<stop stopColor="#FFFFFF" stopOpacity="0" offset="100%"></stop>
|
||||||
</radialGradient>
|
</radialGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||||
<g
|
<g transform="translate(-451.000000, -451.000000)">
|
||||||
id="Consumer-Apps-Sheets-Large-VD-R8-"
|
<g transform="translate(0.000000, 63.000000)">
|
||||||
transform="translate(-451.000000, -451.000000)"
|
<g transform="translate(277.000000, 299.000000)">
|
||||||
>
|
<g transform="translate(174.833333, 89.958333)">
|
||||||
<g id="Hero" transform="translate(0.000000, 63.000000)">
|
<g>
|
||||||
<g id="Personal" transform="translate(277.000000, 299.000000)">
|
<g>
|
||||||
<g id="Sheets-icon" transform="translate(174.833333, 89.958333)">
|
<mask fill="white">
|
||||||
<g id="Group">
|
|
||||||
<g id="Clipped">
|
|
||||||
<mask id="mask-2" fill="white">
|
|
||||||
<use xlinkHref="#path-1"></use>
|
<use xlinkHref="#path-1"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<path
|
<path
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z"
|
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z"
|
||||||
id="Path"
|
|
||||||
fill="#0F9D58"
|
fill="#0F9D58"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
mask="url(#mask-2)"
|
mask="url(#mask-2)"
|
||||||
></path>
|
></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-4" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-3"></use>
|
<use xlinkHref="#path-3"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<path
|
<path
|
||||||
d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z"
|
d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z"
|
||||||
id="Shape"
|
|
||||||
fill="#F1F1F1"
|
fill="#F1F1F1"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
mask="url(#mask-4)"
|
mask="url(#mask-4)"
|
||||||
></path>
|
></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-6" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-5"></use>
|
<use xlinkHref="#path-5"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<polygon
|
<polygon
|
||||||
id="Path"
|
|
||||||
fill="url(#linearGradient-7)"
|
fill="url(#linearGradient-7)"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
mask="url(#mask-6)"
|
mask="url(#mask-6)"
|
||||||
points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"
|
points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"
|
||||||
></polygon>
|
></polygon>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-9" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-8"></use>
|
<use xlinkHref="#path-8"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<g id="Group" mask="url(#mask-9)">
|
<g mask="url(#mask-9)">
|
||||||
<g transform="translate(26.625000, -2.958333)">
|
<g transform="translate(26.625000, -2.958333)">
|
||||||
<path
|
<path
|
||||||
d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z"
|
d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z"
|
||||||
id="Path"
|
|
||||||
fill="#87CEAC"
|
fill="#87CEAC"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
></path>
|
></path>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-11" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-10"></use>
|
<use xlinkHref="#path-10"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<path
|
<path
|
||||||
d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z"
|
d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z"
|
||||||
id="Path"
|
|
||||||
fillOpacity="0.2"
|
fillOpacity="0.2"
|
||||||
fill="#FFFFFF"
|
fill="#FFFFFF"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
mask="url(#mask-11)"
|
mask="url(#mask-11)"
|
||||||
></path>
|
></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-13" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-12"></use>
|
<use xlinkHref="#path-12"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<path
|
<path
|
||||||
d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z"
|
d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z"
|
||||||
id="Path"
|
|
||||||
fillOpacity="0.2"
|
fillOpacity="0.2"
|
||||||
fill="#263238"
|
fill="#263238"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
mask="url(#mask-13)"
|
mask="url(#mask-13)"
|
||||||
></path>
|
></path>
|
||||||
</g>
|
</g>
|
||||||
<g id="Clipped">
|
<g>
|
||||||
<mask id="mask-15" fill="white">
|
<mask fill="white">
|
||||||
<use xlinkHref="#path-14"></use>
|
<use xlinkHref="#path-14"></use>
|
||||||
</mask>
|
</mask>
|
||||||
<g id="SVGID_1_"></g>
|
<g></g>
|
||||||
<path
|
<path
|
||||||
d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z"
|
d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z"
|
||||||
id="Path"
|
|
||||||
fillOpacity="0.1"
|
fillOpacity="0.1"
|
||||||
fill="#263238"
|
fill="#263238"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
@ -165,7 +132,6 @@ export const GoogleSheetsLogo = (props: IconProps) => (
|
|||||||
</g>
|
</g>
|
||||||
<path
|
<path
|
||||||
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z"
|
||||||
id="Path"
|
|
||||||
fill="url(#radialGradient-16)"
|
fill="url(#radialGradient-16)"
|
||||||
fillRule="nonzero"
|
fillRule="nonzero"
|
||||||
></path>
|
></path>
|
||||||
|
@ -2,8 +2,8 @@ import { stringify } from 'qs'
|
|||||||
|
|
||||||
export const getGoogleSheetsConsentScreenUrlQuery = (
|
export const getGoogleSheetsConsentScreenUrlQuery = (
|
||||||
redirectUrl: string,
|
redirectUrl: string,
|
||||||
blockId: string,
|
workspaceId: string,
|
||||||
workspaceId?: string,
|
blockId?: string,
|
||||||
typebotId?: string
|
typebotId?: string
|
||||||
) => {
|
) => {
|
||||||
const queryParams = stringify({
|
const queryParams = stringify({
|
||||||
|
@ -6,79 +6,93 @@ import { SmtpCredentials } from '@typebot.io/schemas'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
config: SmtpCredentials['data']
|
config: SmtpCredentials['data'] | undefined
|
||||||
onConfigChange: (config: SmtpCredentials['data']) => void
|
onConfigChange: (config: SmtpCredentials['data']) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SmtpConfigForm = ({ config, onConfigChange }: Props) => {
|
export const SmtpConfigForm = ({ config, onConfigChange }: Props) => {
|
||||||
const handleFromEmailChange = (email: string) =>
|
const handleFromEmailChange = (email: string) =>
|
||||||
onConfigChange({ ...config, from: { ...config.from, email } })
|
config && onConfigChange({ ...config, from: { ...config.from, email } })
|
||||||
|
|
||||||
const handleFromNameChange = (name: string) =>
|
const handleFromNameChange = (name: string) =>
|
||||||
onConfigChange({ ...config, from: { ...config.from, name } })
|
config && onConfigChange({ ...config, from: { ...config.from, name } })
|
||||||
const handleHostChange = (host: string) => onConfigChange({ ...config, host })
|
|
||||||
|
const handleHostChange = (host: string) =>
|
||||||
|
config && onConfigChange({ ...config, host })
|
||||||
|
|
||||||
const handleUsernameChange = (username: string) =>
|
const handleUsernameChange = (username: string) =>
|
||||||
onConfigChange({ ...config, username })
|
config && onConfigChange({ ...config, username })
|
||||||
|
|
||||||
const handlePasswordChange = (password: string) =>
|
const handlePasswordChange = (password: string) =>
|
||||||
onConfigChange({ ...config, password })
|
config && onConfigChange({ ...config, password })
|
||||||
|
|
||||||
const handleTlsCheck = (isTlsEnabled: boolean) =>
|
const handleTlsCheck = (isTlsEnabled: boolean) =>
|
||||||
onConfigChange({ ...config, isTlsEnabled })
|
config && onConfigChange({ ...config, isTlsEnabled })
|
||||||
|
|
||||||
const handlePortNumberChange = (port?: number) =>
|
const handlePortNumberChange = (port?: number) =>
|
||||||
isDefined(port) && onConfigChange({ ...config, port })
|
config && isDefined(port) && onConfigChange({ ...config, port })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack as="form" spacing={4}>
|
<Stack spacing={4}>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
label="From email"
|
label="From email"
|
||||||
defaultValue={config.from.email ?? ''}
|
defaultValue={config?.from.email}
|
||||||
onChange={handleFromEmailChange}
|
onChange={handleFromEmailChange}
|
||||||
placeholder="notifications@provider.com"
|
placeholder="notifications@provider.com"
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="From name"
|
label="From name"
|
||||||
defaultValue={config.from.name ?? ''}
|
defaultValue={config?.from.name}
|
||||||
onChange={handleFromNameChange}
|
onChange={handleFromNameChange}
|
||||||
placeholder="John Smith"
|
placeholder="John Smith"
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
label="Host"
|
label="Host"
|
||||||
defaultValue={config.host ?? ''}
|
defaultValue={config?.host}
|
||||||
onChange={handleHostChange}
|
onChange={handleHostChange}
|
||||||
placeholder="mail.provider.com"
|
placeholder="mail.provider.com"
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
label="Username"
|
label="Username"
|
||||||
type="email"
|
type="email"
|
||||||
defaultValue={config.username ?? ''}
|
defaultValue={config?.username}
|
||||||
onChange={handleUsernameChange}
|
onChange={handleUsernameChange}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
defaultValue={config.password ?? ''}
|
defaultValue={config?.password}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<SwitchWithLabel
|
<SwitchWithLabel
|
||||||
label="Secure?"
|
label="Secure?"
|
||||||
initialValue={config.isTlsEnabled ?? false}
|
initialValue={config?.isTlsEnabled}
|
||||||
onCheckChange={handleTlsCheck}
|
onCheckChange={handleTlsCheck}
|
||||||
moreInfoContent="If enabled, the connection will use TLS when connecting to server. If disabled then TLS is used if server supports the STARTTLS extension. In most cases enable it if you are connecting to port 465. For port 587 or 25 keep it disabled."
|
moreInfoContent="If enabled, the connection will use TLS when connecting to server. If disabled then TLS is used if server supports the STARTTLS extension. In most cases enable it if you are connecting to port 465. For port 587 or 25 keep it disabled."
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
isRequired
|
isRequired
|
||||||
label="Port number:"
|
label="Port number:"
|
||||||
placeholder="25"
|
placeholder="25"
|
||||||
defaultValue={config.port}
|
defaultValue={config?.port}
|
||||||
onValueChange={handlePortNumberChange}
|
onValueChange={handlePortNumberChange}
|
||||||
withVariableButton={false}
|
withVariableButton={false}
|
||||||
|
isDisabled={!config}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
|
@ -26,9 +26,25 @@ type Props = {
|
|||||||
|
|
||||||
export const SmtpConfigModal = ({
|
export const SmtpConfigModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onNewCredentials,
|
|
||||||
onClose,
|
onClose,
|
||||||
|
onNewCredentials,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<SmtpCreateModalContent
|
||||||
|
onNewCredentials={(id) => {
|
||||||
|
onNewCredentials(id)
|
||||||
|
onClose()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SmtpCreateModalContent = ({
|
||||||
|
onNewCredentials,
|
||||||
|
}: Pick<Props, 'onNewCredentials'>) => {
|
||||||
const { user } = useUser()
|
const { user } = useUser()
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const [isCreating, setIsCreating] = useState(false)
|
const [isCreating, setIsCreating] = useState(false)
|
||||||
@ -53,11 +69,11 @@ export const SmtpConfigModal = ({
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
refetchCredentials()
|
refetchCredentials()
|
||||||
onNewCredentials(data.credentialsId)
|
onNewCredentials(data.credentialsId)
|
||||||
onClose()
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleCreateClick = async () => {
|
const handleCreateClick = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
if (!user?.email || !workspace?.id) return
|
if (!user?.email || !workspace?.id) return
|
||||||
setIsCreating(true)
|
setIsCreating(true)
|
||||||
const { error: testSmtpError } = await testSmtpConfig(
|
const { error: testSmtpError } = await testSmtpConfig(
|
||||||
@ -82,19 +98,18 @@ export const SmtpConfigModal = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClose}>
|
<ModalContent>
|
||||||
<ModalOverlay />
|
<ModalHeader>Create SMTP config</ModalHeader>
|
||||||
<ModalContent>
|
<ModalCloseButton />
|
||||||
<ModalHeader>Create SMTP config</ModalHeader>
|
<form onSubmit={handleCreateClick}>
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<SmtpConfigForm config={smtpConfig} onConfigChange={setSmtpConfig} />
|
<SmtpConfigForm config={smtpConfig} onConfigChange={setSmtpConfig} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
|
type="submit"
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
onClick={handleCreateClick}
|
|
||||||
isDisabled={
|
isDisabled={
|
||||||
isNotDefined(smtpConfig.from.email) ||
|
isNotDefined(smtpConfig.from.email) ||
|
||||||
isNotDefined(smtpConfig.host) ||
|
isNotDefined(smtpConfig.host) ||
|
||||||
@ -107,7 +122,7 @@ export const SmtpConfigModal = ({
|
|||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</form>
|
||||||
</Modal>
|
</ModalContent>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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<SmtpCredentials['data']>()
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Update SMTP config</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<form onSubmit={handleUpdateClick}>
|
||||||
|
<ModalBody>
|
||||||
|
<SmtpConfigForm config={smtpConfig} onConfigChange={setSmtpConfig} />
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
colorScheme="blue"
|
||||||
|
isDisabled={
|
||||||
|
isNotDefined(smtpConfig?.from.email) ||
|
||||||
|
isNotDefined(smtpConfig?.host) ||
|
||||||
|
isNotDefined(smtpConfig?.username) ||
|
||||||
|
isNotDefined(smtpConfig?.password) ||
|
||||||
|
isNotDefined(smtpConfig?.port)
|
||||||
|
}
|
||||||
|
isLoading={isCreating}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
)
|
||||||
|
}
|
@ -1,98 +1,77 @@
|
|||||||
import test, { expect, Page } from '@playwright/test'
|
import test, { expect, Page } from '@playwright/test'
|
||||||
import {
|
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||||
createWebhook,
|
|
||||||
importTypebotInDatabase,
|
|
||||||
} from '@typebot.io/playwright/databaseActions'
|
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import { apiToken } from '@typebot.io/playwright/databaseSetup'
|
import { apiToken } from '@typebot.io/playwright/databaseSetup'
|
||||||
import { env } from '@typebot.io/env'
|
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.describe.configure({ mode: 'parallel' })
|
||||||
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('its configuration should work', async ({ page }) => {
|
test('editor configuration should work', async ({ page }) => {
|
||||||
const typebotId = createId()
|
const typebotId = createId()
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
getTestAsset('typebots/integrations/webhook.json'),
|
getTestAsset('typebots/integrations/webhook.json'),
|
||||||
{
|
{
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await createWebhook(typebotId)
|
|
||||||
|
|
||||||
await page.goto(`/typebots/${typebotId}/edit`)
|
await page.goto(`/typebots/${typebotId}/edit`)
|
||||||
await page.click('text=Configure...')
|
await page.click('text=Configure...')
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'input[placeholder="Paste URL..."]',
|
'input[placeholder="Paste URL..."]',
|
||||||
`${env.NEXTAUTH_URL}/api/mock/webhook`
|
`${env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
|
||||||
)
|
)
|
||||||
await page.click('text=Advanced configuration')
|
await page.click('text=Test the request')
|
||||||
await page.getByRole('button', { name: 'GET' }).click()
|
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
||||||
await page.click('text=POST')
|
`"Group #1": "Go", "secret 1": "content"`,
|
||||||
|
{ timeout: 10000 }
|
||||||
|
)
|
||||||
|
|
||||||
await page.click('text=Query params')
|
await page.fill(
|
||||||
await page.click('text=Add a param')
|
'input[placeholder="Paste URL..."]',
|
||||||
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
|
`${env.NEXTAUTH_URL}/api/mock/webhook`
|
||||||
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
|
)
|
||||||
|
await page.click('text=Advanced configuration')
|
||||||
|
|
||||||
await page.click('text=Add a param')
|
await page.click('text=Query params')
|
||||||
await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
|
await page.click('text=Add a param')
|
||||||
await page.fill(
|
await page.fill('input[placeholder="e.g. email"]', 'firstParam')
|
||||||
'input[placeholder="e.g. {{Email}}"] >> nth=1',
|
await page.fill('input[placeholder="e.g. {{Email}}"]', '{{secret 1}}')
|
||||||
'{{secret 2}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.click('text=Headers')
|
await page.click('text=Add a param')
|
||||||
await page.waitForTimeout(200)
|
await page.fill('input[placeholder="e.g. email"] >> nth=1', 'secondParam')
|
||||||
await page.getByRole('button', { name: 'Add a value' }).click()
|
await page.fill(
|
||||||
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
|
'input[placeholder="e.g. {{Email}}"] >> nth=1',
|
||||||
await page.fill(
|
'{{secret 2}}'
|
||||||
'input[placeholder="e.g. application/json"]',
|
)
|
||||||
'{{secret 3}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.click('text=Body')
|
await page.click('text=Headers')
|
||||||
await page.click('text=Custom body')
|
await page.waitForTimeout(200)
|
||||||
await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
|
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 page.click('text=Body')
|
||||||
await addTestVariable(page, 'secret 1', 'secret1')
|
await page.click('text=Custom body')
|
||||||
await addTestVariable(page, 'secret 2', 'secret2')
|
await page.fill('div[role="textbox"]', '{ "customField": "{{secret 4}}" }')
|
||||||
await addTestVariable(page, 'secret 3', 'secret3')
|
|
||||||
await addTestVariable(page, 'secret 4', 'secret4')
|
|
||||||
|
|
||||||
await page.click('text=Test the request')
|
await page.click('text=Variable values for test')
|
||||||
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
await addTestVariable(page, 'secret 1', 'secret1')
|
||||||
'"statusCode": 200'
|
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=Test the request')
|
||||||
await page.click('text=Add an entry >> nth=-1')
|
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
||||||
await page.click('input[placeholder="Select the data"]')
|
'"statusCode": 200'
|
||||||
await page.click('text=data.flatMap(item => item.name)')
|
)
|
||||||
})
|
|
||||||
|
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) => {
|
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)
|
await page.fill('input >> nth=-1', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('API', () => {
|
test('Webhook API endpoints should work', async ({ request }) => {
|
||||||
const typebotId = 'webhook-flow'
|
const typebotId = createId()
|
||||||
|
await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
|
||||||
|
id: typebotId,
|
||||||
|
})
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
// GET webhook blocks
|
||||||
try {
|
const getResponse = await request.get(
|
||||||
await importTypebotInDatabase(getTestAsset('typebots/api.json'), {
|
`/api/v1/typebots/${typebotId}/webhookBlocks`,
|
||||||
id: typebotId,
|
{
|
||||||
})
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
await createWebhook(typebotId)
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
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 }) => {
|
// Subscribe webhook
|
||||||
const response = await request.get(
|
const url = 'https://test.com'
|
||||||
`/api/v1/typebots/${typebotId}/webhookBlocks`,
|
const subscribeResponse = await request.post(
|
||||||
{
|
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/subscribe`,
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
{
|
||||||
}
|
headers: {
|
||||||
)
|
Authorization: `Bearer ${apiToken}`,
|
||||||
const { webhookBlocks } = await response.json()
|
},
|
||||||
expect(webhookBlocks).toHaveLength(1)
|
data: { url },
|
||||||
expect(webhookBlocks[0]).toEqual({
|
}
|
||||||
id: 'webhookBlock',
|
)
|
||||||
label: 'Webhook > webhookBlock',
|
expect(await subscribeResponse.json()).toEqual({
|
||||||
type: 'Webhook',
|
id: 'webhookBlock',
|
||||||
})
|
url,
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can subscribe webhook', async ({ request }) => {
|
// Unsubscribe webhook
|
||||||
const url = 'https://test.com'
|
const unsubResponse = await request.post(
|
||||||
const response = await request.post(
|
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/unsubscribe`,
|
||||||
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/subscribe`,
|
{
|
||||||
{
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
headers: {
|
}
|
||||||
Authorization: `Bearer ${apiToken}`,
|
)
|
||||||
},
|
expect(await unsubResponse.json()).toEqual({
|
||||||
data: { url },
|
id: 'webhookBlock',
|
||||||
}
|
url: null,
|
||||||
)
|
|
||||||
const body = await response.json()
|
|
||||||
expect(body).toEqual({
|
|
||||||
id: 'webhookBlock',
|
|
||||||
url,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('can unsubscribe webhook', async ({ request }) => {
|
// Get sample result
|
||||||
const response = await request.post(
|
const sampleResponse = await request.get(
|
||||||
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/unsubscribe`,
|
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/getResultExample`,
|
||||||
{
|
{
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
headers: { Authorization: `Bearer ${apiToken}` },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const body = await response.json()
|
const sample = await sampleResponse.json()
|
||||||
expect(body).toEqual({
|
|
||||||
id: 'webhookBlock',
|
|
||||||
url: null,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can get a sample result', async ({ request }) => {
|
expect(omit(sample.resultExample, 'submittedAt')).toMatchObject({
|
||||||
const response = await request.get(
|
message: 'This is a sample result, it has been generated ⬇️',
|
||||||
`/api/v1/typebots/${typebotId}/webhookBlocks/webhookBlock/getResultExample`,
|
Welcome: 'Hi!',
|
||||||
{
|
Email: 'user@email.com',
|
||||||
headers: { Authorization: `Bearer ${apiToken}` },
|
Name: 'answer value',
|
||||||
}
|
Services: 'Website dev, Content Marketing, Social Media, UI / UX Design',
|
||||||
)
|
'Additional information': 'answer value',
|
||||||
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',
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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 (
|
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader>Add Zemantic AI account</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<form onSubmit={createZemanticAiCredentials}>
|
|
||||||
<ModalBody as={Stack} spacing="6">
|
|
||||||
<TextInput
|
|
||||||
isRequired
|
|
||||||
label="Name"
|
|
||||||
onChange={setName}
|
|
||||||
placeholder="My account"
|
|
||||||
withVariableButton={false}
|
|
||||||
debounceTimeout={0}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
isRequired
|
|
||||||
type="password"
|
|
||||||
label="API key"
|
|
||||||
helperText={
|
|
||||||
<>
|
|
||||||
You can generate an API key{' '}
|
|
||||||
<TextLink href={zemanticAIDashboardPage} isExternal>
|
|
||||||
here
|
|
||||||
</TextLink>
|
|
||||||
.
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
onChange={setApiKey}
|
|
||||||
placeholder="ze..."
|
|
||||||
withVariableButton={false}
|
|
||||||
debounceTimeout={0}
|
|
||||||
/>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
isLoading={isCreating}
|
|
||||||
isDisabled={apiKey === '' || name === ''}
|
|
||||||
colorScheme="blue"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
import { TextInput, Textarea, NumberInput } from '@/components/inputs'
|
import { TextInput, Textarea, NumberInput } from '@/components/inputs'
|
||||||
import { CredentialsDropdown } from '@/features/credentials/components/CredentialsDropdown'
|
|
||||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionButton,
|
AccordionButton,
|
||||||
@ -9,15 +7,12 @@ import {
|
|||||||
AccordionPanel,
|
AccordionPanel,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
useDisclosure,
|
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { isEmpty } from '@typebot.io/lib'
|
import { isEmpty } from '@typebot.io/lib'
|
||||||
import { ZemanticAiBlock } from '@typebot.io/schemas'
|
import { ZemanticAiBlock } from '@typebot.io/schemas'
|
||||||
import { ZemanticAiCredentialsModal } from './ZemanticAiCredentialsModal'
|
|
||||||
import { ProjectsDropdown } from './ProjectsDropdown'
|
import { ProjectsDropdown } from './ProjectsDropdown'
|
||||||
import { SearchResponseItem } from './SearchResponseItem'
|
import { SearchResponseItem } from './SearchResponseItem'
|
||||||
import { TableList } from '@/components/TableList'
|
import { TableList } from '@/components/TableList'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
block: ZemanticAiBlock
|
block: ZemanticAiBlock
|
||||||
@ -28,22 +23,6 @@ export const ZemanticAiSettings = ({
|
|||||||
block: { id: blockId, options },
|
block: { id: blockId, options },
|
||||||
onOptionsChange,
|
onOptionsChange,
|
||||||
}: Props) => {
|
}: 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) => {
|
const updateProjectId = (projectId: string | undefined) => {
|
||||||
onOptionsChange({
|
onOptionsChange({
|
||||||
...options,
|
...options,
|
||||||
@ -92,23 +71,6 @@ export const ZemanticAiSettings = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
{workspace && (
|
|
||||||
<>
|
|
||||||
<CredentialsDropdown
|
|
||||||
type="zemanticAi"
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
currentCredentialsId={options?.credentialsId}
|
|
||||||
onCredentialsSelect={updateCredentialsId}
|
|
||||||
onCreateNewClick={onOpen}
|
|
||||||
credentialsName="Zemantic AI account"
|
|
||||||
/>
|
|
||||||
<ZemanticAiCredentialsModal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
onNewCredentials={updateCredentialsId}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{options?.credentialsId && (
|
{options?.credentialsId && (
|
||||||
<>
|
<>
|
||||||
<ProjectsDropdown
|
<ProjectsDropdown
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
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 { smtpCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/sendEmail'
|
||||||
import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
|
import { encrypt } from '@typebot.io/lib/api/encryption/encrypt'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
@ -10,11 +9,11 @@ import {
|
|||||||
Credentials,
|
Credentials,
|
||||||
googleSheetsCredentialsSchema,
|
googleSheetsCredentialsSchema,
|
||||||
stripeCredentialsSchema,
|
stripeCredentialsSchema,
|
||||||
zemanticAiCredentialsSchema,
|
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import { isDefined } from '@typebot.io/lib/utils'
|
import { isDefined } from '@typebot.io/lib/utils'
|
||||||
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
|
import { isWriteWorkspaceForbidden } from '@/features/workspace/helpers/isWriteWorkspaceForbidden'
|
||||||
import { trackEvents } from '@typebot.io/telemetry/trackEvents'
|
import { trackEvents } from '@typebot.io/telemetry/trackEvents'
|
||||||
|
import { forgedCredentialsSchemas } from '@typebot.io/forge-repository/credentials'
|
||||||
|
|
||||||
const inputShape = {
|
const inputShape = {
|
||||||
data: true,
|
data: true,
|
||||||
@ -40,9 +39,10 @@ export const createCredentials = authenticatedProcedure
|
|||||||
stripeCredentialsSchema.pick(inputShape),
|
stripeCredentialsSchema.pick(inputShape),
|
||||||
smtpCredentialsSchema.pick(inputShape),
|
smtpCredentialsSchema.pick(inputShape),
|
||||||
googleSheetsCredentialsSchema.pick(inputShape),
|
googleSheetsCredentialsSchema.pick(inputShape),
|
||||||
openAICredentialsSchema.pick(inputShape),
|
|
||||||
whatsAppCredentialsSchema.pick(inputShape),
|
whatsAppCredentialsSchema.pick(inputShape),
|
||||||
zemanticAiCredentialsSchema.pick(inputShape),
|
...Object.values(forgedCredentialsSchemas).map((i) =>
|
||||||
|
i.pick(inputShape)
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.and(z.object({ id: z.string().cuid2().optional() })),
|
.and(z.object({ id: z.string().cuid2().optional() })),
|
||||||
})
|
})
|
||||||
@ -53,7 +53,12 @@ export const createCredentials = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { credentials }, ctx: { user } }) => {
|
.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({
|
throw new TRPCError({
|
||||||
code: 'CONFLICT',
|
code: 'CONFLICT',
|
||||||
message: 'Credentials already exist.',
|
message: 'Credentials already exist.',
|
||||||
@ -62,7 +67,7 @@ export const createCredentials = authenticatedProcedure
|
|||||||
where: {
|
where: {
|
||||||
id: credentials.workspaceId,
|
id: credentials.workspaceId,
|
||||||
},
|
},
|
||||||
select: { id: true, members: true },
|
select: { id: true, members: { select: { userId: true, role: true } } },
|
||||||
})
|
})
|
||||||
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
|
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
||||||
|
@ -30,11 +30,8 @@ export const deleteCredentials = authenticatedProcedure
|
|||||||
const workspace = await prisma.workspace.findFirst({
|
const workspace = await prisma.workspace.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: workspaceId,
|
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))
|
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
66
apps/builder/src/features/credentials/api/getCredentials.ts
Normal file
66
apps/builder/src/features/credentials/api/getCredentials.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
})
|
@ -1,16 +1,18 @@
|
|||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
import { TRPCError } from '@trpc/server'
|
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 { z } from 'zod'
|
||||||
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
||||||
import { whatsAppCredentialsSchema } from '@typebot.io/schemas/features/whatsapp'
|
import { credentialsTypeSchema } from '@typebot.io/schemas'
|
||||||
import { zemanticAiCredentialsSchema } from '@typebot.io/schemas/features/blocks/integrations/zemanticAi'
|
import { isDefined } from '@udecode/plate-common'
|
||||||
import {
|
|
||||||
googleSheetsCredentialsSchema,
|
const outputCredentialsSchema = z.array(
|
||||||
stripeCredentialsSchema,
|
z.object({
|
||||||
} from '@typebot.io/schemas'
|
id: z.string(),
|
||||||
|
type: credentialsTypeSchema,
|
||||||
|
name: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
export const listCredentials = authenticatedProcedure
|
export const listCredentials = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -25,17 +27,12 @@ export const listCredentials = authenticatedProcedure
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
type: stripeCredentialsSchema.shape.type
|
type: credentialsTypeSchema.optional(),
|
||||||
.or(smtpCredentialsSchema.shape.type)
|
|
||||||
.or(googleSheetsCredentialsSchema.shape.type)
|
|
||||||
.or(openAICredentialsSchema.shape.type)
|
|
||||||
.or(whatsAppCredentialsSchema.shape.type)
|
|
||||||
.or(zemanticAiCredentialsSchema.shape.type),
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.output(
|
.output(
|
||||||
z.object({
|
z.object({
|
||||||
credentials: z.array(z.object({ id: z.string(), name: z.string() })),
|
credentials: outputCredentialsSchema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId, type }, ctx: { user } }) => {
|
||||||
@ -52,6 +49,7 @@ export const listCredentials = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
type: true,
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -60,5 +58,11 @@ export const listCredentials = authenticatedProcedure
|
|||||||
if (!workspace || isReadWorkspaceFobidden(workspace, user))
|
if (!workspace || isReadWorkspaceFobidden(workspace, user))
|
||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Workspace not found' })
|
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))
|
||||||
|
),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,9 +2,13 @@ import { router } from '@/helpers/server/trpc'
|
|||||||
import { createCredentials } from './createCredentials'
|
import { createCredentials } from './createCredentials'
|
||||||
import { deleteCredentials } from './deleteCredentials'
|
import { deleteCredentials } from './deleteCredentials'
|
||||||
import { listCredentials } from './listCredentials'
|
import { listCredentials } from './listCredentials'
|
||||||
|
import { updateCredentials } from './updateCredentials'
|
||||||
|
import { getCredentials } from './getCredentials'
|
||||||
|
|
||||||
export const credentialsRouter = router({
|
export const credentialsRouter = router({
|
||||||
createCredentials,
|
createCredentials,
|
||||||
listCredentials,
|
listCredentials,
|
||||||
|
getCredentials,
|
||||||
deleteCredentials,
|
deleteCredentials,
|
||||||
|
updateCredentials,
|
||||||
})
|
})
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
)
|
@ -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 (
|
||||||
|
<Modal
|
||||||
|
isOpen={creatingType !== undefined}
|
||||||
|
onClose={onClose}
|
||||||
|
size={parseModalSize(creatingType)}
|
||||||
|
>
|
||||||
|
<ModalOverlay />
|
||||||
|
{creatingType && (
|
||||||
|
<CredentialsCreateModalContent
|
||||||
|
type={creatingType}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CredentialsCreateModalContent = ({
|
||||||
|
type,
|
||||||
|
onSubmit,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
type: Credentials['type']
|
||||||
|
onClose: () => void
|
||||||
|
onSubmit: () => void
|
||||||
|
}) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'google sheets':
|
||||||
|
return <GoogleSheetConnectModalContent />
|
||||||
|
case 'smtp':
|
||||||
|
return <SmtpCreateModalContent onNewCredentials={onSubmit} />
|
||||||
|
case 'stripe':
|
||||||
|
return (
|
||||||
|
<StripeCreateModalContent
|
||||||
|
onNewCredentials={onSubmit}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'whatsApp':
|
||||||
|
return (
|
||||||
|
<WhatsAppCreateModalContent
|
||||||
|
onNewCredentials={onSubmit}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<CreateForgedCredentialsModalContent
|
||||||
|
blockDef={forgedBlocks[type]}
|
||||||
|
onNewCredentials={onSubmit}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseModalSize = (type?: Credentials['type']) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'whatsApp':
|
||||||
|
return '3xl'
|
||||||
|
default:
|
||||||
|
return 'lg'
|
||||||
|
}
|
||||||
|
}
|
@ -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<Credentials, 'id' | 'type' | 'name'>
|
||||||
|
|
||||||
|
export const CredentialsSettingsForm = () => {
|
||||||
|
const [creatingType, setCreatingType] = useState<Credentials['type']>()
|
||||||
|
const [editingCredentials, setEditingCredentials] = useState<{
|
||||||
|
id: string
|
||||||
|
type: Credentials['type']
|
||||||
|
}>()
|
||||||
|
const [deletingCredentialsId, setDeletingCredentialsId] = useState<string>()
|
||||||
|
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 (
|
||||||
|
<Stack spacing="6" w="full">
|
||||||
|
<CredentialsCreateModal
|
||||||
|
creatingType={creatingType}
|
||||||
|
onSubmit={() => {
|
||||||
|
refetch()
|
||||||
|
setCreatingType(undefined)
|
||||||
|
}}
|
||||||
|
onClose={() => setCreatingType(undefined)}
|
||||||
|
/>
|
||||||
|
<CredentialsUpdateModal
|
||||||
|
editingCredentials={editingCredentials}
|
||||||
|
onSubmit={() => {
|
||||||
|
refetch()
|
||||||
|
setEditingCredentials(undefined)
|
||||||
|
}}
|
||||||
|
onClose={() => setEditingCredentials(undefined)}
|
||||||
|
/>
|
||||||
|
<HStack justifyContent="space-between">
|
||||||
|
<Heading fontSize="2xl">Credentials</Heading>
|
||||||
|
<Menu isLazy>
|
||||||
|
<MenuButton as={Button} size="sm" leftIcon={<PlusIcon />}>
|
||||||
|
Create new
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
{credentialsTypes.map((type) => (
|
||||||
|
<MenuItem
|
||||||
|
key={type}
|
||||||
|
icon={<CredentialsIcon type={type} boxSize="16px" />}
|
||||||
|
onClick={() => setCreatingType(type)}
|
||||||
|
>
|
||||||
|
<CredentialsLabel type={type} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{credentials && !isLoading ? (
|
||||||
|
(Object.keys(credentials) as Credentials['type'][]).map((type) => (
|
||||||
|
<Stack
|
||||||
|
key={type}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
p="4"
|
||||||
|
spacing={4}
|
||||||
|
data-testid={type}
|
||||||
|
>
|
||||||
|
<HStack spacing="3">
|
||||||
|
<CredentialsIcon type={type} boxSize="24px" />
|
||||||
|
<CredentialsLabel type={type} fontWeight="semibold" />
|
||||||
|
</HStack>
|
||||||
|
<Stack>
|
||||||
|
{credentials[type].map((cred) => (
|
||||||
|
<Stack key={cred.id}>
|
||||||
|
<CredentialsItem
|
||||||
|
type={cred.type}
|
||||||
|
name={cred.name}
|
||||||
|
isDeleting={deletingCredentialsId === cred.id}
|
||||||
|
onEditClick={
|
||||||
|
nonEditableTypes.includes(
|
||||||
|
cred.type as (typeof nonEditableTypes)[number]
|
||||||
|
)
|
||||||
|
? undefined
|
||||||
|
: () =>
|
||||||
|
setEditingCredentials({
|
||||||
|
id: cred.id,
|
||||||
|
type: cred.type,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onDeleteClick={() =>
|
||||||
|
deleteCredentials({
|
||||||
|
workspaceId: workspace!.id,
|
||||||
|
credentialsId: cred.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Divider />
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Stack borderRadius="md" spacing="6">
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<SkeletonCircle />
|
||||||
|
<Stack>
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<SkeletonCircle />
|
||||||
|
<Stack>
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<SkeletonCircle />
|
||||||
|
<Stack>
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<SkeletonCircle />
|
||||||
|
<Stack>
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
<Skeleton height="20px" />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CredentialsIcon = ({
|
||||||
|
type,
|
||||||
|
...props
|
||||||
|
}: { type: Credentials['type'] } & IconProps) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'google sheets':
|
||||||
|
return <BlockIcon type={IntegrationBlockType.GOOGLE_SHEETS} {...props} />
|
||||||
|
case 'smtp':
|
||||||
|
return <BlockIcon type={IntegrationBlockType.EMAIL} {...props} />
|
||||||
|
case 'stripe':
|
||||||
|
return <StripeLogo rounded="sm" {...props} />
|
||||||
|
case 'whatsApp':
|
||||||
|
return <WhatsAppLogo {...props} />
|
||||||
|
default:
|
||||||
|
return <BlockIcon type={type} {...props} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CredentialsLabel = ({
|
||||||
|
type,
|
||||||
|
...props
|
||||||
|
}: { type: Credentials['type'] } & TextProps) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'google sheets':
|
||||||
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
Google Sheets
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'smtp':
|
||||||
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
SMTP
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'stripe':
|
||||||
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
Stripe
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
case 'whatsApp':
|
||||||
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
WhatsApp
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return <BlockLabel type={type} {...props} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CredentialsItem = ({
|
||||||
|
isDeleting,
|
||||||
|
onEditClick,
|
||||||
|
onDeleteClick,
|
||||||
|
...cred
|
||||||
|
}: Pick<Credentials, 'name' | 'type'> & {
|
||||||
|
isDeleting: boolean
|
||||||
|
onEditClick?: () => void
|
||||||
|
onDeleteClick: () => void
|
||||||
|
}) => {
|
||||||
|
const initialFocusRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack justifyContent="space-between" py="2">
|
||||||
|
<Text fontSize="sm">{cred.name}</Text>
|
||||||
|
<HStack>
|
||||||
|
{onEditClick && (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Edit"
|
||||||
|
icon={<EditIcon />}
|
||||||
|
size="xs"
|
||||||
|
onClick={onEditClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Popover isLazy initialFocusRef={initialFocusRef}>
|
||||||
|
{({ onClose }) => (
|
||||||
|
<>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Delete"
|
||||||
|
icon={<TrashIcon />}
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverBody>
|
||||||
|
<Stack spacing="2">
|
||||||
|
<Text fontSize="sm" fontWeight="semibold">
|
||||||
|
Are you sure?
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Make sure this credentials is not used in any of your
|
||||||
|
published bot before proceeding.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</PopoverBody>
|
||||||
|
<PopoverFooter as={Flex} justifyContent="flex-end">
|
||||||
|
<HStack>
|
||||||
|
<Button ref={initialFocusRef} onClick={onClose} size="sm">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="red"
|
||||||
|
onClick={onDeleteClick}
|
||||||
|
isLoading={isDeleting}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</PopoverFooter>
|
||||||
|
</PopoverContent>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupCredentialsByType = (
|
||||||
|
credentials: CredentialsInfo[]
|
||||||
|
): Record<CredentialsInfo['type'], CredentialsInfo[]> => {
|
||||||
|
// 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
|
||||||
|
}
|
@ -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 (
|
||||||
|
<Modal isOpen={editingCredentials !== undefined} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
{editingCredentials && (
|
||||||
|
<CredentialsUpdateModalContent
|
||||||
|
editingCredentials={editingCredentials}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CredentialsUpdateModalContent = ({
|
||||||
|
editingCredentials,
|
||||||
|
onSubmit,
|
||||||
|
}: {
|
||||||
|
editingCredentials: {
|
||||||
|
id: string
|
||||||
|
type: Credentials['type']
|
||||||
|
}
|
||||||
|
onSubmit: () => void
|
||||||
|
}) => {
|
||||||
|
switch (editingCredentials.type) {
|
||||||
|
case 'google sheets':
|
||||||
|
return null
|
||||||
|
case 'smtp':
|
||||||
|
return (
|
||||||
|
<SmtpUpdateModalContent
|
||||||
|
credentialsId={editingCredentials.id}
|
||||||
|
onUpdate={onSubmit}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'stripe':
|
||||||
|
return (
|
||||||
|
<UpdateStripeCredentialsModalContent
|
||||||
|
credentialsId={editingCredentials.id}
|
||||||
|
onUpdate={onSubmit}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'whatsApp':
|
||||||
|
return null
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<UpdateForgedCredentialsModalContent
|
||||||
|
credentialsId={editingCredentials.id}
|
||||||
|
blockDef={forgedBlocks[editingCredentials.type]}
|
||||||
|
onUpdate={onSubmit}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
39
apps/builder/src/features/credentials/credentials.spec.ts
Normal file
39
apps/builder/src/features/credentials/credentials.spec.ts
Normal file
@ -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()
|
||||||
|
})
|
@ -10,13 +10,19 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
|||||||
import { WorkspaceDropdown } from '@/features/workspace/components/WorkspaceDropdown'
|
import { WorkspaceDropdown } from '@/features/workspace/components/WorkspaceDropdown'
|
||||||
import { WorkspaceSettingsModal } from '@/features/workspace/components/WorkspaceSettingsModal'
|
import { WorkspaceSettingsModal } from '@/features/workspace/components/WorkspaceSettingsModal'
|
||||||
import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider'
|
import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvider'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
export const DashboardHeader = () => {
|
export const DashboardHeader = () => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
const { user, logOut } = useUser()
|
const { user, logOut } = useUser()
|
||||||
const { workspace, switchWorkspace, createWorkspace } = useWorkspace()
|
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 = () =>
|
const handleCreateNewWorkspace = () =>
|
||||||
createWorkspace(user?.name ?? undefined)
|
createWorkspace(user?.name ?? undefined)
|
||||||
@ -45,6 +51,9 @@ export const DashboardHeader = () => {
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
user={user}
|
user={user}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
|
defaultTab={
|
||||||
|
isRedirectFromCredentialsCreation ? 'credentials' : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ParentModalProvider>
|
</ParentModalProvider>
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useColorModeValue } from '@chakra-ui/react'
|
import { IconProps, useColorModeValue } from '@chakra-ui/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FlagIcon, SendEmailIcon, ThunderIcon } from '@/components/icons'
|
|
||||||
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
|
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
|
||||||
import { ScriptIcon } from '@/features/blocks/logic/script/components/ScriptIcon'
|
import { ScriptIcon } from '@/features/blocks/logic/script/components/ScriptIcon'
|
||||||
import { JumpIcon } from '@/features/blocks/logic/jump/components/JumpIcon'
|
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 { Block } from '@typebot.io/schemas'
|
||||||
import { OpenAILogo } from '@/features/blocks/integrations/openai/components/OpenAILogo'
|
import { OpenAILogo } from '@/features/blocks/integrations/openai/components/OpenAILogo'
|
||||||
import { ForgedBlockIcon } from '@/features/forge/ForgedBlockIcon'
|
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 blue = useColorModeValue('blue.500', 'blue.300')
|
||||||
const orange = useColorModeValue('orange.500', 'orange.300')
|
const orange = useColorModeValue('orange.500', 'orange.300')
|
||||||
const purple = useColorModeValue('purple.500', 'purple.300')
|
const purple = useColorModeValue('purple.500', 'purple.300')
|
||||||
@ -51,78 +52,78 @@ export const BlockIcon = ({ type, mt }: BlockIconProps): JSX.Element => {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BubbleBlockType.TEXT:
|
case BubbleBlockType.TEXT:
|
||||||
return <TextBubbleIcon color={blue} mt={mt} />
|
return <TextBubbleIcon color={blue} {...props} />
|
||||||
case BubbleBlockType.IMAGE:
|
case BubbleBlockType.IMAGE:
|
||||||
return <ImageBubbleIcon color={blue} mt={mt} />
|
return <ImageBubbleIcon color={blue} {...props} />
|
||||||
case BubbleBlockType.VIDEO:
|
case BubbleBlockType.VIDEO:
|
||||||
return <VideoBubbleIcon color={blue} mt={mt} />
|
return <VideoBubbleIcon color={blue} {...props} />
|
||||||
case BubbleBlockType.EMBED:
|
case BubbleBlockType.EMBED:
|
||||||
return <EmbedBubbleIcon color={blue} mt={mt} />
|
return <EmbedBubbleIcon color={blue} {...props} />
|
||||||
case BubbleBlockType.AUDIO:
|
case BubbleBlockType.AUDIO:
|
||||||
return <AudioBubbleIcon color={blue} mt={mt} />
|
return <AudioBubbleIcon color={blue} {...props} />
|
||||||
case InputBlockType.TEXT:
|
case InputBlockType.TEXT:
|
||||||
return <TextInputIcon color={orange} mt={mt} />
|
return <TextInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.NUMBER:
|
case InputBlockType.NUMBER:
|
||||||
return <NumberInputIcon color={orange} mt={mt} />
|
return <NumberInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.EMAIL:
|
case InputBlockType.EMAIL:
|
||||||
return <EmailInputIcon color={orange} mt={mt} />
|
return <EmailInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.URL:
|
case InputBlockType.URL:
|
||||||
return <UrlInputIcon color={orange} mt={mt} />
|
return <UrlInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.DATE:
|
case InputBlockType.DATE:
|
||||||
return <DateInputIcon color={orange} mt={mt} />
|
return <DateInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.PHONE:
|
case InputBlockType.PHONE:
|
||||||
return <PhoneInputIcon color={orange} mt={mt} />
|
return <PhoneInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.CHOICE:
|
case InputBlockType.CHOICE:
|
||||||
return <ButtonsInputIcon color={orange} mt={mt} />
|
return <ButtonsInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.PICTURE_CHOICE:
|
case InputBlockType.PICTURE_CHOICE:
|
||||||
return <PictureChoiceIcon color={orange} mt={mt} />
|
return <PictureChoiceIcon color={orange} {...props} />
|
||||||
case InputBlockType.PAYMENT:
|
case InputBlockType.PAYMENT:
|
||||||
return <PaymentInputIcon color={orange} mt={mt} />
|
return <PaymentInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.RATING:
|
case InputBlockType.RATING:
|
||||||
return <RatingInputIcon color={orange} mt={mt} />
|
return <RatingInputIcon color={orange} {...props} />
|
||||||
case InputBlockType.FILE:
|
case InputBlockType.FILE:
|
||||||
return <FileInputIcon color={orange} mt={mt} />
|
return <FileInputIcon color={orange} {...props} />
|
||||||
case LogicBlockType.SET_VARIABLE:
|
case LogicBlockType.SET_VARIABLE:
|
||||||
return <SetVariableIcon color={purple} mt={mt} />
|
return <SetVariableIcon color={purple} {...props} />
|
||||||
case LogicBlockType.CONDITION:
|
case LogicBlockType.CONDITION:
|
||||||
return <ConditionIcon color={purple} mt={mt} />
|
return <ConditionIcon color={purple} {...props} />
|
||||||
case LogicBlockType.REDIRECT:
|
case LogicBlockType.REDIRECT:
|
||||||
return <RedirectIcon color={purple} mt={mt} />
|
return <RedirectIcon color={purple} {...props} />
|
||||||
case LogicBlockType.SCRIPT:
|
case LogicBlockType.SCRIPT:
|
||||||
return <ScriptIcon mt={mt} />
|
return <ScriptIcon {...props} />
|
||||||
case LogicBlockType.WAIT:
|
case LogicBlockType.WAIT:
|
||||||
return <WaitIcon color={purple} mt={mt} />
|
return <WaitIcon color={purple} {...props} />
|
||||||
case LogicBlockType.JUMP:
|
case LogicBlockType.JUMP:
|
||||||
return <JumpIcon color={purple} mt={mt} />
|
return <JumpIcon color={purple} {...props} />
|
||||||
case LogicBlockType.TYPEBOT_LINK:
|
case LogicBlockType.TYPEBOT_LINK:
|
||||||
return <TypebotLinkIcon color={purple} mt={mt} />
|
return <TypebotLinkIcon color={purple} {...props} />
|
||||||
case LogicBlockType.AB_TEST:
|
case LogicBlockType.AB_TEST:
|
||||||
return <AbTestIcon color={purple} mt={mt} />
|
return <AbTestIcon color={purple} {...props} />
|
||||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||||
return <GoogleSheetsLogo mt={mt} />
|
return <GoogleSheetsLogo {...props} />
|
||||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||||
return <GoogleAnalyticsLogo mt={mt} />
|
return <GoogleAnalyticsLogo {...props} />
|
||||||
case IntegrationBlockType.WEBHOOK:
|
case IntegrationBlockType.WEBHOOK:
|
||||||
return <ThunderIcon mt={mt} />
|
return <ThunderIcon {...props} />
|
||||||
case IntegrationBlockType.ZAPIER:
|
case IntegrationBlockType.ZAPIER:
|
||||||
return <ZapierLogo mt={mt} />
|
return <ZapierLogo {...props} />
|
||||||
case IntegrationBlockType.MAKE_COM:
|
case IntegrationBlockType.MAKE_COM:
|
||||||
return <MakeComLogo mt={mt} />
|
return <MakeComLogo {...props} />
|
||||||
case IntegrationBlockType.PABBLY_CONNECT:
|
case IntegrationBlockType.PABBLY_CONNECT:
|
||||||
return <PabblyConnectLogo mt={mt} />
|
return <PabblyConnectLogo {...props} />
|
||||||
case IntegrationBlockType.EMAIL:
|
case IntegrationBlockType.EMAIL:
|
||||||
return <SendEmailIcon mt={mt} />
|
return <SendEmailIcon {...props} />
|
||||||
case IntegrationBlockType.CHATWOOT:
|
case IntegrationBlockType.CHATWOOT:
|
||||||
return <ChatwootLogo mt={mt} />
|
return <ChatwootLogo {...props} />
|
||||||
case IntegrationBlockType.PIXEL:
|
case IntegrationBlockType.PIXEL:
|
||||||
return <PixelLogo mt={mt} />
|
return <PixelLogo {...props} />
|
||||||
case IntegrationBlockType.ZEMANTIC_AI:
|
case IntegrationBlockType.ZEMANTIC_AI:
|
||||||
return <ZemanticAiLogo mt={mt} />
|
return <ZemanticAiLogo {...props} />
|
||||||
case 'start':
|
case 'start':
|
||||||
return <FlagIcon mt={mt} />
|
return <FlagIcon {...props} />
|
||||||
case IntegrationBlockType.OPEN_AI:
|
case IntegrationBlockType.OPEN_AI:
|
||||||
return <OpenAILogo mt={mt} fill={openAIColor} />
|
return <OpenAILogo {...props} fill={openAIColor} />
|
||||||
default:
|
default:
|
||||||
return <ForgedBlockIcon type={type} mt={mt} />
|
return <ForgedBlockIcon type={type} {...props} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Text } from '@chakra-ui/react'
|
import { Text, TextProps } from '@chakra-ui/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
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 { Block } from '@typebot.io/schemas'
|
||||||
import { ForgedBlockLabel } from '@/features/forge/ForgedBlockLabel'
|
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()
|
const { t } = useTranslate()
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'start':
|
case 'start':
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.start.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.start.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case BubbleBlockType.TEXT:
|
case BubbleBlockType.TEXT:
|
||||||
case InputBlockType.TEXT:
|
case InputBlockType.TEXT:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.text.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.text.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case BubbleBlockType.IMAGE:
|
case BubbleBlockType.IMAGE:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.image.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.image.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case BubbleBlockType.VIDEO:
|
case BubbleBlockType.VIDEO:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.video.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.video.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case BubbleBlockType.EMBED:
|
case BubbleBlockType.EMBED:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.embed.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.embed.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case BubbleBlockType.AUDIO:
|
case BubbleBlockType.AUDIO:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.audio.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.audio.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.NUMBER:
|
case InputBlockType.NUMBER:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.number.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.number.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.EMAIL:
|
case InputBlockType.EMAIL:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.email.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.email.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.URL:
|
case InputBlockType.URL:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.website.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.website.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.DATE:
|
case InputBlockType.DATE:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.date.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.date.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.PHONE:
|
case InputBlockType.PHONE:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.phone.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.phone.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.CHOICE:
|
case InputBlockType.CHOICE:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.button.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.button.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.PICTURE_CHOICE:
|
case InputBlockType.PICTURE_CHOICE:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.picChoice.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.picChoice.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case InputBlockType.PAYMENT:
|
case InputBlockType.PAYMENT:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.payment.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.payment.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.RATING:
|
case InputBlockType.RATING:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.rating.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.rating.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case InputBlockType.FILE:
|
case InputBlockType.FILE:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.file.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.file.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case LogicBlockType.SET_VARIABLE:
|
case LogicBlockType.SET_VARIABLE:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.setVariable.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.setVariable.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case LogicBlockType.CONDITION:
|
case LogicBlockType.CONDITION:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.condition.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.condition.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case LogicBlockType.REDIRECT:
|
case LogicBlockType.REDIRECT:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.redirect.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.redirect.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case LogicBlockType.SCRIPT:
|
case LogicBlockType.SCRIPT:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.script.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.script.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case LogicBlockType.TYPEBOT_LINK:
|
case LogicBlockType.TYPEBOT_LINK:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.typebot.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.typebot.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case LogicBlockType.WAIT:
|
case LogicBlockType.WAIT:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.wait.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.wait.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case LogicBlockType.JUMP:
|
case LogicBlockType.JUMP:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.jump.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.jump.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case LogicBlockType.AB_TEST:
|
case LogicBlockType.AB_TEST:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.abTest.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.abTest.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.sheets.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.sheets.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
case IntegrationBlockType.GOOGLE_ANALYTICS:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.analytics.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.analytics.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case IntegrationBlockType.WEBHOOK:
|
case IntegrationBlockType.WEBHOOK:
|
||||||
return <Text fontSize="sm">HTTP request</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
HTTP request
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.ZAPIER:
|
case IntegrationBlockType.ZAPIER:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.zapier.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.zapier.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.MAKE_COM:
|
case IntegrationBlockType.MAKE_COM:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.makecom.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.makecom.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.PABBLY_CONNECT:
|
case IntegrationBlockType.PABBLY_CONNECT:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.pabbly.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.pabbly.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.EMAIL:
|
case IntegrationBlockType.EMAIL:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.email.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.email.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.CHATWOOT:
|
case IntegrationBlockType.CHATWOOT:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.chatwoot.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.chatwoot.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
case IntegrationBlockType.OPEN_AI:
|
case IntegrationBlockType.OPEN_AI:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.openai.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.openai.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.PIXEL:
|
case IntegrationBlockType.PIXEL:
|
||||||
return <Text fontSize="sm">{t('editor.sidebarBlock.pixel.label')}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.pixel.label')}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
case IntegrationBlockType.ZEMANTIC_AI:
|
case IntegrationBlockType.ZEMANTIC_AI:
|
||||||
return (
|
return (
|
||||||
<Text fontSize="sm">{t('editor.sidebarBlock.zemanticAi.label')}</Text>
|
<Text fontSize="sm" {...props}>
|
||||||
|
{t('editor.sidebarBlock.zemanticAi.label')}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return <ForgedBlockLabel type={type} />
|
return <ForgedBlockLabel type={type} {...props} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 { ForgedBlock } from '@typebot.io/forge-repository/types'
|
||||||
import { useForgedBlock } from './hooks/useForgedBlock'
|
import { useForgedBlock } from './hooks/useForgedBlock'
|
||||||
|
|
||||||
export const ForgedBlockIcon = ({
|
export const ForgedBlockIcon = ({
|
||||||
type,
|
type,
|
||||||
mt,
|
...props
|
||||||
}: {
|
}: {
|
||||||
type: ForgedBlock['type']
|
type: ForgedBlock['type']
|
||||||
mt?: string
|
} & IconProps): JSX.Element => {
|
||||||
}): JSX.Element => {
|
|
||||||
const { colorMode } = useColorMode()
|
const { colorMode } = useColorMode()
|
||||||
const { blockDef } = useForgedBlock(type)
|
const { blockDef } = useForgedBlock(type)
|
||||||
if (!blockDef) return <></>
|
if (!blockDef) return <></>
|
||||||
if (colorMode === 'dark' && blockDef.DarkLogo)
|
if (colorMode === 'dark' && blockDef.DarkLogo)
|
||||||
return <blockDef.DarkLogo width="1rem" style={{ marginTop: mt }} />
|
return (
|
||||||
return <blockDef.LightLogo width="1rem" style={{ marginTop: mt }} />
|
<blockDef.DarkLogo
|
||||||
|
width="1rem"
|
||||||
|
style={{ marginTop: props.mt?.toString() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<blockDef.LightLogo
|
||||||
|
width="1rem"
|
||||||
|
style={{ marginTop: props.mt?.toString() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { ForgedBlock } from '@typebot.io/forge-repository/types'
|
import { ForgedBlock } from '@typebot.io/forge-repository/types'
|
||||||
import { useForgedBlock } from './hooks/useForgedBlock'
|
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)
|
const { blockDef } = useForgedBlock(type)
|
||||||
|
|
||||||
return <Text fontSize="sm">{blockDef?.name}</Text>
|
return (
|
||||||
|
<Text fontSize="sm" {...props}>
|
||||||
|
{blockDef?.name}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
|
||||||
})
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
)
|
|
@ -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 }
|
|
||||||
})
|
|
@ -1,12 +1,6 @@
|
|||||||
import { router } from '@/helpers/server/trpc'
|
import { router } from '@/helpers/server/trpc'
|
||||||
import { fetchSelectItems } from './fetchSelectItems'
|
import { fetchSelectItems } from './fetchSelectItems'
|
||||||
import { createCredentials } from './credentials/createCredentials'
|
|
||||||
import { deleteCredentials } from './credentials/deleteCredentials'
|
|
||||||
import { listCredentials } from './credentials/listCredentials'
|
|
||||||
|
|
||||||
export const forgeRouter = router({
|
export const forgeRouter = router({
|
||||||
fetchSelectItems,
|
fetchSelectItems,
|
||||||
createCredentials,
|
|
||||||
listCredentials,
|
|
||||||
deleteCredentials,
|
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Stack, useDisclosure } from '@chakra-ui/react'
|
import { Stack, useDisclosure } from '@chakra-ui/react'
|
||||||
import { BlockOptions } from '@typebot.io/schemas'
|
import { BlockOptions } from '@typebot.io/schemas'
|
||||||
import { ForgedCredentialsDropdown } from './credentials/ForgedCredentialsDropdown'
|
import { ForgedCredentialsDropdown } from './credentials/ForgedCredentialsDropdown'
|
||||||
import { ForgedCredentialsModal } from './credentials/ForgedCredentialsModal'
|
import { CreateForgedCredentialsModal } from './credentials/CreateForgedCredentialsModal'
|
||||||
import { ZodObjectLayout } from './zodLayouts/ZodObjectLayout'
|
import { ZodObjectLayout } from './zodLayouts/ZodObjectLayout'
|
||||||
import { ZodActionDiscriminatedUnion } from './zodLayouts/ZodActionDiscriminatedUnion'
|
import { ZodActionDiscriminatedUnion } from './zodLayouts/ZodActionDiscriminatedUnion'
|
||||||
import { useForgedBlock } from '../hooks/useForgedBlock'
|
import { useForgedBlock } from '../hooks/useForgedBlock'
|
||||||
@ -64,7 +64,7 @@ export const ForgedBlockSettings = ({ block, onOptionsChange }: Props) => {
|
|||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
{blockDef.auth && (
|
{blockDef.auth && (
|
||||||
<>
|
<>
|
||||||
<ForgedCredentialsModal
|
<CreateForgedCredentialsModal
|
||||||
blockDef={blockDef}
|
blockDef={blockDef}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
@ -16,20 +16,44 @@ import {
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ZodObjectLayout } from '../zodLayouts/ZodObjectLayout'
|
import { ZodObjectLayout } from '../zodLayouts/ZodObjectLayout'
|
||||||
import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
|
import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
|
||||||
|
import { Credentials } from '@typebot.io/schemas'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
blockDef: ForgedBlockDefinition
|
blockDef: ForgedBlockDefinition
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
defaultData?: any
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onNewCredentials: (id: string) => void
|
onNewCredentials: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ForgedCredentialsModal = ({
|
export const CreateForgedCredentialsModal = ({
|
||||||
blockDef,
|
blockDef,
|
||||||
isOpen,
|
isOpen,
|
||||||
|
defaultData,
|
||||||
onClose,
|
onClose,
|
||||||
onNewCredentials,
|
onNewCredentials,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
if (!blockDef.auth) return null
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
||||||
|
<ModalOverlay />
|
||||||
|
<CreateForgedCredentialsModalContent
|
||||||
|
defaultData={defaultData}
|
||||||
|
blockDef={blockDef}
|
||||||
|
onNewCredentials={(id) => {
|
||||||
|
onClose()
|
||||||
|
onNewCredentials(id)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateForgedCredentialsModalContent = ({
|
||||||
|
blockDef,
|
||||||
|
onNewCredentials,
|
||||||
|
}: Pick<Props, 'blockDef' | 'onNewCredentials' | 'defaultData'>) => {
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
@ -42,7 +66,8 @@ export const ForgedCredentialsModal = ({
|
|||||||
listCredentials: { refetch: refetchCredentials },
|
listCredentials: { refetch: refetchCredentials },
|
||||||
},
|
},
|
||||||
} = trpc.useContext()
|
} = trpc.useContext()
|
||||||
const { mutate } = trpc.forge.createCredentials.useMutation({
|
|
||||||
|
const { mutate } = trpc.credentials.createCredentials.useMutation({
|
||||||
onMutate: () => setIsCreating(true),
|
onMutate: () => setIsCreating(true),
|
||||||
onSettled: () => setIsCreating(false),
|
onSettled: () => setIsCreating(false),
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
@ -54,59 +79,55 @@ export const ForgedCredentialsModal = ({
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
refetchCredentials()
|
refetchCredentials()
|
||||||
onNewCredentials(data.credentialsId)
|
onNewCredentials(data.credentialsId)
|
||||||
onClose()
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const createOpenAICredentials = async (e: React.FormEvent) => {
|
const createOpenAICredentials = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!workspace) return
|
if (!workspace || !blockDef.auth) return
|
||||||
mutate({
|
mutate({
|
||||||
credentials: {
|
credentials: {
|
||||||
type: blockDef.id,
|
type: blockDef.id as Credentials['type'],
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
name,
|
name,
|
||||||
data,
|
data,
|
||||||
},
|
} as Credentials,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!blockDef.auth) return null
|
if (!blockDef.auth) return null
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
<ModalContent>
|
||||||
<ModalOverlay />
|
<ModalHeader>Add {blockDef.auth.name}</ModalHeader>
|
||||||
<ModalContent>
|
<ModalCloseButton />
|
||||||
<ModalHeader>Add {blockDef.auth.name}</ModalHeader>
|
<form onSubmit={createOpenAICredentials}>
|
||||||
<ModalCloseButton />
|
<ModalBody as={Stack} spacing="6">
|
||||||
<form onSubmit={createOpenAICredentials}>
|
<TextInput
|
||||||
<ModalBody as={Stack} spacing="6">
|
isRequired
|
||||||
<TextInput
|
label="Name"
|
||||||
isRequired
|
onChange={setName}
|
||||||
label="Name"
|
placeholder="My account"
|
||||||
onChange={setName}
|
withVariableButton={false}
|
||||||
placeholder="My account"
|
debounceTimeout={0}
|
||||||
withVariableButton={false}
|
/>
|
||||||
debounceTimeout={0}
|
<ZodObjectLayout
|
||||||
/>
|
schema={blockDef.auth.schema}
|
||||||
<ZodObjectLayout
|
data={data}
|
||||||
schema={blockDef.auth.schema}
|
onDataChange={setData}
|
||||||
data={data}
|
/>
|
||||||
onDataChange={setData}
|
</ModalBody>
|
||||||
/>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
isLoading={isCreating}
|
isLoading={isCreating}
|
||||||
isDisabled={Object.keys(data).length === 0}
|
isDisabled={Object.keys(data).length === 0}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -16,6 +16,7 @@ import { trpc } from '@/lib/trpc'
|
|||||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||||
import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
|
import { ForgedBlockDefinition } from '@typebot.io/forge-repository/types'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
|
import { Credentials } from '@typebot.io/schemas/features/credentials'
|
||||||
|
|
||||||
type Props = Omit<ButtonProps, 'type'> & {
|
type Props = Omit<ButtonProps, 'type'> & {
|
||||||
blockDef: ForgedBlockDefinition
|
blockDef: ForgedBlockDefinition
|
||||||
@ -34,13 +35,14 @@ export const ForgedCredentialsDropdown = ({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const { workspace, currentRole } = useWorkspace()
|
const { workspace, currentRole } = useWorkspace()
|
||||||
const { data, refetch, isLoading } = trpc.forge.listCredentials.useQuery(
|
const { data, refetch, isLoading } =
|
||||||
{
|
trpc.credentials.listCredentials.useQuery(
|
||||||
workspaceId: workspace?.id as string,
|
{
|
||||||
type: blockDef.id,
|
workspaceId: workspace?.id as string,
|
||||||
},
|
type: blockDef.id as Credentials['type'],
|
||||||
{ enabled: !!workspace?.id }
|
},
|
||||||
)
|
{ enabled: !!workspace?.id }
|
||||||
|
)
|
||||||
const [isDeleting, setIsDeleting] = useState<string>()
|
const [isDeleting, setIsDeleting] = useState<string>()
|
||||||
|
|
||||||
const { mutate } = trpc.credentials.deleteCredentials.useMutation({
|
const { mutate } = trpc.credentials.deleteCredentials.useMutation({
|
||||||
|
@ -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<any>()
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Update {blockDef.auth.name}</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<form onSubmit={updateCredentials}>
|
||||||
|
<ModalBody as={Stack} spacing="6">
|
||||||
|
<TextInput
|
||||||
|
isRequired
|
||||||
|
label="Name"
|
||||||
|
defaultValue={name}
|
||||||
|
onChange={setName}
|
||||||
|
placeholder="My account"
|
||||||
|
withVariableButton={false}
|
||||||
|
debounceTimeout={0}
|
||||||
|
/>
|
||||||
|
<ZodObjectLayout
|
||||||
|
schema={blockDef.auth.schema}
|
||||||
|
data={data}
|
||||||
|
onDataChange={setData}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
isLoading={isUpdating}
|
||||||
|
isDisabled={!data || Object.keys(data).length === 0}
|
||||||
|
colorScheme="blue"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
)
|
||||||
|
}
|
@ -64,6 +64,21 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onNewCredentials,
|
onNewCredentials,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<WhatsAppCreateModalContent
|
||||||
|
onNewCredentials={onNewCredentials}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WhatsAppCreateModalContent = ({
|
||||||
|
onNewCredentials,
|
||||||
|
onClose,
|
||||||
|
}: Pick<Props, 'onNewCredentials' | 'onClose'>) => {
|
||||||
const { workspace } = useWorkspace()
|
const { workspace } = useWorkspace()
|
||||||
const { showToast } = useToast()
|
const { showToast } = useToast()
|
||||||
const { activeStep, goToNext, goToPrevious, setActiveStep } = useSteps({
|
const { activeStep, goToNext, goToPrevious, setActiveStep } = useSteps({
|
||||||
@ -226,82 +241,78 @@ export const WhatsAppCredentialsModal = ({
|
|||||||
|
|
||||||
goToNext()
|
goToNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
|
<ModalContent>
|
||||||
<ModalOverlay />
|
<ModalHeader>
|
||||||
<ModalContent>
|
<HStack h="40px">
|
||||||
<ModalHeader>
|
{activeStep > 0 && (
|
||||||
<HStack h="40px">
|
<IconButton
|
||||||
{activeStep > 0 && (
|
icon={<ChevronLeftIcon />}
|
||||||
<IconButton
|
aria-label={'Go back'}
|
||||||
icon={<ChevronLeftIcon />}
|
variant="ghost"
|
||||||
aria-label={'Go back'}
|
onClick={goToPrevious}
|
||||||
variant="ghost"
|
/>
|
||||||
onClick={goToPrevious}
|
)}
|
||||||
/>
|
<Heading size="md">Add a WhatsApp phone number</Heading>
|
||||||
)}
|
</HStack>
|
||||||
<Heading size="md">Add a WhatsApp phone number</Heading>
|
</ModalHeader>
|
||||||
</HStack>
|
<ModalCloseButton />
|
||||||
</ModalHeader>
|
<ModalBody as={Stack} spacing="10">
|
||||||
<ModalCloseButton />
|
<Stepper index={activeStep} size="sm" pt="4">
|
||||||
<ModalBody as={Stack} spacing="10">
|
{steps.map((step, index) => (
|
||||||
<Stepper index={activeStep} size="sm" pt="4">
|
<Step key={index}>
|
||||||
{steps.map((step, index) => (
|
<StepIndicator>
|
||||||
<Step key={index}>
|
<StepStatus
|
||||||
<StepIndicator>
|
complete={<StepIcon />}
|
||||||
<StepStatus
|
incomplete={<StepNumber />}
|
||||||
complete={<StepIcon />}
|
active={<StepNumber />}
|
||||||
incomplete={<StepNumber />}
|
/>
|
||||||
active={<StepNumber />}
|
</StepIndicator>
|
||||||
/>
|
|
||||||
</StepIndicator>
|
|
||||||
|
|
||||||
<Box flexShrink="0">
|
<Box flexShrink="0">
|
||||||
<StepTitle>{step.title}</StepTitle>
|
<StepTitle>{step.title}</StepTitle>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<StepSeparator />
|
<StepSeparator />
|
||||||
</Step>
|
</Step>
|
||||||
))}
|
))}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
{activeStep === 0 && <Requirements />}
|
{activeStep === 0 && <Requirements />}
|
||||||
{activeStep === 1 && (
|
{activeStep === 1 && (
|
||||||
<SystemUserToken
|
<SystemUserToken
|
||||||
initialToken={systemUserAccessToken}
|
initialToken={systemUserAccessToken}
|
||||||
setToken={setSystemUserAccessToken}
|
setToken={setSystemUserAccessToken}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeStep === 2 && (
|
{activeStep === 2 && (
|
||||||
<PhoneNumber
|
<PhoneNumber
|
||||||
appId={tokenInfoData?.appId}
|
appId={tokenInfoData?.appId}
|
||||||
initialPhoneNumberId={phoneNumberId}
|
initialPhoneNumberId={phoneNumberId}
|
||||||
setPhoneNumberId={setPhoneNumberId}
|
setPhoneNumberId={setPhoneNumberId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeStep === 3 && (
|
{activeStep === 3 && (
|
||||||
<Webhook
|
<Webhook
|
||||||
appId={tokenInfoData?.appId}
|
appId={tokenInfoData?.appId}
|
||||||
verificationToken={verificationToken}
|
verificationToken={verificationToken}
|
||||||
credentialsId={credentialsId}
|
credentialsId={credentialsId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={goToNextStep}
|
onClick={goToNextStep}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
isDisabled={
|
isDisabled={
|
||||||
(activeStep === 1 && isEmpty(systemUserAccessToken)) ||
|
(activeStep === 1 && isEmpty(systemUserAccessToken)) ||
|
||||||
(activeStep === 2 && isEmpty(phoneNumberId))
|
(activeStep === 2 && isEmpty(phoneNumberId))
|
||||||
}
|
}
|
||||||
isLoading={isVerifying || isCreating}
|
isLoading={isVerifying || isCreating}
|
||||||
>
|
>
|
||||||
{activeStep === steps.length - 1 ? 'Submit' : 'Continue'}
|
{activeStep === steps.length - 1 ? 'Submit' : 'Continue'}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
HardDriveIcon,
|
HardDriveIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
|
WalletIcon,
|
||||||
} from '@/components/icons'
|
} from '@/components/icons'
|
||||||
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
|
import { EmojiOrImageIcon } from '@/components/EmojiOrImageIcon'
|
||||||
import { User, WorkspaceRole } from '@typebot.io/prisma'
|
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 { BillingSettingsLayout } from '@/features/billing/components/BillingSettingsLayout'
|
||||||
import { useTranslate } from '@tolgee/react'
|
import { useTranslate } from '@tolgee/react'
|
||||||
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
||||||
|
import { CredentialsSettingsForm } from '@/features/credentials/components/CredentialsSettingsForm'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
user: User
|
user: User
|
||||||
workspace: WorkspaceInApp
|
workspace: WorkspaceInApp
|
||||||
|
defaultTab?: SettingsTab
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,17 +43,19 @@ type SettingsTab =
|
|||||||
| 'workspace-settings'
|
| 'workspace-settings'
|
||||||
| 'members'
|
| 'members'
|
||||||
| 'billing'
|
| 'billing'
|
||||||
|
| 'credentials'
|
||||||
|
|
||||||
export const WorkspaceSettingsModal = ({
|
export const WorkspaceSettingsModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
user,
|
user,
|
||||||
workspace,
|
workspace,
|
||||||
|
defaultTab = 'my-account',
|
||||||
onClose,
|
onClose,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslate()
|
const { t } = useTranslate()
|
||||||
const { ref } = useParentModal()
|
const { ref } = useParentModal()
|
||||||
const { currentRole } = useWorkspace()
|
const { currentRole } = useWorkspace()
|
||||||
const [selectedTab, setSelectedTab] = useState<SettingsTab>('my-account')
|
const [selectedTab, setSelectedTab] = useState<SettingsTab>(defaultTab)
|
||||||
|
|
||||||
const canEditWorkspace = currentRole === WorkspaceRole.ADMIN
|
const canEditWorkspace = currentRole === WorkspaceRole.ADMIN
|
||||||
|
|
||||||
@ -121,6 +126,18 @@ export const WorkspaceSettingsModal = ({
|
|||||||
{t('workspace.settings.modal.menu.settings.label')}
|
{t('workspace.settings.modal.menu.settings.label')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canEditWorkspace && (
|
||||||
|
<Button
|
||||||
|
variant={selectedTab === 'credentials' ? 'solid' : 'ghost'}
|
||||||
|
onClick={() => setSelectedTab('credentials')}
|
||||||
|
leftIcon={<WalletIcon />}
|
||||||
|
size="sm"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
pl="4"
|
||||||
|
>
|
||||||
|
Credentials
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{currentRole !== WorkspaceRole.GUEST && (
|
{currentRole !== WorkspaceRole.GUEST && (
|
||||||
<Button
|
<Button
|
||||||
variant={selectedTab === 'members' ? 'solid' : 'ghost'}
|
variant={selectedTab === 'members' ? 'solid' : 'ghost'}
|
||||||
@ -186,6 +203,8 @@ const SettingsContent = ({
|
|||||||
return <MembersList />
|
return <MembersList />
|
||||||
case 'billing':
|
case 'billing':
|
||||||
return <BillingSettingsLayout />
|
return <BillingSettingsLayout />
|
||||||
|
case 'credentials':
|
||||||
|
return <CredentialsSettingsForm />
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
if (!state) return badRequest(res)
|
if (!state) return badRequest(res)
|
||||||
const { typebotId, redirectUrl, blockId, workspaceId } = JSON.parse(
|
const { typebotId, redirectUrl, blockId, workspaceId } = JSON.parse(
|
||||||
Buffer.from(state, 'base64').toString()
|
Buffer.from(state, 'base64').toString()
|
||||||
)
|
) as {
|
||||||
|
redirectUrl: string
|
||||||
|
workspaceId: string
|
||||||
|
typebotId?: string
|
||||||
|
blockId?: string
|
||||||
|
}
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
const code = req.query.code as string | undefined
|
const code = req.query.code as string | undefined
|
||||||
if (!workspaceId) return badRequest(res)
|
if (!workspaceId) return badRequest(res)
|
||||||
@ -55,6 +60,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const { id: credentialsId } = await prisma.credentials.create({
|
const { id: credentialsId } = await prisma.credentials.create({
|
||||||
data: credentials,
|
data: credentials,
|
||||||
})
|
})
|
||||||
|
if (!typebotId) return res.redirect(`${redirectUrl.split('?')[0]}`)
|
||||||
const typebot = await prisma.typebot.findFirst({
|
const typebot = await prisma.typebot.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
{
|
{
|
||||||
"id": "qujHPjZ44xbrHb1hS1d8qC",
|
"version": "6",
|
||||||
"createdAt": "2022-02-05T06:21:16.522Z",
|
"id": "clyoe5iot0001grw96sdkhsfo",
|
||||||
"updatedAt": "2022-02-05T06:21:16.522Z",
|
|
||||||
"name": "My typebot",
|
"name": "My typebot",
|
||||||
"folderId": null,
|
"events": [
|
||||||
"groups": [
|
|
||||||
{
|
{
|
||||||
"id": "k6kY6gwRE6noPoYQNGzgUq",
|
"id": "k6kY6gwRE6noPoYQNGzgUq",
|
||||||
"blocks": [
|
"outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE",
|
||||||
{
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
"id": "22HP69iipkLjJDTUcc1AWW",
|
"type": "start"
|
||||||
"type": "start",
|
}
|
||||||
"label": "Start",
|
],
|
||||||
"groupId": "k6kY6gwRE6noPoYQNGzgUq",
|
"groups": [
|
||||||
"outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Start",
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "kinRXxYop2X4d7F9qt8WNB",
|
"id": "kinRXxYop2X4d7F9qt8WNB",
|
||||||
|
"title": "Welcome",
|
||||||
|
"graphCoordinates": { "x": 1, "y": 148 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "sc1y8VwDabNJgiVTBi4qtif",
|
"id": "sc1y8VwDabNJgiVTBi4qtif",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "kinRXxYop2X4d7F9qt8WNB",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -42,37 +35,27 @@
|
|||||||
{
|
{
|
||||||
"id": "s7YqZTBeyCa4Hp3wN2j922c",
|
"id": "s7YqZTBeyCa4Hp3wN2j922c",
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"groupId": "kinRXxYop2X4d7F9qt8WNB",
|
|
||||||
"content": {
|
"content": {
|
||||||
"url": "https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g"
|
"url": "https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "sbjZWLJGVkHAkDqS4JQeGow",
|
"id": "sbjZWLJGVkHAkDqS4JQeGow",
|
||||||
|
"outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim",
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"items": [
|
"items": [{ "id": "hQw2zbp7FDX7XYK9cFpbgC", "content": "Hi!" }],
|
||||||
{
|
"options": { "isMultipleChoice": false, "buttonLabel": "Send" }
|
||||||
"id": "hQw2zbp7FDX7XYK9cFpbgC",
|
|
||||||
"type": 0,
|
|
||||||
"blockId": "sbjZWLJGVkHAkDqS4JQeGow",
|
|
||||||
"content": "Hi!"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"groupId": "kinRXxYop2X4d7F9qt8WNB",
|
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
|
||||||
"outgoingEdgeId": "i51YhHpk1dtSyduFNf5Wim"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Welcome",
|
|
||||||
"graphCoordinates": { "x": 1, "y": 148 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "o4SH1UtKANnW5N5D67oZUz",
|
"id": "o4SH1UtKANnW5N5D67oZUz",
|
||||||
|
"title": "Email",
|
||||||
|
"graphCoordinates": { "x": 669, "y": 141 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "sxeYubYN6XzhAfG7m9Fivhc",
|
"id": "sxeYubYN6XzhAfG7m9Fivhc",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "o4SH1UtKANnW5N5D67oZUz",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -85,7 +68,6 @@
|
|||||||
{
|
{
|
||||||
"id": "scQ5kduafAtfP9T8SHUJnGi",
|
"id": "scQ5kduafAtfP9T8SHUJnGi",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "o4SH1UtKANnW5N5D67oZUz",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -99,25 +81,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "snbsad18Bgry8yZ8DZCfdFD",
|
"id": "snbsad18Bgry8yZ8DZCfdFD",
|
||||||
|
"outgoingEdgeId": "w3MiN1Ct38jT5NykVsgmb5",
|
||||||
"type": "email input",
|
"type": "email input",
|
||||||
"groupId": "o4SH1UtKANnW5N5D67oZUz",
|
|
||||||
"options": {
|
"options": {
|
||||||
"labels": { "button": "Send", "placeholder": "Type your email..." },
|
"variableId": "3VFChNVSCXQ2rXv4DrJ8Ah",
|
||||||
"variableId": "3VFChNVSCXQ2rXv4DrJ8Ah"
|
"labels": { "placeholder": "Type your email...", "button": "Send" }
|
||||||
},
|
}
|
||||||
"outgoingEdgeId": "w3MiN1Ct38jT5NykVsgmb5"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Email",
|
|
||||||
"graphCoordinates": { "x": 669, "y": 141 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
|
"id": "q5dAhqSTCaNdiGSJm9B9Rw",
|
||||||
|
"title": "Name",
|
||||||
|
"graphCoordinates": { "x": 340, "y": 143 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "sgtE2Sy7cKykac9B223Kq9R",
|
"id": "sgtE2Sy7cKykac9B223Kq9R",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
{ "type": "p", "children": [{ "text": "What's your name?" }] }
|
||||||
@ -126,29 +106,27 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "sqEsMo747LTDnY9FjQcEwUv",
|
"id": "sqEsMo747LTDnY9FjQcEwUv",
|
||||||
|
"outgoingEdgeId": "4tYbERpi5Po4goVgt6rWXg",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw",
|
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
|
||||||
"labels": {
|
"labels": {
|
||||||
"button": "Send",
|
"placeholder": "Type your answer...",
|
||||||
"placeholder": "Type your answer..."
|
"button": "Send"
|
||||||
},
|
},
|
||||||
"variableId": "giiLFGw5xXBCHzvp1qAbdX"
|
"variableId": "giiLFGw5xXBCHzvp1qAbdX",
|
||||||
},
|
"isLong": false
|
||||||
"outgoingEdgeId": "4tYbERpi5Po4goVgt6rWXg"
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Name",
|
|
||||||
"graphCoordinates": { "x": 340, "y": 143 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fKqRz7iswk7ULaj5PJocZL",
|
"id": "fKqRz7iswk7ULaj5PJocZL",
|
||||||
|
"title": "Services",
|
||||||
|
"graphCoordinates": { "x": 1002, "y": 144 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "su7HceVXWyTCzi2vv3m4QbK",
|
"id": "su7HceVXWyTCzi2vv3m4QbK",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "fKqRz7iswk7ULaj5PJocZL",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -160,48 +138,26 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "s5VQGsVF4hQgziQsXVdwPDW",
|
"id": "s5VQGsVF4hQgziQsXVdwPDW",
|
||||||
|
"outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk",
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{ "id": "fnLCBF4NdraSwcubnBhk8H", "content": "Website dev" },
|
||||||
"id": "fnLCBF4NdraSwcubnBhk8H",
|
{ "id": "a782h8ynMouY84QjH7XSnR", "content": "Content Marketing" },
|
||||||
"type": 0,
|
{ "id": "jGvh94zBByvVFpSS3w97zY", "content": "Social Media" },
|
||||||
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
|
{ "id": "6PRLbKUezuFmwWtLVbvAQ7", "content": "UI / UX Design" }
|
||||||
"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"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"groupId": "fKqRz7iswk7ULaj5PJocZL",
|
"options": { "isMultipleChoice": true, "buttonLabel": "Send" }
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": true },
|
|
||||||
"outgoingEdgeId": "ohTRakmcYJ7GdFWRZrWRjk"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Services",
|
|
||||||
"graphCoordinates": { "x": 1002, "y": 144 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "7qHBEyCMvKEJryBHzPmHjV",
|
"id": "7qHBEyCMvKEJryBHzPmHjV",
|
||||||
|
"title": "Additional information",
|
||||||
|
"graphCoordinates": { "x": 1337, "y": 145 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "sqR8Sz9gW21aUYKtUikq7qZ",
|
"id": "sqR8Sz9gW21aUYKtUikq7qZ",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "7qHBEyCMvKEJryBHzPmHjV",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -215,33 +171,34 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "sqFy2G3C1mh9p6s3QBdSS5x",
|
"id": "sqFy2G3C1mh9p6s3QBdSS5x",
|
||||||
|
"outgoingEdgeId": "sH5nUssG2XQbm6ZidGv9BY",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "7qHBEyCMvKEJryBHzPmHjV",
|
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": true,
|
"labels": {
|
||||||
"labels": { "button": "Send", "placeholder": "Type your answer..." }
|
"placeholder": "Type your answer...",
|
||||||
},
|
"button": "Send"
|
||||||
"outgoingEdgeId": "sH5nUssG2XQbm6ZidGv9BY"
|
},
|
||||||
|
"isLong": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Additional information",
|
|
||||||
"graphCoordinates": { "x": 1337, "y": 145 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "vF7AD7zSAj7SNvN3gr9N94",
|
"id": "vF7AD7zSAj7SNvN3gr9N94",
|
||||||
|
"title": "Bye",
|
||||||
|
"graphCoordinates": { "x": 1668, "y": 143 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "seLegenCgUwMopRFeAefqZ7",
|
"id": "seLegenCgUwMopRFeAefqZ7",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [{ "type": "p", "children": [{ "text": "Perfect!" }] }]
|
"richText": [{ "type": "p", "children": [{ "text": "Perfect!" }] }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "s779Q1y51aVaDUJVrFb16vv",
|
"id": "s779Q1y51aVaDUJVrFb16vv",
|
||||||
|
"outgoingEdgeId": "fTVo43AG97eKcaTrZf9KyV",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
|
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{
|
{
|
||||||
@ -249,110 +206,107 @@
|
|||||||
"children": [{ "text": "We'll get back to you at {{Email}}" }]
|
"children": [{ "text": "We'll get back to you at {{Email}}" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"outgoingEdgeId": "fTVo43AG97eKcaTrZf9KyV"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"title": "Bye",
|
|
||||||
"graphCoordinates": { "x": 1668, "y": 143 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "webhookGroup",
|
"id": "webhookGroup",
|
||||||
"graphCoordinates": { "x": 1996, "y": 134 },
|
|
||||||
"title": "Webhook",
|
"title": "Webhook",
|
||||||
|
"graphCoordinates": { "x": 1996, "y": 134 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "webhookBlock",
|
"id": "webhookBlock",
|
||||||
"groupId": "webhookGroup",
|
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": { "responseVariableMapping": [], "variablesForTest": [] },
|
"options": {
|
||||||
"webhookId": "webhook1"
|
"variablesForTest": [],
|
||||||
|
"responseVariableMapping": [],
|
||||||
|
"webhook": { "method": "POST" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variables": [
|
|
||||||
{ "id": "giiLFGw5xXBCHzvp1qAbdX", "name": "Name" },
|
|
||||||
{ "id": "3VFChNVSCXQ2rXv4DrJ8Ah", "name": "Email" }
|
|
||||||
],
|
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "oNvqaqNExdSH2kKEhKZHuE",
|
"id": "oNvqaqNExdSH2kKEhKZHuE",
|
||||||
"to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" },
|
"from": { "eventId": "k6kY6gwRE6noPoYQNGzgUq" },
|
||||||
"from": {
|
"to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" }
|
||||||
"blockId": "22HP69iipkLjJDTUcc1AWW",
|
|
||||||
"groupId": "k6kY6gwRE6noPoYQNGzgUq"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "i51YhHpk1dtSyduFNf5Wim",
|
"id": "i51YhHpk1dtSyduFNf5Wim",
|
||||||
"to": { "groupId": "q5dAhqSTCaNdiGSJm9B9Rw" },
|
"from": { "blockId": "sbjZWLJGVkHAkDqS4JQeGow" },
|
||||||
"from": {
|
"to": { "groupId": "q5dAhqSTCaNdiGSJm9B9Rw" }
|
||||||
"blockId": "sbjZWLJGVkHAkDqS4JQeGow",
|
|
||||||
"groupId": "kinRXxYop2X4d7F9qt8WNB"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "4tYbERpi5Po4goVgt6rWXg",
|
"id": "4tYbERpi5Po4goVgt6rWXg",
|
||||||
"to": { "groupId": "o4SH1UtKANnW5N5D67oZUz" },
|
"from": { "blockId": "sqEsMo747LTDnY9FjQcEwUv" },
|
||||||
"from": {
|
"to": { "groupId": "o4SH1UtKANnW5N5D67oZUz" }
|
||||||
"blockId": "sqEsMo747LTDnY9FjQcEwUv",
|
|
||||||
"groupId": "q5dAhqSTCaNdiGSJm9B9Rw"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "w3MiN1Ct38jT5NykVsgmb5",
|
"id": "w3MiN1Ct38jT5NykVsgmb5",
|
||||||
"to": { "groupId": "fKqRz7iswk7ULaj5PJocZL" },
|
"from": { "blockId": "snbsad18Bgry8yZ8DZCfdFD" },
|
||||||
"from": {
|
"to": { "groupId": "fKqRz7iswk7ULaj5PJocZL" }
|
||||||
"blockId": "snbsad18Bgry8yZ8DZCfdFD",
|
|
||||||
"groupId": "o4SH1UtKANnW5N5D67oZUz"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ohTRakmcYJ7GdFWRZrWRjk",
|
"id": "ohTRakmcYJ7GdFWRZrWRjk",
|
||||||
"to": { "groupId": "7qHBEyCMvKEJryBHzPmHjV" },
|
"from": { "blockId": "s5VQGsVF4hQgziQsXVdwPDW" },
|
||||||
"from": {
|
"to": { "groupId": "7qHBEyCMvKEJryBHzPmHjV" }
|
||||||
"blockId": "s5VQGsVF4hQgziQsXVdwPDW",
|
|
||||||
"groupId": "fKqRz7iswk7ULaj5PJocZL"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "sH5nUssG2XQbm6ZidGv9BY",
|
"id": "sH5nUssG2XQbm6ZidGv9BY",
|
||||||
"to": { "groupId": "vF7AD7zSAj7SNvN3gr9N94" },
|
"from": { "blockId": "sqFy2G3C1mh9p6s3QBdSS5x" },
|
||||||
"from": {
|
"to": { "groupId": "vF7AD7zSAj7SNvN3gr9N94" }
|
||||||
"blockId": "sqFy2G3C1mh9p6s3QBdSS5x",
|
|
||||||
"groupId": "7qHBEyCMvKEJryBHzPmHjV"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "fTVo43AG97eKcaTrZf9KyV",
|
||||||
"groupId": "vF7AD7zSAj7SNvN3gr9N94",
|
"from": { "blockId": "s779Q1y51aVaDUJVrFb16vv" },
|
||||||
"blockId": "s779Q1y51aVaDUJVrFb16vv"
|
"to": { "groupId": "webhookGroup" }
|
||||||
},
|
}
|
||||||
"to": { "groupId": "webhookGroup" },
|
],
|
||||||
"id": "fTVo43AG97eKcaTrZf9KyV"
|
"variables": [
|
||||||
|
{
|
||||||
|
"id": "giiLFGw5xXBCHzvp1qAbdX",
|
||||||
|
"name": "Name",
|
||||||
|
"isSessionVariable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3VFChNVSCXQ2rXv4DrJ8Ah",
|
||||||
|
"name": "Email",
|
||||||
|
"isSessionVariable": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } },
|
||||||
"chat": {
|
"chat": {
|
||||||
|
"hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
|
||||||
|
"guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
|
||||||
|
"buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"color": "#303235",
|
|
||||||
"backgroundColor": "#FFFFFF",
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"color": "#303235",
|
||||||
"placeholderColor": "#9095A0"
|
"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": {
|
"settings": {
|
||||||
"general": { "isBrandingEnabled": true },
|
"general": { "isBrandingEnabled": true },
|
||||||
|
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||||
"metadata": {
|
"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."
|
"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,
|
"publicId": null,
|
||||||
"customDomain": null
|
"customDomain": null,
|
||||||
|
"workspaceId": "proWorkspace",
|
||||||
|
"resultsTablePreferences": null,
|
||||||
|
"isArchived": false,
|
||||||
|
"isClosed": false,
|
||||||
|
"whatsAppCredentialsId": null,
|
||||||
|
"riskLevel": null
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,7 @@
|
|||||||
"variablesForTest": [],
|
"variablesForTest": [],
|
||||||
"isAdvancedConfig": false,
|
"isAdvancedConfig": false,
|
||||||
"isCustomBody": false
|
"isCustomBody": false
|
||||||
},
|
}
|
||||||
"webhookId": "webhook1"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,23 @@
|
|||||||
{
|
{
|
||||||
"id": "ckz8gli9e9842no1afuppdn0z",
|
"version": "6",
|
||||||
"createdAt": "2022-02-04T13:44:30.386Z",
|
"id": "clyoe6owl0003grw9k9hzc9qs",
|
||||||
"updatedAt": "2022-02-04T13:44:30.386Z",
|
|
||||||
"name": "My typebot",
|
"name": "My typebot",
|
||||||
"folderId": null,
|
"events": [
|
||||||
"groups": [
|
|
||||||
{
|
{
|
||||||
"id": "p6GeeRXHgwiJeoJRBkKaMJ",
|
"id": "p6GeeRXHgwiJeoJRBkKaMJ",
|
||||||
"blocks": [
|
"outgoingEdgeId": "cyEJPaLU7AchnBSaeWoyiS",
|
||||||
{
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
"id": "iDS7jFemUsQ7Sp3eu3xg3w",
|
"type": "start"
|
||||||
"type": "start",
|
}
|
||||||
"label": "Start",
|
],
|
||||||
"groupId": "p6GeeRXHgwiJeoJRBkKaMJ",
|
"groups": [
|
||||||
"outgoingEdgeId": "cyEJPaLU7AchnBSaeWoyiS"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Start",
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "kBneEpKdMYrF65XxUQ5GS7",
|
"id": "kBneEpKdMYrF65XxUQ5GS7",
|
||||||
"graphCoordinates": { "x": 260, "y": 186 },
|
|
||||||
"title": "Group #1",
|
"title": "Group #1",
|
||||||
|
"graphCoordinates": { "x": 260, "y": 186 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "skSkZ4PNP7m1gYvu9Ew6ngM",
|
"id": "skSkZ4PNP7m1gYvu9Ew6ngM",
|
||||||
"groupId": "kBneEpKdMYrF65XxUQ5GS7",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }]
|
"richText": [{ "type": "p", "children": [{ "text": "Ready?" }] }]
|
||||||
@ -34,85 +25,89 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "sh6ZVRA3o72y6BEiNKVcoma",
|
"id": "sh6ZVRA3o72y6BEiNKVcoma",
|
||||||
"groupId": "kBneEpKdMYrF65XxUQ5GS7",
|
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "rr5mKKBPq73ZrfXZ3uuupz",
|
"id": "rr5mKKBPq73ZrfXZ3uuupz",
|
||||||
"blockId": "sh6ZVRA3o72y6BEiNKVcoma",
|
"outgoingEdgeId": "1sLicz8gq2QxytFTwBd8ac",
|
||||||
"type": 0,
|
"content": "Go"
|
||||||
"content": "Go",
|
|
||||||
"outgoingEdgeId": "1sLicz8gq2QxytFTwBd8ac"
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"options": { "isMultipleChoice": false, "buttonLabel": "Send" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "8XnDM1QsqPms4LQHh8q3Jo",
|
"id": "8XnDM1QsqPms4LQHh8q3Jo",
|
||||||
"graphCoordinates": { "x": 646, "y": 511 },
|
|
||||||
"title": "Group #2",
|
"title": "Group #2",
|
||||||
|
"graphCoordinates": { "x": 646, "y": 511 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "soSmiE7zyb3WF77GxFxAjYX",
|
"id": "soSmiE7zyb3WF77GxFxAjYX",
|
||||||
"groupId": "8XnDM1QsqPms4LQHh8q3Jo",
|
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": {
|
"options": {
|
||||||
"responseVariableMapping": [],
|
|
||||||
"variablesForTest": [],
|
"variablesForTest": [],
|
||||||
|
"responseVariableMapping": [],
|
||||||
"isAdvancedConfig": false,
|
"isAdvancedConfig": false,
|
||||||
"isCustomBody": false
|
"isCustomBody": false,
|
||||||
},
|
"webhook": { "method": "POST" }
|
||||||
"webhookId": "webhook1"
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "cyEJPaLU7AchnBSaeWoyiS",
|
||||||
|
"from": { "eventId": "p6GeeRXHgwiJeoJRBkKaMJ" },
|
||||||
|
"to": { "groupId": "kBneEpKdMYrF65XxUQ5GS7" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1sLicz8gq2QxytFTwBd8ac",
|
||||||
|
"from": {
|
||||||
|
"blockId": "sh6ZVRA3o72y6BEiNKVcoma",
|
||||||
|
"itemId": "rr5mKKBPq73ZrfXZ3uuupz"
|
||||||
|
},
|
||||||
|
"to": { "groupId": "8XnDM1QsqPms4LQHh8q3Jo" }
|
||||||
|
}
|
||||||
|
],
|
||||||
"variables": [
|
"variables": [
|
||||||
{ "id": "var1", "name": "secret 1" },
|
{ "id": "var1", "name": "secret 1" },
|
||||||
{ "id": "var2", "name": "secret 2" },
|
{ "id": "var2", "name": "secret 2" },
|
||||||
{ "id": "var3", "name": "secret 3" },
|
{ "id": "var3", "name": "secret 3" },
|
||||||
{ "id": "var4", "name": "secret 4" }
|
{ "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": {
|
"theme": {
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } },
|
||||||
"chat": {
|
"chat": {
|
||||||
|
"hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
|
||||||
|
"guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
|
||||||
|
"buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"color": "#303235",
|
|
||||||
"backgroundColor": "#FFFFFF",
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"color": "#303235",
|
||||||
"placeholderColor": "#9095A0"
|
"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": {
|
"settings": {
|
||||||
"general": { "isBrandingEnabled": true },
|
"general": { "isBrandingEnabled": true },
|
||||||
|
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||||
"metadata": {
|
"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."
|
"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
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
1
apps/viewer/.eslintignore
Normal file
1
apps/viewer/.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/test/reporters
|
@ -40,7 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@faire/mjml-react": "3.3.0",
|
"@faire/mjml-react": "3.3.0",
|
||||||
"@paralleldrive/cuid2": "2.2.1",
|
"@paralleldrive/cuid2": "2.2.1",
|
||||||
"@playwright/test": "1.43.1",
|
"@playwright/test": "1.45.2",
|
||||||
"@typebot.io/emails": "workspace:*",
|
"@typebot.io/emails": "workspace:*",
|
||||||
"@typebot.io/env": "workspace:*",
|
"@typebot.io/env": "workspace:*",
|
||||||
"@typebot.io/forge": "workspace:*",
|
"@typebot.io/forge": "workspace:*",
|
||||||
|
@ -10,13 +10,13 @@ export default defineConfig({
|
|||||||
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
|
timeout: process.env.CI ? 10 * 1000 : 5 * 1000,
|
||||||
},
|
},
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
workers: process.env.CI ? 1 : 3,
|
workers: process.env.CI ? 1 : 4,
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 1,
|
||||||
reporter: [
|
reporter: [
|
||||||
[process.env.CI ? 'github' : 'list'],
|
[process.env.CI ? 'github' : 'list'],
|
||||||
['html', { outputFolder: 'src/test/reporters' }],
|
['html', { outputFolder: 'src/test/reporters' }],
|
||||||
],
|
],
|
||||||
maxFailures: process.env.CI ? 10 : undefined,
|
maxFailures: 10,
|
||||||
webServer: process.env.CI
|
webServer: process.env.CI
|
||||||
? {
|
? {
|
||||||
command: 'pnpm run start',
|
command: 'pnpm run start',
|
||||||
|
@ -1,28 +1,20 @@
|
|||||||
{
|
{
|
||||||
"id": "chat-sub-bot",
|
"version": "6",
|
||||||
"createdAt": "2022-11-24T09:06:52.903Z",
|
"id": "clyoehfmp0007grw9ubdop6u0",
|
||||||
"updatedAt": "2022-11-24T09:13:16.782Z",
|
|
||||||
"icon": "👶",
|
|
||||||
"name": "Sub bot",
|
"name": "Sub bot",
|
||||||
"folderId": null,
|
"events": [
|
||||||
"groups": [
|
|
||||||
{
|
{
|
||||||
"id": "clauup2lh0002vs1a5ei32mmi",
|
"id": "clauup2lh0002vs1a5ei32mmi",
|
||||||
"title": "Start",
|
"outgoingEdgeId": "clauupl9n001b3b6qdk4czgom",
|
||||||
"blocks": [
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
{
|
"type": "start"
|
||||||
"id": "clauup2li0003vs1aas14fwpc",
|
}
|
||||||
"type": "start",
|
],
|
||||||
"label": "Start",
|
"groups": [
|
||||||
"groupId": "clauup2lh0002vs1a5ei32mmi",
|
|
||||||
"outgoingEdgeId": "clauupl9n001b3b6qdk4czgom"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "clauupd6q00183b6qcm8qbz62",
|
"id": "clauupd6q00183b6qcm8qbz62",
|
||||||
"title": "Group #1",
|
"title": "Group #1",
|
||||||
|
"graphCoordinates": { "x": 375.36328125, "y": 167.2578125 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauupd6q00193b6qhegmlnxj",
|
"id": "clauupd6q00193b6qhegmlnxj",
|
||||||
@ -36,69 +28,69 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauupd6q00183b6qcm8qbz62"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauupk97001a3b6q2w9qqkec",
|
"id": "clauupk97001a3b6q2w9qqkec",
|
||||||
"type": "rating input",
|
"type": "rating input",
|
||||||
"groupId": "clauupd6q00183b6qcm8qbz62",
|
|
||||||
"options": {
|
"options": {
|
||||||
"labels": { "button": "Send" },
|
|
||||||
"length": 10,
|
|
||||||
"buttonType": "Numbers",
|
"buttonType": "Numbers",
|
||||||
|
"length": 10,
|
||||||
|
"labels": { "button": "Send" },
|
||||||
"customIcon": { "isEnabled": false }
|
"customIcon": { "isEnabled": false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 375.36328125, "y": 167.2578125 }
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variables": [],
|
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "clauupl9n001b3b6qdk4czgom",
|
"id": "clauupl9n001b3b6qdk4czgom",
|
||||||
"to": { "groupId": "clauupd6q00183b6qcm8qbz62" },
|
"from": { "eventId": "clauup2lh0002vs1a5ei32mmi" },
|
||||||
"from": {
|
"to": { "groupId": "clauupd6q00183b6qcm8qbz62" }
|
||||||
"blockId": "clauup2li0003vs1aas14fwpc",
|
|
||||||
"groupId": "clauup2lh0002vs1a5ei32mmi"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"variables": [],
|
||||||
"theme": {
|
"theme": {
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } },
|
||||||
"chat": {
|
"chat": {
|
||||||
"inputs": {
|
|
||||||
"color": "#303235",
|
|
||||||
"backgroundColor": "#FFFFFF",
|
|
||||||
"placeholderColor": "#9095A0"
|
|
||||||
},
|
|
||||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
|
||||||
"hostAvatar": {
|
"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" },
|
"hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
|
||||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
"guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
|
||||||
},
|
"buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
|
||||||
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
"inputs": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"color": "#303235",
|
||||||
|
"placeholderColor": "#9095A0"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"selectedThemeTemplateId": null,
|
||||||
"settings": {
|
"settings": {
|
||||||
"general": {
|
"general": {
|
||||||
"isBrandingEnabled": false,
|
"isBrandingEnabled": false,
|
||||||
"isInputPrefillEnabled": true,
|
"isInputPrefillEnabled": true,
|
||||||
"isResultSavingEnabled": true,
|
|
||||||
"isHideQueryParamsEnabled": true,
|
"isHideQueryParamsEnabled": true,
|
||||||
"isNewResultOnRefreshEnabled": false
|
"isNewResultOnRefreshEnabled": false
|
||||||
},
|
},
|
||||||
|
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||||
"metadata": {
|
"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."
|
"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,
|
"publicId": null,
|
||||||
"customDomain": null,
|
"customDomain": null,
|
||||||
"workspaceId": "proWorkspace",
|
"workspaceId": "proWorkspace",
|
||||||
"resultsTablePreferences": null,
|
"resultsTablePreferences": null,
|
||||||
"isArchived": false,
|
"isArchived": false,
|
||||||
"isClosed": false
|
"isClosed": false,
|
||||||
|
"whatsAppCredentialsId": null,
|
||||||
|
"riskLevel": null
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,20 @@
|
|||||||
{
|
{
|
||||||
"id": "clauujawp00011avs2vj97zma",
|
"version": "6",
|
||||||
"createdAt": "2022-11-24T09:02:23.737Z",
|
"id": "clyoegjca0005grw9ek6h984v",
|
||||||
"updatedAt": "2022-11-24T09:12:57.036Z",
|
|
||||||
"icon": "🤖",
|
|
||||||
"name": "Complete bot",
|
"name": "Complete bot",
|
||||||
"folderId": null,
|
"events": [
|
||||||
"groups": [
|
|
||||||
{
|
{
|
||||||
"id": "clauujawn0000vs1a8z6k2k7d",
|
"id": "clauujawn0000vs1a8z6k2k7d",
|
||||||
"title": "Start",
|
"outgoingEdgeId": "clauuk4o300083b6q7b2iowv3",
|
||||||
"blocks": [
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
{
|
"type": "start"
|
||||||
"id": "clauujawn0001vs1a0mk8docp",
|
}
|
||||||
"type": "start",
|
],
|
||||||
"label": "Start",
|
"groups": [
|
||||||
"groupId": "clauujawn0000vs1a8z6k2k7d",
|
|
||||||
"outgoingEdgeId": "clauuk4o300083b6q7b2iowv3"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "clauujxdc00063b6q42ca20gj",
|
"id": "clauujxdc00063b6q42ca20gj",
|
||||||
"title": "Welcome",
|
"title": "Welcome",
|
||||||
|
"graphCoordinates": { "x": 5.81640625, "y": 172.359375 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauujxdd00073b6qpejnkzcy",
|
"id": "clauujxdd00073b6qpejnkzcy",
|
||||||
@ -31,8 +23,7 @@
|
|||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Hi there! 👋" }] }
|
{ "type": "p", "children": [{ "text": "Hi there! 👋" }] }
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauujxdc00063b6q42ca20gj"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauukaad00093b6q07av51yc",
|
"id": "clauukaad00093b6q07av51yc",
|
||||||
@ -44,29 +35,27 @@
|
|||||||
"children": [{ "text": "Welcome. What's your name?" }]
|
"children": [{ "text": "Welcome. What's your name?" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauujxdc00063b6q42ca20gj"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauukip8000a3b6qtzl288tu",
|
"id": "clauukip8000a3b6qtzl288tu",
|
||||||
|
"outgoingEdgeId": "clauul0sk000f3b6q2tvy5wfi",
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"groupId": "clauujxdc00063b6q42ca20gj",
|
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
|
||||||
"labels": {
|
"labels": {
|
||||||
"button": "Send",
|
"placeholder": "Type your answer...",
|
||||||
"placeholder": "Type your answer..."
|
"button": "Send"
|
||||||
},
|
},
|
||||||
"variableId": "vclauuklnc000b3b6q7xchq4yf"
|
"variableId": "vclauuklnc000b3b6q7xchq4yf",
|
||||||
},
|
"isLong": false
|
||||||
"outgoingEdgeId": "clauul0sk000f3b6q2tvy5wfi"
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 5.81640625, "y": 172.359375 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauukoka000c3b6qe6chawis",
|
"id": "clauukoka000c3b6qe6chawis",
|
||||||
"title": "Age",
|
"title": "Age",
|
||||||
|
"graphCoordinates": { "x": 361.17578125, "y": 170.10546875 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauukoka000d3b6qxqi38cmk",
|
"id": "clauukoka000d3b6qxqi38cmk",
|
||||||
@ -78,16 +67,14 @@
|
|||||||
"children": [{ "text": "Nice to meet you {{Name}}" }]
|
"children": [{ "text": "Nice to meet you {{Name}}" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauukoka000c3b6qe6chawis"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuku5o000e3b6q90rm30p1",
|
"id": "clauuku5o000e3b6q90rm30p1",
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"content": {
|
"content": {
|
||||||
"url": "https://media2.giphy.com/media/l0MYGb1LuZ3n7dRnO/giphy-downsized.gif?cid=fe3852a3yd2leg4yi8iual3wgyw893zzocuuqlp3wytt802h&rid=giphy-downsized.gif&ct=g"
|
"url": "https://media2.giphy.com/media/l0MYGb1LuZ3n7dRnO/giphy-downsized.gif?cid=fe3852a3yd2leg4yi8iual3wgyw893zzocuuqlp3wytt802h&rid=giphy-downsized.gif&ct=g"
|
||||||
},
|
}
|
||||||
"groupId": "clauukoka000c3b6qe6chawis"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauul4vg000g3b6qr0q2h0uy",
|
"id": "clauul4vg000g3b6qr0q2h0uy",
|
||||||
@ -96,60 +83,56 @@
|
|||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "How old are you?" }] }
|
{ "type": "p", "children": [{ "text": "How old are you?" }] }
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauukoka000c3b6qe6chawis"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauul90j000h3b6qjfrw9js4",
|
"id": "clauul90j000h3b6qjfrw9js4",
|
||||||
|
"outgoingEdgeId": "clauum41j000n3b6qpqu12icm",
|
||||||
"type": "number input",
|
"type": "number input",
|
||||||
"groupId": "clauukoka000c3b6qe6chawis",
|
|
||||||
"options": {
|
"options": {
|
||||||
"labels": { "button": "Send", "placeholder": "Type a number..." },
|
"variableId": "vclauulfjk000i3b6qmujooweu",
|
||||||
"variableId": "vclauulfjk000i3b6qmujooweu"
|
"labels": { "placeholder": "Type a number...", "button": "Send" }
|
||||||
},
|
}
|
||||||
"outgoingEdgeId": "clauum41j000n3b6qpqu12icm"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 361.17578125, "y": 170.10546875 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauulhqf000j3b6qm8y5oifc",
|
"id": "clauulhqf000j3b6qm8y5oifc",
|
||||||
"title": "Is major?",
|
"title": "Is major?",
|
||||||
|
"graphCoordinates": { "x": 726.2265625, "y": 240.80078125 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauulhqf000k3b6qsrc1hd74",
|
"id": "clauulhqf000k3b6qsrc1hd74",
|
||||||
|
"outgoingEdgeId": "clauumm5v000t3b6qu62qcft8",
|
||||||
"type": "Condition",
|
"type": "Condition",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "clauulhqg000l3b6qaxn4qli5",
|
"id": "clauulhqg000l3b6qaxn4qli5",
|
||||||
"type": 1,
|
"outgoingEdgeId": "clauumi0x000q3b6q9bwkqnmr",
|
||||||
"blockId": "clauulhqf000k3b6qsrc1hd74",
|
|
||||||
"content": {
|
"content": {
|
||||||
|
"logicalOperator": "AND",
|
||||||
"comparisons": [
|
"comparisons": [
|
||||||
{
|
{
|
||||||
"id": "clauuliyn000m3b6q10gwx8ii",
|
"id": "clauuliyn000m3b6q10gwx8ii",
|
||||||
"value": "21",
|
|
||||||
"variableId": "vclauulfjk000i3b6qmujooweu",
|
"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",
|
"id": "clauum8x7000o3b6qx8hqduf8",
|
||||||
"title": "Group #4",
|
"title": "Group #4",
|
||||||
|
"graphCoordinates": { "x": 1073.38671875, "y": 232.25 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauum8x7000p3b6qxjud5hdc",
|
"id": "clauum8x7000p3b6qxjud5hdc",
|
||||||
|
"outgoingEdgeId": "clauuom2y000y3b6qkcjy2ri7",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -158,39 +141,35 @@
|
|||||||
"children": [{ "text": "Ok, you are an adult then 😁" }]
|
"children": [{ "text": "Ok, you are an adult then 😁" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauum8x7000o3b6qx8hqduf8",
|
|
||||||
"outgoingEdgeId": "clauuom2y000y3b6qkcjy2ri7"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 1073.38671875, "y": 232.25 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauumjq4000r3b6q8l6bi9ra",
|
"id": "clauumjq4000r3b6q8l6bi9ra",
|
||||||
"title": "Group #4 copy",
|
"title": "Group #4 copy",
|
||||||
|
"graphCoordinates": { "x": 1073.984375, "y": 408.6875 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauumjq5000s3b6qqjhrklv4",
|
"id": "clauumjq5000s3b6qqjhrklv4",
|
||||||
|
"outgoingEdgeId": "clauuol8t000x3b6qcw1few70",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
{ "type": "p", "children": [{ "text": "Oh, you are a kid 😁" }] }
|
{ "type": "p", "children": [{ "text": "Oh, you are a kid 😁" }] }
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauumjq4000r3b6q8l6bi9ra",
|
|
||||||
"outgoingEdgeId": "clauuol8t000x3b6qcw1few70"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 1073.984375, "y": 408.6875 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuoekh000u3b6q6zmlx7f9",
|
"id": "clauuoekh000u3b6q6zmlx7f9",
|
||||||
"title": "Magic number",
|
"title": "Magic number",
|
||||||
|
"graphCoordinates": { "x": 1465.359375, "y": 299.25390625 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauuoeki000v3b6qvsh7kde1",
|
"id": "clauuoeki000v3b6qvsh7kde1",
|
||||||
"type": "Set variable",
|
"type": "Set variable",
|
||||||
"groupId": "clauuoekh000u3b6q6zmlx7f9",
|
|
||||||
"options": {
|
"options": {
|
||||||
"variableId": "vclauuohyp000w3b6qbqrs6c6w",
|
"variableId": "vclauuohyp000w3b6qbqrs6c6w",
|
||||||
"expressionToEvaluate": "42"
|
"expressionToEvaluate": "42"
|
||||||
@ -198,6 +177,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuontu000z3b6q3ydx6ao1",
|
"id": "clauuontu000z3b6q3ydx6ao1",
|
||||||
|
"outgoingEdgeId": "clauuq8je001e3b6qksm4j11g",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -206,38 +186,33 @@
|
|||||||
"children": [{ "text": "My magic number is {{Magic number}}" }]
|
"children": [{ "text": "My magic number is {{Magic number}}" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"groupId": "clauuoekh000u3b6q6zmlx7f9",
|
|
||||||
"outgoingEdgeId": "clauuq8je001e3b6qksm4j11g"
|
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"graphCoordinates": { "x": 1465.359375, "y": 299.25390625 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuq2l6001c3b6qpmq3ivwk",
|
"id": "clauuq2l6001c3b6qpmq3ivwk",
|
||||||
"graphCoordinates": { "x": 1836.43359375, "y": 295.39453125 },
|
|
||||||
"title": "Rate the experience",
|
"title": "Rate the experience",
|
||||||
|
"graphCoordinates": { "x": 1836.43359375, "y": 295.39453125 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauuq2l6001d3b6qyltfcvgb",
|
"id": "clauuq2l6001d3b6qyltfcvgb",
|
||||||
"groupId": "clauuq2l6001c3b6qpmq3ivwk",
|
"outgoingEdgeId": "clauureo3001h3b6qk6epabxq",
|
||||||
"type": "Typebot link",
|
"type": "Typebot link",
|
||||||
"options": {
|
"options": {
|
||||||
"typebotId": "chat-sub-bot",
|
"typebotId": "chat-sub-bot",
|
||||||
"groupId": "clauupd6q00183b6qcm8qbz62"
|
"groupId": "clauupd6q00183b6qcm8qbz62"
|
||||||
},
|
}
|
||||||
"outgoingEdgeId": "clauureo3001h3b6qk6epabxq"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauur7od001f3b6qq140oe55",
|
"id": "clauur7od001f3b6qq140oe55",
|
||||||
"graphCoordinates": { "x": 2201.45703125, "y": 299.1328125 },
|
|
||||||
"title": "Multiple input in group",
|
"title": "Multiple input in group",
|
||||||
|
"graphCoordinates": { "x": 2201.45703125, "y": 299.1328125 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauur7od001g3b6qkoeij3f7",
|
"id": "clauur7od001g3b6qkoeij3f7",
|
||||||
"groupId": "clauur7od001f3b6qq140oe55",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -252,53 +227,39 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauurluf001i3b6qjf78puug",
|
"id": "clauurluf001i3b6qjf78puug",
|
||||||
"groupId": "clauur7od001f3b6qq140oe55",
|
|
||||||
"type": "email input",
|
"type": "email input",
|
||||||
"options": {
|
"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?"
|
"retryMessageContent": "This email doesn't seem to be valid. Can you type it again?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauurokp001j3b6qyrw7boca",
|
"id": "clauurokp001j3b6qyrw7boca",
|
||||||
"groupId": "clauur7od001f3b6qq140oe55",
|
|
||||||
"type": "url input",
|
"type": "url input",
|
||||||
"options": {
|
"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?"
|
"retryMessageContent": "This URL doesn't seem to be valid. Can you type it again?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauurs1o001k3b6qgrj0xf59",
|
"id": "clauurs1o001k3b6qgrj0xf59",
|
||||||
"groupId": "clauur7od001f3b6qq140oe55",
|
"outgoingEdgeId": "clauushy3001p3b6qqnyrxgtb",
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{ "id": "clauurs1o001l3b6qu9hr712h", "content": "Yes" },
|
||||||
"id": "clauurs1o001l3b6qu9hr712h",
|
{ "id": "clauuru6t001m3b6qp8vkt23l", "content": "No" }
|
||||||
"blockId": "clauurs1o001k3b6qgrj0xf59",
|
|
||||||
"type": 0,
|
|
||||||
"content": "Yes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "clauuru6t001m3b6qp8vkt23l",
|
|
||||||
"content": "No",
|
|
||||||
"blockId": "clauurs1o001k3b6qgrj0xf59",
|
|
||||||
"type": 0
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"outgoingEdgeId": "clauushy3001p3b6qqnyrxgtb"
|
"options": { "isMultipleChoice": false, "buttonLabel": "Send" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauusa9z001n3b6qys3xvz1l",
|
"id": "clauusa9z001n3b6qys3xvz1l",
|
||||||
"graphCoordinates": { "x": 2558.609375, "y": 297.078125 },
|
|
||||||
"title": "Get Chuck Norris joke",
|
"title": "Get Chuck Norris joke",
|
||||||
|
"graphCoordinates": { "x": 2558.609375, "y": 297.078125 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauusaa0001o3b6qgddldaen",
|
"id": "clauusaa0001o3b6qgddldaen",
|
||||||
"groupId": "clauusa9z001n3b6qys3xvz1l",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -308,7 +269,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauusrfh001q3b6q7xaapi4h",
|
"id": "clauusrfh001q3b6q7xaapi4h",
|
||||||
"groupId": "clauusa9z001n3b6qys3xvz1l",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -321,33 +281,31 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauut2nq001r3b6qi437ixc7",
|
"id": "clauut2nq001r3b6qi437ixc7",
|
||||||
"groupId": "clauusa9z001n3b6qys3xvz1l",
|
"outgoingEdgeId": "clauuwjq2001x3b6qciu53855",
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": {
|
"options": {
|
||||||
|
"variablesForTest": [],
|
||||||
"responseVariableMapping": [
|
"responseVariableMapping": [
|
||||||
{
|
{
|
||||||
"id": "clauuvvdr001t3b6qqdxzc057",
|
"id": "clauuvvdr001t3b6qqdxzc057",
|
||||||
"bodyPath": "data.value",
|
"variableId": "vclauuwchv001u3b6qepx6e0a9",
|
||||||
"variableId": "vclauuwchv001u3b6qepx6e0a9"
|
"bodyPath": "data.value"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variablesForTest": [],
|
|
||||||
"isAdvancedConfig": true,
|
"isAdvancedConfig": true,
|
||||||
"isCustomBody": false
|
"isCustomBody": false,
|
||||||
},
|
"webhook": { "method": "POST" }
|
||||||
"webhookId": "chat-webhook-id",
|
}
|
||||||
"outgoingEdgeId": "clauuwjq2001x3b6qciu53855"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuwhyl001v3b6qarbpiqbv",
|
"id": "clauuwhyl001v3b6qarbpiqbv",
|
||||||
"graphCoordinates": { "x": 2900.9609375, "y": 288.29296875 },
|
|
||||||
"title": "Display joke",
|
"title": "Display joke",
|
||||||
|
"graphCoordinates": { "x": 2900.9609375, "y": 288.29296875 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "clauuwhyl001w3b6q7ai0zeyt",
|
"id": "clauuwhyl001w3b6q7ai0zeyt",
|
||||||
"groupId": "clauuwhyl001v3b6qarbpiqbv",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [{ "type": "p", "children": [{ "text": "{{Joke}}" }] }]
|
"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": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "clauuk4o300083b6q7b2iowv3",
|
"id": "clauuk4o300083b6q7b2iowv3",
|
||||||
"to": { "groupId": "clauujxdc00063b6q42ca20gj" },
|
"from": { "eventId": "clauujawn0000vs1a8z6k2k7d" },
|
||||||
"from": {
|
"to": { "groupId": "clauujxdc00063b6q42ca20gj" }
|
||||||
"blockId": "clauujawn0001vs1a0mk8docp",
|
|
||||||
"groupId": "clauujawn0000vs1a8z6k2k7d"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauul0sk000f3b6q2tvy5wfi",
|
"id": "clauul0sk000f3b6q2tvy5wfi",
|
||||||
"to": { "groupId": "clauukoka000c3b6qe6chawis" },
|
"from": { "blockId": "clauukip8000a3b6qtzl288tu" },
|
||||||
"from": {
|
"to": { "groupId": "clauukoka000c3b6qe6chawis" }
|
||||||
"blockId": "clauukip8000a3b6qtzl288tu",
|
|
||||||
"groupId": "clauujxdc00063b6q42ca20gj"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauum41j000n3b6qpqu12icm",
|
"id": "clauum41j000n3b6qpqu12icm",
|
||||||
"to": { "groupId": "clauulhqf000j3b6qm8y5oifc" },
|
"from": { "blockId": "clauul90j000h3b6qjfrw9js4" },
|
||||||
"from": {
|
"to": { "groupId": "clauulhqf000j3b6qm8y5oifc" }
|
||||||
"blockId": "clauul90j000h3b6qjfrw9js4",
|
|
||||||
"groupId": "clauukoka000c3b6qe6chawis"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauumi0x000q3b6q9bwkqnmr",
|
"id": "clauumi0x000q3b6q9bwkqnmr",
|
||||||
"to": { "groupId": "clauum8x7000o3b6qx8hqduf8" },
|
|
||||||
"from": {
|
"from": {
|
||||||
"itemId": "clauulhqg000l3b6qaxn4qli5",
|
|
||||||
"blockId": "clauulhqf000k3b6qsrc1hd74",
|
"blockId": "clauulhqf000k3b6qsrc1hd74",
|
||||||
"groupId": "clauulhqf000j3b6qm8y5oifc"
|
"itemId": "clauulhqg000l3b6qaxn4qli5"
|
||||||
}
|
},
|
||||||
|
"to": { "groupId": "clauum8x7000o3b6qx8hqduf8" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauumm5v000t3b6qu62qcft8",
|
"id": "clauumm5v000t3b6qu62qcft8",
|
||||||
"to": { "groupId": "clauumjq4000r3b6q8l6bi9ra" },
|
"from": { "blockId": "clauulhqf000k3b6qsrc1hd74" },
|
||||||
"from": {
|
"to": { "groupId": "clauumjq4000r3b6q8l6bi9ra" }
|
||||||
"blockId": "clauulhqf000k3b6qsrc1hd74",
|
|
||||||
"groupId": "clauulhqf000j3b6qm8y5oifc"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuol8t000x3b6qcw1few70",
|
"id": "clauuol8t000x3b6qcw1few70",
|
||||||
"to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" },
|
"from": { "blockId": "clauumjq5000s3b6qqjhrklv4" },
|
||||||
"from": {
|
"to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" }
|
||||||
"blockId": "clauumjq5000s3b6qqjhrklv4",
|
|
||||||
"groupId": "clauumjq4000r3b6q8l6bi9ra"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "clauuom2y000y3b6qkcjy2ri7",
|
"id": "clauuom2y000y3b6qkcjy2ri7",
|
||||||
"to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" },
|
"from": { "blockId": "clauum8x7000p3b6qxjud5hdc" },
|
||||||
"from": {
|
"to": { "groupId": "clauuoekh000u3b6q6zmlx7f9" }
|
||||||
"blockId": "clauum8x7000p3b6qxjud5hdc",
|
|
||||||
"groupId": "clauum8x7000o3b6qx8hqduf8"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "clauuq8je001e3b6qksm4j11g",
|
||||||
"groupId": "clauuoekh000u3b6q6zmlx7f9",
|
"from": { "blockId": "clauuontu000z3b6q3ydx6ao1" },
|
||||||
"blockId": "clauuontu000z3b6q3ydx6ao1"
|
"to": { "groupId": "clauuq2l6001c3b6qpmq3ivwk" }
|
||||||
},
|
|
||||||
"to": { "groupId": "clauuq2l6001c3b6qpmq3ivwk" },
|
|
||||||
"id": "clauuq8je001e3b6qksm4j11g"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "clauureo3001h3b6qk6epabxq",
|
||||||
"groupId": "clauuq2l6001c3b6qpmq3ivwk",
|
"from": { "blockId": "clauuq2l6001d3b6qyltfcvgb" },
|
||||||
"blockId": "clauuq2l6001d3b6qyltfcvgb"
|
"to": { "groupId": "clauur7od001f3b6qq140oe55" }
|
||||||
},
|
|
||||||
"to": { "groupId": "clauur7od001f3b6qq140oe55" },
|
|
||||||
"id": "clauureo3001h3b6qk6epabxq"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "clauushy3001p3b6qqnyrxgtb",
|
||||||
"groupId": "clauur7od001f3b6qq140oe55",
|
"from": { "blockId": "clauurs1o001k3b6qgrj0xf59" },
|
||||||
"blockId": "clauurs1o001k3b6qgrj0xf59"
|
"to": { "groupId": "clauusa9z001n3b6qys3xvz1l" }
|
||||||
},
|
|
||||||
"to": { "groupId": "clauusa9z001n3b6qys3xvz1l" },
|
|
||||||
"id": "clauushy3001p3b6qqnyrxgtb"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "clauuwjq2001x3b6qciu53855",
|
||||||
"groupId": "clauusa9z001n3b6qys3xvz1l",
|
"from": { "blockId": "clauut2nq001r3b6qi437ixc7" },
|
||||||
"blockId": "clauut2nq001r3b6qi437ixc7"
|
"to": { "groupId": "clauuwhyl001v3b6qarbpiqbv" }
|
||||||
},
|
|
||||||
"to": { "groupId": "clauuwhyl001v3b6qarbpiqbv" },
|
|
||||||
"id": "clauuwjq2001x3b6qciu53855"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme": {
|
"variables": [
|
||||||
"chat": {
|
{
|
||||||
"inputs": {
|
"id": "vclauuklnc000b3b6q7xchq4yf",
|
||||||
"color": "#303235",
|
"name": "Name",
|
||||||
"backgroundColor": "#FFFFFF",
|
"isSessionVariable": true
|
||||||
"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" }
|
|
||||||
},
|
},
|
||||||
"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": {
|
"settings": {
|
||||||
"general": {
|
"general": {
|
||||||
"isBrandingEnabled": false,
|
"isBrandingEnabled": false,
|
||||||
"isInputPrefillEnabled": true,
|
"isInputPrefillEnabled": true,
|
||||||
"isResultSavingEnabled": true,
|
|
||||||
"isHideQueryParamsEnabled": true,
|
"isHideQueryParamsEnabled": true,
|
||||||
"isNewResultOnRefreshEnabled": false
|
"isNewResultOnRefreshEnabled": false
|
||||||
},
|
},
|
||||||
|
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||||
"metadata": {
|
"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."
|
"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,
|
"publicId": null,
|
||||||
"customDomain": null,
|
"customDomain": null,
|
||||||
"workspaceId": "proWorkspace",
|
"workspaceId": "proWorkspace",
|
||||||
"resultsTablePreferences": null,
|
"resultsTablePreferences": null,
|
||||||
"isArchived": false,
|
"isArchived": false,
|
||||||
"isClosed": false
|
"isClosed": false,
|
||||||
|
"whatsAppCredentialsId": null,
|
||||||
|
"riskLevel": null
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
{
|
{
|
||||||
"version": "5",
|
"version": "6",
|
||||||
"id": "clnbugp6a00011ackz0k3zfkp",
|
"id": "clyoehs240009grw9vcxfw1ku",
|
||||||
"name": "My typebot",
|
"name": "My typebot",
|
||||||
"groups": [
|
"events": [
|
||||||
{
|
{
|
||||||
"id": "k2nokn9v0zyhae0wqcxsbqa7",
|
"id": "k2nokn9v0zyhae0wqcxsbqa7",
|
||||||
"title": "Start",
|
"outgoingEdgeId": "fj2ga89lctnuwcdsshwtxmhp",
|
||||||
"graphCoordinates": { "x": 0, "y": 0 },
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
"blocks": [
|
"type": "start"
|
||||||
{
|
}
|
||||||
"id": "sx4xmdbosubnxkhcg6x521p1",
|
],
|
||||||
"groupId": "k2nokn9v0zyhae0wqcxsbqa7",
|
"groups": [
|
||||||
"outgoingEdgeId": "fj2ga89lctnuwcdsshwtxmhp",
|
|
||||||
"type": "start",
|
|
||||||
"label": "Start"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "g8kdars2ahr3cyz2qf1f7w4i",
|
"id": "g8kdars2ahr3cyz2qf1f7w4i",
|
||||||
"title": "Group #1",
|
"title": "Group #1",
|
||||||
@ -24,7 +18,6 @@
|
|||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "prh6snup7cbmoxtf5vox8kjw",
|
"id": "prh6snup7cbmoxtf5vox8kjw",
|
||||||
"groupId": "g8kdars2ahr3cyz2qf1f7w4i",
|
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"options": {
|
"options": {
|
||||||
"labels": {
|
"labels": {
|
||||||
@ -36,7 +29,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dpyyb38amnwwl4q461el2uf6",
|
"id": "dpyyb38amnwwl4q461el2uf6",
|
||||||
"groupId": "g8kdars2ahr3cyz2qf1f7w4i",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -50,10 +42,7 @@
|
|||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"id": "fj2ga89lctnuwcdsshwtxmhp",
|
"id": "fj2ga89lctnuwcdsshwtxmhp",
|
||||||
"from": {
|
"from": { "eventId": "k2nokn9v0zyhae0wqcxsbqa7" },
|
||||||
"groupId": "k2nokn9v0zyhae0wqcxsbqa7",
|
|
||||||
"blockId": "sx4xmdbosubnxkhcg6x521p1"
|
|
||||||
},
|
|
||||||
"to": { "groupId": "g8kdars2ahr3cyz2qf1f7w4i" }
|
"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."
|
"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",
|
"createdAt": "2024-07-16T12:39:37.804Z",
|
||||||
"updatedAt": "2023-10-04T14:29:11.949Z",
|
"updatedAt": "2024-07-16T12:39:37.804Z",
|
||||||
"icon": null,
|
"icon": null,
|
||||||
"folderId": null,
|
"folderId": null,
|
||||||
"publicId": null,
|
"publicId": null,
|
||||||
@ -98,5 +87,6 @@
|
|||||||
"resultsTablePreferences": null,
|
"resultsTablePreferences": null,
|
||||||
"isArchived": false,
|
"isArchived": false,
|
||||||
"isClosed": false,
|
"isClosed": false,
|
||||||
"whatsAppCredentialsId": null
|
"whatsAppCredentialsId": null,
|
||||||
|
"riskLevel": null
|
||||||
}
|
}
|
||||||
|
@ -1,85 +1,61 @@
|
|||||||
{
|
{
|
||||||
"id": "cl9ip9u0l00001ad79a2lzm55",
|
"version": "6",
|
||||||
"createdAt": "2022-10-21T16:22:07.414Z",
|
"id": "clyoep429000dgrw904vfzaez",
|
||||||
"updatedAt": "2022-10-21T16:30:57.642Z",
|
|
||||||
"icon": null,
|
|
||||||
"name": "My typebot",
|
"name": "My typebot",
|
||||||
"folderId": null,
|
"events": [
|
||||||
"version": "4",
|
|
||||||
"groups": [
|
|
||||||
{
|
{
|
||||||
"id": "cl9ip9u0j0000d71a5d98gwni",
|
"id": "cl9ip9u0j0000d71a5d98gwni",
|
||||||
"title": "Start",
|
"outgoingEdgeId": "cl9ipkkb2001b3b6oh3vptq9k",
|
||||||
"blocks": [
|
"graphCoordinates": { "x": 0, "y": 0 },
|
||||||
{
|
"type": "start"
|
||||||
"id": "cl9ip9u0j0001d71a44dsd2p1",
|
}
|
||||||
"type": "start",
|
],
|
||||||
"label": "Start",
|
"groups": [
|
||||||
"groupId": "cl9ip9u0j0000d71a5d98gwni",
|
|
||||||
"outgoingEdgeId": "cl9ipkkb2001b3b6oh3vptq9k"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"graphCoordinates": { "x": 0, "y": 0 }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "cl9ipa38j00083b6o69e90m4t",
|
"id": "cl9ipa38j00083b6o69e90m4t",
|
||||||
"graphCoordinates": { "x": 340, "y": 341 },
|
|
||||||
"title": "Group #1",
|
"title": "Group #1",
|
||||||
|
"graphCoordinates": { "x": 340, "y": 341 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipaaut000a3b6ovrqlec3x",
|
"id": "cl9ipaaut000a3b6ovrqlec3x",
|
||||||
"groupId": "cl9ipa38j00083b6o69e90m4t",
|
|
||||||
"type": "text input",
|
"type": "text input",
|
||||||
"options": {
|
"options": {
|
||||||
"isLong": false,
|
"labels": { "placeholder": "Type a name...", "button": "Send" },
|
||||||
"labels": { "button": "Send", "placeholder": "Type a name..." },
|
"variableId": "vcl9ipajth000c3b6okl97r81j",
|
||||||
"variableId": "vcl9ipajth000c3b6okl97r81j"
|
"isLong": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipan8f000d3b6oo2ovi3ac",
|
"id": "cl9ipan8f000d3b6oo2ovi3ac",
|
||||||
"groupId": "cl9ipa38j00083b6o69e90m4t",
|
|
||||||
"type": "number input",
|
"type": "number input",
|
||||||
"options": {
|
"options": {
|
||||||
"labels": { "button": "Send", "placeholder": "Type an age..." },
|
"variableId": "vcl9ipaszl000e3b6ousjxuw7b",
|
||||||
"variableId": "vcl9ipaszl000e3b6ousjxuw7b"
|
"labels": { "placeholder": "Type an age...", "button": "Send" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipb08n000f3b6ok3mi2p48",
|
"id": "cl9ipb08n000f3b6ok3mi2p48",
|
||||||
"groupId": "cl9ipa38j00083b6o69e90m4t",
|
"outgoingEdgeId": "cl9ipcp83000o3b6odsn0a9a1",
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"options": {
|
|
||||||
"buttonLabel": "Send",
|
|
||||||
"isMultipleChoice": false,
|
|
||||||
"variableId": "vcl9ipg4tb00103b6oue08w3nm"
|
|
||||||
},
|
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{ "id": "cl9ipb08n000g3b6okr691uad", "content": "Male" },
|
||||||
"id": "cl9ipb08n000g3b6okr691uad",
|
{ "id": "cl9ipb2kk000h3b6oadwtonnz", "content": "Female" }
|
||||||
"blockId": "cl9ipb08n000f3b6ok3mi2p48",
|
|
||||||
"type": 0,
|
|
||||||
"content": "Male"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"blockId": "cl9ipb08n000f3b6ok3mi2p48",
|
|
||||||
"type": 0,
|
|
||||||
"id": "cl9ipb2kk000h3b6oadwtonnz",
|
|
||||||
"content": "Female"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"outgoingEdgeId": "cl9ipcp83000o3b6odsn0a9a1"
|
"options": {
|
||||||
|
"variableId": "vcl9ipg4tb00103b6oue08w3nm",
|
||||||
|
"isMultipleChoice": false,
|
||||||
|
"buttonLabel": "Send"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipbcjy000j3b6oqngo7luv",
|
"id": "cl9ipbcjy000j3b6oqngo7luv",
|
||||||
"graphCoordinates": { "x": 781, "y": 91 },
|
|
||||||
"title": "Group #2",
|
"title": "Group #2",
|
||||||
|
"graphCoordinates": { "x": 781, "y": 91 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipbl6l000m3b6o3evn41kv",
|
"id": "cl9ipbl6l000m3b6o3evn41kv",
|
||||||
"groupId": "cl9ipbcjy000j3b6oqngo7luv",
|
|
||||||
"type": "Set variable",
|
"type": "Set variable",
|
||||||
"options": {
|
"options": {
|
||||||
"variableId": "vcl9ipbokm000n3b6o06hvarrf",
|
"variableId": "vcl9ipbokm000n3b6o06hvarrf",
|
||||||
@ -88,9 +64,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipbcjy000k3b6oe8lta5c1",
|
"id": "cl9ipbcjy000k3b6oe8lta5c1",
|
||||||
"groupId": "cl9ipbcjy000j3b6oqngo7luv",
|
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": {
|
"options": {
|
||||||
|
"variablesForTest": [],
|
||||||
"responseVariableMapping": [
|
"responseVariableMapping": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipdspg000p3b6ognbfvmdx",
|
"id": "cl9ipdspg000p3b6ognbfvmdx",
|
||||||
@ -98,15 +74,17 @@
|
|||||||
"bodyPath": "data"
|
"bodyPath": "data"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variablesForTest": [],
|
|
||||||
"isAdvancedConfig": true,
|
"isAdvancedConfig": true,
|
||||||
"isCustomBody": true
|
"isCustomBody": true,
|
||||||
},
|
"webhook": {
|
||||||
"webhookId": "full-body-webhook"
|
"url": "http://localhost:3000/api/mock/webhook-easy-config",
|
||||||
|
"body": "{\n \"name\": \"{{Name}}\",\n \"age\": {{Age}},\n \"gender\": \"{{Gender}}\"\n }"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipe5t8000s3b6ocswre500",
|
"id": "cl9ipe5t8000s3b6ocswre500",
|
||||||
"groupId": "cl9ipbcjy000j3b6oqngo7luv",
|
"outgoingEdgeId": "cl9ipet83000z3b6of6zfqota",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -117,21 +95,20 @@
|
|||||||
{ "type": "p", "children": [{ "text": "" }] },
|
{ "type": "p", "children": [{ "text": "" }] },
|
||||||
{ "type": "p", "children": [{ "text": "{{Data}}" }] }
|
{ "type": "p", "children": [{ "text": "{{Data}}" }] }
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"outgoingEdgeId": "cl9ipet83000z3b6of6zfqota"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipej6b000u3b6oeaz305l6",
|
"id": "cl9ipej6b000u3b6oeaz305l6",
|
||||||
"graphCoordinates": { "x": 1138, "y": 85 },
|
|
||||||
"title": "Group #2 copy",
|
"title": "Group #2 copy",
|
||||||
|
"graphCoordinates": { "x": 1138, "y": 85 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipej6c000w3b6otzk247vl",
|
"id": "cl9ipej6c000w3b6otzk247vl",
|
||||||
"groupId": "cl9ipej6b000u3b6oeaz305l6",
|
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": {
|
"options": {
|
||||||
|
"variablesForTest": [],
|
||||||
"responseVariableMapping": [
|
"responseVariableMapping": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipdspg000p3b6ognbfvmdx",
|
"id": "cl9ipdspg000p3b6ognbfvmdx",
|
||||||
@ -139,15 +116,16 @@
|
|||||||
"bodyPath": "data"
|
"bodyPath": "data"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variablesForTest": [],
|
|
||||||
"isAdvancedConfig": true,
|
"isAdvancedConfig": true,
|
||||||
"isCustomBody": true
|
"isCustomBody": true,
|
||||||
},
|
"webhook": {
|
||||||
"webhookId": "partial-body-webhook"
|
"url": "http://localhost:3000/api/mock/webhook-easy-config",
|
||||||
|
"body": "{{Full body}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipej6c000y3b6oegzkgloq",
|
"id": "cl9ipej6c000y3b6oegzkgloq",
|
||||||
"groupId": "cl9ipej6b000u3b6oeaz305l6",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": {
|
"content": {
|
||||||
"richText": [
|
"richText": [
|
||||||
@ -164,97 +142,94 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipkaer00153b6ov230yuv2",
|
"id": "cl9ipkaer00153b6ov230yuv2",
|
||||||
"graphCoordinates": { "x": 333, "y": 26 },
|
|
||||||
"title": "Group #4",
|
"title": "Group #4",
|
||||||
|
"graphCoordinates": { "x": 333, "y": 26 },
|
||||||
"blocks": [
|
"blocks": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipkaer00163b6o0ohmmscn",
|
"id": "cl9ipkaer00163b6o0ohmmscn",
|
||||||
"groupId": "cl9ipkaer00153b6ov230yuv2",
|
|
||||||
"type": "choice input",
|
"type": "choice input",
|
||||||
"options": { "buttonLabel": "Send", "isMultipleChoice": false },
|
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "cl9ipkaer00173b6oxof4zrqn",
|
"id": "cl9ipkaer00173b6oxof4zrqn",
|
||||||
"blockId": "cl9ipkaer00163b6o0ohmmscn",
|
|
||||||
"type": 0,
|
|
||||||
"content": "Send failing webhook"
|
"content": "Send failing webhook"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"options": { "isMultipleChoice": false, "buttonLabel": "Send" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cl9ipki9u00193b6okmhudo0f",
|
"id": "cl9ipki9u00193b6okmhudo0f",
|
||||||
"groupId": "cl9ipkaer00153b6ov230yuv2",
|
"outgoingEdgeId": "cl9ipklm0001c3b6oy0a5nbhr",
|
||||||
"type": "Webhook",
|
"type": "Webhook",
|
||||||
"options": {
|
"options": {
|
||||||
"responseVariableMapping": [],
|
|
||||||
"variablesForTest": [],
|
"variablesForTest": [],
|
||||||
|
"responseVariableMapping": [],
|
||||||
"isAdvancedConfig": false,
|
"isAdvancedConfig": false,
|
||||||
"isCustomBody": false
|
"isCustomBody": false,
|
||||||
},
|
"webhook": { "url": "http://localhost:3001/api/mock/fail" }
|
||||||
"webhookId": "failing-webhook",
|
}
|
||||||
"outgoingEdgeId": "cl9ipklm0001c3b6oy0a5nbhr"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"variables": [
|
|
||||||
{ "id": "vcl9ipajth000c3b6okl97r81j", "name": "Name" },
|
|
||||||
{ "id": "vcl9ipaszl000e3b6ousjxuw7b", "name": "Age" },
|
|
||||||
{ "id": "vcl9ipbokm000n3b6o06hvarrf", "name": "Full body" },
|
|
||||||
{ "id": "vcl9ipdxnj000q3b6oy55th4xb", "name": "Data" },
|
|
||||||
{ "id": "vcl9ipg4tb00103b6oue08w3nm", "name": "Gender" }
|
|
||||||
],
|
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "cl9ipkkb2001b3b6oh3vptq9k",
|
||||||
"groupId": "cl9ipa38j00083b6o69e90m4t",
|
"from": { "eventId": "cl9ip9u0j0000d71a5d98gwni" },
|
||||||
"blockId": "cl9ipb08n000f3b6ok3mi2p48"
|
"to": { "groupId": "cl9ipkaer00153b6ov230yuv2" }
|
||||||
},
|
|
||||||
"to": { "groupId": "cl9ipbcjy000j3b6oqngo7luv" },
|
|
||||||
"id": "cl9ipcp83000o3b6odsn0a9a1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "cl9ipcp83000o3b6odsn0a9a1",
|
||||||
"groupId": "cl9ipbcjy000j3b6oqngo7luv",
|
"from": { "blockId": "cl9ipb08n000f3b6ok3mi2p48" },
|
||||||
"blockId": "cl9ipe5t8000s3b6ocswre500"
|
"to": { "groupId": "cl9ipbcjy000j3b6oqngo7luv" }
|
||||||
},
|
|
||||||
"to": { "groupId": "cl9ipej6b000u3b6oeaz305l6" },
|
|
||||||
"id": "cl9ipet83000z3b6of6zfqota"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "cl9ipet83000z3b6of6zfqota",
|
||||||
"groupId": "cl9ip9u0j0000d71a5d98gwni",
|
"from": { "blockId": "cl9ipe5t8000s3b6ocswre500" },
|
||||||
"blockId": "cl9ip9u0j0001d71a44dsd2p1"
|
"to": { "groupId": "cl9ipej6b000u3b6oeaz305l6" }
|
||||||
},
|
|
||||||
"to": { "groupId": "cl9ipkaer00153b6ov230yuv2" },
|
|
||||||
"id": "cl9ipkkb2001b3b6oh3vptq9k"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": {
|
"id": "cl9ipklm0001c3b6oy0a5nbhr",
|
||||||
"groupId": "cl9ipkaer00153b6ov230yuv2",
|
"from": { "blockId": "cl9ipki9u00193b6okmhudo0f" },
|
||||||
"blockId": "cl9ipki9u00193b6okmhudo0f"
|
"to": { "groupId": "cl9ipa38j00083b6o69e90m4t" }
|
||||||
},
|
}
|
||||||
"to": { "groupId": "cl9ipa38j00083b6o69e90m4t" },
|
],
|
||||||
"id": "cl9ipklm0001c3b6oy0a5nbhr"
|
"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": {
|
"theme": {
|
||||||
|
"general": { "font": "Open Sans", "background": { "type": "None" } },
|
||||||
"chat": {
|
"chat": {
|
||||||
"inputs": {
|
|
||||||
"color": "#303235",
|
|
||||||
"backgroundColor": "#FFFFFF",
|
|
||||||
"placeholderColor": "#9095A0"
|
|
||||||
},
|
|
||||||
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
|
|
||||||
"hostAvatar": {
|
"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" },
|
"hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
|
||||||
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
|
"guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
|
||||||
},
|
"buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
|
||||||
"general": { "font": "Open Sans", "background": { "type": "None" } }
|
"inputs": {
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"color": "#303235",
|
||||||
|
"placeholderColor": "#9095A0"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"selectedThemeTemplateId": null,
|
||||||
"settings": {
|
"settings": {
|
||||||
"general": {
|
"general": {
|
||||||
"isBrandingEnabled": false,
|
"isBrandingEnabled": false,
|
||||||
@ -262,14 +237,21 @@
|
|||||||
"isHideQueryParamsEnabled": true,
|
"isHideQueryParamsEnabled": true,
|
||||||
"isNewResultOnRefreshEnabled": false
|
"isNewResultOnRefreshEnabled": false
|
||||||
},
|
},
|
||||||
|
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||||
"metadata": {
|
"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."
|
"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,
|
"publicId": null,
|
||||||
"customDomain": null,
|
"customDomain": null,
|
||||||
"workspaceId": "proWorkspace",
|
"workspaceId": "proWorkspace",
|
||||||
|
"resultsTablePreferences": null,
|
||||||
"isArchived": false,
|
"isArchived": false,
|
||||||
"isClosed": false
|
"isClosed": false,
|
||||||
|
"whatsAppCredentialsId": null,
|
||||||
|
"riskLevel": null
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,30 @@ import { getTestAsset } from '@/test/utils/playwright'
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import prisma from '@typebot.io/lib/prisma'
|
import prisma from '@typebot.io/lib/prisma'
|
||||||
import {
|
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||||
createWebhook,
|
|
||||||
deleteTypebots,
|
|
||||||
deleteWebhooks,
|
|
||||||
importTypebotInDatabase,
|
|
||||||
} from '@typebot.io/playwright/databaseActions'
|
|
||||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
|
|
||||||
import { StartChatInput, StartPreviewChatInput } from '@typebot.io/schemas'
|
import { StartChatInput, StartPreviewChatInput } from '@typebot.io/schemas'
|
||||||
|
|
||||||
test.afterEach(async () => {
|
test.describe.configure({ mode: 'parallel' })
|
||||||
await deleteWebhooks(['chat-webhook-id'])
|
|
||||||
await deleteTypebots(['chat-sub-bot', 'starting-with-input'])
|
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 }) => {
|
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,
|
id: typebotId,
|
||||||
publicId,
|
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
|
let chatSessionId: string
|
||||||
|
|
||||||
@ -104,22 +100,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
id: typebotId,
|
id: typebotId,
|
||||||
publicId,
|
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
|
let chatSessionId: string
|
||||||
|
|
||||||
await test.step('Start the chat', async () => {
|
await test.step('Start the chat', async () => {
|
||||||
|
@ -1,50 +1,14 @@
|
|||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import {
|
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||||
createWebhook,
|
|
||||||
importTypebotInDatabase,
|
|
||||||
} from '@typebot.io/playwright/databaseActions'
|
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/webhook/constants'
|
|
||||||
|
|
||||||
const typebotId = createId()
|
test('should execute webhooks properly', async ({ page }) => {
|
||||||
|
const typebotId = createId()
|
||||||
test.beforeEach(async () => {
|
|
||||||
await importTypebotInDatabase(getTestAsset('typebots/webhook.json'), {
|
await importTypebotInDatabase(getTestAsset('typebots/webhook.json'), {
|
||||||
id: typebotId,
|
id: typebotId,
|
||||||
publicId: `${typebotId}-public`,
|
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.goto(`/${typebotId}-public`)
|
||||||
await page.locator('text=Send failing webhook').click()
|
await page.locator('text=Send failing webhook').click()
|
||||||
await page.locator('[placeholder="Type a name..."]').fill('John')
|
await page.locator('[placeholder="Type a name..."]').fill('John')
|
||||||
|
@ -335,8 +335,8 @@ export const convertKeyValueTableToObject = (
|
|||||||
const value = parseVariables(variables)(item.value)
|
const value = parseVariables(variables)(item.value)
|
||||||
if (isEmpty(key) || isEmpty(value)) return object
|
if (isEmpty(key) || isEmpty(value)) return object
|
||||||
if (object[key] && concatDuplicateInArray) {
|
if (object[key] && concatDuplicateInArray) {
|
||||||
if (Array.isArray(object[key])) object[key].push(value)
|
if (Array.isArray(object[key])) (object[key] as string[]).push(value)
|
||||||
else object[key] = [object[key], value]
|
else object[key] = [object[key] as string, value]
|
||||||
} else object[key] = value
|
} else object[key] = value
|
||||||
return object
|
return object
|
||||||
}, {})
|
}, {})
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { anthropicBlock } from '.'
|
import { anthropicBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const anthropicBlockSchema = parseBlockSchema(anthropicBlock)
|
export const anthropicBlockSchema = parseBlockSchema(anthropicBlock)
|
||||||
export const anthropicCredentialsSchema = parseBlockCredentials(anthropicBlock)
|
export const anthropicCredentialsSchema = parseBlockCredentials(
|
||||||
|
anthropicBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { calComBlock } from '.'
|
import { calComBlock } from '.'
|
||||||
|
|
||||||
export const calComBlockSchema = parseBlockSchema(calComBlock)
|
export const calComBlockSchema = parseBlockSchema(calComBlock)
|
||||||
export const calComCredentialsSchema = parseBlockCredentials(calComBlock)
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { chatNodeBlock } from '.'
|
import { chatNodeBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const chatNodeBlockSchema = parseBlockSchema(chatNodeBlock)
|
export const chatNodeBlockSchema = parseBlockSchema(chatNodeBlock)
|
||||||
export const chatNodeCredentialsSchema = parseBlockCredentials(chatNodeBlock)
|
export const chatNodeCredentialsSchema = parseBlockCredentials(
|
||||||
|
chatNodeBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { difyAiBlock } from '.'
|
import { difyAiBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const difyAiBlockSchema = parseBlockSchema(difyAiBlock)
|
export const difyAiBlockSchema = parseBlockSchema(difyAiBlock)
|
||||||
export const difyAiCredentialsSchema = parseBlockCredentials(difyAiBlock)
|
export const difyAiCredentialsSchema = parseBlockCredentials(
|
||||||
|
difyAiBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { elevenlabsBlock } from '.'
|
import { elevenlabsBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const elevenlabsBlockSchema = parseBlockSchema(elevenlabsBlock)
|
export const elevenlabsBlockSchema = parseBlockSchema(elevenlabsBlock)
|
||||||
export const elevenlabsCredentialsSchema =
|
export const elevenlabsCredentialsSchema = parseBlockCredentials(
|
||||||
parseBlockCredentials(elevenlabsBlock)
|
elevenlabsBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { mistralBlock } from '.'
|
import { mistralBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const mistralBlockSchema = parseBlockSchema(mistralBlock)
|
export const mistralBlockSchema = parseBlockSchema(mistralBlock)
|
||||||
export const mistralCredentialsSchema = parseBlockCredentials(mistralBlock)
|
export const mistralCredentialsSchema = parseBlockCredentials(
|
||||||
|
mistralBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { nocodbBlock } from '.'
|
import { nocodbBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const nocodbBlockSchema = parseBlockSchema(nocodbBlock)
|
export const nocodbBlockSchema = parseBlockSchema(nocodbBlock)
|
||||||
export const nocodbCredentialsSchema = parseBlockCredentials(nocodbBlock)
|
export const nocodbCredentialsSchema = parseBlockCredentials(
|
||||||
|
nocodbBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { openRouterBlock } from '.'
|
import { openRouterBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const openRouterBlockSchema = parseBlockSchema(openRouterBlock)
|
export const openRouterBlockSchema = parseBlockSchema(openRouterBlock)
|
||||||
export const openRouterCredentialsSchema =
|
export const openRouterCredentialsSchema = parseBlockCredentials(
|
||||||
parseBlockCredentials(openRouterBlock)
|
openRouterBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { openAIBlock } from '.'
|
import { openAIBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const openAIBlockSchema = parseBlockSchema(openAIBlock)
|
export const openAIBlockSchema = parseBlockSchema(openAIBlock)
|
||||||
export const openAICredentialsSchema = parseBlockCredentials(openAIBlock)
|
export const openAICredentialsSchema = parseBlockCredentials(
|
||||||
|
openAIBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { qrCodeBlock } from '.'
|
import { qrCodeBlock } from '.'
|
||||||
|
|
||||||
export const qrCodeBlockSchema = parseBlockSchema(qrCodeBlock)
|
export const qrCodeBlockSchema = parseBlockSchema(qrCodeBlock)
|
||||||
export const qrCodeCredentialsSchema = parseBlockCredentials(qrCodeBlock)
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { togetherAiBlock } from '.'
|
import { togetherAiBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const togetherAiBlockSchema = parseBlockSchema(togetherAiBlock)
|
export const togetherAiBlockSchema = parseBlockSchema(togetherAiBlock)
|
||||||
export const togetherAiCredentialsSchema =
|
export const togetherAiCredentialsSchema = parseBlockCredentials(
|
||||||
parseBlockCredentials(togetherAiBlock)
|
togetherAiBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
// Do not edit this file manually
|
// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
||||||
import { zemanticAiBlock } from '.'
|
import { zemanticAiBlock } from '.'
|
||||||
|
import { auth } from './auth'
|
||||||
|
|
||||||
export const zemanticAiBlockSchema = parseBlockSchema(zemanticAiBlock)
|
export const zemanticAiBlockSchema = parseBlockSchema(zemanticAiBlock)
|
||||||
export const zemanticAiCredentialsSchema =
|
export const zemanticAiCredentialsSchema = parseBlockCredentials(
|
||||||
parseBlockCredentials(zemanticAiBlock)
|
zemanticAiBlock.id,
|
||||||
|
auth.schema
|
||||||
|
)
|
||||||
|
@ -273,6 +273,7 @@ const createSchemasFile = async (
|
|||||||
path: string,
|
path: string,
|
||||||
{
|
{
|
||||||
id,
|
id,
|
||||||
|
auth,
|
||||||
}: { id: string; name: string; auth: 'apiKey' | 'encryptedData' | 'none' }
|
}: { id: string; name: string; auth: 'apiKey' | 'encryptedData' | 'none' }
|
||||||
) => {
|
) => {
|
||||||
const camelCaseName = camelize(id as string)
|
const camelCaseName = camelize(id as string)
|
||||||
@ -280,11 +281,19 @@ const createSchemasFile = async (
|
|||||||
join(path, 'schemas.ts'),
|
join(path, 'schemas.ts'),
|
||||||
await prettier.format(
|
await prettier.format(
|
||||||
`// Do not edit this file manually
|
`// Do not edit this file manually
|
||||||
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
|
import { ${
|
||||||
import { ${camelCaseName}Block } from '.'
|
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}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 }
|
{ parser: 'typescript', ...prettierRc }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -82,22 +82,18 @@ export const parseBlockSchema = <
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseBlockCredentials = <
|
export const parseBlockCredentials = <I extends string>(
|
||||||
I extends string,
|
blockId: I,
|
||||||
A extends AuthDefinition,
|
authSchema: z.ZodObject<any>
|
||||||
O extends z.ZodObject<any>
|
|
||||||
>(
|
|
||||||
blockDefinition: BlockDefinition<I, A, O>
|
|
||||||
) => {
|
) => {
|
||||||
if (!blockDefinition.auth) return null
|
|
||||||
return z.object({
|
return z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
type: z.literal(blockDefinition.id),
|
type: z.literal(blockId),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
iv: z.string(),
|
iv: z.string(),
|
||||||
data: blockDefinition.auth.schema,
|
data: authSchema,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { anthropicBlock } from '@typebot.io/anthropic-block'
|
import { anthropicBlock } from '@typebot.io/anthropic-block'
|
||||||
import { anthropicCredentialsSchema } from '@typebot.io/anthropic-block/schemas'
|
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 { chatNodeBlock } from '@typebot.io/chat-node-block'
|
||||||
import { chatNodeCredentialsSchema } from '@typebot.io/chat-node-block/schemas'
|
import { chatNodeCredentialsSchema } from '@typebot.io/chat-node-block/schemas'
|
||||||
import { difyAiBlock } from '@typebot.io/dify-ai-block'
|
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 { openRouterCredentialsSchema } from '@typebot.io/open-router-block/schemas'
|
||||||
import { openAIBlock } from '@typebot.io/openai-block'
|
import { openAIBlock } from '@typebot.io/openai-block'
|
||||||
import { openAICredentialsSchema } from '@typebot.io/openai-block/schemas'
|
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 { togetherAiBlock } from '@typebot.io/together-ai-block'
|
||||||
import { togetherAiCredentialsSchema } from '@typebot.io/together-ai-block/schemas'
|
import { togetherAiCredentialsSchema } from '@typebot.io/together-ai-block/schemas'
|
||||||
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
|
import { zemanticAiBlock } from '@typebot.io/zemantic-ai-block'
|
||||||
@ -26,9 +22,7 @@ import { nocodbCredentialsSchema } from '@typebot.io/nocodb-block/schemas'
|
|||||||
export const forgedCredentialsSchemas = {
|
export const forgedCredentialsSchemas = {
|
||||||
[openAIBlock.id]: openAICredentialsSchema,
|
[openAIBlock.id]: openAICredentialsSchema,
|
||||||
[zemanticAiBlock.id]: zemanticAiCredentialsSchema,
|
[zemanticAiBlock.id]: zemanticAiCredentialsSchema,
|
||||||
[calComBlock.id]: calComCredentialsSchema,
|
|
||||||
[chatNodeBlock.id]: chatNodeCredentialsSchema,
|
[chatNodeBlock.id]: chatNodeCredentialsSchema,
|
||||||
[qrCodeBlock.id]: qrCodeCredentialsSchema,
|
|
||||||
[difyAiBlock.id]: difyAiCredentialsSchema,
|
[difyAiBlock.id]: difyAiCredentialsSchema,
|
||||||
[mistralBlock.id]: mistralCredentialsSchema,
|
[mistralBlock.id]: mistralCredentialsSchema,
|
||||||
[elevenlabsBlock.id]: elevenlabsCredentialsSchema,
|
[elevenlabsBlock.id]: elevenlabsCredentialsSchema,
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"types": "./index.ts",
|
"types": "./index.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@paralleldrive/cuid2": "2.2.1",
|
"@paralleldrive/cuid2": "2.2.1",
|
||||||
"@playwright/test": "1.43.1",
|
"@playwright/test": "1.45.2",
|
||||||
"@typebot.io/env": "workspace:*",
|
"@typebot.io/env": "workspace:*",
|
||||||
"@typebot.io/prisma": "workspace:*",
|
"@typebot.io/prisma": "workspace:*",
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
@ -49,4 +49,4 @@
|
|||||||
"wildcard-match": "5.1.3",
|
"wildcard-match": "5.1.3",
|
||||||
"zod": "3.22.4"
|
"zod": "3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,25 +151,6 @@ export const updateUser = (data: Partial<User>) =>
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createWebhook = async (
|
|
||||||
typebotId: string,
|
|
||||||
webhookProps?: Partial<HttpRequest>
|
|
||||||
) => {
|
|
||||||
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<TypebotV6>[]) => {
|
export const createTypebots = async (partialTypebots: Partial<TypebotV6>[]) => {
|
||||||
const typebotsWithId = partialTypebots.map((typebot) => {
|
const typebotsWithId = partialTypebots.map((typebot) => {
|
||||||
const typebotId = typebot.id ?? createId()
|
const typebotId = typebot.id ?? createId()
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Baptiste Arnaud",
|
"author": "Baptiste Arnaud",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/test": "1.43.1",
|
"@playwright/test": "1.45.2",
|
||||||
"@typebot.io/lib": "workspace:*",
|
"@typebot.io/lib": "workspace:*",
|
||||||
"@typebot.io/prisma": "workspace:*",
|
"@typebot.io/prisma": "workspace:*",
|
||||||
"@typebot.io/schemas": "workspace:*",
|
"@typebot.io/schemas": "workspace:*",
|
||||||
|
@ -3,16 +3,26 @@ import { stripeCredentialsSchema } from './blocks/inputs/payment/schema'
|
|||||||
import { googleSheetsCredentialsSchema } from './blocks/integrations/googleSheets/schema'
|
import { googleSheetsCredentialsSchema } from './blocks/integrations/googleSheets/schema'
|
||||||
import { smtpCredentialsSchema } from './blocks/integrations/sendEmail'
|
import { smtpCredentialsSchema } from './blocks/integrations/sendEmail'
|
||||||
import { whatsAppCredentialsSchema } from './whatsapp'
|
import { whatsAppCredentialsSchema } from './whatsapp'
|
||||||
import { zemanticAiCredentialsSchema } from './blocks'
|
import { forgedCredentialsSchemas } from '@typebot.io/forge-repository/credentials'
|
||||||
import { openAICredentialsSchema } from './blocks/integrations/openai'
|
|
||||||
|
|
||||||
export const credentialsSchema = z.discriminatedUnion('type', [
|
const credentialsSchema = z.discriminatedUnion('type', [
|
||||||
smtpCredentialsSchema,
|
smtpCredentialsSchema,
|
||||||
googleSheetsCredentialsSchema,
|
googleSheetsCredentialsSchema,
|
||||||
stripeCredentialsSchema,
|
stripeCredentialsSchema,
|
||||||
openAICredentialsSchema,
|
|
||||||
whatsAppCredentialsSchema,
|
whatsAppCredentialsSchema,
|
||||||
zemanticAiCredentialsSchema,
|
...Object.values(forgedCredentialsSchemas),
|
||||||
])
|
])
|
||||||
|
|
||||||
export type Credentials = z.infer<typeof credentialsSchema>
|
export type Credentials = z.infer<typeof credentialsSchema>
|
||||||
|
|
||||||
|
export const credentialsTypes = [
|
||||||
|
'smtp',
|
||||||
|
'google sheets',
|
||||||
|
'stripe',
|
||||||
|
'whatsApp',
|
||||||
|
...(Object.keys(forgedCredentialsSchemas) as Array<
|
||||||
|
keyof typeof forgedCredentialsSchemas
|
||||||
|
>),
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const credentialsTypeSchema = z.enum(credentialsTypes)
|
||||||
|
44
pnpm-lock.yaml
generated
44
pnpm-lock.yaml
generated
@ -288,8 +288,8 @@ importers:
|
|||||||
specifier: 2.9.2
|
specifier: 2.9.2
|
||||||
version: 2.9.2
|
version: 2.9.2
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: 1.43.1
|
specifier: 1.45.2
|
||||||
version: 1.43.1
|
version: 1.45.2
|
||||||
'@typebot.io/billing':
|
'@typebot.io/billing':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../ee/packages/billing
|
version: link:../../ee/packages/billing
|
||||||
@ -478,8 +478,8 @@ importers:
|
|||||||
specifier: 2.2.1
|
specifier: 2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: 1.43.1
|
specifier: 1.45.2
|
||||||
version: 1.43.1
|
version: 1.45.2
|
||||||
'@typebot.io/emails':
|
'@typebot.io/emails':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/emails
|
version: link:../../packages/emails
|
||||||
@ -1785,8 +1785,8 @@ importers:
|
|||||||
specifier: 2.2.1
|
specifier: 2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: 1.43.1
|
specifier: 1.45.2
|
||||||
version: 1.43.1
|
version: 1.45.2
|
||||||
'@typebot.io/env':
|
'@typebot.io/env':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../env
|
version: link:../env
|
||||||
@ -1856,8 +1856,8 @@ importers:
|
|||||||
packages/playwright:
|
packages/playwright:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: 1.43.1
|
specifier: 1.45.2
|
||||||
version: 1.43.1
|
version: 1.45.2
|
||||||
'@typebot.io/env':
|
'@typebot.io/env':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../env
|
version: link:../env
|
||||||
@ -4687,9 +4687,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-+zk04eXRiaJGaRnJZkCxXbBtBvQDQJXCoxqlXhLY3HzAovXfsBnh6DjXRujPRQQ7GKtT8/tOlyvZ9h6ReM+GLQ==}
|
resolution: {integrity: sha512-+zk04eXRiaJGaRnJZkCxXbBtBvQDQJXCoxqlXhLY3HzAovXfsBnh6DjXRujPRQQ7GKtT8/tOlyvZ9h6ReM+GLQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
'@playwright/test@1.43.1':
|
'@playwright/test@1.45.2':
|
||||||
resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==}
|
resolution: {integrity: sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@popperjs/core@2.11.8':
|
'@popperjs/core@2.11.8':
|
||||||
@ -10645,14 +10645,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
playwright-core@1.43.1:
|
playwright-core@1.45.2:
|
||||||
resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==}
|
resolution: {integrity: sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
playwright@1.43.1:
|
playwright@1.45.2:
|
||||||
resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==}
|
resolution: {integrity: sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
pngjs@5.0.0:
|
pngjs@5.0.0:
|
||||||
@ -16357,9 +16357,9 @@ snapshots:
|
|||||||
|
|
||||||
'@planetscale/database@1.8.0': {}
|
'@planetscale/database@1.8.0': {}
|
||||||
|
|
||||||
'@playwright/test@1.43.1':
|
'@playwright/test@1.45.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
playwright: 1.43.1
|
playwright: 1.45.2
|
||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@popperjs/core@2.11.8': {}
|
||||||
|
|
||||||
@ -24207,11 +24207,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
find-up: 4.1.0
|
find-up: 4.1.0
|
||||||
|
|
||||||
playwright-core@1.43.1: {}
|
playwright-core@1.45.2: {}
|
||||||
|
|
||||||
playwright@1.43.1:
|
playwright@1.45.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
playwright-core: 1.43.1
|
playwright-core: 1.45.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user