♻️ Introduce typebot v6 with events (#1013)

Closes #885
This commit is contained in:
Baptiste Arnaud
2023-11-08 15:34:16 +01:00
committed by GitHub
parent 68e4fc71fb
commit 35300eaf34
634 changed files with 58971 additions and 31449 deletions

View File

@@ -18,6 +18,7 @@ import { headerHeight } from '@/features/editor/constants'
import { ChatThemeSettings } from './chat/ChatThemeSettings'
import { GeneralSettings } from './general/GeneralSettings'
import { ThemeTemplates } from './ThemeTemplates'
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
export const ThemeSideMenu = () => {
const { typebot, updateTypebot } = useTypebot()
@@ -25,7 +26,7 @@ export const ThemeSideMenu = () => {
const updateChatTheme = (chat: ChatTheme) =>
typebot && updateTypebot({ updates: { theme: { ...typebot.theme, chat } } })
const updateGeneralTheme = (general: GeneralTheme) =>
const updateGeneralTheme = (general?: GeneralTheme) =>
typebot &&
updateTypebot({ updates: { theme: { ...typebot.theme, general } } })
@@ -106,7 +107,10 @@ export const ThemeSideMenu = () => {
<AccordionPanel pb={4}>
{typebot && (
<GeneralSettings
isBrandingEnabled={typebot.settings.general.isBrandingEnabled}
isBrandingEnabled={
typebot.settings.general?.isBrandingEnabled ??
defaultSettings.general.isBrandingEnabled
}
generalTheme={typebot.theme.general}
onGeneralThemeChange={updateGeneralTheme}
onBrandingChange={updateBranding}

View File

@@ -15,9 +15,13 @@ import {
Image,
useColorModeValue,
} from '@chakra-ui/react'
import { BackgroundType, ThemeTemplate } from '@typebot.io/schemas'
import { Theme, ThemeTemplate } from '@typebot.io/schemas'
import { useState } from 'react'
import { DefaultAvatar } from './DefaultAvatar'
import {
BackgroundType,
defaultTheme,
} from '@typebot.io/schemas/features/typebot/theme/constants'
export const ThemeTemplateCard = ({
workspaceId,
@@ -56,12 +60,36 @@ export const ThemeTemplateCard = ({
}
const rounded =
themeTemplate.theme.chat.roundness === 'large'
themeTemplate.theme.chat?.roundness === 'large'
? 'md'
: themeTemplate.theme.chat.roundness === 'none'
: themeTemplate.theme.chat?.roundness === 'none'
? 'none'
: 'sm'
const hostAvatar = {
isEnabled:
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
defaultTheme.chat.hostAvatar.isEnabled,
url: themeTemplate.theme.chat?.hostAvatar?.url,
}
const hostBubbleBgColor =
themeTemplate.theme.chat?.hostBubbles ?? defaultTheme.chat.hostBubbles
const guestAvatar = {
isEnabled:
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
defaultTheme.chat.guestAvatar.isEnabled,
url: themeTemplate.theme.chat?.guestAvatar?.url,
}
const guestBubbleBgColor =
themeTemplate.theme.chat?.guestBubbles ?? defaultTheme.chat.guestBubbles
const buttonBgColor =
themeTemplate.theme.chat?.buttons?.backgroundColor ??
defaultTheme.chat.buttons.backgroundColor
return (
<Stack
borderWidth={borderWidth}
@@ -84,17 +112,12 @@ export const ThemeTemplateCard = ({
<Box
borderTopRadius="md"
backgroundSize="cover"
{...parseBackground(themeTemplate.theme.general.background)}
{...parseBackground(themeTemplate.theme.general?.background)}
borderColor={isSelected ? 'blue.400' : undefined}
>
<HStack mt="4" ml="4" spacing={0.5} alignItems="flex-end">
<AvatarPreview avatar={themeTemplate.theme.chat.hostAvatar} />
<Box
rounded="sm"
w="80px"
h="16px"
background={themeTemplate.theme.chat.hostBubbles.backgroundColor}
/>
<AvatarPreview avatar={hostAvatar} />
<Box rounded="sm" w="80px" h="16px" background={hostBubbleBgColor} />
</HStack>
<HStack
@@ -104,23 +127,13 @@ export const ThemeTemplateCard = ({
justifyContent="flex-end"
alignItems="flex-end"
>
<Box
rounded="sm"
w="80px"
h="16px"
background={themeTemplate.theme.chat.guestBubbles.backgroundColor}
/>
<AvatarPreview avatar={themeTemplate.theme.chat.guestAvatar} />
<Box rounded="sm" w="80px" h="16px" background={guestBubbleBgColor} />
<AvatarPreview avatar={guestAvatar} />
</HStack>
<HStack mt="1" ml="4" spacing={0.5} alignItems="flex-end">
<AvatarPreview avatar={themeTemplate.theme.chat.hostAvatar} />
<Box
rounded="sm"
w="80px"
h="16px"
background={themeTemplate.theme.chat.hostBubbles.backgroundColor}
/>
<AvatarPreview avatar={hostAvatar} />
<Box rounded="sm" w="80px" h="16px" background={hostBubbleBgColor} />
</HStack>
<Flex
mt="1"
@@ -131,24 +144,9 @@ export const ThemeTemplateCard = ({
justifyContent="flex-end"
gap="1"
>
<Box
rounded={rounded}
w="20px"
h="10px"
background={themeTemplate.theme.chat.buttons.backgroundColor}
/>
<Box
rounded={rounded}
w="20px"
h="10px"
background={themeTemplate.theme.chat.buttons.backgroundColor}
/>
<Box
rounded={rounded}
w="20px"
h="10px"
background={themeTemplate.theme.chat.buttons.backgroundColor}
/>
<Box rounded={rounded} w="20px" h="10px" background={buttonBgColor} />
<Box rounded={rounded} w="20px" h="10px" background={buttonBgColor} />
<Box rounded={rounded} w="20px" h="10px" background={buttonBgColor} />
</Flex>
</Box>
<HStack p="2" justifyContent="space-between">
@@ -186,14 +184,15 @@ export const ThemeTemplateCard = ({
)
}
const parseBackground = (background: {
type: BackgroundType
content?: string
}) => {
switch (background.type) {
const parseBackground = (
background: NonNullable<Theme['general']>['background']
) => {
switch (background?.type) {
case undefined:
case BackgroundType.COLOR:
return {
backgroundColor: background.content,
backgroundColor:
background?.content ?? defaultTheme.general.background.content,
}
case BackgroundType.IMAGE:
return { backgroundImage: `url(${background.content})` }
@@ -205,15 +204,10 @@ const parseBackground = (background: {
const AvatarPreview = ({
avatar,
}: {
avatar:
| {
isEnabled: boolean
url?: string | undefined
}
| undefined
avatar: NonNullable<Theme['chat']>['hostAvatar']
}) => {
if (!avatar?.isEnabled) return null
return avatar.url ? (
if (avatar?.isEnabled) return null
return avatar?.url ? (
<Image
src={avatar.url}
alt="Avatar preview in theme template card"

View File

@@ -1,11 +1,11 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from '@typebot.io/schemas'
import { Theme } from '@typebot.io/schemas'
import React from 'react'
import { ColorPicker } from '../../../../components/ColorPicker'
type Props = {
buttons: ContainerColors
onButtonsChange: (buttons: ContainerColors) => void
buttons: NonNullable<Theme['chat']>['buttons']
onButtonsChange: (buttons: NonNullable<Theme['chat']>['buttons']) => void
}
export const ButtonsTheme = ({ buttons, onButtonsChange }: Props) => {
@@ -19,13 +19,13 @@ export const ButtonsTheme = ({ buttons, onButtonsChange }: Props) => {
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
value={buttons.backgroundColor}
value={buttons?.backgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker value={buttons.color} onColorChange={handleTextChange} />
<ColorPicker value={buttons?.color} onColorChange={handleTextChange} />
</Flex>
</Stack>
)

View File

@@ -5,23 +5,19 @@ import {
} from '@/components/icons'
import { RadioButtons } from '@/components/inputs/RadioButtons'
import { Heading, Stack } from '@chakra-ui/react'
import {
AvatarProps,
ChatTheme,
ContainerColors,
InputColors,
} from '@typebot.io/schemas'
import { AvatarProps, ChatTheme, Theme } from '@typebot.io/schemas'
import React from 'react'
import { AvatarForm } from './AvatarForm'
import { ButtonsTheme } from './ButtonsTheme'
import { GuestBubbles } from './GuestBubbles'
import { HostBubbles } from './HostBubbles'
import { InputsTheme } from './InputsTheme'
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
type Props = {
workspaceId: string
typebotId: string
chatTheme: ChatTheme
chatTheme: Theme['chat']
onChatThemeChange: (chatTheme: ChatTheme) => void
}
@@ -31,18 +27,24 @@ export const ChatThemeSettings = ({
chatTheme,
onChatThemeChange,
}: Props) => {
const handleHostBubblesChange = (hostBubbles: ContainerColors) =>
onChatThemeChange({ ...chatTheme, hostBubbles })
const handleGuestBubblesChange = (guestBubbles: ContainerColors) =>
onChatThemeChange({ ...chatTheme, guestBubbles })
const handleButtonsChange = (buttons: ContainerColors) =>
const updateHostBubbles = (
hostBubbles: NonNullable<Theme['chat']>['hostBubbles']
) => onChatThemeChange({ ...chatTheme, hostBubbles })
const updateGuestBubbles = (
guestBubbles: NonNullable<Theme['chat']>['guestBubbles']
) => onChatThemeChange({ ...chatTheme, guestBubbles })
const updateButtons = (buttons: NonNullable<Theme['chat']>['buttons']) =>
onChatThemeChange({ ...chatTheme, buttons })
const handleInputsChange = (inputs: InputColors) =>
const updateInputs = (inputs: NonNullable<Theme['chat']>['inputs']) =>
onChatThemeChange({ ...chatTheme, inputs })
const handleHostAvatarChange = (hostAvatar: AvatarProps) =>
const updateHostAvatar = (hostAvatar: AvatarProps) =>
onChatThemeChange({ ...chatTheme, hostAvatar })
const handleGuestAvatarChange = (guestAvatar: AvatarProps) =>
const updateGuestAvatar = (guestAvatar: AvatarProps) =>
onChatThemeChange({ ...chatTheme, guestAvatar })
return (
@@ -54,9 +56,9 @@ export const ChatThemeSettings = ({
fileName: 'hostAvatar',
}}
title="Bot avatar"
avatarProps={chatTheme.hostAvatar}
avatarProps={chatTheme?.hostAvatar}
isDefaultCheck
onAvatarChange={handleHostAvatarChange}
onAvatarChange={updateHostAvatar}
/>
<AvatarForm
uploadFileProps={{
@@ -65,37 +67,34 @@ export const ChatThemeSettings = ({
fileName: 'guestAvatar',
}}
title="User avatar"
avatarProps={chatTheme.guestAvatar}
onAvatarChange={handleGuestAvatarChange}
avatarProps={chatTheme?.guestAvatar}
onAvatarChange={updateGuestAvatar}
/>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Bot bubbles</Heading>
<HostBubbles
hostBubbles={chatTheme.hostBubbles}
onHostBubblesChange={handleHostBubblesChange}
hostBubbles={chatTheme?.hostBubbles}
onHostBubblesChange={updateHostBubbles}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">User bubbles</Heading>
<GuestBubbles
guestBubbles={chatTheme.guestBubbles}
onGuestBubblesChange={handleGuestBubblesChange}
guestBubbles={chatTheme?.guestBubbles}
onGuestBubblesChange={updateGuestBubbles}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Buttons</Heading>
<ButtonsTheme
buttons={chatTheme.buttons}
onButtonsChange={handleButtonsChange}
buttons={chatTheme?.buttons}
onButtonsChange={updateButtons}
/>
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Inputs</Heading>
<InputsTheme
inputs={chatTheme.inputs}
onInputsChange={handleInputsChange}
/>
<InputsTheme inputs={chatTheme?.inputs} onInputsChange={updateInputs} />
</Stack>
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
<Heading fontSize="lg">Corners roundness</Heading>
@@ -114,7 +113,7 @@ export const ChatThemeSettings = ({
value: 'large',
},
]}
value={chatTheme.roundness ?? 'medium'}
value={chatTheme?.roundness ?? defaultTheme.chat.roundness}
onSelect={(roundness) =>
onChatThemeChange({ ...chatTheme, roundness })
}

View File

@@ -2,16 +2,18 @@ import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from '@typebot.io/schemas'
import React from 'react'
import { ColorPicker } from '../../../../components/ColorPicker'
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
type Props = {
guestBubbles: ContainerColors
onGuestBubblesChange: (hostBubbles: ContainerColors) => void
guestBubbles: ContainerColors | undefined
onGuestBubblesChange: (hostBubbles: ContainerColors | undefined) => void
}
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
const handleBackgroundChange = (backgroundColor: string) =>
const updateBackground = (backgroundColor: string) =>
onGuestBubblesChange({ ...guestBubbles, backgroundColor })
const handleTextChange = (color: string) =>
const updateText = (color: string) =>
onGuestBubblesChange({ ...guestBubbles, color })
return (
@@ -19,15 +21,18 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
value={guestBubbles.backgroundColor}
onColorChange={handleBackgroundChange}
value={
guestBubbles?.backgroundColor ??
defaultTheme.chat.guestBubbles.backgroundColor
}
onColorChange={updateBackground}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
value={guestBubbles.color}
onColorChange={handleTextChange}
value={guestBubbles?.color ?? defaultTheme.chat.guestBubbles.color}
onColorChange={updateText}
/>
</Flex>
</Stack>

View File

@@ -2,10 +2,11 @@ import { Stack, Flex, Text } from '@chakra-ui/react'
import { ContainerColors } from '@typebot.io/schemas'
import React from 'react'
import { ColorPicker } from '../../../../components/ColorPicker'
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
type Props = {
hostBubbles: ContainerColors
onHostBubblesChange: (hostBubbles: ContainerColors) => void
hostBubbles: ContainerColors | undefined
onHostBubblesChange: (hostBubbles: ContainerColors | undefined) => void
}
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
@@ -19,14 +20,17 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
value={hostBubbles.backgroundColor}
value={
hostBubbles?.backgroundColor ??
defaultTheme.chat.hostBubbles.backgroundColor
}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker
value={hostBubbles.color}
value={hostBubbles?.color ?? defaultTheme.chat.hostBubbles.color}
onColorChange={handleTextChange}
/>
</Flex>

View File

@@ -1,10 +1,10 @@
import { Stack, Flex, Text } from '@chakra-ui/react'
import { InputColors } from '@typebot.io/schemas'
import { InputColors, Theme } from '@typebot.io/schemas'
import React from 'react'
import { ColorPicker } from '../../../../components/ColorPicker'
type Props = {
inputs: InputColors
inputs: NonNullable<Theme['chat']>['inputs']
onInputsChange: (buttons: InputColors) => void
}
@@ -21,18 +21,18 @@ export const InputsTheme = ({ inputs, onInputsChange }: Props) => {
<Flex justify="space-between" align="center">
<Text>Background:</Text>
<ColorPicker
value={inputs.backgroundColor}
value={inputs?.backgroundColor}
onColorChange={handleBackgroundChange}
/>
</Flex>
<Flex justify="space-between" align="center">
<Text>Text:</Text>
<ColorPicker value={inputs.color} onColorChange={handleTextChange} />
<ColorPicker value={inputs?.color} onColorChange={handleTextChange} />
</Flex>
<Flex justify="space-between" align="center">
<Text>Placeholder text:</Text>
<ColorPicker
value={inputs.placeholderColor}
value={inputs?.placeholderColor}
onColorChange={handlePlaceholderChange}
/>
</Flex>

View File

@@ -11,17 +11,19 @@ import {
Portal,
} from '@chakra-ui/react'
import { isNotEmpty } from '@typebot.io/lib'
import { Background, BackgroundType } from '@typebot.io/schemas'
import { Background } from '@typebot.io/schemas'
import React from 'react'
import { ColorPicker } from '../../../../components/ColorPicker'
import {
BackgroundType,
defaultTheme,
} from '@typebot.io/schemas/features/typebot/theme/constants'
type BackgroundContentProps = {
background?: Background
onBackgroundContentChange: (content: string) => void
}
const defaultBackgroundColor = '#ffffff'
export const BackgroundContent = ({
background,
onBackgroundContentChange,
@@ -36,7 +38,9 @@ export const BackgroundContent = ({
<Flex justify="space-between" align="center">
<Text>Background color:</Text>
<ColorPicker
value={background.content ?? defaultBackgroundColor}
value={
background.content ?? defaultTheme.general.background.content
}
onColorChange={handleContentChange}
/>
</Flex>

View File

@@ -1,8 +1,9 @@
import { RadioButtons } from '@/components/inputs/RadioButtons'
import { Stack, Text } from '@chakra-ui/react'
import { Background, BackgroundType } from '@typebot.io/schemas'
import { Background } from '@typebot.io/schemas'
import React from 'react'
import { BackgroundContent } from './BackgroundContent'
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
type Props = {
background?: Background

View File

@@ -1,5 +1,5 @@
import { Flex, FormLabel, Stack, Switch, useDisclosure } from '@chakra-ui/react'
import { Background, GeneralTheme } from '@typebot.io/schemas'
import { Background, Theme } from '@typebot.io/schemas'
import React from 'react'
import { BackgroundSelector } from './BackgroundSelector'
import { FontSelector } from './FontSelector'
@@ -12,8 +12,8 @@ import { useTranslate } from '@tolgee/react'
type Props = {
isBrandingEnabled: boolean
generalTheme: GeneralTheme
onGeneralThemeChange: (general: GeneralTheme) => void
generalTheme: Theme['general']
onGeneralThemeChange: (general: Theme['general']) => void
onBrandingChange: (isBrandingEnabled: boolean) => void
}
@@ -62,11 +62,11 @@ export const GeneralSettings = ({
/>
</Flex>
<FontSelector
activeFont={generalTheme.font}
activeFont={generalTheme?.font}
onSelectFont={handleSelectFont}
/>
<BackgroundSelector
background={generalTheme.background}
background={generalTheme?.background}
onBackgroundChange={handleBackgroundChange}
/>
</Stack>

View File

@@ -1,4 +1,5 @@
import { BackgroundType, ThemeTemplate } from '@typebot.io/schemas'
import { ThemeTemplate } from '@typebot.io/schemas'
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
export const galleryTemplates: Pick<ThemeTemplate, 'id' | 'name' | 'theme'>[] =
[

View File

@@ -82,7 +82,9 @@ test.describe.parallel('Theme page', () => {
await page.click('button:has-text("Chat")')
// Host avatar
await expect(page.locator('[data-testid="default-avatar"]')).toBeVisible()
await expect(
page.locator('[data-testid="default-avatar"]').nth(1)
).toBeVisible()
await page.click('[data-testid="default-avatar"]')
await page.click('button:has-text("Link")')
await page.fill(
@@ -271,9 +273,7 @@ test.describe('Free workspace', () => {
await page.goto(`/typebots/${typebotId}/theme`)
await expect(page.locator('text="What\'s your name?"')).toBeVisible()
await page.getByRole('button', { name: 'Global' }).click()
await expect(
page.locator('[data-testid="starter-lock-tag"]')
).toBeVisible()
await expect(page.locator('[data-testid="starter-lock-tag"]')).toBeVisible()
await page.click('text=Show Typebot brand')
await expect(
page.locator(