2
0

Introduce bot v2 in builder (#328)

Also, the new engine is the default for updated typebots for viewer

Closes #211
This commit is contained in:
Baptiste Arnaud
2023-02-21 15:25:14 +01:00
committed by GitHub
parent 527dc8a5b1
commit debdac12ff
208 changed files with 4462 additions and 5236 deletions

View File

@ -11,6 +11,7 @@ import {
MenuItem,
useDisclosure,
ButtonProps,
useColorModeValue,
} from '@chakra-ui/react'
import {
ChevronLeftIcon,
@ -27,6 +28,7 @@ import { ChangePlanModal, isFreePlan, LimitReached } from '@/features/billing'
import { timeSince } from '@/utils/helpers'
export const PublishButton = (props: ButtonProps) => {
const warningTextColor = useColorModeValue('red.300', 'red.600')
const { workspace } = useWorkspace()
const { push, query } = useRouter()
const { isOpen, onOpen, onClose } = useDisclosure()
@ -71,12 +73,17 @@ export const PublishButton = (props: ButtonProps) => {
type={LimitReached.FILE_INPUT}
/>
<Tooltip
borderRadius="md"
hasArrow
placement="bottom-end"
label={
<Stack>
<Text>There are non published changes.</Text>
{!publishedTypebot?.version ? (
<Text color={warningTextColor} fontWeight="semibold">
This will deploy your bot with an updated engine. Make sure to
test it properly in preview mode before publishing.
</Text>
) : (
<Text>There are non published changes.</Text>
)}
<Text fontStyle="italic">
Published version from{' '}
{publishedTypebot &&

View File

@ -23,6 +23,7 @@ import {
IframeModal,
WixModal,
} from './modals'
import { OtherModal } from './modals/OtherModal'
export type ModalProps = {
publicId: string
@ -139,7 +140,7 @@ export const integrationsList = [
<EmbedButton
logo={<OtherLogo height={100} width="70px" />}
label="Other"
Modal={JavascriptModal}
Modal={OtherModal}
{...props}
/>
),

View File

@ -0,0 +1,77 @@
import { AlertInfo } from '@/components/AlertInfo'
import { ChevronLeftIcon } from '@/components/icons'
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
HStack,
IconButton,
Heading,
ModalCloseButton,
ModalBody,
Stack,
ModalFooter,
} from '@chakra-ui/react'
import { capitalize } from 'utils'
import { EmbedTypeMenu } from './EmbedTypeMenu/EmbedTypeMenu'
type Props = {
selectedEmbedType: 'standard' | 'popup' | 'bubble' | undefined
titlePrefix: string
isOpen: boolean
isPublished: boolean
children: React.ReactNode
onClose: () => void
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble' | undefined) => void
}
export const EmbedModal = ({
selectedEmbedType,
isOpen,
isPublished,
titlePrefix,
children,
onSelectEmbedType,
onClose,
}: Props) => (
<Modal
isOpen={isOpen}
onClose={onClose}
size={!selectedEmbedType ? '2xl' : 'xl'}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
<HStack>
{selectedEmbedType && (
<IconButton
icon={<ChevronLeftIcon />}
aria-label="back"
variant="ghost"
colorScheme="gray"
mr={2}
onClick={() => onSelectEmbedType(undefined)}
/>
)}
<Heading size="md">
{titlePrefix}{' '}
{selectedEmbedType && `- ${capitalize(selectedEmbedType)}`}
</Heading>
</HStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing={4} pt={0}>
{!isPublished && (
<AlertInfo>You need to publish your bot first.</AlertInfo>
)}
{!selectedEmbedType ? (
<EmbedTypeMenu onSelectEmbedType={onSelectEmbedType} />
) : (
children
)}
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)

View File

@ -0,0 +1,33 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { BubbleIllustration } from './illustrations/BubbleIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const BubbleMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
flex="1"
height="250px"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<BubbleIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Bubble
</Text>
<Text textColor="gray.500">Embed in a chat bubble</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,18 @@
import { HStack } from '@chakra-ui/react'
import { BubbleMenuButton } from './BubbleMenuButton'
import { PopupMenuButton } from './PopupMenuButton'
import { StandardMenuButton } from './StandardMenuButton'
type Props = {
onSelectEmbedType: (type: 'standard' | 'popup' | 'bubble') => void
}
export const EmbedTypeMenu = ({ onSelectEmbedType }: Props) => {
return (
<HStack spacing={4}>
<StandardMenuButton onClick={() => onSelectEmbedType('standard')} />
<PopupMenuButton onClick={() => onSelectEmbedType('popup')} />
<BubbleMenuButton onClick={() => onSelectEmbedType('bubble')} />
</HStack>
)
}

View File

@ -0,0 +1,35 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { PopupIllustration } from './illustrations/PopupIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const PopupMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
height="250px"
flex="1"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<PopupIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Popup
</Text>
<Text textColor="gray.500">
Embed in a popup on top of your website
</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,33 @@
import { MotionStack } from '@/components/MotionStack'
import { Stack, Button, StackProps, Text, ButtonProps } from '@chakra-ui/react'
import { StandardIllustration } from './illustrations/StandardIllustration'
type Props = StackProps & Pick<ButtonProps, 'isDisabled'>
export const StandardMenuButton = (props: Props) => {
return (
<MotionStack
as={Button}
fontWeight="normal"
alignItems="center"
variant="outline"
colorScheme="gray"
whiteSpace={'normal'}
spacing="6"
height="250px"
flex="1"
animate="default"
whileHover="animateBubbles"
transition={{ staggerChildren: 0.1 }}
{...props}
>
<StandardIllustration />
<Stack>
<Text fontSize="lg" fontWeight="semibold">
Standard
</Text>
<Text textColor="gray.500">Embed in a container on your site</Text>
</Stack>
</MotionStack>
)
}

View File

@ -0,0 +1,77 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const BubbleIllustration = () => {
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
width="100"
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
/>
<rect x="164" y="59" width="287" height="305" rx="10" fill="#0042DA" />
<motion.rect
x="227"
y="91"
width="156"
height="34"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="227"
y="134"
width="156"
height="65"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="198"
cy="228"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="227"
y="208"
width="156"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="412"
cy="277"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="253"
y="257"
width="130"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<circle cx="411" cy="430" r="40" fill="#0042DA" />
</svg>
)
}

View File

@ -0,0 +1,75 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const PopupIllustration = () => {
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
width="100"
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['400'], colors.gray['900'])}
/>
<rect x="105" y="77" width="290" height="352" rx="10" fill="#0042DA" />
<motion.rect
x="171"
y="117"
width="156"
height="34"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="171"
y="160"
width="156"
height="65"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="142"
cy="254"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="171"
y="234"
width="156"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.circle
cx="356"
cy="303"
r="20"
fill={bubbleColor}
variants={animationVariants}
/>
<motion.rect
x="197"
y="283"
width="130"
height="40"
rx="10"
fill={bubbleColor}
variants={animationVariants}
/>
</svg>
)
}

View File

@ -0,0 +1,79 @@
import { colors } from '@/lib/theme'
import { useColorModeValue } from '@chakra-ui/react'
import { motion } from 'framer-motion'
import { animationVariants } from './animationVariants'
export const StandardIllustration = () => {
const gray = useColorModeValue(colors.gray[400], colors.gray[700])
const bubbleColor = useColorModeValue('white', colors.blue[100])
return (
<svg
viewBox="0 0 500 500"
width="100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="20"
fill={useColorModeValue(colors.gray['200'], colors.gray['900'])}
/>
<rect x="49" y="49" width="108" height="109" rx="10" fill={gray} />
<rect x="188" y="74" width="263" height="25" rx="5" fill={gray} />
<rect x="188" y="111" width="263" height="25" rx="5" fill={gray} />
<rect x="49" y="189" width="402" height="262" rx="10" fill="#0042DA" />
<motion.rect
variants={animationVariants}
x="121"
y="217"
width="218"
height="34"
rx="10"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="121"
y="260"
width="218"
height="65"
rx="10"
fill={bubbleColor}
/>
<motion.circle
variants={animationVariants}
cx="93"
cy="354"
r="20"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="121"
y="334"
width="218"
height="40"
rx="10"
fill={bubbleColor}
/>
<motion.circle
variants={animationVariants}
cx="407"
cy="410"
r="20"
fill={bubbleColor}
/>
<motion.rect
variants={animationVariants}
x="250"
y="390"
width="130"
height="40"
rx="10"
fill={bubbleColor}
/>
</svg>
)
}

View File

@ -0,0 +1,8 @@
import { Variants } from 'framer-motion'
export const animationVariants: Variants = {
animateBubbles: {
opacity: [0, 1],
},
default: { opacity: 1 },
}

View File

@ -1,41 +0,0 @@
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()}/${
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

@ -1,223 +0,0 @@
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="0" 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"
>
<FormLabel htmlFor="fullscreen-option" mb="0">
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

@ -1,59 +0,0 @@
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()}/${
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

@ -1,120 +0,0 @@
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

@ -1,37 +0,0 @@
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()}/${
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

@ -1,66 +0,0 @@
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

@ -1,161 +0,0 @@
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()}/${
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()}/${
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()}/${
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

@ -1,140 +0,0 @@
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

@ -1,155 +0,0 @@
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

@ -1,132 +0,0 @@
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()}/${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,30 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { GtmInstructions } from './instructions/GtmInstructions'
export const GtmModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="GTM"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<GtmInstructions type={selectedEmbedType} publicId={publicId} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './GtmModal'

View File

@ -1,70 +0,0 @@
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,45 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const GtmBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<Text>Paste the code below:</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,22 @@
import { GtmBubbleInstructions } from './GtmBubbleInstructions'
import { GtmPopupInstructions } from './GtmPopupInstructions'
import { GtmStandardInstructions } from './GtmStandardInstructions'
type GtmInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const GtmInstructions = ({ type, publicId }: GtmInstructionsProps) => {
switch (type) {
case 'standard': {
return <GtmStandardInstructions publicId={publicId} />
}
case 'popup': {
return <GtmPopupInstructions />
}
case 'bubble': {
return <GtmBubbleInstructions />
}
}
}

View File

@ -0,0 +1,30 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const GtmPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>Paste the code below:</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,62 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { Typebot } from 'models'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import {
parseStandardElementCode,
parseStandardHeadCode,
} from '../../Javascript/JavascriptStandardSnippet'
export const GtmStandardInstructions = ({
publicId,
}: Pick<Typebot, 'publicId'>) => {
const [windowSizes, setWindowSizes] = useState<{
height: string
width?: string
}>({
height: '100%',
width: '100%',
})
const headCode = parseStandardHeadCode(publicId)
const elementCode = parseStandardElementCode(
windowSizes.width,
windowSizes.height
)
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your GTM account dashboard, click on <Code>Add a new tag</Code>
</ListItem>
<ListItem>
Choose Custom <Code>HTML tag</Code> type
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>Paste the code below:</Text>
<CodeEditor value={headCode} isReadOnly lang="html" />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<Text>
On your web page, you need to have an element on which the typebot
will go:
</Text>
<CodeEditor value={elementCode} isReadOnly lang="html" />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -11,12 +11,15 @@ import {
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'
import { ModalProps } from '../../EmbedButton'
import { StandardSettings } from '../../settings/StandardSettings'
import { IframeSnippet } from './IframeSnippet'
export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
const [inputValues, setInputValues] = useState({
const [inputValues, setInputValues] = useState<{
heightLabel: string
widthLabel?: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
@ -27,17 +30,21 @@ export const IframeModal = ({ isPublished, isOpen, onClose }: ModalProps) => {
<ModalContent>
<ModalHeader>Iframe</ModalHeader>
<ModalCloseButton />
<ModalBody as={Stack} spacing={4}>
<ModalBody as={Stack} spacing={4} pt="0">
{!isPublished && (
<AlertInfo>You need to publish your bot first.</AlertInfo>
)}
<Text>Paste this anywhere in your HTML code:</Text>
<StandardEmbedWindowSettings
<StandardSettings
onUpdateWindowSettings={(settings) =>
setInputValues({ ...settings })
}
/>
<IframeEmbedCode {...inputValues} />
<Text>Paste this anywhere in your HTML code:</Text>
<IframeSnippet
widthLabel={inputValues.widthLabel ?? '100%'}
heightLabel={inputValues.heightLabel}
/>
</ModalBody>
<ModalFooter />
</ModalContent>

View File

@ -2,21 +2,24 @@ import { FlexProps } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor'
import { env, getViewerUrl } from 'utils'
import { CodeEditor } from '@/components/CodeEditor'
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
type Props = {
widthLabel: string
heightLabel: string
onCopied?: () => void
}
export const IframeEmbedCode = ({
widthLabel,
heightLabel,
}: Props & FlexProps) => {
} & FlexProps
export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => {
const { typebot } = useTypebot()
const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
typebot?.publicId
}`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" style="border: none"></iframe>`
const code = prettier.format(
`<iframe src="${src}" style="border: none; width='${widthLabel}'; height='${heightLabel}'"></iframe>`,
{ parser: 'html', plugins: [parserHtml] }
)
return <CodeEditor value={code} lang="html" isReadOnly />
}

View File

@ -0,0 +1,36 @@
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { parseInitBubbleCode } from '../../snippetParsers'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { BubbleProps } from '@typebot.io/js'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = Pick<BubbleProps, 'theme' | 'previewMessage'>
export const JavascriptBubbleSnippet = ({ theme, previewMessage }: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`<script type="module">${parseInitBubbleCode({
typebot: typebot?.publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
theme: {
...theme,
chatWindow: {
backgroundColor: typebot?.theme.general.background.content ?? '#fff',
},
},
previewMessage,
})}</script>`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}

View File

@ -1,89 +0,0 @@
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

@ -1,69 +1,29 @@
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'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { JavascriptInstructions } from './instructions/JavascriptInstructions'
export const JavascriptModal = ({
isOpen,
onClose,
isPublished,
}: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
<EmbedModal
titlePrefix="Javascript"
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
<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>
{isDefined(selectedEmbedType) && (
<JavascriptInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,33 @@
import { useTypebot } from '@/features/editor'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitPopupCode } from '../../snippetParsers'
import { CodeEditor } from '@/components/CodeEditor'
import { PopupProps } from '@typebot.io/js'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = Pick<PopupProps, 'autoShowDelay'>
export const JavascriptPopupSnippet = ({ autoShowDelay }: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
typebot: typebot?.publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
autoShowDelay,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: PopupProps): string => {
const jsCode = parseInitPopupCode(params)
return `<script type="module">${jsCode}</script>`
}

View File

@ -0,0 +1,51 @@
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { parseInitStandardCode } from '../../snippetParsers'
import { useTypebot } from '@/features/editor'
import { CodeEditor } from '@/components/CodeEditor'
import { isCloudProdInstance } from '@/utils/helpers'
import { env, getViewerUrl } from 'utils'
type Props = {
widthLabel?: string
heightLabel?: string
}
export const JavascriptStandardSnippet = ({
widthLabel,
heightLabel,
}: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`${parseStandardHeadCode(typebot?.publicId)}
${parseStandardElementCode(widthLabel, heightLabel)}`,
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
export const parseStandardHeadCode = (publicId?: string | null) =>
prettier.format(
`<script type="module">${parseInitStandardCode({
typebot: publicId ?? '',
apiHost: isCloudProdInstance
? undefined
: env('VIEWER_INTERNAL_URL') ?? getViewerUrl(),
})}</script>`,
{ parser: 'html', plugins: [parserHtml] }
)
export const parseStandardElementCode = (width?: string, height?: string) => {
if (!width && !height) return '<typebot-standard></typebot-standard>'
return prettier.format(
`<typebot-standard style="${width ? `width: ${width}; ` : ''}${
height ? `height: ${height}; ` : ''
}"></typebot-standard>`,
{ parser: 'html', plugins: [parserHtml] }
)
}

View File

@ -0,0 +1,43 @@
import { useTypebot } from '@/features/editor'
import { Stack, Code, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { Typebot } from 'models'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
button: {
backgroundColor: typebot?.theme.chat.buttons.backgroundColor,
iconColor: typebot?.theme.chat.buttons.color,
},
previewMessage: {
backgroundColor: typebot?.theme.general.background.content ?? 'white',
textColor: 'black',
},
})
export const JavascriptBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={typebot?.theme.chat.hostAvatar?.url ?? ''}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptBubbleSnippet theme={theme} previewMessage={previewMessage} />
</Stack>
)
}

View File

@ -0,0 +1,23 @@
import { JavascriptBubbleInstructions } from './JavascriptBubbleInstructions'
import { JavascriptPopupInstructions } from './JavascriptPopupInstructions'
import { JavascriptStandardInstructions } from './JavascriptStandardInstructions'
type JavascriptInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const JavascriptInstructions = ({
type,
}: JavascriptInstructionsProps) => {
switch (type) {
case 'standard': {
return <JavascriptStandardInstructions />
}
case 'popup': {
return <JavascriptPopupInstructions />
}
case 'bubble': {
return <JavascriptBubbleInstructions />
}
}
}

View File

@ -0,0 +1,20 @@
import { Stack, Code, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../JavascriptPopupSnippet'
export const JavascriptPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) => setInputValue(settings.autoShowDelay)}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
)
}

View File

@ -0,0 +1,26 @@
import { Stack, Code, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import { JavascriptStandardSnippet } from '../JavascriptStandardSnippet'
export const JavascriptStandardInstructions = () => {
const [inputValues, setInputValues] = useState<{
heightLabel: string
widthLabel?: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(settings) => setInputValues({ ...settings })}
/>
<Text>
Paste this anywhere in the <Code>{'<body>'}</Code>:
</Text>
<JavascriptStandardSnippet {...inputValues} />
</Stack>
)
}

View File

@ -10,11 +10,13 @@ import {
ModalBody,
OrderedList,
ListItem,
Tag,
Code,
InputGroup,
Input,
InputRightElement,
ModalFooter,
Text,
Stack,
} from '@chakra-ui/react'
import { env, getViewerUrl } from 'utils'
import { ModalProps } from '../EmbedButton'
@ -37,28 +39,30 @@ export const NotionModal = ({
{!isPublished && (
<AlertInfo mb="4">You need to publish your bot first.</AlertInfo>
)}
<OrderedList spacing={3}>
<OrderedList spacing={4}>
<ListItem>
Type <Tag>/embed</Tag>
Type <Code>/embed</Code>
</ListItem>
<ListItem>
Paste your typebot URL
<InputGroup size="md" mt={2}>
<Input
pr="4.5rem"
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
<Stack>
<Text>Paste your typebot URL</Text>
<InputGroup size="sm">
<Input
type={'text'}
defaultValue={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
<InputRightElement width="60px">
<CopyButton
size="sm"
textToCopy={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
</Stack>
</ListItem>
</OrderedList>
</ModalBody>

View File

@ -0,0 +1,25 @@
import React, { useState } from 'react'
import { isDefined } from '@udecode/plate-common'
import { EmbedModal } from '../EmbedModal'
import { JavascriptInstructions } from './Javascript/instructions/JavascriptInstructions'
import { ModalProps } from '../EmbedButton'
export const OtherModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Other"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<JavascriptInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,11 @@
import { CodeEditor } from '@/components/CodeEditor'
export const InstallReactPackageSnippet = () => {
return (
<CodeEditor
value={`npm install @typebot.io/js @typebot.io/react`}
isReadOnly
lang="shell"
/>
)
}

View File

@ -0,0 +1,31 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import { BubbleProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactBubbleProps } from '../../snippetParsers'
export const ReactBubbleSnippet = ({
theme,
previewMessage,
}: Pick<BubbleProps, 'theme' | 'previewMessage'>) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Bubble } from "@typebot.io/react";
const App = () => {
return <Bubble ${parseReactBubbleProps({
typebot: typebot?.publicId ?? '',
theme,
previewMessage,
})}/>
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -1,95 +0,0 @@
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

@ -1,65 +1,25 @@
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'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { ReactInstructions } from './instructions/ReactInstructions'
export const ReactModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [chosenEmbedType, setChosenEmbedType] = useState<
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<Modal
<EmbedModal
titlePrefix="React"
isOpen={isOpen}
onClose={onClose}
size={!chosenEmbedType ? '2xl' : 'xl'}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
<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>
{isDefined(selectedEmbedType) && (
<ReactInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1,29 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import { PopupProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactPopupProps } from '../../snippetParsers'
export const ReactPopupSnippet = ({
autoShowDelay,
}: Pick<PopupProps, 'autoShowDelay'>) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Popup } from "@typebot.io/react";
const App = () => {
return <Popup ${parseReactPopupProps({
typebot: typebot?.publicId ?? '',
autoShowDelay,
})}/>;
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -0,0 +1,28 @@
import { CodeEditor } from '@/components/CodeEditor'
import { useTypebot } from '@/features/editor'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseReactBotProps } from '../../snippetParsers'
type ReactStandardSnippetProps = { widthLabel?: string; heightLabel: string }
export const ReactStandardSnippet = ({
widthLabel,
heightLabel,
}: ReactStandardSnippetProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Standard } from "@typebot.io/react";
const App = () => {
return <Standard ${parseReactBotProps({
typebot: typebot?.publicId ?? '',
})} style={{width: "${widthLabel}", height: "${heightLabel}"}} />
}`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="javascript" isReadOnly />
}

View File

@ -0,0 +1,42 @@
import { useTypebot } from '@/features/editor'
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactBubbleSnippet } from '../ReactBubbleSnippet'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
export const ReactBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
theme={theme}
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
onThemeChange={setTheme}
onPreviewMessageChange={setPreviewMessage}
/>
<ReactBubbleSnippet theme={theme} previewMessage={previewMessage} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,21 @@
import { ReactBubbleInstructions } from './ReactBubbleInstructions'
import { ReactPopupInstructions } from './ReactPopupInstructions'
import { ReactStandardInstructions } from './ReactStandardInstructions'
type Props = {
type: 'standard' | 'popup' | 'bubble'
}
export const ReactInstructions = ({ type }: Props) => {
switch (type) {
case 'standard': {
return <ReactStandardInstructions />
}
case 'popup': {
return <ReactPopupInstructions />
}
case 'bubble': {
return <ReactBubbleInstructions />
}
}
}

View File

@ -0,0 +1,30 @@
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactPopupSnippet } from '../ReactPopupSnippet'
export const ReactPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<ReactPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,36 @@
import { ListItem, OrderedList, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import { InstallReactPackageSnippet } from '../InstallReactPackageSnippet'
import { ReactStandardSnippet } from '../ReactStandardSnippet'
export const ReactStandardInstructions = () => {
const [inputValues, setInputValues] = useState<{
widthLabel?: string
heightLabel: string
}>({
heightLabel: '100%',
widthLabel: '100%',
})
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
<Stack spacing={4}>
<Text>Install the packages</Text>
<InstallReactPackageSnippet />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(settings) =>
setInputValues({ ...settings })
}
/>
<ReactStandardSnippet {...inputValues} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -1,146 +0,0 @@
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()}/${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,30 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { ShopifyInstructions } from './instructions/ShopifyInstructions'
export const ShopifyModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Shopify"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<ShopifyInstructions type={selectedEmbedType} publicId={publicId} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './ShopifyModal'

View File

@ -1,70 +0,0 @@
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,47 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const ShopifyBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
theme={theme}
onPreviewMessageChange={setPreviewMessage}
onThemeChange={setTheme}
/>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,25 @@
import { ShopifyBubbleInstructions } from './ShopifyBubbleInstructions'
import { ShopifyPopupInstructions } from './ShopifyPopupInstructions'
import { ShopifyStandardInstructions } from './ShopifyStandardInstructions'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
publicId: string
}
export const ShopifyInstructions = ({
type,
publicId,
}: ShopifyInstructionsProps) => {
switch (type) {
case 'standard': {
return <ShopifyStandardInstructions publicId={publicId} />
}
case 'popup': {
return <ShopifyPopupInstructions />
}
case 'bubble': {
return <ShopifyBubbleInstructions />
}
}
}

View File

@ -0,0 +1,31 @@
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const ShopifyPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,65 @@
import { CodeEditor } from '@/components/CodeEditor'
import { OrderedList, ListItem, Stack, Text, Code } from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
import {
parseStandardElementCode,
parseStandardHeadCode,
} from '../../Javascript/JavascriptStandardSnippet'
type Props = {
publicId: string
}
export const ShopifyStandardInstructions = ({ publicId }: Props) => {
const [windowSizes, setWindowSizes] = useState<{
width?: string
height: string
}>({
height: '100%',
width: '100%',
})
const headCode = parseStandardHeadCode(publicId)
const elementCode = parseStandardElementCode(
windowSizes.width,
windowSizes.height
)
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
On your shop dashboard in the <Code>Themes</Code> page, click on{' '}
<Code>Actions {'>'} Edit code</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>
In <Code>Layout {'>'} theme.liquid</Code> file, paste this code just
before the closing <Code>{'<head>'}</Code> tag:
</Text>
<CodeEditor value={headCode} lang="html" isReadOnly />
</Stack>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<Text>
Place an element on which the typebot will go in any file in the{' '}
<Code>{'<body>'}</Code>:
</Text>
<CodeEditor value={elementCode} lang="html" isReadOnly />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -1,91 +0,0 @@
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,26 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { WebflowInstructions } from './instructions/WebflowInstructions'
export const WebflowModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Webflow"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<WebflowInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './WebflowModal'

View File

@ -1,65 +0,0 @@
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,46 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const WebflowBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Press <Code>A</Code> to open the <Code>Add elements</Code> panel
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
theme={theme}
onPreviewMessageChange={setPreviewMessage}
onThemeChange={setTheme}
/>
<Text>
Add an <Code>embed</Code> element from the <Code>components</Code>{' '}
section and paste this code:
</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,21 @@
import { WebflowStandardInstructions } from './WebflowStandardInstructions'
import { WebflowPopupInstructions } from './WebflowPopupInstructions'
import { WebflowBubbleInstructions } from './WebflowBubbleInstructions'
type WebflowInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const WebflowInstructions = ({ type }: WebflowInstructionsProps) => {
switch (type) {
case 'standard': {
return <WebflowStandardInstructions />
}
case 'popup': {
return <WebflowPopupInstructions />
}
case 'bubble': {
return <WebflowBubbleInstructions />
}
}
}

View File

@ -0,0 +1,30 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const WebflowPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Press <Code>A</Code> to open the <Code>Add elements</Code> panel
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>
Add an <Code>embed</Code> element from the <Code>components</Code>{' '}
section and paste this code:
</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,19 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { JavascriptStandardSnippet } from '../../Javascript/JavascriptStandardSnippet'
export const WebflowStandardInstructions = () => (
<OrderedList spacing={4} pl={5}>
<ListItem>
Press <Code>A</Code> to open the <Code>Add elements</Code> panel
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>
Add an <Code>embed</Code> element from the <Code>components</Code>{' '}
section and paste this code:
</Text>
<JavascriptStandardSnippet />
</Stack>
</ListItem>
</OrderedList>
)

View File

@ -1,105 +0,0 @@
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,26 @@
import React, { useState } from 'react'
import { ModalProps } from '../../EmbedButton'
import { EmbedModal } from '../../EmbedModal'
import { isDefined } from '@udecode/plate-common'
import { WixInstructions } from './instructions/WixInstructions'
export const WixModal = ({ isOpen, onClose, isPublished }: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Wix"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<WixInstructions type={selectedEmbedType} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './WixModal'

View File

@ -1,65 +0,0 @@
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,49 @@
import { useTypebot } from '@/features/editor'
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
import { JavascriptBubbleSnippet } from '../../Javascript/JavascriptBubbleSnippet'
export const WixBubbleInstructions = () => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Go to <Code>Settings</Code> in your dashboard on Wix
</ListItem>
<ListItem>
Click on <Code>Custom Code</Code> under <Code>Advanced</Code>
</ListItem>
<ListItem>
Click <Code>+ Add Custom Code</Code> at the top right.
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
theme={theme}
onPreviewMessageChange={setPreviewMessage}
onThemeChange={setTheme}
/>
<Text> Paste this snippet in the code box:</Text>
<JavascriptBubbleSnippet
theme={theme}
previewMessage={previewMessage}
/>
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,21 @@
import { WixBubbleInstructions } from './WixBubbleInstructions'
import { WixPopupInstructions } from './WixPopupInstructions'
import { WixStandardInstructions } from './WixStandardInstuctions'
type WixInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
}
export const WixInstructions = ({ type }: WixInstructionsProps) => {
switch (type) {
case 'standard': {
return <WixStandardInstructions />
}
case 'popup': {
return <WixPopupInstructions />
}
case 'bubble': {
return <WixBubbleInstructions />
}
}
}

View File

@ -0,0 +1,33 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { JavascriptPopupSnippet } from '../../Javascript/JavascriptPopupSnippet'
export const WixPopupInstructions = () => {
const [inputValue, setInputValue] = useState<number>()
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Go to <Code>Settings</Code> in your dashboard on Wix
</ListItem>
<ListItem>
Click on <Code>Custom Code</Code> under <Code>Advanced</Code>
</ListItem>
<ListItem>
Click <Code>+ Add Custom Code</Code> at the top right.
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setInputValue(settings.autoShowDelay)
}
/>
<Text>Paste this snippet in the code box:</Text>
<JavascriptPopupSnippet autoShowDelay={inputValue} />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,23 @@
import { OrderedList, ListItem, Code, Stack, Text } from '@chakra-ui/react'
import { JavascriptStandardSnippet } from '../../Javascript/JavascriptStandardSnippet'
export const WixStandardInstructions = () => {
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
In the Wix Website Editor:
<Code>
Add {'>'} Embed {'>'} Embed a Widget
</Code>
</ListItem>
<ListItem>
<Stack spacing={4}>
<Text>
Click on <Code>Enter code</Code> and paste this code:
</Text>
<JavascriptStandardSnippet widthLabel="100%" heightLabel="100%" />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -1,80 +0,0 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
Heading,
ModalCloseButton,
ModalBody,
OrderedList,
ListItem,
InputGroup,
Input,
InputRightElement,
ModalFooter,
Link,
useColorModeValue,
} 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={useColorModeValue('blue.500', 'blue.300')}
>
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()
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
}/${publicId}`}
/>
</InputRightElement>
</InputGroup>
</ListItem>
<ListItem>Complete the setup in your Wordpress interface</ListItem>
</OrderedList>
</ModalBody>
<ModalFooter />
</ModalContent>
</Modal>
)
}

View File

@ -0,0 +1,30 @@
import { isDefined } from 'utils'
import { ModalProps } from '../../EmbedButton'
import { useState } from 'react'
import { EmbedModal } from '../../EmbedModal'
import { WordpressInstructions } from './instructions/WordpressInstructions'
export const WordpressModal = ({
isOpen,
onClose,
isPublished,
publicId,
}: ModalProps) => {
const [selectedEmbedType, setSelectedEmbedType] = useState<
'standard' | 'popup' | 'bubble' | undefined
>()
return (
<EmbedModal
titlePrefix="Wordpress"
isOpen={isOpen}
onClose={onClose}
isPublished={isPublished}
onSelectEmbedType={setSelectedEmbedType}
selectedEmbedType={selectedEmbedType}
>
{isDefined(selectedEmbedType) && (
<WordpressInstructions type={selectedEmbedType} publicId={publicId} />
)}
</EmbedModal>
)
}

View File

@ -0,0 +1 @@
export * from './WordpressModal'

View File

@ -0,0 +1,74 @@
import { CodeEditor } from '@/components/CodeEditor'
import { ExternalLinkIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor'
import {
OrderedList,
ListItem,
useColorModeValue,
Link,
Stack,
Text,
} from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { useState } from 'react'
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
import { parseInitBubbleCode } from '../../../snippetParsers'
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
type Props = {
publicId: string
}
export const WordpressBubbleInstructions = ({ publicId }: Props) => {
const { typebot } = useTypebot()
const [theme, setTheme] = useState<BubbleProps['theme']>(
parseDefaultBubbleTheme(typebot)
)
const [previewMessage, setPreviewMessage] =
useState<BubbleProps['previewMessage']>()
const initCode = parseInitBubbleCode({
typebot: publicId,
theme: {
...theme,
chatWindow: {
backgroundColor: typebot?.theme.general.background.content ?? '#fff',
},
},
previewMessage,
})
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Install{' '}
<Link
href="https://wordpress.org/plugins/typebot/"
isExternal
color={useColorModeValue('blue.500', 'blue.300')}
>
the official Typebot WordPress plugin
<ExternalLinkIcon mx="2px" />
</Link>
</ListItem>
<ListItem>
<Stack spacing={4}>
<BubbleSettings
previewMessage={previewMessage}
defaultPreviewMessageAvatar={
typebot?.theme.chat.hostAvatar?.url ?? ''
}
theme={theme}
onPreviewMessageChange={setPreviewMessage}
onThemeChange={setTheme}
/>
<Text>
You can now place the following code snippet in the Typebot panel in
your WordPress admin:
</Text>
<CodeEditor value={initCode} lang="javascript" isReadOnly />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,22 @@
import { WordpressBubbleInstructions } from './WordpressBubbleInstructions'
import { WordpressPopupInstructions } from './WordpressPopupInstructions'
import { WordpressStandardInstructions } from './WordpressStandardInstructions'
type Props = {
publicId: string
type: 'standard' | 'popup' | 'bubble'
}
export const WordpressInstructions = ({ publicId, type }: Props) => {
switch (type) {
case 'standard': {
return <WordpressStandardInstructions publicId={publicId} />
}
case 'popup': {
return <WordpressPopupInstructions publicId={publicId} />
}
case 'bubble': {
return <WordpressBubbleInstructions publicId={publicId} />
}
}
}

View File

@ -0,0 +1,55 @@
import { CodeEditor } from '@/components/CodeEditor'
import { ExternalLinkIcon } from '@/components/icons'
import {
OrderedList,
ListItem,
useColorModeValue,
Link,
Stack,
Text,
} from '@chakra-ui/react'
import { useState } from 'react'
import { PopupSettings } from '../../../settings/PopupSettings'
import { parseInitPopupCode } from '../../../snippetParsers/popup'
type Props = {
publicId: string
}
export const WordpressPopupInstructions = ({ publicId }: Props) => {
const [autoShowDelay, setAutoShowDelay] = useState<number>()
const initCode = parseInitPopupCode({
typebot: publicId,
autoShowDelay,
})
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Install{' '}
<Link
href="https://wordpress.org/plugins/typebot/"
isExternal
color={useColorModeValue('blue.500', 'blue.300')}
>
the official Typebot WordPress plugin
<ExternalLinkIcon mx="2px" />
</Link>
</ListItem>
<ListItem>
<Stack spacing={4}>
<PopupSettings
onUpdateSettings={(settings) =>
setAutoShowDelay(settings.autoShowDelay)
}
/>
<Text>
You can now place the following code snippet in the Typebot panel in
your WordPress admin:
</Text>
<CodeEditor value={initCode} lang="javascript" isReadOnly />
</Stack>
</ListItem>
</OrderedList>
)
}

View File

@ -0,0 +1,80 @@
import { CodeEditor } from '@/components/CodeEditor'
import { ExternalLinkIcon } from '@/components/icons'
import {
OrderedList,
ListItem,
useColorModeValue,
Link,
Stack,
Text,
Code,
} from '@chakra-ui/react'
import { useState } from 'react'
import { StandardSettings } from '../../../settings/StandardSettings'
type Props = {
publicId: string
}
export const WordpressStandardInstructions = ({ publicId }: Props) => {
const [windowSizes, setWindowSizes] = useState<{
width?: string
height: string
}>({
height: '100%',
width: '100%',
})
const elementCode = parseWordpressShortcode({ ...windowSizes, publicId })
return (
<OrderedList spacing={4} pl={5}>
<ListItem>
Install{' '}
<Link
href="https://wordpress.org/plugins/typebot/"
isExternal
color={useColorModeValue('blue.500', 'blue.300')}
>
the official Typebot WordPress plugin
<ExternalLinkIcon mx="2px" />
</Link>
</ListItem>
<ListItem>
<Stack spacing={4}>
<StandardSettings
onUpdateWindowSettings={(sizes) =>
setWindowSizes({
height: sizes.heightLabel,
width: sizes.widthLabel,
})
}
/>
<Text>
You can now place the following shortcode anywhere on your site:
</Text>
<CodeEditor value={elementCode} lang="shell" isReadOnly />
<Text>
Note: Your page templating system probably has a{' '}
<Code>Shortcode</Code> element (if not, use a text element).
</Text>
</Stack>
</ListItem>
</OrderedList>
)
}
const parseWordpressShortcode = ({
width,
height,
publicId,
}: {
width?: string
height?: string
publicId: string
}) => {
return `[typebot typebot="${publicId}"${width ? ` width="${width}"` : ''}${
height ? ` height="${height}"` : ''
}]
`
}

View File

@ -1,4 +1,4 @@
export * from './IframeModal'
export * from './IframeModal/IframeModal'
export * from './NotionModal'
export * from './WordpressModal'
export * from './WixModal'

View File

@ -0,0 +1,96 @@
import { Stack, Heading, HStack, Flex, Text, Image } from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import { isDefined } from 'utils'
import { PreviewMessageSettings } from './PreviewMessageSettings'
import { ThemeSettings } from './ThemeSettings'
type Props = {
defaultPreviewMessageAvatar: string
theme: BubbleProps['theme']
previewMessage: BubbleProps['previewMessage']
onThemeChange: (theme: BubbleProps['theme']) => void
onPreviewMessageChange: (
previewMessage: BubbleProps['previewMessage']
) => void
}
export const BubbleSettings = ({
defaultPreviewMessageAvatar,
theme,
previewMessage,
onThemeChange,
onPreviewMessageChange,
}: Props) => {
const updatePreviewMessage = (
previewMessage: BubbleProps['previewMessage']
) => {
onPreviewMessageChange(previewMessage)
}
const updateTheme = (theme: BubbleProps['theme']) => {
onThemeChange(theme)
}
return (
<Stack spacing="4">
<Heading size="sm">Chat bubble settings</Heading>
<Stack pl="4" spacing={4}>
<PreviewMessageSettings
defaultAvatar={defaultPreviewMessageAvatar}
onChange={updatePreviewMessage}
/>
<ThemeSettings
theme={theme}
onChange={updateTheme}
isPreviewMessageEnabled={isDefined(previewMessage)}
/>
<Heading size="sm">Preview:</Heading>
<Stack alignItems="flex-end">
{isDefined(previewMessage) && (
<HStack
bgColor={theme?.previewMessage?.backgroundColor}
shadow="md"
rounded="md"
p="3"
maxW="280px"
spacing={4}
>
{previewMessage.avatarUrl && (
<Image
src={previewMessage.avatarUrl}
w="40px"
h="40px"
rounded="full"
alt="Preview message avatar"
objectFit="cover"
/>
)}
<Text color={theme?.previewMessage?.textColor}>
{previewMessage.message}
</Text>
</HStack>
)}
<Flex
align="center"
justifyContent="center"
boxSize="3rem"
bgColor={theme?.button?.backgroundColor}
rounded="full"
>
<svg
viewBox="0 0 24 24"
style={{
stroke: theme?.button?.iconColor,
}}
width="30px"
strokeWidth="2px"
fill="transparent"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
</Flex>
</Stack>
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,65 @@
import { ColorPicker } from '@/components/ColorPicker'
import { Heading, HStack, Input, Stack, Text } from '@chakra-ui/react'
import { ButtonTheme } from '@typebot.io/js/dist/features/bubble/types'
import React from 'react'
type Props = {
buttonTheme: ButtonTheme | undefined
onChange: (newButtonTheme?: ButtonTheme) => void
}
export const ButtonThemeSettings = ({ buttonTheme, onChange }: Props) => {
const updateBackgroundColor = (backgroundColor: string) => {
onChange({
...buttonTheme,
backgroundColor,
})
}
const updateIconColor = (iconColor: string) => {
onChange({
...buttonTheme,
iconColor,
})
}
const updateCustomIconSrc = (customIconSrc: string) => {
onChange({
...buttonTheme,
customIconSrc,
})
}
return (
<Stack spacing={4} borderWidth="1px" rounded="md" p="4">
<Heading size="sm">Button</Heading>
<Stack spacing={4}>
<HStack justify="space-between">
<Text>Background color</Text>
<ColorPicker
initialColor={buttonTheme?.backgroundColor}
onColorChange={updateBackgroundColor}
/>
</HStack>
<HStack justify="space-between">
<Text>Icon color</Text>
<ColorPicker
initialColor={buttonTheme?.iconColor}
onColorChange={updateIconColor}
/>
</HStack>
<HStack justify="space-between">
<Text>Custom icon</Text>
<Input
placeholder={'Paste image link (.png, .svg)'}
value={buttonTheme?.customIconSrc}
onChange={(e) => updateCustomIconSrc(e.target.value)}
minW="0"
w="300px"
size="sm"
/>
</HStack>
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,121 @@
import { SmartNumberInput } from '@/components/inputs'
import { FormLabel, HStack, Input, Stack, Switch, Text } from '@chakra-ui/react'
import { PreviewMessageParams } from '@typebot.io/js/dist/features/bubble/types'
import { useState } from 'react'
import { isDefined } from 'utils'
type Props = {
defaultAvatar: string
onChange: (newPreviewMessage?: PreviewMessageParams) => void
}
export const PreviewMessageSettings = ({ defaultAvatar, onChange }: Props) => {
const [isPreviewMessageEnabled, setIsPreviewMessageEnabled] = useState(false)
const [previewMessage, setPreviewMessage] = useState<PreviewMessageParams>()
const [autoShowDelay, setAutoShowDelay] = useState(10)
const [isAutoShowEnabled, setIsAutoShowEnabled] = useState(false)
const updatePreviewMessage = (previewMessage: PreviewMessageParams) => {
setPreviewMessage(previewMessage)
onChange(previewMessage)
}
const updateAutoShowDelay = (autoShowDelay?: number) => {
setAutoShowDelay(autoShowDelay ?? 0)
updatePreviewMessage({
...previewMessage,
message: previewMessage?.message ?? '',
autoShowDelay,
})
}
const updateAvatarUrl = (avatarUrl: string) => {
updatePreviewMessage({
...previewMessage,
message: previewMessage?.message ?? '',
avatarUrl,
})
}
const updateMessage = (message: string) => {
updatePreviewMessage({ ...previewMessage, message })
}
const updatePreviewMessageCheck = (isChecked: boolean) => {
setIsPreviewMessageEnabled(isChecked)
const newPreviewMessage = {
autoShowDelay: isAutoShowEnabled ? autoShowDelay : undefined,
message: previewMessage?.message ?? 'I have a question for you!',
avatarUrl: previewMessage?.avatarUrl ?? defaultAvatar,
}
if (isChecked) setPreviewMessage(newPreviewMessage)
onChange(isChecked ? newPreviewMessage : undefined)
}
const updateAutoShowDelayCheck = (isChecked: boolean) => {
setIsAutoShowEnabled(isChecked)
updatePreviewMessage({
...previewMessage,
message: previewMessage?.message ?? '',
autoShowDelay: isChecked ? autoShowDelay : undefined,
})
}
return (
<Stack spacing={4}>
<HStack justifyContent="space-between">
<FormLabel htmlFor="preview" mb="0">
Preview message
</FormLabel>
<Switch
id="preview"
isChecked={isPreviewMessageEnabled}
onChange={(e) => updatePreviewMessageCheck(e.target.checked)}
/>
</HStack>
{isPreviewMessageEnabled && (
<Stack pl="4" spacing={4}>
<HStack justify="space-between">
<Text>Avatar URL</Text>
<Input
onChange={(e) => updateAvatarUrl(e.target.value)}
value={previewMessage?.avatarUrl}
placeholder={'Paste image link (.png, .jpg)'}
/>
</HStack>
<HStack justify="space-between">
<Text>Message</Text>
<Input
onChange={(e) => updateMessage(e.target.value)}
value={previewMessage?.message}
/>
</HStack>
<HStack>
<Text>Auto show</Text>
<Switch
isChecked={isAutoShowEnabled}
onChange={(e) => updateAutoShowDelayCheck(e.target.checked)}
/>
{isAutoShowEnabled && (
<>
<Text>After</Text>
<SmartNumberInput
size="sm"
w="70px"
defaultValue={autoShowDelay}
onValueChange={(val) =>
isDefined(val) && updateAutoShowDelay(val)
}
withVariableButton={false}
/>
<Text>seconds</Text>
</>
)}
</HStack>
</Stack>
)}
</Stack>
)
}

View File

@ -0,0 +1,80 @@
import { ColorPicker } from '@/components/ColorPicker'
import { Heading, HStack, Stack, Text } from '@chakra-ui/react'
import { PreviewMessageTheme } from '@typebot.io/js/dist/features/bubble/types'
import React from 'react'
type Props = {
previewMessageTheme?: PreviewMessageTheme
onChange: (newPreviewMessageTheme?: PreviewMessageTheme) => void
}
export const PreviewMessageThemeSettings = ({
previewMessageTheme,
onChange,
}: Props) => {
const updateBackgroundColor = (backgroundColor: string) => {
onChange({
...previewMessageTheme,
backgroundColor,
})
}
const updateTextColor = (textColor: string) => {
onChange({
...previewMessageTheme,
textColor,
})
}
const updateCloseButtonBackgroundColor = (
closeButtonBackgroundColor: string
) => {
onChange({
...previewMessageTheme,
closeButtonBackgroundColor,
})
}
const updateCloseButtonIconColor = (closeButtonIconColor: string) => {
onChange({
...previewMessageTheme,
closeButtonIconColor,
})
}
return (
<Stack spacing={4} borderWidth="1px" rounded="md" p="4">
<Heading size="sm">Preview message</Heading>
<Stack spacing={4}>
<HStack justify="space-between">
<Text>Background color</Text>
<ColorPicker
initialColor={previewMessageTheme?.backgroundColor}
onColorChange={updateBackgroundColor}
/>
</HStack>
<HStack justify="space-between">
<Text>Text color</Text>
<ColorPicker
initialColor={previewMessageTheme?.textColor}
onColorChange={updateTextColor}
/>
</HStack>
<HStack justify="space-between">
<Text>Close button background</Text>
<ColorPicker
initialColor={previewMessageTheme?.closeButtonBackgroundColor}
onColorChange={updateCloseButtonBackgroundColor}
/>
</HStack>
<HStack justify="space-between">
<Text>Close icon color</Text>
<ColorPicker
initialColor={previewMessageTheme?.closeButtonIconColor}
onColorChange={updateCloseButtonIconColor}
/>
</HStack>
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,68 @@
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
HStack,
Stack,
Text,
} from '@chakra-ui/react'
import { BubbleProps } from '@typebot.io/js'
import {
ButtonTheme,
PreviewMessageTheme,
} from '@typebot.io/js/dist/features/bubble/types'
import { ButtonThemeSettings } from './ButtonThemeSettings'
import { PreviewMessageThemeSettings } from './PreviewMessageThemeSettings'
type Props = {
isPreviewMessageEnabled: boolean
theme: BubbleProps['theme']
onChange: (newBubbleTheme: BubbleProps['theme']) => void
}
export const ThemeSettings = ({
isPreviewMessageEnabled,
theme,
onChange,
}: Props) => {
const updateButtonTheme = (button?: ButtonTheme) => {
onChange({
...theme,
button,
})
}
const updatePreviewMessageTheme = (previewMessage?: PreviewMessageTheme) => {
onChange({
...theme,
previewMessage,
})
}
return (
<Accordion allowMultiple>
<AccordionItem>
<AccordionButton px="0">
<HStack flex="1">
<Text>Theme</Text>
</HStack>
<AccordionIcon />
</AccordionButton>
<AccordionPanel as={Stack} pb={4} spacing={4} px="0">
<ButtonThemeSettings
buttonTheme={theme?.button}
onChange={updateButtonTheme}
/>
{isPreviewMessageEnabled ? (
<PreviewMessageThemeSettings
previewMessageTheme={theme?.previewMessage}
onChange={updatePreviewMessageTheme}
/>
) : null}
</AccordionPanel>
</AccordionItem>
</Accordion>
)
}

View File

@ -0,0 +1,54 @@
import { SmartNumberInput } from '@/components/inputs'
import {
StackProps,
Stack,
Heading,
Switch,
HStack,
Text,
} from '@chakra-ui/react'
import { PopupProps } from '@typebot.io/js'
import { useState, useEffect } from 'react'
import { isDefined } from 'utils'
type Props = {
onUpdateSettings: (windowSettings: Pick<PopupProps, 'autoShowDelay'>) => void
} & StackProps
export const PopupSettings = ({ onUpdateSettings, ...props }: Props) => {
const [isEnabled, setIsEnabled] = useState(false)
const [inputValue, setInputValue] = useState(5)
useEffect(() => {
onUpdateSettings({
autoShowDelay: isEnabled ? inputValue * 1000 : undefined,
})
}, [inputValue, isEnabled, onUpdateSettings])
return (
<Stack {...props} spacing={4}>
<Heading size="sm">Popup settings</Heading>
<HStack pl={4}>
<p>Auto show</p>
<Switch
isChecked={isEnabled}
onChange={(e) => setIsEnabled(e.target.checked)}
/>
{isEnabled && (
<>
<SmartNumberInput
label="After"
size="sm"
w="70px"
defaultValue={inputValue}
onValueChange={(val) => isDefined(val) && setInputValue(val)}
withVariableButton={false}
/>
<Text>seconds</Text>
</>
)}
</HStack>
</Stack>
)
}

View File

@ -0,0 +1,107 @@
import {
StackProps,
Stack,
Flex,
Heading,
Input,
HStack,
Text,
} from '@chakra-ui/react'
import { DropdownList } from '@/components/DropdownList'
import { useState, useEffect } from 'react'
import { SwitchWithLabel } from '@/components/SwitchWithLabel'
type Props = {
onUpdateWindowSettings: (windowSettings: {
heightLabel: string
widthLabel?: string
}) => void
} & StackProps
export const StandardSettings = ({
onUpdateWindowSettings,
...props
}: Props) => {
const [isFullscreenChecked, setIsFullscreenChecked] = useState(false)
const [inputValues, setInputValues] = useState({
widthValue: '100',
widthType: '%',
heightValue: '600',
heightType: 'px',
})
useEffect(() => {
onUpdateWindowSettings({
widthLabel: isFullscreenChecked
? undefined
: inputValues.widthValue + inputValues.widthType,
heightLabel: isFullscreenChecked
? '100vh'
: inputValues.heightValue + inputValues.heightType,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValues, isFullscreenChecked])
const handleWidthTypeSelect = (widthType: string) =>
setInputValues({ ...inputValues, widthType })
const handleHeightTypeSelect = (heightType: string) =>
setInputValues({ ...inputValues, heightType })
return (
<Stack {...props} spacing={4}>
<Heading size="sm">Window settings</Heading>
<Stack pl="4" spacing={4}>
<SwitchWithLabel
label="Set to fullscreen?"
initialValue={isFullscreenChecked}
onCheckChange={() => setIsFullscreenChecked(!isFullscreenChecked)}
/>
{!isFullscreenChecked && (
<>
<Flex justify="space-between" align="center">
<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">
<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>
</Stack>
)
}

View File

@ -0,0 +1,162 @@
import { BubbleProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import {
parseStringParam,
parseBotProps,
parseNumberOrBoolParam,
parseReactBotProps,
typebotImportUrl,
} from './shared'
const parseButtonTheme = (
button: NonNullable<BubbleProps['theme']>['button']
): string => {
if (!button) return ''
const { backgroundColor, iconColor, customIconSrc } = button
const backgroundColorLine = parseStringParam(
'backgroundColor',
backgroundColor
)
const iconColorLine = parseStringParam('iconColor', iconColor)
const customIconLine = parseStringParam('customIconSrc', customIconSrc)
const line = `button: {${backgroundColorLine}${iconColorLine}${customIconLine}},`
if (line === 'button: {},') return ''
return line
}
const parsePreviewMessageTheme = (
previewMessage: NonNullable<BubbleProps['theme']>['previewMessage']
): string => {
if (!previewMessage) return ''
const {
backgroundColor,
closeButtonBackgroundColor,
closeButtonIconColor,
textColor,
} = previewMessage
const backgroundColorLine = parseStringParam(
'backgroundColor',
backgroundColor
)
const closeButtonBackgroundColorLine = parseStringParam(
'closeButtonBackgroundColor',
closeButtonBackgroundColor
)
const closeButtonIconColorLine = parseStringParam(
'closeButtonIconColor',
closeButtonIconColor
)
const textColorLine = parseStringParam('textColor', textColor)
const line = `previewMessage: {${backgroundColorLine}${textColorLine}${closeButtonBackgroundColorLine}${closeButtonIconColorLine}},`
if (line === 'previewMessage: {},') return ''
return line
}
const parseChatWindowTheme = (
chatWindow: NonNullable<BubbleProps['theme']>['chatWindow']
) => {
if (!chatWindow) return ''
const backgroundColorLine = parseStringParam(
'backgroundColor',
chatWindow.backgroundColor
)
const line = `chatWindow: {${backgroundColorLine}},`
if (line === 'chatWindow: {},') return ''
return line
}
const parseBubbleTheme = (theme: BubbleProps['theme']): string => {
if (!theme) return ''
const { button, previewMessage } = theme
const buttonThemeLine = parseButtonTheme(button)
const previewMessageThemeLine = parsePreviewMessageTheme(previewMessage)
const chatWindowThemeLine = parseChatWindowTheme(theme.chatWindow)
const line = `theme: {${buttonThemeLine}${previewMessageThemeLine}${chatWindowThemeLine}},`
if (line === 'theme: {},') return ''
return line
}
const parsePreviewMessage = (
previewMessage: BubbleProps['previewMessage']
): string => {
if (!previewMessage) return ''
const { message, autoShowDelay, avatarUrl } = previewMessage
const messageLine = parseStringParam('message', message)
const autoShowDelayLine = parseNumberOrBoolParam(
'autoShowDelay',
autoShowDelay
)
const avatarUrlLine = parseStringParam('avatarUrl', avatarUrl)
const line = `previewMessage: {${messageLine}${autoShowDelayLine}${avatarUrlLine}},`
if (line === 'previewMessage: {},') return ''
return line
}
const parseBubbleProps = ({
previewMessage,
theme,
}: Pick<BubbleProps, 'previewMessage' | 'theme'>) => {
const previewMessageLine = parsePreviewMessage(previewMessage)
const themeLine = parseBubbleTheme(theme)
return `${previewMessageLine}${themeLine}`
}
export const parseInitBubbleCode = ({
typebot,
apiHost,
previewMessage,
theme,
}: BubbleProps) => {
const botProps = parseBotProps({ typebot, apiHost })
const bubbleProps = parseBubbleProps({ previewMessage, theme })
return prettier.format(
`import Typebot from '${typebotImportUrl}'
Typebot.initBubble({${botProps}${bubbleProps}});`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
}
const parseReactBubbleTheme = (theme: BubbleProps['theme']): string => {
if (!theme) return ''
const { button, previewMessage } = theme
const buttonThemeLine = parseButtonTheme(button)
const previewMessageThemeLine = parsePreviewMessageTheme(previewMessage)
const line = `theme={{${buttonThemeLine}${previewMessageThemeLine}}}`
if (line === 'theme={{}}') return ''
return line
}
const parseReactPreviewMessage = (
previewMessage: BubbleProps['previewMessage']
): string => {
if (!previewMessage) return ''
const { message, autoShowDelay, avatarUrl } = previewMessage
const messageLine = parseStringParam('message', message)
const autoShowDelayLine = parseNumberOrBoolParam(
'autoShowDelay',
autoShowDelay
)
const avatarUrlLine = parseStringParam('avatarUrl', avatarUrl)
const line = `previewMessage={{${messageLine}${autoShowDelayLine}${avatarUrlLine}}}`
if (line === 'previewMessage={{}}') return ''
return line
}
export const parseReactBubbleProps = ({
typebot,
apiHost,
previewMessage,
theme,
}: BubbleProps) => {
const botProps = parseReactBotProps({ typebot, apiHost })
const previewMessageProp = parseReactPreviewMessage(previewMessage)
const themeProp = parseReactBubbleTheme(theme)
return `${botProps} ${previewMessageProp} ${themeProp}`
}

View File

@ -0,0 +1,4 @@
export * from './bubble'
export * from './popup'
export * from './shared'
export * from './standard'

View File

@ -0,0 +1,77 @@
import { PopupProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import {
parseBotProps,
parseNumberOrBoolParam,
parseReactBotProps,
parseReactNumberOrBoolParam,
parseReactStringParam,
parseStringParam,
typebotImportUrl,
} from './shared'
const parsePopupTheme = (theme: PopupProps['theme']): string => {
if (!theme) return ''
const { width } = theme
const widthLine = parseStringParam('width', width)
const line = `theme: {${widthLine}},`
if (line === 'theme: {}') return ''
return line
}
const parsePopupProps = ({
autoShowDelay,
theme,
}: Pick<PopupProps, 'theme' | 'autoShowDelay'>) => {
const autoShowDelayLine = parseNumberOrBoolParam(
'autoShowDelay',
autoShowDelay
)
const themeLine = parsePopupTheme(theme)
return `${autoShowDelayLine}${themeLine}`
}
export const parseInitPopupCode = ({
typebot,
apiHost,
theme,
autoShowDelay,
}: PopupProps) => {
const botProps = parseBotProps({ typebot, apiHost })
const bubbleProps = parsePopupProps({ theme, autoShowDelay })
return prettier.format(
`import Typebot from '${typebotImportUrl}'
Typebot.initPopup({${botProps}${bubbleProps}});`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
}
const parseReactThemeProp = (theme: PopupProps['theme']): string => {
if (!theme) return ''
const { width } = theme
const widthProp = parseReactStringParam('width', width)
if (widthProp === 'theme={{}}') return ''
return widthProp
}
export const parseReactPopupProps = ({
typebot,
apiHost,
theme,
autoShowDelay,
}: PopupProps) => {
const botProps = parseReactBotProps({ typebot, apiHost })
const autoShowDelayProp = parseReactNumberOrBoolParam(
'autoShowDelay',
autoShowDelay
)
const themeProp = parseReactThemeProp(theme)
return `${botProps} ${autoShowDelayProp} ${themeProp}`
}

View File

@ -0,0 +1,32 @@
import { BotProps } from '@typebot.io/js'
import { isDefined } from 'utils'
export const parseStringParam = (fieldName: string, fieldValue?: string) =>
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
export const parseNumberOrBoolParam = (
fieldName: string,
fieldValue?: number | boolean
) => (isDefined(fieldValue) ? `${fieldName}: ${fieldValue},` : ``)
export const parseBotProps = ({ typebot, apiHost }: BotProps) => {
const typebotLine = parseStringParam('typebot', typebot as string)
const apiHostLine = parseStringParam('apiHost', apiHost)
return `${typebotLine}${apiHostLine}`
}
export const parseReactStringParam = (fieldName: string, fieldValue?: string) =>
fieldValue ? `${fieldName}="${fieldValue}"` : ``
export const parseReactNumberOrBoolParam = (
fieldName: string,
fieldValue?: number | boolean
) => (isDefined(fieldValue) ? `${fieldName}={${fieldValue}}` : ``)
export const parseReactBotProps = ({ typebot, apiHost }: BotProps) => {
const typebotLine = parseReactStringParam('typebot', typebot as string)
const apiHostLine = parseReactStringParam('apiHost', apiHost)
return `${typebotLine} ${apiHostLine}`
}
export const typebotImportUrl = `https://cdn.jsdelivr.net/npm/@typebot.io/js@0.0.9/dist/web.js`

View File

@ -0,0 +1,21 @@
import { BotProps } from '@typebot.io/js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { parseBotProps, typebotImportUrl } from './shared'
export const parseInitStandardCode = ({
typebot,
apiHost,
}: Pick<BotProps, 'typebot' | 'apiHost'>) => {
const botProps = parseBotProps({ typebot, apiHost })
return prettier.format(
`import Typebot from '${typebotImportUrl}'
Typebot.initStandard({${botProps}});`,
{
parser: 'babel',
plugins: [parserBabel],
}
)
}