Skip to content

Commit 9af938e

Browse files
Merge pull request #1228 from ral-facilities/handle-404-errors-with-react-router-manufacturers
Handle 404 errors with React Router for manufacturers
2 parents 4f88339 + 41d0568 commit 9af938e

11 files changed

+305
-72
lines changed

cypress/e2e/with_mock_data/manufacturers.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ describe('Manufacturer', () => {
379379
cy.visit('/manufacturers/invalid');
380380

381381
cy.findByText(
382-
`This manufacturer doesn't exist. Please click the Home button to navigate to the manufacturer table`
382+
`The manufacturer route you are trying to access doesn't exist. Please click the Home button to navigate back to the Manufacturer Home page.`
383383
).should('exist');
384384

385385
cy.findByRole('button', { name: 'navigate to manufacturers home' }).click();

src/App.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import React from 'react';
77
// import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
88
import { LocalizationProvider } from '@mui/x-date-pickers';
99
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
10+
import type { Router } from '@remix-run/router';
1011
import { AxiosError } from 'axios';
1112
import { enGB } from 'date-fns/locale/en-GB';
12-
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
13+
import {
14+
RouterProvider,
15+
createBrowserRouter,
16+
type RouteObject,
17+
} from 'react-router-dom';
1318
import AdminPage from './admin/admin.component';
1419
import {
1520
clearFailedAuthRequestsQueue,
@@ -18,15 +23,18 @@ import {
1823
import { MicroFrontendId } from './app.types';
1924
import Catalogue from './catalogue/catalogue.component';
2025
import CatalogueItemsLandingPage from './catalogue/items/catalogueItemsLandingPage.component';
21-
import ErrorPage from './common/errorPage.component';
2226
import ConfigProvider from './configProvider.component';
2327
import handleIMS_APIError from './handleIMS_APIError';
2428
import { HomePage } from './homePage/homePage.component';
2529
import IMSThemeProvider from './imsThemeProvider.component';
2630
import Items from './items/items.component';
2731
import ItemsLandingPage from './items/itemsLandingPage.component';
2832
import ManufacturerLandingPage from './manufacturer/manufacturerLandingPage.component';
29-
import ManufacturerLayout from './manufacturer/manufacturerLayout.component';
33+
import ManufacturerLayout, {
34+
ManufacturerErrorComponent,
35+
ManufacturerLayoutErrorComponent,
36+
manufacturerLayoutLoader,
37+
} from './manufacturer/manufacturerLayout.component';
3038
import ManufacturerTable from './manufacturer/manufacturerTable.component';
3139
import Preloader from './preloader/preloader.component';
3240
import retryIMS_APIErrors from './retryIMS_APIErrors';
@@ -71,7 +79,7 @@ const queryClient = new QueryClient({
7179
},
7280
});
7381

74-
const router = createBrowserRouter([
82+
const routeObject: RouteObject[] = [
7583
{
7684
Component: Layout,
7785
children: [
@@ -92,25 +100,34 @@ const router = createBrowserRouter([
92100
{
93101
path: paths.manufacturers,
94102
Component: ManufacturerLayout,
103+
loader: manufacturerLayoutLoader(queryClient),
104+
ErrorBoundary: ManufacturerLayoutErrorComponent,
95105
children: [
96106
{ index: true, Component: ManufacturerTable },
97107
{ path: paths.manufacturer, Component: ManufacturerLandingPage },
98108
{
99109
path: '*',
100-
Component: () => (
101-
<ErrorPage
102-
boldErrorText="Invalid Manufacturer Route"
103-
errorText="The manufacturer route you are trying to access doesn't exist. Please click the Home button to navigate back to the Manufacturer Home page."
104-
/>
105-
),
110+
Component: ManufacturerErrorComponent,
106111
},
107112
],
108113
},
109114
],
110115
},
111-
]);
116+
];
117+
118+
let router: Router;
119+
const isUsingMSW =
120+
import.meta.env.DEV || import.meta.env.VITE_APP_INCLUDE_MSW === 'true';
121+
122+
if (!isUsingMSW) router = createBrowserRouter(routeObject);
123+
124+
// If the application is using MSW (Mock Service Worker),
125+
// it creates the router using `createBrowserRouter` within the App so it can wait for MSW to load. This is necessary
126+
// because MSW needs to be running before the router is created to handle requests properly in the loader. In a production
127+
// environment, this is not needed.
112128

113129
export default function App() {
130+
if (isUsingMSW) router = createBrowserRouter(routeObject);
114131
return <RouterProvider router={router} />;
115132
}
116133

src/api/manufacturers.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
queryOptions,
23
useMutation,
34
UseMutationResult,
45
useQueries,
@@ -75,15 +76,18 @@ const getManufacturer = async (id: string): Promise<Manufacturer> => {
7576
.get(`/v1/manufacturers/${id}`)
7677
.then((response) => response.data);
7778
};
78-
79-
export const useGetManufacturer = (
80-
id?: string | null
81-
): UseQueryResult<Manufacturer, AxiosError> => {
82-
return useQuery({
79+
export const getManufacturerQuery = (id?: string | null, retry?: boolean) =>
80+
queryOptions<Manufacturer, AxiosError>({
8381
queryKey: ['Manufacturer', id],
8482
queryFn: () => getManufacturer(id ?? ''),
8583
enabled: !!id,
84+
retry: retry ? false : undefined,
8685
});
86+
87+
export const useGetManufacturer = (
88+
id?: string | null
89+
): UseQueryResult<Manufacturer, AxiosError> => {
90+
return useQuery(getManufacturerQuery(id));
8791
};
8892

8993
export const useGetManufacturerIds = (

src/app.types.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ export interface TransferState {
2525
state: 'success' | 'error' | 'information';
2626
}
2727

28-
// ------------------------------------ CATALOGUE CATEGORIES ------------------------------------
28+
export enum RoutesHomeLocation {
29+
Catalogue = 'catalogue',
30+
Admin = 'admin-ims',
31+
Systems = 'systems',
32+
Manufacturers = 'manufacturers',
33+
}
34+
export type RoutesHomeLocationType = keyof typeof RoutesHomeLocation;
35+
2936
export interface AllowedValuesList {
3037
type: 'list';
3138
values: {
@@ -34,6 +41,9 @@ export interface AllowedValuesList {
3441
values: { av_placement_id: string; value: any }[];
3542
};
3643
}
44+
45+
// ------------------------------------ CATALOGUE CATEGORIES ------------------------------------
46+
3747
export type AllowedValues = AllowedValuesList;
3848

3949
export interface AddCatalogueCategoryProperty {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Box, Grid } from '@mui/material';
2+
import React from 'react';
3+
import { useNavigate } from 'react-router-dom';
4+
import type { BreadcrumbsInfo } from '../api/api.types';
5+
import { RoutesHomeLocation, type RoutesHomeLocationType } from '../app.types';
6+
import Breadcrumbs from '../view/breadcrumbs.component';
7+
8+
export interface BaseLayoutHeaderProps {
9+
breadcrumbsInfo?: BreadcrumbsInfo;
10+
children: React.ReactNode;
11+
homeLocation: RoutesHomeLocationType;
12+
}
13+
14+
function BaseLayoutHeader(props: BaseLayoutHeaderProps) {
15+
const { breadcrumbsInfo, children, homeLocation } = props;
16+
const navigate = useNavigate();
17+
const onChangeNode = React.useCallback(
18+
(id: string | null) => {
19+
navigate(`/${RoutesHomeLocation[homeLocation]}${id ? `/${id}` : ''}`);
20+
},
21+
[homeLocation, navigate]
22+
);
23+
return (
24+
<Box height="100%" width="100%">
25+
<Grid
26+
container
27+
alignItems="center"
28+
sx={{
29+
justifyContent: 'left',
30+
paddingLeft: 0.5,
31+
position: 'sticky',
32+
top: 0,
33+
backgroundColor: 'background.default',
34+
zIndex: 1000,
35+
width: '100%',
36+
paddingTop: 2.5,
37+
paddingBottom: 2.5,
38+
}}
39+
>
40+
<Breadcrumbs
41+
onChangeNode={onChangeNode}
42+
onChangeNavigateHome={() => onChangeNode(null)}
43+
breadcrumbsInfo={breadcrumbsInfo}
44+
homeLocation={homeLocation}
45+
/>
46+
</Grid>
47+
{children}
48+
</Box>
49+
);
50+
}
51+
52+
export default BaseLayoutHeader;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Manufacturer Layout Error Component > renders manufacturer error page correctly 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="MuiBox-root css-v2kfba"
7+
>
8+
<div
9+
class="MuiGrid-root MuiGrid-container css-1r501zp-MuiGrid-root"
10+
>
11+
<div
12+
class="MuiBox-root css-70qvj9"
13+
>
14+
<span
15+
aria-label="Manufacturers Home"
16+
class=""
17+
data-mui-internal-clone-element="true"
18+
>
19+
<button
20+
aria-label="navigate to manufacturers home"
21+
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeMedium css-78trlr-MuiButtonBase-root-MuiIconButton-root"
22+
tabindex="0"
23+
type="button"
24+
>
25+
<svg
26+
aria-hidden="true"
27+
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
28+
data-testid="HomeIcon"
29+
focusable="false"
30+
viewBox="0 0 24 24"
31+
>
32+
<path
33+
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
34+
/>
35+
</svg>
36+
<span
37+
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
38+
>
39+
<span
40+
class="css-y4cjyz-MuiTouchRipple-ripple MuiTouchRipple-ripple MuiTouchRipple-rippleVisible"
41+
style="width: 1px; height: 1px; top: -0.5px; left: -0.5px;"
42+
>
43+
<span
44+
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
45+
/>
46+
</span>
47+
</span>
48+
</button>
49+
</span>
50+
<nav
51+
aria-label="breadcrumb"
52+
class="MuiTypography-root MuiTypography-body1 MuiBreadcrumbs-root css-1mc59t7-MuiTypography-root-MuiBreadcrumbs-root"
53+
>
54+
<ol
55+
class="MuiBreadcrumbs-ol css-4pdmu4-MuiBreadcrumbs-ol"
56+
/>
57+
</nav>
58+
</div>
59+
</div>
60+
<div
61+
class="MuiBox-root css-7gabc3"
62+
>
63+
<p
64+
class="MuiTypography-root MuiTypography-body1 css-11gsi37-MuiTypography-root"
65+
>
66+
Invalid Manufacturer Route
67+
</p>
68+
<p
69+
class="MuiTypography-root MuiTypography-body1 css-1uwgr7b-MuiTypography-root"
70+
>
71+
The manufacturer route you are trying to access doesn't exist. Please click the Home button to navigate back to the Manufacturer Home page.
72+
</p>
73+
</div>
74+
</div>
75+
</DocumentFragment>
76+
`;

src/manufacturer/manufacturerLandingPage.component.test.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,6 @@ describe('Manufacturer Landing page', () => {
4242
expect(screen.getAllByText('None')[1]).toBeInTheDocument();
4343
});
4444

45-
it('shows no manufacturer page correctly', async () => {
46-
createView('/manufacturers/invalid');
47-
48-
await waitFor(() => {
49-
expect(
50-
screen.getByText(
51-
`This manufacturer doesn't exist. Please click the Home button to navigate to the manufacturer table`
52-
)
53-
).toBeInTheDocument();
54-
});
55-
});
56-
5745
it('shows the loading indicator', async () => {
5846
createView('/manufacturers/1');
5947

src/manufacturer/manufacturerLandingPage.component.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import React from 'react';
1212
import { useParams } from 'react-router-dom';
1313
import { useGetManufacturer } from '../api/manufacturers';
14-
import ErrorPage from '../common/errorPage.component';
1514
import { formatDateTimeStrings } from '../utils';
1615
import ManufacturerDialog from './manufacturerDialog.component';
1716

@@ -201,14 +200,7 @@ function ManufacturerLandingPage() {
201200
</Grid>
202201
</Grid>
203202
)}
204-
{!manufacturerDataLoading ? (
205-
!manufacturerData && (
206-
<ErrorPage
207-
boldErrorText="No result found"
208-
errorText={`This manufacturer doesn't exist. Please click the Home button to navigate to the manufacturer table`}
209-
/>
210-
)
211-
) : (
203+
{manufacturerDataLoading && (
212204
<Box sx={{ width: '100%' }}>
213205
<LinearProgress />
214206
</Box>

0 commit comments

Comments
 (0)