2
0

♻️ (builder) Change to features-centric folder structure

This commit is contained in:
Baptiste Arnaud
2022-11-15 09:35:48 +01:00
committed by Baptiste Arnaud
parent 3686465a85
commit 643571fe7d
683 changed files with 3907 additions and 3643 deletions

View File

@ -0,0 +1,80 @@
import {
HStack,
Tooltip,
EditablePreview,
EditableInput,
Text,
Editable,
Button,
ButtonProps,
useEditableControls,
} from '@chakra-ui/react'
import { EditIcon } from '@/components/icons'
import { CopyButton } from '@/components/CopyButton'
import { useToast } from '@/hooks/useToast'
import React, { useState } from 'react'
type EditableUrlProps = {
hostname: string
pathname?: string
onPathnameChange: (pathname: string) => void
}
export const EditableUrl = ({
hostname,
pathname,
onPathnameChange,
}: EditableUrlProps) => {
const { showToast } = useToast()
const [value, setValue] = useState(pathname)
const handleSubmit = (newPathname: string) => {
if (/^[a-z0-9-]*$/.test(newPathname)) return onPathnameChange(newPathname)
setValue(pathname)
showToast({
title: 'Invalid ID',
description: 'Should contain only contain letters, numbers and dashes.',
})
}
return (
<Editable
as={HStack}
spacing={3}
value={value}
onChange={setValue}
onSubmit={handleSubmit}
>
<HStack spacing={1}>
<Text>{hostname}/</Text>
<Tooltip label="Edit">
<EditablePreview
mx={1}
borderWidth="1px"
px={3}
rounded="md"
cursor="text"
display="flex"
fontWeight="semibold"
/>
</Tooltip>
<EditableInput px={2} />
</HStack>
<HStack>
<EditButton size="xs" />
<CopyButton size="xs" textToCopy={`${hostname}/${pathname ?? ''}`} />
</HStack>
</Editable>
)
}
const EditButton = (props: ButtonProps) => {
const { isEditing, getEditButtonProps } = useEditableControls()
return isEditing ? null : (
<Button leftIcon={<EditIcon />} {...props} {...getEditButtonProps()}>
Edit
</Button>
)
}

View File

@ -0,0 +1,140 @@
import {
Button,
HStack,
IconButton,
Stack,
Tooltip,
Text,
Menu,
MenuButton,
MenuList,
MenuItem,
useDisclosure,
ButtonProps,
} from '@chakra-ui/react'
import {
ChevronLeftIcon,
CloudOffIcon,
LockedIcon,
UnlockedIcon,
} from '@/components/icons'
import { useTypebot } from '@/features/editor'
import { useWorkspace } from '@/features/workspace'
import { InputBlockType } from 'models'
import { useRouter } from 'next/router'
import { isNotDefined } from 'utils'
import { ChangePlanModal, isFreePlan, LimitReached } from '@/features/billing'
import { timeSince } from '@/utils/helpers'
export const PublishButton = (props: ButtonProps) => {
const { workspace } = useWorkspace()
const { push, query } = useRouter()
const { isOpen, onOpen, onClose } = useDisclosure()
const {
isPublishing,
isPublished,
publishTypebot,
publishedTypebot,
restorePublishedTypebot,
typebot,
isSavingLoading,
updateTypebot,
unpublishTypebot,
save,
} = useTypebot()
const hasInputFile = typebot?.groups
.flatMap((g) => g.blocks)
.some((b) => b.type === InputBlockType.FILE)
const handlePublishClick = () => {
if (isFreePlan(workspace) && hasInputFile) return onOpen()
publishTypebot()
if (!publishedTypebot) push(`/typebots/${query.typebotId}/share`)
}
const closeTypebot = async () => {
updateTypebot({ isClosed: true })
await save()
}
const openTypebot = async () => {
updateTypebot({ isClosed: false })
await save()
}
return (
<HStack spacing="1px">
<ChangePlanModal
isOpen={isOpen}
onClose={onClose}
type={LimitReached.FILE_INPUT}
/>
<Tooltip
borderRadius="md"
hasArrow
placement="bottom-end"
label={
<Stack>
<Text>There are non published changes.</Text>
<Text fontStyle="italic">
Published version from{' '}
{publishedTypebot &&
timeSince(publishedTypebot.updatedAt.toString())}{' '}
ago
</Text>
</Stack>
}
isDisabled={isNotDefined(publishedTypebot) || isPublished}
>
<Button
colorScheme="blue"
isLoading={isPublishing || isSavingLoading}
isDisabled={isPublished}
onClick={handlePublishClick}
borderRightRadius={publishedTypebot ? 0 : undefined}
{...props}
>
{isPublished
? typebot?.isClosed
? 'Closed'
: 'Published'
: 'Publish'}
</Button>
</Tooltip>
{publishedTypebot && (
<Menu>
<MenuButton
as={IconButton}
colorScheme={'blue'}
borderLeftRadius={0}
icon={<ChevronLeftIcon transform="rotate(-90deg)" />}
aria-label="Show published typebot menu"
size="sm"
isDisabled={isPublishing || isSavingLoading}
/>
<MenuList>
{!isPublished && (
<MenuItem onClick={restorePublishedTypebot}>
Restore published version
</MenuItem>
)}
{!typebot?.isClosed ? (
<MenuItem onClick={closeTypebot} icon={<LockedIcon />}>
Close typebot to new responses
</MenuItem>
) : (
<MenuItem onClick={openTypebot} icon={<UnlockedIcon />}>
Reopen typebot to new responses
</MenuItem>
)}
<MenuItem onClick={unpublishTypebot} icon={<CloudOffIcon />}>
Unpublish typebot
</MenuItem>
</MenuList>
</Menu>
)}
</HStack>
)
}

View File

@ -0,0 +1,133 @@
import { TrashIcon } from '@/components/icons'
import { Seo } from '@/components/Seo'
import {
isProPlan,
LimitReached,
LockTag,
UpgradeButton,
} from '@/features/billing'
import { CustomDomainsDropdown } from '@/features/customDomains'
import { TypebotHeader, useTypebot } from '@/features/editor'
import { useWorkspace } from '@/features/workspace'
import { useToast } from '@/hooks/useToast'
import {
Flex,
Heading,
HStack,
IconButton,
Stack,
Wrap,
Text,
} from '@chakra-ui/react'
import { Plan } from 'db'
import { isDefined, getViewerUrl, isNotDefined } from 'utils'
import { isPublicDomainAvailableQuery } from '../queries/isPublicDomainAvailableQuery'
import { parseDefaultPublicId } from '../utils'
import { EditableUrl } from './EditableUrl'
import { integrationsList } from './embeds/EmbedButton'
export const SharePage = () => {
const { workspace } = useWorkspace()
const { typebot, updateTypebot } = useTypebot()
const { showToast } = useToast()
const handlePublicIdChange = async (publicId: string) => {
if (publicId === typebot?.publicId) return
if (publicId.length < 4)
return showToast({ description: 'ID must be longer than 4 characters' })
const { data } = await isPublicDomainAvailableQuery(publicId)
if (!data?.isAvailable)
return showToast({ description: 'ID is already taken' })
updateTypebot({ publicId })
}
const publicId = typebot
? typebot?.publicId ?? parseDefaultPublicId(typebot.name, typebot.id)
: ''
const isPublished = isDefined(typebot?.publishedTypebotId)
const handlePathnameChange = (pathname: string) => {
if (!typebot?.customDomain) return
const existingHost = typebot.customDomain?.split('/')[0]
const newDomain =
pathname === '' ? existingHost : existingHost + '/' + pathname
handleCustomDomainChange(newDomain)
}
const handleCustomDomainChange = (customDomain: string | undefined) =>
updateTypebot({ customDomain })
return (
<Flex flexDir="column" pb="40">
<Seo title="Share" />
<TypebotHeader />
<Flex h="full" w="full" justifyContent="center" align="flex-start">
<Stack maxW="1000px" w="full" pt="10" spacing={10}>
<Stack spacing={4} align="flex-start">
<Heading fontSize="2xl" as="h1">
Your typebot link
</Heading>
{typebot && (
<EditableUrl
hostname={
getViewerUrl({ isBuilder: true }) ?? 'https://typebot.io'
}
pathname={publicId}
onPathnameChange={handlePublicIdChange}
/>
)}
{typebot?.customDomain && (
<HStack>
<EditableUrl
hostname={'https://' + typebot.customDomain.split('/')[0]}
pathname={typebot.customDomain.split('/')[1]}
onPathnameChange={handlePathnameChange}
/>
<IconButton
icon={<TrashIcon />}
aria-label="Remove custom domain"
size="xs"
onClick={() => handleCustomDomainChange(undefined)}
/>
</HStack>
)}
{isNotDefined(typebot?.customDomain) ? (
<>
{isProPlan(workspace) ? (
<CustomDomainsDropdown
onCustomDomainSelect={handleCustomDomainChange}
/>
) : (
<UpgradeButton
colorScheme="gray"
limitReachedType={LimitReached.CUSTOM_DOMAIN}
>
<Text mr="2">Add my domain</Text>{' '}
<LockTag plan={Plan.PRO} />
</UpgradeButton>
)}
</>
) : null}
</Stack>
<Stack spacing={4}>
<Heading fontSize="2xl" as="h1">
Embed your typebot
</Heading>
<Wrap spacing={7}>
{integrationsList.map((IntegrationButton, idx) => (
<IntegrationButton
key={idx}
publicId={publicId}
isPublished={isPublished}
/>
))}
</Wrap>
</Stack>
</Stack>
</Flex>
</Flex>
)
}

