1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

chore: update react-router to v6 (#946)

* refactor: fix child selector warnings

* refactor: update react-router-dom

* refactor: use BrowserRouter as in react-router docs

* refactor: replace Redirect with Navigate

* refactor: replace Switch with Routes

* refactor: replace useHistory with useNavigate

* refactor: replace useParams types with useRequiredPathParam

* refactor: replace NavLink activeStyle with callback

* refactor: fix matchPath arg order

* refactor: Remove unused link state

* refactor: delete broken snapshot test

* refactor: render 404 page without redirect

* refactor: normalize path parameter names

* refactor: fix Route component usage
This commit is contained in:
olav 2022-05-05 13:42:18 +02:00 committed by GitHub
parent 4c42639a62
commit d8143c6ff4
112 changed files with 558 additions and 950 deletions

View File

@ -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",

View File

@ -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 (
<ProtectedRoute
key={route.path}
path={route.path}
component={route.component}
unauthorized={unauthorized}
/>
);
}
return (
<Route
key={route.path}
path={route.path}
render={() => createElement(route.component, {}, null)}
/>
);
};
return (
<SWRProvider isUnauthorized={isUnauthorized}>
<SWRProvider isUnauthorized={!isLoggedIn}>
<ConditionallyRender
condition={!hasFetchedAuth}
show={<Loader />}
@ -55,19 +28,24 @@ export const App = () => {
<div className={styles.container}>
<ToastRenderer />
<LayoutPicker>
<Switch>
<ProtectedRoute
exact
<Routes>
{routes.map(route => (
<Route
key={route.path}
path={route.path}
element={
<ProtectedRoute route={route} />
}
/>
))}
<Route
path="/"
unauthorized={isUnauthorized()}
component={() => (
<Redirect to="/features" />
)}
element={
<Navigate to="/features" replace />
}
/>
{routes.map(renderRoute)}
<Route path="/404" component={NotFound} />
<Redirect to="/404" />
</Switch>
<Route path="*" element={<NotFound />} />
</Routes>
<FeedbackNPS openUrl="http://feedback.unleash.run" />
<SplashPageRedirect />
</LayoutPicker>

View File

@ -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<IAddonFormProps> = ({
}) => {
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<IAddonFormProps> = ({
};
const onCancel = () => {
history.goBack();
navigate(-1);
};
const onSubmit: FormEventHandler<HTMLFormElement> = async event => {
@ -152,14 +152,14 @@ export const AddonForm: VFC<IAddonFormProps> = ({
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,

View File

@ -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) => (
<ListItem key={provider.name}>
@ -41,9 +41,7 @@ export const AvailableAddons = ({
<ListItemSecondaryAction>
<PermissionButton
permission={CREATE_ADDON}
onClick={() =>
history.push(`/addons/create/${provider.name}`)
}
onClick={() => navigate(`/addons/create/${provider.name}`)}
>
Configure
</PermissionButton>

View File

@ -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<IAddon>({
id: 0,
@ -124,7 +124,7 @@ export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
<PermissionIconButton
permission={UPDATE_ADDON}
tooltip="Edit Addon"
onClick={() => history.push(`/addons/edit/${addon.id}`)}
onClick={() => navigate(`/addons/edit/${addon.id}`)}
>
<Edit />
</PermissionIconButton>

View File

@ -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<IAddonCreateParams>();
const providerId = useRequiredPathParam('providerId');
const { providers, refetchAddons } = useAddons();
const editMode = false;

View File

@ -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<IAddonEditParams>();
const addonId = useRequiredPathParam('addonId');
const { providers, addons, refetchAddons } = useAddons();
const editMode = true;

View File

@ -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 (
<PageContent
@ -35,7 +35,7 @@ export const ApiTokenPage = () => {
variant="contained"
color="primary"
onClick={() =>
history.push('/admin/api/create-token')
navigate('/admin/api/create-token')
}
data-testid={CREATE_API_TOKEN_BUTTON}
>

View File

@ -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 (

View File

@ -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 = () => <Redirect to="/admin/users" />;
const render = () => <Navigate to="/admin/users" replace />;
render.propTypes = {
match: PropTypes.object.isRequired,

View File

@ -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() {
<Tab
value="/admin/users"
label={
<NavLink
to="/admin/users"
activeStyle={activeNavLinkStyle}
style={navLinkStyle}
>
<NavLink to="/admin/users" style={createNavLinkStyle}>
<span>Users</span>
</NavLink>
}
@ -51,8 +55,7 @@ function AdminMenu() {
label={
<NavLink
to="/admin/roles"
activeStyle={activeNavLinkStyle}
style={navLinkStyle}
style={createNavLinkStyle}
>
<span>PROJECT ROLES</span>
</NavLink>
@ -63,11 +66,7 @@ function AdminMenu() {
<Tab
value="/admin/api"
label={
<NavLink
to="/admin/api"
activeStyle={activeNavLinkStyle}
style={navLinkStyle}
>
<NavLink to="/admin/api" style={createNavLinkStyle}>
API Access
</NavLink>
}
@ -75,11 +74,7 @@ function AdminMenu() {
<Tab
value="/admin/auth"
label={
<NavLink
to="/admin/auth"
activeStyle={activeNavLinkStyle}
style={navLinkStyle}
>
<NavLink to="/admin/auth" style={createNavLinkStyle}>
Single Sign-On
</NavLink>
}

View File

@ -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 (

View File

@ -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 (

View File

@ -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"

View File

@ -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 (
<div>
@ -32,7 +32,7 @@ const ProjectRoles = () => {
variant="contained"
color="primary"
onClick={() =>
history.push(
navigate(
'/admin/create-project-role'
)
}

View File

@ -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 (

View File

@ -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 (

View File

@ -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

View File

@ -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',
},

View File

@ -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 = ({
<IconButton
data-loading
onClick={() =>
history.push(`/admin/users/${user.id}/edit`)
navigate(`/admin/users/${user.id}/edit`)
}
size="large"
>

View File

@ -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(
<AccessProviderMock permissions={[{ permission: ADMIN }]}>
<ThemeProvider>
<UIProvider>
<MemoryRouter initialEntries={['/test']}>
<ApplicationEdit
fetchApplication={() => Promise.resolve({})}
storeApplicationMetaData={jest.fn()}
deleteApplication={jest.fn()}
locationSettings={{ locale: 'en-GB' }}
/>
</MemoryRouter>
</UIProvider>
</ThemeProvider>
</AccessProviderMock>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
test('renders correctly without permission', () => {
const tree = renderer
.create(
<MemoryRouter>
<ThemeProvider>
<UIProvider>
<AccessProviderMock
permissions={[{ permission: ADMIN }]}
>
<ApplicationEdit
fetchApplication={() => 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' }}
/>
</AccessProviderMock>
</UIProvider>
</ThemeProvider>
</MemoryRouter>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
test('renders correctly with permissions', () => {
const tree = renderer
.create(
<MemoryRouter>
<ThemeProvider>
<UIProvider>
<AccessProviderMock
permissions={[{ permission: ADMIN }]}
>
<ApplicationEdit
fetchApplication={() => 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' }}
/>
</AccessProviderMock>
</UIProvider>
</ThemeProvider>
</MemoryRouter>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@ -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));
}

View File

@ -1,64 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly if no application 1`] = `
<div>
<p>
Loading...
</p>
<span
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate mui-130qhmc-MuiLinearProgress-root"
role="progressbar"
>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate mui-yt7v2r-MuiLinearProgress-bar1"
style={Object {}}
/>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar2Indeterminate mui-1wjhhwq-MuiLinearProgress-bar2"
style={Object {}}
/>
</span>
</div>
`;
exports[`renders correctly with permissions 1`] = `
<div>
<p>
Loading...
</p>
<span
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate mui-130qhmc-MuiLinearProgress-root"
role="progressbar"
>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate mui-yt7v2r-MuiLinearProgress-bar1"
style={Object {}}
/>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar2Indeterminate mui-1wjhhwq-MuiLinearProgress-bar2"
style={Object {}}
/>
</span>
</div>
`;
exports[`renders correctly without permission 1`] = `
<div>
<p>
Loading...
</p>
<span
className="MuiLinearProgress-root MuiLinearProgress-colorPrimary MuiLinearProgress-indeterminate mui-130qhmc-MuiLinearProgress-root"
role="progressbar"
>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate mui-yt7v2r-MuiLinearProgress-bar1"
style={Object {}}
/>
<span
className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar2Indeterminate mui-1wjhhwq-MuiLinearProgress-bar2"
style={Object {}}
/>
</span>
</div>
`;

View File

@ -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,

View File

@ -1,7 +1,7 @@
import { Redirect } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
const RedirectArchive = () => {
return <Redirect to="/archive" />;
return <Navigate to="/archive" replace />;
};
export default RedirectArchive;

View File

@ -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 (

View File

@ -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 <Navigate to={loginLink} replace />;
};

View File

@ -8,6 +8,7 @@ export const useStyles = makeStyles()({
minHeight: '100vh',
padding: '2rem',
position: 'fixed',
inset: 0,
backgroundColor: '#fff',
width: '100%',
},

View File

@ -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 (

View File

@ -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<IProtectedRouteProps & RouteProps> = ({
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 ? (
<Route {...rest} render={() => <Redirect to={loginLink} />} />
) : (
<Route {...rest} component={Component} />
);
if (!isLoggedIn && route.type === 'protected') {
return <LoginRedirect />;
}
return <route.component />;
};
export default ProtectedRoute;

View File

@ -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 = () => {
<Tooltip title="Edit context field">
<IconButton
onClick={() =>
history.push(`/context/edit/${field.name}`)
navigate(`/context/edit/${field.name}`)
}
size="large"
>
@ -120,7 +120,7 @@ const ContextList: VFC = () => {
show={
<Tooltip title="Add context type">
<IconButton
onClick={() => history.push('/context/create')}
onClick={() => navigate('/context/create')}
size="large"
>
<Add />
@ -129,7 +129,7 @@ const ContextList: VFC = () => {
}
elseShow={
<Button
onClick={() => history.push('/context/create')}
onClick={() => navigate('/context/create')}
color="primary"
variant="contained"
>

View File

@ -1,12 +1,13 @@
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { CreateUnleashContext } from 'component/context/CreateUnleashContext/CreateUnleashContext';
export const CreateUnleashContextPage = () => {
const { push, goBack } = useHistory();
const navigate = useNavigate();
return (
<CreateUnleashContext
onSubmit={() => push('/context')}
onCancel={() => goBack()}
onSubmit={() => navigate('/context')}
onCancel={() => navigate(-1)}
/>
);
};

View File

@ -6,11 +6,12 @@ import useContext from 'hooks/api/getters/useContext/useContext';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { useEffect } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { scrollToTop } from 'component/common/util';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ContextForm } from '../ContextForm/ContextForm';
import { useContextForm } from '../hooks/useContextForm';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const EditContext = () => {
useEffect(() => {
@ -19,10 +20,10 @@ export const EditContext = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { name } = useParams<{ name: string }>();
const name = useRequiredPathParam('name');
const { context, refetch } = useContext(name);
const { updateContext, loading } = useContextsApi();
const history = useHistory();
const navigate = useNavigate();
const {
contextName,
contextDesc,
@ -59,7 +60,7 @@ export const EditContext = () => {
try {
await updateContext(payload);
refetch();
history.push('/context');
navigate('/context');
setToastData({
title: 'Context information updated',
type: 'success',
@ -70,7 +71,7 @@ export const EditContext = () => {
};
const onCancel = () => {
history.goBack();
navigate(-1);
};
return (

View File

@ -1,4 +1,4 @@
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import useEnvironmentForm from '../hooks/useEnvironmentForm';
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
@ -19,7 +19,7 @@ import { formatUnknownError } from 'utils/formatUnknownError';
const CreateEnvironment = () => {
const { setToastApiError, setToastData } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const navigate = useNavigate();
const { environments } = useEnvironments();
const canCreateMoreEnvs = environments.length < 7;
const { createEnvironment, loading } = useEnvironmentApi();
@ -49,7 +49,7 @@ const CreateEnvironment = () => {
type: 'success',
confetti: true,
});
history.push('/environments');
navigate('/environments');
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
@ -66,7 +66,7 @@ const CreateEnvironment = () => {
};
const handleCancel = () => {
history.goBack();
navigate(-1);
};
return (

View File

@ -5,21 +5,21 @@ import useEnvironment from 'hooks/api/getters/useEnvironment/useEnvironment';
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { useHistory, useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { ADMIN } from 'component/providers/AccessProvider/permissions';
import EnvironmentForm from '../EnvironmentForm/EnvironmentForm';
import useEnvironmentForm from '../hooks/useEnvironmentForm';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const EditEnvironment = () => {
const { uiConfig } = useUiConfig();
const { setToastData, setToastApiError } = useToast();
const { id } = useParams<{ id: string }>();
const id = useRequiredPathParam('id');
const { environment } = useEnvironment(id);
const { updateEnvironment } = useEnvironmentApi();
const history = useHistory();
const navigate = useNavigate();
const { name, type, setName, setType, errors, clearErrors } =
useEnvironmentForm(environment.name, environment.type);
const { refetch } = useProjectRolePermissions();
@ -45,7 +45,7 @@ const EditEnvironment = () => {
try {
await updateEnvironment(id, editPayload());
refetch();
history.push('/environments');
navigate('/environments');
setToastData({
type: 'success',
title: 'Successfully updated environment.',
@ -56,7 +56,7 @@ const EditEnvironment = () => {
};
const handleCancel = () => {
history.goBack();
navigate(-1);
};
return (

View File

@ -1,5 +1,5 @@
import { useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { List } from '@mui/material';
import { Add } from '@mui/icons-material';
import useToast from 'hooks/useToast';
@ -36,7 +36,7 @@ const EnvironmentList = () => {
const [toggleDialog, setToggleDialog] = useState(false);
const [confirmName, setConfirmName] = useState('');
const history = useHistory();
const navigate = useNavigate();
const { setToastApiError, setToastData } = useToast();
const {
deleteEnvironment,
@ -155,7 +155,7 @@ const EnvironmentList = () => {
));
const navigateToCreateEnvironment = () => {
history.push('/environments/create');
navigate('/environments/create');
};
return (
<PageContent

View File

@ -25,7 +25,7 @@ import { useDrag, useDrop, DropTargetMonitor } from 'react-dnd';
import { XYCoord, Identifier } from 'dnd-core';
import DisabledIndicator from 'component/common/DisabledIndicator/DisabledIndicator';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
interface IEnvironmentListItemProps {
env: IEnvironment;
@ -56,7 +56,7 @@ const EnvironmentListItem = ({
moveListItemApi,
setToggleDialog,
}: IEnvironmentListItemProps) => {
const history = useHistory();
const navigate = useNavigate();
const ref = useRef<HTMLLIElement>(null);
const ACCEPT_TYPE = 'LIST_ITEM';
const [{ isDragging }, drag] = useDrag({
@ -182,7 +182,7 @@ const EnvironmentListItem = ({
<IconButton
disabled={env.protected}
onClick={() => {
history.push(`/environments/${env.name}`);
navigate(`/environments/${env.name}`);
}}
size="large"
>

View File

@ -5,13 +5,14 @@ import {
FormEventHandler,
ChangeEventHandler,
} from 'react';
import { Link, useHistory, useParams } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import {
Button,
TextField,
Switch,
Paper,
FormControlLabel,
Alert,
} from '@mui/material';
import { FileCopy } from '@mui/icons-material';
import { styles as themeStyles } from 'component/common';
@ -19,10 +20,10 @@ import { formatUnknownError } from 'utils/formatUnknownError';
import styles from './CopyFeature.module.scss';
import { trim } from 'component/common/util';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Alert } from '@mui/material';
import { getTogglePath } from 'utils/routePathHelpers';
import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const CopyFeatureToggle = () => {
const [replaceGroupId, setReplaceGroupId] = useState(true);
@ -31,12 +32,10 @@ export const CopyFeatureToggle = () => {
const [newToggleName, setNewToggleName] = useState<string>();
const { cloneFeatureToggle, validateFeatureToggleName } = useFeatureApi();
const inputRef = useRef<HTMLInputElement>();
const { name: copyToggleName, id: projectId } = useParams<{
name: string;
id: string;
}>();
const { feature } = useFeature(projectId, copyToggleName);
const history = useHistory();
const featureId = useRequiredPathParam('featureId');
const projectId = useRequiredPathParam('projectId');
const { feature } = useFeature(projectId, featureId);
const navigate = useNavigate();
useEffect(() => {
inputRef.current?.focus();
@ -69,11 +68,11 @@ export const CopyFeatureToggle = () => {
}
try {
await cloneFeatureToggle(projectId, copyToggleName, {
await cloneFeatureToggle(projectId, featureId, {
name: newToggleName as string,
replaceGroupId,
});
history.push(getTogglePath(projectId, newToggleName as string));
navigate(getTogglePath(projectId, newToggleName as string));
} catch (error) {
setApiError(formatUnknownError(error));
}
@ -87,7 +86,7 @@ export const CopyFeatureToggle = () => {
style={{ overflow: 'visible' }}
>
<div className={styles.header}>
<h1>Copy&nbsp;{copyToggleName}</h1>
<h1>Copy&nbsp;{featureId}</h1>
</div>
<ConditionallyRender
condition={Boolean(apiError)}
@ -97,8 +96,8 @@ export const CopyFeatureToggle = () => {
<p className={styles.text}>
You are about to create a new feature toggle by cloning the
configuration of feature toggle&nbsp;
<Link to={getTogglePath(projectId, copyToggleName)}>
{copyToggleName}
<Link to={getTogglePath(projectId, featureId)}>
{featureId}
</Link>
. You must give the new feature toggle a unique name before
you can proceed.

View File

@ -1,5 +1,5 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import FeatureForm from '../FeatureForm/FeatureForm';
import useFeatureForm from '../hooks/useFeatureForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@ -16,7 +16,7 @@ const CreateFeature = () => {
const { setToastData, setToastApiError } = useToast();
const { setShowFeedback } = useContext(UIContext);
const { uiConfig } = useUiConfig();
const history = useHistory();
const navigate = useNavigate();
const {
type,
@ -46,7 +46,7 @@ const CreateFeature = () => {
const payload = getTogglePayload();
try {
await createFeatureToggle(project, payload);
history.push(`/projects/${project}/features/${name}`);
navigate(`/projects/${project}/features/${name}`);
setToastData({
title: 'Toggle created successfully',
text: 'Now you can start using your toggle.',
@ -70,7 +70,7 @@ const CreateFeature = () => {
};
const handleCancel = () => {
history.goBack();
navigate(-1);
};
return (

View File

@ -1,5 +1,5 @@
import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import { useHistory, useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import FeatureForm from '../FeatureForm/FeatureForm';
import useFeatureForm from '../hooks/useFeatureForm';
import * as jsonpatch from 'fast-json-patch';
@ -9,14 +9,15 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useToast from 'hooks/useToast';
import { IFeatureViewParams } from 'interfaces/params';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const EditFeature = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const history = useHistory();
const { projectId, featureId } = useParams<IFeatureViewParams>();
const navigate = useNavigate();
const { patchFeatureToggle, loading } = useFeatureApi();
const { feature } = useFeature(projectId, featureId);
@ -53,7 +54,7 @@ const EditFeature = () => {
const patch = createPatch();
try {
await patchFeatureToggle(project, featureId, patch);
history.push(`/projects/${project}/features/${name}`);
navigate(`/projects/${project}/features/${name}`);
setToastData({
title: 'Toggle updated successfully',
type: 'success',
@ -73,7 +74,7 @@ const EditFeature = () => {
};
const handleCancel = () => {
history.goBack();
navigate(-1);
};
return (

View File

@ -16,7 +16,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { trim } from 'component/common/util';
import Input from 'component/common/Input/Input';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import React from 'react';
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
@ -60,7 +60,7 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
}) => {
const { classes: styles } = useStyles();
const { featureTypes } = useFeatureTypes();
const history = useHistory();
const navigate = useNavigate();
const { permissions } = useAuthPermissions();
const editable = mode !== 'Edit';
@ -116,7 +116,9 @@ const FeatureForm: React.FC<IFeatureToggleForm> = ({
value={project}
onChange={projectId => {
setProject(projectId);
history.replace(`/projects/${projectId}/create-toggle`);
navigate(`/projects/${projectId}/create-toggle`, {
replace: true,
});
}}
enabled={editable}
filter={projectFilterGenerator(permissions, CREATE_FEATURE)}

View File

@ -6,7 +6,7 @@ import FormTemplate from 'component/common/FormTemplate/FormTemplate';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import useToast from 'hooks/useToast';
import { IFeatureStrategy } from 'interfaces/strategy';
import {
@ -38,7 +38,7 @@ export const FeatureStrategyCreate = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const { unleashUrl } = uiConfig;
const { push } = useHistory();
const navigate = useNavigate();
const { feature, refetchFeature } = useFeatureImmutable(
projectId,
@ -72,7 +72,7 @@ export const FeatureStrategyCreate = () => {
confetti: true,
});
refetchFeature();
push(formatFeaturePath(projectId, featureId));
navigate(formatFeaturePath(projectId, featureId));
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}

View File

@ -6,7 +6,7 @@ import { useRequiredQueryParam } from 'hooks/useRequiredQueryParam';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import useToast from 'hooks/useToast';
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
@ -29,7 +29,7 @@ export const FeatureStrategyEdit = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
const { unleashUrl } = uiConfig;
const { push } = useHistory();
const navigate = useNavigate();
const { feature, refetchFeature } = useFeatureImmutable(
projectId,
@ -77,7 +77,7 @@ export const FeatureStrategyEdit = () => {
confetti: true,
});
refetchFeature();
push(formatFeaturePath(projectId, featureId));
navigate(formatFeaturePath(projectId, featureId));
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}

View File

@ -11,7 +11,7 @@ import {
import { IFeatureToggle } from 'interfaces/featureToggle';
import { useStyles } from './FeatureStrategyForm.styles';
import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { STRATEGY_FORM_SUBMIT_ID } from 'utils/testIds';
@ -51,7 +51,7 @@ export const FeatureStrategyForm = ({
const hasValidConstraints = useConstraintsValidation(strategy.constraints);
const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId);
const { hasAccess } = useContext(AccessContext);
const { push } = useHistory();
const navigate = useNavigate();
const {
uiConfig,
@ -60,7 +60,7 @@ export const FeatureStrategyForm = ({
} = useUiConfig();
const onCancel = () => {
push(formatFeaturePath(feature.project, feature.name));
navigate(formatFeaturePath(feature.project, feature.name));
};
const onSubmitOrProdGuard = async (event: React.FormEvent) => {

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import useToast from 'hooks/useToast';
import { formatFeaturePath } from '../FeatureStrategyEdit/FeatureStrategyEdit';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
@ -35,7 +35,7 @@ export const FeatureStrategyRemove = ({
const { deleteStrategyFromFeature } = useFeatureStrategyApi();
const { refetchFeature } = useFeature(projectId, featureId);
const { setToastData, setToastApiError } = useToast();
const { push } = useHistory();
const navigate = useNavigate();
const onRemove = async (event: React.FormEvent) => {
try {
@ -51,7 +51,7 @@ export const FeatureStrategyRemove = ({
type: 'success',
});
refetchFeature();
push(formatFeaturePath(projectId, featureId));
navigate(formatFeaturePath(projectId, featureId));
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}

View File

@ -1,12 +1,12 @@
import { useParams } from 'react-router';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { useStyles } from './FeatureLog.styles';
import { IFeatureViewParams } from 'interfaces/params';
import { FeatureEventHistory } from 'component/history/FeatureEventHistory/FeatureEventHistory';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
const FeatureLog = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { classes: styles } = useStyles();
const { projectId, featureId } = useParams<IFeatureViewParams>();
const { feature } = useFeature(projectId, featureId);
if (!feature.name) {

View File

@ -1,5 +1,3 @@
import { useParams } from 'react-router';
import { IFeatureViewParams } from 'interfaces/params';
import { useFeatureMetricsRaw } from 'hooks/api/getters/useFeatureMetricsRaw/useFeatureMetricsRaw';
import PageContent from 'component/common/PageContent';
import { useEffect, useMemo, useState } from 'react';
@ -17,9 +15,11 @@ import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useStyles } from './FeatureMetrics.styles';
import { usePageTitle } from 'hooks/usePageTitle';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const FeatureMetrics = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const environments = useFeatureMetricsEnvironments(projectId, featureId);
const applications = useFeatureMetricsApplications(featureId);
const { classes: styles } = useStyles();

View File

@ -1,13 +1,14 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { getCreateTogglePath } from 'utils/routePathHelpers';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useStyles } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound.styles';
import { IFeatureViewParams } from 'interfaces/params';
import { useFeaturesArchive } from 'hooks/api/getters/useFeaturesArchive/useFeaturesArchive';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const FeatureNotFound = () => {
const { projectId, featureId } = useParams<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { archivedFeatures } = useFeaturesArchive();
const { classes: styles } = useStyles();
const { uiConfig } = useUiConfig();

View File

@ -1,7 +1,5 @@
import { DialogContentText } from '@mui/material';
import { useParams } from 'react-router';
import React, { useState } from 'react';
import { IFeatureViewParams } from 'interfaces/params';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import Input from 'component/common/Input/Input';
import { useStyles } from './AddTagDialog.styles';
@ -11,6 +9,7 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi';
import useTags from 'hooks/api/getters/useTags/useTags';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
interface IAddTagDialogProps {
open: boolean;
@ -27,7 +26,7 @@ interface IDefaultTag {
const AddTagDialog = ({ open, setOpen }: IAddTagDialogProps) => {
const DEFAULT_TAG: IDefaultTag = { type: 'simple', value: '' };
const { classes: styles } = useStyles();
const { featureId } = useParams<IFeatureViewParams>();
const featureId = useRequiredPathParam('featureId');
const { addTagToFeature, loading } = useFeatureApi();
const { refetch } = useTags(featureId);
const [errors, setErrors] = useState({ tagError: '' });

View File

@ -2,7 +2,7 @@ import FeatureOverviewMetaData from './FeatureOverviewMetaData/FeatureOverviewMe
import { useStyles } from './FeatureOverview.styles';
import FeatureOverviewEnvironments from './FeatureOverviewEnvironments/FeatureOverviewEnvironments';
import FeatureOverviewEnvSwitches from './FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitches';
import { Switch, Route, useHistory } from 'react-router-dom';
import { Routes, Route, useNavigate } from 'react-router-dom';
import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import {
@ -14,11 +14,11 @@ import { usePageTitle } from 'hooks/usePageTitle';
const FeatureOverview = () => {
const { classes: styles } = useStyles();
const { push } = useHistory();
const navigate = useNavigate();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const featurePath = formatFeaturePath(projectId, featureId);
const onSidebarClose = () => push(featurePath);
const onSidebarClose = () => navigate(featurePath);
usePageTitle(featureId);
return (
@ -30,26 +30,32 @@ const FeatureOverview = () => {
<div className={styles.mainContent}>
<FeatureOverviewEnvironments />
</div>
<Switch>
<Route path="/projects/:projectId/features/:featureId/strategies/create">
<SidebarModal
label="Create feature strategy"
onClose={onSidebarClose}
open
>
<FeatureStrategyCreate />
</SidebarModal>
</Route>
<Route path="/projects/:projectId/features/:featureId/strategies/edit">
<SidebarModal
label="Edit feature strategy"
onClose={onSidebarClose}
open
>
<FeatureStrategyEdit />
</SidebarModal>
</Route>
</Switch>
<Routes>
<Route
path="strategies/create"
element={
<SidebarModal
label="Create feature strategy"
onClose={onSidebarClose}
open
>
<FeatureStrategyCreate />
</SidebarModal>
}
/>
<Route
path="strategies/edit"
element={
<SidebarModal
label="Edit feature strategy"
onClose={onSidebarClose}
open
>
<FeatureStrategyEdit />
</SidebarModal>
}
/>
</Routes>
</div>
);
};

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
useFeatureApi();
const { refetchFeature } = useFeature(projectId, featureId);

View File

@ -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<IFeatureViewParams>();
useFeatureApi();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
useFeatureApi();
const [showInfoBox, setShowInfoBox] = useState(false);
const [environmentName, setEnvironmentName] = useState('');

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { metrics } = useFeatureMetrics(projectId, featureId);
const { feature } = useFeature(projectId, featureId);

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { classes: styles } = useStyles();
if (!featureEnvironment) {

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const theme = useTheme();
const { classes: styles } = useStyles();
const Icon = getFeatureStrategyIcon(strategy.name);

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
if (!feature) return null;

View File

@ -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<IFeatureViewParams>();
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);

View File

@ -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<HTMLButtonElement> {
projectId: string;
@ -33,7 +32,7 @@ const FeatureOverviewTags: React.FC<IFeatureOverviewTagsProps> = ({
type: '',
});
const { classes: styles } = useStyles();
const { featureId } = useParams<IFeatureViewParams>();
const featureId = useRequiredPathParam('featureId');
const { tags, refetch } = useTags(featureId);
const { tagTypes } = useTagTypes();
const { deleteTagFromFeature } = useFeatureApi();

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { patchFeatureToggle } = useFeatureApi();
const { refetchFeature } = useFeature(projectId, featureId);

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const [settings, setSettings] = useState(METADATA);
return (

View File

@ -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 (

View File

@ -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<IFeatureViewParams>();
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);

View File

@ -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<IFeatureViewParams>();
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));
}

View File

@ -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<Record<string, string>>({});
const { classes: themeStyles } = useThemeStyles();
const { projectId, featureId } = useParams<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { feature } = useFeature(projectId, featureId);
const [variants, setVariants] = useState<IFeatureVariant[]>([]);
const { context } = useUnleashContext();

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
const { feature, refetchFeature } = useFeature(projectId, featureId);
const [variants, setVariants] = useState<IFeatureVariant[]>([]);
const [editing, setEditing] = useState(false);

View File

@ -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<IFeatureViewParams>();
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 = () => {
</Tabs>
</div>
</div>
<Switch>
<Route
path={`/projects/:projectId/features/:featureId/metrics`}
component={FeatureMetrics}
/>
<Route
path={`/projects/:projectId/features/:featureId/logs`}
component={FeatureLog}
/>
<Route
path={`/projects/:projectId/features/:featureId/variants`}
component={FeatureVariants}
/>
<Route
path={`/projects/:projectId/features/:featureId/settings`}
component={FeatureSettings}
/>
<Route
path={`/projects/:projectId/features/:featureId`}
component={FeatureOverview}
/>
</Switch>
<Routes>
<Route path="metrics" element={<FeatureMetrics />} />
<Route path="logs" element={<FeatureLog />} />
<Route path="variants" element={<FeatureVariants />} />
<Route path="settings" element={<FeatureSettings />} />
<Route path="*" element={<FeatureOverview />} />
</Routes>
<Dialogue
onClick={() => archiveToggle()}
open={showDelDialog}

View File

@ -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<IRedirectParams>();
const featureId = useRequiredPathParam('featureId');
const { features = [] } = useFeatures();
const [featureToggle, setFeatureToggle] = useState<FeatureSchema>();
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 (
<Redirect
<Navigate
to={getTogglePath(featureToggle?.project, featureToggle?.name)}
replace
/>
);
};

View File

@ -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<IFeatureViewParams>();
const projectId = useRequiredPathParam('projectId');
const params = useQueryParams();
const { validateFeatureToggleName } = useFeatureApi();
const toggleQueryName = params.get('name');

View File

@ -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,
},
},

View File

@ -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 <FeatureEventHistory toggleName={toggleName} />;
};

View File

@ -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',
];

View File

@ -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 {

View File

@ -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: {},

View File

@ -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 (

View File

@ -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 (

View File

@ -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: <ProjectOverview projectId={id} />,
component: <ProjectOverview projectId={projectId} />,
path: basePath,
name: 'overview',
},
{
title: 'Health',
component: <ProjectHealth projectId={id} />,
component: <ProjectHealth projectId={projectId} />,
path: `${basePath}/health`,
name: 'health',
},
@ -49,13 +52,13 @@ const Project = () => {
},
{
title: 'Environments',
component: <ProjectEnvironment projectId={id} />,
component: <ProjectEnvironment projectId={projectId} />,
path: `${basePath}/environments`,
name: 'environments',
},
{
title: 'Archive',
component: <ProjectFeaturesArchive projectId={id} />,
component: <ProjectFeaturesArchive projectId={projectId} />,
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 = () => {
<PermissionIconButton
permission={UPDATE_PROJECT}
projectId={project?.id}
onClick={() => history.push(`/projects/${id}/edit`)}
onClick={() =>
navigate(`/projects/${projectId}/edit`)
}
tooltip="Edit project"
data-loading
>

View File

@ -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 = ({
<ResponsiveButton
onClick={() =>
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 = ({
<FeatureToggleListNew
features={filteredFeatures}
loading={loading}
projectId={id}
projectId={projectId}
/>
}
elseShow={
@ -111,11 +111,11 @@ export const ProjectFeatureToggles = ({
No feature toggles added yet.
</p>
<ConditionallyRender
condition={hasAccess(CREATE_FEATURE, id)}
condition={hasAccess(CREATE_FEATURE, projectId)}
show={
<Link
to={getCreateTogglePath(
id,
projectId,
uiConfig.flags.E
)}
className={styles.link}

View File

@ -5,8 +5,6 @@ import { ProjectAccessAddUser } from './ProjectAccessAddUser/ProjectAccessAddUse
import PageContent from 'component/common/PageContent';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useStyles } from './ProjectAccess.styles';
import { useParams } from 'react-router-dom';
import { IProjectViewParams } from 'interfaces/params';
import usePagination from 'hooks/usePagination';
import PaginateUI from 'component/common/PaginateUI/PaginateUI';
import useToast from 'hooks/useToast';
@ -17,9 +15,10 @@ import useProjectAccess, {
import useProjectApi from 'hooks/api/actions/useProjectApi/useProjectApi';
import { HeaderTitle } from 'component/common/HeaderTitle/HeaderTitle';
import { ProjectAccessList } from './ProjectAccessList/ProjectAccessList';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export const ProjectAccess = () => {
const { id: projectId } = useParams<IProjectViewParams>();
const projectId = useRequiredPathParam('projectId');
const { classes: styles } = useStyles();
const { access, refetchProjectAccess } = useProjectAccess(projectId);
const { setToastData } = useToast();

View File

@ -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<IProjectAccessUser | undefined>();
const [role, setRole] = useState<IProjectRole | undefined>();
const [options, setOptions] = useState<IProjectAccessUser[]>([]);
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.';
}

View File

@ -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<IProjectViewParams>();
const projectId = useRequiredPathParam('projectId');
const { classes: styles } = useStyles();
const labelId = `checkbox-list-secondary-label-${user.id}`;

View File

@ -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 = ({
<MenuItem
onClick={e => {
e.preventDefault();
history.push(getProjectEditPath(id));
navigate(getProjectEditPath(id));
}}
>
<Edit className={classes.icon} />

View File

@ -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<projectMap>({});
@ -94,12 +94,7 @@ export const ProjectListNew = () => {
return (
<Link
key={project.id}
to={{
pathname: `/projects/${project.id}`,
state: {
projectName: project.name,
},
}}
to={`/projects/${project.id}`}
className={styles.cardLink}
>
<ProjectCard
@ -151,7 +146,7 @@ export const ProjectListNew = () => {
actions={
<ResponsiveButton
Icon={Add}
onClick={() => history.push('/projects/create')}
onClick={() => navigate('/projects/create')}
maxWidth="700px"
permission={CREATE_PROJECT}
disabled={createButtonData.disabled}

View File

@ -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<ISWRProviderProps> = ({
isUnauthorized,
}) => {
const { cache } = useSWRConfig();
const history = useHistory();
const navigate = useNavigate();
const { setToastApiError } = useToast();
// @ts-expect-error
@ -41,11 +41,11 @@ const SWRProvider: React.FC<ISWRProviderProps> = ({
// @ts-expect-error
cache.clear();
history.push('/login');
navigate('/login');
return;
}
if (!isUnauthorized()) {
if (!isUnauthorized) {
setToastApiError(error.message);
}
};

View File

@ -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,

View File

@ -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',

View File

@ -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<ISegmentFormPartOneProps> = ({
clearErrors,
setCurrentStep,
}) => {
const history = useHistory();
const navigate = useNavigate();
const { classes: styles } = useStyles();
return (
@ -78,7 +78,7 @@ export const SegmentFormStepOne: React.FC<ISegmentFormPartOneProps> = ({
type="button"
className={styles.cancelButton}
onClick={() => {
history.push('/segments');
navigate('/segments');
}}
>
Cancel

View File

@ -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<ISegmentFormPartTwoProps> = ({
mode,
}) => {
const constraintsAccordionListRef = useRef<IConstraintAccordionListRef>();
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<ISegmentFormPartTwoProps> = ({
type="button"
className={styles.cancelButton}
onClick={() => {
history.push('/segments');
navigate('/segments');
}}
>
Cancel

View File

@ -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={
<PermissionButton
onClick={() => history.push('/segments/create')}
onClick={() => navigate('/segments/create')}
permission={CREATE_SEGMENT}
data-testid={NAVIGATE_TO_CREATE_SEGMENT}
>

View File

@ -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 (
<TableRow className={styles.tableRow}>
@ -63,7 +63,7 @@ export const SegmentListItem = ({
<PermissionIconButton
data-loading
onClick={() => {
push(`/segments/edit/${id}`);
navigate(`/segments/edit/${id}`);
}}
permission={UPDATE_SEGMENT}
tooltip="Edit segment"

View File

@ -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>
<Route path="/splash/environments">
<SplashPageEnvironments />
</Route>
<Route path="/splash/operators">
<SplashPageOperators />
</Route>
<Route>
<Redirect to="/" />
</Route>
</Switch>
);
switch (splashId) {
case 'environments':
return <SplashPageEnvironments />;
case 'operators':
return <SplashPageOperators />;
default:
return <Navigate to="/" replace />;
}
};
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 => {

View File

@ -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 (

View File

@ -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 = () => {
<h1 className={styles.title}>New strategy operators</h1>
<IconButton
className={styles.close}
onClick={() => push('/')}
onClick={() => navigate('/')}
size="large"
>
<CloseOutlined titleAccess="Close" />

View File

@ -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 <Redirect to={`/splash/${showSplashId}`} />;
return <Navigate to={`/splash/${showSplashId}`} replace />;
};
const hasSeenSplashId = (splashId: SplashId, splash: IAuthSplash): boolean => {

View File

@ -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 (

View File

@ -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 (

View File

@ -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={
<PermissionIconButton
data-testid={ADD_NEW_STRATEGY_ID}
onClick={() => history.push('/strategies/create')}
onClick={() => navigate('/strategies/create')}
permission={CREATE_STRATEGY}
tooltip="New strategy"
>
@ -79,7 +79,7 @@ export const StrategiesList = () => {
}
elseShow={
<PermissionButton
onClick={() => 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={
<PermissionIconButton
onClick={() =>
history.push(`/strategies/${strategy?.name}/edit`)
navigate(`/strategies/${strategy?.name}/edit`)
}
permission={UPDATE_STRATEGY}
tooltip="Edit strategy"

View File

@ -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;

View File

@ -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 (

View File

@ -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 (

View File

@ -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 = () => {
<Tooltip title="Add tag type">
<IconButton
onClick={() =>
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

Some files were not shown because too many files have changed in this diff Show More