From d4218d550d47fcafd555a3e7a556985d34643765 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 13:01:43 -0700
Subject: [PATCH 01/11] Upgrade react-router-dom to access useBlocker()
---
met-web/package-lock.json | 56 +++++++++++++++++++--------------------
met-web/package.json | 2 +-
2 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/met-web/package-lock.json b/met-web/package-lock.json
index 1f2a20c6f..c6d7ff152 100644
--- a/met-web/package-lock.json
+++ b/met-web/package-lock.json
@@ -74,7 +74,7 @@
"react-map-gl": "^7.0.21",
"react-player": "^2.12.0",
"react-redux": "^7.2.8",
- "react-router-dom": "^6.10.0",
+ "react-router-dom": "^6.23.1",
"react-scripts": "^5.0.1",
"react-svg": "^15.0.1",
"recharts": "^2.4.3",
@@ -4815,11 +4815,11 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
- "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==",
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
+ "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
"engines": {
- "node": ">=14"
+ "node": ">=14.0.0"
}
},
"node_modules/@rollup/plugin-babel": {
@@ -21103,29 +21103,29 @@
}
},
"node_modules/react-router": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz",
- "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
+ "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
"dependencies": {
- "@remix-run/router": "1.5.0"
+ "@remix-run/router": "1.16.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz",
- "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
+ "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
"dependencies": {
- "@remix-run/router": "1.5.0",
- "react-router": "6.10.0"
+ "@remix-run/router": "1.16.1",
+ "react-router": "6.23.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
@@ -29626,9 +29626,9 @@
}
},
"@remix-run/router": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
- "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg=="
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
+ "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig=="
},
"@rollup/plugin-babel": {
"version": "5.3.1",
@@ -41818,20 +41818,20 @@
}
},
"react-router": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz",
- "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
+ "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
"requires": {
- "@remix-run/router": "1.5.0"
+ "@remix-run/router": "1.16.1"
}
},
"react-router-dom": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz",
- "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
+ "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
"requires": {
- "@remix-run/router": "1.5.0",
- "react-router": "6.10.0"
+ "@remix-run/router": "1.16.1",
+ "react-router": "6.23.1"
}
},
"react-scripts": {
diff --git a/met-web/package.json b/met-web/package.json
index 4607e85e3..dec3a8b97 100644
--- a/met-web/package.json
+++ b/met-web/package.json
@@ -69,7 +69,7 @@
"react-map-gl": "^7.0.21",
"react-player": "^2.12.0",
"react-redux": "^7.2.8",
- "react-router-dom": "^6.10.0",
+ "react-router-dom": "^6.23.1",
"react-scripts": "^5.0.1",
"react-svg": "^15.0.1",
"recharts": "^2.4.3",
From 92541d79f3410634fd2ab501ee6a446220163628 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 13:05:21 -0700
Subject: [PATCH 02/11] Update app structure to use createBrowserRouter; style
changes
---
met-web/src/App.tsx | 117 ++++++---------
.../appLayouts/AuthenticatedLayout.tsx | 41 +++++
.../components/appLayouts/PublicLayout.tsx | 27 ++++
.../src/components/common/Layout/index.tsx | 36 +++--
.../common/Navigation/Breadcrumb.tsx | 60 +++++++-
.../layout/Header/InternalHeader.tsx | 6 +-
.../src/components/layout/SideNav/SideNav.tsx | 1 +
met-web/src/routes/AuthenticatedRoutes.tsx | 142 +++++++++++-------
met-web/src/routes/UnauthenticatedRoutes.tsx | 56 +++----
9 files changed, 308 insertions(+), 178 deletions(-)
create mode 100644 met-web/src/components/appLayouts/AuthenticatedLayout.tsx
create mode 100644 met-web/src/components/appLayouts/PublicLayout.tsx
diff --git a/met-web/src/App.tsx b/met-web/src/App.tsx
index 8413a16f8..8ede5f481 100644
--- a/met-web/src/App.tsx
+++ b/met-web/src/App.tsx
@@ -1,24 +1,22 @@
import React, { useEffect, useState, useContext } from 'react';
import '@bcgov/design-tokens/css-prefixed/variables.css'; // Will be available to use in all component
import './App.scss';
-import { Route, BrowserRouter as Router, Routes } from 'react-router-dom';
+import {
+ Route,
+ BrowserRouter as Router,
+ RouterProvider,
+ Routes,
+ createBrowserRouter,
+ createRoutesFromElements,
+} from 'react-router-dom';
import { useAppSelector, useAppDispatch } from './hooks';
-import { MidScreenLoader, MobileToolbar } from './components/common';
-import { Box, Container, useMediaQuery, Theme } from '@mui/material';
-import InternalHeader from './components/layout/Header/InternalHeader';
-import PublicHeader from './components/layout/Header/PublicHeader';
+import { MidScreenLoader } from './components/common';
import UnauthenticatedRoutes from './routes/UnauthenticatedRoutes';
import AuthenticatedRoutes from './routes/AuthenticatedRoutes';
-import { Notification } from 'components/common/notification';
-import PageViewTracker from 'routes/PageViewTracker';
-import { NotificationModal } from 'components/common/modal';
-import { FeedbackModal } from 'components/feedback/FeedbackModal';
import { AppConfig } from 'config';
import NoAccess from 'routes/NoAccess';
import { getTenant } from 'services/tenantService';
import NotFound from 'routes/NotFound';
-import Footer from 'components/layout/Footer';
-import { ZIndex } from 'styles/Theme';
import { TenantState, loadingTenant, saveTenant } from 'reduxSlices/tenantSlice';
import { LanguageState } from 'reduxSlices/languageSlice';
import { openNotification } from 'services/notificationService/notificationSlice';
@@ -27,6 +25,8 @@ import DocumentTitle from 'DocumentTitle';
import { Languages } from 'constants/language';
import { AuthKeyCloakContext } from './components/auth/AuthKeycloakContext';
import { determinePathSegments, findTenantInPath } from './utils';
+import { AuthenticatedLayout } from 'components/appLayouts/AuthenticatedLayout';
+import { PublicLayout } from 'components/appLayouts/PublicLayout';
interface Translations {
[languageId: string]: { [key: string]: string };
@@ -34,7 +34,6 @@ interface Translations {
const App = () => {
const drawerWidth = 280;
- const isMediumScreen: boolean = useMediaQuery((theme: Theme) => theme.breakpoints.up('md'));
const dispatch = useAppDispatch();
const roles = useAppSelector((state) => state.user.roles);
const authenticationLoading = useAppSelector((state) => state.user.authentication.loading);
@@ -203,71 +202,49 @@ const App = () => {
}
if (!isAuthenticated) {
- return (
-
-
-
-
-
-
-
-
-
-
+ const router = createBrowserRouter(
+ [
+ {
+ element: ,
+ children: createRoutesFromElements(UnauthenticatedRoutes()),
+ },
+ ],
+ { basename: `/${basename}` },
);
+ return ;
}
if (roles.length === 0) {
- return (
-
-
-
-
-
-
-
-
-
+ const router = createBrowserRouter(
+ [
+ {
+ element: ,
+ children: [
+ {
+ path: '*',
+ element: ,
+ },
+ ],
+ },
+ ],
+ { basename: `/${basename}` },
);
+ return ;
}
- if (!isMediumScreen) {
- return (
-
-
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ const router = createBrowserRouter(
+ [
+ {
+ element: ,
+ children: createRoutesFromElements(AuthenticatedRoutes()),
+ handle: {
+ crumb: () => ({ name: 'Dashboard', link: '/home' }),
+ },
+ },
+ ],
+ { basename: `/${basename}` },
);
+
+ return ;
};
export default App;
diff --git a/met-web/src/components/appLayouts/AuthenticatedLayout.tsx b/met-web/src/components/appLayouts/AuthenticatedLayout.tsx
new file mode 100644
index 000000000..d795031b2
--- /dev/null
+++ b/met-web/src/components/appLayouts/AuthenticatedLayout.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import '@bcgov/design-tokens/css-prefixed/variables.css';
+import { Outlet } from 'react-router-dom';
+import { Box } from '@mui/material';
+import InternalHeader from '../layout/Header/InternalHeader';
+import { Notification } from 'components/common/notification';
+import { NotificationModal } from 'components/common/modal';
+import { FeedbackModal } from 'components/feedback/FeedbackModal';
+import Footer from 'components/layout/Footer';
+import { ZIndex } from 'styles/Theme';
+import DocumentTitle from 'DocumentTitle';
+import ScrollToTop from 'components/scrollToTop';
+import FormioListener from 'components/FormioListener';
+
+export const AuthenticatedLayout = ({ drawerWidth = 280 }: { drawerWidth?: number }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/met-web/src/components/appLayouts/PublicLayout.tsx b/met-web/src/components/appLayouts/PublicLayout.tsx
new file mode 100644
index 000000000..12cdcf27e
--- /dev/null
+++ b/met-web/src/components/appLayouts/PublicLayout.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import '@bcgov/design-tokens/css-prefixed/variables.css'; // Will be available to use in all component
+import { Outlet } from 'react-router-dom';
+import PublicHeader from '../layout/Header/PublicHeader';
+import { Notification } from 'components/common/notification';
+import PageViewTracker from 'routes/PageViewTracker';
+import { NotificationModal } from 'components/common/modal';
+import { FeedbackModal } from 'components/feedback/FeedbackModal';
+import Footer from 'components/layout/Footer';
+import DocumentTitle from 'DocumentTitle';
+import ScrollToTop from 'components/scrollToTop';
+
+export const PublicLayout = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/met-web/src/components/common/Layout/index.tsx b/met-web/src/components/common/Layout/index.tsx
index eab336533..697a53dc6 100644
--- a/met-web/src/components/common/Layout/index.tsx
+++ b/met-web/src/components/common/Layout/index.tsx
@@ -1,24 +1,28 @@
-import { styled, Box as MuiBox, Theme, useMediaQuery } from '@mui/material';
+import React from 'react';
+import { Box, BoxProps, Theme, useMediaQuery, useTheme } from '@mui/material';
const desktopOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('md'));
const tabletOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('sm'));
// A container that decreases its padding on smaller screens
-export const ResponsiveContainer = styled(MuiBox)(({ theme }) => {
- if (desktopOrLarger(theme)) {
- return {
- padding: '1.5em 4.0em',
- };
- }
- if (tabletOrLarger(theme)) {
- return {
- padding: '1.5em 2.0em',
- };
- }
- return {
- padding: '1.5em 1.0em',
- };
-});
+export const ResponsiveContainer: React.FC = (props: BoxProps) => {
+ const theme = useTheme();
+ const isDesktopOrLarger = desktopOrLarger(theme);
+ const isTabletOrLarger = tabletOrLarger(theme);
+
+ const horizontalPadding = isDesktopOrLarger ? '4.0em' : isTabletOrLarger ? '2.0em' : '1.0em';
+
+ return (
+
+ {props.children}
+
+ );
+};
export { Table, TableHead, TableHeadRow, TableHeadCell, TableBody, TableRow, TableCell, TableContainer } from './Table';
export { DetailsContainer, Detail } from './Details';
diff --git a/met-web/src/components/common/Navigation/Breadcrumb.tsx b/met-web/src/components/common/Navigation/Breadcrumb.tsx
index 32e263694..a4b04b394 100644
--- a/met-web/src/components/common/Navigation/Breadcrumb.tsx
+++ b/met-web/src/components/common/Navigation/Breadcrumb.tsx
@@ -1,7 +1,8 @@
import { Breadcrumbs } from '@mui/material';
-import React from 'react';
+import React, { Suspense } from 'react';
import { BodyText } from '../Typography';
import { Link } from '../Navigation';
+import { Await, UIMatch, useMatches } from 'react-router-dom';
type BreadcrumbProps = {
name: string;
@@ -33,3 +34,60 @@ export const BreadcrumbTrail: React.FC<{ crumbs: BreadcrumbProps[]; smallScreenO
);
};
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+interface UIMatchWithCrumb
+ extends UIMatch Promise<{ name: string; link?: string }> }> {}
+
+/**
+ * Automatically generates breadcrumbs based on the `handle.crumb` function of the current route and its parents.
+ * @param smallScreenOnly If true, only displays the breadcrumbs on small screens.
+ * @returns A list of breadcrumbs.
+ */
+export const AutoBreadcrumbs: React.FC<{ smallScreenOnly?: boolean }> = ({ smallScreenOnly }) => {
+ const matches = (useMatches() as UIMatchWithCrumb[]).filter((match) => match.handle?.crumb);
+ return (
+
+ {matches.map((match, index) => {
+ const data = match.data as unknown;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const handle = match.handle as {
+ crumb?: (data: unknown) => Promise<{ name: string; link?: string }>;
+ };
+ if (!handle?.crumb) return null;
+ const crumb = handle.crumb?.(data);
+ return (
+
+ Loading...
+
+ }
+ >
+
+ {(resolvedCrumb) => {
+ const name = resolvedCrumb?.name;
+ const link =
+ index < matches.length - 1 ? resolvedCrumb?.link ?? match.pathname : undefined;
+ return link ? (
+
+ {name}
+
+ ) : (
+
+ {name}
+
+ );
+ }}
+
+
+ );
+ })}
+
+ );
+};
diff --git a/met-web/src/components/layout/Header/InternalHeader.tsx b/met-web/src/components/layout/Header/InternalHeader.tsx
index 14c940a6e..3b8d4a650 100644
--- a/met-web/src/components/layout/Header/InternalHeader.tsx
+++ b/met-web/src/components/layout/Header/InternalHeader.tsx
@@ -41,7 +41,7 @@ const InternalHeader = ({ drawerWidth = 280 }: HeaderProps) => {
>
-
+
{
}}
sx={{ flexGrow: 1, cursor: 'pointer' }}
>
- {tenant.title}
+ {tenant.name}
) : (
{
navigate('/home');
}}
- sx={{ flexGrow: 1, cursor: 'pointer' }}
+ sx={{ flexGrow: 1, cursor: 'pointer', textTransform: 'capitalize' }}
>
{tenant.short_name}
diff --git a/met-web/src/components/layout/SideNav/SideNav.tsx b/met-web/src/components/layout/SideNav/SideNav.tsx
index 3a4f1b7ea..7d5f69c1e 100644
--- a/met-web/src/components/layout/SideNav/SideNav.tsx
+++ b/met-web/src/components/layout/SideNav/SideNav.tsx
@@ -67,6 +67,7 @@ const DrawerBox = () => {
};
const SideNav = ({ open, setOpen, isMediumScreen, drawerWidth = 280 }: SideNavProps) => {
+ if (!drawerWidth) return <>>;
return (
<>
{isMediumScreen ? (
diff --git a/met-web/src/routes/AuthenticatedRoutes.tsx b/met-web/src/routes/AuthenticatedRoutes.tsx
index d3f55739d..e124021c6 100644
--- a/met-web/src/routes/AuthenticatedRoutes.tsx
+++ b/met-web/src/routes/AuthenticatedRoutes.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Route, Routes } from 'react-router-dom';
+import { Params, defer, Navigate, Route } from 'react-router-dom';
import NotFound from './NotFound';
import EngagementForm from '../components/engagement/form';
import EngagementListing from '../components/engagement/listing';
@@ -22,81 +22,109 @@ import Unauthorized from './Unauthorized';
import AuthGate from './AuthGate';
import { USER_ROLES } from 'services/userService/constants';
import UserProfile from 'components/userManagement/userDetails';
-import ScrollToTop from 'components/scrollToTop';
import ReportSettings from 'components/survey/report';
-import FormioListener from 'components/FormioListener';
import TenantListingPage from 'components/tenantManagement/Listing';
import TenantCreationPage from 'components/tenantManagement/Create';
import TenantEditPage from 'components/tenantManagement/Edit';
import TenantDetail from 'components/tenantManagement/Detail';
import Language from 'components/language';
+import { Tenant } from 'models/tenant';
+import { getAllTenants, getTenant } from 'services/tenantService';
const AuthenticatedRoutes = () => {
return (
<>
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
}>
- } />
-
- }>
- } />
+ } />
+ } />
}>
- } />
+ } />
+
+
+ } />
}>
- } />
+ } />
}>
- } />
-
- } />
- } />
- } />
- } />
- } />
- } />
- }>
- } />
-
- }>
- } />
-
- }>
- } />
-
- }>
- } />
-
- }>
- } />
-
- }>
- } />
-
- }>
- } />
-
- } />
- } />
- }>
- } />
+ } />
- }>
- } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ }
+ loader={async () => {
+ return defer({ tenants: getAllTenants() });
+ }}
+ handle={{
+ crumb: () => ({ name: 'Tenant Admin' }),
+ }}
+ >
+ } />
+ }
+ handle={{
+ crumb: () => ({ name: 'Create Tenant Instance' }),
+ }}
+ />
+ }) => {
+ const tenant = getTenant(params.tenantShortName ?? '');
+ return defer({ tenant: tenant });
+ }}
+ handle={{
+ crumb: async (data: { tenant: Promise }) => {
+ return data.tenant.then((tenant) => {
+ return {
+ link: `/tenantadmin/${tenant.short_name}/detail`,
+ name: tenant.name,
+ };
+ });
+ },
+ }}
+ errorElement={}
+ >
+ } />
+ } />
+ }
+ handle={{
+ crumb: () => ({ name: 'Edit Instance' }),
+ }}
+ />
- } />
- } />
-
+
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ } />
+ } />
>
);
};
diff --git a/met-web/src/routes/UnauthenticatedRoutes.tsx b/met-web/src/routes/UnauthenticatedRoutes.tsx
index 98fc9efeb..469702818 100644
--- a/met-web/src/routes/UnauthenticatedRoutes.tsx
+++ b/met-web/src/routes/UnauthenticatedRoutes.tsx
@@ -1,18 +1,17 @@
import SurveySubmit from 'components/survey/submit';
import EditSurvey from 'components/survey/edit';
import React from 'react';
-import { Route, Routes } from 'react-router-dom';
import EngagementView from '../components/engagement/view';
import NotAvailable from './NotAvailable';
-import NotFound from './NotFound';
import EngagementComments from '../components/engagement/dashboard/comment';
import PublicDashboard from 'components/publicDashboard';
import Landing from 'components/landing';
import ManageSubscription from '../components/engagement/view/widgets/Subscribe/ManageSubscription';
import { FormCAC } from 'components/FormCAC';
-import ScrollToTop from 'components/scrollToTop';
import { RedirectLogin } from './RedirectLogin';
import withLanguageParam from './LanguageParam';
+import { Route } from 'react-router-dom';
+import NotFound from './NotFound';
const ManageSubscriptionWrapper = withLanguageParam(ManageSubscription);
const EngagementViewWrapper = withLanguageParam(EngagementView);
@@ -26,34 +25,29 @@ const RedirectLoginWrapper = withLanguageParam(RedirectLogin);
const UnauthenticatedRoutes = () => {
return (
<>
-
-
- } />
- }
- />
- } />
- } />
- }
- />
- } />
- }
- />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
+ } />
+ } />
+ } />
+
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
>
);
};
From c92763bd7db9e4531b1fad04342421f8615f5f23 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 13:55:49 -0700
Subject: [PATCH 03/11] Update changelog
---
CHANGELOG.MD | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 7d828e4f7..0afee8c3e 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,3 +1,9 @@
+## June 6, 2024
+
+- **Feature** Use createBrowserRouter insead of in the App component [🎟️ DESENG-627](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-627)
+ - Updated the App component to use the more flexible createBrowserRouter function instead of the component
+ - This enables the use of [data router](https://reactrouter.com/en/6.23.0/routers/picking-a-router) functionality and other advanced features in the future, most notably the Blocker component
+
## June 3, 2024
- **Bugfix** Patch button styles (follow-up to [🎟️ DESENG-583](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-583))
@@ -7,6 +13,7 @@
## May 30, 2024
- **Feature** Remove web components [🎟️ DESENG-616](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-616)
+
- Removed web components and all code that references it
- **Feature** Remove EAO process widget [🎟️ DESENG-626](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-626)
From 45f46f7e6294d685ea762d5c61c4c6ce571808da Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 13:57:56 -0700
Subject: [PATCH 04/11] update changelog again
---
CHANGELOG.MD | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 0afee8c3e..94af6cf04 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -3,6 +3,7 @@
- **Feature** Use createBrowserRouter insead of in the App component [🎟️ DESENG-627](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-627)
- Updated the App component to use the more flexible createBrowserRouter function instead of the component
- This enables the use of [data router](https://reactrouter.com/en/6.23.0/routers/picking-a-router) functionality and other advanced features in the future, most notably the Blocker component
+ - Added the AutoBreadcrumbs component to the common components library, which will be used to generate breadcrumbs based on the current route
## June 3, 2024
From 4ddd210e9193128f32e4b15d10e56d7d19db1a12 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 13:58:42 -0700
Subject: [PATCH 05/11] Fix formatting issue
---
CHANGELOG.MD | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index 94af6cf04..cc6fec952 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -1,6 +1,6 @@
## June 6, 2024
-- **Feature** Use createBrowserRouter insead of in the App component [🎟️ DESENG-627](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-627)
+- **Feature** Use createBrowserRouter insead of \ in the App component [🎟️ DESENG-627](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-627)
- Updated the App component to use the more flexible createBrowserRouter function instead of the component
- This enables the use of [data router](https://reactrouter.com/en/6.23.0/routers/picking-a-router) functionality and other advanced features in the future, most notably the Blocker component
- Added the AutoBreadcrumbs component to the common components library, which will be used to generate breadcrumbs based on the current route
From 3626ce62dc603a4e5bfac136438d7aa153f55fa6 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 14:43:36 -0700
Subject: [PATCH 06/11] are you happy now, sonarcloud?
---
.../src/components/common/Layout/index.tsx | 19 ++++++++++++++-----
.../common/Navigation/Breadcrumb.tsx | 1 +
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/met-web/src/components/common/Layout/index.tsx b/met-web/src/components/common/Layout/index.tsx
index 697a53dc6..97edd97da 100644
--- a/met-web/src/components/common/Layout/index.tsx
+++ b/met-web/src/components/common/Layout/index.tsx
@@ -1,16 +1,25 @@
import React from 'react';
import { Box, BoxProps, Theme, useMediaQuery, useTheme } from '@mui/material';
-const desktopOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('md'));
-const tabletOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('sm'));
+const useDesktopOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('md'));
+const useTabletOrLarger = (theme: Theme) => useMediaQuery(theme.breakpoints.up('sm'));
// A container that decreases its padding on smaller screens
export const ResponsiveContainer: React.FC = (props: BoxProps) => {
const theme = useTheme();
- const isDesktopOrLarger = desktopOrLarger(theme);
- const isTabletOrLarger = tabletOrLarger(theme);
+ const isDesktopOrLarger = useDesktopOrLarger(theme);
+ const isTabletOrLarger = useTabletOrLarger(theme);
- const horizontalPadding = isDesktopOrLarger ? '4.0em' : isTabletOrLarger ? '2.0em' : '1.0em';
+ let horizontalPadding;
+ if (isDesktopOrLarger) {
+ horizontalPadding = '4.0em';
+ } else {
+ if (isTabletOrLarger) {
+ horizontalPadding = '2.0em';
+ } else {
+ horizontalPadding = '1.0em';
+ }
+ }
return (
= ({ small
const crumb = handle.crumb?.(data);
return (
Loading...
From 3cb62377759937701ffbb9b984794d5f0aa974b3 Mon Sep 17 00:00:00 2001
From: NatSquared
Date: Thu, 6 Jun 2024 14:44:06 -0700
Subject: [PATCH 07/11] Breadcrumbs and Blockers and Loaders, oh my!
---
.../components/tenantManagement/Create.tsx | 19 +-
.../components/tenantManagement/Detail.tsx | 560 +++++++++---------
.../src/components/tenantManagement/Edit.tsx | 123 ++--
.../components/tenantManagement/Listing.tsx | 131 ++--
.../tenantManagement/TenantForm.tsx | 125 ++--
.../tenantManagement/CreateTenant.test.tsx | 95 ++-
.../tenantManagement/EditTenant.test.tsx | 100 ++--
.../tenantManagement/TenantDetail.test.tsx | 82 +--
.../tenantManagement/TenantListing.test.tsx | 19 +-
9 files changed, 657 insertions(+), 597 deletions(-)
diff --git a/met-web/src/components/tenantManagement/Create.tsx b/met-web/src/components/tenantManagement/Create.tsx
index ea3af4a74..56ad2dd12 100644
--- a/met-web/src/components/tenantManagement/Create.tsx
+++ b/met-web/src/components/tenantManagement/Create.tsx
@@ -3,24 +3,26 @@ import React from 'react';
import { Grid } from '@mui/material';
import { ResponsiveContainer } from 'components/common/Layout';
import { Header1, Header2, BodyText } from 'components/common/Typography/';
-import { BreadcrumbTrail } from 'components/common/Navigation';
import { TenantForm } from './TenantForm';
import { createTenant } from 'services/tenantService';
import { SubmitHandler } from 'react-hook-form';
import { Tenant } from 'models/tenant';
import { useAppDispatch } from 'hooks';
import { openNotification } from 'services/notificationService/notificationSlice';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, useRevalidator } from 'react-router-dom';
+import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
const TenantCreationPage = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
+ const revalidator = useRevalidator();
const onSubmit: SubmitHandler = async (data) => {
try {
await createTenant(data);
dispatch(openNotification({ text: 'Tenant created successfully!', severity: 'success' }));
- navigate('../tenantadmin');
+ revalidator.revalidate();
+ navigate('/tenantadmin');
} catch (error) {
dispatch(openNotification({ text: 'Unknown error while creating tenant', severity: 'error' }));
console.error(error);
@@ -28,19 +30,12 @@ const TenantCreationPage = () => {
};
const onCancel = () => {
- navigate('../tenantadmin');
+ navigate('/tenantadmin');
};
return (
-
+
Create Tenant Instance
diff --git a/met-web/src/components/tenantManagement/Detail.tsx b/met-web/src/components/tenantManagement/Detail.tsx
index 5da1c1364..9b3db3f0c 100644
--- a/met-web/src/components/tenantManagement/Detail.tsx
+++ b/met-web/src/components/tenantManagement/Detail.tsx
@@ -1,12 +1,12 @@
-import React, { useEffect } from 'react';
+import React, { Suspense } from 'react';
import { Box, Grid, Skeleton } from '@mui/material';
import { Header1, Header2, BodyText } from 'components/common/Typography/';
import { ResponsiveContainer, DetailsContainer, Detail } from 'components/common/Layout';
-import { useParams, useNavigate } from 'react-router-dom';
-import { getTenant, deleteTenant } from 'services/tenantService';
+import { useNavigate, useRouteLoaderData, Await, useRevalidator } from 'react-router-dom';
+import { deleteTenant } from 'services/tenantService';
import { useAppDispatch } from 'hooks';
import { openNotification } from 'services/notificationService/notificationSlice';
-import { BreadcrumbTrail } from 'components/common/Navigation/Breadcrumb';
+import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
import { Tenant } from 'models/tenant';
import { Button } from 'components/common/Input/Button';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -16,145 +16,14 @@ import LandingPageBanner from 'assets/images/LandingPageBanner.png';
import { globalFocusVisible } from 'components/common';
const TenantDetail = () => {
- const [tenant, setTenant] = React.useState(null);
- const [loading, setLoading] = React.useState(true);
+ const { tenant } = useRouteLoaderData('tenant') as { tenant: Tenant };
const navigate = useNavigate();
- const { tenantId } = useParams<{ tenantId: string }>();
const dispatch = useAppDispatch();
+ const revalidator = useRevalidator();
const penToSquareIcon = ;
const trashCanIcon = ;
const faCopyIcon = ;
- useEffect(() => {
- if (!tenantId) {
- return;
- }
- const fetchTenant = () => {
- getTenant(tenantId)
- .then((returnedTenant) => {
- setTenant(returnedTenant);
- setLoading(false);
- })
- .catch((error) => {
- dispatch(openNotification({ text: error.message, severity: 'error' }));
- setLoading(false);
- });
- };
- fetchTenant();
- }, [tenantId, dispatch]);
-
- if (loading || !tenant) {
- return (
-
-
-
- Loading...
-
-
-
-
- Loading...
-
-
-
-
-
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
-
- Loading...
-
-
- Loading...
-
-
- Loading...
-
-
- Loading...
-
-
-
-
-
-
-
-
-
-
- );
- }
-
const handleDeleteTenant = async (tenantId: string) => {
try {
await deleteTenant(tenantId);
@@ -164,7 +33,8 @@ const TenantDetail = () => {
text: `Tenant "${tenantId}" successfully deleted`,
}),
);
- navigate('../tenantadmin');
+ revalidator.revalidate();
+ navigate('/tenantadmin');
} catch (error) {
console.log(error);
dispatch(
@@ -204,173 +74,277 @@ const TenantDetail = () => {
};
return (
-
-
- {tenant.name}
-
-
- Tenant Details
-
-
-
-
-
-
-
-
-
-
- Tenant Instance Name
- {tenant.name}
-
-
-
+
+
+
+ Loading...
+
+
+ Loading...
+
+
-
-
-
- Primary Contact
-
-
- {tenant.contact_name}
-
- ) => {
- if (e.key === 'Enter' || e.key === ' ') {
- e.preventDefault();
- copyEmail();
- }
- }}
- >
-
- {tenant.contact_email} {faCopyIcon}
-
-
-
-
-
-
+
+
+ Loading...
+
+
+ Loading...
+
+
-
-
-
- Short Name
- {tenant.short_name}
-
-
-
+
+
+ Loading...
+
+
+ Loading...
+
+
-
-
-
- Hero Banner Title
- {tenant.title}
-
-
-
+
+
+ Loading...
+
+
+ Loading...
+
+
-
-
-
- Hero Banner Description
- {tenant.description}
-
-
-
+
+
+ Loading...
+
+
+ Loading...
+
+
-
-
-
- Hero Banner Image
-
-
- {tenant.logo_credit && (
+
+
+
+ Loading...
+
+
+ Loading...
+
+
+ Loading...
+
+
+ Loading...
+
+
+
- Photo Credit
- {tenant.logo_credit}
+
- )}
- {tenant.logo_description && (
-
- Description
- {tenant.logo_description}
-
- )}
+
+
+
+
+ }
+ >
+
+ {(resolvedTenant) => (
+
+
+ {resolvedTenant.name}
+
+
+ Tenant Details
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+ Tenant Instance Name
+ {resolvedTenant.name}
+
+
+
+
+
+
+
+ Primary Contact
+
+
+ {resolvedTenant.contact_name}
+
+ ) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ copyEmail();
+ }
+ }}
+ >
+
+ {resolvedTenant.contact_email} {faCopyIcon}
+
+
+
+
+
+
+
+
+
+
+ Short Name
+ {resolvedTenant.short_name}
+
+
+
+
+
+
+
+ Hero Banner Title
+ {resolvedTenant.title}
+
+
+
+
+
+
+
+ Hero Banner Description
+ {resolvedTenant.description}
+
+
+
+
+
+
+
+ Hero Banner Image
+
+
+ {resolvedTenant.logo_credit && (
+
+ Photo Credit
+ {resolvedTenant.logo_credit}
+
+ )}
+ {resolvedTenant.logo_description && (
+
+ Description
+ {resolvedTenant.logo_description}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
);
};
diff --git a/met-web/src/components/tenantManagement/Edit.tsx b/met-web/src/components/tenantManagement/Edit.tsx
index 301e1580f..68e5005fe 100644
--- a/met-web/src/components/tenantManagement/Edit.tsx
+++ b/met-web/src/components/tenantManagement/Edit.tsx
@@ -1,92 +1,71 @@
-import React, { useEffect } from 'react';
+import React, { Suspense } from 'react';
import { Grid } from '@mui/material';
import { ResponsiveContainer } from 'components/common/Layout';
import { Header1, Header2, BodyText } from 'components/common/Typography/';
-import { BreadcrumbTrail } from 'components/common/Navigation';
import { TenantForm } from './TenantForm';
-import { updateTenant, getTenant } from 'services/tenantService';
+import { updateTenant } from 'services/tenantService';
import { SubmitHandler } from 'react-hook-form';
import { Tenant } from 'models/tenant';
import { useAppDispatch } from 'hooks';
import { openNotification } from 'services/notificationService/notificationSlice';
-import { useNavigate, useParams } from 'react-router-dom';
-import NotFound from 'routes/NotFound';
+import { useRouteLoaderData, useNavigate, Await, useRevalidator } from 'react-router-dom';
import { MidScreenLoader } from 'components/common';
+import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
const TenantEditPage = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
- const { tenantShortName: shortName } = useParams<{ tenantShortName: string }>();
- const [tenant, setTenant] = React.useState();
- const [loading, setLoading] = React.useState(true);
-
- useEffect(() => {
- const fetchTenant = async () => {
- if (!shortName) {
- setLoading(false);
- return;
- }
- try {
- const tenant = await getTenant(shortName);
- setTenant(tenant);
- setLoading(false);
- } catch (error) {
- setLoading(false);
- }
- };
-
- fetchTenant();
- }, [shortName, dispatch]);
-
- if (loading) {
- return ;
- }
-
- if (!tenant || !shortName) {
- return ;
- }
-
- const onSubmit: SubmitHandler = async (data) => {
- try {
- await updateTenant(data, shortName);
- dispatch(openNotification({ text: 'Tenant updated successfully!', severity: 'success' }));
- navigate(`../tenantadmin/${shortName}/detail`);
- } catch (error) {
- dispatch(openNotification({ text: 'Unknown error while saving tenant', severity: 'error' }));
- console.error(error);
- }
- };
-
- const onCancel = () => {
- navigate(`../tenantadmin/${shortName}/detail`);
- };
+ const revalidator = useRevalidator();
+ const { tenant } = useRouteLoaderData('tenant') as { tenant: Tenant };
return (
-
-
+ }>
+
+ {(resolvedTenant) => {
+ const shortName = resolvedTenant?.short_name;
+ const onCancel = () => {
+ navigate(`/tenantadmin/${shortName}/detail`);
+ };
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ await updateTenant(data, shortName);
+ dispatch(openNotification({ text: 'Tenant updated successfully!', severity: 'success' }));
+ revalidator.revalidate();
+ navigate(`/tenantadmin/${shortName}/detail`);
+ } catch (error) {
+ dispatch(
+ openNotification({ text: 'Unknown error while saving tenant', severity: 'error' }),
+ );
+ console.error(error);
+ }
+ };
+ return (
+
+
- Edit Tenant Instance
-
-
-
- Tenant Details
-
-
-
- * Required fields
-
-
-
-
+ Edit Tenant Instance
+
+
+
+ Tenant Details
+
+
+
+ * Required fields
+
+
+
+
+ );
+ }}
+
+
);
};
diff --git a/met-web/src/components/tenantManagement/Listing.tsx b/met-web/src/components/tenantManagement/Listing.tsx
index bea58e5f2..d489ce0fa 100644
--- a/met-web/src/components/tenantManagement/Listing.tsx
+++ b/met-web/src/components/tenantManagement/Listing.tsx
@@ -13,48 +13,32 @@ import {
TableHeadRow,
TableRow,
} from 'components/common/Layout/Table';
-import { getAllTenants } from 'services/tenantService';
-import React, { useEffect } from 'react';
+import React, { Suspense } from 'react';
import { Tenant } from 'models/tenant';
-import { Else, If, Then } from 'react-if';
-import { BreadcrumbTrail } from 'components/common/Navigation/Breadcrumb';
-import { useAppDispatch } from 'hooks';
-import { openNotification } from 'services/notificationService/notificationSlice';
-import { useNavigate } from 'react-router-dom';
+import { AutoBreadcrumbs } from 'components/common/Navigation/Breadcrumb';
+import { Await, useNavigate, useRouteLoaderData } from 'react-router-dom';
const TenantListingPage = () => {
- const [tenants, setTenants] = React.useState([]);
- const [loading, setLoading] = React.useState(true);
- const dispatch = useAppDispatch();
const navigate = useNavigate();
const circlePlusIcon = ;
- useEffect(() => {
- const fetchTenants = () => {
- getAllTenants()
- .then((returnedTenants) => {
- setTenants(returnedTenants);
- setLoading(false);
- })
- .catch((error) => {
- dispatch(openNotification({ text: error.message, severity: 'error' }));
- setLoading(false);
- });
- };
- fetchTenants();
- }, []);
+ const { tenants } = useRouteLoaderData('tenant-admin') as { tenants: Tenant[] };
return (
-
+
Tenant Admin
- Tenant Instances {!loading && `(${tenants.length})`}
+
+ Tenant Instances{' '}
+ }>
+
+ {(resolvedTenants) => ({resolvedTenants.length})}
+
+
+
}>
+ {(resolvedTenants) => (
+ <>
+ {resolvedTenants.map((tenant: Tenant) => {
+ return (
+ {
+ navigate(`/tenantadmin/${tenant.short_name}/detail`);
+ }}
+ onKeyDown={(event) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ navigate(`/tenantadmin/${tenant.short_name}/detail`);
+ }
+ }}
+ key={tenant.name}
+ tabIndex={0}
+ >
+
+
+ {tenant.name}
+
+ {tenant.contact_name}
+
+
+ {tenant.description}
+
+
+ );
+ })}
+ >
+ )}
+
+
diff --git a/met-web/src/components/tenantManagement/TenantForm.tsx b/met-web/src/components/tenantManagement/TenantForm.tsx
index f5c49504b..6a6513c09 100644
--- a/met-web/src/components/tenantManagement/TenantForm.tsx
+++ b/met-web/src/components/tenantManagement/TenantForm.tsx
@@ -8,7 +8,7 @@ import { Tenant } from 'models/tenant';
import { Controller, useForm, SubmitHandler } from 'react-hook-form';
import { saveObject } from 'services/objectStorageService';
import { UploadGuidelines } from 'components/imageUpload/UploadGuidelines';
-import { getAllTenants } from 'services/tenantService';
+import { Await, useRouteLoaderData, useBlocker } from 'react-router-dom';
import { useAppDispatch } from 'hooks';
import { openNotificationModal } from 'services/notificationModalService/notificationModalSlice';
@@ -27,14 +27,8 @@ export const TenantForm = ({
}) => {
const [bannerImage, setBannerImage] = useState();
const [savedBannerImageFileName, setSavedBannerImageFileName] = useState(initialTenant?.logo_url ?? '');
- const [tenantShortNames, setTenantShortNames] = useState([]);
const dispatch = useAppDispatch();
-
- useEffect(() => {
- getAllTenants().then((tenants) =>
- setTenantShortNames(tenants.map((tenant) => tenant.short_name.toLowerCase())),
- );
- }, []);
+ const { tenants } = useRouteLoaderData('tenant-admin') as { tenants: Tenant[] };
const { handleSubmit, formState, control, reset, setValue, watch } = useForm({
defaultValues: {
@@ -53,7 +47,34 @@ export const TenantForm = ({
mode: 'onBlur',
reValidateMode: 'onChange',
});
- const { isDirty, isValid, errors } = formState;
+ const { isDirty, isSubmitted, isValid, errors } = formState;
+
+ const blocker = useBlocker(
+ ({ currentLocation, nextLocation }) =>
+ isDirty && !isSubmitted && nextLocation.pathname !== currentLocation.pathname,
+ );
+
+ useEffect(() => {
+ if (blocker.state === 'blocked') {
+ dispatch(
+ openNotificationModal({
+ open: true,
+ data: {
+ style: 'warning',
+ header: 'Unsaved Changes',
+ subHeader:
+ 'If you leave this page, your changes will not be saved. Are you sure you want to leave this page?',
+ subText: [],
+ confirmButtonText: 'Leave',
+ cancelButtonText: 'Stay',
+ handleConfirm: blocker.proceed,
+ handleClose: blocker.reset,
+ },
+ type: 'confirm',
+ }),
+ );
+ }
+ }, [blocker, dispatch]);
const hasLogoUrl = watch('logo_url');
@@ -114,29 +135,6 @@ export const TenantForm = ({
}
};
- const handleCancelAttempt = () => {
- if (isDirty) {
- dispatch(
- openNotificationModal({
- open: true,
- data: {
- style: 'warning',
- header: 'Unsaved Changes',
- subHeader:
- 'If you leave this page, your changes will not be saved. Are you sure you want to leave this page?',
- subText: [],
- confirmButtonText: 'Leave',
- cancelButtonText: 'Stay',
- handleConfirm: onCancel,
- },
- type: 'confirm',
- }),
- );
- } else {
- onCancel?.();
- }
- };
-
const handleKeys = (event: React.KeyboardEvent) => {
// Handle as many key combinations as possible
if ((event.ctrlKey || event.metaKey || event.altKey) && event.key === 'Enter') {
@@ -226,34 +224,43 @@ export const TenantForm = ({
/>
-
- tenantShortNames.includes(value.toLowerCase()) &&
- value.toLowerCase() !== initialTenant?.short_name.toLowerCase()
- ? 'This short name is already in use'
- : true,
- }}
- render={({ field }) => (
-
- met.gov.bc.ca/
-
- }
+
+ {(resolvedTenants: Tenant[]) => (
+
+ resolvedTenants
+ .map((tenant) => tenant.short_name)
+ .includes(value.toLowerCase()) &&
+ value.toLowerCase() !== initialTenant?.short_name.toLowerCase()
+ ? 'This short name is already in use'
+ : true,
+ }}
+ render={({ field }) => (
+
+ met.gov.bc.ca/
+
+ }
+ />
+ )}
/>
)}
- />
+
{onCancel && (
-