@@ -43,6 +43,6 @@
|
||||
"rollup-plugin-postcss": "4.0.2",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.1.6"
|
||||
"typescript": "5.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,21 +12,22 @@ import {
|
||||
} from '@/utils/storage'
|
||||
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
|
||||
import immutableCss from '../assets/immutable.css'
|
||||
import { InputBlock, StartElementId } from '@typebot.io/schemas'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
export type BotProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
typebot: string | any
|
||||
isPreview?: boolean
|
||||
resultId?: string
|
||||
startGroupId?: string
|
||||
prefilledVariables?: Record<string, unknown>
|
||||
apiHost?: string
|
||||
onNewInputBlock?: (ids: { id: string; groupId: string }) => void
|
||||
onNewInputBlock?: (inputBlock: InputBlock) => void
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onInit?: () => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: OutgoingLog[]) => void
|
||||
}
|
||||
} & StartElementId
|
||||
|
||||
export const Bot = (props: BotProps & { class?: string }) => {
|
||||
const [initialChatReply, setInitialChatReply] = createSignal<
|
||||
@@ -54,11 +55,15 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
||||
resultId: isNotEmpty(props.resultId)
|
||||
? props.resultId
|
||||
: getExistingResultIdFromStorage(typebotIdFromProps),
|
||||
startGroupId: props.startGroupId,
|
||||
prefilledVariables: {
|
||||
...prefilledVariables,
|
||||
...props.prefilledVariables,
|
||||
},
|
||||
...('startGroupId' in props
|
||||
? { startGroupId: props.startGroupId }
|
||||
: 'startEventId' in props
|
||||
? { startEventId: props.startEventId }
|
||||
: {}),
|
||||
})
|
||||
if (error && 'code' in error && typeof error.code === 'string') {
|
||||
if (typeof props.typebot !== 'string' || (props.isPreview ?? false)) {
|
||||
@@ -80,7 +85,7 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
||||
}
|
||||
|
||||
if (data.resultId && typebotIdFromProps)
|
||||
setResultInStorage(data.typebot.settings.general.rememberUser?.storage)(
|
||||
setResultInStorage(data.typebot.settings.general?.rememberUser?.storage)(
|
||||
typebotIdFromProps,
|
||||
data.resultId
|
||||
)
|
||||
@@ -88,10 +93,7 @@ export const Bot = (props: BotProps & { class?: string }) => {
|
||||
setCustomCss(data.typebot.theme.customCss ?? '')
|
||||
|
||||
if (data.input?.id && props.onNewInputBlock)
|
||||
props.onNewInputBlock({
|
||||
id: data.input.id,
|
||||
groupId: data.input.groupId,
|
||||
})
|
||||
props.onNewInputBlock(data.input)
|
||||
if (data.logs) props.onNewLogs?.(data.logs)
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ type BotContentProps = {
|
||||
initialChatReply: InitialChatReply
|
||||
context: BotContext
|
||||
class?: string
|
||||
onNewInputBlock?: (block: { id: string; groupId: string }) => void
|
||||
onNewInputBlock?: (inputBlock: InputBlock) => void
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: OutgoingLog[]) => void
|
||||
@@ -176,13 +178,15 @@ const BotContent = (props: BotContentProps) => {
|
||||
existingFont
|
||||
?.getAttribute('href')
|
||||
?.includes(
|
||||
props.initialChatReply.typebot?.theme?.general?.font ?? 'Open Sans'
|
||||
props.initialChatReply.typebot?.theme?.general?.font ??
|
||||
defaultTheme.general.font
|
||||
)
|
||||
)
|
||||
return
|
||||
const font = document.createElement('link')
|
||||
font.href = `https://fonts.bunny.net/css2?family=${
|
||||
props.initialChatReply.typebot?.theme?.general?.font ?? 'Open Sans'
|
||||
props.initialChatReply.typebot?.theme?.general?.font ??
|
||||
defaultTheme.general.font
|
||||
}:ital,wght@0,300;0,400;0,600;1,300;1,400;1,600&display=swap');')`
|
||||
font.rel = 'stylesheet'
|
||||
font.id = 'bot-font'
|
||||
@@ -224,7 +228,9 @@ const BotContent = (props: BotContentProps) => {
|
||||
/>
|
||||
</div>
|
||||
<Show
|
||||
when={props.initialChatReply.typebot.settings.general.isBrandingEnabled}
|
||||
when={
|
||||
props.initialChatReply.typebot.settings.general?.isBrandingEnabled
|
||||
}
|
||||
>
|
||||
<LiteBadge botContainer={botContainer} />
|
||||
</Show>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { BotContext, ChatChunk as ChatChunkType } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { ChatReply, Settings, Theme } from '@typebot.io/schemas'
|
||||
import { ChatReply, Settings, Theme } from '@typebot.io/schemas'
|
||||
import { createSignal, For, onMount, Show } from 'solid-js'
|
||||
import { HostBubble } from '../bubbles/HostBubble'
|
||||
import { InputChatBlock } from '../InputChatBlock'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { StreamingBubble } from '../bubbles/StreamingBubble'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
|
||||
|
||||
type Props = Pick<ChatReply, 'messages' | 'input'> & {
|
||||
theme: Theme
|
||||
@@ -56,12 +58,13 @@ export const ChatChunk = (props: Props) => {
|
||||
<div class={'flex' + (isMobile() ? ' gap-1' : ' gap-2')}>
|
||||
<Show
|
||||
when={
|
||||
props.theme.chat.hostAvatar?.isEnabled &&
|
||||
(props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled) &&
|
||||
props.messages.length > 0
|
||||
}
|
||||
>
|
||||
<AvatarSideContainer
|
||||
hostAvatarSrc={props.theme.chat.hostAvatar?.url}
|
||||
hostAvatarSrc={props.theme.chat?.hostAvatar?.url}
|
||||
hideAvatar={props.hideAvatar}
|
||||
/>
|
||||
</Show>
|
||||
@@ -69,13 +72,15 @@ export const ChatChunk = (props: Props) => {
|
||||
<div
|
||||
class="flex flex-col flex-1 gap-2"
|
||||
style={{
|
||||
'max-width': props.theme.chat.guestAvatar?.isEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
: isMobile()
|
||||
? 'calc(100% - 32px)'
|
||||
: 'calc(100% - 48px)',
|
||||
'max-width':
|
||||
props.theme.chat?.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
: isMobile()
|
||||
? 'calc(100% - 32px)'
|
||||
: 'calc(100% - 48px)',
|
||||
}}
|
||||
>
|
||||
<For each={props.messages.slice(0, displayedMessageIndex() + 1)}>
|
||||
@@ -95,11 +100,15 @@ export const ChatChunk = (props: Props) => {
|
||||
ref={inputRef}
|
||||
block={props.input}
|
||||
inputIndex={props.inputIndex}
|
||||
hasHostAvatar={props.theme.chat.hostAvatar?.isEnabled ?? false}
|
||||
guestAvatar={props.theme.chat.guestAvatar}
|
||||
hasHostAvatar={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
}
|
||||
guestAvatar={props.theme.chat?.guestAvatar}
|
||||
context={props.context}
|
||||
isInputPrefillEnabled={
|
||||
props.settings.general.isInputPrefillEnabled ?? true
|
||||
props.settings.general?.isInputPrefillEnabled ??
|
||||
defaultSettings.general.isInputPrefillEnabled
|
||||
}
|
||||
hasError={props.hasError}
|
||||
onSubmit={props.onSubmit}
|
||||
@@ -109,9 +118,14 @@ export const ChatChunk = (props: Props) => {
|
||||
<Show when={props.streamingMessageId} keyed>
|
||||
{(streamingMessageId) => (
|
||||
<div class={'flex' + (isMobile() ? ' gap-1' : ' gap-2')}>
|
||||
<Show when={props.theme.chat.hostAvatar?.isEnabled}>
|
||||
<Show
|
||||
when={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
}
|
||||
>
|
||||
<AvatarSideContainer
|
||||
hostAvatarSrc={props.theme.chat.hostAvatar?.url}
|
||||
hostAvatarSrc={props.theme.chat?.hostAvatar?.url}
|
||||
hideAvatar={props.hideAvatar}
|
||||
/>
|
||||
</Show>
|
||||
@@ -119,13 +133,15 @@ export const ChatChunk = (props: Props) => {
|
||||
<div
|
||||
class="flex flex-col flex-1 gap-2"
|
||||
style={{
|
||||
'max-width': props.theme.chat.guestAvatar?.isEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
: isMobile()
|
||||
? 'calc(100% - 32px)'
|
||||
: 'calc(100% - 48px)',
|
||||
'max-width':
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
? isMobile()
|
||||
? 'calc(100% - 32px - 32px)'
|
||||
: 'calc(100% - 48px - 48px)'
|
||||
: isMobile()
|
||||
? 'calc(100% - 32px)'
|
||||
: 'calc(100% - 48px)',
|
||||
}}
|
||||
>
|
||||
<StreamingBubble streamingMessageId={streamingMessageId} />
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ChatReply, SendMessageInput, Theme } from '@typebot.io/schemas'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/enums'
|
||||
import {
|
||||
ChatReply,
|
||||
InputBlock,
|
||||
SendMessageInput,
|
||||
Theme,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
createEffect,
|
||||
createSignal,
|
||||
@@ -25,6 +29,7 @@ import {
|
||||
formattedMessages,
|
||||
setFormattedMessages,
|
||||
} from '@/utils/formattedMessagesSignal'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
|
||||
const parseDynamicTheme = (
|
||||
initialTheme: Theme,
|
||||
@@ -34,26 +39,26 @@ const parseDynamicTheme = (
|
||||
chat: {
|
||||
...initialTheme.chat,
|
||||
hostAvatar:
|
||||
initialTheme.chat.hostAvatar && dynamicTheme?.hostAvatarUrl
|
||||
initialTheme.chat?.hostAvatar && dynamicTheme?.hostAvatarUrl
|
||||
? {
|
||||
...initialTheme.chat.hostAvatar,
|
||||
url: dynamicTheme.hostAvatarUrl,
|
||||
}
|
||||
: initialTheme.chat.hostAvatar,
|
||||
: initialTheme.chat?.hostAvatar,
|
||||
guestAvatar:
|
||||
initialTheme.chat.guestAvatar && dynamicTheme?.guestAvatarUrl
|
||||
initialTheme.chat?.guestAvatar && dynamicTheme?.guestAvatarUrl
|
||||
? {
|
||||
...initialTheme.chat.guestAvatar,
|
||||
url: dynamicTheme?.guestAvatarUrl,
|
||||
}
|
||||
: initialTheme.chat.guestAvatar,
|
||||
: initialTheme.chat?.guestAvatar,
|
||||
},
|
||||
})
|
||||
|
||||
type Props = {
|
||||
initialChatReply: InitialChatReply
|
||||
context: BotContext
|
||||
onNewInputBlock?: (ids: { id: string; groupId: string }) => void
|
||||
onNewInputBlock?: (inputBlock: InputBlock) => void
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: OutgoingLog[]) => void
|
||||
@@ -178,11 +183,8 @@ export const ConversationContainer = (props: Props) => {
|
||||
}
|
||||
if (data.logs) props.onNewLogs?.(data.logs)
|
||||
if (data.dynamicTheme) setDynamicTheme(data.dynamicTheme)
|
||||
if (data.input?.id && props.onNewInputBlock) {
|
||||
props.onNewInputBlock({
|
||||
id: data.input.id,
|
||||
groupId: data.input.groupId,
|
||||
})
|
||||
if (data.input && props.onNewInputBlock) {
|
||||
props.onNewInputBlock(data.input)
|
||||
}
|
||||
if (data.clientSideActions) {
|
||||
const actionsBeforeFirstBubble = data.clientSideActions.filter((action) =>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Theme } from '@typebot.io/schemas'
|
||||
import { Show } from 'solid-js'
|
||||
import { LoadingBubble } from '../bubbles/LoadingBubble'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
theme: Theme
|
||||
@@ -11,9 +12,14 @@ export const LoadingChunk = (props: Props) => (
|
||||
<div class="flex w-full">
|
||||
<div class="flex flex-col w-full min-w-0">
|
||||
<div class="flex gap-2">
|
||||
<Show when={props.theme.chat.hostAvatar?.isEnabled}>
|
||||
<Show
|
||||
when={
|
||||
props.theme.chat?.hostAvatar?.isEnabled ??
|
||||
defaultTheme.chat.hostAvatar.isEnabled
|
||||
}
|
||||
>
|
||||
<AvatarSideContainer
|
||||
hostAvatarSrc={props.theme.chat.hostAvatar?.url}
|
||||
hostAvatarSrc={props.theme.chat?.hostAvatar?.url}
|
||||
/>
|
||||
</Show>
|
||||
<LoadingBubble />
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type {
|
||||
ChatReply,
|
||||
ChoiceInputBlock,
|
||||
DateInputOptions,
|
||||
EmailInputBlock,
|
||||
FileInputBlock,
|
||||
NumberInputBlock,
|
||||
PaymentInputOptions,
|
||||
PhoneNumberInputBlock,
|
||||
RatingInputBlock,
|
||||
RuntimeOptions,
|
||||
@@ -13,8 +11,9 @@ import type {
|
||||
Theme,
|
||||
UrlInputBlock,
|
||||
PictureChoiceBlock,
|
||||
PaymentInputBlock,
|
||||
DateInputBlock,
|
||||
} from '@typebot.io/schemas'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/enums'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { BotContext, InputSubmitContent } from '@/types'
|
||||
import { TextInput } from '@/features/blocks/inputs/textInput'
|
||||
@@ -34,12 +33,15 @@ import { Buttons } from '@/features/blocks/inputs/buttons/components/Buttons'
|
||||
import { SinglePictureChoice } from '@/features/blocks/inputs/pictureChoice/SinglePictureChoice'
|
||||
import { MultiplePictureChoice } from '@/features/blocks/inputs/pictureChoice/MultiplePictureChoice'
|
||||
import { formattedMessages } from '@/utils/formattedMessagesSignal'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
||||
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
type Props = {
|
||||
ref: HTMLDivElement | undefined
|
||||
block: NonNullable<ChatReply['input']>
|
||||
hasHostAvatar: boolean
|
||||
guestAvatar?: Theme['chat']['guestAvatar']
|
||||
guestAvatar?: NonNullable<Theme['chat']>['guestAvatar']
|
||||
inputIndex: number
|
||||
context: BotContext
|
||||
isInputPrefillEnabled: boolean
|
||||
@@ -74,7 +76,10 @@ export const InputChatBlock = (props: Props) => {
|
||||
<Match when={answer() && !props.hasError}>
|
||||
<GuestBubble
|
||||
message={formattedMessage() ?? (answer() as string)}
|
||||
showAvatar={props.guestAvatar?.isEnabled ?? false}
|
||||
showAvatar={
|
||||
props.guestAvatar?.isEnabled ??
|
||||
defaultTheme.chat.guestAvatar.isEnabled
|
||||
}
|
||||
avatarSrc={props.guestAvatar?.url && props.guestAvatar.url}
|
||||
/>
|
||||
</Match>
|
||||
@@ -122,8 +127,8 @@ const Input = (props: {
|
||||
const submitPaymentSuccess = () =>
|
||||
props.onSubmit({
|
||||
value:
|
||||
(props.block.options as PaymentInputOptions).labels.success ??
|
||||
'Success',
|
||||
(props.block.options as PaymentInputBlock['options'])?.labels
|
||||
?.success ?? defaultPaymentInputOptions.labels.success,
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -158,9 +163,9 @@ const Input = (props: {
|
||||
</Match>
|
||||
<Match when={props.block.type === InputBlockType.PHONE}>
|
||||
<PhoneInput
|
||||
labels={(props.block as PhoneNumberInputBlock).options.labels}
|
||||
labels={(props.block as PhoneNumberInputBlock).options?.labels}
|
||||
defaultCountryCode={
|
||||
(props.block as PhoneNumberInputBlock).options.defaultCountryCode
|
||||
(props.block as PhoneNumberInputBlock).options?.defaultCountryCode
|
||||
}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
@@ -168,7 +173,7 @@ const Input = (props: {
|
||||
</Match>
|
||||
<Match when={props.block.type === InputBlockType.DATE}>
|
||||
<DateForm
|
||||
options={props.block.options as DateInputOptions}
|
||||
options={props.block.options as DateInputBlock['options']}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
@@ -176,7 +181,7 @@ const Input = (props: {
|
||||
<Match when={isButtonsBlock(props.block)} keyed>
|
||||
{(block) => (
|
||||
<Switch>
|
||||
<Match when={!block.options.isMultipleChoice}>
|
||||
<Match when={!block.options?.isMultipleChoice}>
|
||||
<Buttons
|
||||
inputIndex={props.inputIndex}
|
||||
defaultItems={block.items}
|
||||
@@ -184,7 +189,7 @@ const Input = (props: {
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={block.options.isMultipleChoice}>
|
||||
<Match when={block.options?.isMultipleChoice}>
|
||||
<MultipleChoicesForm
|
||||
inputIndex={props.inputIndex}
|
||||
defaultItems={block.items}
|
||||
@@ -198,14 +203,14 @@ const Input = (props: {
|
||||
<Match when={isPictureChoiceBlock(props.block)} keyed>
|
||||
{(block) => (
|
||||
<Switch>
|
||||
<Match when={!block.options.isMultipleChoice}>
|
||||
<Match when={!block.options?.isMultipleChoice}>
|
||||
<SinglePictureChoice
|
||||
defaultItems={block.items}
|
||||
options={block.options}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={block.options.isMultipleChoice}>
|
||||
<Match when={block.options?.isMultipleChoice}>
|
||||
<MultiplePictureChoice
|
||||
defaultItems={block.items}
|
||||
options={block.options}
|
||||
@@ -237,7 +242,7 @@ const Input = (props: {
|
||||
{
|
||||
...props.block.options,
|
||||
...props.block.runtimeOptions,
|
||||
} as PaymentInputOptions & RuntimeOptions
|
||||
} as PaymentInputBlock['options'] & RuntimeOptions
|
||||
}
|
||||
onSuccess={submitPaymentSuccess}
|
||||
/>
|
||||
|
||||
@@ -4,20 +4,20 @@ import { ImageBubble } from '@/features/blocks/bubbles/image'
|
||||
import { TextBubble } from '@/features/blocks/bubbles/textBubble'
|
||||
import { VideoBubble } from '@/features/blocks/bubbles/video'
|
||||
import type {
|
||||
AudioBubbleContent,
|
||||
AudioBubbleBlock,
|
||||
ChatMessage,
|
||||
EmbedBubbleContent,
|
||||
ImageBubbleContent,
|
||||
TextBubbleContent,
|
||||
TypingEmulation,
|
||||
VideoBubbleContent,
|
||||
EmbedBubbleBlock,
|
||||
ImageBubbleBlock,
|
||||
Settings,
|
||||
TextBubbleBlock,
|
||||
VideoBubbleBlock,
|
||||
} from '@typebot.io/schemas'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/enums'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { Match, Switch } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
message: ChatMessage
|
||||
typingEmulation: TypingEmulation
|
||||
typingEmulation: Settings['typingEmulation']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -30,32 +30,32 @@ export const HostBubble = (props: Props) => {
|
||||
<Switch>
|
||||
<Match when={props.message.type === BubbleBlockType.TEXT}>
|
||||
<TextBubble
|
||||
content={props.message.content as TextBubbleContent}
|
||||
content={props.message.content as TextBubbleBlock['content']}
|
||||
typingEmulation={props.typingEmulation}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.message.type === BubbleBlockType.IMAGE}>
|
||||
<ImageBubble
|
||||
content={props.message.content as ImageBubbleContent}
|
||||
content={props.message.content as ImageBubbleBlock['content']}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.message.type === BubbleBlockType.VIDEO}>
|
||||
<VideoBubble
|
||||
content={props.message.content as VideoBubbleContent}
|
||||
content={props.message.content as VideoBubbleBlock['content']}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.message.type === BubbleBlockType.EMBED}>
|
||||
<EmbedBubble
|
||||
content={props.message.content as EmbedBubbleContent}
|
||||
content={props.message.content as EmbedBubbleBlock['content']}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.message.type === BubbleBlockType.AUDIO}>
|
||||
<AudioBubble
|
||||
content={props.message.content as AudioBubbleContent}
|
||||
content={props.message.content as AudioBubbleBlock['content']}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
/>
|
||||
</Match>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { AudioBubbleContent } from '@typebot.io/schemas'
|
||||
import { AudioBubbleBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { defaultAudioBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/audio/constants'
|
||||
|
||||
type Props = {
|
||||
content: AudioBubbleContent
|
||||
content: AudioBubbleBlock['content']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -50,8 +51,11 @@ export const AudioBubble = (props: Props) => {
|
||||
</div>
|
||||
<audio
|
||||
ref={audioElement}
|
||||
src={props.content.url}
|
||||
autoplay={props.content.isAutoplayEnabled ?? true}
|
||||
src={props.content?.url}
|
||||
autoplay={
|
||||
props.content?.isAutoplayEnabled ??
|
||||
defaultAudioBubbleContent.isAutoplayEnabled
|
||||
}
|
||||
class={
|
||||
'z-10 text-fade-in ' +
|
||||
(isTyping() ? 'opacity-0' : 'opacity-100 m-2')
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { EmbedBubbleContent } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { clsx } from 'clsx'
|
||||
import { EmbedBubbleBlock } from '@typebot.io/schemas'
|
||||
import { defaultEmbedBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/embed/constants'
|
||||
|
||||
type Props = {
|
||||
content: EmbedBubbleContent
|
||||
content: EmbedBubbleBlock['content']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -53,12 +54,14 @@ export const EmbedBubble = (props: Props) => {
|
||||
? isMobile()
|
||||
? '32px'
|
||||
: '36px'
|
||||
: `${props.content.height}px`,
|
||||
: `${
|
||||
props.content?.height ?? defaultEmbedBubbleContent.height
|
||||
}px`,
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
id="embed-bubble-content"
|
||||
src={props.content.url}
|
||||
src={props.content?.url}
|
||||
class={'w-full h-full '}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { ImageBubbleContent } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { clsx } from 'clsx'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { ImageBubbleBlock } from '@typebot.io/schemas'
|
||||
import { defaultImageBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/image/constants'
|
||||
|
||||
type Props = {
|
||||
content: ImageBubbleContent
|
||||
content: ImageBubbleBlock['content']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -44,8 +45,10 @@ export const ImageBubble = (props: Props) => {
|
||||
const Image = (
|
||||
<img
|
||||
ref={image}
|
||||
src={props.content.url}
|
||||
alt={props.content.clickLink?.alt ?? 'Bubble image'}
|
||||
src={props.content?.url}
|
||||
alt={
|
||||
props.content?.clickLink?.alt ?? defaultImageBubbleContent.clickLink.alt
|
||||
}
|
||||
class={
|
||||
'text-fade-in w-full ' + (isTyping() ? 'opacity-0' : 'opacity-100')
|
||||
}
|
||||
@@ -71,7 +74,7 @@ export const ImageBubble = (props: Props) => {
|
||||
>
|
||||
{isTyping() ? <TypingBubble /> : null}
|
||||
</div>
|
||||
{props.content.clickLink ? (
|
||||
{props.content?.clickLink ? (
|
||||
<a
|
||||
href={props.content.clickLink.url}
|
||||
target="_blank"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { TextBubbleContent, TypingEmulation } from '@typebot.io/schemas'
|
||||
import type { Settings, TextBubbleBlock } from '@typebot.io/schemas'
|
||||
import { For, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { PlateElement } from './plate/PlateBlock'
|
||||
import { computePlainText } from '../helpers/convertRichTextToPlainText'
|
||||
@@ -8,8 +8,8 @@ import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { computeTypingDuration } from '@typebot.io/bot-engine/computeTypingDuration'
|
||||
|
||||
type Props = {
|
||||
content: TextBubbleContent
|
||||
typingEmulation: TypingEmulation
|
||||
content: TextBubbleBlock['content']
|
||||
typingEmulation: Settings['typingEmulation']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ export const TextBubble = (props: Props) => {
|
||||
|
||||
onMount(() => {
|
||||
if (!isTyping) return
|
||||
const plainText = computePlainText(props.content.richText)
|
||||
const plainText = props.content?.richText
|
||||
? computePlainText(props.content.richText)
|
||||
: ''
|
||||
const typingDuration =
|
||||
props.typingEmulation?.enabled === false
|
||||
? 0
|
||||
@@ -69,7 +71,7 @@ export const TextBubble = (props: Props) => {
|
||||
height: isTyping() ? (isMobile() ? '16px' : '20px') : '100%',
|
||||
}}
|
||||
>
|
||||
<For each={props.content.richText}>
|
||||
<For each={props.content?.richText}>
|
||||
{(element) => <PlateElement element={element} />}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { VideoBubbleContent } from '@typebot.io/schemas'
|
||||
import { VideoBubbleContentType } from '@typebot.io/schemas/features/blocks/bubbles/video/enums'
|
||||
import { createSignal, Match, onCleanup, onMount, Switch } from 'solid-js'
|
||||
import { clsx } from 'clsx'
|
||||
import {
|
||||
defaultVideoBubbleContent,
|
||||
VideoBubbleContentType,
|
||||
} from '@typebot.io/schemas/features/blocks/bubbles/video/constants'
|
||||
import { VideoBubbleBlock } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
content: VideoBubbleContent
|
||||
content: VideoBubbleBlock['content']
|
||||
onTransitionEnd: (offsetTop?: number) => void
|
||||
}
|
||||
|
||||
@@ -60,7 +63,7 @@ export const VideoBubble = (props: Props) => {
|
||||
>
|
||||
<video
|
||||
autoplay
|
||||
src={props.content.url}
|
||||
src={props.content?.url}
|
||||
controls
|
||||
class={
|
||||
'p-4 focus:outline-none w-full z-10 text-fade-in rounded-md ' +
|
||||
@@ -90,15 +93,18 @@ export const VideoBubble = (props: Props) => {
|
||||
? isMobile()
|
||||
? '32px'
|
||||
: '36px'
|
||||
: `${props.content.height ?? '400'}px`,
|
||||
: `${
|
||||
props.content?.height ??
|
||||
defaultVideoBubbleContent.height
|
||||
}px`,
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
src={`${
|
||||
props.content.type === VideoBubbleContentType.VIMEO
|
||||
props.content?.type === VideoBubbleContentType.VIMEO
|
||||
? 'https://player.vimeo.com/video'
|
||||
: 'https://www.youtube.com/embed'
|
||||
}/${props.content.id}`}
|
||||
}/${props.content?.id}`}
|
||||
class={'w-full h-full'}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
|
||||
@@ -3,8 +3,8 @@ import { SearchInput } from '@/components/inputs/SearchInput'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { ChoiceInputBlock } from '@typebot.io/schemas'
|
||||
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice'
|
||||
import { For, Show, createSignal, onMount } from 'solid-js'
|
||||
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants'
|
||||
|
||||
type Props = {
|
||||
inputIndex: number
|
||||
@@ -34,13 +34,13 @@ export const Buttons = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<Show when={props.options.isSearchable}>
|
||||
<Show when={props.options?.isSearchable}>
|
||||
<div class="flex items-end typebot-input w-full">
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
onInput={filterItems}
|
||||
placeholder={
|
||||
props.options.searchInputPlaceholder ??
|
||||
props.options?.searchInputPlaceholder ??
|
||||
defaultChoiceInputOptions.searchInputPlaceholder
|
||||
}
|
||||
onClear={() => setFilteredItems(props.defaultItems)}
|
||||
@@ -51,7 +51,7 @@ export const Buttons = (props: Props) => {
|
||||
<div
|
||||
class={
|
||||
'flex flex-wrap justify-end gap-2' +
|
||||
(props.options.isSearchable
|
||||
(props.options?.isSearchable
|
||||
? ' overflow-y-scroll max-h-80 rounded-md hide-scrollbar'
|
||||
: '')
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ChoiceInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, For, onMount, Show } from 'solid-js'
|
||||
import { Checkbox } from './Checkbox'
|
||||
import { SearchInput } from '@/components/inputs/SearchInput'
|
||||
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice'
|
||||
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants'
|
||||
|
||||
type Props = {
|
||||
inputIndex: number
|
||||
@@ -59,13 +59,13 @@ export const MultipleChoicesForm = (props: Props) => {
|
||||
|
||||
return (
|
||||
<form class="flex flex-col items-end gap-2 w-full" onSubmit={handleSubmit}>
|
||||
<Show when={props.options.isSearchable}>
|
||||
<Show when={props.options?.isSearchable}>
|
||||
<div class="flex items-end typebot-input w-full">
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
onInput={filterItems}
|
||||
placeholder={
|
||||
props.options.searchInputPlaceholder ??
|
||||
props.options?.searchInputPlaceholder ??
|
||||
defaultChoiceInputOptions.searchInputPlaceholder
|
||||
}
|
||||
onClear={() => setFilteredItems(props.defaultItems)}
|
||||
@@ -75,7 +75,7 @@ export const MultipleChoicesForm = (props: Props) => {
|
||||
<div
|
||||
class={
|
||||
'flex flex-wrap justify-end gap-2' +
|
||||
(props.options.isSearchable
|
||||
(props.options?.isSearchable
|
||||
? ' overflow-y-scroll max-h-80 rounded-md hide-scrollbar'
|
||||
: '')
|
||||
}
|
||||
@@ -144,7 +144,7 @@ export const MultipleChoicesForm = (props: Props) => {
|
||||
</div>
|
||||
{selectedItemIds().length > 0 && (
|
||||
<SendButton disableIcon>
|
||||
{props.options?.buttonLabel ?? 'Send'}
|
||||
{props.options?.buttonLabel ?? defaultChoiceInputOptions.buttonLabel}
|
||||
</SendButton>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import type { DateInputOptions } from '@typebot.io/schemas'
|
||||
import { DateInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { defaultDateInputOptions } from '@typebot.io/schemas/features/blocks/inputs/date/constants'
|
||||
|
||||
type Props = {
|
||||
onSubmit: (inputValue: InputSubmitContent) => void
|
||||
options?: DateInputOptions
|
||||
options?: DateInputBlock['options']
|
||||
defaultValue?: string
|
||||
}
|
||||
|
||||
@@ -38,7 +39,8 @@ export const DateForm = (props: Props) => {
|
||||
>
|
||||
{props.options?.isRange && (
|
||||
<p class="font-semibold">
|
||||
{props.options.labels?.from ?? 'From:'}
|
||||
{props.options.labels?.from ??
|
||||
defaultDateInputOptions.labels.from}
|
||||
</p>
|
||||
)}
|
||||
<input
|
||||
@@ -65,7 +67,8 @@ export const DateForm = (props: Props) => {
|
||||
<div class="flex items-center p-4">
|
||||
{props.options.isRange && (
|
||||
<p class="font-semibold">
|
||||
{props.options.labels?.to ?? 'To:'}
|
||||
{props.options.labels?.to ??
|
||||
defaultDateInputOptions.labels.to}
|
||||
</p>
|
||||
)}
|
||||
<input
|
||||
@@ -95,7 +98,8 @@ export const DateForm = (props: Props) => {
|
||||
isDisabled={inputValues().to === '' && inputValues().from === ''}
|
||||
class="my-2 ml-2"
|
||||
>
|
||||
{props.options?.labels?.button ?? 'Send'}
|
||||
{props.options?.labels?.button ??
|
||||
defaultDateInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { EmailInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { defaultEmailInputOptions } from '@typebot.io/schemas/features/blocks/inputs/email/constants'
|
||||
|
||||
type Props = {
|
||||
block: EmailInputBlock
|
||||
@@ -57,7 +58,8 @@ export const EmailInput = (props: Props) => {
|
||||
ref={inputRef}
|
||||
value={inputValue()}
|
||||
placeholder={
|
||||
props.block.options?.labels?.placeholder ?? 'Type your email...'
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultEmailInputOptions.labels.placeholder
|
||||
}
|
||||
onInput={handleInput}
|
||||
type="email"
|
||||
@@ -69,7 +71,8 @@ export const EmailInput = (props: Props) => {
|
||||
class="my-2 ml-2"
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
{props.block.options?.labels?.button ??
|
||||
defaultEmailInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { BotContext, InputSubmitContent } from '@/types'
|
||||
import { FileInputBlock } from '@typebot.io/schemas'
|
||||
import { defaultFileInputOptions } from '@typebot.io/schemas/features/blocks/inputs/file'
|
||||
import { createSignal, Match, Show, Switch } from 'solid-js'
|
||||
import { Button } from '@/components/Button'
|
||||
import { Spinner } from '@/components/Spinner'
|
||||
import { uploadFiles } from '../helpers/uploadFiles'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import { getRuntimeVariable } from '@typebot.io/env/getRuntimeVariable'
|
||||
import { defaultFileInputOptions } from '@typebot.io/schemas/features/blocks/inputs/file/constants'
|
||||
|
||||
type Props = {
|
||||
context: BotContext
|
||||
@@ -27,14 +27,16 @@ export const FileUploadForm = (props: Props) => {
|
||||
setErrorMessage(undefined)
|
||||
const newFiles = Array.from(files)
|
||||
const sizeLimit =
|
||||
props.block.options.sizeLimit ??
|
||||
getRuntimeVariable('NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE')
|
||||
props.block.options && 'sizeLimit' in props.block.options
|
||||
? props.block.options?.sizeLimit ??
|
||||
getRuntimeVariable('NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE')
|
||||
: undefined
|
||||
if (
|
||||
sizeLimit &&
|
||||
newFiles.some((file) => file.size > sizeLimit * 1024 * 1024)
|
||||
)
|
||||
return setErrorMessage(`A file is larger than ${sizeLimit}MB`)
|
||||
if (!props.block.options.isMultipleAllowed && files)
|
||||
if (!props.block.options?.isMultipleAllowed && files)
|
||||
return startSingleFileUpload(newFiles[0])
|
||||
setSelectedFiles([...selectedFiles(), ...newFiles])
|
||||
}
|
||||
@@ -118,7 +120,7 @@ export const FileUploadForm = (props: Props) => {
|
||||
|
||||
const skip = () =>
|
||||
props.onSkip(
|
||||
props.block.options.labels.skip ?? defaultFileInputOptions.labels.skip
|
||||
props.block.options?.labels?.skip ?? defaultFileInputOptions.labels.skip
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -165,14 +167,20 @@ export const FileUploadForm = (props: Props) => {
|
||||
</Show>
|
||||
<p
|
||||
class="text-sm text-gray-500 text-center"
|
||||
innerHTML={props.block.options.labels.placeholder}
|
||||
innerHTML={
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultFileInputOptions.labels.placeholder
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="dropzone-file"
|
||||
type="file"
|
||||
class="hidden"
|
||||
multiple={props.block.options.isMultipleAllowed}
|
||||
multiple={
|
||||
props.block.options?.isMultipleAllowed ??
|
||||
defaultFileInputOptions.isMultipleAllowed
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (!e.currentTarget.files) return
|
||||
onNewFiles(e.currentTarget.files)
|
||||
@@ -185,19 +193,19 @@ export const FileUploadForm = (props: Props) => {
|
||||
<Show
|
||||
when={
|
||||
selectedFiles().length === 0 &&
|
||||
props.block.options.isRequired === false
|
||||
props.block.options?.isRequired === false
|
||||
}
|
||||
>
|
||||
<div class="flex justify-end">
|
||||
<Button on:click={skip}>
|
||||
{props.block.options.labels.skip ??
|
||||
{props.block.options?.labels?.skip ??
|
||||
defaultFileInputOptions.labels.skip}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
<Show
|
||||
when={
|
||||
props.block.options.isMultipleAllowed &&
|
||||
props.block.options?.isMultipleAllowed &&
|
||||
selectedFiles().length > 0 &&
|
||||
!isUploading()
|
||||
}
|
||||
@@ -206,17 +214,17 @@ export const FileUploadForm = (props: Props) => {
|
||||
<div class="flex gap-2">
|
||||
<Show when={selectedFiles().length}>
|
||||
<Button variant="secondary" on:click={clearFiles}>
|
||||
{props.block.options.labels.clear ??
|
||||
{props.block.options?.labels?.clear ??
|
||||
defaultFileInputOptions.labels.clear}
|
||||
</Button>
|
||||
</Show>
|
||||
<SendButton type="submit" disableIcon>
|
||||
{props.block.options.labels.button ===
|
||||
{props.block.options?.labels?.button ===
|
||||
defaultFileInputOptions.labels.button
|
||||
? `Upload ${selectedFiles().length} file${
|
||||
selectedFiles().length > 1 ? 's' : ''
|
||||
}`
|
||||
: props.block.options.labels.button}
|
||||
: props.block.options?.labels?.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { NumberInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { numberInputHelper } from '../numberInputHelper'
|
||||
import { defaultNumberInputOptions } from '@typebot.io/schemas/features/blocks/inputs/number/constants'
|
||||
|
||||
type NumberInputProps = {
|
||||
block: NumberInputBlock
|
||||
@@ -67,7 +68,8 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
// eslint-disable-next-line solid/jsx-no-undef
|
||||
use:bindValue
|
||||
placeholder={
|
||||
props.block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultNumberInputOptions.labels.placeholder
|
||||
}
|
||||
onInput={(e) => {
|
||||
setInputValue(targetValue(e.currentTarget))
|
||||
@@ -83,7 +85,8 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
class="my-2 ml-2"
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
{props.block.options?.labels?.button ??
|
||||
defaultNumberInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { BotContext } from '@/types'
|
||||
import type { PaymentInputOptions, RuntimeOptions } from '@typebot.io/schemas'
|
||||
import { PaymentProvider } from '@typebot.io/schemas/features/blocks/inputs/payment/enums'
|
||||
import { Match, Switch } from 'solid-js'
|
||||
import { StripePaymentForm } from './StripePaymentForm'
|
||||
import { PaymentInputBlock, RuntimeOptions } from '@typebot.io/schemas'
|
||||
|
||||
type Props = {
|
||||
context: BotContext
|
||||
options: PaymentInputOptions & RuntimeOptions
|
||||
options: PaymentInputBlock['options'] & RuntimeOptions
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
export const PaymentForm = (props: Props) => (
|
||||
<Switch>
|
||||
<Match when={props.options.provider === PaymentProvider.STRIPE}>
|
||||
<StripePaymentForm
|
||||
onSuccess={props.onSuccess}
|
||||
options={props.options}
|
||||
context={props.context}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
<StripePaymentForm
|
||||
onSuccess={props.onSuccess}
|
||||
options={props.options}
|
||||
context={props.context}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -2,16 +2,17 @@ import { SendButton } from '@/components/SendButton'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
import type { Stripe, StripeElements } from '@stripe/stripe-js'
|
||||
import { BotContext } from '@/types'
|
||||
import type { PaymentInputOptions, RuntimeOptions } from '@typebot.io/schemas'
|
||||
import type { PaymentInputBlock, RuntimeOptions } from '@typebot.io/schemas'
|
||||
import { loadStripe } from '@/lib/stripe'
|
||||
import {
|
||||
removePaymentInProgressFromStorage,
|
||||
setPaymentInProgressInStorage,
|
||||
} from '../helpers/paymentInProgressStorage'
|
||||
import { defaultPaymentInputOptions } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
|
||||
|
||||
type Props = {
|
||||
context: BotContext
|
||||
options: PaymentInputOptions & RuntimeOptions
|
||||
options: PaymentInputBlock['options'] & RuntimeOptions
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
@@ -28,7 +29,9 @@ export const StripePaymentForm = (props: Props) => {
|
||||
|
||||
onMount(async () => {
|
||||
initShadowMountPoint(paymentElementSlot)
|
||||
stripe = await loadStripe(props.options.publicKey)
|
||||
if (!props.options?.publicKey)
|
||||
return setMessage('Missing Stripe public key')
|
||||
stripe = await loadStripe(props.options?.publicKey)
|
||||
if (!stripe) return
|
||||
elements = stripe.elements({
|
||||
appearance: {
|
||||
@@ -60,16 +63,16 @@ export const StripePaymentForm = (props: Props) => {
|
||||
typebot: props.context.typebot,
|
||||
})
|
||||
const { postalCode, ...address } =
|
||||
props.options.additionalInformation?.address ?? {}
|
||||
props.options?.additionalInformation?.address ?? {}
|
||||
const { error, paymentIntent } = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: window.location.href,
|
||||
payment_method_data: {
|
||||
billing_details: {
|
||||
name: props.options.additionalInformation?.name,
|
||||
email: props.options.additionalInformation?.email,
|
||||
phone: props.options.additionalInformation?.phoneNumber,
|
||||
name: props.options?.additionalInformation?.name,
|
||||
email: props.options?.additionalInformation?.email,
|
||||
phone: props.options?.additionalInformation?.phoneNumber,
|
||||
address: {
|
||||
...address,
|
||||
postal_code: postalCode,
|
||||
@@ -100,7 +103,9 @@ export const StripePaymentForm = (props: Props) => {
|
||||
class="mt-4 w-full max-w-lg animate-fade-in"
|
||||
disableIcon
|
||||
>
|
||||
{props.options.labels.button} {props.options.amountLabel}
|
||||
{props.options?.labels?.button ??
|
||||
defaultPaymentInputOptions.labels.button}{' '}
|
||||
{props.options?.amountLabel}
|
||||
</SendButton>
|
||||
</Show>
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@ import { ChevronDownIcon } from '@/components/icons/ChevronDownIcon'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { PhoneNumberInputOptions } from '@typebot.io/schemas'
|
||||
import { createSignal, For, onCleanup, onMount } from 'solid-js'
|
||||
import { isEmpty } from '@typebot.io/lib'
|
||||
import { phoneCountries } from '@typebot.io/lib/phoneCountries'
|
||||
import { CommandData } from '@/features/commands/types'
|
||||
import { PhoneNumberInputBlock } from '@typebot.io/schemas'
|
||||
import { defaultPhoneInputOptions } from '@typebot.io/schemas/features/blocks/inputs/phone/constants'
|
||||
|
||||
type PhoneInputProps = Pick<
|
||||
PhoneNumberInputOptions,
|
||||
NonNullable<PhoneNumberInputBlock['options']>,
|
||||
'labels' | 'defaultCountryCode'
|
||||
> & {
|
||||
defaultValue?: string
|
||||
@@ -146,7 +147,10 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
ref={inputRef}
|
||||
value={inputValue()}
|
||||
onInput={handleInput}
|
||||
placeholder={props.labels.placeholder ?? 'Your phone number...'}
|
||||
placeholder={
|
||||
props.labels?.placeholder ??
|
||||
defaultPhoneInputOptions.labels.placeholder
|
||||
}
|
||||
autofocus={!isMobile()}
|
||||
/>
|
||||
</div>
|
||||
@@ -157,7 +161,7 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
class="my-2 ml-2"
|
||||
on:click={submit}
|
||||
>
|
||||
{props.labels?.button ?? 'Send'}
|
||||
{props.labels?.button ?? defaultPhoneInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import {
|
||||
PictureChoiceBlock,
|
||||
defaultPictureChoiceOptions,
|
||||
} from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
|
||||
import { For, Show, createSignal, onMount } from 'solid-js'
|
||||
import { Checkbox } from '../buttons/components/Checkbox'
|
||||
import { SendButton } from '@/components'
|
||||
import { isDefined, isEmpty, isSvgSrc } from '@typebot.io/lib'
|
||||
import { SearchInput } from '@/components/inputs/SearchInput'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice/constants'
|
||||
|
||||
type Props = {
|
||||
defaultItems: PictureChoiceBlock['items']
|
||||
@@ -68,13 +66,13 @@ export const MultiplePictureChoice = (props: Props) => {
|
||||
|
||||
return (
|
||||
<form class="flex flex-col gap-2 w-full items-end" onSubmit={handleSubmit}>
|
||||
<Show when={props.options.isSearchable}>
|
||||
<Show when={props.options?.isSearchable}>
|
||||
<div class="flex items-end typebot-input w-full">
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
onInput={filterItems}
|
||||
placeholder={
|
||||
props.options.searchInputPlaceholder ??
|
||||
props.options?.searchInputPlaceholder ??
|
||||
defaultPictureChoiceOptions.searchInputPlaceholder
|
||||
}
|
||||
onClear={() => setFilteredItems(props.defaultItems)}
|
||||
@@ -84,7 +82,7 @@ export const MultiplePictureChoice = (props: Props) => {
|
||||
<div
|
||||
class={
|
||||
'flex flex-wrap justify-end gap-2' +
|
||||
(props.options.isSearchable
|
||||
(props.options?.isSearchable
|
||||
? ' overflow-y-scroll max-h-[464px] rounded-md hide-scrollbar'
|
||||
: '')
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ export const SinglePictureChoice = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<Show when={props.options.isSearchable}>
|
||||
<Show when={props.options?.isSearchable}>
|
||||
<div class="flex items-end typebot-input w-full">
|
||||
<SearchInput
|
||||
ref={inputRef}
|
||||
onInput={filterItems}
|
||||
placeholder={props.options.searchInputPlaceholder ?? ''}
|
||||
placeholder={props.options?.searchInputPlaceholder ?? ''}
|
||||
onClear={() => setFilteredItems(props.defaultItems)}
|
||||
/>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@ export const SinglePictureChoice = (props: Props) => {
|
||||
<div
|
||||
class={
|
||||
'gap-2 flex flex-wrap justify-end' +
|
||||
(props.options.isSearchable
|
||||
(props.options?.isSearchable
|
||||
? ' overflow-y-scroll max-h-[464px] rounded-md hide-scrollbar'
|
||||
: '')
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import type { RatingInputBlock, RatingInputOptions } from '@typebot.io/schemas'
|
||||
import type { RatingInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, For, Match, Switch } from 'solid-js'
|
||||
import { isDefined, isEmpty, isNotDefined } from '@typebot.io/lib'
|
||||
import { Button } from '@/components/Button'
|
||||
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
|
||||
|
||||
type Props = {
|
||||
block: RatingInputBlock
|
||||
@@ -24,14 +25,14 @@ export const RatingForm = (props: Props) => {
|
||||
}
|
||||
|
||||
const handleClick = (rating: number) => {
|
||||
if (props.block.options.isOneClickSubmitEnabled)
|
||||
if (props.block.options?.isOneClickSubmitEnabled)
|
||||
props.onSubmit({ value: rating.toString() })
|
||||
setRating(rating)
|
||||
}
|
||||
|
||||
return (
|
||||
<form class="flex flex-col gap-2" onSubmit={handleSubmit}>
|
||||
{props.block.options.labels.left && (
|
||||
{props.block.options?.labels?.left && (
|
||||
<span class="text-sm w-full rating-label">
|
||||
{props.block.options.labels.left}
|
||||
</span>
|
||||
@@ -40,8 +41,12 @@ export const RatingForm = (props: Props) => {
|
||||
<For
|
||||
each={Array.from(
|
||||
Array(
|
||||
props.block.options.length +
|
||||
(props.block.options.buttonType === 'Numbers' ? 1 : 0)
|
||||
(props.block.options?.length ??
|
||||
defaultRatingInputOptions.length) +
|
||||
((props.block.options?.buttonType ??
|
||||
defaultRatingInputOptions.buttonType) === 'Numbers'
|
||||
? 1
|
||||
: 0)
|
||||
)
|
||||
)}
|
||||
>
|
||||
@@ -50,14 +55,18 @@ export const RatingForm = (props: Props) => {
|
||||
{...props.block.options}
|
||||
rating={rating()}
|
||||
idx={
|
||||
idx() + (props.block.options.buttonType === 'Numbers' ? 0 : 1)
|
||||
idx() +
|
||||
((props.block.options?.buttonType ??
|
||||
defaultRatingInputOptions.buttonType) === 'Numbers'
|
||||
? 0
|
||||
: 1)
|
||||
}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
{props.block.options.labels.right && (
|
||||
{props.block.options?.labels?.right && (
|
||||
<span class="text-sm w-full text-right pr-2 rating-label">
|
||||
{props.block.options.labels.right}
|
||||
</span>
|
||||
@@ -66,7 +75,8 @@ export const RatingForm = (props: Props) => {
|
||||
<div class="flex justify-end">
|
||||
{isDefined(rating()) && (
|
||||
<SendButton disableIcon>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
{props.block.options?.labels?.button ??
|
||||
defaultRatingInputOptions.labels.button}
|
||||
</SendButton>
|
||||
)}
|
||||
</div>
|
||||
@@ -78,7 +88,7 @@ type RatingButtonProps = {
|
||||
rating?: number
|
||||
idx: number
|
||||
onClick: (rating: number) => void
|
||||
} & RatingInputOptions
|
||||
} & RatingInputBlock['options']
|
||||
|
||||
const RatingButton = (props: RatingButtonProps) => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
@@ -87,7 +97,12 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
}
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.buttonType === 'Numbers'}>
|
||||
<Match
|
||||
when={
|
||||
(props.buttonType ?? defaultRatingInputOptions.buttonType) ===
|
||||
'Numbers'
|
||||
}
|
||||
>
|
||||
<Button
|
||||
on:click={handleClick}
|
||||
class={
|
||||
@@ -100,7 +115,12 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
{props.idx}
|
||||
</Button>
|
||||
</Match>
|
||||
<Match when={props.buttonType !== 'Numbers'}>
|
||||
<Match
|
||||
when={
|
||||
(props.buttonType ?? defaultRatingInputOptions.buttonType) !==
|
||||
'Numbers'
|
||||
}
|
||||
>
|
||||
<div
|
||||
class={
|
||||
'flex justify-center items-center rating-icon-container cursor-pointer ' +
|
||||
@@ -109,7 +129,7 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
: '')
|
||||
}
|
||||
innerHTML={
|
||||
props.customIcon.isEnabled && !isEmpty(props.customIcon.svg)
|
||||
props.customIcon?.isEnabled && !isEmpty(props.customIcon.svg)
|
||||
? props.customIcon.svg
|
||||
: defaultIcon
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { TextInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
|
||||
|
||||
type Props = {
|
||||
block: TextInputBlock
|
||||
@@ -26,12 +27,12 @@ export const TextInput = (props: Props) => {
|
||||
}
|
||||
|
||||
const submitWhenEnter = (e: KeyboardEvent) => {
|
||||
if (props.block.options.isLong) return
|
||||
if (props.block.options?.isLong) return
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
const submitIfCtrlEnter = (e: KeyboardEvent) => {
|
||||
if (!props.block.options.isLong) return
|
||||
if (!props.block.options?.isLong) return
|
||||
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) submit()
|
||||
}
|
||||
|
||||
@@ -55,18 +56,19 @@ export const TextInput = (props: Props) => {
|
||||
class={'flex items-end justify-between pr-2 typebot-input w-full'}
|
||||
data-testid="input"
|
||||
style={{
|
||||
'max-width': props.block.options.isLong ? undefined : '350px',
|
||||
'max-width': props.block.options?.isLong ? undefined : '350px',
|
||||
}}
|
||||
onKeyDown={submitWhenEnter}
|
||||
>
|
||||
{props.block.options.isLong ? (
|
||||
{props.block.options?.isLong ? (
|
||||
<Textarea
|
||||
ref={inputRef as HTMLTextAreaElement}
|
||||
onInput={handleInput}
|
||||
onKeyDown={submitIfCtrlEnter}
|
||||
value={inputValue()}
|
||||
placeholder={
|
||||
props.block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultTextInputOptions.labels.placeholder
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
@@ -75,7 +77,8 @@ export const TextInput = (props: Props) => {
|
||||
onInput={handleInput}
|
||||
value={inputValue()}
|
||||
placeholder={
|
||||
props.block.options?.labels?.placeholder ?? 'Type your answer...'
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultTextInputOptions.labels.placeholder
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -85,7 +88,8 @@ export const TextInput = (props: Props) => {
|
||||
class="my-2 ml-2"
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
{props.block.options?.labels?.button ??
|
||||
defaultTextInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { UrlInputBlock } from '@typebot.io/schemas'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { defaultUrlInputOptions } from '@typebot.io/schemas/features/blocks/inputs/url/constants'
|
||||
|
||||
type Props = {
|
||||
block: UrlInputBlock
|
||||
@@ -63,7 +64,8 @@ export const UrlInput = (props: Props) => {
|
||||
ref={inputRef as HTMLInputElement}
|
||||
value={inputValue()}
|
||||
placeholder={
|
||||
props.block.options?.labels?.placeholder ?? 'Type your URL...'
|
||||
props.block.options?.labels?.placeholder ??
|
||||
defaultUrlInputOptions.labels.placeholder
|
||||
}
|
||||
onInput={handleInput}
|
||||
type="url"
|
||||
@@ -75,7 +77,8 @@ export const UrlInput = (props: Props) => {
|
||||
class="my-2 ml-2"
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
{props.block.options?.labels?.button ??
|
||||
defaultUrlInputOptions.labels.button}
|
||||
</SendButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { sendGaEvent } from '@/lib/gtag'
|
||||
import type { GoogleAnalyticsOptions } from '@typebot.io/schemas'
|
||||
import { GoogleAnalyticsBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const executeGoogleAnalyticsBlock = async (
|
||||
options: GoogleAnalyticsOptions
|
||||
options: GoogleAnalyticsBlock['options']
|
||||
) => {
|
||||
if (!options?.trackingId) return
|
||||
sendGaEvent(options)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { RedirectOptions } from '@typebot.io/schemas'
|
||||
import { RedirectBlock } from '@typebot.io/schemas'
|
||||
|
||||
export const executeRedirect = ({
|
||||
url,
|
||||
isNewTab,
|
||||
}: RedirectOptions): { blockedPopupUrl: string } | undefined => {
|
||||
}: RedirectBlock['options'] = {}): { blockedPopupUrl: string } | undefined => {
|
||||
if (!url) return
|
||||
const updatedWindow = window.open(url, isNewTab ? '_blank' : '_self')
|
||||
if (!updatedWindow)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isDefined, isEmpty } from '@typebot.io/lib/utils'
|
||||
import type { GoogleAnalyticsOptions } from '@typebot.io/schemas'
|
||||
import { GoogleAnalyticsBlock } from '@typebot.io/schemas'
|
||||
|
||||
declare const window: {
|
||||
gtag?: (
|
||||
@@ -39,7 +39,7 @@ export const initGoogleAnalytics = (id: string): Promise<void> => {
|
||||
})
|
||||
}
|
||||
|
||||
export const sendGaEvent = (options: GoogleAnalyticsOptions) => {
|
||||
export const sendGaEvent = (options: GoogleAnalyticsBlock['options']) => {
|
||||
if (!options) return
|
||||
if (!window.gtag) {
|
||||
console.error('Google Analytics was not properly initialized')
|
||||
|
||||
@@ -25,7 +25,7 @@ export const initPixel = (pixelIds: string[]) => {
|
||||
}
|
||||
|
||||
export const trackPixelEvent = (options: PixelBlock['options']) => {
|
||||
if (!options.eventType || !options.pixelId) return
|
||||
if (!options?.eventType || !options.pixelId) return
|
||||
if (!window.fbq) {
|
||||
console.error('Facebook Pixel was not properly initialized')
|
||||
return
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { BotContext, InitialChatReply } from '@/types'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import type { SendMessageInput, StartParams } from '@typebot.io/schemas'
|
||||
import type {
|
||||
SendMessageInput,
|
||||
StartElementId,
|
||||
StartParams,
|
||||
} from '@typebot.io/schemas'
|
||||
import { isNotDefined, isNotEmpty, sendRequest } from '@typebot.io/lib'
|
||||
import {
|
||||
getPaymentInProgressInStorage,
|
||||
@@ -12,13 +16,13 @@ export async function getInitialChatReplyQuery({
|
||||
isPreview,
|
||||
apiHost,
|
||||
prefilledVariables,
|
||||
startGroupId,
|
||||
resultId,
|
||||
stripeRedirectStatus,
|
||||
...props
|
||||
}: StartParams & {
|
||||
stripeRedirectStatus?: string
|
||||
apiHost?: string
|
||||
}) {
|
||||
} & StartElementId) {
|
||||
if (isNotDefined(typebot))
|
||||
throw new Error('Typebot ID is required to get initial messages')
|
||||
|
||||
@@ -40,9 +44,12 @@ export async function getInitialChatReplyQuery({
|
||||
isPreview,
|
||||
typebot,
|
||||
prefilledVariables,
|
||||
startGroupId,
|
||||
resultId,
|
||||
isStreamEnabled: true,
|
||||
startGroupId:
|
||||
'startGroupId' in props ? props.startGroupId : undefined,
|
||||
startEventId:
|
||||
'startEventId' in props ? props.startEventId : undefined,
|
||||
},
|
||||
sessionId: paymentInProgressState?.sessionId,
|
||||
message: paymentInProgressState
|
||||
|
||||
@@ -19,8 +19,6 @@ export const injectStartProps = async (
|
||||
const googleAnalyticsId = startPropsToInject.googleAnalyticsId
|
||||
if (isNotEmpty(googleAnalyticsId))
|
||||
await initGoogleAnalytics(googleAnalyticsId)
|
||||
const pixelIds = startPropsToInject.pixelId
|
||||
? [startPropsToInject.pixelId]
|
||||
: startPropsToInject.pixelIds
|
||||
const pixelIds = startPropsToInject.pixelIds
|
||||
if (isDefined(pixelIds)) initPixel(pixelIds)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ import {
|
||||
InputColors,
|
||||
Theme,
|
||||
} from '@typebot.io/schemas'
|
||||
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/enums'
|
||||
import { isLight, hexToRgb } from '@typebot.io/lib/hexToRgb'
|
||||
import { isNotEmpty } from '@typebot.io/lib'
|
||||
import {
|
||||
BackgroundType,
|
||||
defaultTheme,
|
||||
} from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
const cssVariableNames = {
|
||||
general: {
|
||||
@@ -183,7 +186,7 @@ const setTypebotBackground = (
|
||||
}
|
||||
}
|
||||
|
||||
const parseBackgroundValue = ({ type, content }: Background) => {
|
||||
const parseBackgroundValue = ({ type, content }: Background = {}) => {
|
||||
switch (type) {
|
||||
case BackgroundType.NONE:
|
||||
return 'transparent'
|
||||
@@ -191,6 +194,8 @@ const parseBackgroundValue = ({ type, content }: Background) => {
|
||||
return content ?? '#ffffff'
|
||||
case BackgroundType.IMAGE:
|
||||
return `url(${content})`
|
||||
case undefined:
|
||||
return defaultTheme.general.background.content
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @typebot.io/nextjs
|
||||
npm install @typebot.io/js @typebot.io/nextjs
|
||||
```
|
||||
|
||||
## Standard
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"@babel/preset-typescript": "7.22.5",
|
||||
"@rollup/plugin-alias": "5.0.0",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-terser": "0.4.3",
|
||||
@@ -33,11 +32,10 @@
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"react": "18.2.0",
|
||||
"rollup": "3.26.2",
|
||||
"rollup-plugin-dts": "6.1.0",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tslib": "2.6.0",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.1.6"
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "12.x || 13.x",
|
||||
|
||||
@@ -3,8 +3,6 @@ import terser from '@rollup/plugin-terser'
|
||||
import { babel } from '@rollup/plugin-babel'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import { typescriptPaths } from 'rollup-plugin-typescript-paths'
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import { dts } from 'rollup-plugin-dts'
|
||||
|
||||
const extensions = ['.ts', '.tsx']
|
||||
|
||||
@@ -16,11 +14,6 @@ const indexConfig = {
|
||||
},
|
||||
external: ['next/dynamic', 'react', 'react/jsx-runtime'],
|
||||
plugins: [
|
||||
alias({
|
||||
entries: [
|
||||
{ find: '@typebot.io/js/dist/web', replacement: '../../js/dist/web' },
|
||||
],
|
||||
}),
|
||||
resolve({ extensions }),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
@@ -34,23 +27,6 @@ const indexConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
const typesConfig = {
|
||||
input: './src/index.ts',
|
||||
output: [{ file: './dist/index.d.ts', format: 'es' }],
|
||||
plugins: [
|
||||
alias({
|
||||
entries: [
|
||||
{ find: '@typebot.io/js', replacement: '../../js' },
|
||||
{
|
||||
find: '@/types',
|
||||
replacement: '../types',
|
||||
},
|
||||
],
|
||||
}),
|
||||
dts(),
|
||||
],
|
||||
}
|
||||
|
||||
const configs = [indexConfig, typesConfig]
|
||||
const configs = [indexConfig]
|
||||
|
||||
export default configs
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noEmit": false,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @typebot.io/react
|
||||
npm install @typebot.io/js @typebot.io/react
|
||||
```
|
||||
|
||||
## Standard
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "7.22.5",
|
||||
"@babel/preset-typescript": "7.22.5",
|
||||
"@rollup/plugin-alias": "5.0.0",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-terser": "0.4.3",
|
||||
@@ -37,11 +36,10 @@
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"react": "18.2.0",
|
||||
"rollup": "3.26.2",
|
||||
"rollup-plugin-dts": "6.1.0",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"tslib": "2.6.0",
|
||||
"tsx": "3.12.7",
|
||||
"typescript": "5.1.6"
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "18.x"
|
||||
|
||||
@@ -3,8 +3,6 @@ import terser from '@rollup/plugin-terser'
|
||||
import { babel } from '@rollup/plugin-babel'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import { typescriptPaths } from 'rollup-plugin-typescript-paths'
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import { dts } from 'rollup-plugin-dts'
|
||||
|
||||
const extensions = ['.ts', '.tsx']
|
||||
|
||||
@@ -16,9 +14,6 @@ const indexConfig = {
|
||||
},
|
||||
external: ['react', 'react/jsx-runtime'],
|
||||
plugins: [
|
||||
alias({
|
||||
entries: [{ find: '@typebot.io/js', replacement: '../../js' }],
|
||||
}),
|
||||
resolve({ extensions }),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
@@ -32,23 +27,6 @@ const indexConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
const typesConfig = {
|
||||
input: './src/index.ts',
|
||||
output: [{ file: './dist/index.d.ts', format: 'es' }],
|
||||
plugins: [
|
||||
alias({
|
||||
entries: [
|
||||
{ find: '@typebot.io/js', replacement: '../../js' },
|
||||
{
|
||||
find: '@/types',
|
||||
replacement: '../types',
|
||||
},
|
||||
],
|
||||
}),
|
||||
dts(),
|
||||
],
|
||||
}
|
||||
|
||||
const configs = [indexConfig, typesConfig]
|
||||
const configs = [indexConfig]
|
||||
|
||||
export default configs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { Standard } from './Standard'
|
||||
export { Bubble } from './Bubble'
|
||||
export { Popup } from './Popup'
|
||||
export * from '@typebot.io/js'
|
||||
// export { Bubble } from './Bubble'
|
||||
// export { Popup } from './Popup'
|
||||
// export * from '@typebot.io/js'
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { StartTypebot } from '@typebot.io/schemas'
|
||||
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
||||
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
|
||||
import {
|
||||
BackgroundType,
|
||||
BubbleBlockType,
|
||||
ComparisonOperators,
|
||||
InputBlockType,
|
||||
ItemType,
|
||||
LogicalOperator,
|
||||
LogicBlockType,
|
||||
StartTypebot,
|
||||
} from '@typebot.io/schemas'
|
||||
} from '@typebot.io/schemas/features/blocks/logic/condition/constants'
|
||||
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
|
||||
import { BackgroundType } from '@typebot.io/schemas/features/typebot/theme/constants'
|
||||
|
||||
export const leadGenerationTypebot: StartTypebot = {
|
||||
version: null,
|
||||
version: '3',
|
||||
id: 'clckrl4q5000t3b6sabwokaar',
|
||||
events: null,
|
||||
groups: [
|
||||
{
|
||||
id: 'clckrl4q5000g3b6skizhd262',
|
||||
@@ -21,7 +21,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
id: '22HP69iipkLjJDTUcc1AWW',
|
||||
type: 'start',
|
||||
label: 'Start',
|
||||
groupId: 'clckrl4q5000g3b6skizhd262',
|
||||
outgoingEdgeId: 'clckrlxp400173b6sktq7gqm2',
|
||||
},
|
||||
],
|
||||
@@ -48,7 +47,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: 'Welcome to AA (Awesome Agency)',
|
||||
},
|
||||
groupId: 'clckrl4q5000h3b6sjipn4qga',
|
||||
},
|
||||
{
|
||||
id: 's7YqZTBeyCa4Hp3wN2j922c',
|
||||
@@ -56,7 +54,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
content: {
|
||||
url: 'https://media2.giphy.com/media/XD9o33QG9BoMis7iM4/giphy.gif?cid=fe3852a3ihg8rvipzzky5lybmdyq38fhke2tkrnshwk52c7d&rid=giphy.gif&ct=g',
|
||||
},
|
||||
groupId: 'clckrl4q5000h3b6sjipn4qga',
|
||||
},
|
||||
{
|
||||
id: 'sbjZWLJGVkHAkDqS4JQeGow',
|
||||
@@ -64,12 +61,10 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
items: [
|
||||
{
|
||||
id: 'hQw2zbp7FDX7XYK9cFpbgC',
|
||||
type: 0,
|
||||
blockId: 'sbjZWLJGVkHAkDqS4JQeGow',
|
||||
content: 'Hi!',
|
||||
},
|
||||
],
|
||||
groupId: 'clckrl4q5000h3b6sjipn4qga',
|
||||
options: { buttonLabel: 'Send', isMultipleChoice: false },
|
||||
outgoingEdgeId: 'clckrm7td001b3b6s2769fh7k',
|
||||
},
|
||||
@@ -93,7 +88,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: 'Great! Nice to meet you {{Name}}',
|
||||
},
|
||||
groupId: 'clckrl4q5000i3b6stgxyvscq',
|
||||
},
|
||||
{
|
||||
id: 'scQ5kduafAtfP9T8SHUJnGi',
|
||||
@@ -110,12 +104,10 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: "What's the best email we can reach you at?",
|
||||
},
|
||||
groupId: 'clckrl4q5000i3b6stgxyvscq',
|
||||
},
|
||||
{
|
||||
id: 'snbsad18Bgry8yZ8DZCfdFD',
|
||||
type: InputBlockType.EMAIL,
|
||||
groupId: 'clckrl4q5000i3b6stgxyvscq',
|
||||
options: {
|
||||
labels: { button: 'Send', placeholder: 'Type your email...' },
|
||||
variableId: 'v3VFChNVSCXQ2rXv4DrJ8Ah',
|
||||
@@ -141,12 +133,10 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: "What's your name?",
|
||||
},
|
||||
groupId: 'clckrl4q5000j3b6sloirnxza',
|
||||
},
|
||||
{
|
||||
id: 'sqEsMo747LTDnY9FjQcEwUv',
|
||||
type: InputBlockType.TEXT,
|
||||
groupId: 'clckrl4q5000j3b6sloirnxza',
|
||||
options: {
|
||||
isLong: false,
|
||||
labels: {
|
||||
@@ -177,7 +167,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: 'What services are you interested in?',
|
||||
},
|
||||
groupId: 'clckrl4q5000k3b6s0anufmgy',
|
||||
},
|
||||
{
|
||||
id: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
@@ -185,30 +174,25 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
items: [
|
||||
{
|
||||
id: 'fnLCBF4NdraSwcubnBhk8H',
|
||||
type: 0,
|
||||
blockId: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
content: 'Website dev',
|
||||
},
|
||||
{
|
||||
id: 'a782h8ynMouY84QjH7XSnR',
|
||||
type: 0,
|
||||
blockId: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
content: 'Content Marketing',
|
||||
},
|
||||
{
|
||||
id: 'jGvh94zBByvVFpSS3w97zY',
|
||||
type: 0,
|
||||
blockId: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
content: 'Social Media',
|
||||
},
|
||||
{
|
||||
id: '6PRLbKUezuFmwWtLVbvAQ7',
|
||||
type: 0,
|
||||
blockId: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
content: 'UI / UX Design',
|
||||
},
|
||||
],
|
||||
groupId: 'clckrl4q5000k3b6s0anufmgy',
|
||||
options: { buttonLabel: 'Send', isMultipleChoice: true },
|
||||
outgoingEdgeId: 'clckrl4q5000r3b6s9yxsuxu7',
|
||||
},
|
||||
@@ -234,12 +218,10 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: 'Can you tell me a bit more about your needs?',
|
||||
},
|
||||
groupId: 'clckrl4q5000l3b6scn1r1nns',
|
||||
},
|
||||
{
|
||||
id: 'sqFy2G3C1mh9p6s3QBdSS5x',
|
||||
type: InputBlockType.TEXT,
|
||||
groupId: 'clckrl4q5000l3b6scn1r1nns',
|
||||
options: {
|
||||
isLong: true,
|
||||
labels: { button: 'Send', placeholder: 'Type your answer...' },
|
||||
@@ -261,7 +243,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
richText: [{ type: 'p', children: [{ text: 'Perfect!' }] }],
|
||||
plainText: 'Perfect!',
|
||||
},
|
||||
groupId: 'clckrl4q5000m3b6srabr5a2s',
|
||||
},
|
||||
{
|
||||
id: 's779Q1y51aVaDUJVrFb16vv',
|
||||
@@ -276,7 +257,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
],
|
||||
plainText: "We'll get back to you at {{Email}}",
|
||||
},
|
||||
groupId: 'clckrl4q5000m3b6srabr5a2s',
|
||||
},
|
||||
],
|
||||
graphCoordinates: { x: 1668, y: 143 },
|
||||
@@ -288,13 +268,11 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
blocks: [
|
||||
{
|
||||
id: 'clckrlksq00103b6s3exi90al',
|
||||
groupId: 'clckrlksq000z3b6sequnj9m3',
|
||||
type: LogicBlockType.CONDITION,
|
||||
items: [
|
||||
{
|
||||
id: 'clckrlksq00113b6sz8naxdwx',
|
||||
blockId: 'clckrlksq00103b6s3exi90al',
|
||||
type: ItemType.CONDITION,
|
||||
content: {
|
||||
comparisons: [
|
||||
{
|
||||
@@ -319,13 +297,11 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
blocks: [
|
||||
{
|
||||
id: 'clckrm1zr00193b6szpz37plc',
|
||||
groupId: 'clckrm1zq00183b6sz8ydapth',
|
||||
type: LogicBlockType.CONDITION,
|
||||
items: [
|
||||
{
|
||||
id: 'clckrm1zr001a3b6s1hlfm2jh',
|
||||
blockId: 'clckrm1zr00193b6szpz37plc',
|
||||
type: ItemType.CONDITION,
|
||||
content: {
|
||||
comparisons: [
|
||||
{
|
||||
@@ -350,7 +326,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
blocks: [
|
||||
{
|
||||
id: 'clckrl870000y3b6sxyd24qwc',
|
||||
groupId: 'clckrlqil00133b6sk6msgqt1',
|
||||
type: BubbleBlockType.TEXT,
|
||||
content: {
|
||||
html: '<div>Hi {{Name}}!</div>',
|
||||
@@ -372,7 +347,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
to: { groupId: 'clckrl4q5000i3b6stgxyvscq' },
|
||||
from: {
|
||||
blockId: 'sqEsMo747LTDnY9FjQcEwUv',
|
||||
groupId: 'clckrl4q5000j3b6sloirnxza',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -380,7 +354,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
to: { groupId: 'clckrl4q5000k3b6s0anufmgy' },
|
||||
from: {
|
||||
blockId: 'snbsad18Bgry8yZ8DZCfdFD',
|
||||
groupId: 'clckrl4q5000i3b6stgxyvscq',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -388,7 +361,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
to: { groupId: 'clckrl4q5000l3b6scn1r1nns' },
|
||||
from: {
|
||||
blockId: 's5VQGsVF4hQgziQsXVdwPDW',
|
||||
groupId: 'clckrl4q5000k3b6s0anufmgy',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -396,12 +368,10 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
to: { groupId: 'clckrl4q5000m3b6srabr5a2s' },
|
||||
from: {
|
||||
blockId: 'sqFy2G3C1mh9p6s3QBdSS5x',
|
||||
groupId: 'clckrl4q5000l3b6scn1r1nns',
|
||||
},
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrlksq000z3b6sequnj9m3',
|
||||
blockId: 'clckrlksq00103b6s3exi90al',
|
||||
itemId: 'clckrlksq00113b6sz8naxdwx',
|
||||
},
|
||||
@@ -410,7 +380,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrlksq000z3b6sequnj9m3',
|
||||
blockId: 'clckrlksq00103b6s3exi90al',
|
||||
},
|
||||
to: { groupId: 'clckrl4q5000h3b6sjipn4qga' },
|
||||
@@ -418,7 +387,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrlqil00133b6sk6msgqt1',
|
||||
blockId: 'clckrl870000y3b6sxyd24qwc',
|
||||
},
|
||||
to: { groupId: 'clckrl4q5000h3b6sjipn4qga' },
|
||||
@@ -426,7 +394,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrl4q5000g3b6skizhd262',
|
||||
blockId: '22HP69iipkLjJDTUcc1AWW',
|
||||
},
|
||||
to: { groupId: 'clckrlksq000z3b6sequnj9m3' },
|
||||
@@ -434,7 +401,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrl4q5000h3b6sjipn4qga',
|
||||
blockId: 'sbjZWLJGVkHAkDqS4JQeGow',
|
||||
},
|
||||
to: { groupId: 'clckrm1zq00183b6sz8ydapth' },
|
||||
@@ -442,7 +408,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrm1zq00183b6sz8ydapth',
|
||||
blockId: 'clckrm1zr00193b6szpz37plc',
|
||||
itemId: 'clckrm1zr001a3b6s1hlfm2jh',
|
||||
},
|
||||
@@ -451,7 +416,6 @@ export const leadGenerationTypebot: StartTypebot = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
groupId: 'clckrm1zq00183b6sz8ydapth',
|
||||
blockId: 'clckrm1zr00193b6szpz37plc',
|
||||
},
|
||||
to: { groupId: 'clckrl4q5000j3b6sloirnxza' },
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"noEmit": false,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user