فهرست منبع

Refactor `components` to adopt `React Router v6` hooks for routing

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 1 سال پیش
والد
کامیت
a59b337831

+ 14 - 3
src/components/smart/DashboardPage/DashboardPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
+import { useNavigate } from "react-router";
 
 import DashboardContent from "@src/components/modules/DashboardModule/DashboardContent";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -32,11 +33,15 @@ import Utils from "@src/utils/ObjectUtils";
 
 const Wrapper = styled.div<any>``;
 
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 type State = {
   modalIsOpen: boolean;
 };
 @observer
-class ProjectsPage extends React.Component<{ history: any }, State> {
+class ProjectsPage extends React.Component<Props, State> {
   state = {
     modalIsOpen: false,
   };
@@ -148,7 +153,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
               licenceError={licenceStore.licenceInfoError}
               transfersLoading={transferStore.loading}
               onNewTransferClick={() => {
-                this.props.history.push("/wizard/migration");
+                this.props.onNavigate("/wizard/migration");
               }}
               onNewEndpointClick={() => {
                 this.handleNewEndpointClick();
@@ -181,4 +186,10 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default ProjectsPage;
+function ProjectsPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <ProjectsPage onNavigate={navigate} />;
+}
+
+export default ProjectsPageWithNavigate;

+ 24 - 7
src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
+import { useNavigate, useParams } from "react-router";
 
 import {
   getTransferItemTitle,
@@ -48,8 +49,8 @@ import type { InstanceScript } from "@src/@types/Instance";
 const Wrapper = styled.div<any>``;
 
 type Props = {
-  match: any;
-  history: any;
+  match: { params: { id: string; page: string | null } };
+  onNavigate: (path: string) => void;
 };
 type State = {
   showDeleteDeploymentConfirmation: boolean;
@@ -274,7 +275,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> {
 
   handleDeleteDeploymentConfirmation() {
     this.setState({ showDeleteDeploymentConfirmation: false });
-    this.props.history.push("/deployments");
+    this.props.onNavigate("/deployments");
     if (deploymentStore.deploymentDetails) {
       deploymentStore.delete(deploymentStore.deploymentDetails.id);
     }
@@ -296,7 +297,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> {
     if (!deploymentStore.deploymentDetails?.deployer_id) {
       return;
     }
-    this.props.history.push(
+    this.props.onNavigate(
       `/transfers/${deploymentStore.deploymentDetails.transfer_id}/executions`
     );
   }
@@ -362,7 +363,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> {
         removedUserScripts,
         minionPoolMappings,
       });
-      this.props.history.push(`/deployments/${deployment.id}/tasks`);
+      this.props.onNavigate(`/deployments/${deployment.id}/tasks`);
     } finally {
       this.setState({ deploying: false });
     }
@@ -421,7 +422,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> {
   }
 
   handleUpdateComplete(redirectTo: string) {
-    this.props.history.push(redirectTo);
+    this.props.onNavigate(redirectTo);
   }
 
   renderEditModal() {
@@ -655,4 +656,20 @@ Note that this may lead to scheduled cleanup tasks being forcibly skipped, and t
   }
 }
 
-export default DeploymentDetailsPage;
+function DeploymentDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id, page } = useParams();
+
+  if (!id) {
+    throw new Error("The 'id' parameter is required but was not provided.");
+  }
+
+  return (
+    <DeploymentDetailsPage
+      onNavigate={navigate}
+      match={{ params: { id, page: page || null } }}
+    />
+  );
+}
+
+export default DeploymentDetailsPageWithNavigate;

+ 17 - 5
src/components/smart/DeploymentsPage/DeploymentsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -44,8 +45,13 @@ type State = {
   showCancelDeploymentModal: boolean;
   showRecreateDeploymentsModal: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class DeploymentsPage extends React.Component<{ history: any }, State> {
+class DeploymentsPage extends React.Component<Props, State> {
   state: State = {
     showDeleteDeploymentModal: false,
     showCancelDeploymentModal: false,
@@ -119,9 +125,9 @@ class DeploymentsPage extends React.Component<{ history: any }, State> {
 
   handleItemClick(item: DeploymentItem) {
     if (item.last_execution_status === "RUNNING") {
-      this.props.history.push(`/deployments/${item.id}/tasks`);
+      this.props.onNavigate(`/deployments/${item.id}/tasks`);
     } else {
-      this.props.history.push(`/deployments/${item.id}`);
+      this.props.onNavigate(`/deployments/${item.id}`);
     }
   }
 
@@ -169,7 +175,7 @@ class DeploymentsPage extends React.Component<{ history: any }, State> {
   }
 
   handleEmptyListButtonClick() {
-    this.props.history.push("/wizard/deployment");
+    this.props.onNavigate("/wizard/deployment");
   }
 
   handleModalOpen() {
@@ -377,4 +383,10 @@ class DeploymentsPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default DeploymentsPage;
+function DeploymentsPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <DeploymentsPage onNavigate={navigate} />;
+}
+
+export default DeploymentsPageWithNavigate;

+ 15 - 7
src/components/smart/EndpointDetailsPage/EndpointDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate, useParams } from "react-router";
 
 import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
 import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader";
@@ -44,8 +45,8 @@ import endpointImage from "./images/endpoint.svg";
 const Wrapper = styled.div<any>``;
 
 type Props = {
-  match: any;
-  history: any;
+  id: any;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showDeleteEndpointConfirmation: boolean;
@@ -82,7 +83,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
 
   get endpoint(): EndpointType | null {
     return (
-      endpointStore.endpoints.find(e => e.id === this.props.match.params.id) ||
+      endpointStore.endpoints.find(e => e.id === this.props.id) ||
       null
     );
   }
@@ -91,7 +92,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
     deployments: DeploymentItem[];
     transfers: TransferItem[];
   } {
-    const endpointId = this.props.match.params.id;
+    const endpointId = this.props.id;
     const transfers = transferStore.transfers.filter(
       r =>
         r.origin_endpoint_id === endpointId ||
@@ -145,7 +146,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
     if (this.endpoint) {
       endpointStore.delete(this.endpoint);
     }
-    this.props.history.push("/endpoints");
+    this.props.onNavigate("/endpoints");
   }
 
   handleCloseDeleteEndpointConfirmation() {
@@ -206,7 +207,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
       endpoints: [endpoint],
       onSwitchProject: () => userStore.switchProject(projectId),
     });
-    this.props.history.push("/endpoints");
+    this.props.onNavigate("/endpoints");
   }
 
   handleExportToJsonClick() {
@@ -411,4 +412,11 @@ class EndpointDetailsPage extends React.Component<Props, State> {
   }
 }
 
-export default EndpointDetailsPage;
+function EndpointDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id } = useParams();
+
+  return <EndpointDetailsPage onNavigate={navigate} id={id!} />;
+}
+
+export default EndpointDetailsPageWithNavigate;

+ 15 - 3
src/components/smart/EndpointsPage/EndpointsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -56,8 +57,13 @@ type State = {
   uploadedEndpoint: EndpointType | null;
   multiValidating: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class EndpointsPage extends React.Component<{ history: any }, State> {
+class EndpointsPage extends React.Component<Props, State> {
   state: State = {
     showChooseProviderModal: false,
     showEndpointModal: false,
@@ -136,7 +142,7 @@ class EndpointsPage extends React.Component<{ history: any }, State> {
   }
 
   handleItemClick(item: EndpointType) {
-    this.props.history.push(`/endpoints/${item.id}`);
+    this.props.onNavigate(`/endpoints/${item.id}`);
   }
 
   async duplicate(projectId: string) {
@@ -463,4 +469,10 @@ class EndpointsPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default EndpointsPage;
+function EndpointsPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <EndpointsPage onNavigate={navigate} />;
+}
+
+export default EndpointsPageWithNavigate;

+ 11 - 4
src/components/smart/LoginPage/LoginPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import EmptyTemplate from "@src/components/modules/TemplateModule/EmptyTemplate";
 import Logo from "@src/components/ui/Logo";
@@ -100,7 +101,7 @@ const Disclaimer = styled.div<any>`
 `;
 
 type Props = {
-  history: any;
+  onNavigate: (path: string) => void;
 };
 
 type State = {
@@ -144,9 +145,9 @@ class LoginPage extends React.Component<Props, State> {
     const prevExp = /\?prev=(.*)/;
     const prevMatch = prevExp.exec(window.location.search);
     if (prevMatch) {
-      this.props.history.push(prevMatch[1]);
+      this.props.onNavigate(prevMatch[1]);
     } else {
-      this.props.history.push("/");
+      this.props.onNavigate("/");
     }
   }
 
@@ -180,4 +181,10 @@ class LoginPage extends React.Component<Props, State> {
   }
 }
 
-export default LoginPage;
+function LoginPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <LoginPage onNavigate={navigate} />;
+}
+
+export default LoginPageWithNavigate;

+ 15 - 7
src/components/smart/MetalHubServerDetailsPage/MetalHubServerDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate, useParams } from "react-router";
 
 import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
 import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader";
@@ -38,8 +39,8 @@ import serverImage from "./images/server.svg";
 const Wrapper = styled.div<any>``;
 
 type Props = {
-  match: { params: { id: string } };
-  history: any;
+  id: string;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showDeleteServerAlert: boolean;
@@ -79,7 +80,7 @@ class MetalHubServerDetailsPage extends React.Component<Props, State> {
     this.setState({ showDeleteServerAlert: false });
 
     await metalHubStore.deleteServer(metalHubStore.serverDetails!.id);
-    this.props.history.push("/bare-metal-servers");
+    this.props.onNavigate("/bare-metal-servers");
   }
 
   handleRefresh() {
@@ -91,10 +92,10 @@ class MetalHubServerDetailsPage extends React.Component<Props, State> {
   }
 
   async loadData() {
-    const serverId = Number(this.props.match.params.id);
+    const serverId = Number(this.props.id);
     await metalHubStore.getServerDetails(serverId);
     if (!metalHubStore.serverDetails) {
-      this.props.history.push("/bare-metal-servers");
+      this.props.onNavigate("/bare-metal-servers");
     }
   }
 
@@ -121,7 +122,7 @@ class MetalHubServerDetailsPage extends React.Component<Props, State> {
       source: endpoint,
       selectedInstances: [instance],
     };
-    this.props.history.push(
+    this.props.onNavigate(
       `/wizard/${type}/?d=${window.btoa(
         JSON.stringify({
           data,
@@ -266,4 +267,11 @@ class MetalHubServerDetailsPage extends React.Component<Props, State> {
   }
 }
 
-export default MetalHubServerDetailsPage;
+function MetalHubServerDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id } = useParams<{ id: string }>();
+
+  return <MetalHubServerDetailsPage onNavigate={navigate} id={id!} />;
+}
+
+export default MetalHubServerDetailsPageWithNavigate;

+ 16 - 4
src/components/smart/MetalHubServersPage/MetalHubServersPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -59,8 +60,13 @@ type State = {
   selectedServers: number[];
   showConfirmRemove: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class MetalHubServersPage extends React.Component<{ history: any }, State> {
+class MetalHubServersPage extends React.Component<Props, State> {
   state: State = {
     modalIsOpen: false,
     showNewServerModal: false,
@@ -163,7 +169,7 @@ class MetalHubServersPage extends React.Component<{ history: any }, State> {
       source: endpoint,
       selectedInstances: instances,
     };
-    this.props.history.push(
+    this.props.onNavigate(
       `/wizard/${type}/?d=${window.btoa(
         JSON.stringify({
           data,
@@ -292,7 +298,7 @@ class MetalHubServersPage extends React.Component<{ history: any }, State> {
                 />
               }
               onItemClick={(server: MetalHubServer) => {
-                this.props.history.push(`/bare-metal-servers/${server.id}`);
+                this.props.onNavigate(`/bare-metal-servers/${server.id}`);
               }}
               onReloadButtonClick={() => {
                 this.handleReloadButtonClick();
@@ -355,4 +361,10 @@ class MetalHubServersPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default MetalHubServersPage;
+function MetalHubServersPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <MetalHubServersPage onNavigate={navigate} />;
+}
+
+export default MetalHubServersPageWithNavigate;

+ 22 - 5
src/components/smart/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate, useParams } from "react-router";
 
 import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
 import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader";
@@ -46,7 +47,7 @@ const Wrapper = styled.div<any>``;
 
 type Props = {
   match: { params: { id: string; page: string | null } };
-  history: any;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showEditModal: boolean;
@@ -186,7 +187,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
 
   handleDeleteMinionPool() {
     this.setState({ showDeleteMinionPoolConfirmation: false });
-    this.props.history.push("/minion-pools");
+    this.props.onNavigate("/minion-pools");
     minionPoolStore.deleteMinionPool(this.minionPool!.id);
   }
 
@@ -220,7 +221,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
   }
 
   handleUpdateComplete(redirectTo: string) {
-    this.props.history.push(redirectTo);
+    this.props.onNavigate(redirectTo);
     this.closeEditModal();
   }
 
@@ -246,7 +247,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
     notificationStore.alert("Refreshing minion pool...");
     await minionPoolStore.runAction(this.minionPool.id, "refresh");
     await minionPoolStore.loadMinionPoolDetails(this.minionPool.id);
-    this.props.history.push(`/minion-pools/${this.minionPool.id}/machines`);
+    this.props.onNavigate(`/minion-pools/${this.minionPool.id}/machines`);
   }
 
   async handleDeallocateConfirmation(force: boolean) {
@@ -444,4 +445,20 @@ class MinionPoolDetailsPage extends React.Component<Props, State> {
   }
 }
 
-export default MinionPoolDetailsPage;
+function MinionPoolDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id, page } = useParams();
+
+  if (!id) {
+    throw new Error("The 'id' parameter is required but was not provided.");
+  }
+
+  return (
+    <MinionPoolDetailsPage
+      onNavigate={navigate}
+      match={{ params: { id, page: page || null } }}
+    />
+  );
+}
+
+export default MinionPoolDetailsPageWithNavigate;

+ 16 - 4
src/components/smart/MinionPoolsPage/MinionPoolsPage.tsx

@@ -16,7 +16,7 @@ import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
 
-import { RouteComponentProps } from "react-router-dom";
+import { useNavigate } from "react-router";
 
 import Modal from "@src/components/ui/Modal";
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
@@ -47,6 +47,10 @@ import emptyListImage from "./images/minion-pool-empty-list.svg";
 
 const Wrapper = styled.div<any>``;
 
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 type State = {
   modalIsOpen: boolean;
   selectedMinionPools: MinionPool[];
@@ -59,7 +63,7 @@ type State = {
 };
 
 @observer
-class MinionPoolsPage extends React.Component<RouteComponentProps, State> {
+class MinionPoolsPage extends React.Component<Props, State> {
   state: State = {
     modalIsOpen: false,
     selectedMinionPools: [],
@@ -174,7 +178,7 @@ class MinionPoolsPage extends React.Component<RouteComponentProps, State> {
   }
 
   handleItemClick(item: MinionPool) {
-    this.props.history.push(`/minion-pools/${item.id}`);
+    this.props.onNavigate(`/minion-pools/${item.id}`);
   }
 
   handleReloadButtonClick() {
@@ -457,4 +461,12 @@ class MinionPoolsPage extends React.Component<RouteComponentProps, State> {
   }
 }
 
-export default MinionPoolsPage;
+function MinionPoolsPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return (
+    <MinionPoolsPage onNavigate={navigate} />
+  );
+}
+
+export default MinionPoolsPageWithNavigate;

+ 22 - 14
src/components/smart/ProjectDetailsPage/ProjectDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate, useParams } from "react-router";
 
 import type { User } from "@src/@types/User";
 import type { Project, Role } from "@src/@types/Project";
@@ -36,8 +37,8 @@ import projectImage from "./images/project.svg";
 const Wrapper = styled.div<any>``;
 
 type Props = {
-  match: { params: { id: string } };
-  history: any;
+  id: string;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showProjectModal: boolean;
@@ -77,11 +78,11 @@ class ProjectDetailsPage extends React.Component<Props, State> {
     const enabled = !user.enabled;
 
     await userStore.update(user.id, { enabled });
-    projectStore.getUsers(this.props.match.params.id);
+    projectStore.getUsers(this.props.id);
   }
 
   async handleUserRoleChange(user: User, roleId: string, toggled: boolean) {
-    const projectId = this.props.match.params.id;
+    const projectId = this.props.id;
     if (toggled) {
       await projectStore.assignUserRole(projectId, user.id, roleId);
     } else {
@@ -94,11 +95,11 @@ class ProjectDetailsPage extends React.Component<Props, State> {
     const roles = projectStore.roleAssignments
       .filter(
         a =>
-          a.scope.project && a.scope.project.id === this.props.match.params.id
+          a.scope.project && a.scope.project.id === this.props.id
       )
       .filter(a => a.user.id === user.id)
       .map(ra => ra.role.id);
-    projectStore.removeUser(this.props.match.params.id, user.id, roles);
+    projectStore.removeUser(this.props.id, user.id, roles);
   }
 
   handleEditProjectClick() {
@@ -110,24 +111,24 @@ class ProjectDetailsPage extends React.Component<Props, State> {
   }
 
   async handleProjectUpdateClick(project: Project) {
-    await projectStore.update(this.props.match.params.id, project);
+    await projectStore.update(this.props.id, project);
     this.setState({ showProjectModal: false });
   }
 
   async handleDeleteConfirmation() {
     this.setState({ showDeleteProjectAlert: false });
 
-    await projectStore.delete(this.props.match.params.id);
+    await projectStore.delete(this.props.id);
     if (
       userStore.loggedUser &&
-      this.props.match.params.id === userStore.loggedUser.project.id &&
+      this.props.id === userStore.loggedUser.project.id &&
       projectStore.projects.length > 0
     ) {
       await userStore.switchProject(projectStore.projects[0].id);
       projectStore.getProjects();
-      this.props.history.push("/projects");
+      this.props.onNavigate("/projects");
     } else {
-      this.props.history.push("/projects");
+      this.props.onNavigate("/projects");
     }
   }
 
@@ -142,7 +143,7 @@ class ProjectDetailsPage extends React.Component<Props, State> {
         roles.map(async r => {
           await userStore.assignUserToProjectWithRole(
             userId,
-            this.props.match.params.id,
+            this.props.id,
             r.id
           );
         })
@@ -169,7 +170,7 @@ class ProjectDetailsPage extends React.Component<Props, State> {
   }
 
   loadData() {
-    const projectId = this.props.match.params.id;
+    const projectId = this.props.id;
     projectStore.getProjects();
     projectStore.getProjectDetails(projectId);
     projectStore.getUsers(projectId, true);
@@ -306,4 +307,11 @@ class ProjectDetailsPage extends React.Component<Props, State> {
   }
 }
 
-export default ProjectDetailsPage;
+function ProjectDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id } = useParams<{ id: string }>();
+
+  return <ProjectDetailsPage onNavigate={navigate} id={id!} />;
+}
+
+export default ProjectDetailsPageWithNavigate;

+ 15 - 3
src/components/smart/ProjectsPage/ProjectsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -33,8 +34,13 @@ const Wrapper = styled.div<any>``;
 type State = {
   modalIsOpen: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class ProjectsPage extends React.Component<{ history: any }, State> {
+class ProjectsPage extends React.Component<Props, State> {
   state = {
     modalIsOpen: false,
   };
@@ -135,7 +141,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
               loading={projectStore.loading}
               items={projectStore.projects}
               onItemClick={(user: Project) => {
-                this.props.history.push(`/projects/${user.id}`);
+                this.props.onNavigate(`/projects/${user.id}`);
               }}
               onReloadButtonClick={() => {
                 this.handleReloadButtonClick();
@@ -172,4 +178,10 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default ProjectsPage;
+function ProjectsPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <ProjectsPage onNavigate={navigate} />;
+}
+
+export default ProjectsPageWithNavigate;

+ 10 - 3
src/components/smart/SetupPage/SetupPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled, { css } from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import backgroundImage from "@src/components/ui/Images/star-bg.jpg";
 import configLoader from "@src/utils/Config";
@@ -107,7 +108,7 @@ type PageName = "welcome/password" | "licence" | "legal/help" | "email/error";
 const SETUP_PAGES: PageName[] = ["legal/help"];
 
 type Props = {
-  history: any;
+  onNavigate: (path: string) => void;
 };
 
 type State = {
@@ -222,7 +223,7 @@ class SetupPage extends React.Component<Props, State> {
   async handleGoToLogin() {
     this.setState({ submitting: true });
     await configLoader.setNotFirstLaunch();
-    this.props.history.push("/login");
+    this.props.onNavigate("/login");
     this.setState({ submitting: false });
   }
 
@@ -399,4 +400,10 @@ class SetupPage extends React.Component<Props, State> {
   }
 }
 
-export default SetupPage;
+function SetupPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <SetupPage onNavigate={navigate} />;
+}
+
+export default SetupPageWithNavigate;

+ 24 - 7
src/components/smart/TransferDetailsPage/TransferDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
+import { useNavigate, useParams } from "react-router";
 
 import {
   getTransferItemTitle,
@@ -57,7 +58,7 @@ const Wrapper = styled.div<any>``;
 
 type Props = {
   match: { params: { id: string; page: string | null } };
-  history: any;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showOptionsModal: boolean;
@@ -403,7 +404,7 @@ class TransferDetailsPage extends React.Component<Props, State> {
     if (!transfer) {
       return;
     }
-    this.props.history.push("/transfers");
+    this.props.onNavigate("/transfers");
     transferStore.delete(transfer.id);
   }
 
@@ -421,7 +422,7 @@ class TransferDetailsPage extends React.Component<Props, State> {
       return;
     }
     transferStore.deleteDisks(transfer.id);
-    this.props.history.push(`/transfers/${transfer.id}/executions`);
+    this.props.onNavigate(`/transfers/${transfer.id}/executions`);
   }
 
   handleCloseDeleteTransferDisksConfirmation() {
@@ -548,7 +549,7 @@ class TransferDetailsPage extends React.Component<Props, State> {
         userScriptData: transfer.user_scripts,
         minionPoolMappings,
       });
-      this.props.history.push(`/deployments/${deployment.id}/tasks/`);
+      this.props.onNavigate(`/deployments/${deployment.id}/tasks/`);
     } finally {
       this.setState({ deploying: false });
     }
@@ -564,7 +565,7 @@ class TransferDetailsPage extends React.Component<Props, State> {
       await transferStore.execute(transfer.id, fields);
 
       this.handleCloseOptionsModal();
-      this.props.history.push(`/transfers/${transfer.id}/executions`);
+      this.props.onNavigate(`/transfers/${transfer.id}/executions`);
     } finally {
       this.setState({ executing: false });
     }
@@ -611,7 +612,7 @@ class TransferDetailsPage extends React.Component<Props, State> {
   }
 
   handleUpdateComplete(redirectTo: string) {
-    this.props.history.push(redirectTo);
+    this.props.onNavigate(redirectTo);
     this.closeEditModal();
   }
 
@@ -951,4 +952,20 @@ Note that this may lead to scheduled cleanup tasks being forcibly skipped, and t
   }
 }
 
-export default TransferDetailsPage;
+function TransferDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id, page } = useParams();
+
+  if (!id) {
+    throw new Error("The 'id' parameter is required but was not provided.");
+  }
+
+  return (
+    <TransferDetailsPage
+      onNavigate={navigate}
+      match={{ params: { id, page: page || null } }}
+    />
+  );
+}
+
+export default TransferDetailsPageWithNavigate;

+ 18 - 6
src/components/smart/TransfersPage/TransfersPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -58,8 +59,13 @@ type State = {
   showDeleteDisksModal: boolean;
   showDeleteTransfersModal: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class TransfersPage extends React.Component<{ history: any }, State> {
+class TransfersPage extends React.Component<Props, State> {
   state: State = {
     modalIsOpen: false,
     selectedTransfers: [],
@@ -131,9 +137,9 @@ class TransfersPage extends React.Component<{ history: any }, State> {
 
   handleItemClick(item: TransferItem) {
     if (item.last_execution_status === "RUNNING") {
-      this.props.history.push(`/transfers/${item.id}/executions`);
+      this.props.onNavigate(`/transfers/${item.id}/executions`);
     } else {
-      this.props.history.push(`/transfers/${item.id}`);
+      this.props.onNavigate(`/transfers/${item.id}`);
     }
   }
 
@@ -181,7 +187,7 @@ class TransfersPage extends React.Component<{ history: any }, State> {
       "Deployments successfully created from transfers.",
       "success"
     );
-    this.props.history.push("/deployments");
+    this.props.onNavigate("/deployments");
   }
 
   handleShowDeleteTransfers() {
@@ -243,7 +249,7 @@ class TransfersPage extends React.Component<{ history: any }, State> {
   }
 
   handleEmptyListButtonClick() {
-    this.props.history.push("/wizard/migration");
+    this.props.onNavigate("/wizard/migration");
   }
 
   handleModalOpen() {
@@ -585,4 +591,10 @@ class TransfersPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default TransfersPage;
+function TransfersPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <TransfersPage onNavigate={navigate} />;
+}
+
+export default TransfersPageWithNavigate;

+ 19 - 11
src/components/smart/UserDetailsPage/UserDetailsPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate, useParams } from "react-router";
 
 import type { User } from "@src/@types/User";
 import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
@@ -34,8 +35,8 @@ import userImage from "./images/user.svg";
 const Wrapper = styled.div<any>``;
 
 type Props = {
-  match: { params: { id: string } };
-  history: any;
+  id: string;
+  onNavigate: (path: string) => void;
 };
 type State = {
   showUserModal: boolean;
@@ -57,8 +58,8 @@ class UserDetailsPage extends React.Component<Props, State> {
   }
 
   UNSAFE_componentWillReceiveProps(newProps: Props) {
-    if (newProps.match.params.id !== this.props.match.params.id) {
-      this.loadData(newProps.match.params.id);
+    if (newProps.id !== this.props.id) {
+      this.loadData(newProps.id);
     }
   }
 
@@ -81,8 +82,8 @@ class UserDetailsPage extends React.Component<Props, State> {
   }
 
   async handleDeleteConfirmation() {
-    await userStore.delete(this.props.match.params.id);
-    this.props.history.push("/users");
+    await userStore.delete(this.props.id);
+    this.props.onNavigate("/users");
   }
 
   handleUserEditModalClose() {
@@ -90,8 +91,8 @@ class UserDetailsPage extends React.Component<Props, State> {
   }
 
   async handleUserUpdateClick(user: User) {
-    await userStore.update(this.props.match.params.id, user);
-    userStore.getProjects(this.props.match.params.id);
+    await userStore.update(this.props.id, user);
+    userStore.getProjects(this.props.id);
     this.setState({ showUserModal: false, editPassword: false });
   }
 
@@ -105,8 +106,8 @@ class UserDetailsPage extends React.Component<Props, State> {
 
   loadData(id?: string) {
     projectStore.getProjects();
-    userStore.getProjects(id || this.props.match.params.id);
-    userStore.getUserInfo(id || this.props.match.params.id);
+    userStore.getProjects(id || this.props.id);
+    userStore.getUserInfo(id || this.props.id);
   }
 
   render() {
@@ -211,4 +212,11 @@ class UserDetailsPage extends React.Component<Props, State> {
   }
 }
 
-export default UserDetailsPage;
+function UserDetailsPageWithNavigate() {
+  const navigate = useNavigate();
+  const { id } = useParams<{ id: string }>();
+
+  return <UserDetailsPage onNavigate={navigate} id={id!} />;
+}
+
+export default UserDetailsPageWithNavigate;

+ 15 - 3
src/components/smart/UsersPage/UsersPage.tsx

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import React from "react";
 import styled from "styled-components";
 import { observer } from "mobx-react";
+import { useNavigate } from "react-router";
 
 import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate";
 import Navigation from "@src/components/modules/NavigationModule/Navigation";
@@ -33,8 +34,13 @@ const Wrapper = styled.div<any>``;
 type State = {
   modalIsOpen: boolean;
 };
+
+type Props = {
+  onNavigate: (path: string) => void;
+};
+
 @observer
-class UsersPage extends React.Component<{ history: any }, State> {
+class UsersPage extends React.Component<Props, State> {
   state = {
     modalIsOpen: false,
   };
@@ -127,7 +133,7 @@ class UsersPage extends React.Component<{ history: any }, State> {
               loading={userStore.allUsersLoading}
               items={userStore.users}
               onItemClick={(user: User) => {
-                this.props.history.push(`/users/${user.id}`);
+                this.props.onNavigate(`/users/${user.id}`);
               }}
               onReloadButtonClick={() => {
                 this.handleReloadButtonClick();
@@ -158,4 +164,10 @@ class UsersPage extends React.Component<{ history: any }, State> {
   }
 }
 
-export default UsersPage;
+function UsersPageWithNavigate() {
+  const navigate = useNavigate();
+
+  return <UsersPage onNavigate={navigate} />;
+}
+
+export default UsersPageWithNavigate;

+ 18 - 7
src/components/smart/WizardPage/WizardPage.tsx

@@ -16,6 +16,7 @@ import autobind from "autobind-decorator";
 import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
+import { useNavigate, useParams } from "react-router";
 
 import { TransferItem, ActionItem } from "@src/@types/MainItem";
 import { ProviderTypes } from "@src/@types/Providers";
@@ -59,8 +60,8 @@ const Wrapper = styled.div<any>``;
 
 type Props = {
   match: any;
-  location: { search: string };
-  history: any;
+  location?: { search: string };
+  onNavigate: (path: string) => void;
 };
 type WizardType = "migration" | "replica";
 type State = {
@@ -198,7 +199,7 @@ class WizardPage extends React.Component<Props, State> {
     const typeLabel =
       this.state.type.charAt(0).toUpperCase() + this.state.type.substr(1);
     notificationStore.alert(
-      `${typeLabel}${items.length > 1 ? "s" : ""} was succesfully created`,
+      `${typeLabel}${items.length > 1 ? "s" : ""} was successfully created`,
       "success"
     );
     let schedulePromise = Promise.resolve();
@@ -214,9 +215,9 @@ class WizardPage extends React.Component<Props, State> {
     if (items.length === 1) {
       const location = `/transfers/${items[0].id}/executions`;
       await schedulePromise;
-      this.props.history.push(location);
+      this.props.onNavigate(location);
     } else {
-      this.props.history.push(`/transfers`);
+      this.props.onNavigate(`/transfers`);
     }
   }
 
@@ -240,7 +241,8 @@ class WizardPage extends React.Component<Props, State> {
     });
     wizardStore.clearStorageMap();
     const type = isReplica ? "replica" : "migration";
-    this.props.history.replace(`/wizard/${type}`);
+    this.props.onNavigate(`/wizard/${type}`);
+    this.setState({ type });
   }
 
   handleStorageReloadClick() {
@@ -963,4 +965,13 @@ class WizardPage extends React.Component<Props, State> {
   }
 }
 
-export default WizardPage;
+function WizardPageWithNavigate() {
+  const navigate = useNavigate();
+  const params = useParams();
+
+  return (
+    <WizardPage onNavigate={navigate} match={params} />
+  );
+}
+
+export default WizardPageWithNavigate;