Merge branch 'main' into internal/add-vercel-functions-whatsapp-webhook
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"format:check": "prettier --check ./src --ignore-path ../../.prettierignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typebot.io/theme": "workspace:*",
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
"@chakra-ui/anatomy": "2.1.1",
|
||||
"@chakra-ui/react": "2.7.1",
|
||||
|
||||
@@ -35,10 +35,16 @@ const colorsSelection: `#${string}`[] = [
|
||||
type Props = {
|
||||
value?: string
|
||||
defaultValue?: string
|
||||
isDisabled?: boolean
|
||||
onColorChange: (color: string) => void
|
||||
}
|
||||
|
||||
export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
||||
export const ColorPicker = ({
|
||||
value,
|
||||
defaultValue,
|
||||
isDisabled,
|
||||
onColorChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslate()
|
||||
const [color, setColor] = useState(defaultValue ?? '')
|
||||
const displayedValue = value ?? color
|
||||
@@ -63,6 +69,7 @@ export const ColorPicker = ({ value, defaultValue, onColorChange }: Props) => {
|
||||
padding={0}
|
||||
borderRadius={3}
|
||||
borderWidth={1}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
<Box rounded="full" boxSize="14px" bgColor={displayedValue} />
|
||||
</Button>
|
||||
|
||||
@@ -141,6 +141,8 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
||||
fetchNewImages(query, 0)
|
||||
}}
|
||||
withVariableButton={false}
|
||||
debounceTimeout={500}
|
||||
forceDebounce
|
||||
/>
|
||||
<Link
|
||||
isExternal
|
||||
|
||||
@@ -24,6 +24,7 @@ import { env } from '@typebot.io/env'
|
||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||
|
||||
export type TextInputProps = {
|
||||
forceDebounce?: boolean
|
||||
defaultValue?: string
|
||||
onChange?: (value: string) => void
|
||||
debounceTimeout?: number
|
||||
@@ -62,6 +63,7 @@ export const TextInput = forwardRef(function TextInput(
|
||||
autoComplete,
|
||||
isDisabled,
|
||||
autoFocus,
|
||||
forceDebounce,
|
||||
onChange: _onChange,
|
||||
onFocus,
|
||||
onKeyUp,
|
||||
@@ -83,7 +85,7 @@ export const TextInput = forwardRef(function TextInput(
|
||||
const onChange = useDebouncedCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
_onChange ?? (() => {}),
|
||||
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||
env.NEXT_PUBLIC_E2E_TEST && !forceDebounce ? 0 : debounceTimeout
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { PaymentInputBlock } from '@typebot.io/schemas'
|
||||
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
||||
|
||||
type Props = {
|
||||
block: PaymentInputBlock
|
||||
@@ -9,11 +10,7 @@ type Props = {
|
||||
export const PaymentInputContent = ({ block }: Props) => {
|
||||
const { t } = useTranslate()
|
||||
|
||||
if (
|
||||
!block.options?.amount ||
|
||||
!block.options.credentialsId ||
|
||||
!block.options.currency
|
||||
)
|
||||
if (!block.options?.amount || !block.options.credentialsId)
|
||||
return (
|
||||
<Text color="gray.500">
|
||||
{t('blocks.inputs.payment.placeholder.label')}
|
||||
@@ -22,7 +19,7 @@ export const PaymentInputContent = ({ block }: Props) => {
|
||||
return (
|
||||
<Text noOfLines={1} pr="6">
|
||||
{t('blocks.inputs.payment.collect.label')} {block.options.amount}{' '}
|
||||
{block.options.currency}
|
||||
{block.options.currency ?? defaultPaymentInputOptions.currency}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ export const StripeConfigModal = ({
|
||||
placeholder="sk_test_..."
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
type="password"
|
||||
/>
|
||||
</HStack>
|
||||
</Stack>
|
||||
@@ -187,6 +188,7 @@ export const StripeConfigModal = ({
|
||||
placeholder="sk_live_..."
|
||||
withVariableButton={false}
|
||||
debounceTimeout={0}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
</HStack>
|
||||
|
||||
@@ -70,7 +70,7 @@ export const CountryCodeSelect = ({ countryCode, onSelect }: Props) => {
|
||||
<option value="DK">Denmark (+45)</option>
|
||||
<option value="DJ">Djibouti (+253)</option>
|
||||
<option value="DM">Dominica (+1809)</option>
|
||||
<option value="DO">Dominican Republic (+1809)</option>
|
||||
<option value="DO">Dominican Republic (+18XX)</option>
|
||||
<option value="EC">Ecuador (+593)</option>
|
||||
<option value="EG">Egypt (+20)</option>
|
||||
<option value="SV">El Salvador (+503)</option>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { MakeComBlock } from '@typebot.io/schemas'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
block: MakeComBlock
|
||||
}
|
||||
|
||||
export const MakeComContent = ({ block }: Props) => {
|
||||
const webhook = block.options?.webhook
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
if (!block.options?.webhook?.url)
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Text noOfLines={1} pr="6">
|
||||
{webhook?.url ? 'Trigger scenario' : 'Disabled'}
|
||||
Trigger scenario
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { PabblyConnectBlock } from '@typebot.io/schemas'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
block: PabblyConnectBlock
|
||||
}
|
||||
|
||||
export const PabblyConnectContent = ({ block }: Props) => {
|
||||
const webhook = block.options?.webhook
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
if (!block.options?.webhook?.url)
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Text noOfLines={1} pr="6">
|
||||
{webhook?.url ? 'Trigger scenario' : 'Disabled'}
|
||||
Trigger scenario
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { ZapierBlock } from '@typebot.io/schemas'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
|
||||
type Props = {
|
||||
block: ZapierBlock
|
||||
}
|
||||
|
||||
export const ZapierContent = ({ block }: Props) => {
|
||||
const webhook = block.options?.webhook
|
||||
|
||||
if (isNotDefined(webhook?.body))
|
||||
if (!block.options?.webhook?.url)
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Text noOfLines={1} pr="6">
|
||||
{webhook?.url ? 'Trigger zap' : 'Disabled'}
|
||||
Trigger zap
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useRouter } from 'next/router'
|
||||
|
||||
export const BoardMenuButton = (props: FlexProps) => {
|
||||
const { query } = useRouter()
|
||||
const { typebot } = useTypebot()
|
||||
const { typebot, currentUserMode } = useTypebot()
|
||||
const { user } = useUser()
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
@@ -78,9 +78,11 @@ export const BoardMenuButton = (props: FlexProps) => {
|
||||
<MenuItem icon={<SettingsIcon />} onClick={onOpen}>
|
||||
{t('editor.graph.menu.editorSettingsItem.label')}
|
||||
</MenuItem>
|
||||
<MenuItem icon={<DownloadIcon />} onClick={downloadFlow}>
|
||||
{t('editor.graph.menu.exportFlowItem.label')}
|
||||
</MenuItem>
|
||||
{currentUserMode !== 'guest' ? (
|
||||
<MenuItem icon={<DownloadIcon />} onClick={downloadFlow}>
|
||||
{t('editor.graph.menu.exportFlowItem.label')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
</MenuList>
|
||||
<EditorSettingsModal isOpen={isOpen} onClose={onClose} />
|
||||
</Menu>
|
||||
|
||||
@@ -73,7 +73,6 @@ export const PreviewDrawer = () => {
|
||||
right="0"
|
||||
top={`0`}
|
||||
h={`100%`}
|
||||
w={`${width}px`}
|
||||
bgColor={useColorModeValue('white', 'gray.900')}
|
||||
borderLeftWidth={'1px'}
|
||||
shadow="lg"
|
||||
@@ -82,6 +81,7 @@ export const PreviewDrawer = () => {
|
||||
onMouseLeave={() => setIsResizeHandleVisible(false)}
|
||||
p="6"
|
||||
zIndex={10}
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
<Fade in={isResizeHandleVisible}>
|
||||
<ResizeHandle
|
||||
|
||||
@@ -5,13 +5,13 @@ import { Typebot } from '@typebot.io/schemas'
|
||||
import { useState } from 'react'
|
||||
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||
import { JavascriptBubbleSnippet } from '../JavascriptBubbleSnippet'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { defaultButtonsBackgroundColor } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
export const parseDefaultBubbleTheme = (typebot?: Typebot) => ({
|
||||
button: {
|
||||
backgroundColor:
|
||||
typebot?.theme.chat?.buttons?.backgroundColor ??
|
||||
defaultTheme.chat.buttons.backgroundColor,
|
||||
defaultButtonsBackgroundColor,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -158,6 +158,10 @@ export const WhatsAppCredentialsModal = ({
|
||||
setIsVerifying(false)
|
||||
showToast({
|
||||
description: 'Could not get system info',
|
||||
details:
|
||||
err instanceof Error
|
||||
? { content: err.message, lang: 'json' }
|
||||
: undefined,
|
||||
})
|
||||
return false
|
||||
}
|
||||
@@ -204,7 +208,10 @@ export const WhatsAppCredentialsModal = ({
|
||||
setIsVerifying(false)
|
||||
showToast({
|
||||
description: 'Could not get phone number info',
|
||||
details: { content: JSON.stringify(err), lang: 'json' },
|
||||
details:
|
||||
err instanceof Error
|
||||
? { content: err.message, lang: 'json' }
|
||||
: undefined,
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,12 +8,17 @@ import {
|
||||
Link,
|
||||
Stack,
|
||||
Text,
|
||||
Code,
|
||||
} from '@chakra-ui/react'
|
||||
import { BubbleProps } from '@typebot.io/nextjs'
|
||||
import { useState } from 'react'
|
||||
import { BubbleSettings } from '../../../settings/BubbleSettings/BubbleSettings'
|
||||
import { parseApiHostValue, parseInitBubbleCode } from '../../../snippetParsers'
|
||||
import { parseDefaultBubbleTheme } from '../../Javascript/instructions/JavascriptBubbleInstructions'
|
||||
import packageJson from '../../../../../../../../../../packages/embeds/js/package.json'
|
||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||
|
||||
const typebotCloudLibraryVersion = '0.2'
|
||||
|
||||
type Props = {
|
||||
publicId: string
|
||||
@@ -52,6 +57,14 @@ export const WordpressBubbleInstructions = ({ publicId }: Props) => {
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Set <Code>Library version</Code> to{' '}
|
||||
<Code>
|
||||
{isCloudProdInstance()
|
||||
? typebotCloudLibraryVersion
|
||||
: packageJson.version}
|
||||
</Code>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Stack spacing={4}>
|
||||
<BubbleSettings
|
||||
|
||||
@@ -7,11 +7,16 @@ import {
|
||||
Link,
|
||||
Stack,
|
||||
Text,
|
||||
Code,
|
||||
} from '@chakra-ui/react'
|
||||
import { useState } from 'react'
|
||||
import { PopupSettings } from '../../../settings/PopupSettings'
|
||||
import { parseInitPopupCode } from '../../../snippetParsers/popup'
|
||||
import { parseApiHostValue } from '../../../snippetParsers'
|
||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||
import packageJson from '../../../../../../../../../../packages/embeds/js/package.json'
|
||||
|
||||
const typebotCloudLibraryVersion = '0.2'
|
||||
|
||||
type Props = {
|
||||
publicId: string
|
||||
@@ -42,6 +47,14 @@ export const WordpressPopupInstructions = ({
|
||||
<ExternalLinkIcon mx="2px" />
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Set <Code>Library version</Code> to{' '}
|
||||
<Code>
|
||||
{isCloudProdInstance()
|
||||
? typebotCloudLibraryVersion
|
||||
: packageJson.version}
|
||||
</Code>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Stack spacing={4}>
|
||||
<PopupSettings
|
||||
|
||||
@@ -8,6 +8,7 @@ export const DefaultAvatar = (props: IconProps) => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
boxSize="40px"
|
||||
borderRadius="full"
|
||||
data-testid="default-avatar"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -35,7 +35,7 @@ export const ThemeSideMenu = () => {
|
||||
typebot &&
|
||||
updateTypebot({ updates: { theme: { ...typebot.theme, customCss } } })
|
||||
|
||||
const selectedTemplate = (
|
||||
const selectTemplate = (
|
||||
selectedTemplate: Partial<Pick<ThemeTemplate, 'id' | 'theme'>>
|
||||
) => {
|
||||
if (!typebot) return
|
||||
@@ -56,6 +56,8 @@ export const ThemeSideMenu = () => {
|
||||
},
|
||||
})
|
||||
|
||||
const templateId = typebot?.selectedThemeTemplateId ?? undefined
|
||||
|
||||
return (
|
||||
<Stack
|
||||
flex="1"
|
||||
@@ -84,12 +86,10 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={12}>
|
||||
{typebot && (
|
||||
<ThemeTemplates
|
||||
selectedTemplateId={
|
||||
typebot.selectedThemeTemplateId ?? undefined
|
||||
}
|
||||
selectedTemplateId={templateId}
|
||||
currentTheme={typebot.theme}
|
||||
workspaceId={typebot.workspaceId}
|
||||
onTemplateSelect={selectedTemplate}
|
||||
onTemplateSelect={selectTemplate}
|
||||
/>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
@@ -106,6 +106,7 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<GeneralSettings
|
||||
key={templateId}
|
||||
isBrandingEnabled={
|
||||
typebot.settings.general?.isBrandingEnabled ??
|
||||
defaultSettings.general.isBrandingEnabled
|
||||
@@ -128,9 +129,11 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<ChatThemeSettings
|
||||
key={templateId}
|
||||
workspaceId={typebot.workspaceId}
|
||||
typebotId={typebot.id}
|
||||
chatTheme={typebot.theme.chat}
|
||||
generalBackground={typebot.theme.general?.background}
|
||||
onChatThemeChange={updateChatTheme}
|
||||
/>
|
||||
)}
|
||||
@@ -147,6 +150,7 @@ export const ThemeSideMenu = () => {
|
||||
<AccordionPanel pb={4}>
|
||||
{typebot && (
|
||||
<CustomCssSettings
|
||||
key={templateId}
|
||||
customCss={typebot.theme.customCss}
|
||||
onCustomCssChange={updateCustomCss}
|
||||
/>
|
||||
|
||||
@@ -19,8 +19,13 @@ import { Theme, ThemeTemplate } from '@typebot.io/schemas'
|
||||
import { useState } from 'react'
|
||||
import { DefaultAvatar } from './DefaultAvatar'
|
||||
import {
|
||||
defaultButtonsBackgroundColor,
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultGuestAvatarIsEnabled,
|
||||
defaultGuestBubblesBackgroundColor,
|
||||
defaultHostAvatarIsEnabled,
|
||||
defaultBackgroundColor,
|
||||
defaultHostBubblesBackgroundColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@@ -71,28 +76,28 @@ export const ThemeTemplateCard = ({
|
||||
const hostAvatar = {
|
||||
isEnabled:
|
||||
themeTemplate.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled,
|
||||
defaultHostAvatarIsEnabled,
|
||||
url: themeTemplate.theme.chat?.hostAvatar?.url,
|
||||
}
|
||||
|
||||
const hostBubbleBgColor =
|
||||
themeTemplate.theme.chat?.hostBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.hostBubbles.backgroundColor
|
||||
defaultHostBubblesBackgroundColor
|
||||
|
||||
const guestAvatar = {
|
||||
isEnabled:
|
||||
themeTemplate.theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled,
|
||||
defaultGuestAvatarIsEnabled,
|
||||
url: themeTemplate.theme.chat?.guestAvatar?.url,
|
||||
}
|
||||
|
||||
const guestBubbleBgColor =
|
||||
themeTemplate.theme.chat?.guestBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.guestBubbles.backgroundColor
|
||||
defaultGuestBubblesBackgroundColor
|
||||
|
||||
const buttonBgColor =
|
||||
themeTemplate.theme.chat?.buttons?.backgroundColor ??
|
||||
defaultTheme.chat.buttons.backgroundColor
|
||||
defaultButtonsBackgroundColor
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -197,8 +202,7 @@ const parseBackground = (
|
||||
case undefined:
|
||||
case BackgroundType.COLOR:
|
||||
return {
|
||||
backgroundColor:
|
||||
background?.content ?? defaultTheme.general.background.content,
|
||||
backgroundColor: background?.content ?? defaultBackgroundColor,
|
||||
}
|
||||
case BackgroundType.IMAGE:
|
||||
return { backgroundImage: `url(${background.content})` }
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { FormLabel, HStack, Stack } from '@chakra-ui/react'
|
||||
import { ChatTheme, GeneralTheme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import {
|
||||
defaultBlur,
|
||||
defaultContainerBackgroundColor,
|
||||
defaultContainerMaxHeight,
|
||||
defaultContainerMaxWidth,
|
||||
defaultDarkTextColor,
|
||||
defaultLightTextColor,
|
||||
defaultOpacity,
|
||||
defaultRoundness,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { ContainerThemeForm } from './ContainerThemeForm'
|
||||
import { NumberInput } from '@/components/inputs'
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import { isChatContainerLight } from '@typebot.io/theme/isChatContainerLight'
|
||||
|
||||
type Props = {
|
||||
generalBackground: GeneralTheme['background']
|
||||
container: ChatTheme['container']
|
||||
onContainerChange: (container: ChatTheme['container'] | undefined) => void
|
||||
}
|
||||
|
||||
export const ChatContainerForm = ({
|
||||
generalBackground,
|
||||
container,
|
||||
onContainerChange,
|
||||
}: Props) => {
|
||||
const updateMaxWidth = (maxWidth?: number) =>
|
||||
updateDimension('maxWidth', maxWidth, maxWidthUnit)
|
||||
|
||||
const updateMaxWidthUnit = (unit: string) =>
|
||||
updateDimension('maxWidth', maxWidth, unit)
|
||||
|
||||
const updateMaxHeight = (maxHeight?: number) =>
|
||||
updateDimension('maxHeight', maxHeight, maxHeightUnit)
|
||||
|
||||
const updateMaxHeightUnit = (unit: string) =>
|
||||
updateDimension('maxHeight', maxHeight, unit)
|
||||
|
||||
const updateDimension = (
|
||||
dimension: 'maxWidth' | 'maxHeight',
|
||||
value: number | undefined,
|
||||
unit: string
|
||||
) =>
|
||||
onContainerChange({
|
||||
...container,
|
||||
[dimension]: `${value}${unit}`,
|
||||
})
|
||||
|
||||
const { value: maxWidth, unit: maxWidthUnit } = parseValueAndUnit(
|
||||
container?.maxWidth ?? defaultContainerMaxWidth
|
||||
)
|
||||
|
||||
const { value: maxHeight, unit: maxHeightUnit } = parseValueAndUnit(
|
||||
container?.maxHeight ?? defaultContainerMaxHeight
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Max width:
|
||||
</FormLabel>
|
||||
<HStack>
|
||||
<NumberInput
|
||||
size="sm"
|
||||
width="100px"
|
||||
defaultValue={maxWidth}
|
||||
min={0}
|
||||
step={10}
|
||||
withVariableButton={false}
|
||||
onValueChange={updateMaxWidth}
|
||||
/>
|
||||
<DropdownList
|
||||
size="sm"
|
||||
items={['px', '%', 'vh', 'vw']}
|
||||
currentItem={maxWidthUnit}
|
||||
onItemSelect={updateMaxWidthUnit}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Max height:
|
||||
</FormLabel>
|
||||
<HStack>
|
||||
<NumberInput
|
||||
size="sm"
|
||||
width="100px"
|
||||
defaultValue={maxHeight}
|
||||
min={0}
|
||||
step={10}
|
||||
onValueChange={updateMaxHeight}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
<DropdownList
|
||||
size="sm"
|
||||
items={['px', '%', 'vh', 'vw']}
|
||||
currentItem={maxHeightUnit}
|
||||
onItemSelect={updateMaxHeightUnit}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<ContainerThemeForm
|
||||
theme={container}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultContainerBackgroundColor,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
blur: defaultBlur,
|
||||
opacity: defaultOpacity,
|
||||
color: isChatContainerLight({
|
||||
chatContainer: container,
|
||||
generalBackground,
|
||||
})
|
||||
? defaultLightTextColor
|
||||
: defaultDarkTextColor,
|
||||
}}
|
||||
onThemeChange={onContainerChange}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const parseValueAndUnit = (valueWithUnit: string) => {
|
||||
const value = parseFloat(valueWithUnit)
|
||||
const unit = valueWithUnit.replace(value.toString(), '')
|
||||
return { value, unit }
|
||||
}
|
||||
@@ -1,23 +1,36 @@
|
||||
import {
|
||||
LargeRadiusIcon,
|
||||
MediumRadiusIcon,
|
||||
NoRadiusIcon,
|
||||
} from '@/components/icons'
|
||||
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
||||
import { Heading, Stack } from '@chakra-ui/react'
|
||||
import { AvatarProps, ChatTheme, Theme } from '@typebot.io/schemas'
|
||||
import {
|
||||
AvatarProps,
|
||||
ChatTheme,
|
||||
GeneralTheme,
|
||||
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'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { ChatContainerForm } from './ChatContainerForm'
|
||||
import { ContainerThemeForm } from './ContainerThemeForm'
|
||||
import {
|
||||
defaultButtonsBackgroundColor,
|
||||
defaultButtonsColor,
|
||||
defaultButtonsBorderThickness,
|
||||
defaultGuestBubblesBackgroundColor,
|
||||
defaultGuestBubblesColor,
|
||||
defaultHostBubblesBackgroundColor,
|
||||
defaultHostBubblesColor,
|
||||
defaultInputsBackgroundColor,
|
||||
defaultInputsColor,
|
||||
defaultInputsPlaceholderColor,
|
||||
defaultInputsShadow,
|
||||
defaultOpacity,
|
||||
defaultBlur,
|
||||
defaultRoundness,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
workspaceId: string
|
||||
typebotId: string
|
||||
generalBackground: GeneralTheme['background']
|
||||
chatTheme: Theme['chat']
|
||||
onChatThemeChange: (chatTheme: ChatTheme) => void
|
||||
}
|
||||
@@ -26,6 +39,7 @@ export const ChatThemeSettings = ({
|
||||
workspaceId,
|
||||
typebotId,
|
||||
chatTheme,
|
||||
generalBackground,
|
||||
onChatThemeChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslate()
|
||||
@@ -44,6 +58,16 @@ export const ChatThemeSettings = ({
|
||||
const updateInputs = (inputs: NonNullable<Theme['chat']>['inputs']) =>
|
||||
onChatThemeChange({ ...chatTheme, inputs })
|
||||
|
||||
const updateChatContainer = (
|
||||
container: NonNullable<Theme['chat']>['container']
|
||||
) => onChatThemeChange({ ...chatTheme, container })
|
||||
|
||||
const updateInputsPlaceholderColor = (placeholderColor: string) =>
|
||||
onChatThemeChange({
|
||||
...chatTheme,
|
||||
inputs: { ...chatTheme?.inputs, placeholderColor },
|
||||
})
|
||||
|
||||
const updateHostAvatar = (hostAvatar: AvatarProps) =>
|
||||
onChatThemeChange({ ...chatTheme, hostAvatar })
|
||||
|
||||
@@ -52,6 +76,14 @@ export const ChatThemeSettings = ({
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">Container</Heading>
|
||||
<ChatContainerForm
|
||||
generalBackground={generalBackground}
|
||||
container={chatTheme?.container}
|
||||
onContainerChange={updateChatContainer}
|
||||
/>
|
||||
</Stack>
|
||||
<AvatarForm
|
||||
uploadFileProps={{
|
||||
workspaceId,
|
||||
@@ -59,7 +91,7 @@ export const ChatThemeSettings = ({
|
||||
fileName: 'hostAvatar',
|
||||
}}
|
||||
title={t('theme.sideMenu.chat.botAvatar')}
|
||||
avatarProps={chatTheme?.hostAvatar ?? defaultTheme.chat.hostAvatar}
|
||||
avatarProps={chatTheme?.hostAvatar}
|
||||
isDefaultCheck
|
||||
onAvatarChange={updateHostAvatar}
|
||||
/>
|
||||
@@ -70,63 +102,83 @@ export const ChatThemeSettings = ({
|
||||
fileName: 'guestAvatar',
|
||||
}}
|
||||
title={t('theme.sideMenu.chat.userAvatar')}
|
||||
avatarProps={chatTheme?.guestAvatar ?? defaultTheme.chat.guestAvatar}
|
||||
avatarProps={chatTheme?.guestAvatar}
|
||||
onAvatarChange={updateGuestAvatar}
|
||||
/>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.botBubbles')}</Heading>
|
||||
<HostBubbles
|
||||
hostBubbles={chatTheme?.hostBubbles ?? defaultTheme.chat.hostBubbles}
|
||||
onHostBubblesChange={updateHostBubbles}
|
||||
<ContainerThemeForm
|
||||
testId="hostBubblesTheme"
|
||||
theme={chatTheme?.hostBubbles}
|
||||
onThemeChange={updateHostBubbles}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultHostBubblesBackgroundColor,
|
||||
color: defaultHostBubblesColor,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.userBubbles')}</Heading>
|
||||
<GuestBubbles
|
||||
guestBubbles={
|
||||
chatTheme?.guestBubbles ?? defaultTheme.chat.guestBubbles
|
||||
}
|
||||
onGuestBubblesChange={updateGuestBubbles}
|
||||
<ContainerThemeForm
|
||||
testId="guestBubblesTheme"
|
||||
theme={chatTheme?.guestBubbles}
|
||||
onThemeChange={updateGuestBubbles}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultGuestBubblesBackgroundColor,
|
||||
color: defaultGuestBubblesColor,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.buttons')}</Heading>
|
||||
<ButtonsTheme
|
||||
buttons={chatTheme?.buttons ?? defaultTheme.chat.buttons}
|
||||
onButtonsChange={updateButtons}
|
||||
<ContainerThemeForm
|
||||
testId="buttonsTheme"
|
||||
theme={chatTheme?.buttons}
|
||||
onThemeChange={updateButtons}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultButtonsBackgroundColor,
|
||||
color: defaultButtonsColor,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
thickness: defaultButtonsBorderThickness,
|
||||
color:
|
||||
chatTheme?.buttons?.backgroundColor ??
|
||||
defaultButtonsBackgroundColor,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">{t('theme.sideMenu.chat.inputs')}</Heading>
|
||||
<InputsTheme
|
||||
inputs={chatTheme?.inputs ?? defaultTheme.chat.inputs}
|
||||
onInputsChange={updateInputs}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack borderWidth={1} rounded="md" p="4" spacing={4}>
|
||||
<Heading fontSize="lg">
|
||||
{t('theme.sideMenu.chat.cornersRoundness')}
|
||||
</Heading>
|
||||
<RadioButtons
|
||||
options={[
|
||||
{
|
||||
label: <NoRadiusIcon />,
|
||||
value: 'none',
|
||||
<ContainerThemeForm
|
||||
testId="inputsTheme"
|
||||
theme={chatTheme?.inputs}
|
||||
onThemeChange={updateInputs}
|
||||
onPlaceholderColorChange={updateInputsPlaceholderColor}
|
||||
defaultTheme={{
|
||||
backgroundColor: defaultInputsBackgroundColor,
|
||||
color: defaultInputsColor,
|
||||
placeholderColor: defaultInputsPlaceholderColor,
|
||||
shadow: defaultInputsShadow,
|
||||
opacity: defaultOpacity,
|
||||
blur: defaultBlur,
|
||||
border: {
|
||||
roundeness: defaultRoundness,
|
||||
},
|
||||
{
|
||||
label: <MediumRadiusIcon />,
|
||||
value: 'medium',
|
||||
},
|
||||
{
|
||||
label: <LargeRadiusIcon />,
|
||||
value: 'large',
|
||||
},
|
||||
]}
|
||||
value={chatTheme?.roundness ?? defaultTheme.chat.roundness}
|
||||
onSelect={(roundness) =>
|
||||
onChatThemeChange({ ...chatTheme, roundness })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
import {
|
||||
Stack,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Switch,
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
} from '@chakra-ui/react'
|
||||
import {
|
||||
ContainerTheme,
|
||||
ContainerBorderTheme,
|
||||
InputTheme,
|
||||
} from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import { NumberInput } from '@/components/inputs'
|
||||
import { DropdownList } from '@/components/DropdownList'
|
||||
import {
|
||||
borderRoundness,
|
||||
defaultOpacity,
|
||||
shadows,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props<T extends ((placeholder: string) => void) | undefined> = {
|
||||
theme: (T extends undefined ? ContainerTheme : InputTheme) | undefined
|
||||
defaultTheme: T extends undefined ? ContainerTheme : InputTheme
|
||||
placeholderColor?: T extends undefined ? never : string
|
||||
testId?: string
|
||||
onThemeChange: (
|
||||
theme: T extends undefined ? ContainerTheme : InputTheme
|
||||
) => void
|
||||
onPlaceholderColorChange?: T
|
||||
}
|
||||
|
||||
export const ContainerThemeForm = <
|
||||
T extends ((placeholder: string) => void) | undefined
|
||||
>({
|
||||
theme,
|
||||
testId,
|
||||
defaultTheme,
|
||||
onPlaceholderColorChange,
|
||||
onThemeChange,
|
||||
}: Props<T>) => {
|
||||
const { t } = useTranslate()
|
||||
|
||||
const updateBackgroundColor = (backgroundColor: string) =>
|
||||
onThemeChange({ ...theme, backgroundColor })
|
||||
|
||||
const toggleBackgroundColor = () =>
|
||||
onThemeChange({
|
||||
...theme,
|
||||
backgroundColor:
|
||||
backgroundColor === 'transparent' ? '#ffffff' : 'transparent',
|
||||
})
|
||||
|
||||
const updateTextColor = (color: string) => onThemeChange({ ...theme, color })
|
||||
|
||||
const updateShadow = (shadow?: ContainerTheme['shadow']) =>
|
||||
onThemeChange({ ...theme, shadow })
|
||||
|
||||
const updateBlur = (blur?: number) => onThemeChange({ ...theme, blur })
|
||||
|
||||
const updateOpacity = (opacity?: number) =>
|
||||
onThemeChange({ ...theme, opacity })
|
||||
|
||||
const updateBorder = (border: ContainerBorderTheme) =>
|
||||
onThemeChange({ ...theme, border })
|
||||
|
||||
const updatePlaceholderColor = (color: string) =>
|
||||
onThemeChange({ ...theme, placeholderColor: color } as InputTheme)
|
||||
|
||||
const backgroundColor =
|
||||
theme?.backgroundColor ?? defaultTheme?.backgroundColor
|
||||
|
||||
const shadow = theme?.shadow ?? defaultTheme?.shadow ?? 'none'
|
||||
|
||||
return (
|
||||
<Stack spacing={4} data-testid={testId}>
|
||||
<HStack justify="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
{t('theme.sideMenu.chat.theme.background')}
|
||||
</FormLabel>
|
||||
<HStack>
|
||||
<Switch
|
||||
defaultChecked={backgroundColor !== 'transparent'}
|
||||
onChange={toggleBackgroundColor}
|
||||
/>
|
||||
<ColorPicker
|
||||
isDisabled={backgroundColor === 'transparent'}
|
||||
value={backgroundColor}
|
||||
onColorChange={updateBackgroundColor}
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<HStack justify="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
{t('theme.sideMenu.chat.theme.text')}
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
value={theme?.color ?? defaultTheme?.color}
|
||||
onColorChange={updateTextColor}
|
||||
/>
|
||||
</HStack>
|
||||
{onPlaceholderColorChange && (
|
||||
<HStack justify="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
{t('theme.sideMenu.chat.theme.placeholder')}
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
value={
|
||||
theme && 'placeholderColor' in theme
|
||||
? theme.placeholderColor
|
||||
: defaultTheme && 'placeholderColor' in defaultTheme
|
||||
? defaultTheme.placeholderColor
|
||||
: undefined
|
||||
}
|
||||
onColorChange={updatePlaceholderColor}
|
||||
/>
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Border
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<BorderThemeForm
|
||||
border={theme?.border}
|
||||
defaultBorder={defaultTheme.border}
|
||||
onBorderChange={updateBorder}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Advanced
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel as={Stack}>
|
||||
{backgroundColor !== 'transparent' && (
|
||||
<>
|
||||
<NumberInput
|
||||
size="sm"
|
||||
direction="row"
|
||||
label="Opacity:"
|
||||
width="100px"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.1}
|
||||
defaultValue={theme?.opacity ?? defaultTheme?.opacity}
|
||||
onValueChange={updateOpacity}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
{(theme?.opacity ?? defaultTheme?.opacity) !== 1 && (
|
||||
<NumberInput
|
||||
size="sm"
|
||||
direction="row"
|
||||
label="Blur:"
|
||||
suffix="px"
|
||||
width="100px"
|
||||
min={0}
|
||||
defaultValue={theme?.blur ?? defaultTheme?.blur}
|
||||
onValueChange={updateBlur}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<HStack justify="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Shadow:
|
||||
</FormLabel>
|
||||
<HStack>
|
||||
<DropdownList
|
||||
currentItem={shadow}
|
||||
onItemSelect={updateShadow}
|
||||
items={shadows}
|
||||
size="sm"
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const BorderThemeForm = ({
|
||||
border,
|
||||
defaultBorder,
|
||||
onBorderChange,
|
||||
}: {
|
||||
border: ContainerBorderTheme | undefined
|
||||
defaultBorder: ContainerBorderTheme | undefined
|
||||
onBorderChange: (border: ContainerBorderTheme) => void
|
||||
}) => {
|
||||
const updateRoundness = (roundeness: (typeof borderRoundness)[number]) => {
|
||||
onBorderChange({ ...border, roundeness })
|
||||
}
|
||||
|
||||
const updateCustomRoundeness = (customRoundeness: number | undefined) => {
|
||||
onBorderChange({ ...border, customRoundeness })
|
||||
}
|
||||
|
||||
const updateThickness = (thickness: number | undefined) => {
|
||||
onBorderChange({ ...border, thickness })
|
||||
}
|
||||
|
||||
const updateColor = (color: string | undefined) => {
|
||||
onBorderChange({ ...border, color })
|
||||
}
|
||||
|
||||
const updateOpacity = (opacity: number | undefined) => {
|
||||
onBorderChange({ ...border, opacity })
|
||||
}
|
||||
|
||||
const thickness = border?.thickness ?? defaultBorder?.thickness ?? 0
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Roundness:
|
||||
</FormLabel>
|
||||
<HStack>
|
||||
<DropdownList
|
||||
currentItem={border?.roundeness ?? defaultBorder?.roundeness}
|
||||
onItemSelect={updateRoundness}
|
||||
items={borderRoundness}
|
||||
placeholder="md"
|
||||
size="sm"
|
||||
/>
|
||||
{(border?.roundeness ?? defaultBorder?.roundeness) === 'custom' && (
|
||||
<NumberInput
|
||||
size="sm"
|
||||
suffix="px"
|
||||
width="60px"
|
||||
min={0}
|
||||
defaultValue={border?.customRoundeness}
|
||||
onValueChange={updateCustomRoundeness}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Thickness:
|
||||
</FormLabel>
|
||||
<NumberInput
|
||||
size="sm"
|
||||
suffix="px"
|
||||
width="60px"
|
||||
min={0}
|
||||
defaultValue={thickness}
|
||||
onValueChange={updateThickness}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
{thickness > 0 && (
|
||||
<>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Color:
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
value={border?.color ?? defaultBorder?.color}
|
||||
onColorChange={updateColor}
|
||||
/>
|
||||
</HStack>
|
||||
<NumberInput
|
||||
size="sm"
|
||||
direction="row"
|
||||
label="Opacity:"
|
||||
width="100px"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.1}
|
||||
defaultValue={border?.opacity ?? defaultOpacity}
|
||||
onValueChange={updateOpacity}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
||||
import { ContainerColors } from '@typebot.io/schemas'
|
||||
import { ContainerTheme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultGuestBubblesBackgroundColor,
|
||||
defaultGuestBubblesColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
guestBubbles: ContainerColors | undefined
|
||||
onGuestBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
||||
guestBubbles: ContainerTheme | undefined
|
||||
onGuestBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||
}
|
||||
|
||||
export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
@@ -25,8 +28,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||
<ColorPicker
|
||||
value={
|
||||
guestBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.guestBubbles.backgroundColor
|
||||
guestBubbles?.backgroundColor ?? defaultGuestBubblesBackgroundColor
|
||||
}
|
||||
onColorChange={updateBackground}
|
||||
/>
|
||||
@@ -34,7 +36,7 @@ export const GuestBubbles = ({ guestBubbles, onGuestBubblesChange }: Props) => {
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||
<ColorPicker
|
||||
value={guestBubbles?.color ?? defaultTheme.chat.guestBubbles.color}
|
||||
value={guestBubbles?.color ?? defaultGuestBubblesColor}
|
||||
onColorChange={updateText}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
||||
import { ContainerColors } from '@typebot.io/schemas'
|
||||
import { ContainerTheme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultHostBubblesBackgroundColor,
|
||||
defaultHostBubblesColor,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
hostBubbles: ContainerColors | undefined
|
||||
onHostBubblesChange: (hostBubbles: ContainerColors | undefined) => void
|
||||
hostBubbles: ContainerTheme | undefined
|
||||
onHostBubblesChange: (hostBubbles: ContainerTheme | undefined) => void
|
||||
}
|
||||
|
||||
export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
@@ -24,8 +27,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||
<ColorPicker
|
||||
value={
|
||||
hostBubbles?.backgroundColor ??
|
||||
defaultTheme.chat.hostBubbles.backgroundColor
|
||||
hostBubbles?.backgroundColor ?? defaultHostBubblesBackgroundColor
|
||||
}
|
||||
onColorChange={handleBackgroundChange}
|
||||
/>
|
||||
@@ -33,7 +35,7 @@ export const HostBubbles = ({ hostBubbles, onHostBubblesChange }: Props) => {
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||
<ColorPicker
|
||||
value={hostBubbles?.color ?? defaultTheme.chat.hostBubbles.color}
|
||||
value={hostBubbles?.color ?? defaultHostBubblesColor}
|
||||
onColorChange={handleTextChange}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Stack, Flex, Text } from '@chakra-ui/react'
|
||||
import { InputColors, Theme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
type Props = {
|
||||
inputs: NonNullable<Theme['chat']>['inputs']
|
||||
onInputsChange: (buttons: InputColors) => void
|
||||
}
|
||||
|
||||
export const InputsTheme = ({ inputs, onInputsChange }: Props) => {
|
||||
const { t } = useTranslate()
|
||||
|
||||
const handleBackgroundChange = (backgroundColor: string) =>
|
||||
onInputsChange({ ...inputs, backgroundColor })
|
||||
const handleTextChange = (color: string) =>
|
||||
onInputsChange({ ...inputs, color })
|
||||
const handlePlaceholderChange = (placeholderColor: string) =>
|
||||
onInputsChange({ ...inputs, placeholderColor })
|
||||
|
||||
return (
|
||||
<Stack data-testid="inputs-theme">
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.background')}</Text>
|
||||
<ColorPicker
|
||||
value={inputs?.backgroundColor}
|
||||
onColorChange={handleBackgroundChange}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.text')}</Text>
|
||||
<ColorPicker value={inputs?.color} onColorChange={handleTextChange} />
|
||||
</Flex>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.chat.theme.placeholder')}</Text>
|
||||
<ColorPicker
|
||||
value={inputs?.placeholderColor}
|
||||
onColorChange={handlePlaceholderChange}
|
||||
/>
|
||||
</Flex>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
@@ -16,7 +16,8 @@ import React from 'react'
|
||||
import { ColorPicker } from '../../../../components/ColorPicker'
|
||||
import {
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultBackgroundColor,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@@ -34,10 +35,7 @@ export const BackgroundContent = ({
|
||||
const handleContentChange = (content: string) =>
|
||||
onBackgroundContentChange(content)
|
||||
|
||||
if (
|
||||
(background?.type ?? defaultTheme.general.background.type) ===
|
||||
BackgroundType.IMAGE
|
||||
) {
|
||||
if ((background?.type ?? defaultBackgroundType) === BackgroundType.IMAGE) {
|
||||
if (!typebot) return null
|
||||
return (
|
||||
<Popover isLazy placement="top">
|
||||
@@ -76,15 +74,12 @@ export const BackgroundContent = ({
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
if (
|
||||
(background?.type ?? defaultTheme.general.background.type) ===
|
||||
BackgroundType.COLOR
|
||||
) {
|
||||
if ((background?.type ?? defaultBackgroundType) === BackgroundType.COLOR) {
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
<Text>{t('theme.sideMenu.global.background.color')}</Text>
|
||||
<ColorPicker
|
||||
value={background?.content ?? defaultTheme.general.background.content}
|
||||
value={background?.content ?? defaultBackgroundColor}
|
||||
onColorChange={handleContentChange}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { RadioButtons } from '@/components/inputs/RadioButtons'
|
||||
import { Stack, Text } from '@chakra-ui/react'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { Background } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
import { BackgroundContent } from './BackgroundContent'
|
||||
import {
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
|
||||
@@ -28,7 +28,6 @@ export const BackgroundSelector = ({
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Text>{t('theme.sideMenu.global.background')}</Text>
|
||||
<RadioButtons
|
||||
options={[
|
||||
{
|
||||
@@ -44,7 +43,7 @@ export const BackgroundSelector = ({
|
||||
value: BackgroundType.NONE,
|
||||
},
|
||||
]}
|
||||
value={background?.type ?? defaultTheme.general.background.type}
|
||||
value={background?.type ?? defaultBackgroundType}
|
||||
onSelect={handleBackgroundTypeChange}
|
||||
/>
|
||||
<BackgroundContent
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
Stack,
|
||||
Switch,
|
||||
useDisclosure,
|
||||
Text,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
AccordionIcon,
|
||||
} from '@chakra-ui/react'
|
||||
import { Background, Font, ProgressBar, Theme } from '@typebot.io/schemas'
|
||||
import React from 'react'
|
||||
@@ -16,7 +20,7 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
|
||||
import { useTranslate } from '@tolgee/react'
|
||||
import {
|
||||
defaultTheme,
|
||||
defaultFontType,
|
||||
fontTypes,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
@@ -91,7 +95,7 @@ export const GeneralSettings = ({
|
||||
const fontType =
|
||||
(typeof generalTheme?.font === 'string'
|
||||
? 'Google'
|
||||
: generalTheme?.font?.type) ?? defaultTheme.general.font.type
|
||||
: generalTheme?.font?.type) ?? defaultFontType
|
||||
|
||||
return (
|
||||
<Stack spacing={6}>
|
||||
@@ -115,23 +119,46 @@ export const GeneralSettings = ({
|
||||
onChange={updateBranding}
|
||||
/>
|
||||
</Flex>
|
||||
<ProgressBarForm
|
||||
progressBar={generalTheme?.progressBar}
|
||||
onProgressBarChange={updateProgressBar}
|
||||
/>
|
||||
<Stack>
|
||||
<Text>{t('theme.sideMenu.global.font')}</Text>
|
||||
<RadioButtons
|
||||
options={fontTypes}
|
||||
defaultValue={fontType}
|
||||
onSelect={updateFontType}
|
||||
/>
|
||||
<FontForm font={generalTheme?.font} onFontChange={updateFont} />
|
||||
</Stack>
|
||||
<BackgroundSelector
|
||||
background={generalTheme?.background}
|
||||
onBackgroundChange={handleBackgroundChange}
|
||||
/>
|
||||
<Accordion allowToggle>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
Progress Bar
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<ProgressBarForm
|
||||
progressBar={generalTheme?.progressBar}
|
||||
onProgressBarChange={updateProgressBar}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
{t('theme.sideMenu.global.font')}
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel as={Stack}>
|
||||
<RadioButtons
|
||||
options={fontTypes}
|
||||
defaultValue={fontType}
|
||||
onSelect={updateFontType}
|
||||
/>
|
||||
<FontForm font={generalTheme?.font} onFontChange={updateFont} />
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<AccordionButton justifyContent="space-between">
|
||||
{t('theme.sideMenu.global.background')}
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
<AccordionPanel>
|
||||
<BackgroundSelector
|
||||
background={generalTheme?.background}
|
||||
onBackgroundChange={handleBackgroundChange}
|
||||
/>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Select } from '@/components/inputs/Select'
|
||||
import { env } from '@typebot.io/env'
|
||||
import { GoogleFont } from '@typebot.io/schemas'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { defaultFontFamily } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
type Props = {
|
||||
@@ -11,8 +11,7 @@ type Props = {
|
||||
|
||||
export const GoogleFontForm = ({ font, onFontChange }: Props) => {
|
||||
const [currentFont, setCurrentFont] = useState(
|
||||
(typeof font === 'string' ? font : font?.family) ??
|
||||
defaultTheme.general.font.family
|
||||
(typeof font === 'string' ? font : font?.family) ?? defaultFontFamily
|
||||
)
|
||||
const [googleFonts, setGoogleFonts] = useState<string[]>([])
|
||||
|
||||
|
||||
@@ -5,7 +5,12 @@ import { NumberInput } from '@/components/inputs'
|
||||
import { FormLabel, HStack } from '@chakra-ui/react'
|
||||
import { ProgressBar } from '@typebot.io/schemas'
|
||||
import {
|
||||
defaultTheme,
|
||||
defaultProgressBarBackgroundColor,
|
||||
defaultProgressBarColor,
|
||||
defaultProgressBarIsEnabled,
|
||||
defaultProgressBarPlacement,
|
||||
defaultProgressBarPosition,
|
||||
defaultProgressBarThickness,
|
||||
progressBarPlacements,
|
||||
progressBarPositions,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
@@ -34,33 +39,41 @@ export const ProgressBarForm = ({
|
||||
const updateThickness = (thickness?: number) =>
|
||||
onProgressBarChange({ ...progressBar, thickness })
|
||||
|
||||
const updateBackgroundColor = (backgroundColor: string) =>
|
||||
onProgressBarChange({ ...progressBar, backgroundColor })
|
||||
|
||||
return (
|
||||
<SwitchWithRelatedSettings
|
||||
label={'Enable progress bar?'}
|
||||
initialValue={
|
||||
progressBar?.isEnabled ?? defaultTheme.general.progressBar.isEnabled
|
||||
}
|
||||
initialValue={progressBar?.isEnabled ?? defaultProgressBarIsEnabled}
|
||||
onCheckChange={updateEnabled}
|
||||
>
|
||||
<DropdownList
|
||||
size="sm"
|
||||
direction="row"
|
||||
label="Placement:"
|
||||
currentItem={
|
||||
progressBar?.placement ?? defaultTheme.general.progressBar.placement
|
||||
}
|
||||
currentItem={progressBar?.placement ?? defaultProgressBarPlacement}
|
||||
onItemSelect={updatePlacement}
|
||||
items={progressBarPlacements}
|
||||
/>
|
||||
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Color:
|
||||
Background color:
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
defaultValue={
|
||||
progressBar?.color ?? defaultTheme.general.progressBar.color
|
||||
progressBar?.backgroundColor ?? defaultProgressBarBackgroundColor
|
||||
}
|
||||
onColorChange={updateBackgroundColor}
|
||||
/>
|
||||
</HStack>
|
||||
<HStack justifyContent="space-between">
|
||||
<FormLabel mb="0" mr="0">
|
||||
Color:
|
||||
</FormLabel>
|
||||
<ColorPicker
|
||||
defaultValue={progressBar?.color ?? defaultProgressBarColor}
|
||||
onColorChange={updateColor}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -69,9 +82,7 @@ export const ProgressBarForm = ({
|
||||
direction="row"
|
||||
withVariableButton={false}
|
||||
maxW="100px"
|
||||
defaultValue={
|
||||
progressBar?.thickness ?? defaultTheme.general.progressBar.thickness
|
||||
}
|
||||
defaultValue={progressBar?.thickness ?? defaultProgressBarThickness}
|
||||
onValueChange={updateThickness}
|
||||
size="sm"
|
||||
/>
|
||||
@@ -80,9 +91,7 @@ export const ProgressBarForm = ({
|
||||
direction="row"
|
||||
label="Position when embedded:"
|
||||
moreInfoTooltip='Select "fixed" to always position the progress bar at the top of the window even though your bot is embedded. Select "absolute" to position the progress bar at the top of the chat container.'
|
||||
currentItem={
|
||||
progressBar?.position ?? defaultTheme.general.progressBar.position
|
||||
}
|
||||
currentItem={progressBar?.position ?? defaultProgressBarPosition}
|
||||
onItemSelect={updatePosition}
|
||||
items={progressBarPositions}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,10 @@ import test, { expect } from '@playwright/test'
|
||||
import { createId } from '@paralleldrive/cuid2'
|
||||
import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions'
|
||||
import { freeWorkspaceId } from '@typebot.io/playwright/databaseSetup'
|
||||
import {
|
||||
defaultContainerMaxHeight,
|
||||
defaultContainerMaxWidth,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
const hostAvatarUrl =
|
||||
'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1760&q=80'
|
||||
@@ -30,6 +34,7 @@ test.describe.parallel('Theme page', () => {
|
||||
await expect(page.locator('a:has-text("Made with Typebot")')).toBeHidden()
|
||||
|
||||
// Font
|
||||
await page.getByRole('button', { name: 'Font' }).click()
|
||||
await page.getByRole('textbox').fill('Roboto Slab')
|
||||
await page.getByRole('menuitem', { name: 'Roboto Slab' }).click()
|
||||
await expect(page.locator('.typebot-container')).toHaveCSS(
|
||||
@@ -42,6 +47,7 @@ test.describe.parallel('Theme page', () => {
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)'
|
||||
)
|
||||
await page.getByRole('button', { name: 'Background' }).click()
|
||||
await page.click('text=Color')
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Pick a color' }).click()
|
||||
@@ -82,6 +88,38 @@ test.describe.parallel('Theme page', () => {
|
||||
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
|
||||
await page.click('button:has-text("Chat")')
|
||||
|
||||
// Container
|
||||
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||
'max-width',
|
||||
defaultContainerMaxWidth
|
||||
)
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Max width:px$/ })
|
||||
.getByRole('spinbutton')
|
||||
.fill('600')
|
||||
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||
'max-width',
|
||||
'600px'
|
||||
)
|
||||
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||
'max-height',
|
||||
defaultContainerMaxHeight
|
||||
)
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Max height:%$/ })
|
||||
.getByRole('spinbutton')
|
||||
.fill('80')
|
||||
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||
'max-height',
|
||||
'80%'
|
||||
)
|
||||
await expect(page.locator('.typebot-chat-view')).toHaveCSS(
|
||||
'color',
|
||||
'rgb(48, 50, 53)'
|
||||
)
|
||||
|
||||
// Host avatar
|
||||
await expect(
|
||||
page.locator('[data-testid="default-avatar"]').nth(1)
|
||||
@@ -102,39 +140,13 @@ test.describe.parallel('Theme page', () => {
|
||||
|
||||
await expect(page.locator('.typebot-container img')).toBeHidden()
|
||||
|
||||
// Roundness
|
||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||
'border-radius',
|
||||
'6px'
|
||||
)
|
||||
await page
|
||||
.getByRole('region', { name: 'Chat' })
|
||||
.getByRole('radiogroup')
|
||||
.locator('div')
|
||||
.first()
|
||||
.click()
|
||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||
'border-radius',
|
||||
'0px'
|
||||
)
|
||||
await page
|
||||
.getByRole('region', { name: 'Chat' })
|
||||
.getByRole('radiogroup')
|
||||
.locator('div')
|
||||
.nth(2)
|
||||
.click()
|
||||
await expect(page.getByRole('button', { name: 'Go' })).toHaveCSS(
|
||||
'border-radius',
|
||||
'20px'
|
||||
)
|
||||
|
||||
// Host bubbles
|
||||
await page.click(
|
||||
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
'[data-testid="hostBubblesTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
)
|
||||
await page.fill('input[value="#F7F8FF"]', '#2a9d8f')
|
||||
await page.click(
|
||||
'[data-testid="host-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
'[data-testid="hostBubblesTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
)
|
||||
await page.fill('input[value="#303235"]', '#ffffff')
|
||||
const hostBubble = page.locator('[data-testid="host-bubble"] >> nth=-1')
|
||||
@@ -146,11 +158,11 @@ test.describe.parallel('Theme page', () => {
|
||||
|
||||
// Buttons
|
||||
await page.click(
|
||||
'[data-testid="buttons-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
'[data-testid="buttonsTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
)
|
||||
await page.fill('input[value="#0042DA"]', '#7209b7')
|
||||
await page.click(
|
||||
'[data-testid="buttons-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
'[data-testid="buttonsTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
)
|
||||
await page.fill('input[value="#FFFFFF"]', '#e9c46a')
|
||||
const button = page.getByRole('button', { name: 'Go' })
|
||||
@@ -159,11 +171,11 @@ test.describe.parallel('Theme page', () => {
|
||||
|
||||
// Guest bubbles
|
||||
await page.click(
|
||||
'[data-testid="guest-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
'[data-testid="guestBubblesTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
)
|
||||
await page.fill('input[value="#FF8E21"]', '#d8f3dc')
|
||||
await page.click(
|
||||
'[data-testid="guest-bubbles-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
'[data-testid="guestBubblesTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
)
|
||||
await page.fill('input[value="#FFFFFF"]', '#264653')
|
||||
await page.getByRole('button', { name: 'Go' }).click()
|
||||
@@ -192,11 +204,11 @@ test.describe.parallel('Theme page', () => {
|
||||
await page.waitForTimeout(1000)
|
||||
// Input
|
||||
await page.click(
|
||||
'[data-testid="inputs-theme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
'[data-testid="inputsTheme"] >> [aria-label="Pick a color"] >> nth=0'
|
||||
)
|
||||
await page.fill('input[value="#FFFFFF"]', '#ffe8d6')
|
||||
await page.click(
|
||||
'[data-testid="inputs-theme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
'[data-testid="inputsTheme"] >> [aria-label="Pick a color"] >> nth=1'
|
||||
)
|
||||
await page.fill('input[value="#303235"]', '#023e8a')
|
||||
const input = page.locator('.typebot-input')
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
"blocks.inputs.file.settings.skip.label": "Skip button label:",
|
||||
"blocks.inputs.fileUpload.blockCard.tooltip": "Upload Files",
|
||||
"blocks.inputs.number.settings.step.label": "Step:",
|
||||
"blocks.inputs.payment.collect.label": "Coletar",
|
||||
"blocks.inputs.payment.collect.label": "Collect",
|
||||
"blocks.inputs.payment.placeholder.label": "Configure...",
|
||||
"blocks.inputs.payment.settings.account.label": "Account:",
|
||||
"blocks.inputs.payment.settings.accountText.label": "{provider} account",
|
||||
|
||||
@@ -173,7 +173,12 @@ export const getAuthOptions = ({
|
||||
if (disposableEmailDomains.includes(user.email.split('@')[1]))
|
||||
return false
|
||||
}
|
||||
if (env.DISABLE_SIGNUP && isNewUser && user.email) {
|
||||
if (
|
||||
env.DISABLE_SIGNUP &&
|
||||
isNewUser &&
|
||||
user.email &&
|
||||
!env.ADMIN_EMAIL?.includes(user.email)
|
||||
) {
|
||||
const { invitations, workspaceInvitations } =
|
||||
await getNewUserInvitations(prisma, user.email)
|
||||
if (invitations.length === 0 && workspaceInvitations.length === 0)
|
||||
|
||||
@@ -21342,6 +21342,70 @@
|
||||
"chat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"container": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maxWidth": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxHeight": {
|
||||
"type": "string"
|
||||
},
|
||||
"backgroundColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostAvatar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -21372,6 +21436,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21383,6 +21494,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21394,6 +21552,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -21406,6 +21611,53 @@
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholderColor": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -21417,7 +21669,8 @@
|
||||
"none",
|
||||
"medium",
|
||||
"large"
|
||||
]
|
||||
],
|
||||
"description": "Deprecated, use `container.border.roundeness` instead"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6821,6 +6821,70 @@
|
||||
"chat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"container": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"maxWidth": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxHeight": {
|
||||
"type": "string"
|
||||
},
|
||||
"backgroundColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostAvatar": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6851,6 +6915,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6862,6 +6973,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6873,6 +7031,53 @@
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6885,6 +7090,53 @@
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"blur": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"shadow": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl"
|
||||
]
|
||||
},
|
||||
"border": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thickness": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roundeness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"medium",
|
||||
"large",
|
||||
"custom"
|
||||
]
|
||||
},
|
||||
"customRoundeness": {
|
||||
"type": "number"
|
||||
},
|
||||
"opacity": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"placeholderColor": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -6896,7 +7148,8 @@
|
||||
"none",
|
||||
"medium",
|
||||
"large"
|
||||
]
|
||||
],
|
||||
"description": "Deprecated, use `container.border.roundeness` instead"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ cp .env.example .env
|
||||
|
||||
<Note>
|
||||
The database user should have the `SUPERUSER` role. You can setup and migrate
|
||||
the database with the `pnpm prisma generate && pnpm db:migrate` command.
|
||||
the database with the `pnpm db:migrate` command.
|
||||
</Note>
|
||||
|
||||
3. Install dependencies
|
||||
|
||||
@@ -7,8 +7,11 @@ import { TypebotPageProps, TypebotPageV2 } from '@/components/TypebotPageV2'
|
||||
import { TypebotPageV3, TypebotV3PageProps } from '@/components/TypebotPageV3'
|
||||
import { env } from '@typebot.io/env'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||
import {
|
||||
defaultBackgroundColor,
|
||||
defaultBackgroundType,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
// Browsers that doesn't support ES modules and/or web components
|
||||
const incompatibleBrowsers = [
|
||||
@@ -109,9 +112,10 @@ const getTypebotFromPublicId = async (publicId?: string) => {
|
||||
? ({
|
||||
name: publishedTypebot.typebot.name,
|
||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||
background:
|
||||
publishedTypebot.theme.general?.background ??
|
||||
defaultTheme.general.background,
|
||||
background: publishedTypebot.theme.general?.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
},
|
||||
isHideQueryParamsEnabled:
|
||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||
defaultSettings.general.isHideQueryParamsEnabled,
|
||||
@@ -156,9 +160,10 @@ const getTypebotFromCustomDomain = async (customDomain: string) => {
|
||||
? ({
|
||||
name: publishedTypebot.typebot.name,
|
||||
publicId: publishedTypebot.typebot.publicId ?? null,
|
||||
background:
|
||||
publishedTypebot.theme.general?.background ??
|
||||
defaultTheme.general.background,
|
||||
background: publishedTypebot.theme.general?.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
},
|
||||
isHideQueryParamsEnabled:
|
||||
publishedTypebot.settings.general?.isHideQueryParamsEnabled ??
|
||||
defaultSettings.general.isHideQueryParamsEnabled,
|
||||
@@ -233,7 +238,10 @@ const App = ({
|
||||
defaultSettings.general.isHideQueryParamsEnabled
|
||||
}
|
||||
background={
|
||||
publishedTypebot.background ?? defaultTheme.general.background
|
||||
publishedTypebot.background ?? {
|
||||
type: defaultBackgroundType,
|
||||
content: defaultBackgroundColor,
|
||||
}
|
||||
}
|
||||
metadata={publishedTypebot.metadata ?? {}}
|
||||
font={publishedTypebot.font}
|
||||
|
||||
Reference in New Issue
Block a user