2
0

docs(share): 📝 Add code blocks in embed instructions

This commit is contained in:
Baptiste Arnaud
2022-02-10 08:08:58 +01:00
parent c66827b606
commit 276f1c1e90
23 changed files with 1166 additions and 304 deletions

View File

@ -0,0 +1,38 @@
import { FlexProps } from '@chakra-ui/layout'
import prettier from 'prettier/standalone'
import parserHtml from 'prettier/parser-html'
import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
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({
publishId: typebot?.publicId ?? '',
button,
proactiveMessage,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: BubbleParams): string => {
const jsCode = parseInitBubbleCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -0,0 +1,159 @@
import {
StackProps,
Stack,
Heading,
HStack,
Input,
Flex,
FormControl,
FormLabel,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
Switch,
Text,
Tag,
} from '@chakra-ui/react'
import { ColorPicker } from 'components/theme/GeneralSettings/ColorPicker'
import { useTypebot } from 'contexts/TypebotContext'
import { useState, useEffect } from 'react'
import { BubbleParams } from 'typebot-js'
type ChatEmbedSettingsProps = {
onUpdateSettings: (
windowSettings: Pick<BubbleParams, 'button' | 'proactiveMessage'>
) => void
}
export const ChatEmbedSettings = ({
onUpdateSettings,
...props
}: ChatEmbedSettingsProps & StackProps) => {
const { typebot } = useTypebot()
const [proactiveMessageChecked, setProactiveMessageChecked] = useState(false)
const [rememberProMessageChecked] = useState(true)
const [customIconInputValue, setCustomIconInputValue] = useState('')
const [inputValues, setInputValues] = useState({
messageDelay: '0',
messageContent: 'I have a question for you!',
})
const [bubbleColor, setBubbleColor] = useState(
typebot?.theme.chat.buttons.backgroundColor ?? '#0042DA'
)
useEffect(() => {
if (proactiveMessageChecked) {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: customIconInputValue,
},
proactiveMessage: {
delay: parseInt(inputValues.messageDelay) * 1000,
textContent: inputValues.messageContent,
rememberClose: rememberProMessageChecked,
},
})
} else {
onUpdateSettings({
button: {
color: bubbleColor,
iconUrl: customIconInputValue,
},
proactiveMessage: undefined,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
inputValues,
bubbleColor,
rememberProMessageChecked,
customIconInputValue,
proactiveMessageChecked,
])
return (
<Stack {...props}>
<Heading fontSize="md" fontWeight="semibold">
Chat bubble settings
</Heading>
<Flex justify="space-between" align="center" mb="4">
<Text>Button color</Text>
<ColorPicker
initialColor={bubbleColor}
onColorChange={setBubbleColor}
/>
</Flex>
<HStack>
<Text flexShrink={0}>
Custom button icon <Tag>Optional</Tag>
</Text>
<Input
placeholder={'Paste image link (.png, .svg)'}
value={customIconInputValue}
onChange={(e) => setCustomIconInputValue(e.target.value)}
/>
</HStack>
<Flex alignItems="center">
<FormControl
display="flex"
alignItems="center"
w="full"
justifyContent="space-between"
pr={1}
>
<FormLabel htmlFor="fullscreen-option" mb="1">
Enable popup message?
</FormLabel>
<Switch
id="fullscreen-option"
onChange={() =>
setProactiveMessageChecked(!proactiveMessageChecked)
}
isChecked={proactiveMessageChecked}
/>
</FormControl>
</Flex>
{proactiveMessageChecked && (
<>
<Flex justify="space-between" align="center" pl="4" mb="2">
<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" mb="2">
<Text>Message content</Text>
<Input
type="text"
onChange={(e) =>
setInputValues({
...inputValues,
messageContent: e.target.value,
})
}
value={inputValues.messageContent}
/>
</Flex>
</>
)}
</Stack>
)
}

View File

@ -0,0 +1,56 @@
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 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
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({
publishId: typebot?.publicId ?? '',
heightLabel,
widthLabel,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<ContainerEmbedCodeProps, 'widthLabel' | 'heightLabel'>
const parseSnippet = ({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
customDomain,
hiddenVariables,
backgroundColor,
publishId,
})
return `${typebotJsHtml}
<div id="typebot-container" style="width: ${embedProps.widthLabel}; height: ${embedProps.heightLabel};"></div>
<script>${jsCode}</script>`
}

View File

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

View File

@ -0,0 +1,19 @@
import { FlexProps } from '@chakra-ui/react'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
type Props = {
widthLabel: string
heightLabel: string
onCopied?: () => void
}
export const IframeEmbedCode = ({
widthLabel,
heightLabel,
}: Props & FlexProps) => {
const { typebot } = useTypebot()
const src = `https://${typebot?.publicId}.typebot.io`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" />`
return <CodeEditor value={code} lang="html" isReadOnly />
}

View File

@ -0,0 +1,34 @@
import { FlexProps } from '@chakra-ui/layout'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
import { parseInitPopupCode, typebotJsHtml } from '../params'
type PopupEmbedCodeProps = {
delay: number
withStarterVariables?: boolean
onCopied?: () => void
}
export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
createSnippet({
publishId: typebot?.publicId ?? '',
delay,
}),
{
parser: 'html',
plugins: [parserHtml],
}
)
return <CodeEditor value={snippet} lang="html" isReadOnly />
}
const createSnippet = (params: PopupParams): string => {
const jsCode = parseInitPopupCode(params)
return `${typebotJsHtml}
<script>${jsCode}</script>`
}

View File

@ -0,0 +1,55 @@
import {
StackProps,
Stack,
Flex,
Heading,
NumberInput,
NumberInputField,
NumberInputStepper,
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 [inputValue, setInputValue] = useState(0)
useEffect(() => {
onUpdateSettings({
delay: inputValue * 1000,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue])
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">
<p>Appearance delay</p>
<NumberInput
onChange={(_, val) => setInputValue(val)}
value={inputValue}
min={0}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
</Stack>
)
}

View File

@ -0,0 +1,154 @@
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 { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
widthLabel,
heightLabel,
}: StandardReactDivProps) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
parseContainerSnippet({
publishId: typebot?.publicId ?? '',
heightLabel,
widthLabel,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
type SnippetProps = IframeParams &
Pick<StandardReactDivProps, 'widthLabel' | 'heightLabel'>
const parseContainerSnippet = ({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
...embedProps
}: SnippetProps): string => {
const jsCode = parseInitContainerCode({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
})
return `import {initContainer} 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({
publishId: typebot?.publicId ?? '',
delay,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parsePopupSnippet = ({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
delay,
}: PopupParams): string => {
const jsCode = parseInitPopupCode({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
delay,
})
return `import {initPopup} 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({
publishId: typebot?.publicId ?? '',
button,
proactiveMessage,
}),
{
parser: 'babel',
plugins: [parserBabel],
}
)
return <CodeEditor value={snippet} lang="js" isReadOnly />
}
const parseBubbleSnippet = ({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
}: BubbleParams): string => {
const jsCode = parseInitBubbleCode({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
proactiveMessage,
button,
})
return `import {initBubble} from "typebot-js";
const Component = () => {
useEffect(()=> {
${jsCode}
}, [])
return <></>
}`
}

View File

@ -0,0 +1,142 @@
import {
BubbleParams,
ButtonParams,
IframeParams,
PopupParams,
ProactiveMessageParams,
} from 'typebot-js'
import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
const parseStringParam = (fieldName: string, fieldValue?: string) =>
fieldValue ? `${fieldName}: "${fieldValue}",` : ``
const parseNonStringParam = (
fieldName: string,
fieldValue?: number | boolean
) => (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)
return `button: {${iconUrlString}${buttonColorstring}},`
}
const parseProactiveMessage = (
proactiveMessage?: ProactiveMessageParams
): string => {
if (!proactiveMessage) return ``
const { avatarUrl, textContent, delay, rememberClose } = proactiveMessage
const avatarUrlString = parseStringParam('avatarUrl', avatarUrl)
const textContentString = parseStringParam('textContent', textContent)
const rememberCloseString = parseNonStringParam(
'rememberClose',
rememberClose
)
const delayString = parseNonStringParam('delay', delay)
return `proactiveMessage: {${avatarUrlString}${textContentString}${rememberCloseString}${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 = ({
publishId,
customDomain,
backgroundColor,
hiddenVariables,
}: IframeParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
return prettier.format(
`Typebot.initContainer("typebot-container", {
publishId: "${publishId}",${bgColorString}${customDomainString}${hiddenVariablesString}
});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitPopupCode = ({
publishId,
customDomain,
hiddenVariables,
backgroundColor,
delay,
}: PopupParams) => {
const { customDomainString, hiddenVariablesString, bgColorString } =
parseIframeParams({
customDomain,
hiddenVariables,
backgroundColor,
})
const { delayString } = parsePopupParams({ delay })
return prettier.format(
`var typebotCommands = Typebot.initPopup({publishId: "${publishId}",${delayString}${bgColorString}${customDomainString}${hiddenVariablesString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const parseInitBubbleCode = ({
publishId,
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({publishId: "${publishId}",${bgColorString}${customDomainString}${hiddenVariablesString}${proactiveMessageString}${buttonString}});`,
{ parser: 'babel', plugins: [parserBabel] }
)
}
export const typebotJsHtml = `<script src="https://unpkg.com/typebot-js@2.0.20"></script>`