⚡ (engine) Improve engine overall robustness
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { DateInputOptions } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { parseReadableDate } from '../utils/parseReadableDate'
|
||||
|
||||
type DateInputProps = {
|
||||
|
@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||
<title>Solid App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script src="/src/demo/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
@ -2,20 +2,18 @@
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.0.1",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"main": "dist/index.mjs",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"start:demo": "vite",
|
||||
"dev:demo": "vite",
|
||||
"dev": "rollup --watch --config rollup.config.mjs",
|
||||
"build": "rollup --config rollup.config.mjs",
|
||||
"dev": "rollup --watch --config rollup.config.js",
|
||||
"build": "rollup --config rollup.config.js && rm -rf dist/dts",
|
||||
"lint": "eslint --fix \"src/**/*.ts*\""
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stripe/stripe-js": "1.46.0",
|
||||
"models": "workspace:*",
|
||||
"phone": "3.1.32",
|
||||
"solid-element": "1.6.3",
|
||||
"solid-js": "1.6.9",
|
||||
"utils": "workspace:*"
|
||||
@ -23,9 +21,8 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
"@rollup/plugin-terser": "^0.3.0",
|
||||
"@rollup/plugin-typescript": "11.0.0",
|
||||
"@types/react": "18.0.27",
|
||||
"autoprefixer": "10.4.13",
|
||||
"babel-preset-solid": "1.6.9",
|
||||
"eslint": "8.32.0",
|
||||
@ -37,13 +34,9 @@
|
||||
"rollup-plugin-babel": "4.4.0",
|
||||
"rollup-plugin-dts": "5.1.1",
|
||||
"rollup-plugin-postcss": "4.0.2",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-typescript-paths": "1.4.0",
|
||||
"rollup-plugin-typescript-paths": "^1.4.0",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsup": "6.5.0",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.4",
|
||||
"vite-plugin-solid": "2.5.0"
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,21 @@
|
||||
import resolve from '@rollup/plugin-node-resolve'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import { terser } from 'rollup-plugin-terser'
|
||||
import terser from '@rollup/plugin-terser'
|
||||
import { babel } from '@rollup/plugin-babel'
|
||||
import postcss from 'rollup-plugin-postcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import tailwindcss from 'tailwindcss'
|
||||
import { typescriptPaths } from 'rollup-plugin-typescript-paths'
|
||||
import dts from 'rollup-plugin-dts'
|
||||
import typescript from '@rollup/plugin-typescript'
|
||||
import { typescriptPaths } from 'rollup-plugin-typescript-paths'
|
||||
|
||||
const extensions = ['.ts', '.tsx']
|
||||
|
||||
const webComponentsConfig = {
|
||||
const indexConfig = {
|
||||
input: './src/index.ts',
|
||||
output: {
|
||||
file: 'dist/index.mjs',
|
||||
file: 'dist/index.js',
|
||||
format: 'es',
|
||||
},
|
||||
external: ['models', 'utils', 'react'],
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
resolve({ extensions }),
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
@ -44,13 +37,16 @@ const webComponentsConfig = {
|
||||
],
|
||||
}
|
||||
|
||||
const config = [
|
||||
webComponentsConfig,
|
||||
const configs = [
|
||||
indexConfig,
|
||||
{
|
||||
input: './dist/dts/index.d.ts',
|
||||
output: [{ file: 'dist/index.d.ts', format: 'es' }],
|
||||
plugins: [dts()],
|
||||
...indexConfig,
|
||||
input: './src/web.ts',
|
||||
output: {
|
||||
file: 'dist/web.js',
|
||||
format: 'es',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default config
|
||||
export default configs
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@ -1,10 +1,9 @@
|
||||
import { LiteBadge } from './LiteBadge'
|
||||
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { getViewerUrl, injectCustomHeadCode, isEmpty, isNotEmpty } from 'utils'
|
||||
import { injectCustomHeadCode, isNotEmpty } from 'utils'
|
||||
import { getInitialChatReplyQuery } from '@/queries/getInitialChatReplyQuery'
|
||||
import { ConversationContainer } from './ConversationContainer'
|
||||
import css from '../assets/index.css'
|
||||
import { StartParams } from 'models'
|
||||
import type { ChatReply, StartParams } from 'models'
|
||||
import { setIsMobile } from '@/utils/isMobileSignal'
|
||||
import { BotContext, InitialChatReply } from '@/types'
|
||||
import { ErrorMessage } from './ErrorMessage'
|
||||
@ -20,20 +19,19 @@ export type BotProps = StartParams & {
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onInit?: () => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: ChatReply['logs']) => void
|
||||
}
|
||||
|
||||
export const Bot = (props: BotProps) => {
|
||||
export const Bot = (props: BotProps & { class?: string }) => {
|
||||
const [initialChatReply, setInitialChatReply] = createSignal<
|
||||
InitialChatReply | undefined
|
||||
>()
|
||||
const [error, setError] = createSignal<Error | undefined>(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
isEmpty(isEmpty(props.apiHost) ? getViewerUrl() : props.apiHost)
|
||||
? new Error('process.env.NEXT_PUBLIC_VIEWER_URL is missing in env')
|
||||
: undefined
|
||||
)
|
||||
const [customCss, setCustomCss] = createSignal('')
|
||||
const [isInitialized, setIsInitialized] = createSignal(false)
|
||||
const [error, setError] = createSignal<Error | undefined>()
|
||||
|
||||
const initializeBot = async () => {
|
||||
setIsInitialized(true)
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
props.onInit?.()
|
||||
const prefilledVariables: { [key: string]: string } = {}
|
||||
@ -56,37 +54,51 @@ export const Bot = (props: BotProps) => {
|
||||
if (error && 'code' in error && typeof error.code === 'string') {
|
||||
if (['BAD_REQUEST', 'FORBIDDEN'].includes(error.code))
|
||||
setError(new Error('This bot is now closed.'))
|
||||
if (error.code === 'NOT_FOUND') setError(new Error('Typebot not found.'))
|
||||
if (error.code === 'NOT_FOUND')
|
||||
setError(new Error("The bot you're looking for doesn't exist."))
|
||||
return
|
||||
}
|
||||
|
||||
if (!data) return setError(new Error("Couldn't initiate the chat"))
|
||||
if (!data) return setError(new Error("Error! Couldn't initiate the chat."))
|
||||
|
||||
if (data.resultId) setResultInSession(data.resultId)
|
||||
setInitialChatReply(data)
|
||||
setCustomCss(data.typebot.theme.customCss ?? '')
|
||||
|
||||
if (data.input?.id && props.onNewInputBlock)
|
||||
props.onNewInputBlock({
|
||||
id: data.input.id,
|
||||
groupId: data.input.groupId,
|
||||
})
|
||||
if (data.logs) props.onNewLogs?.(data.logs)
|
||||
const customHeadCode = data.typebot.settings.metadata.customHeadCode
|
||||
if (customHeadCode) injectCustomHeadCode(customHeadCode)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
createEffect(() => {
|
||||
if (!props.typebot || isInitialized()) return
|
||||
initializeBot().then()
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (typeof props.typebot === 'string') return
|
||||
setCustomCss(props.typebot.theme.customCss ?? '')
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
setIsInitialized(false)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{css}</style>
|
||||
<style>{customCss()}</style>
|
||||
<Show when={error()} keyed>
|
||||
{(error) => <ErrorMessage error={error} />}
|
||||
</Show>
|
||||
<Show when={initialChatReply()} keyed>
|
||||
{(initialChatReply) => (
|
||||
<BotContent
|
||||
class={props.class}
|
||||
initialChatReply={{
|
||||
...initialChatReply,
|
||||
typebot: {
|
||||
@ -103,11 +115,13 @@ export const Bot = (props: BotProps) => {
|
||||
}}
|
||||
context={{
|
||||
apiHost: props.apiHost,
|
||||
isPreview: props.isPreview ?? false,
|
||||
isPreview:
|
||||
typeof props.typebot !== 'string' || (props.isPreview ?? false),
|
||||
typebotId: initialChatReply.typebot.id,
|
||||
resultId: initialChatReply.resultId,
|
||||
}}
|
||||
onNewInputBlock={props.onNewInputBlock}
|
||||
onNewLogs={props.onNewLogs}
|
||||
onAnswer={props.onAnswer}
|
||||
onEnd={props.onEnd}
|
||||
/>
|
||||
@ -120,9 +134,11 @@ export const Bot = (props: BotProps) => {
|
||||
type BotContentProps = {
|
||||
initialChatReply: InitialChatReply
|
||||
context: BotContext
|
||||
onNewInputBlock?: (ids: { id: string; groupId: string }) => void
|
||||
class?: string
|
||||
onNewInputBlock?: (block: { id: string; groupId: string }) => void
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: ChatReply['logs']) => void
|
||||
}
|
||||
|
||||
const BotContent = (props: BotContentProps) => {
|
||||
@ -160,7 +176,10 @@ const BotContent = (props: BotContentProps) => {
|
||||
return (
|
||||
<div
|
||||
ref={botContainer}
|
||||
class="relative flex w-full h-full text-base overflow-hidden bg-cover flex-col items-center typebot-container"
|
||||
class={
|
||||
'relative flex w-full h-full text-base overflow-hidden bg-cover flex-col items-center typebot-container ' +
|
||||
props.class
|
||||
}
|
||||
>
|
||||
<div class="flex w-full h-full justify-center">
|
||||
<ConversationContainer
|
||||
@ -169,6 +188,7 @@ const BotContent = (props: BotContentProps) => {
|
||||
onNewInputBlock={props.onNewInputBlock}
|
||||
onAnswer={props.onAnswer}
|
||||
onEnd={props.onEnd}
|
||||
onNewLogs={props.onNewLogs}
|
||||
/>
|
||||
</div>
|
||||
<Show
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { Avatar } from '@/components/avatars/Avatar'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { Avatar } from '../avatars/Avatar'
|
||||
|
||||
type Props = { hostAvatarSrc?: string }
|
||||
|
||||
@ -42,7 +42,7 @@ export const AvatarSideContainer = (props: Props) => {
|
||||
transition: 'top 350ms ease-out',
|
||||
}}
|
||||
>
|
||||
<Avatar avatarSrc={props.hostAvatarSrc} />
|
||||
<Avatar initialAvatarSrc={props.hostAvatarSrc} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BotContext } from '@/types'
|
||||
import { ChatReply, Settings, Theme } from 'models'
|
||||
import { createSignal, For, Show } from 'solid-js'
|
||||
import type { ChatReply, Settings, Theme } from 'models'
|
||||
import { createSignal, For, onMount, Show } from 'solid-js'
|
||||
import { HostBubble } from '../bubbles/HostBubble'
|
||||
import { InputChatBlock } from '../InputChatBlock'
|
||||
import { AvatarSideContainer } from './AvatarSideContainer'
|
||||
@ -19,6 +19,10 @@ type Props = Pick<ChatReply, 'messages' | 'input'> & {
|
||||
export const ChatChunk = (props: Props) => {
|
||||
const [displayedMessageIndex, setDisplayedMessageIndex] = createSignal(0)
|
||||
|
||||
onMount(() => {
|
||||
props.onScrollToBottom()
|
||||
})
|
||||
|
||||
const displayNextMessage = () => {
|
||||
setDisplayedMessageIndex(
|
||||
displayedMessageIndex() === props.messages.length
|
||||
@ -70,6 +74,9 @@ export const ChatChunk = (props: Props) => {
|
||||
onSkip={props.onSkip}
|
||||
guestAvatar={props.theme.chat.guestAvatar}
|
||||
context={props.context}
|
||||
isInputPrefillEnabled={
|
||||
props.settings.general.isInputPrefillEnabled ?? true
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ChatReply, Theme } from 'models'
|
||||
import { createSignal, For } from 'solid-js'
|
||||
import type { ChatReply, Theme } from 'models'
|
||||
import { createEffect, createSignal, For } from 'solid-js'
|
||||
import { sendMessageQuery } from '@/queries/sendMessageQuery'
|
||||
import { ChatChunk } from './ChatChunk'
|
||||
import { BotContext, InitialChatReply } from '@/types'
|
||||
@ -7,24 +7,26 @@ import { executeIntegrations } from '@/utils/executeIntegrations'
|
||||
import { executeLogic } from '@/utils/executeLogic'
|
||||
|
||||
const parseDynamicTheme = (
|
||||
theme: Theme,
|
||||
initialTheme: Theme,
|
||||
dynamicTheme: ChatReply['dynamicTheme']
|
||||
): Theme => ({
|
||||
...theme,
|
||||
...initialTheme,
|
||||
chat: {
|
||||
...theme.chat,
|
||||
hostAvatar: theme.chat.hostAvatar
|
||||
? {
|
||||
...theme.chat.hostAvatar,
|
||||
url: dynamicTheme?.hostAvatarUrl,
|
||||
}
|
||||
: undefined,
|
||||
guestAvatar: theme.chat.guestAvatar
|
||||
? {
|
||||
...theme.chat.guestAvatar,
|
||||
url: dynamicTheme?.guestAvatarUrl,
|
||||
}
|
||||
: undefined,
|
||||
...initialTheme.chat,
|
||||
hostAvatar:
|
||||
initialTheme.chat.hostAvatar && dynamicTheme?.hostAvatarUrl
|
||||
? {
|
||||
...initialTheme.chat.hostAvatar,
|
||||
url: dynamicTheme.hostAvatarUrl,
|
||||
}
|
||||
: initialTheme.chat.hostAvatar,
|
||||
guestAvatar:
|
||||
initialTheme.chat.guestAvatar && dynamicTheme?.guestAvatarUrl
|
||||
? {
|
||||
...initialTheme.chat.guestAvatar,
|
||||
url: dynamicTheme?.guestAvatarUrl,
|
||||
}
|
||||
: initialTheme.chat.guestAvatar,
|
||||
},
|
||||
})
|
||||
|
||||
@ -34,9 +36,11 @@ type Props = {
|
||||
onNewInputBlock?: (ids: { id: string; groupId: string }) => void
|
||||
onAnswer?: (answer: { message: string; blockId: string }) => void
|
||||
onEnd?: () => void
|
||||
onNewLogs?: (logs: ChatReply['logs']) => void
|
||||
}
|
||||
|
||||
export const ConversationContainer = (props: Props) => {
|
||||
let chatContainer: HTMLDivElement | undefined
|
||||
let bottomSpacer: HTMLDivElement | undefined
|
||||
const [chatChunks, setChatChunks] = createSignal<ChatReply[]>([
|
||||
{
|
||||
@ -44,12 +48,16 @@ export const ConversationContainer = (props: Props) => {
|
||||
messages: props.initialChatReply.messages,
|
||||
},
|
||||
])
|
||||
const [theme, setTheme] = createSignal(
|
||||
parseDynamicTheme(
|
||||
props.initialChatReply.typebot.theme,
|
||||
props.initialChatReply.dynamicTheme
|
||||
const [dynamicTheme, setDynamicTheme] = createSignal<
|
||||
ChatReply['dynamicTheme']
|
||||
>(props.initialChatReply.dynamicTheme)
|
||||
const [theme, setTheme] = createSignal(props.initialChatReply.typebot.theme)
|
||||
|
||||
createEffect(() => {
|
||||
setTheme(
|
||||
parseDynamicTheme(props.initialChatReply.typebot.theme, dynamicTheme())
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const sendMessage = async (message: string) => {
|
||||
const currentBlockId = chatChunks().at(-1)?.input?.id
|
||||
@ -61,7 +69,8 @@ export const ConversationContainer = (props: Props) => {
|
||||
message,
|
||||
})
|
||||
if (!data) return
|
||||
if (data.dynamicTheme) applyDynamicTheme(data.dynamicTheme)
|
||||
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,
|
||||
@ -83,19 +92,18 @@ export const ConversationContainer = (props: Props) => {
|
||||
])
|
||||
}
|
||||
|
||||
const applyDynamicTheme = (dynamicTheme: ChatReply['dynamicTheme']) => {
|
||||
setTheme((theme) => parseDynamicTheme(theme, dynamicTheme))
|
||||
}
|
||||
|
||||
const autoScrollToBottom = () => {
|
||||
if (!bottomSpacer) return
|
||||
setTimeout(() => {
|
||||
bottomSpacer?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, 200)
|
||||
chatContainer?.scrollTo(0, chatContainer.scrollHeight)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="overflow-y-scroll w-full min-h-full rounded px-3 pt-10 relative scrollable-container typebot-chat-view">
|
||||
<div
|
||||
ref={chatContainer}
|
||||
class="overflow-y-scroll w-full min-h-full rounded px-3 pt-10 relative scrollable-container typebot-chat-view scroll-smooth"
|
||||
>
|
||||
<For each={chatChunks()}>
|
||||
{(chatChunk, index) => (
|
||||
<ChatChunk
|
||||
|
@ -4,7 +4,7 @@ type Props = {
|
||||
export const ErrorMessage = (props: Props) => {
|
||||
return (
|
||||
<div class="h-full flex justify-center items-center flex-col">
|
||||
<p class="text-5xl">{props.error.message}</p>
|
||||
<p class="text-2xl text-center">{props.error.message}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import {
|
||||
import type {
|
||||
ChatReply,
|
||||
ChoiceInputBlock,
|
||||
DateInputOptions,
|
||||
EmailInputBlock,
|
||||
FileInputBlock,
|
||||
InputBlockType,
|
||||
NumberInputBlock,
|
||||
PaymentInputOptions,
|
||||
PhoneNumberInputBlock,
|
||||
@ -14,6 +13,7 @@ import {
|
||||
Theme,
|
||||
UrlInputBlock,
|
||||
} from 'models'
|
||||
import { InputBlockType } from 'models/features/blocks/inputs/enums'
|
||||
import { GuestBubble } from './bubbles/GuestBubble'
|
||||
import { BotContext, InputSubmitContent } from '@/types'
|
||||
import { TextInput } from '@/features/blocks/inputs/textInput'
|
||||
@ -35,6 +35,7 @@ type Props = {
|
||||
guestAvatar?: Theme['chat']['guestAvatar']
|
||||
inputIndex: number
|
||||
context: BotContext
|
||||
isInputPrefillEnabled: boolean
|
||||
onSubmit: (answer: string) => void
|
||||
onSkip: () => void
|
||||
}
|
||||
@ -72,6 +73,7 @@ export const InputChatBlock = (props: Props) => {
|
||||
context={props.context}
|
||||
block={props.block}
|
||||
inputIndex={props.inputIndex}
|
||||
isInputPrefillEnabled={props.isInputPrefillEnabled}
|
||||
onSubmit={handleSubmit}
|
||||
onSkip={() => props.onSkip()}
|
||||
hasGuestAvatar={props.guestAvatar?.isEnabled ?? false}
|
||||
@ -87,17 +89,21 @@ const Input = (props: {
|
||||
block: NonNullable<ChatReply['input']>
|
||||
inputIndex: number
|
||||
hasGuestAvatar: boolean
|
||||
isInputPrefillEnabled: boolean
|
||||
onSubmit: (answer: InputSubmitContent) => void
|
||||
onSkip: () => void
|
||||
}) => {
|
||||
const onSubmit = (answer: InputSubmitContent) => props.onSubmit(answer)
|
||||
|
||||
const getPrefilledValue = () =>
|
||||
props.isInputPrefillEnabled ? props.block.prefilledValue : undefined
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.block.type === InputBlockType.TEXT}>
|
||||
<TextInput
|
||||
block={props.block as TextInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
hasGuestAvatar={props.hasGuestAvatar}
|
||||
/>
|
||||
@ -105,7 +111,7 @@ const Input = (props: {
|
||||
<Match when={props.block.type === InputBlockType.NUMBER}>
|
||||
<NumberInput
|
||||
block={props.block as NumberInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
hasGuestAvatar={props.hasGuestAvatar}
|
||||
/>
|
||||
@ -113,7 +119,7 @@ const Input = (props: {
|
||||
<Match when={props.block.type === InputBlockType.EMAIL}>
|
||||
<EmailInput
|
||||
block={props.block as EmailInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
hasGuestAvatar={props.hasGuestAvatar}
|
||||
/>
|
||||
@ -121,7 +127,7 @@ const Input = (props: {
|
||||
<Match when={props.block.type === InputBlockType.URL}>
|
||||
<UrlInput
|
||||
block={props.block as UrlInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
hasGuestAvatar={props.hasGuestAvatar}
|
||||
/>
|
||||
@ -129,7 +135,7 @@ const Input = (props: {
|
||||
<Match when={props.block.type === InputBlockType.PHONE}>
|
||||
<PhoneInput
|
||||
block={props.block as PhoneNumberInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
hasGuestAvatar={props.hasGuestAvatar}
|
||||
/>
|
||||
@ -150,7 +156,7 @@ const Input = (props: {
|
||||
<Match when={props.block.type === InputBlockType.RATING}>
|
||||
<RatingForm
|
||||
block={props.block as RatingInputBlock}
|
||||
defaultValue={props.block.prefilledValue}
|
||||
defaultValue={getPrefilledValue()}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
</Match>
|
||||
|
@ -1,25 +1,29 @@
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { Show } from 'solid-js'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { isNotEmpty } from 'utils'
|
||||
import { DefaultAvatar } from './DefaultAvatar'
|
||||
|
||||
export const Avatar = (props: { avatarSrc?: string }) => (
|
||||
<Show
|
||||
when={isNotEmpty(props.avatarSrc)}
|
||||
keyed
|
||||
fallback={() => <DefaultAvatar />}
|
||||
>
|
||||
<figure
|
||||
class={
|
||||
'flex justify-center items-center rounded-full text-white relative animate-fade-in ' +
|
||||
(isMobile() ? 'w-6 h-6 text-sm' : 'w-10 h-10 text-xl')
|
||||
}
|
||||
export const Avatar = (props: { initialAvatarSrc?: string }) => {
|
||||
const [avatarSrc] = createSignal(props.initialAvatarSrc)
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={isNotEmpty(avatarSrc())}
|
||||
keyed
|
||||
fallback={() => <DefaultAvatar />}
|
||||
>
|
||||
<img
|
||||
src={props.avatarSrc}
|
||||
alt="Bot avatar"
|
||||
class="rounded-full object-cover w-full h-full"
|
||||
/>
|
||||
</figure>
|
||||
</Show>
|
||||
)
|
||||
<figure
|
||||
class={
|
||||
'flex justify-center items-center rounded-full text-white relative animate-fade-in ' +
|
||||
(isMobile() ? 'w-6 h-6 text-sm' : 'w-10 h-10 text-xl')
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={avatarSrc()}
|
||||
alt="Bot avatar"
|
||||
class="rounded-full object-cover w-full h-full"
|
||||
/>
|
||||
</figure>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Show } from 'solid-js'
|
||||
import { isDefined } from 'utils'
|
||||
import { Avatar } from '../avatars/Avatar'
|
||||
|
||||
type Props = {
|
||||
@ -19,8 +18,8 @@ export const GuestBubble = (props: Props) => (
|
||||
>
|
||||
{props.message}
|
||||
</span>
|
||||
<Show when={isDefined(props.avatarSrc)}>
|
||||
<Avatar avatarSrc={props.avatarSrc} />
|
||||
<Show when={props.showAvatar}>
|
||||
<Avatar initialAvatarSrc={props.avatarSrc} />
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
|
@ -3,15 +3,15 @@ import { EmbedBubble } from '@/features/blocks/bubbles/embed'
|
||||
import { ImageBubble } from '@/features/blocks/bubbles/image'
|
||||
import { TextBubble } from '@/features/blocks/bubbles/textBubble'
|
||||
import { VideoBubble } from '@/features/blocks/bubbles/video'
|
||||
import {
|
||||
import type {
|
||||
AudioBubbleContent,
|
||||
BubbleBlockType,
|
||||
ChatMessage,
|
||||
EmbedBubbleContent,
|
||||
ImageBubbleContent,
|
||||
TextBubbleContent,
|
||||
VideoBubbleContent,
|
||||
} from 'models'
|
||||
import { BubbleBlockType } from 'models/features/blocks/bubbles/enums'
|
||||
import { Match, Switch } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
|
3
packages/js/src/components/index.ts
Normal file
3
packages/js/src/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './SendButton'
|
||||
export * from './TypingBubble'
|
||||
export * from './inputs'
|
@ -1,4 +1,6 @@
|
||||
import type { BotProps, PopupProps, BubbleProps } from '@typebot.io/js'
|
||||
import type { BubbleProps } from './features/bubble'
|
||||
import type { PopupProps } from './features/popup'
|
||||
import type { BotProps } from './components/Bot'
|
||||
|
||||
export const defaultBotProps: BotProps = {
|
||||
typebot: '',
|
||||
@ -6,6 +8,7 @@ export const defaultBotProps: BotProps = {
|
||||
onAnswer: undefined,
|
||||
onEnd: undefined,
|
||||
onInit: undefined,
|
||||
onNewLogs: undefined,
|
||||
isPreview: undefined,
|
||||
startGroupId: undefined,
|
||||
prefilledVariables: undefined,
|
@ -1,8 +0,0 @@
|
||||
import { Bot } from '@/components/Bot'
|
||||
import type { Component } from 'solid-js'
|
||||
|
||||
export const App: Component = () => {
|
||||
return (
|
||||
<Bot typebot="clbm11cku000t3b6o01ug8awh" apiHost="http://localhost:3001" />
|
||||
)
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { render } from 'solid-js/web'
|
||||
import { App } from './App'
|
||||
import '../assets/index.css'
|
||||
|
||||
render(() => <App />, document.getElementById('root') as HTMLElement)
|
9
packages/js/src/env.d.ts
vendored
Normal file
9
packages/js/src/env.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
export {}
|
||||
|
||||
declare module 'solid-js' {
|
||||
namespace JSX {
|
||||
interface CustomEvents {
|
||||
click: MouseEvent
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { TypingBubble } from '@/components/bubbles/TypingBubble'
|
||||
import { AudioBubbleContent } from 'models'
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { AudioBubbleContent } from 'models'
|
||||
import { createSignal, onCleanup, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TypingBubble } from '@/components/bubbles/TypingBubble'
|
||||
import { EmbedBubbleContent } from 'models'
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { EmbedBubbleContent } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TypingBubble } from '@/components/bubbles/TypingBubble'
|
||||
import { ImageBubbleContent } from 'models'
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { ImageBubbleContent } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TypingBubble } from '@/components/bubbles/TypingBubble'
|
||||
import { TextBubbleContent, TypingEmulation } from 'models'
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { TextBubbleContent, TypingEmulation } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
import { computeTypingDuration } from '../utils/computeTypingDuration'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TypingEmulation } from 'models'
|
||||
import type { TypingEmulation } from 'models'
|
||||
|
||||
export const computeTypingDuration = (
|
||||
bubbleContent: string,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { TypingBubble } from '@/components/bubbles/TypingBubble'
|
||||
import { VideoBubbleContent, VideoBubbleContentType } from 'models'
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { VideoBubbleContent } from 'models'
|
||||
import { VideoBubbleContentType } from 'models/features/blocks/bubbles/video/enums'
|
||||
import { createSignal, Match, onMount, Switch } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
@ -64,10 +65,7 @@ const VideoContent = (props: VideoContentProps) => {
|
||||
<Match
|
||||
when={
|
||||
props.content?.type &&
|
||||
[
|
||||
VideoBubbleContentType.VIMEO,
|
||||
VideoBubbleContentType.YOUTUBE,
|
||||
].includes(props.content.type)
|
||||
props.content.type === VideoBubbleContentType.URL
|
||||
}
|
||||
>
|
||||
<video
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { ChoiceInputBlock } from 'models'
|
||||
import type { ChoiceInputBlock } from 'models'
|
||||
import { createSignal, For } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
@ -12,8 +12,7 @@ type Props = {
|
||||
export const ChoiceForm = (props: Props) => {
|
||||
const [selectedIndices, setSelectedIndices] = createSignal<number[]>([])
|
||||
|
||||
const handleClick = (itemIndex: number) => (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
const handleClick = (itemIndex: number) => {
|
||||
if (props.block.options?.isMultipleChoice)
|
||||
toggleSelectedItemIndex(itemIndex)
|
||||
else props.onSubmit({ value: props.block.items[itemIndex].content ?? '' })
|
||||
@ -47,7 +46,8 @@ export const ChoiceForm = (props: Props) => {
|
||||
role={
|
||||
props.block.options?.isMultipleChoice ? 'checkbox' : 'button'
|
||||
}
|
||||
onClick={(event) => handleClick(index())(event)}
|
||||
type="button"
|
||||
on:click={() => handleClick(index())}
|
||||
class={
|
||||
'py-2 px-4 text-left font-semibold rounded-md transition-all filter hover:brightness-90 active:brightness-75 duration-100 focus:outline-none typebot-button ' +
|
||||
(selectedIndices().some(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { DateInputOptions } from 'models'
|
||||
import type { DateInputOptions } from 'models'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { parseReadableDate } from '../utils/parseReadableDate'
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { ShortTextInput } from '@/components'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { EmailInputBlock } from 'models'
|
||||
import type { EmailInputBlock } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
@ -59,7 +59,7 @@ export const EmailInput = (props: Props) => {
|
||||
type="button"
|
||||
isDisabled={inputValue() === ''}
|
||||
class="my-2 ml-2"
|
||||
onClick={submit}
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
</SendButton>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { SendButton, Spinner } from '@/components/SendButton'
|
||||
import { BotContext, InputSubmitContent } from '@/types'
|
||||
import { defaultFileInputOptions, FileInputBlock } from 'models'
|
||||
import { FileInputBlock } from 'models'
|
||||
import { defaultFileInputOptions } from 'models/features/blocks/inputs/file'
|
||||
import { createSignal, Match, Show, Switch } from 'solid-js'
|
||||
import { uploadFiles } from 'utils'
|
||||
|
||||
@ -140,7 +141,7 @@ export const FileUploadForm = (props: Props) => {
|
||||
<span class="relative">
|
||||
<FileIcon />
|
||||
<div
|
||||
class="total-files-indicator flex items-center justify-center absolute -right-1 rounded-full px-1 h-4"
|
||||
class="total-files-indicator flex items-center justify-center absolute -right-1 rounded-full px-1 w-4 h-4"
|
||||
style={{ bottom: '5px' }}
|
||||
>
|
||||
{selectedFiles().length}
|
||||
@ -177,7 +178,7 @@ export const FileUploadForm = (props: Props) => {
|
||||
class={
|
||||
'py-2 px-4 justify-center font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 typebot-button '
|
||||
}
|
||||
onClick={() => props.onSkip()}
|
||||
on:click={() => props.onSkip()}
|
||||
>
|
||||
{props.block.options.labels.skip ??
|
||||
defaultFileInputOptions.labels.skip}
|
||||
@ -198,7 +199,7 @@ export const FileUploadForm = (props: Props) => {
|
||||
class={
|
||||
'secondary-button py-2 px-4 justify-center font-semibold rounded-md text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 mr-2'
|
||||
}
|
||||
onClick={clearFiles}
|
||||
on:click={clearFiles}
|
||||
>
|
||||
{props.block.options.labels.clear ??
|
||||
defaultFileInputOptions.labels.clear}
|
||||
@ -233,7 +234,7 @@ const UploadIcon = () => (
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mb-3"
|
||||
class="mb-3 text-gray-500"
|
||||
>
|
||||
<polyline points="16 16 12 12 8 16" />
|
||||
<line x1="12" y1="12" x2="12" y2="21" />
|
||||
@ -244,7 +245,6 @@ const UploadIcon = () => (
|
||||
|
||||
const FileIcon = () => (
|
||||
<svg
|
||||
class="mb-3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
@ -254,6 +254,7 @@ const FileIcon = () => (
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="mb-3 text-gray-500"
|
||||
>
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
|
||||
<polyline points="13 2 13 9 20 9" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { ShortTextInput } from '@/components'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { NumberInputBlock } from 'models'
|
||||
import type { NumberInputBlock } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type NumberInputProps = {
|
||||
@ -62,7 +62,7 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
type="button"
|
||||
isDisabled={inputValue() === ''}
|
||||
class="my-2 ml-2"
|
||||
onClick={submit}
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
</SendButton>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { BotContext } from '@/types'
|
||||
import { PaymentInputOptions, PaymentProvider, RuntimeOptions } from 'models'
|
||||
import type { PaymentInputOptions, RuntimeOptions } from 'models'
|
||||
import { PaymentProvider } from 'models/features/blocks/inputs/payment/enums'
|
||||
import { Match, Switch } from 'solid-js'
|
||||
import { StripePaymentForm } from './StripePaymentForm'
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { createSignal, onMount, Show } from 'solid-js'
|
||||
import { loadStripe } from '@stripe/stripe-js/pure'
|
||||
import type { Stripe, StripeElements } from '@stripe/stripe-js'
|
||||
import { BotContext } from '@/types'
|
||||
import { PaymentInputOptions, RuntimeOptions } from 'models'
|
||||
import type { PaymentInputOptions, RuntimeOptions } from 'models'
|
||||
import { loadStripe } from '@/lib/stripe'
|
||||
|
||||
type Props = {
|
||||
context: BotContext
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { ShortTextInput } from '@/components'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
@ -99,7 +99,7 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
type="button"
|
||||
isDisabled={inputValue() === ''}
|
||||
class="my-2 ml-2"
|
||||
onClick={submit}
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
</SendButton>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { RatingInputBlock, RatingInputOptions } from 'models'
|
||||
import type { RatingInputBlock, RatingInputOptions } from 'models'
|
||||
import { createSignal, For, Match, Switch } from 'solid-js'
|
||||
import { isDefined, isEmpty, isNotDefined } from 'utils'
|
||||
|
||||
@ -84,7 +84,7 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
<Switch>
|
||||
<Match when={props.buttonType === 'Numbers'}>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
props.onClick(props.idx)
|
||||
}}
|
||||
@ -111,7 +111,7 @@ const RatingButton = (props: RatingButtonProps) => {
|
||||
? props.customIcon.svg
|
||||
: defaultIcon
|
||||
}
|
||||
onClick={() => props.onClick(props.idx)}
|
||||
on:click={() => props.onClick(props.idx)}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Textarea, ShortTextInput } from '@/components/inputs'
|
||||
import { Textarea, ShortTextInput } from '@/components'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { TextInputBlock } from 'models'
|
||||
import type { TextInputBlock } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
@ -69,7 +69,7 @@ export const TextInput = (props: Props) => {
|
||||
type="button"
|
||||
isDisabled={inputValue() === ''}
|
||||
class="my-2 ml-2"
|
||||
onClick={submit}
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
</SendButton>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { ShortTextInput } from '@/components'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { UrlInputBlock } from 'models'
|
||||
import type { UrlInputBlock } from 'models'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
@ -65,7 +65,7 @@ export const UrlInput = (props: Props) => {
|
||||
type="button"
|
||||
isDisabled={inputValue() === ''}
|
||||
class="my-2 ml-2"
|
||||
onClick={submit}
|
||||
on:click={submit}
|
||||
>
|
||||
{props.block.options?.labels?.button ?? 'Send'}
|
||||
</SendButton>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { executeCode } from '@/features/blocks/logic/code'
|
||||
import { CodeToExecute } from 'models'
|
||||
import type { CodeToExecute } from 'models'
|
||||
|
||||
export const executeChatwoot = (chatwoot: { codeToExecute: CodeToExecute }) => {
|
||||
executeCode(chatwoot.codeToExecute)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { sendGaEvent } from '@/lib/gtag'
|
||||
import { GoogleAnalyticsOptions } from 'models'
|
||||
import type { GoogleAnalyticsOptions } from 'models'
|
||||
|
||||
export const executeGoogleAnalyticsBlock = async (
|
||||
options: GoogleAnalyticsOptions
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CodeToExecute } from 'models'
|
||||
import type { CodeToExecute } from 'models'
|
||||
|
||||
export const executeCode = async ({ content, args }: CodeToExecute) => {
|
||||
const func = Function(...args.map((arg) => arg.id), content)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RedirectOptions } from 'models'
|
||||
import type { RedirectOptions } from 'models'
|
||||
|
||||
export const executeRedirect = ({ url, isNewTab }: RedirectOptions) => {
|
||||
if (!url) return
|
||||
|
@ -1,11 +1,11 @@
|
||||
import styles from '../../../assets/index.css'
|
||||
import { createSignal, onMount, Show, splitProps, onCleanup } from 'solid-js'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
import { CommandData } from '@/features/commands'
|
||||
import styles from '../../../assets/index.css'
|
||||
import { CommandData } from '../../commands'
|
||||
import { BubbleButton } from './BubbleButton'
|
||||
import { PreviewMessage, PreviewMessageProps } from './PreviewMessage'
|
||||
import { isDefined } from 'utils'
|
||||
import { BubbleParams } from '../types'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
|
||||
export type BubbleProps = BotProps &
|
||||
BubbleParams & {
|
||||
@ -131,7 +131,11 @@ export const Bubble = (props: BubbleProps) => {
|
||||
}
|
||||
>
|
||||
<Show when={isBotStarted()}>
|
||||
<Bot {...botProps} prefilledVariables={prefilledVariables()} />
|
||||
<Bot
|
||||
{...botProps}
|
||||
prefilledVariables={prefilledVariables()}
|
||||
class="rounded-lg"
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Show } from 'solid-js'
|
||||
import { isNotDefined } from 'utils'
|
||||
import { ButtonTheme } from '../types'
|
||||
|
||||
type Props = ButtonTheme & {
|
||||
@ -7,6 +8,7 @@ type Props = ButtonTheme & {
|
||||
}
|
||||
|
||||
const defaultButtonColor = '#0042DA'
|
||||
const defaultIconColor = 'white'
|
||||
|
||||
export const BubbleButton = (props: Props) => {
|
||||
return (
|
||||
@ -20,27 +22,23 @@ export const BubbleButton = (props: Props) => {
|
||||
'background-color': props.backgroundColor ?? defaultButtonColor,
|
||||
}}
|
||||
>
|
||||
<Show when={props.icon?.color} keyed>
|
||||
{(color) => (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
stroke: color,
|
||||
}}
|
||||
class={
|
||||
`w-7 stroke-2 fill-transparent absolute duration-200 transition ` +
|
||||
(props.isBotOpened
|
||||
? 'scale-0 opacity-0'
|
||||
: 'scale-100 opacity-100')
|
||||
}
|
||||
>
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
</svg>
|
||||
)}
|
||||
<Show when={isNotDefined(props.customIconSrc)} keyed>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
stroke: props.iconColor ?? defaultIconColor,
|
||||
}}
|
||||
class={
|
||||
`w-7 stroke-2 fill-transparent absolute duration-200 transition ` +
|
||||
(props.isBotOpened ? 'scale-0 opacity-0' : 'scale-100 opacity-100')
|
||||
}
|
||||
>
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={props.icon?.url}>
|
||||
<Show when={props.customIconSrc}>
|
||||
<img
|
||||
src={props.icon?.url}
|
||||
src={props.customIconSrc}
|
||||
class="w-7 h-7 rounded-full object-cover"
|
||||
alt="Bubble button icon"
|
||||
/>
|
||||
@ -48,7 +46,7 @@ export const BubbleButton = (props: Props) => {
|
||||
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{ fill: props.icon?.color ?? 'white' }}
|
||||
style={{ fill: props.iconColor ?? 'white' }}
|
||||
class={
|
||||
`w-7 absolute duration-200 transition ` +
|
||||
(props.isBotOpened
|
||||
|
@ -10,8 +10,8 @@ export type PreviewMessageProps = Pick<
|
||||
onCloseClick: () => void
|
||||
}
|
||||
|
||||
const defaultFontFamily =
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"
|
||||
const defaultBackgroundColor = '#F7F8FF'
|
||||
const defaultTextColor = '#303235'
|
||||
|
||||
export const PreviewMessage = (props: PreviewMessageProps) => {
|
||||
const [isPreviewMessageHovered, setIsPreviewMessageHovered] =
|
||||
@ -23,11 +23,9 @@ export const PreviewMessage = (props: PreviewMessageProps) => {
|
||||
onClick={props.onClick}
|
||||
class="absolute bottom-20 right-4 w-64 rounded-md duration-200 flex items-center gap-4 shadow-md animate-fade-in cursor-pointer hover:shadow-lg p-4"
|
||||
style={{
|
||||
'font-family':
|
||||
props.previewMessageTheme?.fontFamily ?? defaultFontFamily,
|
||||
'background-color':
|
||||
props.previewMessageTheme?.backgroundColor ?? '#F7F8FF',
|
||||
color: props.previewMessageTheme?.color ?? '#303235',
|
||||
props.previewMessageTheme?.backgroundColor ?? defaultBackgroundColor,
|
||||
color: props.previewMessageTheme?.textColor ?? defaultTextColor,
|
||||
}}
|
||||
onMouseEnter={() => setIsPreviewMessageHovered(true)}
|
||||
onMouseLeave={() => setIsPreviewMessageHovered(false)}
|
||||
@ -43,8 +41,10 @@ export const PreviewMessage = (props: PreviewMessageProps) => {
|
||||
}}
|
||||
style={{
|
||||
'background-color':
|
||||
props.previewMessageTheme?.closeButtonBgColor ?? '#F7F8FF',
|
||||
color: props.previewMessageTheme?.closeButtonColor ?? '#303235',
|
||||
props.previewMessageTheme?.closeButtonBackgroundColor ??
|
||||
defaultBackgroundColor,
|
||||
color:
|
||||
props.previewMessageTheme?.closeButtonIconColor ?? defaultTextColor,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
@ -10,10 +10,8 @@ export type BubbleTheme = {
|
||||
|
||||
export type ButtonTheme = {
|
||||
backgroundColor?: string
|
||||
icon?: {
|
||||
color?: string
|
||||
url?: string
|
||||
}
|
||||
iconColor?: string
|
||||
customIconSrc?: string
|
||||
}
|
||||
|
||||
export type PreviewMessageParams = {
|
||||
@ -24,8 +22,7 @@ export type PreviewMessageParams = {
|
||||
|
||||
export type PreviewMessageTheme = {
|
||||
backgroundColor?: string
|
||||
color?: string
|
||||
fontFamily?: string
|
||||
closeButtonBgColor?: string
|
||||
closeButtonColor?: string
|
||||
textColor?: string
|
||||
closeButtonBackgroundColor?: string
|
||||
closeButtonIconColor?: string
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ import {
|
||||
onCleanup,
|
||||
createEffect,
|
||||
} from 'solid-js'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
import { CommandData } from '@/features/commands'
|
||||
import { isDefined } from 'utils'
|
||||
import { CommandData } from '../../commands'
|
||||
import { isDefined, isNotDefined } from 'utils'
|
||||
import { PopupParams } from '../types'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
|
||||
export type PopupProps = BotProps &
|
||||
PopupParams & {
|
||||
@ -43,8 +43,6 @@ export const Popup = (props: PopupProps) => {
|
||||
)
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('pointerdown', processWindowClick)
|
||||
botContainer?.addEventListener('pointerdown', stopPropagation)
|
||||
window.addEventListener('message', processIncomingEvent)
|
||||
const autoShowDelay = popupProps.autoShowDelay
|
||||
if (isDefined(autoShowDelay)) {
|
||||
@ -54,20 +52,14 @@ export const Popup = (props: PopupProps) => {
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const isOpen = popupProps.isOpen
|
||||
if (isDefined(isOpen)) setIsBotOpened(isOpen)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
document.removeEventListener('pointerdown', processWindowClick)
|
||||
botContainer?.removeEventListener('pointerdown', stopPropagation)
|
||||
window.removeEventListener('message', processIncomingEvent)
|
||||
})
|
||||
|
||||
const processWindowClick = () => {
|
||||
setIsBotOpened(false)
|
||||
}
|
||||
createEffect(() => {
|
||||
if (isNotDefined(props.isOpen) || props.isOpen === isBotOpened()) return
|
||||
toggleBot()
|
||||
})
|
||||
|
||||
const stopPropagation = (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
@ -87,24 +79,28 @@ export const Popup = (props: PopupProps) => {
|
||||
}
|
||||
|
||||
const openBot = () => {
|
||||
if (isBotOpened()) popupProps.onOpen?.()
|
||||
if (isDefined(props.isOpen)) return
|
||||
setIsBotOpened(true)
|
||||
popupProps.onOpen?.()
|
||||
document.body.style.overflow = 'hidden'
|
||||
document.addEventListener('pointerdown', closeBot)
|
||||
botContainer?.addEventListener('pointerdown', stopPropagation)
|
||||
}
|
||||
|
||||
const closeBot = () => {
|
||||
if (isBotOpened()) popupProps.onClose?.()
|
||||
if (isDefined(props.isOpen)) return
|
||||
setIsBotOpened(false)
|
||||
popupProps.onClose?.()
|
||||
document.body.style.overflow = 'auto'
|
||||
document.removeEventListener('pointerdown', closeBot)
|
||||
botContainer?.removeEventListener('pointerdown', stopPropagation)
|
||||
}
|
||||
|
||||
const toggleBot = () => {
|
||||
if (isDefined(props.isOpen)) return
|
||||
isBotOpened() ? closeBot() : openBot()
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={isBotOpened()}>
|
||||
<style>{styles}</style>
|
||||
<div
|
||||
class="relative z-10"
|
||||
aria-labelledby="modal-title"
|
||||
|
@ -2,6 +2,5 @@ export type PopupParams = {
|
||||
autoShowDelay?: number
|
||||
theme?: {
|
||||
width?: string
|
||||
backgroundColor?: string
|
||||
}
|
||||
}
|
||||
|
47
packages/js/src/features/standard/components/Standard.tsx
Normal file
47
packages/js/src/features/standard/components/Standard.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import styles from '../../../assets/index.css'
|
||||
import { Bot, BotProps } from '@/components/Bot'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
|
||||
const hostElementCss = `
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
`
|
||||
|
||||
export const Standard = (props: BotProps) => {
|
||||
const [isBotDisplayed, setIsBotDisplayed] = createSignal(false)
|
||||
|
||||
const launchBot = () => {
|
||||
setIsBotDisplayed(true)
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((intersections) => {
|
||||
if (intersections.some((intersection) => intersection.isIntersecting))
|
||||
launchBot()
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
const standardElement = document.querySelector('typebot-standard')
|
||||
if (!standardElement) return
|
||||
observer.observe(standardElement)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{styles}
|
||||
{hostElementCss}
|
||||
</style>
|
||||
<Show when={isBotDisplayed()}>
|
||||
<Bot {...props} />
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
1
packages/js/src/features/standard/components/index.ts
Normal file
1
packages/js/src/features/standard/components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Standard'
|
1
packages/js/src/features/standard/index.ts
Normal file
1
packages/js/src/features/standard/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './components'
|
@ -1,5 +1,5 @@
|
||||
export * from './register'
|
||||
export type { BotProps } from './components/Bot'
|
||||
export type { BubbleProps } from './features/bubble'
|
||||
export type { PopupProps } from './features/popup'
|
||||
export * from './features/commands'
|
||||
|
||||
export type { BotProps } from './components/Bot'
|
||||
export type { PopupProps } from './features/popup/components/Popup'
|
||||
export type { BubbleProps } from './features/bubble/components/Bubble'
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GoogleAnalyticsOptions } from 'models'
|
||||
import type { GoogleAnalyticsOptions } from 'models'
|
||||
|
||||
declare const gtag: (
|
||||
type: string,
|
||||
|
13
packages/js/src/lib/stripe.ts
Normal file
13
packages/js/src/lib/stripe.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Stripe } from '@stripe/stripe-js'
|
||||
|
||||
export const loadStripe = (publishableKey: string): Promise<Stripe> =>
|
||||
new Promise<Stripe>((resolve) => {
|
||||
if (window.Stripe) return resolve(window.Stripe(publishableKey))
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://js.stripe.com/v3'
|
||||
document.body.appendChild(script)
|
||||
script.onload = () => {
|
||||
if (!window.Stripe) throw new Error('Stripe.js failed to load.')
|
||||
resolve(window.Stripe(publishableKey))
|
||||
}
|
||||
})
|
@ -1,6 +1,7 @@
|
||||
import { InitialChatReply } from '@/types'
|
||||
import { SendMessageInput, StartParams } from 'models'
|
||||
import { getViewerUrl, isEmpty, sendRequest } from 'utils'
|
||||
import { guessApiHost } from '@/utils/guessApiHost'
|
||||
import type { SendMessageInput, StartParams } from 'models'
|
||||
import { isNotEmpty, sendRequest } from 'utils'
|
||||
|
||||
export async function getInitialChatReplyQuery({
|
||||
typebot,
|
||||
@ -17,7 +18,7 @@ export async function getInitialChatReplyQuery({
|
||||
|
||||
return sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isEmpty(apiHost) ? getViewerUrl() : apiHost}/api/v1/sendMessage`,
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sendMessage`,
|
||||
body: {
|
||||
startParams: {
|
||||
isPreview,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChatReply, SendMessageInput } from 'models'
|
||||
import type { ChatReply, SendMessageInput } from 'models'
|
||||
import { getViewerUrl, isEmpty, sendRequest } from 'utils'
|
||||
|
||||
export async function sendMessageQuery({
|
||||
|
@ -1,19 +1,16 @@
|
||||
import { customElement } from 'solid-element'
|
||||
import { Bot, BotProps } from './components/Bot'
|
||||
import { Bubble, BubbleProps } from './features/bubble'
|
||||
import { Popup, PopupProps } from './features/popup'
|
||||
import {
|
||||
defaultBotProps,
|
||||
defaultBubbleProps,
|
||||
defaultPopupProps,
|
||||
} from './constants'
|
||||
import { Bubble } from './features/bubble'
|
||||
import { Popup } from './features/popup'
|
||||
import { Standard } from './features/standard'
|
||||
|
||||
export const registerStandardComponent = (props: BotProps) => {
|
||||
export const registerWebComponents = () => {
|
||||
if (typeof window === 'undefined') return
|
||||
customElement('typebot-standard', props, Bot)
|
||||
}
|
||||
|
||||
export const registerBubbleComponent = (props: BubbleProps) => {
|
||||
if (typeof window === 'undefined') return
|
||||
customElement('typebot-bubble', props, Bubble)
|
||||
}
|
||||
|
||||
export const registerPopupComponent = (props: PopupProps) => {
|
||||
if (typeof window === 'undefined') return
|
||||
customElement('typebot-popup', props, Popup)
|
||||
customElement('typebot-standard', defaultBotProps, Standard)
|
||||
customElement('typebot-bubble', defaultBubbleProps, Bubble)
|
||||
customElement('typebot-popup', defaultPopupProps, Popup)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChatReply } from 'models'
|
||||
import type { ChatReply } from 'models'
|
||||
|
||||
export type InputSubmitContent = {
|
||||
label?: string
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { executeChatwoot } from '@/features/blocks/integrations/chatwoot'
|
||||
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/utils/executeGoogleAnalytics'
|
||||
import { ChatReply } from 'models'
|
||||
import type { ChatReply } from 'models'
|
||||
|
||||
export const executeIntegrations = async (
|
||||
integrations: ChatReply['integrations']
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { executeCode } from '@/features/blocks/logic/code'
|
||||
import { executeRedirect } from '@/features/blocks/logic/redirect'
|
||||
import { ChatReply } from 'models'
|
||||
import type { ChatReply } from 'models'
|
||||
|
||||
export const executeLogic = async (logic: ChatReply['logic']) => {
|
||||
if (logic?.codeToExecute) {
|
||||
|
6
packages/js/src/utils/guessApiHost.ts
Normal file
6
packages/js/src/utils/guessApiHost.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { env } from 'utils'
|
||||
|
||||
const cloudViewerUrl = 'https://viewer.typebot.io'
|
||||
|
||||
export const guessApiHost = () =>
|
||||
env('VIEWER_URL')?.split(',')[0] ?? cloudViewerUrl
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
Background,
|
||||
BackgroundType,
|
||||
ChatTheme,
|
||||
ContainerColors,
|
||||
GeneralTheme,
|
||||
InputColors,
|
||||
Theme,
|
||||
} from 'models'
|
||||
import { BackgroundType } from 'models/features/typebot/theme/enums'
|
||||
|
||||
const cssVariableNames = {
|
||||
general: {
|
||||
|
5
packages/js/src/web.ts
Normal file
5
packages/js/src/web.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { registerWebComponents } from './register'
|
||||
import { injectTypebotInWindow } from './window'
|
||||
|
||||
registerWebComponents()
|
||||
injectTypebotInWindow()
|
65
packages/js/src/window.ts
Normal file
65
packages/js/src/window.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { BubbleProps } from './features/bubble'
|
||||
import { PopupProps } from './features/popup'
|
||||
import { BotProps } from './components/Bot'
|
||||
import {
|
||||
close,
|
||||
hidePreviewMessage,
|
||||
open,
|
||||
setPrefilledVariables,
|
||||
showPreviewMessage,
|
||||
toggle,
|
||||
} from './features/commands'
|
||||
|
||||
export const initStandard = (
|
||||
props: BotProps & { style?: string; class?: string }
|
||||
) => {
|
||||
const standardElement = document.querySelector('typebot-standard')
|
||||
if (!standardElement) throw new Error('<typebot-standard> element not found.')
|
||||
Object.assign(standardElement, props)
|
||||
}
|
||||
|
||||
export const initPopup = (props: PopupProps) => {
|
||||
const popupElement = document.createElement('typebot-popup')
|
||||
Object.assign(popupElement, props)
|
||||
document.body.appendChild(popupElement)
|
||||
}
|
||||
|
||||
export const initBubble = (props: BubbleProps) => {
|
||||
const bubbleElement = document.createElement('typebot-bubble')
|
||||
Object.assign(bubbleElement, props)
|
||||
document.body.appendChild(bubbleElement)
|
||||
}
|
||||
|
||||
declare const window:
|
||||
| {
|
||||
Typebot:
|
||||
| {
|
||||
initStandard: typeof initStandard
|
||||
initPopup: typeof initPopup
|
||||
initBubble: typeof initBubble
|
||||
close: typeof close
|
||||
hidePreviewMessage: typeof hidePreviewMessage
|
||||
open: typeof open
|
||||
setPrefilledVariables: typeof setPrefilledVariables
|
||||
showPreviewMessage: typeof showPreviewMessage
|
||||
toggle: typeof toggle
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
| undefined
|
||||
|
||||
export const injectTypebotInWindow = () => {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
window.Typebot = {
|
||||
initStandard,
|
||||
initPopup,
|
||||
initBubble,
|
||||
close,
|
||||
hidePreviewMessage,
|
||||
open,
|
||||
setPrefilledVariables,
|
||||
showPreviewMessage,
|
||||
toggle,
|
||||
}
|
||||
}
|
@ -12,7 +12,8 @@
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"declaration": true,
|
||||
"declarationDir": "dts",
|
||||
"declarationMap": true,
|
||||
"outDir": "dist",
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import solidPlugin from 'vite-plugin-solid'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [solidPlugin()],
|
||||
server: {
|
||||
port: 3005,
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['models', 'utils'],
|
||||
},
|
||||
})
|
11
packages/models/features/blocks/baseSchemas.ts
Normal file
11
packages/models/features/blocks/baseSchemas.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const blockBaseSchema = z.object({
|
||||
id: z.string(),
|
||||
groupId: z.string(),
|
||||
outgoingEdgeId: z.string().optional(),
|
||||
})
|
||||
|
||||
export const optionBaseSchema = z.object({
|
||||
variableId: z.string().optional(),
|
||||
})
|
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { BubbleBlockType } from './enums'
|
||||
|
||||
export const audioBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
@ -1,5 +1,6 @@
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { BubbleBlockType } from './enums'
|
||||
|
||||
export const embedBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
7
packages/models/features/blocks/bubbles/enums.ts
Normal file
7
packages/models/features/blocks/bubbles/enums.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export enum BubbleBlockType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
EMBED = 'embed',
|
||||
AUDIO = 'audio',
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { BubbleBlockType } from './enums'
|
||||
|
||||
export const imageBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
@ -1,6 +1,7 @@
|
||||
export * from './bubbleBlock'
|
||||
export * from './text'
|
||||
export * from './image'
|
||||
export * from './video'
|
||||
export * from './embed'
|
||||
export * from './audio'
|
||||
export * from './embed'
|
||||
export * from './enums'
|
||||
export * from './image'
|
||||
export * from './schemas'
|
||||
export * from './text'
|
||||
export * from './video'
|
@ -1,5 +1,6 @@
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { BubbleBlockType } from './enums'
|
||||
|
||||
export const defaultTextBubbleContent: TextBubbleContent = {
|
||||
html: '',
|
5
packages/models/features/blocks/bubbles/video/enums.ts
Normal file
5
packages/models/features/blocks/bubbles/video/enums.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum VideoBubbleContentType {
|
||||
URL = 'url',
|
||||
YOUTUBE = 'youtube',
|
||||
VIMEO = 'vimeo',
|
||||
}
|
2
packages/models/features/blocks/bubbles/video/index.ts
Normal file
2
packages/models/features/blocks/bubbles/video/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './enums'
|
||||
export * from './schemas'
|
@ -1,11 +1,7 @@
|
||||
import { blockBaseSchema, BubbleBlockType } from '../shared'
|
||||
import { z } from 'zod'
|
||||
|
||||
export enum VideoBubbleContentType {
|
||||
URL = 'url',
|
||||
YOUTUBE = 'youtube',
|
||||
VIMEO = 'vimeo',
|
||||
}
|
||||
import { blockBaseSchema } from '../../baseSchemas'
|
||||
import { BubbleBlockType } from '../enums'
|
||||
import { VideoBubbleContentType } from './enums'
|
||||
|
||||
export const videoBubbleContentSchema = z.object({
|
||||
url: z.string().optional(),
|
@ -1,7 +1,7 @@
|
||||
export * from './blocks'
|
||||
export * from './baseSchemas'
|
||||
export * from './bubbles'
|
||||
export * from './inputs'
|
||||
export * from './logic'
|
||||
export * from './integrations'
|
||||
export * from './item'
|
||||
export * from './shared'
|
||||
export * from './logic'
|
||||
export * from './schemas'
|
||||
export * from './start'
|
@ -1,12 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
blockBaseSchema,
|
||||
InputBlockType,
|
||||
defaultButtonLabel,
|
||||
optionBaseSchema,
|
||||
itemBaseSchema,
|
||||
ItemType,
|
||||
} from '../shared'
|
||||
import { ItemType } from '../../items/enums'
|
||||
import { itemBaseSchema } from '../../items/baseSchemas'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
|
||||
export const choiceInputOptionsSchema = optionBaseSchema.and(
|
||||
z.object({
|
1
packages/models/features/blocks/inputs/constants.ts
Normal file
1
packages/models/features/blocks/inputs/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const defaultButtonLabel = 'Send'
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
blockBaseSchema,
|
||||
InputBlockType,
|
||||
defaultButtonLabel,
|
||||
optionBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
|
||||
export const dateInputOptionsSchema = optionBaseSchema.and(
|
||||
z.object({
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
export const emailInputOptionsSchema = optionBaseSchema
|
12
packages/models/features/blocks/inputs/enums.ts
Normal file
12
packages/models/features/blocks/inputs/enums.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export enum InputBlockType {
|
||||
TEXT = 'text input',
|
||||
NUMBER = 'number input',
|
||||
EMAIL = 'email input',
|
||||
URL = 'url input',
|
||||
DATE = 'date input',
|
||||
PHONE = 'phone number input',
|
||||
CHOICE = 'choice input',
|
||||
PAYMENT = 'payment input',
|
||||
RATING = 'rating input',
|
||||
FILE = 'file input',
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { InputBlockType, optionBaseSchema, blockBaseSchema } from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { InputBlockType } from './enums'
|
||||
|
||||
export const fileInputOptionsSchema = optionBaseSchema.and(
|
||||
z.object({
|
@ -1,11 +1,13 @@
|
||||
export * from './inputBlock'
|
||||
export * from './text'
|
||||
export * from './email'
|
||||
export * from './number'
|
||||
export * from './url'
|
||||
export * from './date'
|
||||
export * from './choice'
|
||||
export * from './constants'
|
||||
export * from './date'
|
||||
export * from './email'
|
||||
export * from './enums'
|
||||
export * from './file'
|
||||
export * from './number'
|
||||
export * from './payment'
|
||||
export * from './phone'
|
||||
export * from './rating'
|
||||
export * from './file'
|
||||
export * from './schemas'
|
||||
export * from './text'
|
||||
export * from './url'
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
export const numberInputOptionsSchema = optionBaseSchema
|
3
packages/models/features/blocks/inputs/payment/enums.ts
Normal file
3
packages/models/features/blocks/inputs/payment/enums.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum PaymentProvider {
|
||||
STRIPE = 'Stripe',
|
||||
}
|
2
packages/models/features/blocks/inputs/payment/index.ts
Normal file
2
packages/models/features/blocks/inputs/payment/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './enums'
|
||||
export * from './schemas'
|
@ -1,5 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { InputBlockType, optionBaseSchema, blockBaseSchema } from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../../baseSchemas'
|
||||
import { InputBlockType } from '../enums'
|
||||
import { PaymentProvider } from './enums'
|
||||
|
||||
export type CreditCardDetails = {
|
||||
number: string
|
||||
@ -8,10 +10,6 @@ export type CreditCardDetails = {
|
||||
cvc: string
|
||||
}
|
||||
|
||||
export enum PaymentProvider {
|
||||
STRIPE = 'Stripe',
|
||||
}
|
||||
|
||||
export const paymentInputOptionsSchema = optionBaseSchema.and(
|
||||
z.object({
|
||||
provider: z.nativeEnum(PaymentProvider),
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
export const phoneNumberInputOptionsSchema = optionBaseSchema
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
|
||||
export const defaultRatingInputOptions: RatingInputOptions = {
|
||||
buttonType: 'Numbers',
|
@ -1,5 +1,4 @@
|
||||
import { z } from 'zod'
|
||||
import { optionBaseSchema } from '../shared'
|
||||
import { choiceInputOptionsSchema, choiceInputSchema } from './choice'
|
||||
import { dateInputOptionsSchema, dateInputSchema } from './date'
|
||||
import { emailInputOptionsSchema, emailInputSchema } from './email'
|
||||
@ -13,6 +12,7 @@ import { ratingInputOptionsSchema, ratingInputBlockSchema } from './rating'
|
||||
import { textInputOptionsSchema, textInputSchema } from './text'
|
||||
import { fileInputOptionsSchema, fileInputStepSchema } from './file'
|
||||
import { urlInputOptionsSchema, urlInputSchema } from './url'
|
||||
import { optionBaseSchema } from '../baseSchemas'
|
||||
|
||||
export type OptionBase = z.infer<typeof optionBaseSchema>
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { blockBaseSchema, optionBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
|
||||
export const textInputOptionsBaseSchema = z.object({
|
||||
labels: z.object({
|
@ -1,10 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
defaultButtonLabel,
|
||||
InputBlockType,
|
||||
optionBaseSchema,
|
||||
blockBaseSchema,
|
||||
} from '../shared'
|
||||
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
|
||||
import { defaultButtonLabel } from './constants'
|
||||
import { InputBlockType } from './enums'
|
||||
import { textInputOptionsBaseSchema } from './text'
|
||||
|
||||
export const urlInputOptionsSchema = optionBaseSchema
|
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema, IntegrationBlockType } from '../shared'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { IntegrationBlockType } from './enums'
|
||||
|
||||
export const chatwootOptionsSchema = z.object({
|
||||
baseUrl: z.string(),
|
10
packages/models/features/blocks/integrations/enums.ts
Normal file
10
packages/models/features/blocks/integrations/enums.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export enum IntegrationBlockType {
|
||||
GOOGLE_SHEETS = 'Google Sheets',
|
||||
GOOGLE_ANALYTICS = 'Google Analytics',
|
||||
WEBHOOK = 'Webhook',
|
||||
EMAIL = 'Email',
|
||||
ZAPIER = 'Zapier',
|
||||
MAKE_COM = 'Make.com',
|
||||
PABBLY_CONNECT = 'Pabbly',
|
||||
CHATWOOT = 'Chatwoot',
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { blockBaseSchema, IntegrationBlockType } from '../shared'
|
||||
import { blockBaseSchema } from '../baseSchemas'
|
||||
import { IntegrationBlockType } from './enums'
|
||||
|
||||
export const googleAnalyticsOptionsSchema = z.object({
|
||||
trackingId: z.string().optional(),
|
@ -0,0 +1,5 @@
|
||||
export enum GoogleSheetsAction {
|
||||
GET = 'Get data from sheet',
|
||||
INSERT_ROW = 'Insert a row',
|
||||
UPDATE_ROW = 'Update a row',
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user