View File

@ -0,0 +1,146 @@
import { Button, useDisclosure, VStack, WrapItem, Text } from '@chakra-ui/react'
import {
WordpressLogo,
ShopifyLogo,
WixLogo,
GtmLogo,
JavascriptLogo,
ReactLogo,
NotionLogo,
WebflowLogo,
IframeLogo,
OtherLogo,
} from './logos'
import React from 'react'
import {
WordpressModal,
ShopifyModal,
WebflowModal,
GtmModal,
JavascriptModal,
ReactModal,
NotionModal,
IframeModal,
WixModal,
} from './modals'
export type ModalProps = {
publicId: string
isPublished: boolean
isOpen: boolean
onClose: () => void
}
type EmbedButtonProps = Pick<ModalProps, 'publicId' | 'isPublished'> & {
logo: JSX.Element
label: string
Modal: (props: ModalProps) => JSX.Element
}
export const EmbedButton = ({
logo,
label,
Modal,
...modalProps
}: EmbedButtonProps) => {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<WrapItem
as={Button}
alignItems="center"
variant="outline"
style={{ width: '225px', height: '270px' }}
onClick={onOpen}
whiteSpace={'normal'}
>
<VStack>
{logo}
<Text>{label}</Text>
</VStack>
<Modal isOpen={isOpen} onClose={onClose} {...modalProps} />
</WrapItem>
)
}
export const integrationsList = [
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<WordpressLogo height={100} width="70px" />}
label="Wordpress"
Modal={WordpressModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<ShopifyLogo height={100} width="65px" />}
label="Shopify"
Modal={ShopifyModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<WixLogo height={100} width="90px" />}
label="Wix"
Modal={WixModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<GtmLogo height={100} width="70px" />}
label="Google Tag Manager"
Modal={GtmModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<JavascriptLogo height={100} width="70px" />}
label="HTML & Javascript"
Modal={JavascriptModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<ReactLogo height={100} width="70px" />}
label="React"
Modal={ReactModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<NotionLogo height={100} width="60px" />}
label="Notion"
Modal={NotionModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<WebflowLogo height={100} width="70px" />}
label="Webflow"
Modal={WebflowModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<IframeLogo height={100} width="70px" />}
label="Iframe"
Modal={IframeModal}
{...props}
/>
),
(props: Pick<ModalProps, 'publicId' | 'isPublished'>) => (
<EmbedButton
logo={<OtherLogo height={100} width="70px" />}
label="Other"
Modal={JavascriptModal}
{...props}
/>
),
]

View File

@ -0,0 +1,41 @@
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { env, getViewerUrl } from 'utils'
import { FlexProps } from '@chakra-ui/react'
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
onCopied?: () => void
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
export const ChatEmbedCode = ({
proactiveMessage,
button,
}: ChatEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
button,
proactiveMessage,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: BubbleParams): string => {
const jsCode = parseInitBubbleCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -0,0 +1,224 @@
import {
StackProps,
Stack,
Heading,
HStack,
Input,
Flex,
FormControl,
FormLabel,
NumberInput,
NumberInputField,
Switch,
Text,
Image,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
} from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { useUser } from '@/features/account'
import { useState, useEffect } from 'react'
import { BubbleParams } from 'typebot-js'
import { ColorPicker } from '@/components/ColorPicker'
type ChatEmbedSettingsProps = {
onUpdateSettings: (
windowSettings: Pick<BubbleParams, 'button' | 'proactiveMessage'>
) => void
}
export const ChatEmbedSettings = ({
onUpdateSettings,
...props
}: ChatEmbedSettingsProps & StackProps) => {
const { user } = useUser()
const { typebot } = useTypebot()
const [proactiveMessageChecked, setProactiveMessageChecked] = useState(false)
const [isCustomIconChecked, setIsCustomIconChecked] = useState(false)
const [rememberProMessageChecked] = useState(true)
const [customIconInputValue, setCustomIconInputValue] = useState('')
const [inputValues, setInputValues] = useState({
messageDelay: '0',
messageContent: 'I have a question for you!',
avatarUrl: typebot?.theme.chat.hostAvatar?.url ?? user?.image ?? '',
})
const [bubbleColor, setBubbleColor] = useState(
typebot?.theme.chat.buttons.backgroundColor ?? '#0042DA'
)
const [bubbleIconColor, setIconBubbleColor] = useState(
typebot?.theme.chat.buttons.color ?? '#FFFFFF'
)
useEffect(() => {
if (proactiveMessageChecked) {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
iconColor:
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
},
proactiveMessage: {
delay: parseInt(inputValues.messageDelay) * 1000,
textContent: inputValues.messageContent,
avatarUrl: inputValues.avatarUrl,
rememberClose: rememberProMessageChecked,
},
})
} else {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: isCustomIconChecked ? customIconInputValue : undefined,
iconColor:
bubbleIconColor === '#FFFFFF' ? undefined : bubbleIconColor,
},
proactiveMessage: undefined,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
inputValues,
bubbleColor,
rememberProMessageChecked,
customIconInputValue,
bubbleIconColor,
proactiveMessageChecked,
isCustomIconChecked,
])
return (
<Stack {...props} spacing="4">
<Heading fontSize="md" fontWeight="semibold">
Chat bubble settings
</Heading>
<Flex justify="space-between" align="center">
<Text>Button color</Text>
<ColorPicker
initialColor={bubbleColor}
onColorChange={setBubbleColor}
/>
</Flex>
<HStack justify="space-between">
<Text>Icon color</Text>
<ColorPicker
initialColor={bubbleIconColor}
onColorChange={setIconBubbleColor}
/>
</HStack>
<HStack justifyContent="space-between">
<FormLabel htmlFor="custom-icon" mb="1" flexShrink={0}>
Custom button icon?
</FormLabel>
<Switch
id="custom-icon"
onChange={() => setIsCustomIconChecked(!isCustomIconChecked)}
isChecked={isCustomIconChecked}
/>
</HStack>
{isCustomIconChecked && (
<>
<HStack pl="4">
<Text>Url:</Text>
<Input
placeholder={'Paste image link (.png, .svg)'}
value={customIconInputValue}
onChange={(e) => setCustomIconInputValue(e.target.value)}
minW="0"
/>
</HStack>
</>
)}
<Flex alignItems="center">
<FormControl
display="flex"
alignItems="center"
w="full"
justifyContent="space-between"
pr={1}
>
<FormLabel htmlFor="fullscreen-option" mb="1">
Enable popup message?
</FormLabel>
<Switch
id="fullscreen-option"
onChange={() =>
setProactiveMessageChecked(!proactiveMessageChecked)
}
isChecked={proactiveMessageChecked}
/>
</FormControl>
</Flex>
{proactiveMessageChecked && (
<>
<Flex pl="4">
<HStack
bgColor="white"
shadow="md"
rounded="md"
p="3"
maxW="280px"
spacing={4}
>
{inputValues.avatarUrl && (
// eslint-disable-next-line jsx-a11y/alt-text
<Image src={inputValues.avatarUrl} w="40px" rounded="full" />
)}
<Text>{inputValues.messageContent}</Text>
</HStack>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Appearance delay</Text>
<NumberInput
onChange={(messageDelay) =>
setInputValues({
...inputValues,
messageDelay,
})
}
value={inputValues.messageDelay}
min={0}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Avatar URL</Text>
<Input
type="text"
onChange={(e) =>
setInputValues({
...inputValues,
avatarUrl: e.target.value,
})
}
value={inputValues.avatarUrl}
placeholder={'Paste image link (.png, .jpg)'}
/>
</Flex>
<Flex justify="space-between" align="center" pl="4">
<Text>Message content</Text>
<Input
type="text"
onChange={(e) =>
setInputValues({
...inputValues,
messageContent: e.target.value,
})
}
value={inputValues.messageContent}
/>
</Flex>
</>
)}
</Stack>
)
}

View File

