🧰 Aggregate utils & set up results collection in viewer
This commit is contained in:
@@ -10,7 +10,7 @@ import { Block, StartBlock } from 'bot-engine'
|
||||
import { useGraph } from 'contexts/GraphContext'
|
||||
import { useDnd } from 'contexts/DndContext'
|
||||
import { StepsList } from './StepsList'
|
||||
import { isDefined } from 'services/utils'
|
||||
import { isDefined } from 'utils'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||
import { BlockNodeContextMenu } from './BlockNodeContextMenu'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Block, StartStep, Step, StepType } from 'bot-engine'
|
||||
import { SourceEndpoint } from './SourceEndpoint'
|
||||
import { useGraph } from 'contexts/GraphContext'
|
||||
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
|
||||
import { isDefined } from 'services/utils'
|
||||
import { isDefined } from 'utils'
|
||||
import { Coordinates } from '@dnd-kit/core/dist/types'
|
||||
import { TextEditor } from './TextEditor/TextEditor'
|
||||
import { StepContent } from './StepContent'
|
||||
|
||||
@@ -91,7 +91,7 @@ export const PreviewDrawer = () => {
|
||||
>
|
||||
<TypebotViewer
|
||||
typebot={publicTypebot}
|
||||
onNewBlockVisisble={handleNewBlockVisible}
|
||||
onNewBlockVisible={handleNewBlockVisible}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -35,11 +35,11 @@ import {
|
||||
import {
|
||||
fetcher,
|
||||
insertItemInList,
|
||||
isDefined,
|
||||
omit,
|
||||
preventUserFromRefreshing,
|
||||
} from 'services/utils'
|
||||
import useSWR from 'swr'
|
||||
import { isDefined } from 'utils'
|
||||
import { NewBlockPayload, Coordinates } from './GraphContext'
|
||||
|
||||
const typebotContext = createContext<{
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { isDefined } from 'services/utils'
|
||||
import { isDefined } from 'utils'
|
||||
import { updateUser as updateUserInDb } from 'services/user'
|
||||
import { useToast } from '@chakra-ui/react'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
6
apps/builder/next.config.js
Normal file
6
apps/builder/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,
|
||||
})
|
||||
@@ -32,6 +32,7 @@
|
||||
"framer-motion": "^4",
|
||||
"htmlparser2": "^7.2.0",
|
||||
"kbar": "^0.1.0-beta.24",
|
||||
"micro": "^9.3.4",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^12.0.7",
|
||||
"next-auth": "beta",
|
||||
@@ -50,7 +51,8 @@
|
||||
"styled-components": "^5.3.3",
|
||||
"svg-round-corners": "^0.3.0",
|
||||
"swr": "^1.1.1",
|
||||
"use-debounce": "^7.0.1"
|
||||
"use-debounce": "^7.0.1",
|
||||
"utils": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
@@ -69,6 +71,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"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DashboardFolder, User } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { DashboardFolder } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import aws from 'aws-sdk'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const maxUploadFileSize = 10485760 // 10 MB
|
||||
const handler = async (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
import Stripe from 'stripe'
|
||||
|
||||
const usdPriceIdTest = 'price_1Jc4TQKexUFvKTWyGvsH4Ff5'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { User } from 'db'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
import Stripe from 'stripe'
|
||||
|
||||
const createCheckoutSession = async (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
import Stripe from 'stripe'
|
||||
import Cors from 'micro-cors'
|
||||
import { buffer } from 'micro'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { User } from 'db'
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import { methodNotAllowed } from 'utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { NextApiResponse } from 'next'
|
||||
|
||||
export const methodNotAllowed = (res: NextApiResponse) =>
|
||||
res.status(405).json({ message: 'Method Not Allowed' })
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
firstStepOffsetY,
|
||||
} from 'contexts/GraphContext'
|
||||
import { roundCorners } from 'svg-round-corners'
|
||||
import { isDefined } from './utils'
|
||||
import { isDefined } from 'utils'
|
||||
|
||||
export const computeFlowChartConnectorPath = ({
|
||||
sourcePosition,
|
||||
|
||||
@@ -39,10 +39,6 @@ export const insertItemInList = <T>(
|
||||
newItem: T
|
||||
): T[] => [...arr.slice(0, index), newItem, ...arr.slice(index)]
|
||||
|
||||
export const isDefined = <T>(value: T | undefined | null): value is T => {
|
||||
return <T>value !== undefined && <T>value !== null
|
||||
}
|
||||
|
||||
export const preventUserFromRefreshing = (e: BeforeUnloadEvent) => {
|
||||
e.preventDefault()
|
||||
e.returnValue = ''
|
||||
|
||||
@@ -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