diff --git a/src/App.tsx b/src/App.tsx
index a0778c3cb..5877dfe1a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,21 +1,26 @@
+import { LocalizationProvider } from '@mui/x-date-pickers';
+import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
+import type { Router } from '@remix-run/router';
import {
QueryCache,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
-import React from 'react';
-// import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
-import { LocalizationProvider } from '@mui/x-date-pickers';
-import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
-import type { Router } from '@remix-run/router';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { AxiosError } from 'axios';
import { enGB } from 'date-fns/locale/en-GB';
+import React from 'react';
import {
RouterProvider,
createBrowserRouter,
type RouteObject,
} from 'react-router-dom';
-import AdminPage from './admin/admin.component';
+import AdminCardView from './admin/adminCardView.component';
+import AdminLayout, {
+ AdminErrorComponent,
+} from './admin/adminLayout.component';
+import Units from './admin/units/units.component';
+import UsageStatuses from './admin/usageStatuses/usageStatuses.component';
import {
clearFailedAuthRequestsQueue,
retryFailedAuthRequests,
@@ -49,7 +54,9 @@ import ViewTabs from './view/viewTabs.component';
export const paths = {
any: '*',
root: '/',
- admin: '/admin-ims/*',
+ admin: '/admin-ims',
+ adminUnits: '/admin-ims/units',
+ adminUsageStatuses: '/admin-ims/usage-statuses',
homepage: '/ims',
catalogue: '/catalogue/*',
systems: '/systems/*',
@@ -85,7 +92,19 @@ const routeObject: RouteObject[] = [
children: [
{ path: paths.root, Component: HomePage },
{ path: paths.homepage, Component: HomePage },
- { path: paths.admin, Component: AdminPage },
+ {
+ path: paths.admin,
+ Component: AdminLayout,
+ children: [
+ { index: true, Component: AdminCardView },
+ { path: paths.adminUnits, Component: Units },
+ { path: paths.adminUsageStatuses, Component: UsageStatuses },
+ {
+ path: '*',
+ Component: AdminErrorComponent,
+ },
+ ],
+ },
{ path: paths.catalogue, Component: Catalogue },
{
path: paths.catalogueItem,
@@ -100,11 +119,14 @@ const routeObject: RouteObject[] = [
{
path: paths.manufacturers,
Component: ManufacturerLayout,
- loader: manufacturerLayoutLoader(queryClient),
ErrorBoundary: ManufacturerLayoutErrorComponent,
children: [
{ index: true, Component: ManufacturerTable },
- { path: paths.manufacturer, Component: ManufacturerLandingPage },
+ {
+ path: paths.manufacturer,
+ Component: ManufacturerLandingPage,
+ loader: manufacturerLayoutLoader(queryClient),
+ },
{
path: '*',
Component: ManufacturerErrorComponent,
@@ -164,7 +186,7 @@ export function Layout() {
}
>
- {/* */}
+
diff --git a/src/admin/__snapshots__/adminCardView.component.test.tsx.snap b/src/admin/__snapshots__/adminCardView.component.test.tsx.snap
new file mode 100644
index 000000000..56b271aad
--- /dev/null
+++ b/src/admin/__snapshots__/adminCardView.component.test.tsx.snap
@@ -0,0 +1,86 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`AdminCardView > renders admin card view correctly 1`] = `
+
+
+
+`;
diff --git a/src/admin/__snapshots__/adminLayout.component.test.tsx.snap b/src/admin/__snapshots__/adminLayout.component.test.tsx.snap
new file mode 100644
index 000000000..d72044ac0
--- /dev/null
+++ b/src/admin/__snapshots__/adminLayout.component.test.tsx.snap
@@ -0,0 +1,248 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Admin Error Component > renders Admin error page correctly 1`] = `
+
+
+
+ Invalid Admin Route
+
+
+ The admin route you are trying to access doesn't exist. Please click the Home button to navigate back to the Admin Home page.
+
+
+
+`;
+
+exports[`Admin Layout > renders admin layout page correctly 1`] = `
+
+
+
+`;
+
+exports[`Admin Layout > renders units breadcrumbs correctly 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Admin Layout > renders usage statuses breadcrumbs correctly 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/admin/admin.component.test.tsx b/src/admin/admin.component.test.tsx
deleted file mode 100644
index 88e6250a1..000000000
--- a/src/admin/admin.component.test.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { screen, waitFor } from '@testing-library/react';
-import { renderComponentWithRouterProvider } from '../testUtils';
-import AdminPage from './admin.component';
-
-const mockedUseNavigate = vi.fn();
-
-vi.mock('react-router-dom', async () => ({
- ...(await vi.importActual('react-router-dom')),
- useNavigate: () => mockedUseNavigate,
-}));
-
-describe('AdminPage', () => {
- const createView = (path: string) => {
- return renderComponentWithRouterProvider(, 'admin', path);
- };
-
- it('renders admin page correctly', async () => {
- createView('/admin-ims');
-
- await waitFor(() => {
- expect(screen.getByText('Units')).toBeInTheDocument();
- });
- expect(screen.getByText('Usage Statuses')).toBeInTheDocument();
- });
-
- it('renders no results page for invalid url', async () => {
- createView('/admin-ims/testFake');
-
- await waitFor(() => {
- expect(screen.getByText('No results found')).toBeInTheDocument();
- });
- expect(
- screen.getByText(
- `The admin URL route you're trying to access doesn't exist. Please return to the homepage by clicking the home button at the top left of your screen.`
- )
- ).toBeInTheDocument();
- });
-});
diff --git a/src/admin/admin.component.tsx b/src/admin/admin.component.tsx
deleted file mode 100644
index 87bae5d64..000000000
--- a/src/admin/admin.component.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import {
- Box,
- Button,
- Card,
- CardContent,
- Grid,
- Typography,
-} from '@mui/material';
-import React from 'react';
-import { Link, useLocation, useNavigate } from 'react-router-dom';
-import { BreadcrumbsInfo } from '../api/api.types';
-import Breadcrumbs from '../view/breadcrumbs.component';
-import Units from './units/units.component';
-import UsageStatuses from './usageStatuses/usageStatuses.component';
-
-export const useNavigateToAdminFunction = () => {
- const navigate = useNavigate();
-
- return React.useCallback(
- (newPath: string | null) => {
- navigate(`/admin-ims${newPath ? `/${newPath}` : ''}`);
- },
- [navigate]
- );
-};
-
-// returns the admin function from the path (null when just on adminPage)
-export const useGetAdminPageName = (): string | null => {
- const location = useLocation();
-
- return React.useMemo(() => {
- let adminPageName: string | null = location.pathname.replace(
- '/admin-ims',
- ''
- );
- adminPageName =
- adminPageName === '' ? null : adminPageName.replace('/', '');
- return adminPageName;
- }, [location.pathname]);
-};
-
-const adminBreadCrumbsTrails: { [key: string]: [string, string] } = {
- ['units']: ['units', 'Units'],
- ['usage-statuses']: ['usage-statuses', 'Usage statuses'],
-};
-
-function AdminPage() {
- const navigateToAdminFunction = useNavigateToAdminFunction();
- const adminPageName = useGetAdminPageName();
-
- const adminBreadCrumbs: BreadcrumbsInfo | undefined = adminPageName
- ? {
- trail: [adminBreadCrumbsTrails[adminPageName] ?? ['', '']],
- full_trail: true,
- }
- : undefined;
-
- return (
-
-
-
-
- navigateToAdminFunction(null)}
- homeLocation="Admin"
- />
-
-
-
-
- {adminPageName === null && (
-
-
-
-
-
-
-
-
-
-
- )}
-
- {adminPageName === 'units' && }
- {adminPageName === 'usage-statuses' && }
- {adminPageName !== null &&
- adminPageName !== 'units' &&
- adminPageName !== 'usage-statuses' && (
-
-
- No results found
-
-
- {`The admin URL route you're trying to access doesn't exist. Please return to the homepage by clicking the home button at the top left of your screen.`}
-
-
- )}
-
- );
-}
-
-export default AdminPage;
diff --git a/src/admin/adminCardView.component.test.tsx b/src/admin/adminCardView.component.test.tsx
new file mode 100644
index 000000000..a9566fe2d
--- /dev/null
+++ b/src/admin/adminCardView.component.test.tsx
@@ -0,0 +1,18 @@
+import { screen, waitFor } from '@testing-library/react';
+import { renderComponentWithRouterProvider } from '../testUtils';
+import AdminCardView from './adminCardView.component';
+
+describe('AdminCardView', () => {
+ const createView = () => {
+ return renderComponentWithRouterProvider();
+ };
+ it('renders admin card view correctly', async () => {
+ const view = createView();
+
+ await waitFor(() => {
+ expect(screen.getByText('Units')).toBeInTheDocument();
+ });
+
+ expect(view.asFragment()).toMatchSnapshot();
+ });
+});
diff --git a/src/admin/adminCardView.component.tsx b/src/admin/adminCardView.component.tsx
new file mode 100644
index 000000000..660b818a4
--- /dev/null
+++ b/src/admin/adminCardView.component.tsx
@@ -0,0 +1,93 @@
+import { Button, Card, CardContent, Grid, Typography } from '@mui/material';
+import { Link } from 'react-router-dom';
+
+function AdminCardView() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default AdminCardView;
diff --git a/src/admin/adminLayout.component.test.tsx b/src/admin/adminLayout.component.test.tsx
new file mode 100644
index 000000000..08f0dcb02
--- /dev/null
+++ b/src/admin/adminLayout.component.test.tsx
@@ -0,0 +1,90 @@
+import { screen, waitFor } from '@testing-library/react';
+import userEvent, { UserEvent } from '@testing-library/user-event';
+import { paths } from '../App';
+import { renderComponentWithRouterProvider } from '../testUtils';
+import AdminLayout, { AdminErrorComponent } from './adminLayout.component';
+
+const mockedUseNavigate = vi.fn();
+
+vi.mock('react-router-dom', async () => ({
+ ...(await vi.importActual('react-router-dom')),
+ useNavigate: () => mockedUseNavigate,
+}));
+
+describe('Admin Layout', () => {
+ let user: UserEvent;
+
+ beforeEach(() => {
+ user = userEvent.setup();
+ });
+ const createView = (path: string, urlPathKey: keyof typeof paths) => {
+ return renderComponentWithRouterProvider(, urlPathKey, path);
+ };
+
+ it('renders admin layout page correctly', async () => {
+ const view = createView('/admin-ims', 'admin');
+
+ await waitFor(() => {
+ expect(
+ screen.getByRole('button', {
+ name: 'navigate to admin home',
+ })
+ ).toBeInTheDocument();
+ });
+
+ expect(view.asFragment()).toMatchSnapshot();
+ });
+
+ it('renders units breadcrumbs correctly', async () => {
+ const view = createView('/admin-ims/units', 'adminUnits');
+
+ await waitFor(() => {
+ expect(screen.getByText('Units')).toBeInTheDocument();
+ });
+
+ expect(view.asFragment()).toMatchSnapshot();
+ });
+
+ it('renders usage statuses breadcrumbs correctly', async () => {
+ const view = createView('/admin-ims/usage-statuses', 'adminUsageStatuses');
+
+ await waitFor(() => {
+ expect(screen.getByText('Usage statuses')).toBeInTheDocument();
+ });
+
+ expect(view.asFragment()).toMatchSnapshot();
+ });
+
+ it('calls useNavigate when the home button is clicked', async () => {
+ createView('/admin-ims/usage-statuses', 'adminUsageStatuses');
+
+ await waitFor(() => {
+ expect(screen.getByText('Usage statuses')).toBeInTheDocument();
+ });
+
+ const homeButton = screen.getByRole('button', {
+ name: 'navigate to admin home',
+ });
+
+ await user.click(homeButton);
+
+ expect(mockedUseNavigate).toHaveBeenCalledTimes(1);
+ expect(mockedUseNavigate).toHaveBeenCalledWith('/admin-ims');
+ });
+});
+
+describe('Admin Error Component', () => {
+ const createView = () => {
+ return renderComponentWithRouterProvider();
+ };
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('renders Admin error page correctly', async () => {
+ const view = createView();
+
+ expect(view.asFragment()).toMatchSnapshot();
+ });
+});
diff --git a/src/admin/adminLayout.component.tsx b/src/admin/adminLayout.component.tsx
new file mode 100644
index 000000000..d0f31630d
--- /dev/null
+++ b/src/admin/adminLayout.component.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { Outlet, useLocation } from 'react-router-dom';
+import { BreadcrumbsInfo } from '../api/api.types';
+import { paths } from '../App';
+import BaseLayoutHeader from '../common/baseLayoutHeader.component';
+import ErrorPage from '../common/errorPage.component';
+
+export const AdminErrorComponent = () => {
+ return (
+
+ );
+};
+
+// returns the admin function from the path (null when just on adminPage)
+export const useGetAdminPageName = (): string | null => {
+ const location = useLocation();
+
+ return React.useMemo(() => {
+ const adminPageName: string | null = location.pathname.replace(
+ paths.admin,
+ ''
+ );
+ return adminPageName === '' ? null : adminPageName.replace('/', '');
+ }, [location.pathname]);
+};
+
+const adminBreadCrumbsTrails: { [key: string]: [string, string] } = {
+ ['units']: ['units', 'Units'],
+ ['usage-statuses']: ['usage-statuses', 'Usage statuses'],
+};
+
+function AdminLayout() {
+ const adminPageName = useGetAdminPageName();
+
+ const adminBreadCrumbs: BreadcrumbsInfo | undefined = adminPageName
+ ? {
+ trail: [adminBreadCrumbsTrails[adminPageName] ?? ['', '']],
+ full_trail: true,
+ }
+ : undefined;
+
+ return (
+
+
+
+ );
+}
+
+export default AdminLayout;
diff --git a/src/admin/units/__snapshots__/units.component.test.tsx.snap b/src/admin/units/__snapshots__/units.component.test.tsx.snap
index 22d2c0ecd..9554870ed 100644
--- a/src/admin/units/__snapshots__/units.component.test.tsx.snap
+++ b/src/admin/units/__snapshots__/units.component.test.tsx.snap
@@ -214,32 +214,6 @@ exports[`Units > renders table correctly 1`] = `
-
renders table correctly 1`] = `
-
{
it('renders table correctly', async () => {
const view = createView();
+ await waitFor(() => {
+ expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
+ });
+
await waitFor(() => {
expect(screen.getByText('megapixels')).toBeInTheDocument();
});