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)
|
||||
|
||||
Reference in New Issue
Block a user