2
0

(chat) Improve chat API compatibility with preview mode

This commit is contained in:
Baptiste Arnaud
2023-01-16 12:13:21 +01:00
parent dbe5c3cdb1
commit 7311988901
55 changed files with 4133 additions and 465 deletions

View File

@ -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'}

View File

@ -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)

View File

@ -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={

View File

@ -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={

View File

@ -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',

View File

@ -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) => {

View File

@ -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={

View File

@ -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={

View File

@ -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')
}
>

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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()} />

View File

@ -1,6 +1,6 @@
export type PopupParams = {
autoShowDelay?: number
style?: {
theme?: {
width?: string
backgroundColor?: string
}