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()