🦴 Add viewer backbone
This commit is contained in:
3
apps/viewer/assets/styles.css
Normal file
3
apps/viewer/assets/styles.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
62
apps/viewer/components/Seo.tsx
Normal file
62
apps/viewer/components/Seo.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import Head from 'next/head'
|
||||
import React from 'react'
|
||||
|
||||
type SEOProps = any
|
||||
|
||||
export const SEO = ({
|
||||
iconUrl,
|
||||
thumbnailUrl,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
chatbotName,
|
||||
}: SEOProps) => (
|
||||
<Head>
|
||||
<title>{title ?? chatbotName}</title>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href={iconUrl ?? 'https://bot.typebot.io/favicon.png'}
|
||||
/>
|
||||
<meta name="title" content={title ?? chatbotName} />
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
description ??
|
||||
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
|
||||
}
|
||||
/>
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={url ?? 'https://bot.typebot.io'} />
|
||||
<meta property="og:title" content={title ?? chatbotName} />
|
||||
<meta property="og:site_name" content={title ?? chatbotName} />
|
||||
<meta
|
||||
property="og:description"
|
||||
content={
|
||||
description ??
|
||||
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
|
||||
}
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
itemProp="image"
|
||||
content={thumbnailUrl ?? 'https://bot.typebot.io/site-preview.png'}
|
||||
/>
|
||||
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={url ?? 'https://bot.typebot.io'} />
|
||||
<meta property="twitter:title" content={title ?? chatbotName} />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content={
|
||||
description ??
|
||||
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.'
|
||||
}
|
||||
/>
|
||||
<meta
|
||||
property="twitter:image"
|
||||
content={thumbnailUrl ?? 'https://bot.typebot.io/site-preview.png'}
|
||||
/>
|
||||
</Head>
|
||||
)
|
29
apps/viewer/layouts/ErrorPage.tsx
Normal file
29
apps/viewer/layouts/ErrorPage.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
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."
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{error === '500' && (
|
||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>500</h1>
|
||||
)}
|
||||
<h2>{errorLabel}</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
16
apps/viewer/layouts/NotFoundPage.tsx
Normal file
16
apps/viewer/layouts/NotFoundPage.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export const NotFoundPage = () => (
|
||||
<div
|
||||
style={{
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>404</h1>
|
||||
<h2>The bot you're looking for doesn't exist</h2>
|
||||
</div>
|
||||
)
|
26
apps/viewer/layouts/TypebotPage.tsx
Normal file
26
apps/viewer/layouts/TypebotPage.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { PublicTypebot, TypebotViewer } from 'bot-engine'
|
||||
import React from 'react'
|
||||
import { SEO } from '../components/Seo'
|
||||
import { ErrorPage } from './ErrorPage'
|
||||
import { NotFoundPage } from './NotFoundPage'
|
||||
|
||||
export type TypebotPageProps = {
|
||||
typebot?: PublicTypebot
|
||||
url: string
|
||||
isIE: boolean
|
||||
}
|
||||
|
||||
export const TypebotPage = ({ typebot, isIE, url }: TypebotPageProps) => {
|
||||
if (!typebot) {
|
||||
return <NotFoundPage />
|
||||
}
|
||||
if (isIE) {
|
||||
return <ErrorPage error={'IE'} />
|
||||
}
|
||||
return (
|
||||
<div style={{ height: '100vh' }}>
|
||||
<SEO url={url} chatbotName={typebot.name} />
|
||||
<TypebotViewer typebot={typebot} />
|
||||
</div>
|
||||
)
|
||||
}
|
15
apps/viewer/libs/prisma.ts
Normal file
15
apps/viewer/libs/prisma.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { PrismaClient } from 'db'
|
||||
|
||||
declare const global: { prisma: PrismaClient }
|
||||
let prisma: PrismaClient
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
if (!global.prisma) {
|
||||
global.prisma = new PrismaClient()
|
||||
}
|
||||
prisma = global.prisma
|
||||
}
|
||||
|
||||
export default prisma
|
@ -11,20 +11,20 @@
|
||||
"dependencies": {
|
||||
"bot-engine": "*",
|
||||
"db": "*",
|
||||
"next": "^12.0.4",
|
||||
"next": "^12.0.7",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.9",
|
||||
"@types/react": "^17.0.35",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@types/node": "^17.0.4",
|
||||
"@types/react": "^17.0.38",
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.0",
|
||||
"eslint": "<8.0.0",
|
||||
"eslint-config-next": "12.0.4",
|
||||
"eslint-config-next": "12.0.7",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.5.4"
|
||||
}
|
||||
}
|
||||
|
5
apps/viewer/pages/404.tsx
Normal file
5
apps/viewer/pages/404.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import React from 'react'
|
||||
import { NotFoundPage } from '../layouts/NotFoundPage'
|
||||
|
||||
const NotFoundErrorPage = () => <NotFoundPage />
|
||||
export default NotFoundErrorPage
|
44
apps/viewer/pages/[publicId].tsx
Normal file
44
apps/viewer/pages/[publicId].tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { PublicTypebot } from 'bot-engine'
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
||||
import { TypebotPage, TypebotPageProps } from '../layouts/TypebotPage'
|
||||
import prisma from '../libs/prisma'
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
let typebot: PublicTypebot | undefined
|
||||
const isIE = /MSIE|Trident/.test(context.req.headers['user-agent'] ?? '')
|
||||
const pathname = context.resolvedUrl.split('?')[0]
|
||||
try {
|
||||
if (!context.req.headers.host) return { props: {} }
|
||||
typebot = await getTypebotFromPublicId(context.query.publicId.toString())
|
||||
if (!typebot) return { props: {} }
|
||||
return {
|
||||
props: {
|
||||
typebot,
|
||||
isIE,
|
||||
url: `https://${context.req.headers.host}${pathname}`,
|
||||
},
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
isIE,
|
||||
url: `https://${context.req.headers.host}${pathname}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const getTypebotFromPublicId = async (
|
||||
publicId: string
|
||||
): Promise<PublicTypebot | undefined> => {
|
||||
const typebot = await prisma.publicTypebot.findUnique({
|
||||
where: { publicId },
|
||||
})
|
||||
return (typebot as unknown as PublicTypebot | undefined) ?? undefined
|
||||
}
|
||||
|
||||
const App = (props: TypebotPageProps) => <TypebotPage {...props} />
|
||||
export default App
|
15
apps/viewer/pages/_app.tsx
Normal file
15
apps/viewer/pages/_app.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import '../assets/styles.css'
|
||||
|
||||
type Props = {
|
||||
Component: React.ComponentType
|
||||
pageProps: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export default function MyApp({ Component, pageProps }: Props): JSX.Element {
|
||||
const { ...componentProps } = pageProps
|
||||
|
||||
return <Component {...componentProps} />
|
||||
}
|
@ -1,7 +1,46 @@
|
||||
import React from 'react'
|
||||
import { PublicTypebot } from 'bot-engine'
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from 'next'
|
||||
import { TypebotPage, TypebotPageProps } from '../layouts/TypebotPage'
|
||||
import prisma from '../libs/prisma'
|
||||
|
||||
const HomePage = () => {
|
||||
return <div>Welcome to "Viewer"!</div>
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
let typebot: PublicTypebot | undefined
|
||||
const isIE = /MSIE|Trident/.test(context.req.headers['user-agent'] ?? '')
|
||||
const pathname = context.resolvedUrl.split('?')[0]
|
||||
try {
|
||||
if (!context.req.headers.host) return { props: {} }
|
||||
typebot = await getTypebotFromUrl(context.req.headers.host)
|
||||
if (!typebot) return { props: {} }
|
||||
return {
|
||||
props: {
|
||||
typebot,
|
||||
isIE,
|
||||
url: `https://${context.req.headers.host}${pathname}`,
|
||||
},
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
isIE,
|
||||
url: `https://${context.req.headers.host}${pathname}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default HomePage
|
||||
const getTypebotFromUrl = async (
|
||||
hostname: string
|
||||
): Promise<PublicTypebot | undefined> => {
|
||||
const publicId = hostname.split('.').shift()
|
||||
if (!publicId) return
|
||||
const typebot = await prisma.publicTypebot.findUnique({
|
||||
where: { publicId },
|
||||
})
|
||||
return (typebot as unknown as PublicTypebot | undefined) ?? undefined
|
||||
}
|
||||
|
||||
const App = (props: TypebotPageProps) => <TypebotPage {...props} />
|
||||
export default App
|
||||
|
Reference in New Issue
Block a user