diff --git a/frontend/public/svgs/404.svg b/frontend/public/svgs/404.svg
new file mode 100644
index 00000000..bf4cadbd
--- /dev/null
+++ b/frontend/public/svgs/404.svg
@@ -0,0 +1,12 @@
+
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 0dc848d9..d90f71f4 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -18,6 +18,7 @@ import ExtractProcess from "./pages/ExtractProcess.tsx";
import { SaveTemplate } from "./pages/SaveTemplate.tsx";
import ReviewTemplate from "./pages/ReviewTemplate.tsx";
import SubmissionTemplate from "./pages/SubmissionTemplate.tsx";
+import NotFound from "./pages/404Page.tsx";
@@ -54,6 +55,10 @@ const router = createBrowserRouter([
path: "/extract/submit",
element: ,
},
+ {
+ path: "*",
+ element:
+ }
]);
createRoot(document.getElementById("root")!).render(
diff --git a/frontend/src/pages/404Page.scss b/frontend/src/pages/404Page.scss
new file mode 100644
index 00000000..d5a0ca80
--- /dev/null
+++ b/frontend/src/pages/404Page.scss
@@ -0,0 +1,18 @@
+.error-content {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ font-size: 1rem;
+ background-color: #f8f9fa;
+ margin: 0;
+ padding: 0;
+ font-family: 'Roboto', sans-serif;
+ text-align: center;
+ flex-direction: column;
+}
+
+.error-cta {
+ text-align: center;
+ text-wrap: wrap;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/404Page.test.tsx b/frontend/src/pages/404Page.test.tsx
new file mode 100644
index 00000000..d63ca81e
--- /dev/null
+++ b/frontend/src/pages/404Page.test.tsx
@@ -0,0 +1,47 @@
+// NotFound.test.tsx
+import { render, screen, fireEvent } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import { describe, it, expect, vi } from 'vitest';
+import NotFound from './404Page';
+
+const mockNavigate = vi.fn(); // Create a mock function for navigation
+// Mock the useNavigate hook from react-router-dom
+vi.mock('react-router-dom', async (importOriginal) => {
+ const actual = await importOriginal();
+ return {
+ ...actual as object,
+ useNavigate: () => mockNavigate,
+ };
+});
+describe('NotFound Component', () => {
+ it('should render the NotFound component', () => {
+ render(
+
+
+
+ );
+
+ // Check if the 404 image is rendered
+ expect(screen.getByTestId('404-image')).toBeInTheDocument();
+
+ // Check if the title and description are rendered
+ expect(screen.getByText("Sorry, this page can’t be found")).toBeInTheDocument();
+ expect(screen.getByText("The page you are looking for doesn’t exist or has been moved.")).toBeInTheDocument();
+
+ // Check if the button is rendered
+ expect(screen.getByRole('button', { name: /Back to Previous Page/i })).toBeInTheDocument();
+ });
+
+
+ it('should navigate back when the back button is clicked', () => {
+ render(
+
+
+
+ );
+
+ fireEvent.click(screen.getByText(/back/i));
+
+ expect(mockNavigate).toHaveBeenCalledWith('/');
+ });
+});
diff --git a/frontend/src/pages/404Page.tsx b/frontend/src/pages/404Page.tsx
new file mode 100644
index 00000000..31b569e8
--- /dev/null
+++ b/frontend/src/pages/404Page.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import image from '../../public/svgs/404.svg'
+import { AppHeader } from '../components/AppHeader/AppHeader';
+import { Button } from '@trussworks/react-uswds';
+import { useNavigate } from 'react-router-dom';
+
+import './404Page.scss';
+
+const NotFound: React.FC = () => {
+ const navigate = useNavigate();
+ return (
+
+
+
+
Sorry, this page can’t be found
+
+
The page you are looking for doesn’t exist or has been moved.
+
+
+
+ );
+};
+
+export default NotFound;