✨ 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:
@ -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 &&
|
||||
|
@ -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}
|
||||
/>
|
||||
),
|
||||
|
@ -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>
|
||||
)
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { Variants } from 'framer-motion'
|
||||
|
||||
export const animationVariants: Variants = {
|
||||
animateBubbles: {
|
||||
opacity: [0, 1],
|
||||
},
|
||||
default: { opacity: 1 },
|
||||
}
|
@ -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>`
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>`
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>`
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 <></>
|
||||
}`
|
||||
}
|
@ -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>`
|
@ -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>
|
||||
)
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './GtmModal'
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
@ -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 />
|
||||
}
|
@ -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 />
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>`
|
||||
}
|
@ -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] }
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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"
|
||||
/>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 />
|
||||
}
|
@ -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 />
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './ShopifyModal'
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './WebflowModal'
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './WixModal'
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './WordpressModal'
|
@ -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>
|
||||
)
|
||||
}
|
@ -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} />
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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}"` : ''
|
||||
}]
|
||||
`
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export * from './IframeModal'
|
||||
export * from './IframeModal/IframeModal'
|
||||
export * from './NotionModal'
|
||||
export * from './WordpressModal'
|
||||
export * from './WixModal'
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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}`
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export * from './bubble'
|
||||
export * from './popup'
|
||||
export * from './shared'
|
||||
export * from './standard'
|
@ -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}`
|
||||
}
|
@ -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`
|
@ -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],
|
||||
}
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user