Sfoglia il codice sorgente

Home class -> FC refactor

Justin Rhee 3 anni fa
parent
commit
a3a99ec49c
1 ha cambiato i file con 266 aggiunte e 304 eliminazioni
  1. 266 304
      dashboard/src/main/home/Home.tsx

+ 266 - 304
dashboard/src/main/home/Home.tsx

@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { useEffect, useState, useContext, useRef } from "react";
 import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
 import styled from "styled-components";
 
@@ -45,65 +45,76 @@ const GuardedIntegrations = fakeGuardedRoute("integrations", "", [
   "delete",
 ])(Integrations);
 
-type PropsType = RouteComponentProps &
-  WithAuthProps & {
-    logOut: () => void;
-    currentProject: ProjectType;
-    currentCluster: ClusterType;
-    currentRoute: PorterUrl;
-  };
-
-type StateType = {
-  forceSidebar: boolean;
-  showWelcome: boolean;
-  handleDO: boolean; // Trigger DO infra calls after oauth flow if needed
-  ghRedirect: boolean;
-  forceRefreshClusters: boolean; // For updating ClusterSection from modal on deletion
-
-  // Track last project id for refreshing clusters on project change
-  prevProjectId: number | null;
-  showWelcomeForm: boolean;
+type Props = RouteComponentProps & WithAuthProps & {
+  logOut: () => void;
+  currentProject: ProjectType;
+  currentCluster: ClusterType;
+  currentRoute: PorterUrl;
 };
 
-class Home extends Component<PropsType, StateType> {
-  state = {
-    forceSidebar: true,
-    showWelcome: false,
-    prevProjectId: null as number | null,
-    forceRefreshClusters: false,
-    sidebarReady: false,
-    handleDO: false,
-    ghRedirect: false,
-    showWelcomeForm: true,
+const Home: React.FC<Props> = props => {
+  const {
+    user,
+    projects,
+    currentCluster,
+    currentProject,
+    currentModal,
+    currentOverlay,
+    hasFinishedOnboarding,
+    setProjects,
+    setCurrentProject,
+    setCapabilities,
+    setCanCreateProject,
+    setHasFinishedOnboarding,
+    setCurrentError,
+    setCurrentModal,
+    setHasBillingEnabled,
+    setUsage,
+  } = useContext(Context);
+
+  const [showWelcome, setShowWelcome] = useState(false);
+  const [prevProjectId, setPrevProjectId] = useState<number | null>(null);
+  const [forceRefreshClusters, setForceRefreshClusters] = useState(false);
+  const [sidebarReady, setSidebarReady] = useState(false);
+  const [handleDO, setHandleDO] = useState(false);
+  const [ghRedirect, setGhRedirect] = useState(false);
+  const [showWelcomeForm, setShowWelcomeForm] = useState(true);
+  const [forceSidebar, setForceSidebar] = useState(true);
+
+  const redirectToNewProject = () => {
+    pushFiltered(props, "/new-project", ["project_id"]);
   };
 
-  getMetadata = () => {
+  const redirectToOnboarding = () => {
+    pushFiltered(props, "/onboarding", []);
+  };
+
+  const getMetadata = () => {
     api
       .getMetadata("<token>", {}, {})
-      .then((res) => {
-        this.context.setCapabilities(res.data);
+      .then(res => {
+        setCapabilities(res.data);
       })
       .catch((err) => {
         console.log(err);
       });
   };
 
-  getProjects = (id?: number) => {
-    let { user, setProjects, setCurrentProject } = this.context;
-    let { currentProject } = this.props;
+  const getProjects = (id?: number) => {
+    let { currentProject } = props;
     let queryString = window.location.search;
     let urlParams = new URLSearchParams(queryString);
     let projectId = urlParams.get("project_id");
     if (!projectId && currentProject?.id) {
-      pushQueryParams(this.props, { project_id: currentProject.id.toString() });
+      pushQueryParams(props, { project_id: currentProject.id.toString() });
     }
 
     api
       .getProjects("<token>", {}, { id: user.userId })
-      .then((res) => {
+      .then(res => {
         if (res.data) {
           if (res.data.length === 0) {
-            this.redirectToNewProject();
+            redirectToNewProject();
           } else if (res.data.length > 0 && !currentProject) {
             setProjects(res.data);
 
@@ -133,28 +144,47 @@ class Home extends Component<PropsType, StateType> {
       .catch(console.log);
   };
 
-  checkIfCanCreateProject = () => {
+  const checkIfCanCreateProject = () => {
     api
       .getCanCreateProject("<token>", {}, {})
       .then((res) => {
         if (res.status === 403) {
-          this.context.setCanCreateProject(false);
+          setCanCreateProject(false);
           return;
         }
-        this.context.setCanCreateProject(true);
+        setCanCreateProject(true);
       })
       .catch((err) => {
-        this.context.setCanCreateProject(false);
+        setCanCreateProject(false);
         console.error(err);
       });
   };
 
-  componentDidMount() {
-    this.checkOnboarding();
-    this.checkIfCanCreateProject();
-    let { match } = this.props;
+  const checkOnboarding = async () => {
+    try {
+      const project_id = currentProject?.id;
+      if (!project_id) {
+        return;
+      }
+      const res = await api.getOnboardingState("<token>", {}, { project_id });
 
-    let { user } = this.context;
+      if (res.status === 404) {
+        setHasFinishedOnboarding(true);
+        return;
+      }
+
+      if (res?.data && res?.data.current_step !== "clean_up") {
+        setHasFinishedOnboarding(false);
+      } else {
+        setHasFinishedOnboarding(true);
+      }
+    } catch (error) {}
+  }
+
+  useEffect(() => {
+    checkOnboarding();
+    checkIfCanCreateProject();
+    let { match } = props;
 
     // Handle redirect from DO
     let queryString = window.location.search;
@@ -162,30 +192,30 @@ class Home extends Component<PropsType, StateType> {
 
     let err = urlParams.get("error");
     if (err) {
-      this.context.setCurrentError(err);
+      setCurrentError(err);
     }
 
     let defaultProjectId = parseInt(urlParams.get("project_id"));
 
-    this.setState({ ghRedirect: urlParams.get("gh_oauth") !== null });
+    setGhRedirect(urlParams.get("gh_oauth") !== null);
     urlParams.delete("gh_oauth");
-    this.getProjects(defaultProjectId);
-    this.getMetadata();
+    getProjects(defaultProjectId);
+    getMetadata();
 
     if (
-      !this.context.hasFinishedOnboarding &&
-      this.props.history.location.pathname &&
-      !this.props.history.location.pathname.includes("onboarding")
+      !hasFinishedOnboarding &&
+      props.history.location.pathname &&
+      !props.history.location.pathname.includes("onboarding")
     ) {
-      this.context.setCurrentModal("RedirectToOnboardingModal");
+      setCurrentModal("RedirectToOnboardingModal");
     }
-  }
 
-  componentWillUnmount(): void {
-    this.context.setCanCreateProject(false);
-  }
+    return () => {
+      setCanCreateProject(false);
+    }
+  }, []);
 
-  async checkIfProjectHasBilling(projectId: number) {
+  const checkIfProjectHasBilling = async (projectId: number) => {
     if (!projectId) {
       return false;
     }
@@ -195,117 +225,96 @@ class Home extends Component<PropsType, StateType> {
         {},
         { project_id: projectId }
       );
-      this.context.setHasBillingEnabled(res.data?.has_billing);
+      setHasBillingEnabled(res.data?.has_billing);
       return res?.data?.has_billing;
     } catch (error) {
       console.log(error);
     }
   }
 
-  async checkOnboarding() {
-    try {
-      const project_id = this.context?.currentProject?.id;
-      if (!project_id) {
-        return;
-      }
-      const res = await api.getOnboardingState("<token>", {}, { project_id });
-
-      if (res.status === 404) {
-        this.context.setHasFinishedOnboarding(true);
-        return;
-      }
-
-      if (res?.data && res?.data.current_step !== "clean_up") {
-        this.context.setHasFinishedOnboarding(false);
-      } else {
-        this.context.setHasFinishedOnboarding(true);
-      }
-    } catch (error) {}
-  }
+  useEffect(() => {
+    getMetadata();
+    checkOnboarding();
+    if (!process.env.DISABLE_BILLING) {
+      checkIfProjectHasBilling(currentProject?.id)
+        .then((isBillingEnabled) => {
+          if (isBillingEnabled) {
+            api
+              .getUsage(
+                "<token>",
+                {},
+                { project_id: currentProject?.id }
+              )
+              .then((res) => {
+                const usage = res.data;
+                setUsage(usage);
+                if (usage.exceeded) {
+                  setCurrentModal("UsageWarningModal", { usage });
+                }
+              })
+              .catch(console.log);
+          }
+        })
+        .catch(console.log);
+    }
+  }, [props.currentProject?.id])
 
-  // TODO: Need to handle the following cases. Do a deep rearchitecture (Prov -> Dashboard?) if need be:
-  // 1. Make sure clicking cluster in drawer shows cluster-dashboard
-  // 2. Make sure switching projects shows appropriate initial view (dashboard || provisioner)
-  // 3. Make sure initializing from URL (DO oauth) displays the appropriate initial view
-  componentDidUpdate(prevProps: PropsType) {
+  useEffect(() => {
     if (
-      !this.context.hasFinishedOnboarding &&
-      prevProps.match.url !== this.props.match.url &&
-      this.props.history.location.pathname &&
-      !this.props.history.location.pathname.includes("onboarding") &&
-      !this.props.history.location.pathname.includes("new-project") &&
-      !this.props.history.location.pathname.includes("project-settings")
+      !hasFinishedOnboarding &&
+      props.history.location.pathname &&
+      !props.history.location.pathname.includes("onboarding") &&
+      !props.history.location.pathname.includes("new-project") &&
+      !props.history.location.pathname.includes("project-settings")
     ) {
-      this.context.setCurrentModal("RedirectToOnboardingModal");
+      setCurrentModal("RedirectToOnboardingModal");
     }
+  }, [props.match.url]);
 
-    if (prevProps.currentProject?.id !== this.props.currentProject?.id) {
-      this.checkOnboarding();
-
-      if (!process.env.DISABLE_BILLING) {
-        this.checkIfProjectHasBilling(this?.context?.currentProject?.id)
-          .then((isBillingEnabled) => {
-            if (isBillingEnabled) {
-              api
-                .getUsage(
-                  "<token>",
-                  {},
-                  { project_id: this.context?.currentProject?.id }
-                )
-                .then((res) => {
-                  const usage = res.data;
-                  this.context.setUsage(usage);
-                  if (usage.exceeded) {
-                    this.context.setCurrentModal("UsageWarningModal", {
-                      usage,
-                    });
-                  }
-                })
-                .catch(console.log);
-            }
-          })
-          .catch(console.log);
-      }
+  const prevCurrentCluster: any = useRef();
+  useEffect(() => {
+    if (!prevCurrentCluster.current && props.currentCluster) {
+      getMetadata();
     }
 
-    if (
-      prevProps.currentProject !== this.props.currentProject ||
-      (!prevProps.currentCluster && this.props.currentCluster)
-    ) {
-      this.getMetadata();
-    }
-  }
+    // Store previous value (legacy retrofit)
+    prevCurrentCluster.current = props.currentCluster;
+  }, [props.currentCluster]);
 
-  projectOverlayCall = async () => {
-    let { user, setProjects, setCurrentProject } = this.context;
+  const projectOverlayCall = async () => {
     try {
-      const res = await api.getProjects("<token>", {}, { id: user.userId });
+      const res = await api.getProjects(
+        "<token>",
+        {},
+        { id: user.userId }
+      );
       if (!res.data) {
-        this.context.setCurrentModal(null, null);
+        setCurrentModal(null, null);
         return;
       }
 
       setProjects(res.data);
       if (!res.data.length) {
-        setCurrentProject(null, () => this.redirectToNewProject());
+        setCurrentProject(null, () => redirectToNewProject());
       } else {
         setCurrentProject(res.data[0]);
       }
-      this.context.setCurrentModal(null, null);
+      setCurrentModal(null, null);
     } catch (error) {
-      /** @todo Centralize with error handler */
       console.log(error);
     }
   };
 
-  handleDelete = async () => {
-    let { setCurrentModal, currentProject } = this.context;
+  const handleDelete = async () => {
     localStorage.removeItem(currentProject.id + "-cluster");
     try {
-      await api.deleteProject("<token>", {}, { id: currentProject?.id });
-      this.projectOverlayCall();
+      await api.deleteProject(
+        "<token>",
+        {},
+        { id: currentProject?.id }
+      );
+      projectOverlayCall();
     } catch (error) {
-      /** @todo Centralize with error handler */
       console.log(error);
     }
 
@@ -334,189 +343,142 @@ class Home extends Component<PropsType, StateType> {
       console.log(error);
     }
     setCurrentModal(null, null);
-    pushFiltered(this.props, "/dashboard", []);
+    pushFiltered(props, "/dashboard", []);
   };
 
-  redirectToNewProject = () => {
-    pushFiltered(this.props, "/new-project", ["project_id"]);
-  };
-
-  redirectToOnboarding = () => {
-    pushFiltered(this.props, "/onboarding", []);
-  };
-
-  render() {
-    let {
-      currentModal,
-      setCurrentModal,
-      currentProject,
-      currentOverlay,
-      projects,
-    } = this.context;
-
-    const { cluster, baseRoute } = this.props.match.params as any;
-    return (
-      <StyledHome>
-        <ModalHandler
-          setRefreshClusters={(x) => this.setState({ forceRefreshClusters: x })}
+  const { cluster, baseRoute } = props.match.params as any;
+  return (
+    <StyledHome>
+      <ModalHandler setRefreshClusters={setForceRefreshClusters} />
+      {currentOverlay && (
+        <ConfirmOverlay
+          show={true}
+          message={currentOverlay.message}
+          onYes={currentOverlay.onYes}
+          onNo={currentOverlay.onNo}
+        />
+      )}
+
+      {/* Render sidebar when there's at least one project */}
+      {projects?.length > 0 && baseRoute !== "new-project" ? (
+        <Sidebar
+          key="sidebar"
+          forceSidebar={forceSidebar}
+          setWelcome={setShowWelcome}
+          currentView={props.currentRoute}
+          forceRefreshClusters={forceRefreshClusters}
+          setRefreshClusters={setForceRefreshClusters}
+        />
+      ) : (
+        <DiscordButton href="https://discord.gg/34n7NN7FJ7" target="_blank">
+          <Icon src={discordLogo} />
+          Join Our Discord
+        </DiscordButton>
+      )}
+
+      <ViewWrapper id="HomeViewWrapper">
+        <Navbar
+          logOut={props.logOut}
+          currentView={props.currentRoute} // For form feedback
         />
-        {currentOverlay && (
-          <ConfirmOverlay
-            show={true}
-            message={currentOverlay.message}
-            onYes={currentOverlay.onYes}
-            onNo={currentOverlay.onNo}
-          />
-        )}
-
-        {/* Render sidebar when there's at least one project */}
-        {projects?.length > 0 && baseRoute !== "new-project" ? (
-          <Sidebar
-            key="sidebar"
-            forceSidebar={this.state.forceSidebar}
-            setWelcome={(x: boolean) => this.setState({ showWelcome: x })}
-            currentView={this.props.currentRoute}
-            forceRefreshClusters={this.state.forceRefreshClusters}
-            setRefreshClusters={(x: boolean) =>
-              this.setState({ forceRefreshClusters: x })
-            }
-          />
-        ) : (
-          <>
-            <DiscordButton href="https://discord.gg/34n7NN7FJ7" target="_blank">
-              <Icon src={discordLogo} />
-              Join Our Discord
-            </DiscordButton>
-            {/* This should only be shown on the first render of the app */}
-            {/* this.state.showWelcomeForm &&
-              localStorage.getItem("welcomed") != "true" &&
-              projects?.length === 0 && (
-                <>
-                  <WelcomeForm
-                    closeForm={() => this.setState({ showWelcomeForm: false })}
-                  />
-                  <Navbar
-                    logOut={this.props.logOut}
-                    currentView={this.props.currentRoute} // For form feedback
-                  />
-                </>
-              ) */}
-          </>
-        )}
-
-        <ViewWrapper id="HomeViewWrapper">
-          <Navbar
-            logOut={this.props.logOut}
-            currentView={this.props.currentRoute} // For form feedback
-          />
 
-          <Switch>
-            <Route
-              path="/new-project"
-              render={() => {
-                return <NewProjectFC />;
-              }}
-            ></Route>
-            <Route
-              path="/onboarding"
-              render={() => {
-                return <Onboarding />;
-              }}
-            />
-            {this.context?.user?.isPorterUser ||
-            overrideInfraTabEnabled({
-              projectID: this.context?.currentProject?.id,
-            }) ? (
-              <Route
-                path="/infrastructure"
-                render={() => {
-                  return (
-                    <DashboardWrapper>
-                      <InfrastructureRouter />
-                    </DashboardWrapper>
-                  );
-                }}
-              />
-            ) : null}
+        <Switch>
+          <Route
+            path="/new-project"
+            render={() => {
+              return <NewProjectFC />;
+            }}
+          ></Route>
+          <Route
+            path="/onboarding"
+            render={() => {
+              return <Onboarding />;
+            }}
+          />
+          {user?.isPorterUser || overrideInfraTabEnabled({
+            projectID: currentProject?.id,
+          }) ? (
             <Route
-              path="/dashboard"
+              path="/infrastructure"
               render={() => {
                 return (
                   <DashboardWrapper>
-                    <Dashboard
-                      projectId={this.context.currentProject?.id}
-                      setRefreshClusters={(x: boolean) =>
-                        this.setState({ forceRefreshClusters: x })
-                      }
-                    />
+                    <InfrastructureRouter />
                   </DashboardWrapper>
                 );
               }}
             />
-            <Route
-              path={[
-                "/cluster-dashboard",
-                "/applications",
-                "/jobs",
-                "/env-groups",
-                "/databases",
-                "/preview-environments",
-                "/stacks",
-              ]}
-              render={() => {
-                let { currentCluster } = this.context;
-                if (currentCluster?.id === -1) {
-                  return <Loading />;
-                } else if (!currentCluster || !currentCluster.name) {
-                  return (
-                    <DashboardWrapper>
-                      <PageNotFound />
-                    </DashboardWrapper>
-                  );
-                }
+          ) : null}
+          <Route
+            path="/dashboard"
+            render={() => {
+              return (
+                <DashboardWrapper>
+                  <Dashboard
+                    projectId={currentProject?.id}
+                    setRefreshClusters={setForceRefreshClusters}
+                  />
+                </DashboardWrapper>
+              );
+            }}
+          />
+          <Route
+            path={[
+              "/cluster-dashboard",
+              "/applications",
+              "/jobs",
+              "/env-groups",
+              "/databases",
+              "/preview-environments",
+              "/stacks",
+            ]}
+            render={() => {
+              if (currentCluster?.id === -1) {
+                return <Loading />;
+              } else if (!currentCluster || !currentCluster.name) {
                 return (
                   <DashboardWrapper>
-                    <ClusterDashboard
-                      currentCluster={currentCluster}
-                      setSidebar={(x: boolean) =>
-                        this.setState({ forceSidebar: x })
-                      }
-                      currentView={this.props.currentRoute}
-                      // setCurrentView={(x: string) => this.setState({ currentView: x })}
-                    />
+                    <PageNotFound />
                   </DashboardWrapper>
                 );
-              }}
-            />
-            <Route
-              path={"/integrations"}
-              render={() => <GuardedIntegrations />}
-            />
-            <Route
-              path={"/project-settings"}
-              render={() => <GuardedProjectSettings />}
-            />
-            <Route path={"*"} render={() => <LaunchWrapper />} />
-          </Switch>
-        </ViewWrapper>
-
-        <ConfirmOverlay
-          show={currentModal === "UpdateProjectModal"}
-          message={
-            currentProject
-              ? `Are you sure you want to delete ${currentProject.name}?`
-              : ""
-          }
-          onYes={this.handleDelete}
-          onNo={() => setCurrentModal(null, null)}
-        />
-      </StyledHome>
-    );
-  }
+              }
+              return (
+                <DashboardWrapper>
+                  <ClusterDashboard
+                    currentCluster={currentCluster}
+                    setSidebar={setForceSidebar}
+                    currentView={props.currentRoute}
+                  />
+                </DashboardWrapper>
+              );
+            }}
+          />
+          <Route
+            path={"/integrations"}
+            render={() => <GuardedIntegrations />}
+          />
+          <Route
+            path={"/project-settings"}
+            render={() => <GuardedProjectSettings />}
+          />
+          <Route path={"*"} render={() => <LaunchWrapper />} />
+        </Switch>
+      </ViewWrapper>
+
+      <ConfirmOverlay
+        show={currentModal === "UpdateProjectModal"}
+        message={
+          currentProject
+            ? `Are you sure you want to delete ${currentProject.name}?`
+            : ""
+        }
+        onYes={handleDelete}
+        onNo={() => setCurrentModal(null, null)}
+      />
+    </StyledHome>
+  );
 }
 
-Home.contextType = Context;
-
 export default withRouter(withAuth(Home));
 
 const ViewWrapper = styled.div`