@ -0,0 +1,59 @@
import { FlexProps } from '@chakra-ui/react'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitContainerCode, typebotJsHtml } from '../params'
import { IframeParams } from 'typebot-js'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { env, getViewerUrl } from 'utils'
type ContainerEmbedCodeProps = {
widthLabel: string
heightLabel: string
withStarterVariables?: boolean
onCopied?: () => void
}
export const ContainerEmbedCode = ({
widthLabel,
heightLabel,
}: ContainerEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<ContainerEmbedCodeProps, 'widthLabel' | 'heightLabel'>
const parseSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
customDomain,
hiddenVariables,
backgroundColor,
url,
})
return `${typebotJsHtml}
<div id="typebot-container" style="width: ${embedProps.widthLabel}; height: ${embedProps.heightLabel};"></div>
<script>${jsCode}</script>`
}

View File

@ -0,0 +1,120 @@
import {
StackProps,
Stack,
Flex,
Heading,
FormControl,
FormLabel,
Switch,
Input,
HStack,
Text,
} from '@chakra-ui/react'
import { DropdownList } from '@/components/DropdownList'
import { useState, useEffect } from 'react'
type StandardEmbedWindowSettingsProps = {
onUpdateWindowSettings: (windowSettings: {
heightLabel: string
widthLabel: string
}) => void
}
export const StandardEmbedWindowSettings = ({
onUpdateWindowSettings,
...props
}: StandardEmbedWindowSettingsProps & StackProps) => {
const [fullscreen, setFullscreen] = useState(false)
const [inputValues, setInputValues] = useState({
widthValue: '100',
widthType: '%',
heightValue: '600',
heightType: 'px',
})
useEffect(() => {
onUpdateWindowSettings({
widthLabel: fullscreen
? '100%'
: inputValues.widthValue + inputValues.widthType,
heightLabel: fullscreen
? '100vh'
: inputValues.heightValue + inputValues.heightType,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValues, fullscreen])
const handleWidthTypeSelect = (widthType: string) =>
setInputValues({ ...inputValues, widthType })
const handleHeightTypeSelect = (heightType: string) =>
setInputValues({ ...inputValues, heightType })
return (
<Stack {...props}>
<Flex alignItems="center" justifyContent="space-between">
<Heading fontSize="md" fontWeight="semibold" style={{ flexShrink: 0 }}>
Window settings
</Heading>
<FormControl
display="flex"
alignItems="center"
w="full"
justifyContent="flex-end"
>
<FormLabel htmlFor="fullscreen-option" mb="1">
Set to fullscreen?
</FormLabel>
<Switch
id="fullscreen-option"
onChange={() => setFullscreen(!fullscreen)}
isChecked={fullscreen}
/>
</FormControl>
</Flex>
{!fullscreen && (
<>
<Flex justify="space-between" align="center" mb="2">
<Text>Width</Text>
<HStack>
<Input
onChange={(e) =>
setInputValues({
...inputValues,
widthValue: e.target.value,
})
}
w="70px"
value={inputValues.widthValue}
/>
<DropdownList<string>
items={['px', '%']}
onItemSelect={handleWidthTypeSelect}
currentItem={inputValues.widthType}
/>
</HStack>
</Flex>
<Flex justify="space-between" align="center" mb="2">
<Text>Height</Text>
<HStack>
<Input
onChange={(e) =>
setInputValues({
...inputValues,
heightValue: e.target.value,
})
}
w="70px"
value={inputValues.heightValue}
/>
<DropdownList<string>
items={['px', '%']}
onItemSelect={handleHeightTypeSelect}
currentItem={inputValues.heightType}
/>
</HStack>
</Flex>
</>
)}
</Stack>
)
}

View File

@ -0,0 +1,22 @@
import { FlexProps } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
type Props = {
widthLabel: string
heightLabel: string
onCopied?: () => void
}
export const IframeEmbedCode = ({
widthLabel,
heightLabel,
}: Props & FlexProps) => {
const { typebot } = useTypebot()
const src = `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" style="border: none"></iframe>`
return <CodeEditor value={code} lang="html" isReadOnly />
}

View File

@ -0,0 +1,37 @@
import { FlexProps } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
import { env, getViewerUrl } from 'utils'
import { parseInitPopupCode, typebotJsHtml } from '../params'
import { CodeEditor } from '@/components/CodeEditor'
type PopupEmbedCodeProps = {
delay?: number
withStarterVariables?: boolean
onCopied?: () => void
}
export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
delay,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: PopupParams): string => {
const jsCode = parseInitPopupCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -0,0 +1,66 @@
import {
StackProps,
Stack,
Flex,
Heading,
NumberInput,
NumberInputField,
Switch,
HStack,
NumberIncrementStepper,
NumberDecrementStepper,
} from '@chakra-ui/react'
import { useState, useEffect } from 'react'
import { PopupParams } from 'typebot-js'
type PopupEmbedSettingsProps = {
onUpdateSettings: (windowSettings: Pick<PopupParams, 'delay'>) => void
}
export const PopupEmbedSettings = ({
onUpdateSettings,
...props
}: PopupEmbedSettingsProps & StackProps) => {
const [isEnabled, setIsEnabled] = useState(false)
const [inputValue, setInputValue] = useState(0)
useEffect(() => {
onUpdateSettings({
delay: isEnabled ? inputValue * 1000 : undefined,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue, isEnabled])
return (
<Stack {...props}>
<Flex alignItems="center" justifyContent="space-between">
<Heading fontSize="md" fontWeight="semibold">
Popup settings
</Heading>
</Flex>
<Flex justify="space-between" align="center" mb="2">
<HStack>
<p>Appearance delay</p>
<Switch
isChecked={isEnabled}
onChange={(e) => setIsEnabled(e.target.checked)}
/>
</HStack>
{isEnabled && (
<NumberInput
onChange={(_, val) => setInputValue(val)}
value={inputValue}
min={0}
>
<NumberInputField />
<NumberIncrementStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberIncrementStepper>
</NumberInput>
)}
</Flex>
</Stack>
)
}

View File

@ -0,0 +1,161 @@
import { FlexProps } from '@chakra-ui/react'
import React from 'react'
import { BubbleParams, IframeParams, PopupParams } from 'typebot-js'
import {
parseInitBubbleCode,
parseInitContainerCode,
parseInitPopupCode,
} from './params'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { useTypebot } from '@/features/editor'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
widthLabel,
heightLabel,
}: StandardReactDivProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseContainerSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<StandardReactDivProps, 'widthLabel' | 'heightLabel'>
const parseContainerSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <div id="typebot-container" style={{width: "${embedProps.widthLabel}", height: "${embedProps.heightLabel}"}} />
}`
}
type PopupEmbedCodeProps = {
delay?: number
withStarterVariables?: boolean
}
export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parsePopupSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
delay,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parsePopupSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
delay,
}: PopupParams): string => {
const jsCode = parseInitPopupCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
delay,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <></>;
}`
}
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
} & Pick<BubbleParams, 'button' | 'proactiveMessage'>
export const ChatReactCode = ({
proactiveMessage,
button,
}: ChatEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseBubbleSnippet({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${typebot?.publicId}`,
button,
proactiveMessage,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parseBubbleSnippet = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
}: BubbleParams): string => {
const jsCode = parseInitBubbleCode({
url,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
})
return `import Typebot from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <></>
}`
}

View File

@ -0,0 +1,140 @@
import {
BubbleParams,
ButtonParams,
IframeParams,
PopupParams,
ProactiveMessageParams,
} from 'typebot-js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { isDefined } from 'utils'
const parseStringParam = (fieldName: string, fieldValue?: string) =>
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
const parseNonStringParam = (
fieldName: string,
fieldValue?: number | boolean
) => (isDefined(fieldValue) ? `${fieldName}: ${fieldValue},` : ``)
const parseCustomDomain = (domain?: string): string =>
parseStringParam('customDomain', domain)
const parseHiddenVariables = (
variables: { [key: string]: string | undefined } | undefined
): string => (variables ? `hiddenVariables: ${JSON.stringify(variables)},` : ``)
const parseBackgroundColor = (bgColor?: string): string =>
parseStringParam('backgroundColor', bgColor)
const parseDelay = (delay?: number) => parseNonStringParam('delay', delay)
const parseButton = (button?: ButtonParams): string => {
if (!button) return ''
const iconUrlString = parseStringParam('iconUrl', button.iconUrl)
const buttonColorstring = parseStringParam('color', button.color)
const buttonIconColorString = parseStringParam('iconColor', button.iconColor)
return `button: {${iconUrlString}${buttonColorstring}${buttonIconColorString}},`
}
const parseProactiveMessage = (
proactiveMessage?: ProactiveMessageParams
): string => {
if (!proactiveMessage) return ``
const { avatarUrl, textContent, delay } = proactiveMessage
const avatarUrlString = parseStringParam('avatarUrl', avatarUrl)
const textContentString = parseStringParam('textContent', textContent)
const delayString = parseNonStringParam('delay', delay)
return `proactiveMessage: {${avatarUrlString}${textContentString}${delayString}},`
}
const parseIframeParams = ({
customDomain,
hiddenVariables,
backgroundColor,
}: Pick<
IframeParams,
'customDomain' | 'hiddenVariables' | 'backgroundColor'
>) => ({
customDomainString: parseCustomDomain(customDomain),
hiddenVariablesString: parseHiddenVariables(hiddenVariables),
bgColorString: parseBackgroundColor(backgroundColor),
})
const parsePopupParams = ({ delay }: Pick<PopupParams, 'delay'>) => ({
delayString: parseDelay(delay),
})
const parseBubbleParams = ({
button,
proactiveMessage,
}: Pick<BubbleParams, 'button' | 'proactiveMessage'>) => ({
proactiveMessageString: parseProactiveMessage(proactiveMessage),
buttonString: parseButton(button),
})
export const parseInitContainerCode = ({
url,
customDomain,
backgroundColor,
hiddenVariables,
}: IframeParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
return prettier.format(
`Typebot.initContainer("typebot-container", {
url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}
});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitPopupCode = ({
url,
customDomain,
hiddenVariables,
backgroundColor,
delay,
}: PopupParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
const { delayString } = parsePopupParams({ delay })
return prettier.format(
`var typebotCommands = Typebot.initPopup({url: "${url}",${delayString}${bgColorString}${customDomainString}${hiddenVariablesString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitBubbleCode = ({
url,
customDomain,
hiddenVariables,
backgroundColor,
button,
proactiveMessage,
}: BubbleParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
const { buttonString, proactiveMessageString } = parseBubbleParams({
button,
proactiveMessage,
})
return prettier.format(
`var typebotCommands = Typebot.initBubble({url: "${url}",${bgColorString}${customDomainString}${hiddenVariablesString}${proactiveMessageString}${buttonString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.2"></script>`

View File

@ -0,0 +1,29 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const GtmLogo = (props: IconProps) => (
<Icon
width="314"
height="315"
viewBox="0 0 314 315"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M184.34 302.11L129.819 247.525L246.935 129.214L302.461 184.727L184.34 302.11Z"
fill="#8AB4F8"
/>
<path
d="M184.735 67.0265L129.222 11.501L11.5085 129.215C-3.82556 144.536 -3.83828 169.368 11.4831 184.702C11.4958 184.715 11.5085 184.727 11.5085 184.727L129.222 302.441L183.68 247.729L94.7269 157.047L184.735 67.0265Z"
fill="#4285F4"
/>
<path
d="M302.449 129.214L184.735 11.5005C169.401 -3.83351 144.544 -3.83351 129.21 11.5005C113.875 26.8346 113.875 51.692 129.21 67.0134L246.987 184.727C262.321 200.061 287.178 200.061 302.5 184.727C317.834 169.393 317.834 144.536 302.5 129.214H302.449Z"
fill="#8AB4F8"
/>
<path
d="M156.546 314.049C178.034 314.049 195.453 296.63 195.453 275.142C195.453 253.654 178.034 236.234 156.546 236.234C135.058 236.234 117.639 253.654 117.639 275.142C117.639 296.63 135.058 314.049 156.546 314.049Z"
fill="#246FDB"
/>
</Icon>
)

View File

@ -0,0 +1,50 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const IframeLogo = (props: IconProps) => {
return (
<Icon
id="Capa_1"
enableBackground="new 0 0 512 512"
height="512"
viewBox="0 0 512 512"
width="512"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g>
<g>
<path
d="m512 141.17v229.66c0 39.96-32.51 72.47-72.46 72.47h-367.08c-39.95 0-72.46-32.51-72.46-72.47v-229.66c0-39.96 32.51-72.47 72.46-72.47h367.08c39.95 0 72.46 32.51 72.46 72.47z"
fill="#6aa9ff"
/>
</g>
<path
d="m512 141.17v229.66c0 39.96-32.51 72.47-72.46 72.47h-183.54v-374.6h183.54c39.95 0 72.46 32.51 72.46 72.47z"
fill="#4987ea"
/>
<g>
<path
d="m146.16 349.223-78.4-78.4c-5.858-5.858-5.858-15.355 0-21.213l86.833-86.833c5.857-5.858 15.355-5.858 21.213 0s5.858 15.355 0 21.213l-76.226 76.226 67.793 67.794c5.858 5.858 5.858 15.355 0 21.213-5.857 5.858-15.355 5.859-21.213 0z"
fill="#f0f7ff"
/>
</g>
<g>
<path
d="m336.194 349.223c-5.858-5.858-5.858-15.355 0-21.213l76.226-76.227-67.793-67.794c-5.858-5.858-5.858-15.355 0-21.213 5.857-5.858 15.355-5.858 21.213 0l78.4 78.4c5.858 5.858 5.858 15.355 0 21.213l-86.833 86.833c-5.856 5.859-15.355 5.86-21.213.001z"
fill="#dfe7f4"
/>
</g>
<g>
<path
d="m309.54 148.7-53.54 151.6-25.78 72.99c-2.792 7.888-11.443 11.903-19.14 9.15-7.81-2.76-11.91-11.33-9.15-19.14l54.07-153.1 25.25-71.49c2.76-7.81 11.33-11.91 19.14-9.15s11.91 11.33 9.15 19.14z"
fill="#f0f7ff"
/>
</g>
<path
d="m309.54 148.7-53.54 151.6v-90.1l25.25-71.49c2.76-7.81 11.33-11.91 19.14-9.15s11.91 11.33 9.15 19.14z"
fill="#dfe7f4"
/>
</g>
</Icon>
)
}

View File

@ -0,0 +1,16 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const JavascriptLogo = (props: IconProps) => (
<Icon
viewBox="0 0 448 448"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<rect width="448" height="448" fill="#2B2B2B" />
<path
d="M0 0V448H448V0H0ZM240 348C240 391.61 214.24 412.87 176.95 412.87C143.27 412.87 123.72 395.43 113.8 374.38L148.08 353.63C154.69 365.36 159.71 375.28 174.14 375.28C186.14 375.28 196 369.87 196 348.82V208H240V348ZM339.35 411.87C300.26 411.87 275 394.23 262.67 369.87L297 350C306 364.74 317.75 374.56 338.5 374.56C355.94 374.56 366.07 366.84 366.07 354.81C366.07 340.38 355.64 335.27 336.39 326.81L325.87 322.29C295.49 309.37 275.35 293.13 275.35 258.84C275.35 227.27 299.4 204.21 336.99 204.21C363.76 204.21 382.99 212.53 396.84 236.89L364 258C356.78 245.07 349 240 336.94 240C324.61 240 316.79 247.82 316.79 258C316.79 270.63 324.61 275.74 342.65 283.56L353.17 288.07C388.96 303.41 409.11 319.07 409.11 354.23C409.12 392.13 379.35 411.87 339.35 411.87Z"
fill="#F7DF1E"
/>
</Icon>
)

View File

@ -0,0 +1,19 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const NotionLogo = (props: IconProps) => (
<Icon
width="246"
height="246"
viewBox="0 0 246 246"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M46.0982 43.7783C53.7102 49.9627 56.5657 49.4909 70.8599 48.5371L205.616 40.4456C208.474 40.4456 206.098 37.5944 205.144 37.1206L182.764 20.9415C178.476 17.6122 172.763 13.7995 161.813 14.7532L31.3283 24.2703C26.5695 24.7422 25.6191 27.1216 27.5142 29.0287L46.0982 43.7783ZM54.1887 75.1833V216.97C54.1887 224.59 57.9966 227.441 66.5672 226.97L214.664 218.4C223.239 217.929 224.194 212.688 224.194 206.497V65.6619C224.194 59.4818 221.817 56.1491 216.568 56.6248L61.805 65.6619C56.0934 66.1419 54.1883 68.9989 54.1883 75.1833H54.1887ZM200.39 82.789C201.339 87.0755 200.39 91.3581 196.095 91.84L188.96 93.2618V197.938C182.764 201.268 177.051 203.172 172.291 203.172C164.668 203.172 162.759 200.791 157.05 193.658L110.375 120.384V191.278L125.145 194.611C125.145 194.611 125.145 203.172 113.229 203.172L80.3785 205.077C79.4242 203.172 80.3785 198.418 83.7107 197.465L92.2832 195.089V101.353L80.3809 100.399C79.4261 96.1126 81.8036 89.932 88.4753 89.4525L123.716 87.0769L172.291 161.305V95.6407L159.906 94.2194C158.955 88.9792 162.759 85.1742 167.522 84.7023L200.39 82.789ZM20.3726 11.4244L156.097 1.42918C172.765 -0.000288379 177.053 0.957344 187.529 8.56689L230.854 39.0181C238.003 44.2545 240.386 45.6801 240.386 51.3884V218.4C240.386 228.867 236.572 235.057 223.242 236.005L65.624 245.523C55.6168 246 50.8541 244.574 45.6134 237.908L13.7082 196.513C7.99173 188.893 5.61426 183.192 5.61426 176.523V28.0715C5.61426 19.512 9.42842 12.3719 20.3726 11.4244V11.4244Z"
fill="black"
/>
</Icon>
)

View File

@ -0,0 +1,31 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const OtherLogo = (props: IconProps) => (
<Icon
width="512"
height="512"
viewBox="0 0 512 512"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M256 282C270.359 282 282 270.359 282 256C282 241.641 270.359 230 256 230C241.641 230 230 241.641 230 256C230 270.359 241.641 282 256 282Z"
fill="black"
/>
<path
d="M346 282C360.359 282 372 270.359 372 256C372 241.641 360.359 230 346 230C331.641 230 320 241.641 320 256C320 270.359 331.641 282 346 282Z"
fill="black"
/>
<path
d="M166 282C180.359 282 192 270.359 192 256C192 241.641 180.359 230 166 230C151.641 230 140 241.641 140 256C140 270.359 151.641 282 166 282Z"
fill="black"
/>
<path
d="M448 256C448 150 362 64 256 64C150 64 64 150 64 256C64 362 150 448 256 448C362 448 448 362 448 256Z"
stroke="black"
strokeWidth="32"
strokeMiterlimit="10"
/>
</Icon>
)

View File

@ -0,0 +1,21 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const ReactLogo = (props: IconProps) => (
<Icon
width="492"
height="437"
viewBox="0 0 492 437"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M491.3 218.5C491.3 186 450.6 155.2 388.2 136.1C402.6 72.5 396.2 21.9 368 5.7C361.5 1.9 353.9 0.0999985 345.6 0.0999985V22.4C350.2 22.4 353.9 23.3 357 25C370.6 32.8 376.5 62.5 371.9 100.7C370.8 110.1 369 120 366.8 130.1C347.2 125.3 325.8 121.6 303.3 119.2C289.8 100.7 275.8 83.9 261.7 69.2C294.3 38.9 324.9 22.3 345.7 22.3V0C318.2 0 282.2 19.6 245.8 53.6C209.4 19.8 173.4 0.400002 145.9 0.400002V22.7C166.6 22.7 197.3 39.2 229.9 69.3C215.9 84 201.9 100.7 188.6 119.2C166 121.6 144.6 125.3 125 130.2C122.7 120.2 121 110.5 119.8 101.2C115.1 63 120.9 33.3 134.4 25.4C137.4 23.6 141.3 22.8 145.9 22.8V0.5C137.5 0.5 129.9 2.3 123.3 6.1C95.2002 22.3 88.9002 72.8 103.4 136.2C41.2002 155.4 0.700195 186.1 0.700195 218.5C0.700195 251 41.4002 281.8 103.8 300.9C89.4002 364.5 95.8002 415.1 124 431.3C130.5 435.1 138.1 436.9 146.5 436.9C174 436.9 210 417.3 246.4 383.3C282.8 417.1 318.8 436.5 346.3 436.5C354.7 436.5 362.3 434.7 368.9 430.9C397 414.7 403.3 364.2 388.8 300.8C450.8 281.7 491.3 250.9 491.3 218.5ZM361.1 151.8C357.4 164.7 352.8 178 347.6 191.3C343.5 183.3 339.2 175.3 334.5 167.3C329.9 159.3 325 151.5 320.1 143.9C334.3 146 348 148.6 361.1 151.8ZM315.3 258.3C307.5 271.8 299.5 284.6 291.2 296.5C276.3 297.8 261.2 298.5 246 298.5C230.9 298.5 215.8 297.8 201 296.6C192.7 284.7 184.6 272 176.8 258.6C169.2 245.5 162.3 232.2 156 218.8C162.2 205.4 169.2 192 176.7 178.9C184.5 165.4 192.5 152.6 200.8 140.7C215.7 139.4 230.8 138.7 246 138.7C261.1 138.7 276.2 139.4 291 140.6C299.3 152.5 307.4 165.2 315.2 178.6C322.8 191.7 329.7 205 336 218.4C329.7 231.8 322.8 245.2 315.3 258.3ZM347.6 245.3C353 258.7 357.6 272.1 361.4 285.1C348.3 288.3 334.5 291 320.2 293.1C325.1 285.4 330 277.5 334.6 269.4C339.2 261.4 343.5 253.3 347.6 245.3ZM246.2 352C236.9 342.4 227.6 331.7 218.4 320C227.4 320.4 236.6 320.7 245.9 320.7C255.3 320.7 264.6 320.5 273.7 320C264.7 331.7 255.4 342.4 246.2 352ZM171.8 293.1C157.6 291 143.9 288.4 130.8 285.2C134.5 272.3 139.1 259 144.3 245.7C148.4 253.7 152.7 261.7 157.4 269.7C162.1 277.7 166.9 285.5 171.8 293.1ZM245.7 85C255 94.6 264.3 105.3 273.5 117C264.5 116.6 255.3 116.3 246 116.3C236.6 116.3 227.3 116.5 218.2 117C227.2 105.3 236.5 94.6 245.7 85ZM171.7 143.9C166.8 151.6 161.9 159.5 157.3 167.6C152.7 175.6 148.4 183.6 144.3 191.6C138.9 178.2 134.3 164.8 130.5 151.8C143.6 148.7 157.4 146 171.7 143.9ZM81.2002 269.1C45.8002 254 22.9002 234.2 22.9002 218.5C22.9002 202.8 45.8002 182.9 81.2002 167.9C89.8002 164.2 99.2002 160.9 108.9 157.8C114.6 177.4 122.1 197.8 131.4 218.7C122.2 239.5 114.8 259.8 109.2 279.3C99.3002 276.2 89.9002 272.8 81.2002 269.1ZM135 412C121.4 404.2 115.5 374.5 120.1 336.3C121.2 326.9 123 317 125.2 306.9C144.8 311.7 166.2 315.4 188.7 317.8C202.2 336.3 216.2 353.1 230.3 367.8C197.7 398.1 167.1 414.7 146.3 414.7C141.8 414.6 138 413.7 135 412ZM372.2 335.8C376.9 374 371.1 403.7 357.6 411.6C354.6 413.4 350.7 414.2 346.1 414.2C325.4 414.2 294.7 397.7 262.1 367.6C276.1 352.9 290.1 336.2 303.4 317.7C326 315.3 347.4 311.6 367 306.7C369.3 316.8 371.1 326.5 372.2 335.8ZM410.7 269.1C402.1 272.8 392.7 276.1 383 279.2C377.3 259.6 369.8 239.2 360.5 218.3C369.7 197.5 377.1 177.2 382.7 157.7C392.6 160.8 402 164.2 410.8 167.9C446.2 183 469.1 202.8 469.1 218.5C469 234.2 446.1 254.1 410.7 269.1Z"
fill="#61DAFB"
/>
<path
d="M245.9 264.2C271.14 264.2 291.6 243.739 291.6 218.5C291.6 193.26 271.14 172.8 245.9 172.8C220.661 172.8 200.2 193.26 200.2 218.5C200.2 243.739 220.661 264.2 245.9 264.2Z"
fill="#61DAFB"
/>
</Icon>
)

View File

@ -0,0 +1,25 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const ShopifyLogo = (props: IconProps) => (
<Icon
width="228"
height="259"
viewBox="0 0 228 259"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M199.689 49.7478C199.51 48.4431 198.371 47.7193 197.425 47.6405C196.486 47.5617 178.094 47.2822 178.094 47.2822C178.094 47.2822 162.712 32.3448 161.192 30.8253C159.673 29.3058 156.705 29.7645 155.551 30.1085C155.537 30.1157 152.663 31.0045 147.825 32.5025C147.015 29.8792 145.825 26.6537 144.126 23.414C138.65 12.9636 130.63 7.43736 120.939 7.42307C120.925 7.42307 120.917 7.42307 120.903 7.42307C120.229 7.42307 119.563 7.48757 118.889 7.54488C118.603 7.20084 118.316 6.86399 118.015 6.53424C113.793 2.01867 108.382 -0.181787 101.895 0.0117384C89.3801 0.370119 76.9158 9.40847 66.8095 25.4639C59.6991 36.7601 54.2875 50.9521 52.7537 61.9399C38.3826 66.3909 28.3335 69.5015 28.1114 69.5734C20.8577 71.8525 20.6284 72.0747 19.6823 78.9129C18.9798 84.0804 0 230.845 0 230.845L159.056 258.354L227.994 241.216C227.994 241.216 199.869 51.0524 199.689 49.7478ZM139.862 34.9682C136.199 36.1006 132.034 37.3908 127.519 38.7885C127.426 32.4523 126.673 23.6362 123.72 16.017C133.217 17.8161 137.89 28.5603 139.862 34.9682ZM119.197 41.3688C110.861 43.949 101.766 46.7659 92.6414 49.5902C95.2073 39.7633 100.074 29.9795 106.052 23.5645C108.274 21.1777 111.385 18.5185 115.069 16.999C118.531 24.2239 119.283 34.4521 119.197 41.3688ZM102.145 8.34048C105.084 8.27598 107.557 8.92109 109.672 10.3116C106.289 12.0677 103.02 14.5907 99.9524 17.8806C92.0033 26.4101 85.9108 39.6486 83.481 52.4213C75.9048 54.765 68.4935 57.0661 61.6699 59.1734C65.978 39.0681 82.8287 8.89956 102.145 8.34048Z"
fill="#95BF47"
/>
<path
d="M197.43 47.6487C196.491 47.5699 178.099 47.2904 178.099 47.2904C178.099 47.2904 162.717 32.353 161.198 30.8335C160.631 30.2673 159.865 29.9734 159.062 29.8516L159.069 258.348L228 241.217C228 241.217 199.874 51.0607 199.695 49.756C199.515 48.4513 198.369 47.7275 197.43 47.6487Z"
fill="#5E8E3E"
/>
<path
d="M120.852 83.0913L112.846 113.044C112.846 113.044 103.915 108.981 93.3288 109.647C77.8034 110.629 77.6385 120.42 77.7965 122.879C78.6422 136.275 113.885 139.199 115.864 170.579C117.419 195.264 102.768 212.151 81.6598 213.484C56.3222 215.083 42.374 200.138 42.374 200.138L47.7427 177.302C47.7427 177.302 61.7838 187.896 73.0226 187.186C80.3624 186.72 82.9856 180.75 82.7203 176.528C81.6167 159.053 52.9175 160.085 51.1043 131.372C49.5774 107.21 65.4468 82.7257 100.46 80.5181C113.95 79.6509 120.852 83.0913 120.852 83.0913Z"
fill="white"
/>
</Icon>
)

View File

@ -0,0 +1,21 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const WebflowLogo = (props: IconProps) => (
<Icon
width="224"
height="224"
viewBox="0 0 224 224"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M112 224C173.856 224 224 173.856 224 112C224 50.1441 173.856 0 112 0C50.1441 0 0 50.1441 0 112C0 173.856 50.1441 224 112 224Z"
fill="#4353FF"
/>
<path
d="M144.9 98.7016L134.75 130.692C134.75 128.242 127.75 75.1816 127.75 75.1816C111.65 75.1816 103.11 86.5916 98.6301 98.7016L86.2401 130.762C86.2401 128.452 84.4901 98.9816 84.4901 98.9816C83.8925 92.602 80.9759 86.663 76.2929 82.2897C71.6099 77.9163 65.4856 75.4122 59.0801 75.2516L72.2401 155.612C89.0401 155.612 98.1401 144.202 102.83 132.092L113.33 104.792C113.33 105.912 120.33 155.612 120.33 155.612C127.2 155.603 133.888 153.407 139.426 149.341C144.964 145.276 149.063 139.553 151.13 133.002L174.93 75.1816C158.13 75.1816 149.31 86.5916 144.83 98.7016H144.9Z"
fill="white"
/>
</Icon>
)

View File

@ -0,0 +1,29 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const WixLogo = (props: IconProps) => (
<Icon
width="311"
height="121"
viewBox="0 0 311 121"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M178 2.29971C172 5.29971 169.4 10.8997 169.4 26.0997C169.4 26.0997 172.4 23.0997 177.2 21.2997C180.7 19.9997 183.2 18.2997 185 16.9997C190.2 13.0997 191 8.39971 191 0.199713C190.9 0.199713 182.7 -0.300287 178 2.29971Z"
fill="#FBBD71"
/>
<path
d="M141.3 5.79963C136.1 10.0996 134.8 17.4996 134.8 17.4996L118 81.8996L104.2 29.1996C102.9 23.5996 100.3 16.6996 96.4 11.8996C91.6 5.79963 81.6 5.39963 80.4 5.39963C79.5 5.39963 69.6 5.79963 64.4 11.8996C60.5 16.6996 57.9 23.5996 56.6 29.1996L43.6 81.8996L26.8 17.4996C26.8 17.4996 25.5 10.5996 20.3 5.79963C12.1 -1.60037 0 0.199629 0 0.199629L32 120.7C32 120.7 42.4 121.6 47.6 119C54.5 115.5 58 113 61.9 96.4996C65.8 81.7996 76.2 38.9996 77 35.9996C77.4 34.6996 78.3 30.7996 80.9 30.7996C83.5 30.7996 84.4 34.2996 84.8 35.9996C85.7 38.9996 96 81.7996 99.9 96.4996C104.2 112.9 107.2 115.5 114.2 119C119.4 121.6 129.8 120.7 129.8 120.7L161.6 0.199629C161.6 0.199629 149.5 -1.50037 141.3 5.79963Z"
fill="black"
/>
<path
d="M190.9 19.5996C190.9 19.5996 188.7 22.5996 184.4 25.1996C181.4 26.8996 178.8 27.7996 175.8 29.4996C170.6 32.0996 169.3 34.6996 169.3 38.5996V39.8996V46.3996V47.6996V120.3C169.3 120.3 177.5 121.2 182.7 118.6C189.6 115.1 190.9 111.7 190.9 96.9996V24.3996V19.5996Z"
fill="black"
/>
<path
d="M270.4 60.7003L311 0.600311C311 0.600311 294.2 -2.39969 285.5 5.40031C279.9 10.2003 274.3 19.2003 274.3 19.2003L259.6 40.8003C258.7 42.1003 257.9 43.0003 256.6 43.0003C255.3 43.0003 254 41.7003 253.6 40.8003L238.9 19.2003C238.9 19.2003 232.9 10.6003 227.7 5.40031C219.1 -2.39969 202.2 0.600311 202.2 0.600311L241.5 60.6003L201.3 120.6C201.3 120.6 219 122.8 227.7 115C233.3 110.2 238.5 102 238.5 102L253.2 80.4003C254.1 79.1003 254.9 78.2003 256.2 78.2003C257.5 78.2003 258.8 79.5003 259.2 80.4003L273.9 102C273.9 102 279.5 110.2 284.7 115C293.3 122.8 310.6 120.6 310.6 120.6L270.4 60.7003Z"
fill="black"
/>
</Icon>
)

View File

@ -0,0 +1,33 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const WordpressLogo = (props: IconProps) => (
<Icon
width="123"
height="123"
viewBox="0 0 123 123"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M8.70801 61.2601C8.70801 82.0621 20.797 100.039 38.327 108.558L13.258 39.8721C10.342 46.4081 8.70801 53.6411 8.70801 61.2601Z"
fill="#464342"
/>
<path
d="M96.7396 58.608C96.7396 52.113 94.4066 47.615 92.4056 44.114C89.7416 39.785 87.2446 36.119 87.2446 31.79C87.2446 26.959 90.9086 22.462 96.0696 22.462C96.3026 22.462 96.5236 22.491 96.7506 22.504C87.4006 13.938 74.9436 8.70801 61.2616 8.70801C42.9016 8.70801 26.7486 18.128 17.3516 32.396C18.5846 32.433 19.7466 32.459 20.7336 32.459C26.2306 32.459 34.7396 31.792 34.7396 31.792C37.5726 31.625 37.9066 35.786 35.0766 36.121C35.0766 36.121 32.2296 36.456 29.0616 36.622L48.1996 93.547L59.7006 59.054L51.5126 36.62C48.6826 36.454 46.0016 36.119 46.0016 36.119C43.1696 35.953 43.5016 31.623 46.3336 31.79C46.3336 31.79 55.0126 32.457 60.1766 32.457C65.6726 32.457 74.1826 31.79 74.1826 31.79C77.0176 31.623 77.3506 35.784 74.5196 36.119C74.5196 36.119 71.6666 36.454 68.5046 36.62L87.4966 93.114L92.7386 75.597C95.0106 68.328 96.7396 63.107 96.7396 58.608Z"
fill="#464342"
/>
<path
d="M62.184 65.8574L46.416 111.676C51.124 113.06 56.103 113.817 61.262 113.817C67.382 113.817 73.251 112.759 78.714 110.838C78.573 110.613 78.445 110.374 78.34 110.114L62.184 65.8574Z"
fill="#464342"
/>
<path
d="M107.376 36.0459C107.602 37.7199 107.73 39.5169 107.73 41.4499C107.73 46.7829 106.734 52.7779 103.734 60.2739L87.6807 106.687C103.305 97.5759 113.814 80.6489 113.814 61.2609C113.815 52.1239 111.481 43.5319 107.376 36.0459Z"
fill="#464342"
/>
<path
d="M61.262 0C27.483 0 0 27.481 0 61.26C0 95.043 27.483 122.523 61.262 122.523C95.04 122.523 122.527 95.043 122.527 61.26C122.526 27.481 95.04 0 61.262 0ZM61.262 119.715C29.032 119.715 2.809 93.492 2.809 61.26C2.809 29.03 29.031 2.809 61.262 2.809C93.491 2.809 119.712 29.03 119.712 61.26C119.712 93.492 93.491 119.715 61.262 119.715Z"
fill="#464342"
/>
</Icon>
)

View File

@ -0,0 +1,10 @@
export * from './GtmLogo'
export * from './IframeLogo'
export * from './JavascriptLogo'
export * from './NotionLogo'
export * from './OtherLogo'
export * from './ReactLogo'
export * from './ShopifyLogo'
export * from './WebflowLogo'
export * from './WixLogo'
export * from './WordpressLogo'

View File

@ -0,0 +1,155 @@
import { HStack, Button, Text, Stack } from '@chakra-ui/react'
type ChooseEmbedTypeListProps = {
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
disabledTypes?: ('standard' | 'popup' | 'bubble')[]
}
export const ChooseEmbedTypeList = ({
onSelectEmbedType,
disabledTypes = [],
}: ChooseEmbedTypeListProps) => {
return (
<HStack mx="auto">
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('standard')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('standard')}
>
<StandardEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Standard
</Text>
<Text textColor="gray.500">Embed in a container on your site</Text>
</Stack>
</Stack>
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('popup')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('popup')}
>
<PopupEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Popup
</Text>
<Text textColor="gray.500">
Embed in a popup window on top of your website
</Text>
</Stack>
</Stack>
<Stack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
style={{ width: '225px', height: '270px' }}
onClick={() => onSelectEmbedType('bubble')}
whiteSpace={'normal'}
spacing="6"
isDisabled={disabledTypes.includes('bubble')}
>
<BubbleEmbedSvg />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Bubble
</Text>
<Text textColor="gray.500">
Embed in a chat bubble on the corner of your site
</Text>
</Stack>
</Stack>
</HStack>
)
}
const StandardEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<rect x="10" y="28" width="80" height="42" rx="6" fill="#FF8E20" />
<circle cx="18" cy="37" r="5" fill="white" />
<rect x="24" y="33" width="45" height="8" rx="4" fill="white" />
<circle cx="18" cy="61" r="5" fill="white" />
<rect x="24" y="57" width="45" height="8" rx="4" fill="white" />
<rect x="31" y="45" width="45" height="8" rx="4" fill="white" />
<circle cx="82" cy="49" r="5" fill="white" />
<rect x="10" y="9" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="14" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="19" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="80" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="85" width="80" height="1" rx="0.5" fill="white" />
<rect x="10" y="90" width="80" height="1" rx="0.5" fill="white" />
</svg>
)
const PopupEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<rect x="19" y="20" width="63" height="63" rx="6" fill="#FF8E20" />
<circle cx="25.7719" cy="33.7719" r="3.77193" fill="white" />
<rect x="31" y="30" width="27" height="8" rx="4" fill="white" />
<circle
r="3.77193"
transform="matrix(-1 0 0 1 75.2281 43.7719)"
fill="white"
/>
<rect
width="22"
height="8"
rx="4"
transform="matrix(-1 0 0 1 70 40)"
fill="white"
/>
<rect
x="31.0527"
y="52"
width="26.9473"
height="7.54386"
rx="3.77193"
fill="white"
/>
<circle cx="25.7719" cy="67.7719" r="3.77193" fill="white" />
<rect x="31" y="64" width="27" height="8" rx="4" fill="white" />
</svg>
)
const BubbleEmbedSvg = () => (
<svg
width="100"
height="100"
viewBox="0 0 100 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="100" height="100" rx="5" fill="#0042DA" />
<circle cx="85.5" cy="85.5" r="7.5" fill="#FF8E20" />
</svg>
)

View File

@ -0,0 +1,134 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { env, getViewerUrl } from 'utils'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import {
parseInitContainerCode,
typebotJsHtml,
} from '../../codeSnippets/params'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
import { ModalProps } from '../../EmbedButton'
type GtmInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions publicId={publicId} />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState({
height: '100%',
width: '100%',
})
const jsCode = parseInitContainerCode({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${publicId}`,
})
const headCode = `${typebotJsHtml}
<script>
${jsCode}
</script>`
const elementCode = `<div id="typebot-container" style="height: ${windowSizes.height}; width: ${windowSizes.width}"></div>`
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<CodeEditor value={headCode} mt={2} isReadOnly lang="html" />
</ListItem>
<ListItem>
On your webpage, you need to have an element on which the typebot will
go. It needs to have the id <Tag>typebot-container</Tag>:
<StandardEmbedWindowSettings
my={4}
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<CodeEditor value={elementCode} mt={2} isReadOnly lang="html" />
</ListItem>
</OrderedList>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<PopupEmbedSettings
my={4}
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your GTM account dashboard, click on <Tag>Add a new tag</Tag>
</ListItem>
<ListItem>
Choose Custom <Tag>HTML tag</Tag> type
</ListItem>
<ListItem>
Paste the code below:
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode my={4} {...inputValues} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,70 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { GtmInstructions } from './GtmInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const GtmModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<GtmInstructions type={chosenEmbedType} publicId={publicId} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,46 @@
import { AlertInfo } from '@/components/AlertInfo'
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
Stack,
ModalFooter,
Text,
} from '@chakra-ui/react'
import { useState } from 'react'
import { StandardEmbedWindowSettings } from '../codeSnippets/Container/EmbedSettings'
import { IframeEmbedCode } from '../codeSnippets/Iframe/EmbedCode'
import { ModalProps } from '../EmbedButton'
export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
const [inputValues, setInputValues] = useState({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Modal isOpen={isOpen} onClose={onClose} size={'xl'}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Iframe</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing={4}>
{!isPublished && (
<AlertInfo>You need to publish your bot first.</AlertInfo>
)}
<Text>Paste this anywhere in your HTML code:</Text>
<StandardEmbedWindowSettings
onUpdateWindowSettings={(settings) =>
setInputValues({ ...settings })
}
/>
<IframeEmbedCode {...inputValues} />
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,89 @@
import { Stack, Tag, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type JavascriptInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const JavascriptInstructions = ({
type,
}: JavascriptInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = () => {
const [inputValues, setInputValues] = useState({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<StandardEmbedWindowSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<ContainerEmbedCode withStarterVariables={true} {...inputValues} mt={4} />
</Stack>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<PopupEmbedSettings
mb={4}
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</Stack>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<Stack spacing={4}>
<Text>
Paste this anywhere in the <Tag>body</Tag>
</Text>
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode {...inputValues} />
</Stack>
)
}

View File

@ -0,0 +1,69 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { JavascriptInstructions } from './JavascriptInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const JavascriptModal = ({
isOpen,
onClose,
isPublished,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Javascript {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<JavascriptInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,71 @@
import { AlertInfo } from '@/components/AlertInfo'
import { CopyButton } from '@/components/CopyButton'
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
Heading,
ModalCloseButton,
ModalBody,
OrderedList,
ListItem,
Tag,
InputGroup,
Input,
InputRightElement,
ModalFooter,
} from '@chakra-ui/react'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
export const NotionModal = ({
isPublished,
publicId,
isOpen,
onClose,
}: ModalProps): JSX.Element => {
return (
<Modal isOpen={isOpen} onClose={onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Heading size="md">Notion</Heading>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="4">You need to publish your bot first.</AlertInfo>
)}
<OrderedList spacing={3}>
<ListItem>
Type <Tag>/embed</Tag>
</ListItem>
<ListItem>
Paste your typebot URL
<InputGroup size="md" mt={2}>
<Input
pr="4.5rem"
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
</ListItem>
</OrderedList>
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,95 @@
import { CodeEditor } from '@/components/CodeEditor'
import { Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
import {
StandardReactDiv,
PopupReactCode,
ChatReactCode,
} from '../../codeSnippets/ReactCode'
type Props = {
type: 'standard' | 'popup' | 'bubble'
}
export const ReactInstructions = ({ type }: Props) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = () => {
const [inputValues, setInputValues] = useState({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<StandardEmbedWindowSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>Insert the typebot container</Text>
<StandardReactDiv {...inputValues} />
</Stack>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<PopupEmbedSettings
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<Text>Initialize the typebot</Text>
<PopupReactCode withStarterVariables={true} delay={inputValue} />
</Stack>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<Stack spacing={4}>
<InstallPackageInstruction />
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>Initialize the typebot</Text>
<ChatReactCode withStarterVariables={true} {...inputValues} mt={4} />
</Stack>
)
}
const InstallPackageInstruction = () => {
return (
<Stack>
<Text>Install the package:</Text>
<CodeEditor value={`npm install typebot-js`} isReadOnly />
</Stack>
)
}

View File

@ -0,0 +1,65 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { ReactInstructions } from './ReactInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const ReactModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
React {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<ReactInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,148 @@
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ModalProps } from '../../EmbedButton'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { StandardEmbedWindowSettings } from '../../codeSnippets/Container/EmbedSettings'
import {
parseInitContainerCode,
typebotJsHtml,
} from '../../codeSnippets/params'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const ShopifyInstructions = ({
type,
publicId,
}: ShopifyInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions publicId={publicId} />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState({
height: '100%',
width: '100%',
})
const jsCode = parseInitContainerCode({
url: `${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl({ isBuilder: true })
}/${publicId}`,
})
const headCode = prettier.format(
`${typebotJsHtml}<script>${jsCode}</script>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
const elementCode = prettier.format(
`<div id="typebot-container" style="height: ${windowSizes.height}; width: ${windowSizes.width}"></div>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<CodeEditor value={headCode} mt={2} lang="html" isReadOnly />
</ListItem>
<ListItem>
Then, you can place an element on which the typebot will go in any file
in the <Tag>body</Tag> tags. It needs to have the id{' '}
<Tag>typebot-container</Tag>:
<StandardEmbedWindowSettings
my={4}
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<CodeEditor value={elementCode} mt={2} lang="html" isReadOnly />
</ListItem>
</OrderedList>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<PopupEmbedSettings
my="4"
onUpdateSettings={(settings) => setInputValue(settings.delay)}
/>
<PopupEmbedCode delay={inputValue} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
On your shop dashboard in the <Tag>Themes</Tag> page, click on{' '}
<Tag>Actions {'>'} Edit code</Tag>
</ListItem>
<ListItem>
In <Tag>Layout {'>'} theme.liquid</Tag> file, paste this code just
before the closing <Tag>head</Tag> tag:
<ChatEmbedSettings
my="4"
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode mt={4} {...inputValues} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,70 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { ShopifyInstructions } from './ShopifyInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const ShopifyModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Shopify {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<ShopifyInstructions type={chosenEmbedType} publicId={publicId} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,91 @@
import { OrderedList, ListItem, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type WebflowInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const WebflowInstructions = ({ type }: WebflowInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
default:
return <></>
}
}
const StandardInstructions = () => (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<ContainerEmbedCode widthLabel="100%" heightLabel="100%" my={4} />
</ListItem>
</OrderedList>
)
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<PopupEmbedSettings
onUpdateSettings={(settings) => setInputValue(settings.delay)}
my={4}
/>
<PopupEmbedCode delay={inputValue} mt={4} />
</ListItem>
</OrderedList>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
Press <Tag>A</Tag> to open the <Tag>Add elements</Tag> panel
</ListItem>
<ListItem>
Add an <Tag>embed</Tag> element from the <Tag>components</Tag>
section and paste this code:
<ChatEmbedSettings
onUpdateSettings={(settings) => setInputValues({ ...settings })}
my={4}
/>
<ChatEmbedCode withStarterVariables={true} {...inputValues} my={4} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,65 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { capitalize } from 'utils'
import { WebflowInstructions } from './WebflowInstructions'
import { AlertInfo } from '@/components/AlertInfo'
export const WebflowModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Webflow {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<WebflowInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,105 @@
import { ListItem, OrderedList, Tag } from '@chakra-ui/react'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { ChatEmbedCode } from '../../codeSnippets/Chat/EmbedCode'
import { ChatEmbedSettings } from '../../codeSnippets/Chat/EmbedSettings'
import { ContainerEmbedCode } from '../../codeSnippets/Container/EmbedCode'
import { PopupEmbedCode } from '../../codeSnippets/Popup/EmbedCode'
import { PopupEmbedSettings } from '../../codeSnippets/Popup/EmbedSettings'
type WixInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const WixInstructions = ({ type }: WixInstructionsProps) => {
switch (type) {
case 'standard': {
return <StandardInstructions />
}
case 'popup': {
return <PopupInstructions />
}
case 'bubble': {
return <BubbleInstructions />
}
}
}
const StandardInstructions = () => {
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
In the Wix Website Editor:
<Tag>
Add {'>'} Embed {'>'} Embed a Widget
</Tag>
</ListItem>
<ListItem>
Click on <Tag>Enter code</Tag> and paste this code:
</ListItem>
<ContainerEmbedCode widthLabel="100%" heightLabel="100%" />
</OrderedList>
)
}
const PopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<>
<OrderedList spacing={2} mb={4}>
<ListItem>
Go to <Tag>Settings</Tag> in your dashboard on Wix
</ListItem>
<ListItem>
Click on <Tag>Custom Code</Tag> under <Tag>Advanced</Tag>
</ListItem>
<ListItem>
Click <Tag>+ Add Custom Code</Tag> at the top right.
</ListItem>
<ListItem>
Paste this snippet in the code box:
<PopupEmbedSettings
onUpdateSettings={(settings) => setInputValue(settings.delay)}
my={4}
/>
<PopupEmbedCode delay={inputValue} />
</ListItem>
</OrderedList>
</>
)
}
const BubbleInstructions = () => {
const [inputValues, setInputValues] = useState<
Pick<BubbleParams, 'proactiveMessage' | 'button'>
>({
proactiveMessage: undefined,
button: {
color: '',
iconUrl: '',
},
})
return (
<OrderedList spacing={2} mb={4}>
<ListItem>
Go to <Tag>Settings</Tag> in your dashboard on Wix
</ListItem>
<ListItem>
Click on <Tag>Custom Code</Tag> under <Tag>Advanced</Tag>
</ListItem>
<ListItem>
Click <Tag>+ Add Custom Code</Tag> at the top right.
</ListItem>
<ListItem>
Paste this snippet in the code box:{' '}
<ChatEmbedSettings
my="4"
onUpdateSettings={(settings) => setInputValues({ ...settings })}
/>
<ChatEmbedCode {...inputValues} />
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,65 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
IconButton,
Heading,
HStack,
} from '@chakra-ui/react'
import { ChevronLeftIcon } from '@/components/icons'
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { ChooseEmbedTypeList } from '../ChooseEmbedTypeList'
import { WixInstructions } from './WixInstructions'
import { capitalize } from 'utils'
import { AlertInfo } from '@/components/AlertInfo'
export const WixModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{chosenEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => setChosenEmbedType(undefined)}
/>
)}
<Heading size="md">
Wix {chosenEmbedType && `- ${capitalize(chosenEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
{!chosenEmbedType ? (
<ChooseEmbedTypeList onSelectEmbedType={setChosenEmbedType} />
) : (
<WixInstructions type={chosenEmbedType} />
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,81 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
Heading,
ModalCloseButton,
ModalBody,
OrderedList,
ListItem,
InputGroup,
Input,
InputRightElement,
ModalFooter,
Link,
} from '@chakra-ui/react'
import { ExternalLinkIcon } from '@/components/icons'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
import { AlertInfo } from '@/components/AlertInfo'
import { CopyButton } from '@/components/CopyButton'
export const WordpressModal = ({
publicId,
isPublished,
isOpen,
onClose,
}: ModalProps): JSX.Element => {
return (
<Modal isOpen={isOpen} onClose={onClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Heading size="md">WordPress</Heading>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{!isPublished && (
<AlertInfo mb="2">You need to publish your bot first.</AlertInfo>
)}
<OrderedList spacing={3}>
<ListItem>
Install{' '}
<Link
href="https://wordpress.org/plugins/typebot/"
isExternal
color="blue.500"
>
the official Typebot WordPress plugin
<ExternalLinkIcon mx="2px" />
</Link>
</ListItem>
<ListItem>
Copy your typebot URL
<InputGroup size="md" mt={2}>
<Input
pr="4.5rem"
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
env('VIEWER_INTERNAL_URL') ??
getViewerUrl({ isBuilder: true })
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
</ListItem>
<ListItem>Complete the setup in your Wordpress interface</ListItem>
</OrderedList>
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,9 @@
export * from './IframeModal'
export * from './NotionModal'
export * from './WordpressModal'
export * from './WixModal'
export * from './GtmModal'
export * from './React/ReactModal'
export * from './Javascript/JavascriptModal'
export * from './WebflowModal'
export * from './ShopifyModal'