diff --git a/packages/client/components/error-boundary.tsx b/packages/client/components/error-boundary.tsx new file mode 100644 index 00000000..9c61f345 --- /dev/null +++ b/packages/client/components/error-boundary.tsx @@ -0,0 +1,98 @@ +import React, { ReactNode } from 'react'; +import { Title, Text, Button, Stack } from '@mantine/core'; +import Link from 'next/link'; +import Image from 'next/image'; +import axios from 'axios'; + +type Props = { + children: ReactNode; + appConfig?: any; +}; + +type State = { + hasError: false; +}; + +export default class ErrorBoundary extends React.Component { + constructor(props: Props) { + super(props); + + // Define a state variable to track whether is an error or not + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: any) { + // Update state so the next render will show the fallback UI + + return { hasError: true }; + } + + async componentDidCatch(error: any, errorInfo: any) { + // You can use your own error logging service here + console.log({ error, errorInfo }); + + try { + await axios.post('/api/webhook', { + message: error?.message, + brandName: this.props?.appConfig?.brand?.name, + faviconUrl: this.props?.appConfig?.brand?.faviconUrl, + url: window?.location?.href, + openGraphUrl: this.props?.appConfig?.brand?.openGraphUrl, + hostname: window?.location?.hostname, + }); + } catch (err) { + console.log('Unable to send webhook'); + } + } + + render() { + // Check if the error is thrown + if (this.state.hasError) { + // You can render any custom fallback UI + return ( + + + + + An unknown error has occurred + + + Our development team has been notified and will be working on a + fix. + + + + + + ); + } + + // Return children components in case of no error + return this.props.children; + } +} diff --git a/packages/client/middleware.ts b/packages/client/middleware.ts index b449405e..5543ad2f 100644 --- a/packages/client/middleware.ts +++ b/packages/client/middleware.ts @@ -32,6 +32,7 @@ export function middleware(request: NextRequest) { value: sessionId, path: '/', secure: process.env.NODE_ENV === 'production', + httpOnly: true, }); return response; diff --git a/packages/client/pages/_app.tsx b/packages/client/pages/_app.tsx index c326d524..7ca69e0b 100644 --- a/packages/client/pages/_app.tsx +++ b/packages/client/pages/_app.tsx @@ -22,6 +22,7 @@ import { UpdateLocationModal, } from '../components/organisms/modals'; import { AppProps } from 'next/app'; +import ErrorBoundary from '../components/error-boundary'; function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { const appConfig = useAppConfig(); @@ -53,7 +54,9 @@ function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { }} > - + + + diff --git a/packages/client/pages/api/webhook.ts b/packages/client/pages/api/webhook.ts new file mode 100644 index 00000000..bbb66c4d --- /dev/null +++ b/packages/client/pages/api/webhook.ts @@ -0,0 +1,61 @@ +import { type NextApiHandler } from 'next'; +import axios from 'axios'; +import nookies from 'nookies'; +import z from 'zod'; + +const Webhook: NextApiHandler = async (req, res) => { + switch (req.method) { + case 'POST': + return await POST(req, res); + default: + res.statusCode = 404; + res.send('Not found'); + } +}; + +const POST: NextApiHandler = async (req, res) => { + const cookies = nookies.get({ req }); + const sessionId = cookies['session-id']; + if (process.env.WEBHOOK_ALERT_URL != null && sessionId != null) { + const bodySchema = z.object({ + message: z.string(), + brandName: z.string(), + faviconUrl: z.string(), + url: z.string(), + openGraphUrl: z.string(), + hostname: z.string(), + }); + + const body = await bodySchema.parseAsync(req.body); + + try { + await axios.post(`${process.env.WEBHOOK_ALERT_URL}`, { + embeds: [ + { + title: 'An unhandled error has occurred', + description: body.message, + author: { + name: body.brandName, + icon_url: body.faviconUrl, + }, + url: body.url, + image: { + url: body.openGraphUrl, + }, + timestamp: new Date(), + footer: { + text: body.hostname, + }, + }, + ], + }); + } catch (err) { + console.log('unable to send webhook request.'); + } + } + + res.statusCode = 200; + res.send('Done'); +}; + +export default Webhook; diff --git a/packages/client/public/undraw_bug_fixing.svg b/packages/client/public/undraw_bug_fixing.svg new file mode 100644 index 00000000..02d59907 --- /dev/null +++ b/packages/client/public/undraw_bug_fixing.svg @@ -0,0 +1 @@ + \ No newline at end of file