✨ Introduce bot v2 in builder (#328)
Also, the new engine is the default for updated typebots for viewer Closes #211
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { AnswerInput, PublicTypebot, Typebot, VariableWithValue } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
injectCustomHeadCode,
|
||||
isDefined,
|
||||
isNotDefined,
|
||||
isNotEmpty,
|
||||
} from 'utils'
|
||||
import { SEO } from './Seo'
|
||||
import { ErrorPage } from './ErrorPage'
|
||||
import { createResultQuery, updateResultQuery } from '@/features/results'
|
||||
import { upsertAnswerQuery } from '@/features/answers'
|
||||
import { gtmBodyElement } from '@/lib/google-tag-manager'
|
||||
import {
|
||||
getExistingResultFromSession,
|
||||
setResultInSession,
|
||||
} from '@/utils/sessionStorage'
|
||||
|
||||
export type TypebotPageProps = {
|
||||
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
|
||||
typebot: Pick<Typebot, 'name' | 'isClosed' | 'isArchived'>
|
||||
}
|
||||
url: string
|
||||
isIE: boolean
|
||||
customHeadCode: string | null
|
||||
}
|
||||
|
||||
export const TypebotPage = ({
|
||||
publishedTypebot,
|
||||
isIE,
|
||||
url,
|
||||
customHeadCode,
|
||||
}: TypebotPageProps) => {
|
||||
const { asPath, push } = useRouter()
|
||||
const [showTypebot, setShowTypebot] = useState(false)
|
||||
const [predefinedVariables, setPredefinedVariables] = useState<{
|
||||
[key: string]: string
|
||||
}>()
|
||||
const [error, setError] = useState<Error | undefined>(
|
||||
isIE ? new Error('Internet explorer is not supported') : undefined
|
||||
)
|
||||
const [resultId, setResultId] = useState<string | undefined>()
|
||||
const [variableUpdateQueue, setVariableUpdateQueue] = useState<
|
||||
VariableWithValue[][]
|
||||
>([])
|
||||
const [chatStarted, setChatStarted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setShowTypebot(true)
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
clearQueryParams()
|
||||
const predefinedVariables: { [key: string]: string } = {}
|
||||
urlParams.forEach((value, key) => {
|
||||
predefinedVariables[key] = value
|
||||
})
|
||||
setPredefinedVariables(predefinedVariables)
|
||||
initializeResult().then()
|
||||
if (isDefined(customHeadCode)) injectCustomHeadCode(customHeadCode)
|
||||
const gtmId = publishedTypebot.settings.metadata.googleTagManagerId
|
||||
if (isNotEmpty(gtmId)) document.body.prepend(gtmBodyElement(gtmId))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const clearQueryParams = () => {
|
||||
const hasQueryParams = asPath.includes('?')
|
||||
if (
|
||||
hasQueryParams &&
|
||||
publishedTypebot.settings.general.isHideQueryParamsEnabled !== false
|
||||
)
|
||||
push(asPath.split('?')[0], undefined, { shallow: true })
|
||||
}
|
||||
|
||||
const initializeResult = async () => {
|
||||
const resultIdFromSession = getExistingResultFromSession()
|
||||
if (resultIdFromSession) setResultId(resultIdFromSession)
|
||||
else {
|
||||
const { error, data } = await createResultQuery(
|
||||
publishedTypebot.typebotId
|
||||
)
|
||||
if (error) return setError(error)
|
||||
if (data?.hasReachedLimit)
|
||||
return setError(new Error('This bot is now closed.'))
|
||||
if (data?.result) {
|
||||
setResultId(data.result.id)
|
||||
if (
|
||||
publishedTypebot.settings.general.isNewResultOnRefreshEnabled !== true
|
||||
)
|
||||
setResultInSession(data.result.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!resultId || variableUpdateQueue.length === 0) return
|
||||
Promise.all(variableUpdateQueue.map(sendNewVariables(resultId))).then()
|
||||
setVariableUpdateQueue([])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [resultId])
|
||||
|
||||
const handleNewVariables = async (variables: VariableWithValue[]) => {
|
||||
if (!resultId)
|
||||
return setVariableUpdateQueue([...variableUpdateQueue, variables])
|
||||
await sendNewVariables(resultId)(variables)
|
||||
}
|
||||
|
||||
const sendNewVariables =
|
||||
(resultId: string) => async (variables: VariableWithValue[]) => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
const { error } = await updateResultQuery(resultId, { variables })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
const handleNewAnswer = async (
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => {
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
||||
const { error } = await upsertAnswerQuery({ ...answer, resultId })
|
||||
if (error) setError(error)
|
||||
}
|
||||
if (chatStarted) return
|
||||
updateResultQuery(resultId, {
|
||||
hasStarted: true,
|
||||
}).then(({ error }) => (error ? setError(error) : setChatStarted(true)))
|
||||
}
|
||||
|
||||
const handleCompleted = async () => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
const { error } = await updateResultQuery(resultId, { isCompleted: true })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage error={error} />
|
||||
}
|
||||
return (
|
||||
<div style={{ height: '100vh' }}>
|
||||
<SEO
|
||||
url={url}
|
||||
typebotName={publishedTypebot.typebot.name}
|
||||
metadata={publishedTypebot.settings.metadata}
|
||||
/>
|
||||
{showTypebot && (
|
||||
<TypebotViewer
|
||||
typebot={publishedTypebot}
|
||||
resultId={resultId}
|
||||
predefinedVariables={predefinedVariables}
|
||||
onNewAnswer={handleNewAnswer}
|
||||
onCompleted={handleCompleted}
|
||||
onVariablesUpdated={handleNewVariables}
|
||||
isLoading={isNotDefined(resultId)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +1,161 @@
|
||||
import { Standard } from '@typebot.io/react'
|
||||
import { BackgroundType, Typebot } from 'models'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { AnswerInput, PublicTypebot, Typebot, VariableWithValue } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {
|
||||
injectCustomHeadCode,
|
||||
isDefined,
|
||||
isNotDefined,
|
||||
isNotEmpty,
|
||||
} from 'utils'
|
||||
import { SEO } from './Seo'
|
||||
import { ErrorPage } from './ErrorPage'
|
||||
import { createResultQuery, updateResultQuery } from '@/features/results'
|
||||
import { upsertAnswerQuery } from '@/features/answers'
|
||||
import { gtmBodyElement } from '@/lib/google-tag-manager'
|
||||
import {
|
||||
getExistingResultFromSession,
|
||||
setResultInSession,
|
||||
} from '@/utils/sessionStorage'
|
||||
|
||||
export type TypebotPageProps = {
|
||||
publishedTypebot: Omit<PublicTypebot, 'createdAt' | 'updatedAt'> & {
|
||||
typebot: Pick<Typebot, 'name' | 'isClosed' | 'isArchived' | 'publicId'>
|
||||
}
|
||||
url: string
|
||||
typebot?: Pick<Typebot, 'settings' | 'theme' | 'name' | 'publicId'>
|
||||
isIE: boolean
|
||||
customHeadCode: string | null
|
||||
}
|
||||
|
||||
export const TypebotPage = ({ url, typebot }: TypebotPageProps) => {
|
||||
const { asPath, push, query } = useRouter()
|
||||
export const TypebotPageV2 = ({
|
||||
publishedTypebot,
|
||||
isIE,
|
||||
url,
|
||||
customHeadCode,
|
||||
}: TypebotPageProps) => {
|
||||
const { asPath, push } = useRouter()
|
||||
const [showTypebot, setShowTypebot] = useState(false)
|
||||
const [predefinedVariables, setPredefinedVariables] = useState<{
|
||||
[key: string]: string
|
||||
}>()
|
||||
const [error, setError] = useState<Error | undefined>(
|
||||
isIE ? new Error('Internet explorer is not supported') : undefined
|
||||
)
|
||||
const [resultId, setResultId] = useState<string | undefined>()
|
||||
const [variableUpdateQueue, setVariableUpdateQueue] = useState<
|
||||
VariableWithValue[][]
|
||||
>([])
|
||||
const [chatStarted, setChatStarted] = useState(false)
|
||||
|
||||
const background = typebot?.theme.general.background
|
||||
useEffect(() => {
|
||||
setShowTypebot(true)
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
clearQueryParams()
|
||||
const predefinedVariables: { [key: string]: string } = {}
|
||||
urlParams.forEach((value, key) => {
|
||||
predefinedVariables[key] = value
|
||||
})
|
||||
setPredefinedVariables(predefinedVariables)
|
||||
initializeResult().then()
|
||||
if (isDefined(customHeadCode)) injectCustomHeadCode(customHeadCode)
|
||||
const gtmId = publishedTypebot.settings.metadata.googleTagManagerId
|
||||
if (isNotEmpty(gtmId)) document.body.prepend(gtmBodyElement(gtmId))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const clearQueryParamsIfNecessary = () => {
|
||||
const clearQueryParams = () => {
|
||||
const hasQueryParams = asPath.includes('?')
|
||||
if (
|
||||
!hasQueryParams ||
|
||||
!(typebot?.settings.general.isHideQueryParamsEnabled ?? true)
|
||||
hasQueryParams &&
|
||||
publishedTypebot.settings.general.isHideQueryParamsEnabled !== false
|
||||
)
|
||||
return
|
||||
push(asPath.split('?')[0], undefined, { shallow: true })
|
||||
push(asPath.split('?')[0], undefined, { shallow: true })
|
||||
}
|
||||
|
||||
const initializeResult = async () => {
|
||||
const resultIdFromSession = getExistingResultFromSession()
|
||||
if (resultIdFromSession) setResultId(resultIdFromSession)
|
||||
else {
|
||||
const { error, data } = await createResultQuery(
|
||||
publishedTypebot.typebotId
|
||||
)
|
||||
if (error) return setError(error)
|
||||
if (data?.hasReachedLimit)
|
||||
return setError(new Error('This bot is now closed.'))
|
||||
if (data?.result) {
|
||||
setResultId(data.result.id)
|
||||
if (
|
||||
publishedTypebot.settings.general.isNewResultOnRefreshEnabled !== true
|
||||
)
|
||||
setResultInSession(data.result.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!resultId || variableUpdateQueue.length === 0) return
|
||||
Promise.all(variableUpdateQueue.map(sendNewVariables(resultId))).then()
|
||||
setVariableUpdateQueue([])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [resultId])
|
||||
|
||||
const handleNewVariables = async (variables: VariableWithValue[]) => {
|
||||
if (!resultId)
|
||||
return setVariableUpdateQueue([...variableUpdateQueue, variables])
|
||||
await sendNewVariables(resultId)(variables)
|
||||
}
|
||||
|
||||
const sendNewVariables =
|
||||
(resultId: string) => async (variables: VariableWithValue[]) => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
const { error } = await updateResultQuery(resultId, { variables })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
const handleNewAnswer = async (
|
||||
answer: AnswerInput & { uploadedFiles: boolean }
|
||||
) => {
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled !== false) {
|
||||
const { error } = await upsertAnswerQuery({ ...answer, resultId })
|
||||
if (error) setError(error)
|
||||
}
|
||||
if (chatStarted) return
|
||||
updateResultQuery(resultId, {
|
||||
hasStarted: true,
|
||||
}).then(({ error }) => (error ? setError(error) : setChatStarted(true)))
|
||||
}
|
||||
|
||||
const handleCompleted = async () => {
|
||||
if (publishedTypebot.settings.general.isResultSavingEnabled === false)
|
||||
return
|
||||
if (!resultId) return setError(new Error('Error: result was not created'))
|
||||
const { error } = await updateResultQuery(resultId, { isCompleted: true })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage error={error} />
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
// Set background color to avoid SSR flash
|
||||
backgroundColor:
|
||||
background?.type === BackgroundType.COLOR
|
||||
? background?.content
|
||||
: 'white',
|
||||
}}
|
||||
>
|
||||
{typebot && (
|
||||
<SEO
|
||||
url={url}
|
||||
typebotName={typebot.name}
|
||||
metadata={typebot.settings.metadata}
|
||||
<div style={{ height: '100vh' }}>
|
||||
<SEO
|
||||
url={url}
|
||||
typebotName={publishedTypebot.typebot.name}
|
||||
metadata={publishedTypebot.settings.metadata}
|
||||
/>
|
||||
{showTypebot && (
|
||||
<TypebotViewer
|
||||
typebot={publishedTypebot}
|
||||
resultId={resultId}
|
||||
predefinedVariables={predefinedVariables}
|
||||
onNewAnswer={handleNewAnswer}
|
||||
onCompleted={handleCompleted}
|
||||
onVariablesUpdated={handleNewVariables}
|
||||
isLoading={isNotDefined(resultId)}
|
||||
/>
|
||||
)}
|
||||
<Standard
|
||||
typebot={typebot?.publicId ?? query.publicId?.toString() ?? 'n'}
|
||||
onInit={clearQueryParamsIfNecessary}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
50
apps/viewer/src/components/TypebotPageV3.tsx
Normal file
50
apps/viewer/src/components/TypebotPageV3.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Standard } from '@typebot.io/react'
|
||||
import { BackgroundType, Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import { SEO } from './Seo'
|
||||
|
||||
export type TypebotPageProps = {
|
||||
url: string
|
||||
typebot?: Pick<Typebot, 'settings' | 'theme' | 'name' | 'publicId'>
|
||||
}
|
||||
|
||||
export const TypebotPageV3 = ({ url, typebot }: TypebotPageProps) => {
|
||||
const { asPath, push, query } = useRouter()
|
||||
|
||||
const background = typebot?.theme.general.background
|
||||
|
||||
const clearQueryParamsIfNecessary = () => {
|
||||
const hasQueryParams = asPath.includes('?')
|
||||
if (
|
||||
!hasQueryParams ||
|
||||
!(typebot?.settings.general.isHideQueryParamsEnabled ?? true)
|
||||
)
|
||||
return
|
||||
push(asPath.split('?')[0], undefined, { shallow: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
// Set background color to avoid SSR flash
|
||||
backgroundColor:
|
||||
background?.type === BackgroundType.COLOR
|
||||
? background?.content
|
||||
: 'white',
|
||||
}}
|
||||
>
|
||||
{typebot && (
|
||||
<SEO
|
||||
url={url}
|
||||
typebotName={typebot.name}
|
||||
metadata={typebot.settings.metadata}
|
||||
/>
|
||||
)}
|
||||
<Standard
|
||||
typebot={typebot?.publicId ?? query.publicId?.toString() ?? 'n'}
|
||||
onInit={clearQueryParamsIfNecessary}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user