From d7c0d4bc953015f5b50b7b678ee382cf27939a75 Mon Sep 17 00:00:00 2001 From: John Brunton Date: Sat, 31 Aug 2024 12:05:08 +0100 Subject: [PATCH] feat: generic error handling --- client/src/ErrorPage.tsx | 44 +++++++++++++++++++ client/src/Layout.tsx | 8 ++-- client/src/main.tsx | 9 +++- client/src/router.tsx | 2 + .../navigation/molecules/HeaderTemplate.tsx | 24 ++++++++++ .../shared/navigation/organisms/Header.tsx | 20 +-------- 6 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 client/src/ErrorPage.tsx create mode 100644 client/src/shared/navigation/molecules/HeaderTemplate.tsx diff --git a/client/src/ErrorPage.tsx b/client/src/ErrorPage.tsx new file mode 100644 index 00000000..1aa878af --- /dev/null +++ b/client/src/ErrorPage.tsx @@ -0,0 +1,44 @@ +import React from 'react' +import { Container, Alert, AlertIcon, AlertTitle, AlertDescription, Box } from '@chakra-ui/react' +import { AxiosError } from 'axios' +import { useRouteError } from 'react-router-dom' +import { HeaderTemplate } from './shared/navigation/molecules/HeaderTemplate' + +export const ErrorPage = () => { + const error = useRouteError() + const { message } = getErrorDetails(error) + return ( + <> + + + + + + + Uh oh! + + {message} + + + + + ) +} + +type ErrorDetails = { + message: string +} + +const getErrorDetails = (error: unknown): ErrorDetails => { + if (error instanceof AxiosError) { + const serverMessage = error.response?.data.message + if (serverMessage) { + return { + message: serverMessage, + } + } + } + return { + message: 'There was an unknown error. Please try again later', + } +} diff --git a/client/src/Layout.tsx b/client/src/Layout.tsx index c8cd9dd9..699beb8d 100644 --- a/client/src/Layout.tsx +++ b/client/src/Layout.tsx @@ -1,12 +1,10 @@ -import React from 'react' +import React, { PropsWithChildren } from 'react' import { Outlet } from 'react-router-dom' import { Header } from './shared/navigation/organisms/Header' -export const Layout = () => ( +export const Layout = ({ children }: PropsWithChildren) => ( <>
-
- -
+
{children ?? }
) diff --git a/client/src/main.tsx b/client/src/main.tsx index 45c145e7..2f0fe17c 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -9,7 +9,14 @@ import App from './App' import { AuthProvider } from './features/auth/provider' import { configureDefaults } from './data/config' -const queryClient = new QueryClient() +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + throwOnError: true, + retry: 0, + }, + }, +}) configureDefaults() diff --git a/client/src/router.tsx b/client/src/router.tsx index b572f745..0b049f91 100644 --- a/client/src/router.tsx +++ b/client/src/router.tsx @@ -7,10 +7,12 @@ import { NewRoomPage } from './features/room/pages/New' import { Layout } from './Layout' import { RequireAuth } from './features/auth/organisms/require-auth' import { Callback } from './features/auth/pages/callback' +import { ErrorPage } from './ErrorPage' export const router = createBrowserRouter([ { element: , + errorElement: , children: [ { path: '/', diff --git a/client/src/shared/navigation/molecules/HeaderTemplate.tsx b/client/src/shared/navigation/molecules/HeaderTemplate.tsx new file mode 100644 index 00000000..29c944bb --- /dev/null +++ b/client/src/shared/navigation/molecules/HeaderTemplate.tsx @@ -0,0 +1,24 @@ +import { Flex, Heading, Spacer, HStack, IconButton, Icon } from '@chakra-ui/react' +import React, { FC } from 'react' +import { AiOutlineMenu } from 'react-icons/ai' + +type HeaderTemplateProps = { + title: string + onOpen?: () => void +} + +export const HeaderTemplate: FC = ({ title, onOpen }) => ( + + + {title} + + + {onOpen ? ( + + } aria-label={'Open Menu'} onClick={onOpen} /> + + ) : null} + +) + +const DrawerIcon = () => diff --git a/client/src/shared/navigation/organisms/Header.tsx b/client/src/shared/navigation/organisms/Header.tsx index c0bb723f..f69428c4 100644 --- a/client/src/shared/navigation/organisms/Header.tsx +++ b/client/src/shared/navigation/organisms/Header.tsx @@ -1,13 +1,7 @@ import React, { useEffect } from 'react' import { useLocation } from 'react-router-dom' import { - Icon, - Flex, - Heading, - Spacer, - HStack, useDisclosure, - IconButton, DrawerOverlay, Drawer, DrawerContent, @@ -20,9 +14,9 @@ import { import { SignInButton } from '../../../features/auth/organisms/sign-in-button' import { useParams } from 'react-router-dom' import { useRoom } from '../../../data/rooms' -import { AiOutlineMenu } from 'react-icons/ai' import { RoomSelector } from './RoomSelector' import { useAuth } from '../../../features/auth' +import { HeaderTemplate } from '../molecules/HeaderTemplate' export const Header = () => { const { isAuthenticated, isLoading: isLoadingAuth } = useAuth() @@ -40,23 +34,13 @@ export const Header = () => { return ( <> - - - {roomResponse?.room.name ?? 'Chat Demo'} - - - - } aria-label={'Open Menu'} onClick={onOpen} /> - - + {isOpen ? : null} ) } -const DrawerIcon = () => - type DrawerMenuProps = Required> & { isAuthenticated: boolean }