Просмотр исходного кода

Merge branch 'nico/new-onboarding-flow' of github.com:porter-dev/porter into onboarding-flow-styling

jnfrati 4 лет назад
Родитель
Сommit
60792c274f

+ 4 - 7
dashboard/src/main/Main.tsx

@@ -45,13 +45,10 @@ export default class Main extends Component<PropsType, StateType> {
       .checkAuth("", {}, {})
       .then((res) => {
         if (res && res?.data) {
-          Cohere.identify(
-            res?.data?.id, 
-            {
-              displayName: res?.data?.email,
-              email: res?.data?.email, 
-            }
-          );
+          Cohere.identify(res?.data?.id, {
+            displayName: res?.data?.email,
+            email: res?.data?.email,
+          });
           setUser(res?.data?.id, res?.data?.email);
           this.setState({
             isLoggedIn: true,

+ 146 - 204
dashboard/src/main/home/Home.tsx

@@ -1,5 +1,5 @@
 import React, { Component } from "react";
-import { RouteComponentProps, Switch, withRouter } from "react-router";
+import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
 import styled from "styled-components";
 
 import api from "shared/api";
@@ -26,6 +26,7 @@ import { withAuth, WithAuthProps } from "shared/auth/AuthorizationHoc";
 import discordLogo from "../../assets/discord.svg";
 import Onboarding from "./onboarding/Onboarding";
 import ModalHandler from "./ModalHandler";
+import { NewProjectFC } from "./new-project/NewProject";
 
 // Guarded components
 const GuardedProjectSettings = fakeGuardedRoute("settings", "", [
@@ -135,7 +136,7 @@ class Home extends Component<PropsType, StateType> {
       .then((res) => {
         if (res.data) {
           if (res.data.length === 0) {
-            pushFiltered(this.props, "/onboarding/new-project", ["project_id"]);
+            this.redirectToNewProject();
           } else if (res.data.length > 0 && !currentProject) {
             setProjects(res.data);
 
@@ -167,92 +168,9 @@ class Home extends Component<PropsType, StateType> {
       .catch(console.log);
   };
 
-  provisionDOCR = async (
-    integrationId: number,
-    tier: string,
-    callback?: any
-  ) => {
-    console.log("Provisioning DOCR...");
-    await api.createDOCR(
-      "<token>",
-      {
-        do_integration_id: integrationId,
-        docr_name: this.props.currentProject.name,
-        docr_subscription_tier: tier,
-      },
-      {
-        project_id: this.props.currentProject.id,
-      }
-    );
-    return callback();
-  };
-
-  provisionDOKS = async (
-    integrationId: number,
-    region: string,
-    clusterName: string
-  ) => {
-    console.log("Provisioning DOKS...");
-    await api.createDOKS(
-      "<token>",
-      {
-        do_integration_id: integrationId,
-        doks_name: clusterName,
-        do_region: region,
-      },
-      {
-        project_id: this.props.currentProject.id,
-      }
-    );
-    return pushFiltered(this.props, "/dashboard", ["project_id"], {
-      tab: "provisioner",
-    });
-  };
-
-  checkDO = () => {
-    let { currentProject } = this.props;
-    if (this.state.handleDO && currentProject?.id) {
-      api
-        .getOAuthIds(
-          "<token>",
-          {},
-          {
-            project_id: currentProject.id,
-          }
-        )
-        .then((res) => {
-          let tgtIntegration = res.data.find((integration: any) => {
-            return integration.client === "do";
-          });
-          let queryString = window.location.search;
-          let urlParams = new URLSearchParams(queryString);
-          let tier = urlParams.get("tier");
-          let region = urlParams.get("region");
-          let clusterName = urlParams.get("cluster_name");
-          let infras = urlParams.getAll("infras");
-          if (infras.length === 2) {
-            this.provisionDOCR(tgtIntegration.id, tier, () => {
-              this.provisionDOKS(tgtIntegration.id, region, clusterName);
-            });
-          } else if (infras[0] === "docr") {
-            this.provisionDOCR(tgtIntegration.id, tier, () => {
-              pushFiltered(this.props, "/dashboard", ["project_id"], {
-                tab: "provisioner",
-              });
-            });
-          } else {
-            this.provisionDOKS(tgtIntegration.id, region, clusterName);
-          }
-        })
-        .catch(console.log);
-      this.setState({ handleDO: false });
-    }
-  };
-
   componentDidMount() {
+    this.checkOnboarding();
     let { match } = this.props;
-    let params = match.params as any;
-    let { cluster } = params;
 
     let { user } = this.context;
 
@@ -274,12 +192,7 @@ class Home extends Component<PropsType, StateType> {
       this.context.setCurrentError(err);
     }
 
-    let provision = urlParams.get("provision");
     let defaultProjectId = parseInt(urlParams.get("project_id"));
-    if (provision === "do") {
-      this.setState({ handleDO: true });
-      this.checkDO();
-    }
 
     this.setState({ ghRedirect: urlParams.get("gh_oauth") !== null });
     urlParams.delete("gh_oauth");
@@ -304,6 +217,17 @@ class Home extends Component<PropsType, StateType> {
     }
   }
 
+  async checkOnboarding() {
+    try {
+      const project_id = this.context?.currentProject?.id;
+      const res = await api.getOnboardingState("<token>", {}, { project_id });
+
+      if (res?.data && res?.data.current_step !== "clean_up") {
+        this.redirectToOnboarding();
+      }
+    } catch (error) {}
+  }
+
   // 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)
@@ -338,132 +262,80 @@ class Home extends Component<PropsType, StateType> {
       prevProps.currentProject !== this.props.currentProject ||
       (!prevProps.currentCluster && this.props.currentCluster)
     ) {
-      if (this.state.handleDO) {
-        this.checkDO();
-      } else {
-        this.initializeView();
-        this.getMetadata();
-      }
+      this.initializeView();
+      this.getMetadata();
     }
   }
 
-  // TODO: move into ClusterDashboard
-  renderDashboard = () => {
-    let { currentCluster } = this.context;
-    if (currentCluster?.id === -1) {
-      return <Loading />;
-    } else if (!currentCluster || !currentCluster.name) {
-      return (
-        <DashboardWrapper>
-          <PageNotFound />
-        </DashboardWrapper>
-      );
-    }
-    return (
-      <DashboardWrapper>
-        <ClusterDashboard
-          currentCluster={currentCluster}
-          setSidebar={(x: boolean) => this.setState({ forceSidebar: x })}
-          currentView={this.props.currentRoute}
-          // setCurrentView={(x: string) => this.setState({ currentView: x })}
-        />
-      </DashboardWrapper>
-    );
-  };
+  projectOverlayCall = async () => {
+    let { user, setProjects, setCurrentProject } = this.context;
+    try {
+      const res = await api.getProjects("<token>", {}, { id: user.userId });
+      if (!res.data) {
+        this.context.setCurrentModal(null, null);
+        return;
+      }
 
-  renderContents = () => {
-    let currentView = this.props.currentRoute;
-
-    if (this.context.currentProject && currentView !== "onboarding") {
-      if (
-        currentView === "cluster-dashboard" ||
-        currentView === "applications" ||
-        currentView === "jobs" ||
-        currentView === "env-groups"
-      ) {
-        return this.renderDashboard();
-      } else if (currentView === "dashboard") {
-        return (
-          <DashboardWrapper>
-            <Dashboard
-              projectId={this.context.currentProject?.id}
-              setRefreshClusters={(x: boolean) =>
-                this.setState({ forceRefreshClusters: x })
-              }
-            />
-          </DashboardWrapper>
-        );
-      } else if (currentView === "integrations") {
-        return <GuardedIntegrations />;
-      } else if (currentView === "project-settings") {
-        return <GuardedProjectSettings />;
+      setProjects(res.data);
+      if (!res.data.length) {
+        setCurrentProject(null, () => this.redirectToNewProject());
+      } else {
+        setCurrentProject(res.data[0]);
       }
-      return <Templates />;
-    } else if (currentView === "onboarding") {
-      return <Onboarding />;
+      this.context.setCurrentModal(null, null);
+    } catch (error) {
+      /** @todo Centralize with error handler */
+      console.log(error);
     }
   };
 
-  projectOverlayCall = () => {
-    let { user, setProjects, setCurrentProject } = this.context;
-    api
-      .getProjects("<token>", {}, { id: user.userId })
-      .then((res) => {
-        if (res.data) {
-          setProjects(res.data);
-          if (res.data.length > 0) {
-            setCurrentProject(res.data[0]);
-          } else {
-            setCurrentProject(null, () =>
-              pushFiltered(this.props, "/onboarding/new-project", [
-                "project_id",
-              ])
-            );
-          }
-          this.context.setCurrentModal(null, null);
-        }
-      })
-      .catch(console.log);
-  };
-
-  handleDelete = () => {
+  handleDelete = async () => {
     let { setCurrentModal, currentProject } = this.context;
     localStorage.removeItem(currentProject.id + "-cluster");
-    api
-      .deleteProject("<token>", {}, { id: currentProject.id })
-      .then(this.projectOverlayCall)
-      .catch(console.log);
+    try {
+      await api.deleteProject("<token>", {}, { id: currentProject?.id });
+      this.projectOverlayCall();
+    } catch (error) {
+      /** @todo Centralize with error handler */
+      console.log(error);
+    }
 
-    // Loop through and delete infra of all clusters we've provisioned
-    api
-      .getClusters("<token>", {}, { id: currentProject.id })
-      .then((res) => {
-        // TODO: promise.map
-        for (var i = 0; i < res.data.length; i++) {
-          let cluster = res.data[i];
-          if (!cluster.infra_id) continue;
-
-          // Handle destroying infra we've provisioned
-          api
-            .destroyInfra(
-              "<token>",
-              { name: cluster.name },
-              {
-                project_id: currentProject.id,
-                infra_id: cluster.infra_id,
-              }
-            )
-            .then(() =>
-              console.log("destroyed provisioned infra:", cluster.infra_id)
-            )
-            .catch(console.log);
+    try {
+      const res = await api.getClusters<
+        {
+          infra_id?: number;
+          name: string;
+        }[]
+      >("<token>", {}, { id: currentProject?.id });
+
+      const destroyInfraPromises = res.data.map((cluster) => {
+        if (!cluster.infra_id) {
+          return undefined;
         }
-      })
-      .catch(console.log);
+
+        return api.destroyInfra(
+          "<token>",
+          { name: cluster.name },
+          { project_id: currentProject.id, infra_id: cluster.infra_id }
+        );
+      });
+
+      await Promise.all(destroyInfraPromises);
+    } catch (error) {
+      console.log(error);
+    }
     setCurrentModal(null, null);
     pushFiltered(this.props, "/dashboard", []);
   };
 
+  redirectToNewProject = () => {
+    pushFiltered(this.props, "/new-project", ["project_id"]);
+  };
+
+  redirectToOnboarding = () => {
+    pushFiltered(this.props, "/onboarding", ["project_id"]);
+  };
+
   render() {
     let {
       currentModal,
@@ -473,7 +345,7 @@ class Home extends Component<PropsType, StateType> {
       projects,
     } = this.context;
 
-    const { cluster } = this.props.match.params as any;
+    const { cluster, baseRoute } = this.props.match.params as any;
     return (
       <StyledHome>
         <ModalHandler
@@ -489,7 +361,7 @@ class Home extends Component<PropsType, StateType> {
         )}
 
         {/* Render sidebar when there's at least one project */}
-        {projects?.length > 0 && cluster !== "new-project" ? (
+        {projects?.length > 0 && baseRoute !== "new-project" ? (
           <Sidebar
             key="sidebar"
             forceSidebar={this.state.forceSidebar}
@@ -528,7 +400,77 @@ class Home extends Component<PropsType, StateType> {
             logOut={this.props.logOut}
             currentView={this.props.currentRoute} // For form feedback
           />
-          {this.renderContents()}
+
+          <Switch>
+            <Route
+              path="/new-project"
+              render={() => {
+                return <NewProjectFC />;
+              }}
+            ></Route>
+            <Route
+              path="/onboarding"
+              render={() => {
+                return <Onboarding></Onboarding>;
+              }}
+            />
+            <Route
+              path="/dashboard"
+              render={() => {
+                return (
+                  <DashboardWrapper>
+                    <Dashboard
+                      projectId={this.context.currentProject?.id}
+                      setRefreshClusters={(x: boolean) =>
+                        this.setState({ forceRefreshClusters: x })
+                      }
+                    />
+                  </DashboardWrapper>
+                );
+              }}
+            />
+            <Route
+              path={[
+                "/cluster-dashboard",
+                "/applications",
+                "/jobs",
+                "/env-groups",
+              ]}
+              render={() => {
+                let { currentCluster } = this.context;
+                if (currentCluster?.id === -1) {
+                  return <Loading />;
+                } else if (!currentCluster || !currentCluster.name) {
+                  return (
+                    <DashboardWrapper>
+                      <PageNotFound />
+                    </DashboardWrapper>
+                  );
+                }
+                return (
+                  <DashboardWrapper>
+                    <ClusterDashboard
+                      currentCluster={currentCluster}
+                      setSidebar={(x: boolean) =>
+                        this.setState({ forceSidebar: x })
+                      }
+                      currentView={this.props.currentRoute}
+                      // setCurrentView={(x: string) => this.setState({ currentView: x })}
+                    />
+                  </DashboardWrapper>
+                );
+              }}
+            />
+            <Route
+              path={"/integrations"}
+              render={() => <GuardedIntegrations />}
+            />
+            <Route
+              path={"/project-settings"}
+              render={() => <GuardedProjectSettings />}
+            />
+            <Route path={"*"} render={() => <Templates />} />
+          </Switch>
         </ViewWrapper>
 
         <ConfirmOverlay

+ 173 - 56
dashboard/src/main/home/new-project/NewProject.tsx

@@ -1,4 +1,10 @@
-import React, { Component } from "react";
+import React, { useContext, useMemo, useState } from "react";
+
+import { useRouting } from "shared/routing";
+import api from "shared/api";
+import SaveButton from "components/SaveButton";
+
+import backArrow from "assets/back_arrow.png";
 import styled from "styled-components";
 
 import gradient from "assets/gradient.png";
@@ -7,79 +13,171 @@ import { isAlphanumeric } from "shared/common";
 
 import InputRow from "components/form-components/InputRow";
 import Helper from "components/form-components/Helper";
-import ProvisionerSettings from "../provisioner/ProvisionerSettings";
 import TitleSection from "components/TitleSection";
 
-type PropsType = {};
-
-type StateType = {
-  projectName: string;
-  selectedProvider: string | null;
+type ValidationError = {
+  hasError: boolean;
+  description?: string;
 };
 
-export default class NewProject extends Component<PropsType, StateType> {
-  state = {
-    projectName: "",
-    selectedProvider: null as string | null,
+export const NewProjectFC = () => {
+  const { user, setProjects, setCurrentProject } = useContext(Context);
+  const { pushFiltered } = useRouting();
+  const [buttonStatus, setButtonStatus] = useState("");
+  const [name, setName] = useState("");
+  const { projects } = useContext(Context);
+
+  const isFirstProject = useMemo(() => {
+    return !(projects?.length >= 1);
+  }, [projects]);
+
+  const validateProjectName = (): ValidationError => {
+    if (name === "") {
+      return {
+        hasError: true,
+        description: "The name cannot be empty. Please fill the input.",
+      };
+    }
+    if (!isAlphanumeric(name)) {
+      return {
+        hasError: true,
+        description:
+          'Please be sure that the text is alphanumeric. (lowercase letters, numbers, and "-" only)',
+      };
+    }
+    if (name.length > 25) {
+      return {
+        hasError: true,
+        description:
+          "The length of the name cannot be more than 25 characters.",
+      };
+    }
+
+    return {
+      hasError: false,
+    };
   };
 
-  componentDidMount() {
-    window.analytics.track("provision_new-project", {
-      userId: this.context.user?.id,
-    });
-  }
+  const createProject = async () => {
+    const projectName = name;
+    setButtonStatus("loading");
+    const validation = validateProjectName();
+
+    if (validation.hasError) {
+      setButtonStatus(validation.description);
+      return;
+    }
+
+    try {
+      const project = await api
+        .createProject("<token>", { name: projectName }, {})
+        .then((res) => res.data);
 
-  render() {
-    let { capabilities } = this.context;
-    let { projectName } = this.state;
-    return (
-      <StyledNewProject>
+      const projectList = await api
+        .getProjects(
+          "<token>",
+          {},
+          {
+            id: user.userId,
+          }
+        )
+        .then((res) => res.data);
+      setProjects(projectList);
+      setCurrentProject(project);
+      setButtonStatus("successful");
+    } catch (error) {
+      setButtonStatus("Couldn't create project, try again.");
+      console.log(error);
+    }
+  };
+
+  return (
+    <StyledNewProject>
+      {!isFirstProject && (
+        <BackButton
+          onClick={() => {
+            pushFiltered("/dashboard", []);
+          }}
+        >
+          <BackButtonImg src={backArrow} />
+        </BackButton>
+      )}
+      <FadeWrapper>
         <TitleSection>New Project</TitleSection>
+      </FadeWrapper>
+      <FadeWrapper delay="0.7s">
         <Helper>
           Project name
-          <Warning
-            highlight={
-              !isAlphanumeric(this.state.projectName) &&
-              this.state.projectName !== ""
-            }
-          >
+          <Warning highlight={validateProjectName().hasError}>
             (lowercase letters, numbers, and "-" only)
           </Warning>
           <Required>*</Required>
         </Helper>
+      </FadeWrapper>
+      <SlideWrapper delay="1.2s">
         <InputWrapper>
           <ProjectIcon>
             <ProjectImage src={gradient} />
-            <Letter>
-              {this.state.projectName
-                ? this.state.projectName[0].toUpperCase()
-                : "-"}
-            </Letter>
+            <Letter>{name ? name.toUpperCase().substring(0, 1) : "-"}</Letter>
           </ProjectIcon>
           <InputRow
             type="string"
-            value={this.state.projectName}
-            setValue={(x: string) => this.setState({ projectName: x })}
+            value={name}
+            setValue={(x: string) => {
+              setButtonStatus("");
+              setName(x);
+            }}
             placeholder="ex: perspective-vortex"
             width="470px"
+            disabled={buttonStatus === "loading"}
           />
         </InputWrapper>
-        <ProvisionerSettings
-          isInNewProject={true}
-          projectName={projectName}
-          provisioner={capabilities?.provisioner}
+        <NewProjectSaveButton
+          text="Create Project"
+          disabled={false}
+          onClick={createProject}
+          status={buttonStatus}
+          makeFlush={true}
+          clearPosition={true}
+          statusPosition="right"
+          saveText="Creating project..."
+          successText="Project created successfully!"
         />
-        <Br />
-      </StyledNewProject>
-    );
+      </SlideWrapper>
+    </StyledNewProject>
+  );
+};
+
+const FadeWrapper = styled.div<{ delay?: string }>`
+  opacity: 0;
+  animation: fadeIn 0.5s ${(props) => props.delay || "0.2s"};
+  animation-fill-mode: forwards;
+
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
   }
-}
+`;
 
-NewProject.contextType = Context;
+const SlideWrapper = styled.div<{ delay?: string }>`
+  opacity: 0;
+  animation: slideIn 0.7s 1.3s;
+  animation-fill-mode: forwards;
 
-const Br = styled.div`
-  width: 100%;
-  height: 100px;
+  @keyframes slideIn {
+    from {
+      opacity: 0;
+      transform: translateX(30px);
+    }
+    to {
+      opacity: 1;
+      transform: translateX(0);
+    }
+  }
 `;
 
 const Required = styled.div`
@@ -134,16 +232,6 @@ const Warning = styled.span`
     props.makeFlush ? "" : "5px"};
 `;
 
-const Title = styled.div`
-  font-size: 20px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-  color: #ffffff;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-`;
-
 const StyledNewProject = styled.div`
   width: calc(90% - 130px);
   min-width: 300px;
@@ -151,3 +239,32 @@ const StyledNewProject = styled.div`
   background: red;
   margin-top: calc(50vh - 340px);
 `;
+
+const NewProjectSaveButton = styled(SaveButton)`
+  margin-top: 24px;
+`;
+
+const BackButton = styled.div`
+  margin-bottom: 24px;
+  display: flex;
+  width: 36px;
+  cursor: pointer;
+  height: 36px;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #ffffff55;
+  border-radius: 100px;
+  background: #ffffff11;
+
+  :hover {
+    background: #ffffff22;
+    > img {
+      opacity: 1;
+    }
+  }
+`;
+
+const BackButtonImg = styled.img`
+  width: 16px;
+  opacity: 0.75;
+`;

+ 4 - 15
dashboard/src/main/home/onboarding/Onboarding.tsx

@@ -13,9 +13,11 @@ const Onboarding = () => {
   useSteps();
 
   useEffect(() => {
-    let sub = devtools(OFState, "Onboarding flow state");
+    let unsub = devtools(OFState, "Onboarding flow state");
     return () => {
-      sub();
+      if (typeof unsub === "function") {
+        unsub();
+      }
     };
   }, []);
 
@@ -106,19 +108,6 @@ const Onboarding = () => {
     };
   }, [context.currentProject?.id]);
 
-  // useEffect(() => {
-  //   if (snap.StepHandler.finishedOnboarding) {
-  //     OFState.actions.clearState();
-  //     pushFiltered("/dashboard", []);
-  //   } else if (snap.StepHandler?.currentStep?.url !== location.pathname) {
-  //     pushFiltered(snap.StepHandler.currentStep.url, []);
-  //   }
-  // }, [
-  //   location.pathname,
-  //   snap.StepHandler?.currentStep?.url,
-  //   snap.StepHandler?.finishedOnboarding,
-  // ]);
-
   return (
     <StyledOnboarding>
       <Routes />

+ 0 - 18
dashboard/src/main/home/onboarding/Routes.tsx

@@ -1,32 +1,14 @@
 import React from "react";
 import { Route, Switch } from "react-router";
-import { useSnapshot } from "valtio";
 import { OFState } from "./state";
-import ConnectRegistry from "./steps/ConnectRegistry/ConnectRegistry";
 import ConnectRegistryWrapper from "./steps/ConnectRegistry/ConnectRegistryWrapper";
 import ConnectSource from "./steps/ConnectSource";
-import { NewProjectFC } from "./steps/NewProject";
-import ProvisionResources from "./steps/ProvisionResources/ProvisionResources";
 import ProvisionResourcesWrapper from "./steps/ProvisionResources/ProvisionResourcesWrapper";
 
-const handleContinue = (data?: any) => {
-  OFState.actions.nextStep("continue", data);
-};
-
-const handleSkip = () => {
-  OFState.actions.nextStep("skip");
-};
-
 export const Routes = () => {
-  const snap = useSnapshot(OFState);
   return (
     <>
       <Switch>
-        <Route path={`/onboarding/new-project`}>
-          <NewProjectFC
-            onSuccess={(data) => OFState.actions.nextStep("continue", data)}
-          />
-        </Route>
         <Route path={`/onboarding/source`}>
           <ConnectSource
             onSuccess={(data) => OFState.actions.nextStep("continue", data)}

+ 0 - 271
dashboard/src/main/home/onboarding/steps/NewProject.tsx

@@ -1,271 +0,0 @@
-import React, { useContext, useMemo, useState } from "react";
-import styled from "styled-components";
-
-import gradient from "assets/gradient.png";
-import { isAlphanumeric } from "shared/common";
-
-import InputRow from "components/form-components/InputRow";
-import Helper from "components/form-components/Helper";
-import TitleSection from "components/TitleSection";
-import { useRouting } from "shared/routing";
-import { Context } from "shared/Context";
-import api from "shared/api";
-import SaveButton from "components/SaveButton";
-
-import backArrow from "assets/back_arrow.png";
-
-type ValidationError = {
-  hasError: boolean;
-  description?: string;
-};
-
-export const NewProjectFC: React.FC<{
-  onSuccess: (projectData: { id: number; name: string }) => void;
-}> = ({ onSuccess }) => {
-  const { user, setProjects, setCurrentProject } = useContext(Context);
-  const { pushFiltered } = useRouting();
-  const [buttonStatus, setButtonStatus] = useState("");
-  const [name, setName] = useState("");
-  const { projects } = useContext(Context);
-
-  const isFirstProject = useMemo(() => {
-    return !(projects?.length >= 1);
-  }, [projects]);
-
-  const validateProjectName = (): ValidationError => {
-    if (name === "") {
-      return {
-        hasError: true,
-        description: "The name cannot be empty. Please fill the input.",
-      };
-    }
-    if (!isAlphanumeric(name)) {
-      return {
-        hasError: true,
-        description:
-          'Please be sure that the text is alphanumeric. (lowercase letters, numbers, and "-" only)',
-      };
-    }
-    if (name.length > 25) {
-      return {
-        hasError: true,
-        description:
-          "The length of the name cannot be more than 25 characters.",
-      };
-    }
-
-    return {
-      hasError: false,
-    };
-  };
-
-  const createProject = async () => {
-    const projectName = name;
-    setButtonStatus("loading");
-    const validation = validateProjectName();
-
-    if (validation.hasError) {
-      setButtonStatus(validation.description);
-      return;
-    }
-
-    try {
-      const project = await api
-        .createProject("<token>", { name: projectName }, {})
-        .then((res) => res.data);
-
-      const projectList = await api
-        .getProjects(
-          "<token>",
-          {},
-          {
-            id: user.userId,
-          }
-        )
-        .then((res) => res.data);
-      setProjects(projectList);
-      setCurrentProject(project);
-
-      onSuccess({
-        id: project.id,
-        name: project.name,
-      });
-      setButtonStatus("successful");
-    } catch (error) {
-      setButtonStatus("Couldn't create project, try again.");
-      console.log(error);
-    }
-  };
-
-  return (
-    <StyledNewProject>
-      {!isFirstProject && false && (
-        <BackButton
-          onClick={() => {
-            pushFiltered("/dashboard", []);
-          }}
-        >
-          <BackButtonImg src={backArrow} />
-        </BackButton>
-      )}
-      <FadeWrapper>
-        <TitleSection>Create a Project</TitleSection>
-      </FadeWrapper>
-      <FadeWrapper delay="0.7s">
-      <Helper>
-        Project name
-        <Warning highlight={validateProjectName().hasError}>
-          (lowercase letters, numbers, and "-" only)
-        </Warning>
-        <Required>*</Required>
-      </Helper>
-      </FadeWrapper>
-      <SlideWrapper delay="1.2s">
-      <InputWrapper>
-        <ProjectIcon>
-          <ProjectImage src={gradient} />
-          <Letter>{name ? name.toUpperCase().substring(0, 1) : "-"}</Letter>
-        </ProjectIcon>
-        <InputRow
-          type="string"
-          value={name}
-          setValue={(x: string) => {
-            setButtonStatus("");
-            setName(x);
-          }}
-          placeholder="ex: perspective-vortex"
-          width="570px"
-          disabled={buttonStatus === "loading"}
-        />
-      </InputWrapper>
-      <NewProjectSaveButton
-        text="Create Project"
-        disabled={false}
-        onClick={createProject}
-        status={buttonStatus}
-        makeFlush={true}
-        clearPosition={true}
-        statusPosition="right"
-        saveText="Creating project..."
-        successText="Project created successfully!"
-      />
-      </SlideWrapper>
-    </StyledNewProject>
-  );
-};
-
-const FadeWrapper = styled.div<{ delay?: string }>`
-  opacity: 0;
-  animation: fadeIn 0.5s ${(props) => props.delay || "0.2s"};
-  animation-fill-mode: forwards;
-
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const SlideWrapper = styled.div<{ delay?: string }>`
-  opacity: 0;
-  animation: slideIn 0.7s 1.3s;
-  animation-fill-mode: forwards;
-
-  @keyframes slideIn {
-    from {
-      opacity: 0;
-      transform: translateX(30px);
-    }
-    to {
-      opacity: 1;
-      transform: translateX(0);
-    }
-  }
-`;
-
-const StyledNewProject = styled.div`
-`;
-
-const NewProjectSaveButton = styled(SaveButton)`
-  margin-top: 24px;
-`;
-
-const Required = styled.div`
-  margin-left: 8px;
-  color: #fc4976;
-  display: inline-block;
-`;
-
-const Letter = styled.div`
-  height: 100%;
-  width: 100%;
-  position: absolute;
-  background: #00000028;
-  top: 0;
-  left: 0;
-  display: flex;
-  color: white;
-  align-items: center;
-  justify-content: center;
-  font-size: 24px;
-  font-weight: 500;
-  font-family: "Work Sans", sans-serif;
-`;
-
-const ProjectImage = styled.img`
-  width: 100%;
-  height: 100%;
-`;
-
-const ProjectIcon = styled.div`
-  width: 45px;
-  min-width: 45px;
-  height: 45px;
-  border-radius: 5px;
-  overflow: hidden;
-  position: relative;
-  margin-right: 15px;
-  font-weight: 400;
-  margin-top: 9px;
-`;
-
-const InputWrapper = styled.div`
-  display: flex;
-  align-items: center;
-  margin-top: -15px;
-`;
-
-const Warning = styled.span`
-  color: ${(props: { highlight: boolean; makeFlush?: boolean }) =>
-    props.highlight ? "#f5cb42" : ""};
-  margin-left: ${(props: { highlight: boolean; makeFlush?: boolean }) =>
-    props.makeFlush ? "" : "5px"};
-`;
-
-const BackButton = styled.div`
-  margin-bottom: 24px;
-  display: flex;
-  width: 36px;
-  cursor: pointer;
-  height: 36px;
-  align-items: center;
-  justify-content: center;
-  border: 1px solid #ffffff55;
-  border-radius: 100px;
-  background: #ffffff11;
-
-  :hover {
-    background: #ffffff22;
-    > img {
-      opacity: 1;
-    }
-  }
-`;
-
-const BackButtonImg = styled.img`
-  width: 16px;
-  opacity: 0.75;
-`;

+ 2 - 4
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -78,9 +78,7 @@ class ProjectSection extends Component<PropsType, StateType> {
               selected={false}
               lastItem={true}
               onClick={() =>
-                pushFiltered(this.props, "/onboarding/new-project", [
-                  "project_id",
-                ])
+                pushFiltered(this.props, "/new-project", ["project_id"])
               }
             >
               <ProjectIconAlt>+</ProjectIconAlt>
@@ -119,7 +117,7 @@ class ProjectSection extends Component<PropsType, StateType> {
     return (
       <InitializeButton
         onClick={() =>
-          pushFiltered(this.props, "new-project", ["project_id"], {
+          pushFiltered(this.props, "/new-project", ["project_id"], {
             new_project: true,
           })
         }