2
0

feat(bot): ️ Improve first paint delay

This commit is contained in:
Baptiste Arnaud
2022-06-12 07:57:35 +02:00
parent 4fd5d452a3
commit aeaaa5c398
13 changed files with 42 additions and 22 deletions

View File

@ -3,7 +3,7 @@ import { Answer, PublicTypebot, VariableWithValue } from 'models'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { upsertAnswer } from 'services/answer' import { upsertAnswer } from 'services/answer'
import { isDefined } from 'utils' import { isDefined, isNotDefined } from 'utils'
import { SEO } from '../components/Seo' import { SEO } from '../components/Seo'
import { createResult, updateResult } from '../services/result' import { createResult, updateResult } from '../services/result'
import { ErrorPage } from './ErrorPage' import { ErrorPage } from './ErrorPage'
@ -34,6 +34,7 @@ export const TypebotPage = ({
const [resultId, setResultId] = useState<string | undefined>() const [resultId, setResultId] = useState<string | undefined>()
useEffect(() => { useEffect(() => {
setShowTypebot(true)
const urlParams = new URLSearchParams(location.search) const urlParams = new URLSearchParams(location.search)
clearQueryParams() clearQueryParams()
const predefinedVariables: { [key: string]: string } = {} const predefinedVariables: { [key: string]: string } = {}
@ -68,7 +69,6 @@ export const TypebotPage = ({
setResultInSession(result.id) setResultInSession(result.id)
} }
} }
setShowTypebot(true)
} }
const handleNewVariables = async (variables: VariableWithValue[]) => { const handleNewVariables = async (variables: VariableWithValue[]) => {
@ -108,6 +108,7 @@ export const TypebotPage = ({
onNewAnswer={handleNewAnswer} onNewAnswer={handleNewAnswer}
onCompleted={handleCompleted} onCompleted={handleCompleted}
onVariablesUpdated={handleNewVariables} onVariablesUpdated={handleNewVariables}
isLoading={isNotDefined(resultId)}
/> />
)} )}
</div> </div>

View File

@ -10,7 +10,7 @@
"db": "*", "db": "*",
"models": "*", "models": "*",
"qs": "^6.10.3", "qs": "^6.10.3",
"react-frame-component": "5.2.2-alpha.1", "react-frame-component": "5.2.3",
"react-phone-number-input": "^3.1.52", "react-phone-number-input": "^3.1.52",
"react-scroll": "^1.8.7", "react-scroll": "^1.8.7",
"react-transition-group": "^4.4.2", "react-transition-group": "^4.4.2",

View File

@ -33,6 +33,7 @@ export const AvatarSideContainer = forwardRef(
})) }))
useEffect(() => { useEffect(() => {
if (!document) return
setShow(true) setShow(true)
const resizeObserver = new ResizeObserver(refreshTopOffset) const resizeObserver = new ResizeObserver(refreshTopOffset)
resizeObserver.observe(document.body) resizeObserver.observe(document.body)

View File

@ -12,7 +12,7 @@ type Props = {
export const showAnimationDuration = 400 export const showAnimationDuration = 400
export const EmbedBubble = ({ block, onTransitionEnd }: Props) => { export const EmbedBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot } = useTypebot() const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null) const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true) const [isTyping, setIsTyping] = useState(true)
@ -22,9 +22,10 @@ export const EmbedBubble = ({ block, onTransitionEnd }: Props) => {
) )
useEffect(() => { useEffect(() => {
if (!isTyping || isLoading) return
showContentAfterMediaLoad() showContentAfterMediaLoad()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [isLoading])
const showContentAfterMediaLoad = () => { const showContentAfterMediaLoad = () => {
setTimeout(() => { setTimeout(() => {

View File

@ -14,7 +14,7 @@ export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000 export const mediaLoadingFallbackTimeout = 5000
export const ImageBubble = ({ block, onTransitionEnd }: Props) => { export const ImageBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot } = useTypebot() const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null) const messageContainer = useRef<HTMLDivElement | null>(null)
const image = useRef<HTMLImageElement | null>(null) const image = useRef<HTMLImageElement | null>(null)
const [isTyping, setIsTyping] = useState(true) const [isTyping, setIsTyping] = useState(true)
@ -25,9 +25,10 @@ export const ImageBubble = ({ block, onTransitionEnd }: Props) => {
) )
useEffect(() => { useEffect(() => {
if (!isTyping || isLoading) return
showContentAfterMediaLoad() showContentAfterMediaLoad()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [isLoading])
const showContentAfterMediaLoad = () => { const showContentAfterMediaLoad = () => {
if (!image.current) return if (!image.current) return

View File

@ -19,7 +19,7 @@ const defaultTypingEmulation = {
} }
export const TextBubble = ({ block, onTransitionEnd }: Props) => { export const TextBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot } = useTypebot() const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null) const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true) const [isTyping, setIsTyping] = useState(true)
@ -28,6 +28,7 @@ export const TextBubble = ({ block, onTransitionEnd }: Props) => {
) )
useEffect(() => { useEffect(() => {
if (!isTyping || isLoading) return
const typingTimeout = computeTypingTimeout( const typingTimeout = computeTypingTimeout(
block.content.plainText, block.content.plainText,
typebot.settings?.typingEmulation ?? defaultTypingEmulation typebot.settings?.typingEmulation ?? defaultTypingEmulation
@ -36,7 +37,7 @@ export const TextBubble = ({ block, onTransitionEnd }: Props) => {
onTypingEnd() onTypingEnd()
}, typingTimeout) }, typingTimeout)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [isLoading])
const onTypingEnd = () => { const onTypingEnd = () => {
setIsTyping(false) setIsTyping(false)

View File

@ -19,14 +19,15 @@ export const showAnimationDuration = 400
export const mediaLoadingFallbackTimeout = 5000 export const mediaLoadingFallbackTimeout = 5000
export const VideoBubble = ({ block, onTransitionEnd }: Props) => { export const VideoBubble = ({ block, onTransitionEnd }: Props) => {
const { typebot } = useTypebot() const { typebot, isLoading } = useTypebot()
const messageContainer = useRef<HTMLDivElement | null>(null) const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true) const [isTyping, setIsTyping] = useState(true)
useEffect(() => { useEffect(() => {
if (!isTyping || isLoading) return
showContentAfterMediaLoad() showContentAfterMediaLoad()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [isLoading])
const showContentAfterMediaLoad = () => { const showContentAfterMediaLoad = () => {
setTimeout(() => { setTimeout(() => {

View File

@ -42,7 +42,7 @@ export const StripePaymentForm = ({ options, onSuccess }: Props) => {
description: error.name + ' ' + error.message, description: error.name + ' ' + error.message,
details: error.message, details: error.message,
}) })
if (!data) return if (!data || !frameDocument || !frameWindow?.Stripe) return
await initStripe(frameDocument) await initStripe(frameDocument)
setStripe(frameWindow.Stripe(data.publicKey)) setStripe(frameWindow.Stripe(data.publicKey))
setClientSecret(data.clientSecret) setClientSecret(data.clientSecret)

View File

@ -5,7 +5,7 @@ import { useFrame } from 'react-frame-component'
import { setCssVariablesValue } from '../services/theme' import { setCssVariablesValue } from '../services/theme'
import { useAnswers } from '../contexts/AnswersContext' import { useAnswers } from '../contexts/AnswersContext'
import { Group, Edge, PublicTypebot, Theme, VariableWithValue } from 'models' import { Group, Edge, PublicTypebot, Theme, VariableWithValue } from 'models'
import { byId, isNotDefined } from 'utils' import { byId, isDefined, isNotDefined } from 'utils'
import { animateScroll as scroll } from 'react-scroll' import { animateScroll as scroll } from 'react-scroll'
import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext' import { LinkedTypebot, useTypebot } from 'contexts/TypebotContext'
import { ChatContext } from 'contexts/ChatContext' import { ChatContext } from 'contexts/ChatContext'
@ -83,8 +83,13 @@ export const ConversationContainer = ({
} }
useEffect(() => { useEffect(() => {
if (
isDefined(predefinedVariables) &&
Object.keys(predefinedVariables).length > 0
) {
const prefilledVariables = injectPredefinedVariables(predefinedVariables) const prefilledVariables = injectPredefinedVariables(predefinedVariables)
updateVariables(prefilledVariables) updateVariables(prefilledVariables)
}
displayNextGroup({ displayNextGroup({
edgeId: startGroupId edgeId: startGroupId
? undefined ? undefined
@ -92,13 +97,13 @@ export const ConversationContainer = ({
groupId: startGroupId, groupId: startGroupId,
}) })
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [predefinedVariables])
const injectPredefinedVariables = (predefinedVariables?: { const injectPredefinedVariables = (predefinedVariables: {
[key: string]: string | undefined [key: string]: string | undefined
}) => { }) => {
const prefilledVariables: VariableWithValue[] = [] const prefilledVariables: VariableWithValue[] = []
Object.keys(predefinedVariables ?? {}).forEach((key) => { Object.keys(predefinedVariables).forEach((key) => {
const matchingVariable = typebot.variables.find( const matchingVariable = typebot.variables.find(
(v) => v.name.toLowerCase() === key.toLowerCase() (v) => v.name.toLowerCase() === key.toLowerCase()
) )
@ -112,6 +117,7 @@ export const ConversationContainer = ({
} }
useEffect(() => { useEffect(() => {
if (!frameDocument) return
setCssVariablesValue(theme, frameDocument.body.style) setCssVariablesValue(theme, frameDocument.body.style)
}, [theme, frameDocument]) }, [theme, frameDocument])

View File

@ -6,6 +6,7 @@ export const LiteBadge = () => {
const liteBadge = useRef<HTMLAnchorElement | null>(null) const liteBadge = useRef<HTMLAnchorElement | null>(null)
useEffect(() => { useEffect(() => {
if (!document) return
const container = document.querySelector( const container = document.querySelector(
'[data-testid="container"]' '[data-testid="container"]'
) as HTMLDivElement ) as HTMLDivElement

View File

@ -31,6 +31,7 @@ export type TypebotViewerProps = {
predefinedVariables?: { [key: string]: string | undefined } predefinedVariables?: { [key: string]: string | undefined }
resultId?: string resultId?: string
startGroupId?: string startGroupId?: string
isLoading?: boolean
onNewGroupVisible?: (edge: Edge) => void onNewGroupVisible?: (edge: Edge) => void
onNewAnswer?: (answer: Answer) => Promise<void> onNewAnswer?: (answer: Answer) => Promise<void>
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
@ -42,6 +43,7 @@ export const TypebotViewer = ({
typebot, typebot,
apiHost = process.env.NEXT_PUBLIC_VIEWER_URL?.split(',')[0], apiHost = process.env.NEXT_PUBLIC_VIEWER_URL?.split(',')[0],
isPreview = false, isPreview = false,
isLoading = false,
style, style,
resultId, resultId,
startGroupId, startGroupId,
@ -98,6 +100,7 @@ export const TypebotViewer = ({
apiHost={apiHost} apiHost={apiHost}
isPreview={isPreview} isPreview={isPreview}
onNewLog={handleNewLog} onNewLog={handleNewLog}
isLoading={isLoading}
> >
<AnswersContext <AnswersContext
resultId={resultId} resultId={resultId}

View File

@ -25,6 +25,7 @@ const typebotContext = createContext<{
apiHost: string apiHost: string
isPreview: boolean isPreview: boolean
linkedBotQueue: LinkedTypebotQueue linkedBotQueue: LinkedTypebotQueue
isLoading: boolean
setCurrentTypebotId: (id: string) => void setCurrentTypebotId: (id: string) => void
updateVariableValue: (variableId: string, value: string) => void updateVariableValue: (variableId: string, value: string) => void
createEdge: (edge: Edge) => void createEdge: (edge: Edge) => void
@ -44,11 +45,13 @@ export const TypebotContext = ({
typebot, typebot,
apiHost, apiHost,
isPreview, isPreview,
isLoading,
onNewLog, onNewLog,
}: { }: {
children: ReactNode children: ReactNode
typebot: PublicTypebot typebot: PublicTypebot
apiHost: string apiHost: string
isLoading: boolean
isPreview: boolean isPreview: boolean
onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void onNewLog: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
}) => { }) => {
@ -123,6 +126,7 @@ export const TypebotContext = ({
injectLinkedTypebot, injectLinkedTypebot,
onNewLog, onNewLog,
linkedBotQueue, linkedBotQueue,
isLoading,
pushEdgeIdInLinkedTypebotQueue, pushEdgeIdInLinkedTypebotQueue,
popEdgeIdFromLinkedTypebotQueue, popEdgeIdFromLinkedTypebotQueue,
currentTypebotId, currentTypebotId,

View File

@ -12440,10 +12440,10 @@ react-focus-lock@2.5.2:
use-callback-ref "^1.2.5" use-callback-ref "^1.2.5"
use-sidecar "^1.0.5" use-sidecar "^1.0.5"
react-frame-component@5.2.2-alpha.1: react-frame-component@5.2.3:
version "5.2.2-alpha.1" version "5.2.3"
resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-5.2.2-alpha.1.tgz#fcfec38e0670ea78b3e9b6d00ef7a18613b3b089" resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-5.2.3.tgz#2d5d1e29b23d5b915c839b44980d03bb9cafc453"
integrity sha512-pCXLlBDO8+yh4qbax8M2U+Cl+5tFxyZ0p6HzD0MP+P47u3ZK4fPiOzWQIOi7Eand4tq6nawKZC+vsSHC2KT1Zg== integrity sha512-r+h0o3r/uqOLNT724z4CRVkxQouKJvoi3OPfjqWACD30Y87rtEmeJrNZf1WYPGknn1Y8200HAjx7hY/dPUGgmA==
react-helmet-async@*, react-helmet-async@^1.2.3, react-helmet-async@^1.3.0: react-helmet-async@*, react-helmet-async@^1.2.3, react-helmet-async@^1.3.0:
version "1.3.0" version "1.3.0"