@ -24,7 +24,6 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
|
||||
const handleChangeIcon = (icon: string) => {
|
||||
if (!workspace?.id) return
|
||||
console.log(icon)
|
||||
updateWorkspace(workspace?.id, { icon })
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
PabblyConnectLogo,
|
||||
ZapierLogo,
|
||||
} from 'assets/logos'
|
||||
import { ChatwootLogo } from 'features/chatwoot'
|
||||
import {
|
||||
BubbleBlockType,
|
||||
InputBlockType,
|
||||
@ -95,6 +96,8 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps) => {
|
||||
return <PabblyConnectLogo {...props} />
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return <SendEmailIcon {...props} />
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return <ChatwootLogo {...props} />
|
||||
case 'start':
|
||||
return <FlagIcon {...props} />
|
||||
}
|
||||
|
@ -98,5 +98,7 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
|
||||
return <Text>Pabbly</Text>
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return <Text>Email</Text>
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return <Text>Chatwoot</Text>
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,6 @@ export const ResultsActionButtons = ({
|
||||
const data = dataToUnparse.map<{ [key: string]: string }>((data) => {
|
||||
const newObject: { [key: string]: string } = {}
|
||||
fields?.forEach((field) => {
|
||||
console.log(data[field])
|
||||
newObject[field] = data[field]?.plainText
|
||||
})
|
||||
return newObject
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { ChatwootBlockNodeLabel } from 'features/chatwoot'
|
||||
import {
|
||||
Block,
|
||||
StartBlock,
|
||||
@ -153,6 +154,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
case IntegrationBlockType.EMAIL: {
|
||||
return <SendEmailContent block={block} />
|
||||
}
|
||||
case IntegrationBlockType.CHATWOOT: {
|
||||
return <ChatwootBlockNodeLabel block={block} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>Start</Text>
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
IconButton,
|
||||
} from '@chakra-ui/react'
|
||||
import { ExpandIcon } from 'assets/icons'
|
||||
import { ChatwootSettingsForm } from 'features/chatwoot/components'
|
||||
import {
|
||||
ConditionItem,
|
||||
ConditionBlock,
|
||||
@ -276,5 +277,13 @@ export const BlockSettings = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.CHATWOOT: {
|
||||
return (
|
||||
<ChatwootSettingsForm
|
||||
options={block.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ type Props = {
|
||||
|
||||
export const MoreInfoTooltip = ({ children }: Props) => {
|
||||
return (
|
||||
<Tooltip label={children} hasArrow rounded="md" p="3">
|
||||
<Tooltip label={children} hasArrow rounded="md" p="3" placement="top">
|
||||
<chakra.span cursor="pointer">
|
||||
<HelpCircleIcon />
|
||||
</chakra.span>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import {
|
||||
ComponentWithAs,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
InputProps,
|
||||
TextareaProps,
|
||||
@ -9,6 +11,7 @@ import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { env } from 'utils'
|
||||
import { VariablesButton } from '../buttons/VariablesButton'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
|
||||
export type TextBoxProps = {
|
||||
onChange: (value: string) => void
|
||||
@ -17,6 +20,8 @@ export type TextBoxProps = {
|
||||
| ComponentWithAs<'input', InputProps>
|
||||
withVariableButton?: boolean
|
||||
debounceTimeout?: number
|
||||
label?: string
|
||||
moreInfoTooltip?: string
|
||||
} & Omit<InputProps & TextareaProps, 'onChange'>
|
||||
|
||||
export const TextBox = ({
|
||||
@ -24,6 +29,8 @@ export const TextBox = ({
|
||||
TextBox,
|
||||
withVariableButton = true,
|
||||
debounceTimeout = 1000,
|
||||
label,
|
||||
moreInfoTooltip,
|
||||
...props
|
||||
}: TextBoxProps) => {
|
||||
const textBoxRef = useRef<(HTMLInputElement & HTMLTextAreaElement) | null>(
|
||||
@ -92,29 +99,36 @@ export const TextBox = ({
|
||||
setCarretPosition(textBoxRef.current.selectionStart)
|
||||
}
|
||||
|
||||
if (!withVariableButton) {
|
||||
return (
|
||||
<TextBox
|
||||
ref={textBoxRef}
|
||||
onChange={handleChange}
|
||||
bgColor={'white'}
|
||||
value={value}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const Input = (
|
||||
<TextBox
|
||||
ref={textBoxRef}
|
||||
value={value}
|
||||
onKeyUp={handleKeyUp}
|
||||
onClick={handleKeyUp}
|
||||
onChange={handleChange}
|
||||
bgColor={'white'}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<HStack spacing={0} align={'flex-end'}>
|
||||
<TextBox
|
||||
ref={textBoxRef}
|
||||
value={value}
|
||||
onKeyUp={handleKeyUp}
|
||||
onClick={handleKeyUp}
|
||||
onChange={handleChange}
|
||||
bgColor={'white'}
|
||||
{...props}
|
||||
/>
|
||||
<VariablesButton onSelectVariable={handleVariableSelected} />
|
||||
</HStack>
|
||||
<FormControl isRequired={props.isRequired}>
|
||||
{label && (
|
||||
<FormLabel>
|
||||
{label}{' '}
|
||||
{moreInfoTooltip && (
|
||||
<MoreInfoTooltip>{moreInfoTooltip}</MoreInfoTooltip>
|
||||
)}
|
||||
</FormLabel>
|
||||
)}
|
||||
{withVariableButton ? (
|
||||
<HStack spacing={0} align={'flex-end'}>
|
||||
{Input}
|
||||
<VariablesButton onSelectVariable={handleVariableSelected} />
|
||||
</HStack>
|
||||
) : (
|
||||
Input
|
||||
)}
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
@ -46,8 +46,6 @@ export const ResultsProvider = ({
|
||||
typebotId,
|
||||
})
|
||||
|
||||
console.log(data?.flatMap((d) => d.results) ?? [])
|
||||
|
||||
const fetchMore = () => setSize((state) => state + 1)
|
||||
|
||||
const resultHeader = useMemo(
|
||||
|
43
apps/builder/features/chatwoot/chatwoot.spec.ts
Normal file
43
apps/builder/features/chatwoot/chatwoot.spec.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||
import cuid from 'cuid'
|
||||
import { defaultChatwootOptions, IntegrationBlockType } from 'models'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
|
||||
const typebotId = cuid()
|
||||
|
||||
const chatwootTestWebsiteToken = 'tueXiiqEmrWUCZ4NUyoR7nhE'
|
||||
|
||||
test.describe('Chatwoot block', () => {
|
||||
test('should be configurable', async ({ page }) => {
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
...parseDefaultGroupWithBlock({
|
||||
type: IntegrationBlockType.CHATWOOT,
|
||||
options: defaultChatwootOptions,
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.getByText('Configure...').click()
|
||||
await expect(page.getByLabel('Base URL')).toHaveAttribute(
|
||||
'value',
|
||||
defaultChatwootOptions.baseUrl
|
||||
)
|
||||
await page.getByLabel('Website token').fill(chatwootTestWebsiteToken)
|
||||
await expect(page.getByText('Open Chatwoot')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Set user details' }).click()
|
||||
await page.getByLabel('ID').fill('123')
|
||||
await page.getByLabel('Name').fill('John Doe')
|
||||
await page.getByLabel('Email').fill('john@email.com')
|
||||
await page.getByLabel('Avatar URL').fill('https://domain.com/avatar.png')
|
||||
await page.getByLabel('Phone number').fill('+33654347543')
|
||||
await page.getByRole('button', { name: 'Preview' }).click()
|
||||
await expect(
|
||||
page.getByText("Chatwoot won't open in preview mode").nth(0)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
@ -0,0 +1,13 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { ChatwootBlock } from 'models'
|
||||
|
||||
type Props = {
|
||||
block: ChatwootBlock
|
||||
}
|
||||
|
||||
export const ChatwootBlockNodeLabel = ({ block }: Props) =>
|
||||
block.options.websiteToken.length === 0 ? (
|
||||
<Text color="gray.500">Configure...</Text>
|
||||
) : (
|
||||
<Text>Open Chatwoot</Text>
|
||||
)
|
29
apps/builder/features/chatwoot/components/ChatwootLogo.tsx
Normal file
29
apps/builder/features/chatwoot/components/ChatwootLogo.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Icon, IconProps } from '@chakra-ui/react'
|
||||
|
||||
export const ChatwootLogo = (props: IconProps) => (
|
||||
<Icon
|
||||
viewBox="0 0 512 512"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g
|
||||
id="Square-logo"
|
||||
stroke="none"
|
||||
stroke-width="1"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
>
|
||||
<g id="chatwoot_logo" fill-rule="nonzero">
|
||||
<circle id="Oval" fill="#47A7F6" cx="256" cy="256" r="256"></circle>
|
||||
<path
|
||||
d="M362.807947,368.807947 L244.122956,368.807947 C178.699407,368.807947 125.456954,315.561812 125.456954,250.12177 C125.456954,184.703089 178.699407,131.456954 244.124143,131.456954 C309.565494,131.456954 362.807947,184.703089 362.807947,250.12177 L362.807947,368.807947 Z"
|
||||
id="Fill-1"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="6"
|
||||
fill="#FFFFFF"
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</Icon>
|
||||
)
|
@ -0,0 +1,98 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Stack,
|
||||
} from '@chakra-ui/react'
|
||||
import { Input } from 'components/shared/Textbox'
|
||||
import { ChatwootOptions } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options: ChatwootOptions
|
||||
onOptionsChange: (options: ChatwootOptions) => void
|
||||
}
|
||||
|
||||
export const ChatwootSettingsForm = ({ options, onOptionsChange }: Props) => {
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Input
|
||||
isRequired
|
||||
label="Base URL"
|
||||
defaultValue={options.baseUrl}
|
||||
onChange={(baseUrl: string) => {
|
||||
onOptionsChange({ ...options, baseUrl })
|
||||
}}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
<Input
|
||||
isRequired
|
||||
label="Website token"
|
||||
defaultValue={options.websiteToken}
|
||||
onChange={(websiteToken) =>
|
||||
onOptionsChange({ ...options, websiteToken })
|
||||
}
|
||||
moreInfoTooltip="Can be found in Chatwoot under Settings > Inboxes > Settings > Configuration, in the code snippet."
|
||||
/>
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Set user details
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel pb={4} as={Stack} spacing="4">
|
||||
<Input
|
||||
label="ID"
|
||||
defaultValue={options.user?.id}
|
||||
onChange={(id: string) => {
|
||||
onOptionsChange({ ...options, user: { ...options.user, id } })
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label="Name"
|
||||
defaultValue={options.user?.name}
|
||||
onChange={(name: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
user: { ...options.user, name },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label="Email"
|
||||
defaultValue={options.user?.email}
|
||||
onChange={(email: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
user: { ...options.user, email },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label="Avatar URL"
|
||||
defaultValue={options.user?.avatarUrl}
|
||||
onChange={(avatarUrl: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
user: { ...options.user, avatarUrl },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
label="Phone number"
|
||||
defaultValue={options.user?.phoneNumber}
|
||||
onChange={(phoneNumber: string) => {
|
||||
onOptionsChange({
|
||||
...options,
|
||||
user: { ...options.user, phoneNumber },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
}
|
3
apps/builder/features/chatwoot/components/index.ts
Normal file
3
apps/builder/features/chatwoot/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { ChatwootLogo } from './ChatwootLogo'
|
||||
export { ChatwootBlockNodeLabel } from './ChatwootBlockNodeLabel'
|
||||
export { ChatwootSettingsForm } from './ChatwootSettingsForm'
|
1
apps/builder/features/chatwoot/index.ts
Normal file
1
apps/builder/features/chatwoot/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './components'
|
@ -4,7 +4,6 @@ import { playwrightBaseConfig } from 'configs/playwright'
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
...playwrightBaseConfig,
|
||||
testDir: path.join(__dirname, 'playwright/tests'),
|
||||
webServer: process.env.CI
|
||||
? {
|
||||
...(playwrightBaseConfig.webServer as { command: string }),
|
||||
|
@ -36,7 +36,7 @@ test.describe.parallel('Image bubble block', () => {
|
||||
process.env.S3_ENDPOINT
|
||||
}${process.env.S3_PORT ? `:${process.env.S3_PORT}` : ''}/${
|
||||
process.env.S3_BUCKET
|
||||
}/public/typebots/${typebotId}/blocks/block1`
|
||||
}/public/typebots/${typebotId}/blocks/block2`
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -48,10 +48,10 @@ test.describe.parallel('Buttons input block', () => {
|
||||
await expect(typebotViewer(page).locator('text=Item 3')).toBeVisible()
|
||||
await page.click('button[aria-label="Close"]')
|
||||
|
||||
await page.click('[data-testid="block1-icon"]')
|
||||
await page.click('[data-testid="block2-icon"]')
|
||||
await page.click('text=Multiple choice?')
|
||||
await page.fill('#button', 'Go')
|
||||
await page.click('[data-testid="block1-icon"]')
|
||||
await page.click('[data-testid="block2-icon"]')
|
||||
|
||||
await page.locator('text=Item 1').hover()
|
||||
await page.waitForTimeout(1000)
|
||||
|
@ -65,7 +65,8 @@ test.describe('Webhook block', () => {
|
||||
)
|
||||
|
||||
await page.click('text=Headers')
|
||||
await page.click('text=Add a value')
|
||||
await page.waitForTimeout(200)
|
||||
await page.getByRole('button', { name: 'Add a value' }).click()
|
||||
await page.fill('input[placeholder="e.g. Content-Type"]', 'Custom-Typebot')
|
||||
await page.fill(
|
||||
'input[placeholder="e.g. application/json"]',
|
||||
|
@ -62,6 +62,7 @@ import cuid from 'cuid'
|
||||
import { diff } from 'deep-object-diff'
|
||||
import { duplicateWebhook } from 'services/webhook'
|
||||
import { Plan } from 'db'
|
||||
import { defaultChatwootOptions } from 'models'
|
||||
|
||||
export type TypebotInDashboard = Pick<
|
||||
Typebot,
|
||||
@ -350,6 +351,8 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
|
||||
return defaultWebhookOptions
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return defaultSendEmailOptions
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return defaultChatwootOptions
|
||||
}
|
||||
}
|
||||
|
||||
|
31
apps/viewer/playwright/tests/chatwoot.spec.ts
Normal file
31
apps/viewer/playwright/tests/chatwoot.spec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import cuid from 'cuid'
|
||||
import { createTypebots } from 'utils/playwright/databaseActions'
|
||||
import { parseDefaultGroupWithBlock } from 'utils/playwright/databaseHelpers'
|
||||
import { defaultChatwootOptions, IntegrationBlockType } from 'models'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
|
||||
const typebotId = cuid()
|
||||
|
||||
const chatwootTestWebsiteToken = 'tueXiiqEmrWUCZ4NUyoR7nhE'
|
||||
|
||||
test('should work as expected', async ({ page }) => {
|
||||
await createTypebots([
|
||||
{
|
||||
id: typebotId,
|
||||
...parseDefaultGroupWithBlock(
|
||||
{
|
||||
type: IntegrationBlockType.CHATWOOT,
|
||||
options: {
|
||||
...defaultChatwootOptions,
|
||||
websiteToken: chatwootTestWebsiteToken,
|
||||
},
|
||||
},
|
||||
{ withGoButton: true }
|
||||
),
|
||||
},
|
||||
])
|
||||
await page.goto(`/${typebotId}-public`)
|
||||
await typebotViewer(page).getByRole('button', { name: 'Go' }).click()
|
||||
await expect(page.locator('#chatwoot_live_chat_widget')).toBeVisible()
|
||||
})
|
1
packages/bot-engine/src/features/chatwoot/index.ts
Normal file
1
packages/bot-engine/src/features/chatwoot/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { openChatwootWidget } from './utils/openChatwootWidget'
|
@ -0,0 +1,83 @@
|
||||
import { ChatwootBlock, ChatwootOptions } from 'models/features/chatwoot'
|
||||
import { sendEventToParent } from 'services/chat'
|
||||
import { IntegrationContext } from 'services/integration'
|
||||
import { isEmbedded } from 'services/utils'
|
||||
import { parseCorrectValueType, parseVariables } from 'services/variable'
|
||||
|
||||
const parseSetUserCode = (user: ChatwootOptions['user']) => `
|
||||
window.$chatwoot.setUser("${user?.id ?? user?.email}", {
|
||||
email: ${user?.email ? `"${user.email}"` : 'undefined'},
|
||||
name: ${user?.name ? `"${user.name}"` : 'undefined'},
|
||||
avatar_url: ${user?.avatarUrl ? `"${user.avatarUrl}"` : 'undefined'},
|
||||
phone_number: ${user?.phoneNumber ? `"${user.phoneNumber}"` : 'undefined'},
|
||||
});
|
||||
|
||||
`
|
||||
const parseChatwootOpenCode = ({
|
||||
baseUrl,
|
||||
websiteToken,
|
||||
user,
|
||||
}: ChatwootOptions) => `
|
||||
if (window.$chatwoot) {
|
||||
if(${Boolean(user?.id || user?.email)}) {
|
||||
${parseSetUserCode(user)}
|
||||
}
|
||||
if (typeof Typebot !== 'undefined') Typebot.getBubbleActions?.().close()
|
||||
window.$chatwoot.toggle("open");
|
||||
} else {
|
||||
(function (d, t) {
|
||||
var BASE_URL = "${baseUrl}";
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src = BASE_URL + "/packs/js/sdk.js";
|
||||
g.defer = true;
|
||||
g.async = true;
|
||||
s.parentNode.insertBefore(g, s);
|
||||
g.onload = function () {
|
||||
window.chatwootSDK.run({
|
||||
websiteToken: "${websiteToken}",
|
||||
baseUrl: BASE_URL,
|
||||
});
|
||||
window.addEventListener("chatwoot:ready", function () {
|
||||
if(${Boolean(user?.id || user?.email)}) {
|
||||
${parseSetUserCode(user)}
|
||||
}
|
||||
if (typeof Typebot !== 'undefined') Typebot.getBubbleActions?.().close()
|
||||
window.$chatwoot.toggle("open");
|
||||
});
|
||||
};
|
||||
})(document, "script");
|
||||
}`
|
||||
|
||||
export const openChatwootWidget = async (
|
||||
block: ChatwootBlock,
|
||||
{ variables, isPreview, onNewLog }: IntegrationContext
|
||||
) => {
|
||||
if (isPreview) {
|
||||
onNewLog({
|
||||
status: 'info',
|
||||
description: "Chatwoot won't open in preview mode",
|
||||
details: null,
|
||||
})
|
||||
} else if (isEmbedded) {
|
||||
sendEventToParent({
|
||||
codeToExecute: parseVariables(variables)(
|
||||
parseChatwootOpenCode(block.options)
|
||||
),
|
||||
})
|
||||
} else {
|
||||
const func = Function(
|
||||
...variables.map((v) => v.id),
|
||||
parseVariables(variables, { fieldToParse: 'id' })(
|
||||
parseChatwootOpenCode(block.options)
|
||||
)
|
||||
)
|
||||
try {
|
||||
await func(...variables.map((v) => parseCorrectValueType(v.value)))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return block.outgoingEdgeId
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Log } from 'db'
|
||||
import { openChatwootWidget } from 'features/chatwoot'
|
||||
import {
|
||||
IntegrationBlock,
|
||||
IntegrationBlockType,
|
||||
@ -25,7 +26,7 @@ import { byId, sendRequest } from 'utils'
|
||||
import { sendGaEvent } from '../../lib/gtag'
|
||||
import { parseVariables, parseVariablesInObject } from './variable'
|
||||
|
||||
type IntegrationContext = {
|
||||
export type IntegrationContext = {
|
||||
apiHost: string
|
||||
typebotId: string
|
||||
groupId: string
|
||||
@ -59,6 +60,8 @@ export const executeIntegration = ({
|
||||
return executeWebhook(block, context)
|
||||
case IntegrationBlockType.EMAIL:
|
||||
return sendEmail(block, context)
|
||||
case IntegrationBlockType.CHATWOOT:
|
||||
return openChatwootWidget(block, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,6 @@ const executeCode = async (
|
||||
{ typebot: { variables } }: LogicContext
|
||||
) => {
|
||||
if (!block.options.content) return
|
||||
console.log('isEmbedded', isEmbedded)
|
||||
if (block.options.shouldExecuteInParentContext && isEmbedded) {
|
||||
sendEventToParent({
|
||||
codeToExecute: parseVariables(variables)(block.options.content),
|
||||
|
1
packages/models/features/chatwoot/index.ts
Normal file
1
packages/models/features/chatwoot/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './types'
|
34
packages/models/features/chatwoot/types.ts
Normal file
34
packages/models/features/chatwoot/types.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
blockBaseSchema,
|
||||
IntegrationBlockType,
|
||||
} from '../../typebot/blocks/shared'
|
||||
|
||||
export const chatwootOptionsSchema = z.object({
|
||||
baseUrl: z.string(),
|
||||
websiteToken: z.string(),
|
||||
user: z
|
||||
.object({
|
||||
id: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
avatarUrl: z.string().optional(),
|
||||
phoneNumber: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export const chatwootBlockSchema = blockBaseSchema.and(
|
||||
z.object({
|
||||
type: z.enum([IntegrationBlockType.CHATWOOT]),
|
||||
options: chatwootOptionsSchema,
|
||||
})
|
||||
)
|
||||
|
||||
export const defaultChatwootOptions: ChatwootOptions = {
|
||||
baseUrl: 'https://app.chatwoot.com',
|
||||
websiteToken: '',
|
||||
}
|
||||
|
||||
export type ChatwootBlock = z.infer<typeof chatwootBlockSchema>
|
||||
export type ChatwootOptions = z.infer<typeof chatwootOptionsSchema>
|
@ -5,3 +5,4 @@ export * from './answer'
|
||||
export * from './utils'
|
||||
export * from './credentials'
|
||||
export * from './webhooks'
|
||||
export * from './features/chatwoot'
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
chatwootBlockSchema,
|
||||
chatwootOptionsSchema,
|
||||
} from '../../../features/chatwoot'
|
||||
import {
|
||||
googleAnalyticsOptionsSchema,
|
||||
googleAnalyticsBlockSchema,
|
||||
@ -17,6 +21,7 @@ const integrationBlockOptionsSchema = googleSheetsOptionsSchema
|
||||
.or(googleAnalyticsOptionsSchema)
|
||||
.or(webhookOptionsSchema)
|
||||
.or(sendEmailOptionsSchema)
|
||||
.or(chatwootOptionsSchema)
|
||||
|
||||
export const integrationBlockSchema = googleSheetsBlockSchema
|
||||
.or(googleAnalyticsBlockSchema)
|
||||
@ -25,6 +30,7 @@ export const integrationBlockSchema = googleSheetsBlockSchema
|
||||
.or(zapierBlockSchema)
|
||||
.or(makeComBlockSchema)
|
||||
.or(pabblyConnectBlockSchema)
|
||||
.or(chatwootBlockSchema)
|
||||
|
||||
export type IntegrationBlock = z.infer<typeof integrationBlockSchema>
|
||||
export type IntegrationBlockOptions = z.infer<
|
||||
|
@ -59,4 +59,5 @@ export enum IntegrationBlockType {
|
||||
ZAPIER = 'Zapier',
|
||||
MAKE_COM = 'Make.com',
|
||||
PABBLY_CONNECT = 'Pabbly',
|
||||
CHATWOOT = 'Chatwoot',
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
import cuid from 'cuid'
|
||||
import {
|
||||
Block,
|
||||
defaultChoiceInputOptions,
|
||||
defaultSettings,
|
||||
defaultTheme,
|
||||
InputBlockType,
|
||||
ItemType,
|
||||
PublicTypebot,
|
||||
Typebot,
|
||||
} from 'models'
|
||||
import { isDefined } from '../utils'
|
||||
import { proWorkspaceId } from './databaseSetup'
|
||||
|
||||
export const parseTestTypebot = (
|
||||
@ -30,19 +34,19 @@ export const parseTestTypebot = (
|
||||
edges: [
|
||||
{
|
||||
id: 'edge1',
|
||||
from: { groupId: 'block0', blockId: 'block0' },
|
||||
to: { groupId: 'block1' },
|
||||
from: { groupId: 'group0', blockId: 'block0' },
|
||||
to: { groupId: 'group1' },
|
||||
},
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
id: 'block0',
|
||||
id: 'group0',
|
||||
title: 'Group #0',
|
||||
blocks: [
|
||||
{
|
||||
id: 'block0',
|
||||
type: 'start',
|
||||
groupId: 'block0',
|
||||
groupId: 'group0',
|
||||
label: 'Start',
|
||||
outgoingEdgeId: 'edge1',
|
||||
},
|
||||
@ -66,20 +70,41 @@ export const parseTypebotToPublicTypebot = (
|
||||
edges: typebot.edges,
|
||||
})
|
||||
|
||||
type Options = {
|
||||
withGoButton?: boolean
|
||||
}
|
||||
|
||||
export const parseDefaultGroupWithBlock = (
|
||||
block: Partial<Block>
|
||||
block: Partial<Block>,
|
||||
options?: Options
|
||||
): Pick<Typebot, 'groups'> => ({
|
||||
groups: [
|
||||
{
|
||||
graphCoordinates: { x: 200, y: 200 },
|
||||
id: 'block1',
|
||||
id: 'group1',
|
||||
blocks: [
|
||||
options?.withGoButton
|
||||
? {
|
||||
id: 'block1',
|
||||
groupId: 'group1',
|
||||
type: InputBlockType.CHOICE,
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
blockId: 'block1',
|
||||
type: ItemType.BUTTON,
|
||||
content: 'Go',
|
||||
},
|
||||
],
|
||||
options: defaultChoiceInputOptions,
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
id: 'block1',
|
||||
groupId: 'block1',
|
||||
id: 'block2',
|
||||
groupId: 'group1',
|
||||
...block,
|
||||
} as Block,
|
||||
],
|
||||
].filter(isDefined) as Block[],
|
||||
title: 'Group #1',
|
||||
},
|
||||
],
|
||||
|
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@ -749,10 +749,6 @@ packages:
|
||||
dependencies:
|
||||
'@babel/highlight': 7.18.6
|
||||
|
||||
/@babel/compat-data/7.19.3:
|
||||
resolution: {integrity: sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
/@babel/compat-data/7.19.4:
|
||||
resolution: {integrity: sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -794,7 +790,7 @@ packages:
|
||||
'@babel/parser': 7.19.6
|
||||
'@babel/template': 7.18.10
|
||||
'@babel/traverse': 7.19.6
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
convert-source-map: 1.8.0
|
||||
debug: 4.3.4
|
||||
gensync: 1.0.0-beta.2
|
||||
@ -816,7 +812,7 @@ packages:
|
||||
resolution: {integrity: sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
'@jridgewell/gen-mapping': 0.3.2
|
||||
jsesc: 2.5.2
|
||||
|
||||
@ -839,7 +835,7 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.19.3
|
||||
'@babel/compat-data': 7.19.4
|
||||
'@babel/core': 7.19.6
|
||||
'@babel/helper-validator-option': 7.18.6
|
||||
browserslist: 4.21.4
|
||||
@ -902,13 +898,13 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/template': 7.18.10
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/helper-hoist-variables/7.18.6:
|
||||
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/helper-member-expression-to-functions/7.18.9:
|
||||
resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==}
|
||||
@ -920,7 +916,7 @@ packages:
|
||||
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.19.3
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/helper-module-transforms/7.19.0:
|
||||
resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==}
|
||||
@ -948,7 +944,7 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
'@babel/template': 7.18.10
|
||||
'@babel/traverse': 7.19.6
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -1002,7 +998,7 @@ packages:
|
||||
resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/helper-skip-transparent-expression-wrappers/7.18.9:
|
||||
resolution: {integrity: sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==}
|
||||
@ -1014,11 +1010,12 @@ packages:
|
||||
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/helper-string-parser/7.18.10:
|
||||
resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: false
|
||||
|
||||
/@babel/helper-string-parser/7.19.4:
|
||||
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
|
||||
@ -1049,7 +1046,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/template': 7.18.10
|
||||
'@babel/traverse': 7.19.6
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -1074,7 +1071,7 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.19.6:
|
||||
resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
|
||||
@ -2026,7 +2023,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.18.6
|
||||
'@babel/parser': 7.19.6
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
|
||||
/@babel/traverse/7.19.3_supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==}
|
||||
@ -2057,7 +2054,7 @@ packages:
|
||||
'@babel/helper-hoist-variables': 7.18.6
|
||||
'@babel/helper-split-export-declaration': 7.18.6
|
||||
'@babel/parser': 7.19.6
|
||||
'@babel/types': 7.19.4
|
||||
'@babel/types': 7.20.0
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
@ -2070,6 +2067,7 @@ packages:
|
||||
'@babel/helper-string-parser': 7.18.10
|
||||
'@babel/helper-validator-identifier': 7.19.1
|
||||
to-fast-properties: 2.0.0
|
||||
dev: false
|
||||
|
||||
/@babel/types/7.19.4:
|
||||
resolution: {integrity: sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==}
|
||||
@ -7692,7 +7690,7 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001414
|
||||
caniuse-lite: 1.0.30001429
|
||||
electron-to-chromium: 1.4.270
|
||||
node-releases: 2.0.6
|
||||
update-browserslist-db: 1.0.9_browserslist@4.21.4
|
||||
@ -7845,6 +7843,7 @@ packages:
|
||||
|
||||
/caniuse-lite/1.0.30001414:
|
||||
resolution: {integrity: sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==}
|
||||
dev: false
|
||||
|
||||
/caniuse-lite/1.0.30001429:
|
||||
resolution: {integrity: sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==}
|
||||
|
Reference in New Issue
Block a user