2
0

🦴 Add viewer backbone

This commit is contained in:
Baptiste Arnaud
2021-12-23 16:31:56 +01:00
parent 9a78a341d2
commit d369b4d941
24 changed files with 576 additions and 182 deletions

View File

@ -0,0 +1,3 @@
body {
margin: 0;
}

View 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>
)

View 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>
)
}

View 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&apos;re looking for doesn&apos;t exist</h2>
</div>
)

View 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>
)
}

View 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

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
import React from 'react'
import { NotFoundPage } from '../layouts/NotFoundPage'
const NotFoundErrorPage = () => <NotFoundPage />
export default NotFoundErrorPage

View 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

View 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} />
}

View File

@ -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