139 lines
3.7 KiB
TypeScript
139 lines
3.7 KiB
TypeScript
import {
|
|
Link,
|
|
rootRouteId,
|
|
useMatch,
|
|
useRouter,
|
|
useNavigate,
|
|
redirect,
|
|
} from '@tanstack/react-router'
|
|
import type { ErrorComponentProps } from '@tanstack/react-router'
|
|
import {
|
|
Box,
|
|
Button as MantineButton,
|
|
Text,
|
|
Stack,
|
|
Group,
|
|
Collapse,
|
|
Code,
|
|
Container,
|
|
Center
|
|
} from '@mantine/core'
|
|
import { useDisclosure } from '@mantine/hooks'
|
|
import { useEffect } from 'react'
|
|
import toast from '@/lib/sonner'
|
|
import { logger } from '@/lib/logger'
|
|
import { XCircleIcon, WarningIcon } from '@phosphor-icons/react'
|
|
import Button from './button'
|
|
|
|
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
|
|
const router = useRouter()
|
|
const navigate = useNavigate()
|
|
const isRoot = useMatch({
|
|
strict: false,
|
|
select: (state) => state.id === rootRouteId,
|
|
})
|
|
const [detailsOpened, { toggle: toggleDetails }] = useDisclosure(false)
|
|
|
|
const errorMessage = error?.message || 'Unknown error'
|
|
const errorStack = error?.stack || 'No stack trace available'
|
|
|
|
useEffect(() => {
|
|
logger.error('DefaultCatchBoundary | ', error)
|
|
|
|
if (errorMessage.toLowerCase().includes('unauthenticated')) {
|
|
toast.error('You\'ve been logged out')
|
|
router.history.push('/login')
|
|
throw redirect({ to: '/login' })
|
|
}
|
|
}, [error, errorMessage, navigate])
|
|
|
|
if (errorMessage.toLowerCase().includes('unauthorized')) {
|
|
return (
|
|
<Container size="sm" py="xl">
|
|
<Center>
|
|
<Stack align="center" gap="md">
|
|
<XCircleIcon size={64} color="var(--mantine-color-red-6)" />
|
|
<Text size="xl" fw={600}>Access Denied</Text>
|
|
<Text c="dimmed" ta="center">
|
|
You don't have permission to access this page.
|
|
</Text>
|
|
<Group gap="sm" mt="md">
|
|
<Button
|
|
variant="light"
|
|
onClick={() => window.history.back()}
|
|
>
|
|
Go Back
|
|
</Button>
|
|
<MantineButton
|
|
component={Link}
|
|
to="/"
|
|
variant="filled"
|
|
>
|
|
Home
|
|
</MantineButton>
|
|
</Group>
|
|
</Stack>
|
|
</Center>
|
|
</Container>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Container size="sm" py="xl">
|
|
<Center>
|
|
<Stack align="center" gap="md" w="100%">
|
|
<WarningIcon size={64} color="var(--mantine-color-red-6)" />
|
|
|
|
<Text size="xl" fw={600}>Something went wrong</Text>
|
|
|
|
<Text c="dimmed" ta="center">
|
|
An error occurred while loading this page.
|
|
</Text>
|
|
|
|
<Box w="100%" mt="md">
|
|
<Text size="sm" c="dimmed" mb="xs">Error: {errorMessage}</Text>
|
|
<Button
|
|
variant="subtle"
|
|
size="compact-sm"
|
|
onClick={toggleDetails}
|
|
fullWidth
|
|
>
|
|
{detailsOpened ? 'Hide' : 'Show'} details
|
|
</Button>
|
|
<Collapse in={detailsOpened}>
|
|
<Code block mt="sm" p="sm" style={{ fontSize: '11px' }}>
|
|
{errorStack}
|
|
</Code>
|
|
</Collapse>
|
|
</Box>
|
|
|
|
<Group gap="sm" mt="lg">
|
|
<Button
|
|
variant="light"
|
|
onClick={() => router.invalidate()}
|
|
>
|
|
Retry
|
|
</Button>
|
|
{isRoot ? (
|
|
<MantineButton
|
|
component={Link}
|
|
to="/"
|
|
variant="filled"
|
|
>
|
|
Home
|
|
</MantineButton>
|
|
) : (
|
|
<Button
|
|
variant="filled"
|
|
onClick={() => window.history.back()}
|
|
>
|
|
Go Back
|
|
</Button>
|
|
)}
|
|
</Group>
|
|
</Stack>
|
|
</Center>
|
|
</Container>
|
|
)
|
|
}
|