feat(bot): ⚡️ Improve first paint delay
This commit is contained in:
@ -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>
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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)
|
||||||
|
@ -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])
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
Reference in New Issue
Block a user