Skip to content

Commit

Permalink
Merge pull request #163 from jbrunton/error-boundaries
Browse files Browse the repository at this point in the history
feat: error boundaries
  • Loading branch information
jbrunton authored Aug 31, 2024
2 parents 3aa92ef + 4d1dfba commit a9df5e8
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 55 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: build

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main'}}

on:
push:

Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@chakra-ui/react": "2.4.6",
"@emotion/react": "11.10.5",
"@emotion/styled": "11.10.5",
"@tanstack/react-query": "^4.20.4",
"@tanstack/react-query": "5.53.1",
"axios": "1.3.4",
"date-fns": "2.29.3",
"debug": "4.3.4",
Expand Down
44 changes: 44 additions & 0 deletions client/src/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<HeaderTemplate title='Chat Demo' />
<Container maxWidth='container.lg'>
<Alert status='error' variant='top-accent' height='200px'>
<AlertIcon boxSize='40px' ml='20px' mr='40px' />
<Box>
<AlertTitle mt={4} mb={1} fontSize='lg'>
Uh oh!
</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Box>
</Alert>
</Container>
</>
)
}

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',
}
}
8 changes: 3 additions & 5 deletions client/src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<>
<Header />
<div className='content'>
<Outlet />
</div>
<div className='content'>{children ?? <Outlet />}</div>
</>
)
2 changes: 1 addition & 1 deletion client/src/data/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const useJoinRoom = (roomId: string) => {
return useMutation({
mutationFn: () => joinRoom(roomId),
onSuccess: () => {
client.invalidateQueries(['me'])
client.invalidateQueries({ queryKey: ['me'] })
},
})
}
9 changes: 8 additions & 1 deletion client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 2 additions & 0 deletions client/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Layout />,
errorElement: <ErrorPage />,
children: [
{
path: '/',
Expand Down
24 changes: 24 additions & 0 deletions client/src/shared/navigation/molecules/HeaderTemplate.tsx
Original file line number Diff line number Diff line change
@@ -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<HeaderTemplateProps> = ({ title, onOpen }) => (
<Flex className='header' p='6px' align='center'>
<Heading size={{ base: 'sm', lg: 'md' }} noOfLines={1}>
{title}
</Heading>
<Spacer />
{onOpen ? (
<HStack align='center'>
<IconButton variant='ghost' icon={<DrawerIcon />} aria-label={'Open Menu'} onClick={onOpen} />
</HStack>
) : null}
</Flex>
)

const DrawerIcon = () => <Icon as={AiOutlineMenu} boxSize={5} />
20 changes: 2 additions & 18 deletions client/src/shared/navigation/organisms/Header.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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()
Expand All @@ -40,23 +34,13 @@ export const Header = () => {

return (
<>
<Flex className='header' p='6px' align='center'>
<Heading size={{ base: 'sm', lg: 'md' }} noOfLines={1}>
{roomResponse?.room.name ?? 'Chat Demo'}
</Heading>
<Spacer />
<HStack align='center'>
<IconButton variant='ghost' icon={<DrawerIcon />} aria-label={'Open Menu'} onClick={onOpen} />
</HStack>
</Flex>
<HeaderTemplate title={roomResponse?.room.name ?? 'Chat Demo'} onOpen={onOpen} />

{isOpen ? <DrawerMenu isOpen={isOpen} onClose={onClose} isAuthenticated={isAuthenticated} /> : null}
</>
)
}

const DrawerIcon = () => <Icon as={AiOutlineMenu} boxSize={5} />

type DrawerMenuProps = Required<Pick<UseDisclosureProps, 'onClose' | 'isOpen'>> & {
isAuthenticated: boolean
}
Expand Down
39 changes: 10 additions & 29 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a9df5e8

Please sign in to comment.