Przeglądaj źródła

Made basic form flow work

jnfrati 4 lat temu
rodzic
commit
59aff39e3d

+ 53 - 1
dashboard/src/main/home/onboarding/ConnectRegistry/ConnectRegistry.tsx

@@ -1,7 +1,59 @@
+import Helper from "components/form-components/Helper";
+import SaveButton from "components/SaveButton";
+import TitleSection from "components/TitleSection";
 import React from "react";
+import { useRouting } from "shared/routing";
+import styled from "styled-components";
+import { useSnapshot } from "valtio";
+import ProviderSelector from "../components/ProviderSelector";
+
+import { State } from "./ConnectRegistryState";
+import FormFlowWrapper from "./forms/FormFlow";
 
 const ConnectRegistry = () => {
-  return <div></div>;
+  const snap = useSnapshot(State);
+  const { pushFiltered } = useRouting();
+  const nextStep = () => {
+    console.log("Good work boy!");
+  };
+
+  return (
+    <>
+      <TitleSection>Getting Started</TitleSection>
+      <Subtitle>Step 2 of 3</Subtitle>
+      <Helper>
+        {snap.selectedProvider
+          ? "Link to an existing Docker registry. Don't worry if you don't know what this is"
+          : "Link to an existing docker registry or continue"}
+      </Helper>
+      {snap.selectedProvider ? (
+        <FormFlowWrapper nextStep={nextStep} />
+      ) : (
+        <>
+          <ProviderSelector selectProvider={State.actions.selectProvider} />
+          <NextStep
+            text="Continue"
+            disabled={false}
+            onClick={nextStep}
+            status={""}
+            makeFlush={true}
+            clearPosition={true}
+            statusPosition="right"
+            saveText=""
+          />
+        </>
+      )}
+    </>
+  );
 };
 
 export default ConnectRegistry;
+
+const Subtitle = styled(TitleSection)`
+  font-size: 16px;
+  margin-top: 16px;
+`;
+
+const NextStep = styled(SaveButton)`
+  margin-top: 24px;
+`;

+ 33 - 0
dashboard/src/main/home/onboarding/ConnectRegistry/ConnectRegistryState.ts

@@ -0,0 +1,33 @@
+import { proxy } from "valtio";
+import { devtools } from "valtio/utils";
+
+import { SupportedProviders } from "../components/ProviderSelector";
+
+type AllowedSteps = "credentials" | "settings" | "test_connection" | null;
+
+interface ConnectRegistryState {
+  selectedProvider: SupportedProviders | null;
+  currentStep: AllowedSteps;
+  actions: typeof actions;
+}
+
+const actions = {
+  selectProvider(provider: SupportedProviders) {
+    State.selectedProvider = provider;
+  },
+
+  clearState() {
+    State.selectedProvider = null;
+    State.currentStep = "credentials";
+  },
+};
+
+const initialState: ConnectRegistryState = {
+  selectedProvider: null,
+  currentStep: "credentials",
+  actions,
+};
+
+export const State = proxy(initialState);
+
+devtools(State, "ConnectRegistryState");

+ 101 - 0
dashboard/src/main/home/onboarding/ConnectRegistry/forms/FormFlow.tsx

