From 722c2fff7310b18368d038977e8b7c1f33660f91 Mon Sep 17 00:00:00 2001 From: Rohan-R07 <2007rohanrajesh@gmail.com> Date: Tue, 23 Jun 2026 21:45:44 +0530 Subject: [PATCH] feat(frontend): add custom 404 page for unknown routes --- frontend/src/App.tsx | 5 +- frontend/src/pages/NotFound.tsx | 89 ++++++++++++++++++++++++ frontend/testing/unit/AppRoutes.test.tsx | 32 +++++---- 3 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 frontend/src/pages/NotFound.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9c3e6412b..0f2730009 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import AppShell from './components/AppShell' import Dashboard from './pages/Dashboard' import Toolkit from './pages/Toolkit' @@ -11,6 +11,7 @@ import Settings from './pages/Settings' import Scans from './pages/Scans' import TaskDetails from './pages/TaskDetails' import Workflows from './pages/Workflows' +import NotFound from './pages/NotFound' import ApiKeySetupScreen from './components/ApiKeySetupScreen' import ErrorBoundary from './components/ErrorBoundary' @@ -34,7 +35,7 @@ export function AppRoutes() { } /> } /> - } /> + } /> ) } diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx new file mode 100644 index 000000000..1909e3714 --- /dev/null +++ b/frontend/src/pages/NotFound.tsx @@ -0,0 +1,89 @@ +import React, { useEffect } from 'react' +import { Link } from 'react-router-dom' +import { motion } from 'framer-motion' +import { useTheme } from '../components/ThemeContext' +import { routes } from '../routes' + +export default function NotFound() { + const { theme } = useTheme() + const isLight = theme === 'light' + + useEffect(() => { + document.title = '404 - Page Not Found | SecuScan' + }, []) + + return ( +
+ + {/* Reuse the existing scanline animation class from index.css */} +
+ + {/* Top border decoration */} +
+ + {/* Content */} +
+ {/* SecuScan Branding inside the 404 card */} +
+ SecuScan // Security +
+ + {/* Warning Icon Badge */} +
+ + gpp_bad + +
+ + {/* Error Code & Headings Hierarchy */} +
+

+ 404 +

+ +

+ Page Not Found +

+ +

+ Perimeter Breach // Mismatch +

+
+ + {/* Divider line */} +
+ + {/* Explanation Message */} +

+ The requested page does not exist or has been relocated outside the mapped perimeter matrix. Verification failed. Access denied or target route is not configured. +

+ + {/* Action Button */} + + Return to Dashboard + +
+ +
+ ) +} diff --git a/frontend/testing/unit/AppRoutes.test.tsx b/frontend/testing/unit/AppRoutes.test.tsx index 031ac013e..b6e598641 100644 --- a/frontend/testing/unit/AppRoutes.test.tsx +++ b/frontend/testing/unit/AppRoutes.test.tsx @@ -1,6 +1,7 @@ import { render, screen, waitFor } from '@testing-library/react' import { MemoryRouter, useLocation } from 'react-router-dom' import { AppRoutes } from '../../src/App' +import { ThemeProvider } from '../../src/components/ThemeContext' vi.mock('../../src/api', () => ({ getHealth: vi.fn().mockResolvedValue({ status: 'operational' }), @@ -41,24 +42,29 @@ function PathProbe() { } describe('App route fallback', () => { - it('redirects unknown routes to dashboard', async () => { + it('renders NotFound page for unknown routes', async () => { render( - - - - , + + + + + + , ) await waitFor(() => { - expect(screen.getByTestId('path-probe')).toHaveTextContent('/') + expect(screen.getByTestId('path-probe')).toHaveTextContent('/not-a-real-route') }) + expect(screen.getByText(/Perimeter Breach/i)).toBeInTheDocument() }) it('renders the loaded dashboard summary', async () => { render( - - - , + + + + + , ) expect(await screen.findByText(/Total Findings/i)).toBeInTheDocument() @@ -67,9 +73,11 @@ describe('App route fallback', () => { it('renders the findings workspace', async () => { render( - - - , + + + + + , ) expect(await screen.findByRole('heading', { name: /Findings/i })).toBeInTheDocument()