feat(notfound): add NotFound page with 404 handling and localization
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m33s

This commit is contained in:
2026-03-04 08:57:21 +01:00
parent cdfc585c8a
commit 6e301e2928
4 changed files with 75 additions and 3 deletions

View File

@@ -18,6 +18,7 @@ import FederationInbox from './pages/FederationInbox';
import FederatedRoomDetail from './pages/FederatedRoomDetail';
import Calendar from './pages/Calendar';
import OAuthCallback from './pages/OAuthCallback';
import NotFound from './pages/NotFound';
export default function App() {
const { user, loading } = useAuth();
@@ -65,8 +66,8 @@ export default function App() {
<Route path="/federation/rooms/:id" element={<FederatedRoomDetail />} />
</Route>
{/* Catch all */}
<Route path="*" element={<Navigate to="/" />} />
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}

View File

@@ -412,7 +412,7 @@
"oauthClientSecret": "Client-Secret",
"oauthClientSecretHint": "Leer lassen, um das bestehende Secret beizubehalten",
"oauthDisplayName": "Button-Beschriftung",
"oauthDisplayNameHint": "Wird auf der Login-Seite angezeigt, z. B. Firmen-SSO"",
"oauthDisplayNameHint": "Wird auf der Login-Seite angezeigt, z. B. Firmen-SSO",
"oauthAutoRegister": "Neue Benutzer automatisch registrieren",
"oauthAutoRegisterHint": "Erstellt automatisch Konten für Benutzer, die sich zum ersten Mal per OAuth anmelden.",
"oauthSaved": "OAuth-Konfiguration gespeichert",
@@ -588,5 +588,11 @@
"note": "Der Termin wurde automatisch aus deinem Kalender entfernt.",
"footer": "Diese Nachricht wurde automatisch von {appName} versendet."
}
},
"notFound": {
"title": "Seite nicht gefunden",
"description": "Die Seite, die du suchst, existiert nicht oder wurde verschoben.",
"goBack": "Zurück",
"goHome": "Zur Startseite"
}
}

View File

@@ -588,5 +588,11 @@
"note": "The event has been automatically removed from your calendar.",
"footer": "This message was sent automatically by {appName}."
}
},
"notFound": {
"title": "Page not found",
"description": "The page you are looking for doesn't exist or has been moved.",
"goBack": "Go back",
"goHome": "Back to home"
}
}

59
src/pages/NotFound.jsx Normal file
View File

@@ -0,0 +1,59 @@
import { Link } from 'react-router-dom';
import { useLanguage } from '../contexts/LanguageContext';
import { Ghost, ArrowLeft, Home } from 'lucide-react';
export default function NotFound() {
const { t } = useLanguage();
return (
<div className="min-h-screen flex items-center justify-center p-6 relative overflow-hidden">
{/* Animated background */}
<div className="absolute inset-0 bg-th-bg">
<div className="absolute inset-0 opacity-20">
<div className="absolute top-1/3 left-1/3 w-96 h-96 bg-th-accent rounded-full blur-[128px] animate-pulse" />
<div className="absolute bottom-1/3 right-1/3 w-72 h-72 bg-purple-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '3s' }} />
</div>
</div>
<div className="relative w-full max-w-md text-center">
<div className="card p-10 backdrop-blur-xl bg-th-card/80 border border-th-border shadow-2xl rounded-2xl">
{/* Ghost icon with subtle animation */}
<div className="flex justify-center mb-6">
<div className="w-20 h-20 bg-th-accent/10 rounded-full flex items-center justify-center animate-bounce" style={{ animationDuration: '2s' }}>
<Ghost size={40} className="text-th-accent" />
</div>
</div>
{/* 404 number */}
<h1 className="text-7xl font-extrabold text-th-text mb-2 tracking-tight">404</h1>
<h2 className="text-xl font-semibold text-th-text mb-2">
{t('notFound.title')}
</h2>
<p className="text-th-text-s mb-8">
{t('notFound.description')}
</p>
{/* Action buttons */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<button
onClick={() => window.history.back()}
className="btn-secondary w-full sm:w-auto px-5 py-2.5 flex items-center justify-center gap-2"
>
<ArrowLeft size={16} />
{t('notFound.goBack')}
</button>
<Link
to="/"
className="btn-primary w-full sm:w-auto px-5 py-2.5 flex items-center justify-center gap-2"
>
<Home size={16} />
{t('notFound.goHome')}
</Link>
</div>
</div>
</div>
</div>
);
}