diff --git a/frontend/package.json b/frontend/package.json
index e4f38e04d0..aeb675ec89 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -55,7 +55,6 @@
"@types/node": "17.0.18",
"@types/react": "17.0.44",
"@types/react-dom": "17.0.16",
- "@types/react-router-dom": "5.3.3",
"@types/react-test-renderer": "17.0.2",
"@types/react-timeago": "4.1.3",
"@types/semver": "^7.3.9",
@@ -81,7 +80,7 @@
"react-dnd-html5-backend": "15.1.3",
"react-dom": "17.0.2",
"react-hooks-global-state": "1.0.2",
- "react-router-dom": "5.3.1",
+ "react-router-dom": "6.3.0",
"react-scripts": "5.0.1",
"react-test-renderer": "17.0.2",
"react-timeago": "6.2.1",
diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx
index e8ff881547..870cc0c7be 100644
--- a/frontend/src/component/App.tsx
+++ b/frontend/src/component/App.tsx
@@ -1,18 +1,16 @@
-import { createElement } from 'react';
-import { Redirect, Route, Switch } from 'react-router-dom';
+import { Navigate, Route, Routes } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS';
import { LayoutPicker } from 'component/layout/LayoutPicker/LayoutPicker';
import Loader from 'component/common/Loader/Loader';
import NotFound from 'component/common/NotFound/NotFound';
-import ProtectedRoute from 'component/common/ProtectedRoute/ProtectedRoute';
+import { ProtectedRoute } from 'component/common/ProtectedRoute/ProtectedRoute';
import SWRProvider from 'component/providers/SWRProvider/SWRProvider';
import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer';
import { routes } from 'component/menu/routes';
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
-import { IRoute } from 'interfaces/route';
import styles from 'component/styles.module.scss';
export const App = () => {
@@ -21,33 +19,8 @@ export const App = () => {
const isLoggedIn = Boolean(user?.id);
const hasFetchedAuth = Boolean(authDetails || user);
- const isUnauthorized = (): boolean => {
- return !isLoggedIn;
- };
-
- const renderRoute = (route: IRoute) => {
- if (route.type === 'protected') {
- const unauthorized = isUnauthorized();
- return (
-
- );
- }
- return (
- createElement(route.component, {}, null)}
- />
- );
- };
-
return (
-
+
}
@@ -55,19 +28,24 @@ export const App = () => {
-
-
+ {routes.map(route => (
+
+ }
+ />
+ ))}
+ (
-
- )}
+ element={
+
+ }
/>
- {routes.map(renderRoute)}
-
-
-
+ } />
+
diff --git a/frontend/src/component/addons/AddonForm/AddonForm.tsx b/frontend/src/component/addons/AddonForm/AddonForm.tsx
index 10ba48b855..de18c94909 100644
--- a/frontend/src/component/addons/AddonForm/AddonForm.tsx
+++ b/frontend/src/component/addons/AddonForm/AddonForm.tsx
@@ -16,7 +16,7 @@ import { AddonParameters } from './AddonParameters/AddonParameters';
import { AddonEvents } from './AddonEvents/AddonEvents';
import cloneDeep from 'lodash.clonedeep';
import PageContent from 'component/common/PageContent/PageContent';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi';
import useToast from 'hooks/useToast';
import { makeStyles } from 'tss-react/mui';
@@ -50,7 +50,7 @@ export const AddonForm: VFC
= ({
}) => {
const { createAddon, updateAddon } = useAddonsApi();
const { setToastData, setToastApiError } = useToast();
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
const [formValues, setFormValues] = useState(initialValues);
@@ -119,7 +119,7 @@ export const AddonForm: VFC = ({
};
const onCancel = () => {
- history.goBack();
+ navigate(-1);
};
const onSubmit: FormEventHandler = async event => {
@@ -152,14 +152,14 @@ export const AddonForm: VFC = ({
try {
if (editMode) {
await updateAddon(formValues);
- history.push('/addons');
+ navigate('/addons');
setToastData({
type: 'success',
title: 'Addon updated successfully',
});
} else {
await createAddon(formValues);
- history.push('/addons');
+ navigate('/addons');
setToastData({
type: 'success',
confetti: true,
diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
index 4307c92ee9..838d285bb3 100644
--- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
+++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx
@@ -8,7 +8,7 @@ import {
ListItemText,
} from '@mui/material';
import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
interface IProvider {
@@ -29,7 +29,7 @@ export const AvailableAddons = ({
providers,
getAddonIcon,
}: IAvailableAddonsProps) => {
- const history = useHistory();
+ const navigate = useNavigate();
const renderProvider = (provider: IProvider) => (
@@ -41,9 +41,7 @@ export const AvailableAddons = ({
- history.push(`/addons/create/${provider.name}`)
- }
+ onClick={() => navigate(`/addons/create/${provider.name}`)}
>
Configure
diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
index f507c398a3..eed53c0fc5 100644
--- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
+++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx
@@ -11,7 +11,7 @@ import {
DELETE_ADDON,
UPDATE_ADDON,
} from 'component/providers/AccessProvider/permissions';
-import { Link, useHistory } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import PageContent from 'component/common/PageContent/PageContent';
import useAddons from 'hooks/api/getters/useAddons/useAddons';
import useToast from 'hooks/useToast';
@@ -32,7 +32,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
const { updateAddon, removeAddon } = useAddonsApi();
const { setToastData, setToastApiError } = useToast();
const { hasAccess } = useContext(AccessContext);
- const history = useHistory();
+ const navigate = useNavigate();
const [showDelete, setShowDelete] = useState(false);
const [deletedAddon, setDeletedAddon] = useState({
id: 0,
@@ -124,7 +124,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
history.push(`/addons/edit/${addon.id}`)}
+ onClick={() => navigate(`/addons/edit/${addon.id}`)}
>
diff --git a/frontend/src/component/addons/CreateAddon/CreateAddon.tsx b/frontend/src/component/addons/CreateAddon/CreateAddon.tsx
index a701db85ac..6654e5a056 100644
--- a/frontend/src/component/addons/CreateAddon/CreateAddon.tsx
+++ b/frontend/src/component/addons/CreateAddon/CreateAddon.tsx
@@ -1,12 +1,8 @@
-import { useParams } from 'react-router-dom';
import useAddons from 'hooks/api/getters/useAddons/useAddons';
import { AddonForm } from '../AddonForm/AddonForm';
import cloneDeep from 'lodash.clonedeep';
import { IAddon } from 'interfaces/addons';
-
-interface IAddonCreateParams {
- providerId: string;
-}
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const DEFAULT_DATA = {
provider: '',
@@ -17,8 +13,7 @@ export const DEFAULT_DATA = {
} as unknown as IAddon; // TODO: improve type
export const CreateAddon = () => {
- const { providerId } = useParams();
-
+ const providerId = useRequiredPathParam('providerId');
const { providers, refetchAddons } = useAddons();
const editMode = false;
diff --git a/frontend/src/component/addons/EditAddon/EditAddon.tsx b/frontend/src/component/addons/EditAddon/EditAddon.tsx
index 75de382e08..3fa6cfd4ce 100644
--- a/frontend/src/component/addons/EditAddon/EditAddon.tsx
+++ b/frontend/src/component/addons/EditAddon/EditAddon.tsx
@@ -1,17 +1,12 @@
-import { useParams } from 'react-router-dom';
import useAddons from 'hooks/api/getters/useAddons/useAddons';
import { AddonForm } from '../AddonForm/AddonForm';
import cloneDeep from 'lodash.clonedeep';
import { IAddon } from 'interfaces/addons';
import { DEFAULT_DATA } from '../CreateAddon/CreateAddon';
-
-interface IAddonEditParams {
- addonId: string;
-}
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const EditAddon = () => {
- const { addonId } = useParams();
-
+ const addonId = useRequiredPathParam('addonId');
const { providers, addons, refetchAddons } = useAddons();
const editMode = true;
diff --git a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
index 15fbdb05ec..bfee1fd56f 100644
--- a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
+++ b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
@@ -1,5 +1,5 @@
import { useContext } from 'react';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { Button } from '@mui/material';
import AccessContext from 'contexts/AccessContext';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@@ -20,7 +20,7 @@ export const ApiTokenPage = () => {
const { classes: styles } = useStyles();
const { hasAccess } = useContext(AccessContext);
const { uiConfig } = useUiConfig();
- const history = useHistory();
+ const navigate = useNavigate();
return (
{
variant="contained"
color="primary"
onClick={() =>
- history.push('/admin/api/create-token')
+ navigate('/admin/api/create-token')
}
data-testid={CREATE_API_TOKEN_BUTTON}
>
diff --git a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
index cfa908bf8e..fa9dafb73e 100644
--- a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
+++ b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx
@@ -1,5 +1,5 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import ApiTokenForm from '../ApiTokenForm/ApiTokenForm';
import { CreateButton } from 'component/common/CreateButton/CreateButton';
import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
@@ -15,7 +15,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
export const CreateApiToken = () => {
const { setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
- const history = useHistory();
+ const navigate = useNavigate();
const [showConfirm, setShowConfirm] = useState(false);
const [token, setToken] = useState('');
@@ -57,7 +57,7 @@ export const CreateApiToken = () => {
const closeConfirm = () => {
setShowConfirm(false);
- history.push('/admin/api');
+ navigate('/admin/api');
};
const formatApiCode = () => {
@@ -70,7 +70,7 @@ export const CreateApiToken = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/admin/index.tsx b/frontend/src/component/admin/index.tsx
index 125c446015..6605f52f9f 100644
--- a/frontend/src/component/admin/index.tsx
+++ b/frontend/src/component/admin/index.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Redirect } from 'react-router-dom';
+import { Navigate } from 'react-router-dom';
-const render = () => ;
+const render = () => ;
render.propTypes = {
match: PropTypes.object.isRequired,
diff --git a/frontend/src/component/admin/menu/AdminMenu.tsx b/frontend/src/component/admin/menu/AdminMenu.tsx
index b23999a9c2..160967c623 100644
--- a/frontend/src/component/admin/menu/AdminMenu.tsx
+++ b/frontend/src/component/admin/menu/AdminMenu.tsx
@@ -19,6 +19,14 @@ const activeNavLinkStyle: React.CSSProperties = {
padding: '0.8rem 1.5rem',
};
+const createNavLinkStyle = (props: {
+ isActive: boolean;
+}): React.CSSProperties => {
+ return props.isActive
+ ? { ...navLinkStyle, ...activeNavLinkStyle }
+ : navLinkStyle;
+};
+
function AdminMenu() {
const { uiConfig } = useUiConfig();
const { pathname } = useLocation();
@@ -36,11 +44,7 @@ function AdminMenu() {
+
Users
}
@@ -51,8 +55,7 @@ function AdminMenu() {
label={
PROJECT ROLES
@@ -63,11 +66,7 @@ function AdminMenu() {
+
API Access
}
@@ -75,11 +74,7 @@ function AdminMenu() {
+
Single Sign-On
}
diff --git a/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx b/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx
index 2e8ea62cd3..80a0a2e38c 100644
--- a/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx
+++ b/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx
@@ -1,6 +1,6 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectRolesApi';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
import useProjectRoleForm from '../hooks/useProjectRoleForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@@ -12,7 +12,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
const CreateProjectRole = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
- const history = useHistory();
+ const navigate = useNavigate();
const {
roleName,
roleDesc,
@@ -43,7 +43,7 @@ const CreateProjectRole = () => {
const payload = getProjectRolePayload();
try {
await createRole(payload);
- history.push('/admin/roles');
+ navigate('/admin/roles');
setToastData({
title: 'Project role created',
text: 'Now you can start assigning your project roles to project members.',
@@ -66,7 +66,7 @@ const CreateProjectRole = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx b/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx
index 15396561bd..1294e45c72 100644
--- a/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx
+++ b/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx
@@ -7,19 +7,19 @@ import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { IPermission } from 'interfaces/user';
-import { useParams, useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import useProjectRoleForm from '../hooks/useProjectRoleForm';
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const EditProjectRole = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
+ const projectId = useRequiredPathParam('projectId');
+ const { role } = useProjectRole(projectId);
- const { id } = useParams<{ id: string }>();
- const { role } = useProjectRole(id);
-
- const history = useHistory();
+ const navigate = useNavigate();
const {
roleName,
roleDesc,
@@ -66,7 +66,7 @@ const EditProjectRole = () => {
--data-raw '${JSON.stringify(getProjectRolePayload(), undefined, 2)}'`;
};
- const { refetch } = useProjectRole(id);
+ const { refetch } = useProjectRole(projectId);
const { editRole, loading } = useProjectRolesApi();
const handleSubmit = async (e: Event) => {
@@ -78,9 +78,9 @@ const EditProjectRole = () => {
if (validName && validPermissions) {
try {
- await editRole(id, payload);
+ await editRole(projectId, payload);
refetch();
- history.push('/admin/roles');
+ navigate('/admin/roles');
setToastData({
type: 'success',
title: 'Project role updated',
@@ -94,7 +94,7 @@ const EditProjectRole = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx
index 19454a743f..c95857759b 100644
--- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx
+++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoleList/ProjectRoleListItem/ProjectRoleListItem.tsx
@@ -5,7 +5,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
import SupervisedUserCircleIcon from '@mui/icons-material/SupervisedUserCircle';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { IProjectRole } from 'interfaces/role';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import React from 'react';
interface IRoleListItemProps {
@@ -27,7 +27,7 @@ const RoleListItem = ({
setCurrentRole,
setDelDialog,
}: IRoleListItemProps) => {
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
return (
@@ -52,7 +52,7 @@ const RoleListItem = ({
data-loading
disabled={type === BUILTIN_ROLE_TYPE}
onClick={() => {
- history.push(`/admin/roles/${id}/edit`);
+ navigate(`/admin/roles/${id}/edit`);
}}
permission={ADMIN}
tooltip="Edit role"
diff --git a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx
index 90822b4f92..8ff62c4c66 100644
--- a/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx
+++ b/frontend/src/component/admin/projectRoles/ProjectRoles/ProjectRoles.tsx
@@ -1,6 +1,6 @@
import { Button } from '@mui/material';
import { useContext } from 'react';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle';
@@ -14,7 +14,7 @@ import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
const ProjectRoles = () => {
const { hasAccess } = useContext(AccessContext);
const { classes: styles } = useStyles();
- const history = useHistory();
+ const navigate = useNavigate();
return (
@@ -32,7 +32,7 @@ const ProjectRoles = () => {
variant="contained"
color="primary"
onClick={() =>
- history.push(
+ navigate(
'/admin/create-project-role'
)
}
diff --git a/frontend/src/component/admin/users/CreateUser/CreateUser.tsx b/frontend/src/component/admin/users/CreateUser/CreateUser.tsx
index 367575acdd..0756df9a68 100644
--- a/frontend/src/component/admin/users/CreateUser/CreateUser.tsx
+++ b/frontend/src/component/admin/users/CreateUser/CreateUser.tsx
@@ -1,5 +1,5 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import UserForm from '../UserForm/UserForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useAdminUsersApi from 'hooks/api/actions/useAdminUsersApi/useAdminUsersApi';
@@ -15,7 +15,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
const CreateUser = () => {
const { setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
- const history = useHistory();
+ const navigate = useNavigate();
const {
name,
setName,
@@ -59,7 +59,7 @@ const CreateUser = () => {
};
const closeConfirm = () => {
setShowConfirm(false);
- history.push('/admin/user-admin');
+ navigate('/admin/users');
};
const formatApiCode = () => {
@@ -72,7 +72,7 @@ const CreateUser = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/admin/users/EditUser/EditUser.tsx b/frontend/src/component/admin/users/EditUser/EditUser.tsx
index f80a89fc87..d307511d22 100644
--- a/frontend/src/component/admin/users/EditUser/EditUser.tsx
+++ b/frontend/src/component/admin/users/EditUser/EditUser.tsx
@@ -1,4 +1,4 @@
-import { useHistory, useParams } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import UserForm from '../UserForm/UserForm';
import useAddUserForm from '../hooks/useAddUserForm';
import { scrollToTop } from 'component/common/util';
@@ -12,6 +12,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const EditUser = () => {
useEffect(() => {
@@ -19,10 +20,10 @@ const EditUser = () => {
}, []);
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
- const { id } = useParams<{ id: string }>();
+ const id = useRequiredPathParam('id');
const { user, refetch } = useUserInfo(id);
const { updateUser, userLoading: loading } = useAdminUsersApi();
- const history = useHistory();
+ const navigate = useNavigate();
const {
name,
setName,
@@ -56,7 +57,7 @@ const EditUser = () => {
try {
await updateUser({ ...payload, id });
refetch();
- history.push('/admin/users');
+ navigate('/admin/users');
setToastData({
title: 'User information updated',
type: 'success',
@@ -68,7 +69,7 @@ const EditUser = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/admin/users/UsersAdmin.tsx b/frontend/src/component/admin/users/UsersAdmin.tsx
index acef1e87bd..f0b12a0908 100644
--- a/frontend/src/component/admin/users/UsersAdmin.tsx
+++ b/frontend/src/component/admin/users/UsersAdmin.tsx
@@ -9,13 +9,13 @@ import { Button } from '@mui/material';
import { TableActions } from 'component/common/Table/TableActions/TableActions';
import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle';
import { useStyles } from './UserAdmin.styles';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
const UsersAdmin = () => {
const [search, setSearch] = useState('');
const { hasAccess } = useContext(AccessContext);
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
return (
@@ -41,9 +41,7 @@ const UsersAdmin = () => {
variant="contained"
color="primary"
onClick={() =>
- history.push(
- '/admin/create-user'
- )
+ navigate('/admin/create-user')
}
>
New user
diff --git a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.styles.ts b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.styles.ts
index 8ee4138972..df1a3ca9d3 100644
--- a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.styles.ts
+++ b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.styles.ts
@@ -16,11 +16,11 @@ export const useStyles = makeStyles()(theme => ({
backgroundColor: unleashGrey[200],
fontWeight: 'normal',
border: 0,
- '&:first-child': {
+ '&:first-of-type': {
borderTopLeftRadius: '8px',
borderBottomLeftRadius: '8px',
},
- '&:last-child': {
+ '&:last-of-type': {
borderTopRightRadius: '8px',
borderBottomRightRadius: '8px',
},
diff --git a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx
index be1b03e3d1..08246dd354 100644
--- a/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx
+++ b/frontend/src/component/admin/users/UsersList/UserListItem/UserListItem.tsx
@@ -13,7 +13,7 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext';
import { IUser } from 'interfaces/user';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { ILocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMD } from 'utils/formatDate';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
@@ -37,7 +37,7 @@ const UserListItem = ({
search,
}: IUserListItemProps) => {
const { hasAccess } = useContext(AccessContext);
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
return (
@@ -88,7 +88,7 @@ const UserListItem = ({
- history.push(`/admin/users/${user.id}/edit`)
+ navigate(`/admin/users/${user.id}/edit`)
}
size="large"
>
diff --git a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.test.jsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.test.jsx
deleted file mode 100644
index b49217eabb..0000000000
--- a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.test.jsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { ApplicationEdit } from './ApplicationEdit';
-import renderer from 'react-test-renderer';
-import { MemoryRouter } from 'react-router-dom';
-import { ADMIN } from 'component/providers/AccessProvider/permissions';
-import UIProvider from 'component/providers/UIProvider/UIProvider';
-import { ThemeProvider } from 'themes/ThemeProvider';
-import { AccessProviderMock } from 'component/providers/AccessProvider/AccessProviderMock';
-
-test('renders correctly if no application', () => {
- const tree = renderer
- .create(
-
-
-
-
- Promise.resolve({})}
- storeApplicationMetaData={jest.fn()}
- deleteApplication={jest.fn()}
- locationSettings={{ locale: 'en-GB' }}
- />
-
-
-
-
- )
- .toJSON();
-
- expect(tree).toMatchSnapshot();
-});
-
-test('renders correctly without permission', () => {
- const tree = renderer
- .create(
-
-
-
-
- Promise.resolve({})}
- storeApplicationMetaData={jest.fn()}
- deleteApplication={jest.fn()}
- application={{
- appName: 'test-app',
- instances: [
- {
- instanceId: 'instance-1',
- clientIp: '123.123.123.123',
- lastSeen: '2017-02-23T15:56:49',
- sdkVersion: '4.0',
- },
- ],
- strategies: [
- {
- name: 'StrategyA',
- description: 'A description',
- },
- {
- name: 'StrategyB',
- description: 'B description',
- notFound: true,
- },
- ],
- seenToggles: [
- {
- name: 'ToggleA',
- description: 'this is A toggle',
- enabled: true,
- project: 'default',
- },
- {
- name: 'ToggleB',
- description: 'this is B toggle',
- enabled: false,
- notFound: true,
- project: 'default',
- },
- ],
- url: 'http://example.org',
- description: 'app description',
- }}
- locationSettings={{ locale: 'en-GB' }}
- />
-
-
-
-
- )
- .toJSON();
-
- expect(tree).toMatchSnapshot();
-});
-
-test('renders correctly with permissions', () => {
- const tree = renderer
- .create(
-
-
-
-
- Promise.resolve({})}
- storeApplicationMetaData={jest.fn()}
- deleteApplication={jest.fn()}
- application={{
- appName: 'test-app',
- instances: [
- {
- instanceId: 'instance-1',
- clientIp: '123.123.123.123',
- lastSeen: '2017-02-23T15:56:49',
- sdkVersion: '4.0',
- },
- ],
- strategies: [
- {
- name: 'StrategyA',
- description: 'A description',
- },
- {
- name: 'StrategyB',
- description: 'B description',
- notFound: true,
- },
- ],
- seenToggles: [
- {
- name: 'ToggleA',
- description: 'this is A toggle',
- enabled: true,
- project: 'default',
- },
- {
- name: 'ToggleB',
- description: 'this is B toggle',
- enabled: false,
- notFound: true,
- project: 'default',
- },
- ],
- url: 'http://example.org',
- description: 'app description',
- }}
- locationSettings={{ locale: 'en-GB' }}
- />
-
-
-
-
- )
- .toJSON();
-
- expect(tree).toMatchSnapshot();
-});
diff --git a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
index 8600eb1f23..4e7ba17057 100644
--- a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
+++ b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx
@@ -20,16 +20,17 @@ import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle';
import AccessContext from 'contexts/AccessContext';
import useApplicationsApi from 'hooks/api/actions/useApplicationsApi/useApplicationsApi';
import useApplication from 'hooks/api/getters/useApplication/useApplication';
-import { useHistory, useParams } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { useLocationSettings } from 'hooks/useLocationSettings';
import useToast from 'hooks/useToast';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { formatDateYMD } from 'utils/formatDate';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const ApplicationEdit = () => {
- const history = useHistory();
- const { name } = useParams<{ name: string }>();
+ const navigate = useNavigate();
+ const name = useRequiredPathParam('name');
const { application, loading } = useApplication(name);
const { appName, url, description, icon = 'apps', createdAt } = application;
const { hasAccess } = useContext(AccessContext);
@@ -54,7 +55,7 @@ export const ApplicationEdit = () => {
text: 'Application deleted successfully',
type: 'success',
});
- history.push('/applications');
+ navigate('/applications');
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
diff --git a/frontend/src/component/application/ApplicationEdit/__snapshots__/ApplicationEdit.test.jsx.snap b/frontend/src/component/application/ApplicationEdit/__snapshots__/ApplicationEdit.test.jsx.snap
deleted file mode 100644
index 185ad56475..0000000000
--- a/frontend/src/component/application/ApplicationEdit/__snapshots__/ApplicationEdit.test.jsx.snap
+++ /dev/null
@@ -1,64 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly if no application 1`] = `
-
-
- Loading...
-
-
-
-
-
-
-`;
-
-exports[`renders correctly with permissions 1`] = `
-
-
- Loading...
-
-
-
-
-
-
-`;
-
-exports[`renders correctly without permission 1`] = `
-
-
- Loading...
-
-
-
-
-
-
-`;
diff --git a/frontend/src/component/application/ApplicationView/ApplicationView.tsx b/frontend/src/component/application/ApplicationView/ApplicationView.tsx
index 44a709d340..5942c93d50 100644
--- a/frontend/src/component/application/ApplicationView/ApplicationView.tsx
+++ b/frontend/src/component/application/ApplicationView/ApplicationView.tsx
@@ -1,5 +1,5 @@
import { useContext } from 'react';
-import { Link, useParams } from 'react-router-dom';
+import { Link } from 'react-router-dom';
import {
Grid,
List,
@@ -25,10 +25,11 @@ import useApplication from 'hooks/api/getters/useApplication/useApplication';
import AccessContext from 'contexts/AccessContext';
import { formatDateYMDHMS } from 'utils/formatDate';
import { useLocationSettings } from 'hooks/useLocationSettings';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const ApplicationView = () => {
const { hasAccess } = useContext(AccessContext);
- const { name } = useParams<{ name: string }>();
+ const name = useRequiredPathParam('name');
const { application } = useApplication(name);
const { locationSettings } = useLocationSettings();
const { instances, strategies, seenToggles } = application;
@@ -147,7 +148,7 @@ export const ApplicationView = () => {
permission: CREATE_STRATEGY,
})}
elseShow={foundListItem({
- viewUrl: '/strategies/view',
+ viewUrl: '/strategies',
name,
Icon: Extension,
description,
diff --git a/frontend/src/component/archive/RedirectArchive.tsx b/frontend/src/component/archive/RedirectArchive.tsx
index 7977c51acf..e0cbfe0f31 100644
--- a/frontend/src/component/archive/RedirectArchive.tsx
+++ b/frontend/src/component/archive/RedirectArchive.tsx
@@ -1,7 +1,7 @@
-import { Redirect } from 'react-router-dom';
+import { Navigate } from 'react-router-dom';
const RedirectArchive = () => {
- return ;
+ return ;
};
export default RedirectArchive;
diff --git a/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx b/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx
index 63db51df8c..19d1ee44a2 100644
--- a/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx
+++ b/frontend/src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.tsx
@@ -1,4 +1,4 @@
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import PermissionButton from '../PermissionButton/PermissionButton';
@@ -20,7 +20,7 @@ const EnvironmentStrategyDialog = ({
onClose,
}: IEnvironmentStrategyDialogProps) => {
const { classes: styles } = useStyles();
- const history = useHistory();
+ const navigate = useNavigate();
const createStrategyPath = formatCreateStrategyPath(
projectId,
@@ -31,7 +31,7 @@ const EnvironmentStrategyDialog = ({
const onClick = () => {
onClose();
- history.push(createStrategyPath);
+ navigate(createStrategyPath);
};
return (
diff --git a/frontend/src/component/common/LoginRedirect/LoginRedirect.tsx b/frontend/src/component/common/LoginRedirect/LoginRedirect.tsx
new file mode 100644
index 0000000000..38a9bcbea1
--- /dev/null
+++ b/frontend/src/component/common/LoginRedirect/LoginRedirect.tsx
@@ -0,0 +1,10 @@
+import { useLocation, Navigate } from 'react-router-dom';
+
+export const LoginRedirect = () => {
+ const { pathname, search } = useLocation();
+
+ const redirect = encodeURIComponent(pathname + search);
+ const loginLink = `/login?redirect=${redirect}`;
+
+ return ;
+};
diff --git a/frontend/src/component/common/NotFound/NotFound.styles.ts b/frontend/src/component/common/NotFound/NotFound.styles.ts
index d95a5eebd4..2f6fa50efe 100644
--- a/frontend/src/component/common/NotFound/NotFound.styles.ts
+++ b/frontend/src/component/common/NotFound/NotFound.styles.ts
@@ -8,6 +8,7 @@ export const useStyles = makeStyles()({
minHeight: '100vh',
padding: '2rem',
position: 'fixed',
+ inset: 0,
backgroundColor: '#fff',
width: '100%',
},
diff --git a/frontend/src/component/common/NotFound/NotFound.tsx b/frontend/src/component/common/NotFound/NotFound.tsx
index 017812ddda..3155dfb92f 100644
--- a/frontend/src/component/common/NotFound/NotFound.tsx
+++ b/frontend/src/component/common/NotFound/NotFound.tsx
@@ -1,20 +1,20 @@
import { Button, Typography } from '@mui/material';
-import { useHistory } from 'react-router';
+import { useNavigate } from 'react-router';
import { ReactComponent as LogoIcon } from 'assets/icons/logoBg.svg';
import { useStyles } from './NotFound.styles';
const NotFound = () => {
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
const onClickHome = () => {
- history.push('/');
+ navigate('/');
};
const onClickBack = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx b/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx
index aad100495a..ca1ff60804 100644
--- a/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx
+++ b/frontend/src/component/common/ProtectedRoute/ProtectedRoute.tsx
@@ -1,24 +1,18 @@
-import { VFC } from 'react';
-import { Route, useLocation, Redirect, RouteProps } from 'react-router-dom';
+import { IRoute } from 'interfaces/route';
+import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
+import { LoginRedirect } from 'component/common/LoginRedirect/LoginRedirect';
interface IProtectedRouteProps {
- unauthorized?: boolean;
+ route: IRoute;
}
-const ProtectedRoute: VFC = ({
- component: Component,
- unauthorized,
- ...rest
-}) => {
- const { pathname, search } = useLocation();
- const redirect = encodeURIComponent(pathname + search);
- const loginLink = `/login?redirect=${redirect}`;
+export const ProtectedRoute = ({ route }: IProtectedRouteProps) => {
+ const { user } = useAuthUser();
+ const isLoggedIn = Boolean(user?.id);
- return unauthorized ? (
- } />
- ) : (
-
- );
+ if (!isLoggedIn && route.type === 'protected') {
+ return ;
+ }
+
+ return ;
};
-
-export default ProtectedRoute;
diff --git a/frontend/src/component/context/ContextList/ContextList.tsx b/frontend/src/component/context/ContextList/ContextList.tsx
index bce904009e..725507547c 100644
--- a/frontend/src/component/context/ContextList/ContextList.tsx
+++ b/frontend/src/component/context/ContextList/ContextList.tsx
@@ -1,6 +1,6 @@
import { useContext, useState, VFC } from 'react';
import { Add, Album, Delete, Edit } from '@mui/icons-material';
-import { Link, useHistory } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import {
Button,
IconButton,
@@ -35,7 +35,7 @@ const ContextList: VFC = () => {
const { context, refetchUnleashContext } = useUnleashContext();
const { removeContext } = useContextsApi();
const { setToastData, setToastApiError } = useToast();
- const history = useHistory();
+ const navigate = useNavigate();
const { classes: styles } = useStyles();
const onDeleteContext = async () => {
@@ -83,7 +83,7 @@ const ContextList: VFC = () => {
- history.push(`/context/edit/${field.name}`)
+ navigate(`/context/edit/${field.name}`)
}
size="large"
>
@@ -120,7 +120,7 @@ const ContextList: VFC = () => {
show={
history.push('/context/create')}
+ onClick={() => navigate('/context/create')}
size="large"
>
@@ -129,7 +129,7 @@ const ContextList: VFC = () => {
}
elseShow={
);
};
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx
index 60d8012e5c..31cabc6986 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.tsx
@@ -1,16 +1,15 @@
-import { useParams } from 'react-router';
import { ENVIRONMENT_STRATEGY_ERROR } from 'constants/apiErrors';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useToast from 'hooks/useToast';
import { IFeatureEnvironment } from 'interfaces/featureToggle';
-import { IFeatureViewParams } from 'interfaces/params';
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { UPDATE_FEATURE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions';
import React from 'react';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useStyles } from './FeatureOverviewEnvSwitch.styles';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IFeatureOverviewEnvSwitchProps {
env: IFeatureEnvironment;
@@ -25,7 +24,8 @@ const FeatureOverviewEnvSwitch = ({
text,
showInfoBox,
}: IFeatureOverviewEnvSwitchProps) => {
- const { featureId, projectId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
useFeatureApi();
const { refetchFeature } = useFeature(projectId, featureId);
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches.tsx
index d37c299b30..f2aca60a51 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches.tsx
@@ -1,18 +1,18 @@
import { Tooltip } from '@mui/material';
import { useState } from 'react';
-import { useParams } from 'react-router';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import { IFeatureViewParams } from 'interfaces/params';
import EnvironmentStrategyDialog from 'component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog';
import FeatureOverviewEnvSwitch from './FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch';
import { useStyles } from './FeatureOverviewEnvSwitches.styles';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureOverviewEnvSwitches = () => {
const { classes: styles } = useStyles();
- const { featureId, projectId } = useParams();
- useFeatureApi();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
+ useFeatureApi();
const [showInfoBox, setShowInfoBox] = useState(false);
const [environmentName, setEnvironmentName] = useState('');
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
index 1572a3e31e..0973e128b4 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironment.tsx
@@ -6,11 +6,9 @@ import {
} from '@mui/material';
import { ExpandMore } from '@mui/icons-material';
import React from 'react';
-import { useParams } from 'react-router';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics';
import { IFeatureEnvironment } from 'interfaces/featureToggle';
-import { IFeatureViewParams } from 'interfaces/params';
import { getFeatureMetrics } from 'utils/getFeatureMetrics';
import {
getFeatureStrategyIcon,
@@ -26,6 +24,7 @@ import FeatureOverviewEnvironmentFooter from './FeatureOverviewEnvironmentFooter
import FeatureOverviewEnvironmentMetrics from './FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics';
import { FeatureStrategyMenu } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu';
import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IStrategyIconObject {
count: number;
@@ -41,7 +40,8 @@ const FeatureOverviewEnvironment = ({
env,
}: IFeatureOverviewEnvironmentProps) => {
const { classes: styles } = useStyles();
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { metrics } = useFeatureMetrics(projectId, featureId);
const { feature } = useFeature(projectId, featureId);
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx
index de2ec0165c..33f8ebfc5a 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentBody/FeatureOverviewEnvironmentBody.tsx
@@ -1,10 +1,9 @@
-import { useParams } from 'react-router-dom';
-import { IFeatureViewParams } from 'interfaces/params';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import FeatureOverviewEnvironmentStrategies from '../FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategies';
import { useStyles } from '../FeatureOverviewEnvironment.styles';
import { IFeatureEnvironment } from 'interfaces/featureToggle';
import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IFeatureOverviewEnvironmentBodyProps {
getOverviewText: () => string;
@@ -15,7 +14,8 @@ const FeatureOverviewEnvironmentBody = ({
featureEnvironment,
getOverviewText,
}: IFeatureOverviewEnvironmentBodyProps) => {
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { classes: styles } = useStyles();
if (!featureEnvironment) {
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx
index ef484ee6f5..f7fdb10dd5 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentStrategies/FeatureOverviewEnvironmentStrategy/FeatureOverviewEnvironmentStrategy.tsx
@@ -1,7 +1,6 @@
import { Edit } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
-import { Link, useParams } from 'react-router-dom';
-import { IFeatureViewParams } from 'interfaces/params';
+import { Link } from 'react-router-dom';
import { IFeatureStrategy } from 'interfaces/strategy';
import {
getFeatureStrategyIcon,
@@ -14,6 +13,7 @@ import { useStyles } from './FeatureOverviewEnvironmentStrategy.styles';
import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit';
import { FeatureStrategyRemove } from 'component/feature/FeatureStrategy/FeatureStrategyRemove/FeatureStrategyRemove';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IFeatureOverviewEnvironmentStrategyProps {
environmentId: string;
@@ -24,7 +24,8 @@ const FeatureOverviewEnvironmentStrategy = ({
environmentId,
strategy,
}: IFeatureOverviewEnvironmentStrategyProps) => {
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const theme = useTheme();
const { classes: styles } = useStyles();
const Icon = getFeatureStrategyIcon(strategy.name);
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx
index 5e0bbc8f0d..95e27e9b81 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx
@@ -1,11 +1,10 @@
-import { useParams } from 'react-router-dom';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import { IFeatureViewParams } from 'interfaces/params';
-
import FeatureOverviewEnvironment from './FeatureOverviewEnvironment/FeatureOverviewEnvironment';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureOverviewEnvironments = () => {
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
if (!feature) return null;
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
index f7c0eda63b..9f6fc535e0 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx
@@ -1,26 +1,23 @@
import { capitalize } from '@mui/material';
import classnames from 'classnames';
-import { useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './FeatureOverviewMetadata.styles';
-
import { Edit } from '@mui/icons-material';
-import { IFeatureViewParams } from 'interfaces/params';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import useTags from 'hooks/api/getters/useTags/useTags';
import FeatureOverviewTags from './FeatureOverviewTags/FeatureOverviewTags';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureOverviewMetaData = () => {
const { classes: styles } = useStyles();
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { tags } = useTags(featureId);
-
const { feature } = useFeature(projectId, featureId);
-
const { project, description, type } = feature;
const IconComponent = getFeatureTypeIcons(type);
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx
index 16f30a1c2c..4e7c233a59 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewTags/FeatureOverviewTags.tsx
@@ -1,9 +1,7 @@
import React, { useContext, useState } from 'react';
import { Chip } from '@mui/material';
import { Close, Label } from '@mui/icons-material';
-import { useParams } from 'react-router-dom';
import useTags from 'hooks/api/getters/useTags/useTags';
-import { IFeatureViewParams } from 'interfaces/params';
import { useStyles } from './FeatureOverviewTags.styles';
import slackIcon from 'assets/icons/slack.svg';
import jiraIcon from 'assets/icons/jira.svg';
@@ -18,6 +16,7 @@ import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AccessContext from 'contexts/AccessContext';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IFeatureOverviewTagsProps extends React.HTMLProps {
projectId: string;
@@ -33,7 +32,7 @@ const FeatureOverviewTags: React.FC = ({
type: '',
});
const { classes: styles } = useStyles();
- const { featureId } = useParams();
+ const featureId = useRequiredPathParam('featureId');
const { tags, refetch } = useTags(featureId);
const { tagTypes } = useTagTypes();
const { deleteTagFromFeature } = useFeatureApi();
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
index ef3f8c9f22..e980c260f7 100644
--- a/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureOverview/StaleDialog/StaleDialog.tsx
@@ -1,6 +1,4 @@
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
-import { useParams } from 'react-router-dom';
-import { IFeatureViewParams } from 'interfaces/params';
import { DialogContentText } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
@@ -8,6 +6,7 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import React from 'react';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IStaleDialogProps {
open: boolean;
@@ -17,7 +16,8 @@ interface IStaleDialogProps {
const StaleDialog = ({ open, setOpen, stale }: IStaleDialogProps) => {
const { setToastData, setToastApiError } = useToast();
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { patchFeatureToggle } = useFeatureApi();
const { refetchFeature } = useFeature(projectId, featureId);
diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx
index f0b1caba9b..ff3deb626c 100644
--- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettings.tsx
@@ -4,16 +4,16 @@ import { useStyles } from './FeatureSettings.styles';
import { List, ListItem } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import FeatureSettingsProject from './FeatureSettingsProject/FeatureSettingsProject';
-import { useParams } from 'react-router-dom';
-import { IFeatureViewParams } from 'interfaces/params';
import { FeatureSettingsInformation } from './FeatureSettingsInformation/FeatureSettingsInformation';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const METADATA = 'metadata';
const PROJECT = 'project';
export const FeatureSettings = () => {
const { classes: styles } = useStyles();
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const [settings, setSettings] = useState(METADATA);
return (
diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsInformation/FeatureSettingsInformation.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsInformation/FeatureSettingsInformation.tsx
index 8ad007caca..72e07b3b24 100644
--- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsInformation/FeatureSettingsInformation.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsInformation/FeatureSettingsInformation.tsx
@@ -1,6 +1,6 @@
import { Typography } from '@mui/material';
import { Edit } from '@mui/icons-material';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
@@ -17,10 +17,10 @@ export const FeatureSettingsInformation = ({
}: IFeatureSettingsInformationProps) => {
const { classes: styles } = useStyles();
const { feature } = useFeature(projectId, featureId);
- const history = useHistory();
+ const navigate = useNavigate();
const onEdit = () => {
- history.push(`/projects/${projectId}/features/${featureId}/edit`);
+ navigate(`/projects/${projectId}/features/${featureId}/edit`);
};
return (
diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx
index b2ffa6859a..c665d4bf46 100644
--- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsMetadata/FeatureSettingsMetadata.tsx
@@ -3,19 +3,19 @@ import * as jsonpatch from 'fast-json-patch';
import { TextField } from '@mui/material';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import FeatureTypeSelect from './FeatureTypeSelect/FeatureTypeSelect';
-import { useParams } from 'react-router';
import AccessContext from 'contexts/AccessContext';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import { IFeatureViewParams } from 'interfaces/params';
import useToast from 'hooks/useToast';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureSettingsMetadata = () => {
const { hasAccess } = useContext(AccessContext);
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature, refetchFeature } = useFeature(projectId, featureId);
const [description, setDescription] = useState(feature.description);
const [type, setType] = useState(feature.type);
diff --git a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx
index 6b991b526e..8cc5c65d7b 100644
--- a/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProject.tsx
@@ -1,10 +1,9 @@
import { useContext, useEffect, useState } from 'react';
-import { useHistory, useParams } from 'react-router';
+import { useNavigate } from 'react-router';
import AccessContext from 'contexts/AccessContext';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useToast from 'hooks/useToast';
-import { IFeatureViewParams } from 'interfaces/params';
import { MOVE_FEATURE_TOGGLE } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
@@ -13,10 +12,12 @@ import FeatureSettingsProjectConfirm from './FeatureSettingsProjectConfirm/Featu
import { IPermission } from 'interfaces/user';
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureSettingsProject = () => {
const { hasAccess } = useContext(AccessContext);
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature, refetchFeature } = useFeature(projectId, featureId);
const [project, setProject] = useState(feature.project);
const [dirty, setDirty] = useState(false);
@@ -25,7 +26,7 @@ const FeatureSettingsProject = () => {
const { permissions = [] } = useAuthPermissions();
const { changeFeatureProject } = useFeatureApi();
const { setToastData, setToastApiError } = useToast();
- const history = useHistory();
+ const navigate = useNavigate();
useEffect(() => {
if (project !== feature.project) {
@@ -59,9 +60,9 @@ const FeatureSettingsProject = () => {
});
setDirty(false);
setShowConfirmDialog(false);
- history.replace(
- `/projects/${newProject}/features/${featureId}/settings`
- );
+ navigate(`/projects/${newProject}/features/${featureId}/settings`, {
+ replace: true,
+ });
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
index 8a4e9c8e65..d4bed4ec1b 100644
--- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/AddFeatureVariant.tsx
@@ -15,8 +15,6 @@ import { modalStyles, trim } from 'component/common/util';
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import { useParams } from 'react-router-dom';
-import { IFeatureViewParams } from 'interfaces/params';
import { IFeatureVariant } from 'interfaces/featureToggle';
import cloneDeep from 'lodash.clonedeep';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
@@ -26,6 +24,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import { useOverrides } from './useOverrides';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const payloadOptions = [
{ key: 'string', label: 'string' },
@@ -62,7 +61,8 @@ export const AddVariant = ({
const [overrides, overridesDispatch] = useOverrides([]);
const [error, setError] = useState>({});
const { classes: themeStyles } = useThemeStyles();
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
const [variants, setVariants] = useState([]);
const { context } = useUnleashContext();
diff --git a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
index 6f7cb3846f..bba9a17650 100644
--- a/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/FeatureVariantsList.tsx
@@ -14,8 +14,6 @@ import { AddVariant } from './AddFeatureVariant/AddFeatureVariant';
import { useContext, useEffect, useState } from 'react';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import { useParams } from 'react-router';
-import { IFeatureViewParams } from 'interfaces/params';
import AccessContext from 'contexts/AccessContext';
import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem';
import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions';
@@ -30,10 +28,12 @@ import cloneDeep from 'lodash.clonedeep';
import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureOverviewVariants = () => {
const { hasAccess } = useContext(AccessContext);
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
const { feature, refetchFeature } = useFeature(projectId, featureId);
const [variants, setVariants] = useState([]);
const [editing, setEditing] = useState(false);
diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx
index 39585aca34..85cebec8e1 100644
--- a/frontend/src/component/feature/FeatureView/FeatureView.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx
@@ -1,12 +1,17 @@
import { Tab, Tabs, useMediaQuery } from '@mui/material';
import React, { useState } from 'react';
import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material';
-import { Link, Route, useHistory, useParams, Switch } from 'react-router-dom';
+import {
+ Link,
+ Route,
+ useNavigate,
+ Routes,
+ useLocation,
+} from 'react-router-dom';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useProject from 'hooks/api/getters/useProject/useProject';
import useToast from 'hooks/useToast';
-import { IFeatureViewParams } from 'interfaces/params';
import {
CREATE_FEATURE,
DELETE_FEATURE,
@@ -27,9 +32,12 @@ import AddTagDialog from './FeatureOverview/AddTagDialog/AddTagDialog';
import StatusChip from 'component/common/StatusChip/StatusChip';
import { formatUnknownError } from 'utils/formatUnknownError';
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const FeatureView = () => {
- const { projectId, featureId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
+
const { refetch: projectRefetch } = useProject(projectId);
const [openTagDialog, setOpenTagDialog] = useState(false);
const { archiveFeatureToggle } = useFeatureApi();
@@ -44,7 +52,8 @@ export const FeatureView = () => {
);
const { classes: styles } = useStyles();
- const history = useHistory();
+ const navigate = useNavigate();
+ const { pathname } = useLocation();
const ref = useLoading(loading);
const basePath = `/projects/${projectId}/features/${featureId}`;
@@ -59,7 +68,7 @@ export const FeatureView = () => {
});
setShowDelDialog(false);
projectRefetch();
- history.push(`/projects/${projectId}`);
+ navigate(`/projects/${projectId}`);
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
setShowDelDialog(false);
@@ -88,9 +97,7 @@ export const FeatureView = () => {
},
];
- const activeTab =
- tabData.find(tab => tab.path === history.location.pathname) ??
- tabData[0];
+ const activeTab = tabData.find(tab => tab.path === pathname) ?? tabData[0];
const renderTabs = () => {
return tabData.map((tab, index) => {
@@ -100,7 +107,7 @@ export const FeatureView = () => {
key={tab.title}
label={tab.title}
value={tab.path}
- onClick={() => history.push(tab.path)}
+ onClick={() => navigate(tab.path)}
className={styles.tabButton}
/>
);
@@ -182,28 +189,13 @@ export const FeatureView = () => {
-
-
-
-
-
-
-
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
archiveToggle()}
open={showDelDialog}
diff --git a/frontend/src/component/feature/RedirectFeatureView/RedirectFeatureView.tsx b/frontend/src/component/feature/RedirectFeatureView/RedirectFeatureView.tsx
index 14c2dc6a79..65e06e2fc7 100644
--- a/frontend/src/component/feature/RedirectFeatureView/RedirectFeatureView.tsx
+++ b/frontend/src/component/feature/RedirectFeatureView/RedirectFeatureView.tsx
@@ -1,33 +1,31 @@
import { useEffect, useState } from 'react';
-import { Redirect, useParams } from 'react-router-dom';
+import { Navigate } from 'react-router-dom';
import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures';
import { getTogglePath } from 'utils/routePathHelpers';
import { FeatureSchema } from 'openapi';
-
-interface IRedirectParams {
- name: string;
-}
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const RedirectFeatureView = () => {
- const { name } = useParams();
+ const featureId = useRequiredPathParam('featureId');
const { features = [] } = useFeatures();
const [featureToggle, setFeatureToggle] = useState();
useEffect(() => {
const toggle = features.find(
- (toggle: FeatureSchema) => toggle.name === name
+ (toggle: FeatureSchema) => toggle.name === featureId
);
setFeatureToggle(toggle);
- }, [features, name]);
+ }, [features, featureId]);
if (!featureToggle) {
return null;
}
return (
-
);
};
diff --git a/frontend/src/component/feature/hooks/useFeatureForm.ts b/frontend/src/component/feature/hooks/useFeatureForm.ts
index 7894333925..86da30137e 100644
--- a/frontend/src/component/feature/hooks/useFeatureForm.ts
+++ b/frontend/src/component/feature/hooks/useFeatureForm.ts
@@ -1,8 +1,7 @@
import { useEffect, useState } from 'react';
-import { useParams } from 'react-router-dom';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import useQueryParams from 'hooks/useQueryParams';
-import { IFeatureViewParams } from 'interfaces/params';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const useFeatureForm = (
initialName = '',
@@ -11,7 +10,7 @@ const useFeatureForm = (
initialDescription = '',
initialImpressionData = false
) => {
- const { projectId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
const params = useQueryParams();
const { validateFeatureToggleName } = useFeatureApi();
const toggleQueryName = params.get('name');
diff --git a/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts b/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts
index a2dd576372..c27aefc85e 100644
--- a/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts
+++ b/frontend/src/component/history/EventLog/EventJson/EventJson.styles.ts
@@ -3,7 +3,7 @@ import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles()(theme => ({
historyItem: {
padding: '5px',
- '&:nth-child(odd)': {
+ '&:nth-of-type(odd)': {
backgroundColor: theme.code.background,
},
},
diff --git a/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx b/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx
index 2dfa48a23d..3abc1277a7 100644
--- a/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx
+++ b/frontend/src/component/history/FeatureEventHistoryPage/FeatureEventHistoryPage.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { useParams } from 'react-router-dom';
import { FeatureEventHistory } from '../FeatureEventHistory/FeatureEventHistory';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const FeatureEventHistoryPage = () => {
- const { toggleName } = useParams<{ toggleName: string }>();
+ const toggleName = useRequiredPathParam('toggleName');
return ;
};
diff --git a/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx b/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx
index c6cdb9fef1..01704a1970 100644
--- a/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx
+++ b/frontend/src/component/layout/LayoutPicker/LayoutPicker.tsx
@@ -21,41 +21,17 @@ export const LayoutPicker = ({ children }: ILayoutPickerProps) => {
};
const isStandalonePage = (pathname: string): boolean => {
- const isLoginPage = matchPath(pathname, {
- path: '/login',
+ return standalonePagePatterns.some(pattern => {
+ return matchPath(pattern, pathname);
});
-
- const isNewUserPage = matchPath(pathname, {
- path: '/new-user',
- });
-
- const isChangePasswordPage = matchPath(pathname, {
- path: '/reset-password',
- });
-
- const isResetPasswordSuccessPage = matchPath(pathname, {
- path: '/reset-password-success',
- });
-
- const isForgottenPasswordPage = matchPath(pathname, {
- path: '/forgotten-password',
- });
-
- const isSplashPage = matchPath(pathname, {
- path: '/splash/:id',
- });
-
- const is404 = matchPath(pathname, {
- path: '/404',
- });
-
- return Boolean(
- isLoginPage ||
- isNewUserPage ||
- isChangePasswordPage ||
- isResetPasswordSuccessPage ||
- isForgottenPasswordPage ||
- isSplashPage ||
- is404
- );
};
+
+const standalonePagePatterns = [
+ '/login',
+ '/new-user',
+ '/reset-password',
+ '/reset-password-success',
+ '/forgotten-password',
+ '/splash/:splashId',
+ '/404',
+];
diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
index 89cea78e85..984e2e2d12 100644
--- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
+++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap
@@ -21,23 +21,23 @@ Array [
"component": [Function],
"menu": Object {},
"parent": "/projects",
- "path": "/projects/:id/edit",
- "title": ":id",
+ "path": "/projects/:projectId/edit",
+ "title": ":projectId",
"type": "protected",
},
Object {
"component": [Function],
"menu": Object {},
"parent": "/archive",
- "path": "/projects/:id/archived",
- "title": ":name",
+ "path": "/projects/:projectId/archived",
+ "title": ":projectId",
"type": "protected",
},
Object {
"component": [Function],
"menu": Object {},
- "parent": "/projects/:id/features/:name/:activeTab",
- "path": "/projects/:id/features/:name/:activeTab/copy",
+ "parent": "/projects/:projectId/features/:featureId/:activeTab",
+ "path": "/projects/:projectId/features/:featureId/:activeTab/copy",
"title": "Copy",
"type": "protected",
},
@@ -53,22 +53,14 @@ Array [
"component": [Function],
"menu": Object {},
"parent": "/projects",
- "path": "/projects/:projectId/features/:featureId",
+ "path": "/projects/:projectId/features/:featureId/*",
"title": "FeatureView",
"type": "protected",
},
Object {
"component": [Function],
"menu": Object {},
- "parent": "/projects",
- "path": "/projects/:id/features/:name/:activeTab",
- "title": ":name",
- "type": "protected",
- },
- Object {
- "component": [Function],
- "menu": Object {},
- "parent": "/projects/:id/features",
+ "parent": "/projects/:projectId/features",
"path": "/projects/:projectId/create-toggle",
"title": "Create feature toggle",
"type": "protected",
@@ -77,8 +69,8 @@ Array [
"component": [Function],
"menu": Object {},
"parent": "/features",
- "path": "/projects/:projectId/features2/:name",
- "title": ":name",
+ "path": "/projects/:projectId/features2/:featureId",
+ "title": ":featureId",
"type": "protected",
},
Object {
@@ -86,8 +78,8 @@ Array [
"flag": "P",
"menu": Object {},
"parent": "/projects",
- "path": "/projects/:id/:activeTab",
- "title": ":id",
+ "path": "/projects/:projectId/:activeTab",
+ "title": ":projectId",
"type": "protected",
},
Object {
@@ -95,8 +87,8 @@ Array [
"flag": "P",
"menu": Object {},
"parent": "/projects",
- "path": "/projects/:id",
- "title": ":id",
+ "path": "/projects/:projectId",
+ "title": ":projectId",
"type": "protected",
},
Object {
@@ -112,8 +104,8 @@ Array [
"component": [Function],
"menu": Object {},
"parent": "/features",
- "path": "/features/:activeTab/:name",
- "title": ":name",
+ "path": "/features/:activeTab/:featureId",
+ "title": ":featureId",
"type": "protected",
},
Object {
diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts
index f8f060c921..5a0e133ee2 100644
--- a/frontend/src/component/menu/routes.ts
+++ b/frontend/src/component/menu/routes.ts
@@ -72,24 +72,24 @@ export const routes: IRoute[] = [
menu: {},
},
{
- path: '/projects/:id/edit',
+ path: '/projects/:projectId/edit',
parent: '/projects',
- title: ':id',
+ title: ':projectId',
component: EditProject,
type: 'protected',
menu: {},
},
{
- path: '/projects/:id/archived',
- title: ':name',
+ path: '/projects/:projectId/archived',
+ title: ':projectId',
parent: '/archive',
component: RedirectArchive,
type: 'protected',
menu: {},
},
{
- path: '/projects/:id/features/:name/:activeTab/copy',
- parent: '/projects/:id/features/:name/:activeTab',
+ path: '/projects/:projectId/features/:featureId/:activeTab/copy',
+ parent: '/projects/:projectId/features/:featureId/:activeTab',
title: 'Copy',
component: CopyFeatureToggle,
type: 'protected',
@@ -104,50 +104,42 @@ export const routes: IRoute[] = [
menu: {},
},
{
- path: '/projects/:projectId/features/:featureId',
+ path: '/projects/:projectId/features/:featureId/*',
parent: '/projects',
title: 'FeatureView',
component: FeatureView,
type: 'protected',
menu: {},
},
- {
- path: '/projects/:id/features/:name/:activeTab',
- parent: '/projects',
- title: ':name',
- component: FeatureView,
- type: 'protected',
- menu: {},
- },
{
path: '/projects/:projectId/create-toggle',
- parent: '/projects/:id/features',
+ parent: '/projects/:projectId/features',
title: 'Create feature toggle',
component: CreateFeature,
type: 'protected',
menu: {},
},
{
- path: '/projects/:projectId/features2/:name',
+ path: '/projects/:projectId/features2/:featureId',
parent: '/features',
- title: ':name',
+ title: ':featureId',
component: RedirectFeatureView,
type: 'protected',
menu: {},
},
{
- path: '/projects/:id/:activeTab',
+ path: '/projects/:projectId/:activeTab',
parent: '/projects',
- title: ':id',
+ title: ':projectId',
component: Project,
flag: P,
type: 'protected',
menu: {},
},
{
- path: '/projects/:id',
+ path: '/projects/:projectId',
parent: '/projects',
- title: ':id',
+ title: ':projectId',
component: Project,
flag: P,
type: 'protected',
@@ -163,9 +155,9 @@ export const routes: IRoute[] = [
// Features
{
- path: '/features/:activeTab/:name',
+ path: '/features/:activeTab/:featureId',
parent: '/features',
- title: ':name',
+ title: ':featureId',
component: RedirectFeatureView,
type: 'protected',
menu: {},
diff --git a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
index 9d8e34fa53..157db22682 100644
--- a/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
+++ b/frontend/src/component/project/Project/CreateProject/CreateProject.tsx
@@ -1,4 +1,4 @@
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import ProjectForm from '../ProjectForm/ProjectForm';
import useProjectForm from '../hooks/useProjectForm';
import { CreateButton } from 'component/common/CreateButton/CreateButton';
@@ -14,7 +14,7 @@ const CreateProject = () => {
const { setToastData, setToastApiError } = useToast();
const { refetchUser } = useAuthUser();
const { uiConfig } = useUiConfig();
- const history = useHistory();
+ const navigate = useNavigate();
const {
projectId,
projectName,
@@ -42,7 +42,7 @@ const CreateProject = () => {
try {
await createProject(payload);
refetchUser();
- history.push(`/projects/${projectId}`);
+ navigate(`/projects/${projectId}`);
setToastData({
title: 'Project created',
text: 'Now you can add toggles to this project',
@@ -65,7 +65,7 @@ const CreateProject = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/project/Project/EditProject/EditProject.tsx b/frontend/src/component/project/Project/EditProject/EditProject.tsx
index cf88e84fc7..4fbc658526 100644
--- a/frontend/src/component/project/Project/EditProject/EditProject.tsx
+++ b/frontend/src/component/project/Project/EditProject/EditProject.tsx
@@ -1,4 +1,4 @@
-import { useHistory, useParams } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import ProjectForm from '../ProjectForm/ProjectForm';
import useProjectForm from '../hooks/useProjectForm';
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton';
@@ -9,13 +9,14 @@ import useProject from 'hooks/api/getters/useProject/useProject';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const EditProject = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
- const { id } = useParams<{ id: string }>();
+ const id = useRequiredPathParam('projectId');
const { project } = useProject(id);
- const history = useHistory();
+ const navigate = useNavigate();
const {
projectId,
projectName,
@@ -52,7 +53,7 @@ const EditProject = () => {
try {
await editProject(id, payload);
refetch();
- history.push(`/projects/${id}`);
+ navigate(`/projects/${id}`);
setToastData({
title: 'Project information updated',
type: 'success',
@@ -64,7 +65,7 @@ const EditProject = () => {
};
const handleCancel = () => {
- history.goBack();
+ navigate(-1);
};
return (
diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx
index 1456807f54..25d279b55f 100644
--- a/frontend/src/component/project/Project/Project.tsx
+++ b/frontend/src/component/project/Project/Project.tsx
@@ -1,4 +1,4 @@
-import { useHistory, useParams } from 'react-router';
+import { useNavigate } from 'react-router';
import useProject from 'hooks/api/getters/useProject/useProject';
import useLoading from 'hooks/useLoading';
import ApiError from 'component/common/ApiError/ApiError';
@@ -17,27 +17,30 @@ import ProjectHealth from './ProjectHealth/ProjectHealth';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { TabPanel } from 'component/common/TabNav/TabPanel/TabPanel';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
+import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
const Project = () => {
- const { id, activeTab } = useParams<{ id: string; activeTab: string }>();
+ const projectId = useRequiredPathParam('projectId');
+ const activeTab = useOptionalPathParam('activeTab');
const params = useQueryParams();
- const { project, error, loading, refetch } = useProject(id);
+ const { project, error, loading, refetch } = useProject(projectId);
const ref = useLoading(loading);
const { setToastData } = useToast();
const { classes: styles } = useStyles();
- const history = useHistory();
+ const navigate = useNavigate();
- const basePath = `/projects/${id}`;
+ const basePath = `/projects/${projectId}`;
const tabData = [
{
title: 'Overview',
- component: ,
+ component: ,
path: basePath,
name: 'overview',
},
{
title: 'Health',
- component: ,
+ component: ,
path: `${basePath}/health`,
name: 'health',
},
@@ -49,13 +52,13 @@ const Project = () => {
},
{
title: 'Environments',
- component: ,
+ component: ,
path: `${basePath}/environments`,
name: 'environments',
},
{
title: 'Archive',
- component: ,
+ component: ,
path: `${basePath}/archive`,
name: 'archive',
},
@@ -92,7 +95,7 @@ const Project = () => {
id={`tab-${index}`}
aria-controls={`tabpanel-${index}`}
label={tab.title}
- onClick={() => history.push(tab.path)}
+ onClick={() => navigate(tab.path)}
className={styles.tabButton}
/>
);
@@ -122,7 +125,9 @@ const Project = () => {
history.push(`/projects/${id}/edit`)}
+ onClick={() =>
+ navigate(`/projects/${projectId}/edit`)
+ }
tooltip="Edit project"
data-loading
>
diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx
index 3a84b279a6..a33325da54 100644
--- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx
+++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeatureToggles.tsx
@@ -2,8 +2,7 @@ import { useContext, useMemo, useState } from 'react';
import { IconButton } from '@mui/material';
import { Add } from '@mui/icons-material';
import FilterListIcon from '@mui/icons-material/FilterList';
-import { useParams } from 'react-router';
-import { Link, useHistory } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import AccessContext from 'contexts/AccessContext';
import { SearchField } from 'component/common/SearchField/SearchField';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@@ -18,6 +17,7 @@ import { useStyles } from './ProjectFeatureToggles.styles';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import classnames from 'classnames';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IProjectFeatureTogglesProps {
features: IFeatureToggleListItem[];
@@ -29,8 +29,8 @@ export const ProjectFeatureToggles = ({
loading,
}: IProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles();
- const { id } = useParams<{ id: string }>();
- const history = useHistory();
+ const projectId = useRequiredPathParam('projectId');
+ const navigate = useNavigate();
const { hasAccess } = useContext(AccessContext);
const { uiConfig } = useUiConfig();
const [filter, setFilter] = useState('');
@@ -76,16 +76,16 @@ export const ProjectFeatureToggles = ({
- history.push(
+ navigate(
getCreateTogglePath(
- id,
+ projectId,
uiConfig.flags.E
)
)
}
maxWidth="700px"
Icon={Add}
- projectId={id}
+ projectId={projectId}
permission={CREATE_FEATURE}
className={styles.button}
>
@@ -102,7 +102,7 @@ export const ProjectFeatureToggles = ({
}
elseShow={
@@ -111,11 +111,11 @@ export const ProjectFeatureToggles = ({
No feature toggles added yet.
{
- const { id: projectId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
const { classes: styles } = useStyles();
const { access, refetchProjectAccess } = useProjectAccess(projectId);
const { setToastData } = useToast();
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx
index d491217f48..7dc26da7a3 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessAddUser/ProjectAccessAddUser.tsx
@@ -6,32 +6,32 @@ import {
Button,
InputAdornment,
SelectChangeEvent,
+ Alert,
} from '@mui/material';
import { Search } from '@mui/icons-material';
import Autocomplete from '@mui/material/Autocomplete';
-import { Alert } from '@mui/material';
import { ProjectRoleSelect } from '../ProjectRoleSelect/ProjectRoleSelect';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
-import { useParams } from 'react-router-dom';
import useToast from 'hooks/useToast';
import useProjectAccess, {
IProjectAccessUser,
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
import { IProjectRole } from 'interfaces/role';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IProjectAccessAddUserProps {
roles: IProjectRole[];
}
export const ProjectAccessAddUser = ({ roles }: IProjectAccessAddUserProps) => {
- const { id } = useParams<{ id: string }>();
+ const projectId = useRequiredPathParam('projectId');
const [user, setUser] = useState();
const [role, setRole] = useState();
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const { setToastData } = useToast();
- const { refetchProjectAccess, access } = useProjectAccess(id);
+ const { refetchProjectAccess, access } = useProjectAccess(projectId);
const { searchProjectUser, addUserToRole } = useProjectApi();
@@ -114,7 +114,7 @@ export const ProjectAccessAddUser = ({ roles }: IProjectAccessAddUserProps) => {
}
try {
- await addUserToRole(id, role.id, user.id);
+ await addUserToRole(projectId, role.id, user.id);
refetchProjectAccess();
setUser(undefined);
setOptions([]);
@@ -129,9 +129,9 @@ export const ProjectAccessAddUser = ({ roles }: IProjectAccessAddUserProps) => {
if (
e
.toString()
- .includes(`User already has access to project=${id}`)
+ .includes(`User already has access to project=${projectId}`)
) {
- error = `User already has access to project ${id}`;
+ error = `User already has access to project ${projectId}`;
} else {
error = e.toString() || 'Server problems when adding users.';
}
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessList/ProjectAccessListItem/ProjectAccessListItem.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessList/ProjectAccessListItem/ProjectAccessListItem.tsx
index a8a5a6608c..9d0225f4dd 100644
--- a/frontend/src/component/project/ProjectAccess/ProjectAccessList/ProjectAccessListItem/ProjectAccessListItem.tsx
+++ b/frontend/src/component/project/ProjectAccess/ProjectAccessList/ProjectAccessListItem/ProjectAccessListItem.tsx
@@ -8,17 +8,16 @@ import {
SelectChangeEvent,
} from '@mui/material';
import { Delete } from '@mui/icons-material';
-import { useParams } from 'react-router-dom';
import {
IProjectAccessOutput,
IProjectAccessUser,
} from 'hooks/api/getters/useProjectAccess/useProjectAccess';
-import { IProjectViewParams } from 'interfaces/params';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { ProjectRoleSelect } from 'component/project/ProjectAccess/ProjectRoleSelect/ProjectRoleSelect';
import { useStyles } from '../ProjectAccessListItem/ProjectAccessListItem.styles';
import React from 'react';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IProjectAccessListItemProps {
user: IProjectAccessUser;
@@ -33,7 +32,7 @@ export const ProjectAccessListItem = ({
handleRoleChange,
handleRemoveAccess,
}: IProjectAccessListItemProps) => {
- const { id: projectId } = useParams();
+ const projectId = useRequiredPathParam('projectId');
const { classes: styles } = useStyles();
const labelId = `checkbox-list-secondary-label-${user.id}`;
diff --git a/frontend/src/component/project/ProjectCard/ProjectCard.tsx b/frontend/src/component/project/ProjectCard/ProjectCard.tsx
index 066848863d..03e331eedc 100644
--- a/frontend/src/component/project/ProjectCard/ProjectCard.tsx
+++ b/frontend/src/component/project/ProjectCard/ProjectCard.tsx
@@ -3,7 +3,7 @@ import { useStyles } from './ProjectCard.styles';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIcon.svg';
import { useState } from 'react';
-import { useHistory } from 'react-router-dom';
+import { useNavigate } from 'react-router-dom';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
@@ -36,7 +36,7 @@ export const ProjectCard = ({
const [anchorEl, setAnchorEl] = useState(null);
const [showDelDialog, setShowDelDialog] = useState(false);
const { deleteProject } = useProjectApi();
- const history = useHistory();
+ const navigate = useNavigate();
const { setToastData, setToastApiError } = useToast();
// @ts-expect-error
@@ -92,7 +92,7 @@ export const ProjectCard = ({