@@ -0,0 +1,101 @@
+import React, { useMemo } from "react";
+import styled from "styled-components";
+import { useSnapshot } from "valtio";
+import { State } from "../ConnectRegistryState";
+import {
+  CredentialsForm as AWSCredentialsForm,
+  SettingsForm as AWSSettingsForm,
+  TestRegistryConnection as AWSTestRegistryConnection,
+} from "./_AWSRegistryForm";
+
+import {
+  CredentialsForm as DOCredentialsForm,
+  SettingsForm as DOSettingsForm,
+  TestRegistryConnection as DOTestRegistryConnection,
+} from "./_DORegistryForm";
+
+import {
+  CredentialsForm as GCPCredentialsForm,
+  SettingsForm as GCPSettingsForm,
+  TestRegistryConnection as GCPTestRegistryConnection,
+} from "./_GCPRegistryForm";
+
+const Forms = {
+  aws: {
+    credentials: AWSCredentialsForm,
+    settings: AWSSettingsForm,
+    test_connection: AWSTestRegistryConnection,
+  },
+  gcp: {
+    credentials: GCPCredentialsForm,
+    settings: GCPSettingsForm,
+    test_connection: GCPTestRegistryConnection,
+  },
+  do: {
+    credentials: DOCredentialsForm,
+    settings: DOSettingsForm,
+    test_connection: DOTestRegistryConnection,
+  },
+};
+
+const FormTitle = {
+  aws: "Amazon Elastic Container Registry (ECR)",
+  gcp: "Google Container Registry (GCR)",
+  do: "Digital Ocean Container Registry",
+};
+
+const FormFlowWrapper: React.FC<{ nextStep: () => void }> = ({ nextStep }) => {
+  const snap = useSnapshot(State);
+
+  const nextFormStep = () => {
+    if (snap.currentStep === "credentials") {
+      State.currentStep = "settings";
+    } else if (snap.currentStep === "settings") {
+      State.currentStep = "test_connection";
+    } else if (snap.currentStep === "test_connection") {
+      nextStep();
+    }
+  };
+
+  const CurrentForm = useMemo(() => {
+    const providerSteps = Forms[snap.selectedProvider];
+    if (!providerSteps) {
+      return null;
+    }
+
+    const currentForm = providerSteps[snap.currentStep];
+    if (!currentForm) {
+      return null;
+    }
+
+    return React.createElement(currentForm, {
+      nextFormStep,
+    });
+  }, [snap.currentStep, snap.selectedProvider]);
+
+  return (
+    <>
+      {FormTitle[snap.selectedProvider]}
+      <Breadcrumb>
+        <Text bold={snap.currentStep === "credentials"}>Credentials</Text>
+        {" > "}
+        <Text bold={snap.currentStep === "settings"}>Settings</Text>
+        {" > "}
+        <Text bold={snap.currentStep === "test_connection"}>
+          Test Connection
+        </Text>
+      </Breadcrumb>
+      {CurrentForm}
+    </>
+  );
+};
+
+export default FormFlowWrapper;
+
+const Text = styled.span<{ bold: boolean }>`
+  font-weight: ${(props) => (props.bold ? "600" : "normal")};
+`;
+
+const Breadcrumb = styled.div`
+  margin: 0 10px;
+`;

+ 178 - 7
dashboard/src/main/home/onboarding/ConnectRegistry/forms/_AWSRegistryForm.tsx

@@ -1,13 +1,184 @@
-import React from "react";
+import InputRow from "components/form-components/InputRow";
+import SelectRow from "components/form-components/SelectRow";
+import SaveButton from "components/SaveButton";
+import React, { useState } from "react";
+import api from "shared/api";
 
