diff --git a/app/components/error-message.tsx b/app/components/error-message.tsx
index 26618924..1fc33814 100644
--- a/app/components/error-message.tsx
+++ b/app/components/error-message.tsx
@@ -1,27 +1,214 @@
-import { X } from 'lucide-react'
-import { useNavigate } from 'react-router'
-import { Alert, AlertDescription } from './ui/alert'
+import {
+ AlertCircle,
+ ArrowLeft,
+ Home,
+ RefreshCw,
+ ServerCrash,
+ FileQuestion,
+ ShieldX,
+ WifiOff,
+} from 'lucide-react'
+import { useTranslation } from 'react-i18next'
+import { useNavigate, useRouteError, isRouteErrorResponse } from 'react-router'
+import { Button } from './ui/button'
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from './ui/card'
+
+type ErrorType = 'not_found' | 'forbidden' | 'server' | 'network' | 'generic'
export default function ErrorMessage() {
- let navigate = useNavigate()
+ const navigate = useNavigate()
+ const error = useRouteError()
+ const { t } = useTranslation('common')
+
const goBack = () => navigate(-1)
+ const goHome = () => navigate('/')
+ const refresh = () => window.location.reload()
+
+ const getErrorInfo = (): {
+ type: ErrorType
+ status?: number
+ title: string
+ message: string
+ } => {
+ if (isRouteErrorResponse(error)) {
+ const status = error.status
+ const actualMessage = error.data?.message || error.data
+
+ if (status === 404) {
+ return {
+ type: 'not_found',
+ status,
+ title: t('error_not_found_title'),
+ message: actualMessage || t('error_generic_message'),
+ }
+ }
+
+ if (status === 403) {
+ return {
+ type: 'forbidden',
+ status,
+ title: t('error_forbidden_title'),
+ message: actualMessage || t('error_generic_message'),
+ }
+ }
+
+ if (status >= 500) {
+ return {
+ type: 'server',
+ status,
+ title: t('error_server_title'),
+ message: actualMessage || t('error_generic_message'),
+ }
+ }
+
+ return {
+ type: 'generic',
+ status,
+ title: `${status} ${error.statusText}`,
+ message: actualMessage || t('error_generic_message'),
+ }
+ }
+
+ if (typeof error === 'string') {
+ const errorLower = error.toLowerCase()
+
+ let type: ErrorType = 'generic'
+ if (errorLower.includes('not found')) {
+ type = 'not_found'
+ } else if (
+ errorLower.includes('forbidden') ||
+ errorLower.includes('permission')
+ ) {
+ type = 'forbidden'
+ }
+
+ return {
+ type,
+ title: t('error_occurred'),
+ message: error,
+ }
+ }
+
+ if (error instanceof Error) {
+ let type: ErrorType = 'generic'
+
+ if (
+ error.message.includes('fetch') ||
+ error.message.includes('network')
+ ) {
+ type = 'network'
+ }
+
+ return {
+ type,
+ title: t('error_occurred'),
+ message: error.message,
+ }
+ }
+
+ return {
+ type: 'generic',
+ title: t('error_occurred'),
+ message: t('error_generic_message'),
+ }
+ }
+
+ const { type, status, title, message } = getErrorInfo()
+
+ const getIcon = () => {
+ const iconClass = 'h-12 w-12'
+
+ switch (type) {
+ case 'not_found':
+ return
- Oh no, this shouldn't happen, but don't worry, our team is on the case!
- Add some info here.
+ {status} +
+ )} ++ {t('error_help_text')} +
+