⚡ (chat) Improve chat API compatibility with preview mode
This commit is contained in:
@ -12,7 +12,7 @@ type Props = {
|
||||
export const DateForm = (props: Props) => {
|
||||
const [inputValues, setInputValues] = createSignal({ from: '', to: '' })
|
||||
return (
|
||||
<div class="flex flex-col w-full lg:w-4/6">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<form
|
||||
class={'flex justify-between rounded-lg typebot-input pr-2 items-end'}
|
||||
|
@ -20,7 +20,6 @@ export const parseReadableDate = ({
|
||||
const fromReadable = new Date(
|
||||
hasTime ? from : from.replace(/-/g, '/')
|
||||
).toLocaleString(currentLocale, formatOptions)
|
||||
console.log(to, to.replace(/-/g, '/'))
|
||||
const toReadable = new Date(
|
||||
hasTime ? to : to.replace(/-/g, '/')
|
||||
).toLocaleString(currentLocale, formatOptions)
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { EmailInputBlock } from 'models'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
block: EmailInputBlock & { prefilledValue?: string }
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
block: EmailInputBlock
|
||||
defaultValue?: string
|
||||
hasGuestAvatar: boolean
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
}
|
||||
|
||||
export const EmailInput = (props: Props) => {
|
||||
const [inputValue, setInputValue] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ?? ''
|
||||
)
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string) => setInputValue(inputValue)
|
||||
@ -30,6 +29,10 @@ export const EmailInput = (props: Props) => {
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { NumberInputBlock } from 'models'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type NumberInputProps = {
|
||||
block: NumberInputBlock & { prefilledValue?: string }
|
||||
block: NumberInputBlock
|
||||
defaultValue?: string
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
hasGuestAvatar: boolean
|
||||
}
|
||||
|
||||
export const NumberInput = (props: NumberInputProps) => {
|
||||
const [inputValue, setInputValue] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ?? ''
|
||||
)
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string) => setInputValue(inputValue)
|
||||
@ -30,6 +29,10 @@ export const NumberInput = (props: NumberInputProps) => {
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
|
@ -3,21 +3,19 @@ import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import type { PhoneNumberInputBlock } from 'models'
|
||||
import { createSignal, For } from 'solid-js'
|
||||
import { createSignal, For, onMount } from 'solid-js'
|
||||
import { phoneCountries } from 'utils/phoneCountries'
|
||||
|
||||
type PhoneInputProps = {
|
||||
block: PhoneNumberInputBlock & { prefilledValue?: string }
|
||||
block: PhoneNumberInputBlock
|
||||
defaultValue?: string
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
hasGuestAvatar: boolean
|
||||
}
|
||||
|
||||
export const PhoneInput = (props: PhoneInputProps) => {
|
||||
const [selectedCountryCode, setSelectedCountryCode] = createSignal('INT')
|
||||
const [inputValue, setInputValue] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ?? ''
|
||||
)
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string | undefined) => {
|
||||
@ -47,11 +45,13 @@ export const PhoneInput = (props: PhoneInputProps) => {
|
||||
setSelectedCountryCode(event.currentTarget.value)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
'flex items-end justify-between rounded-lg pr-2 typebot-input w-full'
|
||||
}
|
||||
class={'flex items-end justify-between rounded-lg pr-2 typebot-input'}
|
||||
data-testid="input"
|
||||
style={{
|
||||
'margin-right': props.hasGuestAvatar ? '50px' : '0.5rem',
|
||||
|
@ -5,20 +5,21 @@ import { createSignal, For, Match, Switch } from 'solid-js'
|
||||
import { isDefined, isEmpty, isNotDefined } from 'utils'
|
||||
|
||||
type Props = {
|
||||
block: RatingInputBlock & { prefilledValue?: string }
|
||||
block: RatingInputBlock
|
||||
defaultValue?: string
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
}
|
||||
|
||||
export const RatingForm = (props: Props) => {
|
||||
const [rating, setRating] = createSignal<number | undefined>(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ? Number(props.block.prefilledValue) : undefined
|
||||
props.defaultValue ? Number(props.defaultValue) : undefined
|
||||
)
|
||||
|
||||
const handleSubmit = (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
if (isNotDefined(rating)) return
|
||||
props.onSubmit({ value: rating.toString() })
|
||||
const selectedRating = rating()
|
||||
if (isNotDefined(selectedRating)) return
|
||||
props.onSubmit({ value: selectedRating.toString() })
|
||||
}
|
||||
|
||||
const handleClick = (rating: number) => {
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { Textarea, ShortTextInput } from '@/components/inputs'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { TextInputBlock } from 'models'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
block: TextInputBlock & { prefilledValue?: string }
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
block: TextInputBlock
|
||||
defaultValue?: string
|
||||
hasGuestAvatar: boolean
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
}
|
||||
|
||||
export const TextInput = (props: Props) => {
|
||||
const [inputValue, setInputValue] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ?? ''
|
||||
)
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string) => setInputValue(inputValue)
|
||||
@ -31,6 +30,10 @@ export const TextInput = (props: Props) => {
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { ShortTextInput } from '@/components/inputs'
|
||||
import { SendButton } from '@/components/SendButton'
|
||||
import { InputSubmitContent } from '@/types'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
import { UrlInputBlock } from 'models'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
type Props = {
|
||||
block: UrlInputBlock & { prefilledValue?: string }
|
||||
block: UrlInputBlock
|
||||
defaultValue?: string
|
||||
onSubmit: (value: InputSubmitContent) => void
|
||||
hasGuestAvatar: boolean
|
||||
}
|
||||
|
||||
export const UrlInput = (props: Props) => {
|
||||
const [inputValue, setInputValue] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
props.block.prefilledValue ?? ''
|
||||
)
|
||||
const [inputValue, setInputValue] = createSignal(props.defaultValue ?? '')
|
||||
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
||||
|
||||
const handleInput = (inputValue: string) => {
|
||||
@ -36,6 +35,10 @@ export const UrlInput = (props: Props) => {
|
||||
if (e.key === 'Enter') submit()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!isMobile() && inputRef) inputRef.focus()
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
|
@ -20,7 +20,7 @@ export const Bubble = (props: BubbleProps) => {
|
||||
'onClose',
|
||||
'previewMessage',
|
||||
'onPreviewMessageClick',
|
||||
'button',
|
||||
'theme',
|
||||
])
|
||||
const [prefilledVariables, setPrefilledVariables] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
@ -106,13 +106,13 @@ export const Bubble = (props: BubbleProps) => {
|
||||
<Show when={isPreviewMessageDisplayed()}>
|
||||
<PreviewMessage
|
||||
{...previewMessage()}
|
||||
button={bubbleProps.button}
|
||||
previewMessageTheme={bubbleProps.theme?.previewMessage}
|
||||
onClick={handlePreviewMessageClick}
|
||||
onCloseClick={hideMessage}
|
||||
/>
|
||||
</Show>
|
||||
<BubbleButton
|
||||
{...bubbleProps.button}
|
||||
{...bubbleProps.theme?.button}
|
||||
toggleBot={toggleBot}
|
||||
isBotOpened={isBotOpened()}
|
||||
/>
|
||||
@ -126,7 +126,7 @@ export const Bubble = (props: BubbleProps) => {
|
||||
'box-shadow': 'rgb(0 0 0 / 16%) 0px 5px 40px',
|
||||
}}
|
||||
class={
|
||||
'absolute bottom-20 sm:right-4 rounded-lg bg-white w-full sm:w-[400px] max-h-[704px] ' +
|
||||
'absolute bottom-20 sm:right-4 rounded-lg w-full sm:w-[400px] max-h-[704px] ' +
|
||||
(isBotOpened() ? 'opacity-1' : 'opacity-0 pointer-events-none')
|
||||
}
|
||||
>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Show } from 'solid-js'
|
||||
import { ButtonParams } from '../types'
|
||||
import { ButtonTheme } from '../types'
|
||||
|
||||
type Props = ButtonParams & {
|
||||
type Props = ButtonTheme & {
|
||||
isBotOpened: boolean
|
||||
toggleBot: () => void
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { createSignal } from 'solid-js'
|
||||
import { BubbleParams, PreviewMessageParams } from '../types'
|
||||
import { PreviewMessageParams, PreviewMessageTheme } from '../types'
|
||||
|
||||
export type PreviewMessageProps = Pick<
|
||||
PreviewMessageParams,
|
||||
'avatarUrl' | 'message' | 'style'
|
||||
> &
|
||||
Pick<BubbleParams, 'button'> & {
|
||||
onClick: () => void
|
||||
onCloseClick: () => void
|
||||
}
|
||||
'avatarUrl' | 'message'
|
||||
> & {
|
||||
previewMessageTheme?: PreviewMessageTheme
|
||||
onClick: () => void
|
||||
onCloseClick: () => void
|
||||
}
|
||||
|
||||
const defaultFontFamily =
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'"
|
||||
@ -23,9 +23,11 @@ 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.style?.fontFamily ?? defaultFontFamily,
|
||||
'background-color': props.style?.backgroundColor ?? '#F7F8FF',
|
||||
color: props.style?.color ?? '#303235',
|
||||
'font-family':
|
||||
props.previewMessageTheme?.fontFamily ?? defaultFontFamily,
|
||||
'background-color':
|
||||
props.previewMessageTheme?.backgroundColor ?? '#F7F8FF',
|
||||
color: props.previewMessageTheme?.color ?? '#303235',
|
||||
}}
|
||||
onMouseEnter={() => setIsPreviewMessageHovered(true)}
|
||||
onMouseLeave={() => setIsPreviewMessageHovered(false)}
|
||||
@ -40,8 +42,9 @@ export const PreviewMessage = (props: PreviewMessageProps) => {
|
||||
return props.onCloseClick()
|
||||
}}
|
||||
style={{
|
||||
'background-color': props.style?.closeButtonBgColor ?? '#F7F8FF',
|
||||
color: props.style?.closeButtonColor ?? '#303235',
|
||||
'background-color':
|
||||
props.previewMessageTheme?.closeButtonBgColor ?? '#F7F8FF',
|
||||
color: props.previewMessageTheme?.closeButtonColor ?? '#303235',
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
@ -1,9 +1,14 @@
|
||||
export type BubbleParams = {
|
||||
button: ButtonParams
|
||||
previewMessage: PreviewMessageParams
|
||||
theme?: BubbleTheme
|
||||
previewMessage?: PreviewMessageParams
|
||||
}
|
||||
|
||||
export type ButtonParams = {
|
||||
export type BubbleTheme = {
|
||||
button?: ButtonTheme
|
||||
previewMessage?: PreviewMessageTheme
|
||||
}
|
||||
|
||||
export type ButtonTheme = {
|
||||
backgroundColor?: string
|
||||
icon?: {
|
||||
color?: string
|
||||
@ -15,13 +20,12 @@ export type PreviewMessageParams = {
|
||||
avatarUrl?: string
|
||||
message: string
|
||||
autoShowDelay?: number
|
||||
style?: PreviewMessageStyle
|
||||
}
|
||||
|
||||
type PreviewMessageStyle = Partial<{
|
||||
backgroundColor: string
|
||||
color: string
|
||||
fontFamily: string
|
||||
closeButtonBgColor: string
|
||||
closeButtonColor: string
|
||||
}>
|
||||
export type PreviewMessageTheme = {
|
||||
backgroundColor?: string
|
||||
color?: string
|
||||
fontFamily?: string
|
||||
closeButtonBgColor?: string
|
||||
closeButtonColor?: string
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
import styles from '../../../assets/index.css'
|
||||
import { createSignal, onMount, Show, splitProps, onCleanup } from 'solid-js'
|
||||
import {
|
||||
createSignal,
|
||||
onMount,
|
||||
Show,
|
||||
splitProps,
|
||||
onCleanup,
|
||||
createEffect,
|
||||
} from 'solid-js'
|
||||
import { Bot, BotProps } from '../../../components/Bot'
|
||||
import { CommandData } from '@/features/commands'
|
||||
import { isDefined } from 'utils'
|
||||
@ -7,6 +14,8 @@ import { PopupParams } from '../types'
|
||||
|
||||
export type PopupProps = BotProps &
|
||||
PopupParams & {
|
||||
defaultOpen?: boolean
|
||||
isOpen?: boolean
|
||||
onOpen?: () => void
|
||||
onClose?: () => void
|
||||
}
|
||||
@ -18,7 +27,9 @@ export const Popup = (props: PopupProps) => {
|
||||
'onOpen',
|
||||
'onClose',
|
||||
'autoShowDelay',
|
||||
'style',
|
||||
'theme',
|
||||
'isOpen',
|
||||
'defaultOpen',
|
||||
])
|
||||
|
||||
const [prefilledVariables, setPrefilledVariables] = createSignal(
|
||||
@ -26,10 +37,14 @@ export const Popup = (props: PopupProps) => {
|
||||
botProps.prefilledVariables
|
||||
)
|
||||
|
||||
const [isBotOpened, setIsBotOpened] = createSignal(false)
|
||||
const [isBotOpened, setIsBotOpened] = createSignal(
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
popupProps.isOpen ?? popupProps.defaultOpen ?? false
|
||||
)
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('click', processWindowClick)
|
||||
document.addEventListener('pointerdown', processWindowClick)
|
||||
botContainer?.addEventListener('pointerdown', stopPropagation)
|
||||
window.addEventListener('message', processIncomingEvent)
|
||||
const autoShowDelay = popupProps.autoShowDelay
|
||||
if (isDefined(autoShowDelay)) {
|
||||
@ -39,16 +54,25 @@ export const Popup = (props: PopupProps) => {
|
||||
}
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('message', processIncomingEvent)
|
||||
window.removeEventListener('click', processWindowClick)
|
||||
createEffect(() => {
|
||||
const isOpen = popupProps.isOpen
|
||||
if (isDefined(isOpen)) setIsBotOpened(isOpen)
|
||||
})
|
||||
|
||||
const processWindowClick = (event: MouseEvent) => {
|
||||
if (!botContainer || botContainer.contains(event.target as Node)) return
|
||||
onCleanup(() => {
|
||||
document.removeEventListener('pointerdown', processWindowClick)
|
||||
botContainer?.removeEventListener('pointerdown', stopPropagation)
|
||||
window.removeEventListener('message', processIncomingEvent)
|
||||
})
|
||||
|
||||
const processWindowClick = () => {
|
||||
setIsBotOpened(false)
|
||||
}
|
||||
|
||||
const stopPropagation = (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
const processIncomingEvent = (event: MessageEvent<CommandData>) => {
|
||||
const { data } = event
|
||||
if (!data.isFromTypebot) return
|
||||
@ -63,16 +87,19 @@ export const Popup = (props: PopupProps) => {
|
||||
}
|
||||
|
||||
const openBot = () => {
|
||||
setIsBotOpened(true)
|
||||
if (isBotOpened()) popupProps.onOpen?.()
|
||||
if (isDefined(props.isOpen)) return
|
||||
setIsBotOpened(true)
|
||||
}
|
||||
|
||||
const closeBot = () => {
|
||||
setIsBotOpened(false)
|
||||
if (isBotOpened()) popupProps.onClose?.()
|
||||
if (isDefined(props.isOpen)) return
|
||||
setIsBotOpened(false)
|
||||
}
|
||||
|
||||
const toggleBot = () => {
|
||||
if (isDefined(props.isOpen)) return
|
||||
isBotOpened() ? closeBot() : openBot()
|
||||
}
|
||||
|
||||
@ -85,15 +112,11 @@ export const Popup = (props: PopupProps) => {
|
||||
aria-modal="true"
|
||||
>
|
||||
<style>{styles}</style>
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity animate-fade-in" />
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity animate-fade-in" />
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
|
||||
<div
|
||||
class="relative h-[80vh] transform overflow-hidden rounded-lg text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg"
|
||||
style={{
|
||||
width: popupProps.style?.width ?? '100%',
|
||||
'background-color': popupProps.style?.backgroundColor ?? '#fff',
|
||||
}}
|
||||
ref={botContainer}
|
||||
>
|
||||
<Bot {...botProps} prefilledVariables={prefilledVariables()} />
|
||||
|
@ -1,6 +1,6 @@
|
||||
export type PopupParams = {
|
||||
autoShowDelay?: number
|
||||
style?: {
|
||||
theme?: {
|
||||
width?: string
|
||||
backgroundColor?: string
|
||||
}
|
||||
|
Reference in New Issue
Block a user