🧰 Aggregate utils & set up results collection in viewer
This commit is contained in:
@ -1,15 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ErrorPage = ({ error }: { error: 'offline' | '500' | 'IE' }) => {
|
||||
let errorLabel =
|
||||
'An error occured. Please try to refresh or contact the owner of this bot.'
|
||||
if (error === 'offline') {
|
||||
errorLabel =
|
||||
'Looks like your device is offline. Please, try to refresh the page.'
|
||||
}
|
||||
if (error === 'IE') {
|
||||
errorLabel = "This bot isn't compatible with Internet Explorer."
|
||||
}
|
||||
export const ErrorPage = ({ error }: { error: Error }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -20,10 +11,8 @@ export const ErrorPage = ({ error }: { error: 'offline' | '500' | 'IE' }) => {
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{error === '500' && (
|
||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>500</h1>
|
||||
)}
|
||||
<h2>{errorLabel}</h2>
|
||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>{error.name}</h1>
|
||||
<h2>{error.message}</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { PublicTypebot, TypebotViewer } from 'bot-engine'
|
||||
import React from 'react'
|
||||
import { Answer, PublicTypebot, TypebotViewer } from 'bot-engine'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { SEO } from '../components/Seo'
|
||||
import { createResult, updateResult } from '../services/result'
|
||||
import { ErrorPage } from './ErrorPage'
|
||||
import { NotFoundPage } from './NotFoundPage'
|
||||
|
||||
@ -10,17 +11,61 @@ export type TypebotPageProps = {
|
||||
isIE: boolean
|
||||
}
|
||||
|
||||
const sessionStorageKey = 'resultId'
|
||||
|
||||
export const TypebotPage = ({ typebot, isIE, url }: TypebotPageProps) => {
|
||||
const [error, setError] = useState<Error | undefined>(
|
||||
isIE ? new Error('Internet explorer is not supported') : undefined
|
||||
)
|
||||
const [resultId, setResultId] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
initializeResult()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const initializeResult = async () => {
|
||||
if (!typebot) return
|
||||
const resultIdFromSession = sessionStorage.getItem(sessionStorageKey)
|
||||
if (resultIdFromSession) setResultId(resultIdFromSession)
|
||||
else {
|
||||
const { error, data: result } = await createResult(typebot.typebotId)
|
||||
if (error) setError(error)
|
||||
if (result) {
|
||||
setResultId(result.id)
|
||||
sessionStorage.setItem(sessionStorageKey, result.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleAnswersUpdate = async (answers: Answer[]) => {
|
||||
if (!resultId) return setError(new Error('Result was not created'))
|
||||
const { error } = await updateResult(resultId, { answers })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
const handleCompleted = async () => {
|
||||
if (!resultId) return setError(new Error('Result was not created'))
|
||||
const { error } = await updateResult(resultId, { isCompleted: true })
|
||||
if (error) setError(error)
|
||||
}
|
||||
|
||||
if (!typebot) {
|
||||
return <NotFoundPage />
|
||||
}
|
||||
if (isIE) {
|
||||
return <ErrorPage error={'IE'} />
|
||||
if (error) {
|
||||
return <ErrorPage error={error} />
|
||||
}
|
||||
return (
|
||||
<div style={{ height: '100vh' }}>
|
||||
<SEO url={url} chatbotName={typebot.name} />
|
||||
<TypebotViewer typebot={typebot} />
|
||||
{resultId && (
|
||||
<TypebotViewer
|
||||
typebot={typebot}
|
||||
onAnswersUpdate={handleAnswersUpdate}
|
||||
onCompleted={handleCompleted}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
6
apps/viewer/next.config.js
Normal file
6
apps/viewer/next.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const withTM = require('next-transpile-modules')(['utils'])
|
||||
|
||||
module.exports = withTM({
|
||||
reactStrictMode: true,
|
||||
})
|
@ -13,7 +13,8 @@
|
||||
"db": "*",
|
||||
"next": "^12.0.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"utils": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.4",
|
||||
@ -24,6 +25,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"next-transpile-modules": "^9.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.5.4"
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ export const getServerSideProps: GetServerSideProps = async (
|
||||
const pathname = context.resolvedUrl.split('?')[0]
|
||||
try {
|
||||
if (!context.req.headers.host) return { props: {} }
|
||||
typebot = await getTypebotFromPublicId(context.query.publicId.toString())
|
||||
typebot = await getTypebotFromPublicId(context.query.publicId?.toString())
|
||||
if (!typebot) return { props: {} }
|
||||
return {
|
||||
props: {
|
||||
@ -32,8 +32,9 @@ export const getServerSideProps: GetServerSideProps = async (
|
||||
}
|
||||
|
||||
const getTypebotFromPublicId = async (
|
||||
publicId: string
|
||||
publicId?: string
|
||||
): Promise<PublicTypebot | undefined> => {
|
||||
if (!publicId) return
|
||||
const typebot = await prisma.publicTypebot.findUnique({
|
||||
where: { publicId },
|
||||
})
|
||||
|
16
apps/viewer/pages/api/results.ts
Normal file
16
apps/viewer/pages/api/results.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'POST') {
|
||||
const { typebotId } = JSON.parse(req.body)
|
||||
const result = await prisma.result.create({
|
||||
data: { typebotId },
|
||||
})
|
||||
return res.send(result)
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
18
apps/viewer/pages/api/results/[id].ts
Normal file
18
apps/viewer/pages/api/results/[id].ts
Normal file
@ -0,0 +1,18 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'PATCH') {
|
||||
const data = JSON.parse(req.body)
|
||||
const id = req.query.id.toString()
|
||||
const result = await prisma.result.update({
|
||||
where: { id },
|
||||
data: { ...data, updatedAt: new Date() },
|
||||
})
|
||||
return res.send(result)
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
21
apps/viewer/services/result.ts
Normal file
21
apps/viewer/services/result.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Result } from 'db'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const createResult = async (typebotId: string) => {
|
||||
return sendRequest<Result>({
|
||||
url: `/api/results`,
|
||||
method: 'POST',
|
||||
body: { typebotId },
|
||||
})
|
||||
}
|
||||
|
||||
export const updateResult = async (
|
||||
resultId: string,
|
||||
result: Partial<Result>
|
||||
) => {
|
||||
return sendRequest<Result>({
|
||||
url: `/api/results/${resultId}`,
|
||||
method: 'PATCH',
|
||||
body: result,
|
||||
})
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
@ -14,8 +14,9 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules", "cypress"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user