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 = ({ { e.preventDefault(); - history.push(getProjectEditPath(id)); + navigate(getProjectEditPath(id)); }} > diff --git a/frontend/src/component/project/ProjectList/ProjectList.tsx b/frontend/src/component/project/ProjectList/ProjectList.tsx index 17e300db2d..ab28fe49ff 100644 --- a/frontend/src/component/project/ProjectList/ProjectList.tsx +++ b/frontend/src/component/project/ProjectList/ProjectList.tsx @@ -1,5 +1,5 @@ import { useContext, useMemo, useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { mutate } from 'swr'; import { getProjectFetcher } from 'hooks/api/getters/useProject/getProjectFetcher'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; @@ -45,7 +45,7 @@ function resolveCreateButtonData(isOss: boolean, hasAccess: boolean) { export const ProjectListNew = () => { const { hasAccess } = useContext(AccessContext); - const history = useHistory(); + const navigate = useNavigate(); const { classes: styles } = useStyles(); const { projects, loading, error, refetch } = useProjects(); const [fetchedProjects, setFetchedProjects] = useState({}); @@ -94,12 +94,7 @@ export const ProjectListNew = () => { return ( { actions={ history.push('/projects/create')} + onClick={() => navigate('/projects/create')} maxWidth="700px" permission={CREATE_PROJECT} disabled={createButtonData.disabled} diff --git a/frontend/src/component/providers/SWRProvider/SWRProvider.tsx b/frontend/src/component/providers/SWRProvider/SWRProvider.tsx index 35fc42c88f..95ba668eb3 100644 --- a/frontend/src/component/providers/SWRProvider/SWRProvider.tsx +++ b/frontend/src/component/providers/SWRProvider/SWRProvider.tsx @@ -1,12 +1,12 @@ import { mutate, SWRConfig, useSWRConfig } from 'swr'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router'; import useToast from 'hooks/useToast'; import { formatApiPath } from 'utils/formatPath'; import React from 'react'; import { USER_ENDPOINT_PATH } from 'hooks/api/getters/useAuth/useAuthEndpoint'; interface ISWRProviderProps { - isUnauthorized: () => boolean; + isUnauthorized: boolean; } const INVALID_TOKEN_ERROR = 'InvalidTokenError'; @@ -16,7 +16,7 @@ const SWRProvider: React.FC = ({ isUnauthorized, }) => { const { cache } = useSWRConfig(); - const history = useHistory(); + const navigate = useNavigate(); const { setToastApiError } = useToast(); // @ts-expect-error @@ -41,11 +41,11 @@ const SWRProvider: React.FC = ({ // @ts-expect-error cache.clear(); - history.push('/login'); + navigate('/login'); return; } - if (!isUnauthorized()) { + if (!isUnauthorized) { setToastApiError(error.message); } }; diff --git a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx index ec075cff94..d53224c917 100644 --- a/frontend/src/component/segments/CreateSegment/CreateSegment.tsx +++ b/frontend/src/component/segments/CreateSegment/CreateSegment.tsx @@ -7,7 +7,7 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; import React, { useContext } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useSegmentForm } from '../hooks/useSegmentForm'; import { SegmentForm } from '../SegmentForm/SegmentForm'; @@ -21,7 +21,7 @@ export const CreateSegment = () => { const { uiConfig } = useUiConfig(); const { setToastData, setToastApiError } = useToast(); const { showFeedbackCES } = useContext(feedbackCESContext); - const history = useHistory(); + const navigate = useNavigate(); const { createSegment, loading } = useSegmentsApi(); const { refetchSegments } = useSegments(); @@ -56,7 +56,7 @@ export const CreateSegment = () => { try { await createSegment(getSegmentPayload()); await refetchSegments(); - history.push('/segments/'); + navigate('/segments/'); setToastData({ title: 'Segment created', confetti: true, diff --git a/frontend/src/component/segments/EditSegment/EditSegment.tsx b/frontend/src/component/segments/EditSegment/EditSegment.tsx index f29881d9cb..7757d30bcf 100644 --- a/frontend/src/component/segments/EditSegment/EditSegment.tsx +++ b/frontend/src/component/segments/EditSegment/EditSegment.tsx @@ -8,7 +8,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useToast from 'hooks/useToast'; import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useSegmentForm } from '../hooks/useSegmentForm'; import { SegmentForm } from '../SegmentForm/SegmentForm'; @@ -24,7 +24,7 @@ export const EditSegment = () => { const { segment } = useSegment(Number(segmentId)); const { uiConfig } = useUiConfig(); const { setToastData, setToastApiError } = useToast(); - const history = useHistory(); + const navigate = useNavigate(); const { updateSegment, loading } = useSegmentsApi(); const { refetchSegments } = useSegments(); @@ -64,7 +64,7 @@ export const EditSegment = () => { try { await updateSegment(segment.id, getSegmentPayload()); await refetchSegments(); - history.push('/segments/'); + navigate('/segments/'); setToastData({ title: 'Segment updated', type: 'success', diff --git a/frontend/src/component/segments/SegmentFormStepOne/SegmentFormStepOne.tsx b/frontend/src/component/segments/SegmentFormStepOne/SegmentFormStepOne.tsx index be83ebe4ea..ed6b244672 100644 --- a/frontend/src/component/segments/SegmentFormStepOne/SegmentFormStepOne.tsx +++ b/frontend/src/component/segments/SegmentFormStepOne/SegmentFormStepOne.tsx @@ -1,7 +1,7 @@ import { Button } from '@mui/material'; import Input from 'component/common/Input/Input'; import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useStyles } from 'component/segments/SegmentFormStepOne/SegmentFormStepOne.styles'; import { SegmentFormStep } from '../SegmentForm/SegmentForm'; import { @@ -30,7 +30,7 @@ export const SegmentFormStepOne: React.FC = ({ clearErrors, setCurrentStep, }) => { - const history = useHistory(); + const navigate = useNavigate(); const { classes: styles } = useStyles(); return ( @@ -78,7 +78,7 @@ export const SegmentFormStepOne: React.FC = ({ type="button" className={styles.cancelButton} onClick={() => { - history.push('/segments'); + navigate('/segments'); }} > Cancel diff --git a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx index 5861693ffd..5c5be00845 100644 --- a/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx +++ b/frontend/src/component/segments/SegmentFormStepTwo/SegmentFormStepTwo.tsx @@ -12,7 +12,7 @@ import { } from 'component/providers/AccessProvider/permissions'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; import { IConstraint } from 'interfaces/strategy'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useStyles } from 'component/segments/SegmentFormStepTwo/SegmentFormStepTwo.styles'; import { ConstraintAccordionList, @@ -46,7 +46,7 @@ export const SegmentFormStepTwo: React.FC = ({ mode, }) => { const constraintsAccordionListRef = useRef(); - const history = useHistory(); + const navigate = useNavigate(); const { hasAccess } = useContext(AccessContext); const { classes: styles } = useStyles(); const { context = [] } = useUnleashContext(); @@ -152,7 +152,7 @@ export const SegmentFormStepTwo: React.FC = ({ type="button" className={styles.cancelButton} onClick={() => { - history.push('/segments'); + navigate('/segments'); }} > Cancel diff --git a/frontend/src/component/segments/SegmentList/SegmentList.tsx b/frontend/src/component/segments/SegmentList/SegmentList.tsx index 8305cca8b7..fcd61399f8 100644 --- a/frontend/src/component/segments/SegmentList/SegmentList.tsx +++ b/frontend/src/component/segments/SegmentList/SegmentList.tsx @@ -17,7 +17,7 @@ import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PageContent from 'component/common/PageContent'; @@ -27,7 +27,7 @@ import { SegmentDocsWarning } from 'component/segments/SegmentDocs/SegmentDocs'; import { NAVIGATE_TO_CREATE_SEGMENT } from 'utils/testIds'; export const SegmentsList = () => { - const history = useHistory(); + const navigate = useNavigate(); const { segments = [], refetchSegments } = useSegments(); const { deleteSegment } = useSegmentsApi(); const { page, pages, nextPage, prevPage, setPageIndex, pageIndex } = @@ -95,7 +95,7 @@ export const SegmentsList = () => { title="Segments" actions={ history.push('/segments/create')} + onClick={() => navigate('/segments/create')} permission={CREATE_SEGMENT} data-testid={NAVIGATE_TO_CREATE_SEGMENT} > diff --git a/frontend/src/component/segments/SegmentList/SegmentListItem/SegmentListItem.tsx b/frontend/src/component/segments/SegmentList/SegmentListItem/SegmentListItem.tsx index b983ba2dce..b2d0592f8c 100644 --- a/frontend/src/component/segments/SegmentList/SegmentListItem/SegmentListItem.tsx +++ b/frontend/src/component/segments/SegmentList/SegmentListItem/SegmentListItem.tsx @@ -8,7 +8,7 @@ import { import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import TimeAgo from 'react-timeago'; import { ISegment } from 'interfaces/segment'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { SEGMENT_DELETE_BTN_ID } from 'utils/testIds'; import React from 'react'; @@ -34,7 +34,7 @@ export const SegmentListItem = ({ setDelDialog, }: ISegmentListItemProps) => { const { classes: styles } = useStyles(); - const { push } = useHistory(); + const navigate = useNavigate(); return ( @@ -63,7 +63,7 @@ export const SegmentListItem = ({ { - push(`/segments/edit/${id}`); + navigate(`/segments/edit/${id}`); }} permission={UPDATE_SEGMENT} tooltip="Edit segment" diff --git a/frontend/src/component/splash/SplashPage/SplashPage.tsx b/frontend/src/component/splash/SplashPage/SplashPage.tsx index bbac75b27c..1620c5144e 100644 --- a/frontend/src/component/splash/SplashPage/SplashPage.tsx +++ b/frontend/src/component/splash/SplashPage/SplashPage.tsx @@ -1,4 +1,4 @@ -import { Switch, Route, useHistory, Redirect } from 'react-router-dom'; +import { useNavigate, Navigate } from 'react-router-dom'; import { SplashPageEnvironments } from '../SplashPageEnvironments/SplashPageEnvironments'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi'; @@ -30,34 +30,29 @@ export const SplashPage = () => { return null; } - return ( - - - - - - - - - - - - ); + switch (splashId) { + case 'environments': + return ; + case 'operators': + return ; + default: + return ; + } }; const useNavigationOnKeydown = (key: string, path: string) => { - const { push } = useHistory(); + const navigate = useNavigate(); useEffect(() => { const handler = (event: KeyboardEvent) => { if (event.code === key) { - push(path); + navigate(path); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); - }, [key, path, push]); + }, [key, path, navigate]); }; const isKnownSplashId = (value: string): value is SplashId => { diff --git a/frontend/src/component/splash/SplashPageEnvironments/SplashPageEnvironments.tsx b/frontend/src/component/splash/SplashPageEnvironments/SplashPageEnvironments.tsx index 4a05fccb92..24db5de1fd 100644 --- a/frontend/src/component/splash/SplashPageEnvironments/SplashPageEnvironments.tsx +++ b/frontend/src/component/splash/SplashPageEnvironments/SplashPageEnvironments.tsx @@ -4,14 +4,14 @@ import { VpnKey, CloudCircle } from '@mui/icons-material'; import { useStyles } from 'component/splash/SplashPageEnvironments/SplashPageEnvironments.styles'; import { ReactComponent as Logo1 } from 'assets/img/splashEnv1.svg'; import { ReactComponent as Logo2 } from 'assets/img/splashEnv2.svg'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; export const SplashPageEnvironments = () => { const { classes: styles } = useStyles(); - const { push } = useHistory(); + const navigate = useNavigate(); const onFinish = () => { - push('/'); + navigate('/'); }; return ( diff --git a/frontend/src/component/splash/SplashPageOperators/SplashPageOperators.tsx b/frontend/src/component/splash/SplashPageOperators/SplashPageOperators.tsx index 7fe7bf253e..73dcd3c258 100644 --- a/frontend/src/component/splash/SplashPageOperators/SplashPageOperators.tsx +++ b/frontend/src/component/splash/SplashPageOperators/SplashPageOperators.tsx @@ -1,11 +1,11 @@ import { useStyles } from 'component/splash/SplashPageOperators/SplashPageOperators.styles'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Button, IconButton } from '@mui/material'; import { CloseOutlined } from '@mui/icons-material'; import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert'; export const SplashPageOperators = () => { - const { push } = useHistory(); + const navigate = useNavigate(); const { classes: styles } = useStyles(); return ( @@ -15,7 +15,7 @@ export const SplashPageOperators = () => {

New strategy operators

push('/')} + onClick={() => navigate('/')} size="large" > diff --git a/frontend/src/component/splash/SplashPageRedirect/SplashPageRedirect.tsx b/frontend/src/component/splash/SplashPageRedirect/SplashPageRedirect.tsx index 71ecf37c8a..cd5bc9e14b 100644 --- a/frontend/src/component/splash/SplashPageRedirect/SplashPageRedirect.tsx +++ b/frontend/src/component/splash/SplashPageRedirect/SplashPageRedirect.tsx @@ -1,5 +1,5 @@ import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash'; -import { useLocation, Redirect } from 'react-router-dom'; +import { useLocation, Navigate } from 'react-router-dom'; import { matchPath } from 'react-router'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { IFlags } from 'interfaces/uiConfig'; @@ -18,7 +18,7 @@ export const SplashPageRedirect = () => { return null; } - if (matchPath(pathname, { path: '/splash/:splashId' })) { + if (matchPath('/splash/:splashId', pathname)) { // We've already redirected to the splash page. return null; } @@ -41,7 +41,7 @@ export const SplashPageRedirect = () => { return null; } - return ; + return ; }; const hasSeenSplashId = (splashId: SplashId, splash: IAuthSplash): boolean => { diff --git a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx b/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx index 440d8ac040..71143c4aae 100644 --- a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx +++ b/frontend/src/component/strategies/CreateStrategy/CreateStrategy.tsx @@ -1,4 +1,4 @@ -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; import FormTemplate from 'component/common/FormTemplate/FormTemplate'; @@ -13,7 +13,7 @@ import { CreateButton } from 'component/common/CreateButton/CreateButton'; export const CreateStrategy = () => { const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const history = useHistory(); + const navigate = useNavigate(); const { strategyName, strategyDesc, @@ -41,7 +41,7 @@ export const CreateStrategy = () => { try { await createStrategy(payload); refetchStrategies(); - history.push(`/strategies/${strategyName}`); + navigate(`/strategies/${strategyName}`); setToastData({ title: 'Strategy created', text: 'Successfully created strategy', @@ -64,7 +64,7 @@ export const CreateStrategy = () => { }; const handleCancel = () => { - history.goBack(); + navigate(-1); }; return ( diff --git a/frontend/src/component/strategies/EditStrategy/EditStrategy.tsx b/frontend/src/component/strategies/EditStrategy/EditStrategy.tsx index f80ab8b0d6..bb21258856 100644 --- a/frontend/src/component/strategies/EditStrategy/EditStrategy.tsx +++ b/frontend/src/component/strategies/EditStrategy/EditStrategy.tsx @@ -1,4 +1,4 @@ -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; import FormTemplate from 'component/common/FormTemplate/FormTemplate'; @@ -10,12 +10,13 @@ import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { formatUnknownError } from 'utils/formatUnknownError'; import useStrategy from 'hooks/api/getters/useStrategy/useStrategy'; import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; export const EditStrategy = () => { const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const history = useHistory(); - const { name } = useParams<{ name: string }>(); + const navigate = useNavigate(); + const name = useRequiredPathParam('name'); const { strategy } = useStrategy(name); const { strategyName, @@ -44,7 +45,7 @@ export const EditStrategy = () => { const payload = getStrategyPayload(); try { await updateStrategy(payload); - history.push(`/strategies/${strategyName}`); + navigate(`/strategies/${strategyName}`); setToastData({ type: 'success', title: 'Success', @@ -67,7 +68,7 @@ export const EditStrategy = () => { }; const handleCancel = () => { - history.goBack(); + navigate(-1); }; return ( diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx index 7bfe0afcfc..84206921e8 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import useMediaQuery from '@mui/material/useMediaQuery'; import { IconButton, @@ -45,7 +45,7 @@ interface IDialogueMetaData { } export const StrategiesList = () => { - const history = useHistory(); + const navigate = useNavigate(); const { classes: styles } = useStyles(); const smallScreen = useMediaQuery('(max-width:700px)'); const { hasAccess } = useContext(AccessContext); @@ -70,7 +70,7 @@ export const StrategiesList = () => { show={ history.push('/strategies/create')} + onClick={() => navigate('/strategies/create')} permission={CREATE_STRATEGY} tooltip="New strategy" > @@ -79,7 +79,7 @@ export const StrategiesList = () => { } elseShow={ history.push('/strategies/create')} + onClick={() => navigate('/strategies/create')} color="primary" permission={CREATE_STRATEGY} data-testid={ADD_NEW_STRATEGY_ID} @@ -204,7 +204,7 @@ export const StrategiesList = () => { show={ - history.push(`/strategies/${strategy?.name}/edit`) + navigate(`/strategies/${strategy?.name}/edit`) } permission={UPDATE_STRATEGY} tooltip="Edit strategy" diff --git a/frontend/src/component/strategies/StrategyView/StrategyView.tsx b/frontend/src/component/strategies/StrategyView/StrategyView.tsx index f9fa032f0c..5883b04b0c 100644 --- a/frontend/src/component/strategies/StrategyView/StrategyView.tsx +++ b/frontend/src/component/strategies/StrategyView/StrategyView.tsx @@ -1,5 +1,5 @@ import { Grid } from '@mui/material'; -import { useParams, useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions'; import PageContent from 'component/common/PageContent/PageContent'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; @@ -10,13 +10,14 @@ import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { Edit } from '@mui/icons-material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; export const StrategyView = () => { - const { name } = useParams<{ name: string }>(); + const name = useRequiredPathParam('name'); const { strategies } = useStrategies(); const { features = [] } = useFeatures(); const { applications } = useApplications(); - const history = useHistory(); + const navigate = useNavigate(); const toggles = features.filter(toggle => { return toggle?.strategies?.find(strategy => strategy.name === name); @@ -25,7 +26,7 @@ export const StrategyView = () => { const strategy = strategies.find(strategy => strategy.name === name); const handleEdit = () => { - history.push(`/strategies/${name}/edit`); + navigate(`/strategies/${name}/edit`); }; if (!strategy) return null; diff --git a/frontend/src/component/tags/CreateTagType/CreateTagType.tsx b/frontend/src/component/tags/CreateTagType/CreateTagType.tsx index 0cbaee7ef8..67f4a1b94c 100644 --- a/frontend/src/component/tags/CreateTagType/CreateTagType.tsx +++ b/frontend/src/component/tags/CreateTagType/CreateTagType.tsx @@ -1,4 +1,4 @@ -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import useTagTypeForm from '../TagTypeForm/useTagTypeForm'; import TagTypeForm from '../TagTypeForm/TagTypeForm'; import { CreateButton } from 'component/common/CreateButton/CreateButton'; @@ -12,7 +12,7 @@ import { formatUnknownError } from 'utils/formatUnknownError'; const CreateTagType = () => { const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const history = useHistory(); + const navigate = useNavigate(); const { tagName, tagDesc, @@ -33,7 +33,7 @@ const CreateTagType = () => { const payload = getTagPayload(); try { await createTag(payload); - history.push('/tag-types'); + navigate('/tag-types'); setToastData({ title: 'Tag type created', confetti: true, @@ -55,7 +55,7 @@ const CreateTagType = () => { }; const handleCancel = () => { - history.goBack(); + navigate(-1); }; return ( diff --git a/frontend/src/component/tags/EditTagType/EditTagType.tsx b/frontend/src/component/tags/EditTagType/EditTagType.tsx index 9268731e38..a39cd41f51 100644 --- a/frontend/src/component/tags/EditTagType/EditTagType.tsx +++ b/frontend/src/component/tags/EditTagType/EditTagType.tsx @@ -1,4 +1,4 @@ -import { useHistory, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { UPDATE_TAG_TYPE } from 'component/providers/AccessProvider/permissions'; import useTagTypeForm from '../TagTypeForm/useTagTypeForm'; import TagForm from '../TagTypeForm/TagTypeForm'; @@ -9,11 +9,13 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; + const EditTagType = () => { const { setToastData, setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const history = useHistory(); - const { name } = useParams<{ name: string }>(); + const navigate = useNavigate(); + const name = useRequiredPathParam('name'); const { tagType } = useTagType(name); const { tagName, @@ -32,7 +34,7 @@ const EditTagType = () => { const payload = getTagPayload(); try { await updateTagType(tagName, payload); - history.push('/tag-types'); + navigate('/tag-types'); setToastData({ title: 'Tag type updated', type: 'success', @@ -52,7 +54,7 @@ const EditTagType = () => { }; const handleCancel = () => { - history.goBack(); + navigate(-1); }; return ( diff --git a/frontend/src/component/tags/TagTypeList/TagTypeList.tsx b/frontend/src/component/tags/TagTypeList/TagTypeList.tsx index 26e6d8f4b7..6db340922d 100644 --- a/frontend/src/component/tags/TagTypeList/TagTypeList.tsx +++ b/frontend/src/component/tags/TagTypeList/TagTypeList.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Button, IconButton, @@ -34,7 +34,7 @@ export const TagTypeList = () => { open: boolean; name?: string; }>({ open: false }); - const history = useHistory(); + const navigate = useNavigate(); const smallScreen = useMediaQuery('(max-width:700px)'); const { deleteTagType } = useTagTypesApi(); const { tagTypes, refetch } = useTagTypes(); @@ -70,7 +70,7 @@ export const TagTypeList = () => { - history.push('/tag-types/create') + navigate('/tag-types/create') } size="large" > @@ -83,7 +83,7 @@ export const TagTypeList = () => { variant="contained" color="primary" onClick={() => - history.push('/tag-types/create') + navigate('/tag-types/create') } > New tag type diff --git a/frontend/src/component/user/DemoAuth/DemoAuth.tsx b/frontend/src/component/user/DemoAuth/DemoAuth.tsx index bc623212eb..f619f0cba2 100644 --- a/frontend/src/component/user/DemoAuth/DemoAuth.tsx +++ b/frontend/src/component/user/DemoAuth/DemoAuth.tsx @@ -3,7 +3,7 @@ import { Button, TextField } from '@mui/material'; import styles from './DemoAuth.module.scss'; import { ReactComponent as Logo } from 'assets/img/logo.svg'; import { LOGIN_BUTTON, LOGIN_EMAIL_ID } from 'utils/testIds'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useAuthApi } from 'hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import useToast from 'hooks/useToast'; @@ -17,7 +17,7 @@ interface IDemoAuthProps { const DemoAuth: VFC = ({ authDetails, redirect }) => { const [email, setEmail] = useState(''); - const history = useHistory(); + const navigate = useNavigate(); const { refetchUser } = useAuthUser(); const { emailAuth } = useAuthApi(); const { setToastApiError } = useToast(); @@ -28,7 +28,7 @@ const DemoAuth: VFC = ({ authDetails, redirect }) => { try { await emailAuth(authDetails.path, email); refetchUser(); - history.push(redirect); + navigate(redirect); } catch (error) { setToastApiError(formatUnknownError(error)); } diff --git a/frontend/src/component/user/HostedAuth/HostedAuth.tsx b/frontend/src/component/user/HostedAuth/HostedAuth.tsx index d95e9d74a7..5cbde3d723 100644 --- a/frontend/src/component/user/HostedAuth/HostedAuth.tsx +++ b/frontend/src/component/user/HostedAuth/HostedAuth.tsx @@ -1,7 +1,7 @@ import { FormEventHandler, useState, VFC } from 'react'; import classnames from 'classnames'; import { Button, Grid, TextField, Typography } from '@mui/material'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router'; import { useThemeStyles } from 'themes/themeStyles'; import { useStyles } from './HostedAuth.styles'; import useQueryParams from 'hooks/useQueryParams'; @@ -24,7 +24,7 @@ const HostedAuth: VFC = ({ authDetails, redirect }) => { const { classes: themeStyles } = useThemeStyles(); const { classes: styles } = useStyles(); const { refetchUser } = useAuthUser(); - const history = useHistory(); + const navigate = useNavigate(); const params = useQueryParams(); const { passwordAuth } = useAuthApi(); const [username, setUsername] = useState(params.get('email') || ''); @@ -58,7 +58,7 @@ const HostedAuth: VFC = ({ authDetails, redirect }) => { try { await passwordAuth(authDetails.path, username, password); refetchUser(); - history.push(redirect); + navigate(redirect); } catch (error: any) { if ( error instanceof NotFoundError || diff --git a/frontend/src/component/user/Login/Login.tsx b/frontend/src/component/user/Login/Login.tsx index 08ab2e6904..06db1c02c7 100644 --- a/frontend/src/component/user/Login/Login.tsx +++ b/frontend/src/component/user/Login/Login.tsx @@ -7,7 +7,7 @@ import { DEMO_TYPE } from 'constants/authTypes'; import Authentication from '../Authentication/Authentication'; import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; -import { Redirect } from 'react-router-dom'; +import { Navigate } from 'react-router-dom'; import { parseRedirectParam } from 'component/user/Login/parseRedirectParam'; const Login = () => { @@ -19,7 +19,7 @@ const Login = () => { const redirect = query.get('redirect') || '/'; if (user) { - return ; + return ; } return ( diff --git a/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx b/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx index c2de7303d4..51c54029d6 100644 --- a/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx +++ b/frontend/src/component/user/PasswordAuth/PasswordAuth.tsx @@ -2,7 +2,7 @@ import { FormEventHandler, useState, VFC } from 'react'; import classnames from 'classnames'; import { Button, TextField } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router'; import { useThemeStyles } from 'themes/themeStyles'; import { useStyles } from './PasswordAuth.styles'; import useQueryParams from 'hooks/useQueryParams'; @@ -28,7 +28,7 @@ interface IPasswordAuthProps { const PasswordAuth: VFC = ({ authDetails, redirect }) => { const { classes: themeStyles } = useThemeStyles(); const { classes: styles } = useStyles(); - const history = useHistory(); + const navigate = useNavigate(); const { refetchUser } = useAuthUser(); const params = useQueryParams(); const [username, setUsername] = useState(params.get('email') || ''); @@ -63,7 +63,7 @@ const PasswordAuth: VFC = ({ authDetails, redirect }) => { try { await passwordAuth(authDetails.path, username, password); refetchUser(); - history.push(redirect); + navigate(redirect); } catch (error: any) { if ( error instanceof NotFoundError || diff --git a/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx b/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx index 4cf17c97ed..3cb7078790 100644 --- a/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx +++ b/frontend/src/component/user/SimpleAuth/SimpleAuth.tsx @@ -1,7 +1,7 @@ import { ChangeEventHandler, FormEventHandler, useState, VFC } from 'react'; import { Button, TextField } from '@mui/material'; import styles from './SimpleAuth.module.scss'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useAuthApi } from 'hooks/api/actions/useAuthApi/useAuthApi'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import { LOGIN_BUTTON, LOGIN_EMAIL_ID } from 'utils/testIds'; @@ -18,7 +18,7 @@ const SimpleAuth: VFC = ({ authDetails, redirect }) => { const [email, setEmail] = useState(''); const { refetchUser } = useAuthUser(); const { emailAuth } = useAuthApi(); - const history = useHistory(); + const navigate = useNavigate(); const { setToastApiError } = useToast(); const handleSubmit: FormEventHandler = async evt => { @@ -27,7 +27,7 @@ const SimpleAuth: VFC = ({ authDetails, redirect }) => { try { await emailAuth(authDetails.path, email); refetchUser(); - history.push(redirect); + navigate(redirect); } catch (error) { setToastApiError(formatUnknownError(error)); } diff --git a/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx b/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx index a6e51b7e90..8f6f6024e9 100644 --- a/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx +++ b/frontend/src/component/user/common/ResetPasswordForm/ResetPasswordForm.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState, } from 'react'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router'; import { useThemeStyles } from 'themes/themeStyles'; import { OK } from 'constants/statusCodes'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; @@ -33,7 +33,7 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => { const [confirmPassword, setConfirmPassword] = useState(''); const [matchingPasswords, setMatchingPasswords] = useState(false); const [validOwaspPassword, setValidOwaspPassword] = useState(false); - const history = useHistory(); + const navigate = useNavigate(); const submittable = matchingPasswords && validOwaspPassword; @@ -74,7 +74,7 @@ const ResetPasswordForm = ({ token, setLoading }: IResetPasswordProps) => { const res = await makeResetPasswordReq(); setLoading(false); if (res.status === OK) { - history.push('login?reset=true'); + navigate('login?reset=true'); setApiError(false); } else { setApiError(true); diff --git a/frontend/src/hooks/useQueryStringState.ts b/frontend/src/hooks/useQueryStringState.ts index 8e59142ab0..5aa5de0a6b 100644 --- a/frontend/src/hooks/useQueryStringState.ts +++ b/frontend/src/hooks/useQueryStringState.ts @@ -1,12 +1,12 @@ import { useCallback, useMemo } from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; // Store a value in the query string. Call setState to update the query string. export const useQueryStringState = ( key: string ): [string | undefined, (value: string) => void] => { const { search } = window.location; - const { replace } = useHistory(); + const navigate = useNavigate(); const params = useMemo(() => { return new URLSearchParams(search); @@ -16,9 +16,9 @@ export const useQueryStringState = ( (value: string) => { const next = new URLSearchParams(search); next.set(key, value); - replace({ search: next.toString() }); + navigate({ search: next.toString() }, { replace: true }); }, - [key, search, replace] + [key, search, navigate] ); return [params.get(key) || undefined, setState]; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 8ff21b4200..4d0bcc0cd2 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -2,7 +2,7 @@ import 'whatwg-fetch'; import 'themes/app.css'; import ReactDOM from 'react-dom'; -import { Route, BrowserRouter as Router } from 'react-router-dom'; +import { BrowserRouter } from 'react-router-dom'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { ThemeProvider } from 'themes/ThemeProvider'; @@ -18,16 +18,16 @@ ReactDOM.render( - + - + - + , diff --git a/frontend/src/interfaces/params.ts b/frontend/src/interfaces/params.ts deleted file mode 100644 index 5cac9864d7..0000000000 --- a/frontend/src/interfaces/params.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface IFeatureViewParams { - projectId: string; - featureId: string; - activeTab: string; -} - -export interface IProjectViewParams { - id: string; -} diff --git a/frontend/src/interfaces/route.ts b/frontend/src/interfaces/route.ts index 55377e6742..495237f463 100644 --- a/frontend/src/interfaces/route.ts +++ b/frontend/src/interfaces/route.ts @@ -3,7 +3,7 @@ import { VoidFunctionComponent } from 'react'; export interface IRoute { path: string; title: string; - type: string; + type: 'protected' | 'unprotected'; layout?: string; parent?: string; flag?: string; diff --git a/frontend/src/utils/testRenderer.tsx b/frontend/src/utils/testRenderer.tsx index 9383ff4803..aaed2d4987 100644 --- a/frontend/src/utils/testRenderer.tsx +++ b/frontend/src/utils/testRenderer.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import { BrowserRouter as Router } from 'react-router-dom'; +import { BrowserRouter } from 'react-router-dom'; import { render as rtlRender, RenderOptions } from '@testing-library/react'; import { SWRConfig } from 'swr'; import { ThemeProvider } from 'themes/ThemeProvider'; @@ -25,7 +25,7 @@ export const render = ( - {children} + {children} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fbffe079d7..ada6ef8f77 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1018,7 +1018,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== @@ -2222,11 +2222,6 @@ dependencies: "@types/node" "*" -"@types/history@^4.7.11": - version "4.7.11" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" - integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== - "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" @@ -2372,23 +2367,6 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@5.3.3": - version "5.3.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" - integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "5.1.18" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" - integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== - dependencies: - "@types/history" "^4.7.11" - "@types/react" "*" - "@types/react-test-renderer@17.0.2": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf" @@ -5625,19 +5603,14 @@ headers-polyfill@^3.0.4: resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.0.7.tgz#725c4f591e6748f46b036197eae102c92b959ff4" integrity sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w== -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== +history@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" + integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" + "@babel/runtime" "^7.7.6" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6170,11 +6143,6 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7056,7 +7024,7 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7180,14 +7148,6 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-create-react-context@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" - integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== - dependencies: - "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" - mini-css-extract-plugin@^2.4.5: version "2.6.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" @@ -7697,13 +7657,6 @@ path-to-regexp@3.2.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" @@ -8567,7 +8520,7 @@ react-hooks-global-state@1.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg== -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -8582,34 +8535,20 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-router-dom@5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.1.tgz#0151baf2365c5fcd8493f6ec9b9b31f34d0f8ae1" - integrity sha512-f0pj/gMAbv9e8gahTmCEY20oFhxhrmHwYeIwH5EO5xu0qme+wXtsdB8YfUOAZzUz4VaXmb58m3ceiLtjMhqYmQ== +react-router-dom@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" + integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.3.1" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + history "^5.2.0" + react-router "6.3.0" -react-router@5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.1.tgz#b13e84a016c79b9e80dde123ca4112c4f117e3cf" - integrity sha512-v+zwjqb7bakqgF+wMVKlAPTca/cEmPOvQ9zt7gpSNyPXau1+0qvuYZ5BWzzNDP1y6s15zDwgb9rPN63+SIniRQ== +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.4.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + history "^5.2.0" react-scripts@5.0.1: version "5.0.1" @@ -8885,11 +8824,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - resolve-url-loader@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz#d50d4ddc746bb10468443167acf800dcd6c3ad57" @@ -9755,16 +9689,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -tiny-invariant@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" - integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== - -tiny-warning@^1.0.0, tiny-warning@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10090,11 +10014,6 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"