-export const CredentialsForm = () => {
-  return <></>;
+const regionOptions = [
+  { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
+  { value: "us-east-2", label: "US East (Ohio) us-east-2" },
+  { value: "us-west-1", label: "US West (N. California) us-west-1" },
+  { value: "us-west-2", label: "US West (Oregon) us-west-2" },
+  { value: "af-south-1", label: "Africa (Cape Town) af-south-1" },
+  { value: "ap-east-1", label: "Asia Pacific (Hong Kong) ap-east-1" },
+  { value: "ap-south-1", label: "Asia Pacific (Mumbai) ap-south-1" },
+  { value: "ap-northeast-2", label: "Asia Pacific (Seoul) ap-northeast-2" },
+  { value: "ap-southeast-1", label: "Asia Pacific (Singapore) ap-southeast-1" },
+  { value: "ap-southeast-2", label: "Asia Pacific (Sydney) ap-southeast-2" },
+  { value: "ap-northeast-1", label: "Asia Pacific (Tokyo) ap-northeast-1" },
+  { value: "ca-central-1", label: "Canada (Central) ca-central-1" },
+  { value: "eu-central-1", label: "Europe (Frankfurt) eu-central-1" },
+  { value: "eu-west-1", label: "Europe (Ireland) eu-west-1" },
+  { value: "eu-west-2", label: "Europe (London) eu-west-2" },
+  { value: "eu-south-1", label: "Europe (Milan) eu-south-1" },
+  { value: "eu-west-3", label: "Europe (Paris) eu-west-3" },
+  { value: "eu-north-1", label: "Europe (Stockholm) eu-north-1" },
+  { value: "me-south-1", label: "Middle East (Bahrain) me-south-1" },
+  { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
+];
+
+export const CredentialsForm: React.FC<{ nextFormStep: () => void }> = ({
+  nextFormStep,
+}) => {
+  const [accessId, setAccessId] = useState("");
+  const [secretKey, setSecretKey] = useState("");
+  const [buttonStatus, setButtonStatus] = useState("");
+
+  const validate = () => {
+    if (!accessId) {
+      return {
+        hasError: true,
+        error: "Access ID cannot be empty",
+      };
+    }
+    if (!secretKey) {
+      return {
+        hasError: true,
+        error: "AWS Secret key cannot be empty",
+      };
+    }
+    return {
+      hasError: false,
+      error: "",
+    };
+  };
+
+  const submit = async () => {
+    const validation = validate();
+    if (validation.hasError) {
+      setButtonStatus(validation.error);
+      return;
+    }
+    // TODO: Ask alex how to request the aws_integration_id on this step
+    nextFormStep();
+  };
+
+  return (
+    <>
+      <InputRow
+        type="text"
+        value={accessId}
+        setValue={(x: string) => {
+          setAccessId(x);
+        }}
+        label="👤 AWS Access ID"
+        placeholder="ex: AKIAIOSFODNN7EXAMPLE"
+        width="100%"
+        isRequired={true}
+      />
+      <InputRow
+        type="password"
+        value={secretKey}
+        setValue={(x: string) => {
+          setSecretKey(x);
+        }}
+        label="🔒 AWS Secret Key"
+        placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
+        width="100%"
+        isRequired={true}
+      />
+      <SaveButton
+        text="Continue"
+        disabled={false}
+        onClick={submit}
+        makeFlush={true}
+        clearPosition={true}
+        status={buttonStatus}
+        statusPosition={"right"}
+      />
+    </>
+  );
 };
 
-export const SettingsForm = () => {
-  return <></>;
+export const SettingsForm: React.FC<{ nextFormStep: () => void }> = ({
+  nextFormStep,
+}) => {
+  const [registryName, setRegistryName] = useState("");
+  const [awsRegion, setAWSRegion] = useState("us-east-1");
+  const [buttonStatus, setButtonStatus] = useState("");
+
+  const validate = () => {
+    if (!registryName) {
+      return {
+        hasError: true,
+        error: "Registry name cannot be empty",
+      };
+    }
+    return {
+      hasError: false,
+      error: "",
+    };
+  };
+
+  const submit = async () => {
+    const validation = validate();
+    if (validation.hasError) {
+      setButtonStatus(validation.error);
+      return;
+    }
+
+    nextFormStep();
+  };
+
+  return (
+    <>
+      <InputRow
+        type="text"
+        value={registryName}
+        setValue={(x) => {
+          setRegistryName(String(x));
+        }}
+        label="🏷️ Registry Name"
+        placeholder="ex: porter-awesome-registry"
+        width="100%"
+      />
+      <SelectRow
+        options={regionOptions}
+        width="100%"
+        value={awsRegion}
+        dropdownMaxHeight="240px"
+        setActiveValue={(x: string) => {
+          setAWSRegion(x);
+        }}
+        label="📍 AWS Region"
+      />
+      <SaveButton
+        text="Connect Registry"
+        disabled={false}
+        onClick={submit}
+        makeFlush={true}
+        clearPosition={true}
+        status={buttonStatus}
+        statusPosition={"right"}
+      />
+    </>
+  );
 };
 
-export const TestRegistryConnection = () => {
-  return <></>;
+export const TestRegistryConnection: React.FC<{ nextFormStep: () => void }> = ({
+  nextFormStep,
+}) => {
+  return (
+    <>
+      <SaveButton
+        text="Continue"
+        disabled={false}
+        onClick={nextFormStep}
+        makeFlush={true}
+        clearPosition={true}
+        status={""}
+        statusPosition={"right"}
+      />
+    </>
+  );
 };

+ 1 - 1
dashboard/src/main/home/onboarding/ConnectSource.tsx

@@ -57,7 +57,7 @@ const ConnectSource = () => {
   }, []);
 
   const nextStep = () => {
-    pushFiltered("/onboarding/provision", []);
+    pushFiltered("/onboarding/registry", []);
   };
 
   return (

+ 2 - 2
dashboard/src/main/home/onboarding/NewProject.tsx

@@ -8,7 +8,7 @@ import InputRow from "components/form-components/InputRow";
 import Helper from "components/form-components/Helper";
 import TitleSection from "components/TitleSection";
 import { useSnapshot } from "valtio";
-import { actions, OnboardingState } from "./OnboardingState";
+import { OnboardingState } from "./OnboardingState";
 import { useRouting } from "shared/routing";
 import { Context } from "shared/Context";
 import api from "shared/api";
@@ -129,7 +129,7 @@ export const NewProjectFC = () => {
           value={snap.projectName}
           setValue={(x: string) => {
             setButtonStatus("");
-            actions.setProjectName(x);
+            OnboardingState.actions.setProjectName(x);
           }}
           placeholder="ex: perspective-vortex"
           width="470px"

+ 3 - 3
dashboard/src/main/home/onboarding/Onboarding.tsx

@@ -1,19 +1,19 @@
 import React, { useContext, useEffect } from "react";
 import { Context } from "shared/Context";
 import styled from "styled-components";
-import { actions } from "./OnboardingState";
+import { OnboardingState } from "./OnboardingState";
 import Routes from "./Routes";
 
 const Onboarding = () => {
   const context = useContext(Context);
 
   useEffect(() => {
-    actions.initFromGlobalContext(context);
+    OnboardingState.actions.initFromGlobalContext(context);
   }, [context]);
 
   useEffect(() => {
     return () => {
-      actions.clearState();
+      OnboardingState.actions.clearState();
     };
   }, []);
   return (

+ 33 - 14
dashboard/src/main/home/onboarding/OnboardingState.ts

@@ -1,7 +1,8 @@
-import { isAlphanumeric } from "shared/common";
 import { ContextProps } from "shared/types";
 import { proxy } from "valtio";
-import { derive, devtools } from "valtio/utils";
+import { devtools } from "valtio/utils";
+import { SupportedProviders } from "./components/ProviderSelector";
+import { State as ConnectRegistryState } from "./ConnectRegistry/ConnectRegistryState";
 
 export type OnboardingStateType = {
   [key: string]: unknown;
@@ -11,20 +12,12 @@ export type OnboardingStateType = {
   userId: number | null;
   // Check if it's the first project that will be created
   isFirstProject: boolean | null;
-};
 
-const initialState: OnboardingStateType = {
-  projectName: "",
-  isProvisionerEnabled: null,
-  userId: null,
-  isFirstProject: null,
+  selectedProvider: SupportedProviders | null;
+  actions: typeof actions;
 };
 
-export const OnboardingState = proxy<OnboardingStateType>(initialState);
-
-devtools(OnboardingState, "Onboarding state");
-
-export const actions = {
+const actions = {
   setProjectName: (name: string) => {
     OnboardingState.projectName = name;
   },
@@ -58,11 +51,37 @@ export const actions = {
       actions.setIsFirstProject(true);
     }
   },
+  // Clear own and substates
   clearState: () => {
     Object.keys(OnboardingState).forEach((key) => {
-      if (key in initialState) {
+      if (key in initialState && key !== "actions") {
+        if (
+          key.toLowerCase().includes("state") &&
+          typeof OnboardingState === "object"
+        ) {
+          const subState = OnboardingState[key] as any;
+          if (
+            "clearState" in subState.actions &&
+            typeof subState?.actions?.clearState === "function"
+          ) {
+            subState.actions.clearState();
+          }
+        }
         OnboardingState[key] = initialState[key];
       }
     });
   },
 };
+
+const initialState: OnboardingStateType = {
+  projectName: "",
+  isProvisionerEnabled: null,
+  userId: null,
+  isFirstProject: null,
+  selectedProvider: null,
+  actions,
+  connectRegistry_state: ConnectRegistryState,
+};
+
+export const OnboardingState = proxy<OnboardingStateType>(initialState);
+devtools(OnboardingState, "Onboarding state");