Переглянути джерело

update login/register copy

jusrhee 2 роки тому
батько
коміт
fad73aea4a

+ 18 - 16
dashboard/src/components/porter/Select.tsx

@@ -20,6 +20,7 @@ type Props = {
   setValue?: (value: string) => void;
   prefix?: React.ReactNode;
   width?: string;
+  height?: string;
 };
 
 const Select: React.FC<Props> = ({
@@ -32,11 +33,12 @@ const Select: React.FC<Props> = ({
   setValue,
   prefix,
   width,
+  height,
 }) => {
   return (
     <Div width={width}>
       {label && <Label color={labelColor}>{label}</Label>}
-      <SelectWrapper isDisabled={disabled ?? false}>
+      <SelectWrapper isDisabled={disabled ?? false} height={height}>
         {prefix && (
           <>
             <Prefix>{prefix}</Prefix>
@@ -128,11 +130,11 @@ const Error = styled.div`
   }
 `;
 
-const SelectWrapper = styled.div<{ isDisabled: boolean }>`
+const SelectWrapper = styled.div<{ isDisabled: boolean; height?: string }>`
   position: relative;
   padding-left: 10px;
   padding-right: 28px;
-  height: 30px;
+  height: ${(props) => props.height || "30px"};
   transition: all 0.2s;
   background: ${(props) => props.theme.fg};
   border: 1px solid #494b4f;
@@ -152,18 +154,18 @@ const SelectWrapper = styled.div<{ isDisabled: boolean }>`
   }
 
   ${(props) =>
-    !props.isDisabled ?
-    css`
-      :hover {
-        border: 1px solid #7a7b80;
-      }
-    ` : 
-    css`
-      color: #ffffff55;
-      > img {
-        opacity: 0.5;
-      }
-    `}
+    !props.isDisabled
+      ? css`
+          :hover {
+            border: 1px solid #7a7b80;
+          }
+        `
+      : css`
+          color: #ffffff55;
+          > img {
+            opacity: 0.5;
+          }
+        `}
 `;
 
 const SelectLayer = styled.select<{
@@ -176,7 +178,7 @@ const SelectLayer = styled.select<{
   left: 0;
   width: 100%;
   height: 100%;
-  cursor: ${props => props.disabled ? "not-allowed" : "pointer"};
+  cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
   background: none;
   appearance: none;
   opacity: 0;

+ 117 - 0
dashboard/src/main/auth/InfoPanel.tsx

@@ -0,0 +1,117 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+
+import Container from "components/porter/Container";
+import Spacer from "components/porter/Spacer";
+
+import logo from "assets/logo.png";
+
+const InfoPanel: React.FC = () => {
+  // ret2 set to cloud.porter.run
+  if (window.location.hostname === "cloud.porter.run") {
+    return (
+      <Wrapper>
+        <Container row>
+          <a href="https://porter.run">
+            <Logo src={logo} />
+          </a>
+          <Badge>Cloud</Badge>
+        </Container>
+        <Spacer y={2} />
+        <Jumbotron>
+          Deploy and scale <Shiny>effortlessly</Shiny> with Porter
+        </Jumbotron>
+        <Spacer y={2} />
+        <CheckRow>
+          <i className="material-icons">done</i> $5 in free credits on sign-up
+        </CheckRow>
+        <Spacer y={0.5} />
+        <CheckRow>
+          <i className="material-icons">done</i> Instantly deploy from any Git
+          repo
+        </CheckRow>
+        <Spacer y={0.5} />
+        <CheckRow>
+          <i className="material-icons">done</i> Eject at any time to your own
+          AWS/Azure/GCP account
+        </CheckRow>
+      </Wrapper>
+    );
+  }
+  return (
+    <Wrapper>
+      <a href="https://porter.run">
+        <Logo src={logo} />
+      </a>
+      <Spacer y={2} />
+      <Jumbotron>
+        Deploy and scale <Shiny>effortlessly</Shiny> with Porter
+      </Jumbotron>
+      <Spacer y={2} />
+      <CheckRow>
+        <i className="material-icons">done</i> 14 day free trial
+      </CheckRow>
+      <Spacer y={0.5} />
+      <CheckRow>
+        <i className="material-icons">done</i> Generous startup program for
+        seed-stage companies
+      </CheckRow>
+      <Spacer y={0.5} />
+      <CheckRow>
+        <i className="material-icons">done</i> Bring your own cloud and use your
+        credits
+      </CheckRow>
+    </Wrapper>
+  );
+};
+
+export default InfoPanel;
+
+const Badge = styled.div`
+  margin-left: 17px;
+  margin-top: -6px;
+  background: ${(props) => props.theme.clickable};
+  padding: 5px 10px;
+  border: 1px solid #aaaabb;
+  border-radius: 5px;
+`;
+
+const CheckRow = styled.div`
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  color: #aaaabb;
+  > i {
+    font-size: 18px;
+    margin-right: 10px;
+    float: left;
+    color: #4797ff;
+  }
+`;
+
+const Shiny = styled.span`
+  background-image: linear-gradient(225deg, #fff, #7980ff);
+  -webkit-background-clip: text;
+  background-clip: text;
+  -webkit-text-fill-color: transparent;
+`;
+
+const Jumbotron = styled.div`
+  font-size: 32px;
+  font-weight: 500;
+  line-height: 1.5;
+`;
+
+const Logo = styled.img`
+  height: 24px;
+  user-select: none;
+`;
+
+const Wrapper = styled.div`
+  width: 500px;
+  margin-top: -20px;
+  position: relative;
+  padding: 25px;
+  border-radius: 5px;
+  font-size: 13px;
+`;

+ 53 - 50
dashboard/src/main/auth/Login.tsx

@@ -1,25 +1,24 @@
-import React, { useEffect, useState, useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import github from "assets/github-icon.png";
-import logo from "assets/logo.png";
-import docs from "assets/docs.png";
-import blog from "assets/blog.png";
-import community from "assets/community.png";
-import GoogleIcon from "assets/GoogleIcon";
-
-import api from "shared/api";
-import { emailRegex } from "shared/regex";
-import { Context } from "shared/Context";
-
 import DynamicLink from "components/DynamicLink";
 import Heading from "components/form-components/Heading";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Input from "components/porter/Input";
+import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import Link from "components/porter/Link";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { emailRegex } from "shared/regex";
+import blog from "assets/blog.png";
+import community from "assets/community.png";
+import docs from "assets/docs.png";
+import github from "assets/github-icon.png";
+import GoogleIcon from "assets/GoogleIcon";
+import logo from "assets/logo.png";
 
 type Props = {
   authenticate: () => void;
@@ -28,11 +27,9 @@ type Props = {
 const getWindowDimensions = () => {
   const { innerWidth: width, innerHeight: height } = window;
   return { width, height };
-}
+};
 
-const Login: React.FC<Props> = ({
-  authenticate,
-}) => {
+const Login: React.FC<Props> = ({ authenticate }) => {
   const { setUser, setCurrentError } = useContext(Context);
   const [email, setEmail] = useState("");
   const [password, setPassword] = useState("");
@@ -42,7 +39,9 @@ const Login: React.FC<Props> = ({
   const [hasGithub, setHasGithub] = useState(true);
   const [hasGoogle, setHasGoogle] = useState(false);
   const [hasResetPassword, setHasResetPassword] = useState(true);
-  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
+  const [windowDimensions, setWindowDimensions] = useState(
+    getWindowDimensions()
+  );
 
   const handleLogin = (): void => {
     if (!emailRegex.test(email)) {
@@ -50,11 +49,8 @@ const Login: React.FC<Props> = ({
     } else if (password === "") {
       setCredentialError(true);
     } else {
-      api.logInUser(
-        "",
-        { email: email, password: password },
-        {}
-      )
+      api
+        .logInUser("", { email, password }, {})
         .then((res) => {
           if (res?.data?.redirect) {
             window.location.href = res.data.redirect;
@@ -63,7 +59,9 @@ const Login: React.FC<Props> = ({
             authenticate();
           }
         })
-        .catch((err) => setCurrentError(err.response.data.error));
+        .catch((err) => {
+          setCurrentError(err.response.data.error);
+        });
     }
   };
 
@@ -74,7 +72,7 @@ const Login: React.FC<Props> = ({
   const handleKeyDown = (e: any) => {
     if (e.key === "Enter") {
       handleLogin();
-    };
+    }
   };
 
   // Manually re-register event listener on email/password change
@@ -87,34 +85,36 @@ const Login: React.FC<Props> = ({
   }, [email, password]);
 
   useEffect(() => {
-
     // Get capabilities to case on login methods
-    api.getMetadata("", {}, {})
+    api
+      .getMetadata("", {}, {})
       .then((res) => {
         setHasBasic(res.data?.basic_login);
         setHasGithub(res.data?.github_login);
         setHasGoogle(res.data?.google_login);
         setHasResetPassword(res.data?.email);
       })
-      .catch((err) => console.log(err));
+      .catch((err) => {
+        console.log(err);
+      });
 
     const urlParams = new URLSearchParams(window.location.search);
     const emailFromCLI = urlParams.get("email");
     emailFromCLI && setEmail(emailFromCLI);
 
-    window.addEventListener('resize', handleResize);
+    window.addEventListener("resize", handleResize);
     return () => {
-      window.removeEventListener('resize', handleResize);
+      window.removeEventListener("resize", handleResize);
     };
   }, []);
 
   const githubRedirect = () => {
-    let redirectUrl = `/api/oauth/login/github`;
+    const redirectUrl = `/api/oauth/login/github`;
     window.location.href = redirectUrl;
   };
 
   const googleRedirect = () => {
-    let redirectUrl = `/api/oauth/login/google`;
+    const redirectUrl = `/api/oauth/login/google`;
     window.location.href = redirectUrl;
   };
 
@@ -122,9 +122,12 @@ const Login: React.FC<Props> = ({
     <StyledLogin>
       {windowDimensions.width > windowDimensions.height && (
         <Wrapper>
-          <a href="https://porter.run">
-            <Logo src={logo} />
-          </a>
+          <Container row>
+            <a href="https://porter.run">
+              <Logo src={logo} />
+            </a>
+            <Badge>Cloud</Badge>
+          </Container>
           <Spacer y={2} />
           <Jumbotron>
             <Shiny>Welcome back to Porter</Shiny>
@@ -137,10 +140,6 @@ const Login: React.FC<Props> = ({
           <LinkRow to="https://porter.run/blog" target="_blank">
             <img src={blog} /> See what's new with Porter
           </LinkRow>
-          <Spacer y={0.5} />
-          <LinkRow to="https://discord.com/invite/34n7NN7FJ7" target="_blank">
-            <img src={community} /> Join the community
-          </LinkRow>
         </Wrapper>
       )}
       <Wrapper>
@@ -152,9 +151,7 @@ const Login: React.FC<Props> = ({
             <Spacer y={2} />
           </Flex>
         )}
-        <Heading isAtTop>
-          Log in to your Porter account
-        </Heading>
+        <Heading isAtTop>Log in to your Porter account</Heading>
         <Spacer y={1} />
         {(hasGithub || hasGoogle) && (
           <>
@@ -165,9 +162,7 @@ const Login: React.FC<Props> = ({
                   Log in with GitHub
                 </OAuthButton>
               )}
-              {hasGithub && hasGoogle && (
-                <Spacer inline x={2} />
-              )}
+              {hasGithub && hasGoogle && <Spacer inline x={2} />}
               {hasGoogle && (
                 <OAuthButton onClick={googleRedirect}>
                   <StyledGoogleIcon />
@@ -227,11 +222,10 @@ const Login: React.FC<Props> = ({
           </>
         )}
         <Spacer y={1} />
-        <Text
-          size={13}
-          color="helper"
-        >
-          Don't have an account?<Spacer width="5px" inline /><Link to="/register">Sign up</Link>
+        <Text size={13} color="helper">
+          Don't have an account?
+          <Spacer width="5px" inline />
+          <Link to="/register">Sign up</Link>
         </Text>
       </Wrapper>
     </StyledLogin>
@@ -240,6 +234,15 @@ const Login: React.FC<Props> = ({
 
 export default Login;
 
+const Badge = styled.div`
+  margin-left: 17px;
+  margin-top: -6px;
+  background: ${(props) => props.theme.clickable};
+  padding: 5px 10px;
+  border: 1px solid #aaaabb;
+  border-radius: 5px;
+`;
+
 const ForgotPassword = styled.div`
   position: absolute;
   right: 0;

+ 178 - 127
dashboard/src/main/auth/Register.tsx

@@ -1,22 +1,23 @@
-import React, { useEffect, useState, useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import github from "assets/github-icon.png";
-import logo from "assets/logo.png";
-import GoogleIcon from "assets/GoogleIcon";
-
-import api from "shared/api";
-import { emailRegex } from "shared/regex";
-import { Context } from "shared/Context";
-
 import Heading from "components/form-components/Heading";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Input from "components/porter/Input";
-import Spacer from "components/porter/Spacer";
-import Text from "components/porter/Text";
 import Link from "components/porter/Link";
 import Select from "components/porter/Select";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { emailRegex } from "shared/regex";
+import github from "assets/github-icon.png";
+import GoogleIcon from "assets/GoogleIcon";
+import logo from "assets/logo.png";
+
+import InfoPanel from "./InfoPanel";
 
 type Props = {
   authenticate: () => void;
@@ -25,11 +26,9 @@ type Props = {
 const getWindowDimensions = () => {
   const { innerWidth: width, innerHeight: height } = window;
   return { width, height };
-}
+};
 
-const Register: React.FC<Props> = ({
-  authenticate,
-}) => {
+const Register: React.FC<Props> = ({ authenticate }) => {
   const { setUser, setCurrentError } = useContext(Context);
   const [firstName, setFirstName] = useState("");
   const [firstNameError, setFirstNameError] = useState(false);
@@ -45,16 +44,22 @@ const Register: React.FC<Props> = ({
   const [hasBasic, setHasBasic] = useState(true);
   const [hasGithub, setHasGithub] = useState(true);
   const [hasGoogle, setHasGoogle] = useState(false);
-  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
+  const [windowDimensions, setWindowDimensions] = useState(
+    getWindowDimensions()
+  );
   const [buttonDisabled, setButtonDisabled] = useState(false);
 
-  const [chosenReferralOption, setChosenReferralOption] = useState<string>("(None provided)");
+  const [chosenReferralOption, setChosenReferralOption] =
+    useState<string>("(None provided)");
   const [referralOtherText, setReferralOtherText] = useState<string>("");
 
   const referralOptions = [
     { value: "(None provided)", label: "Please select an option:" },
     { value: "Email", label: "Email" },
-    { value: "Word of mouth", label: "Word of mouth (friend, colleague, etc.)" },
+    {
+      value: "Word of mouth",
+      label: "Word of mouth (friend, colleague, etc.)",
+    },
     { value: "YC", label: "YC" },
     { value: "YC Startup School", label: "YC Startup School" },
     { value: "Facebook", label: "Facebook" },
@@ -64,18 +69,19 @@ const Register: React.FC<Props> = ({
     { value: "LinkedIn", label: "LinkedIn" },
     { value: "Porter blog", label: "Porter blog" },
     { value: "Other", label: "Other" },
-  ]
+  ];
 
   const handleRegister = (): void => {
+    const isHosted = window.location.hostname === "cloud.porter.run";
     if (!emailRegex.test(email)) {
       setEmailError(true);
     }
 
-    if (firstName === "") {
+    if (firstName === "" && !isHosted) {
       setFirstNameError(true);
     }
 
-    if (lastName === "") {
+    if (lastName === "" && !isHosted) {
       setLastNameError(true);
     }
 
@@ -83,12 +89,13 @@ const Register: React.FC<Props> = ({
       setPasswordError(true);
     }
 
-    if (companyName === "") {
+    if (companyName === "" && !isHosted) {
       setCompanyNameError(true);
     }
 
     // Check for valid input
     if (
+      !isHosted &&
       emailRegex.test(email) &&
       firstName !== "" &&
       lastName !== "" &&
@@ -102,12 +109,15 @@ const Register: React.FC<Props> = ({
         .registerUser(
           "",
           {
-            email: email,
-            password: password,
+            email,
+            password,
             first_name: firstName,
             last_name: lastName,
             company_name: companyName,
-            referral_method: chosenReferralOption === "Other" ? `Other: ${referralOtherText}` : chosenReferralOption,
+            referral_method:
+              chosenReferralOption === "Other"
+                ? `Other: ${referralOtherText}`
+                : chosenReferralOption,
           },
           {}
         )
@@ -117,14 +127,14 @@ const Register: React.FC<Props> = ({
           } else {
             setUser(res?.data?.id, res?.data?.email);
             authenticate();
-            
+
             try {
               window.dataLayer?.push({
-                event: 'sign-up',
+                event: "sign-up",
                 data: {
-                  method: 'email',
-                  email: res?.data?.email
-                }
+                  method: "email",
+                  email: res?.data?.email,
+                },
               });
             } catch (err) {
               console.log(err);
@@ -138,7 +148,60 @@ const Register: React.FC<Props> = ({
         .catch((err) => {
           console.log("registration:", err);
           if (err.response?.data?.error) {
-            setCurrentError(err.response.data.error)
+            setCurrentError(err.response.data.error);
+          } else {
+            location.reload();
+          }
+          setButtonDisabled(false);
+        });
+    } else if (isHosted && emailRegex.test(email) && password !== "") {
+      setButtonDisabled(true);
+
+      // Attempt user registration
+      api
+        .registerUser(
+          "",
+          {
+            email,
+            password,
+            first_name: email,
+            last_name: email,
+            company_name: email,
+            referral_method:
+              chosenReferralOption === "Other"
+                ? `Other: ${referralOtherText}`
+                : chosenReferralOption,
+          },
+          {}
+        )
+        .then((res: any) => {
+          if (res?.data?.redirect) {
+            window.location.href = res.data.redirect;
+          } else {
+            setUser(res?.data?.id, res?.data?.email);
+            authenticate();
+
+            try {
+              window.dataLayer?.push({
+                event: "sign-up",
+                data: {
+                  method: "email",
+                  email: res?.data?.email,
+                },
+              });
+            } catch (err) {
+              console.log(err);
+            }
+          }
+
+          // Temp
+          location.reload();
+          setButtonDisabled(false);
+        })
+        .catch((err) => {
+          console.log("registration:", err);
+          if (err.response?.data?.error) {
+            setCurrentError(err.response.data.error);
           } else {
             location.reload();
           }
@@ -154,7 +217,7 @@ const Register: React.FC<Props> = ({
   const handleKeyDown = (e: any) => {
     if (e.key === "Enter") {
       handleRegister();
-    };
+    }
   };
 
   // Manually re-register event listener on email/password change
@@ -167,67 +230,48 @@ const Register: React.FC<Props> = ({
   }, [email, password, firstName, lastName]);
 
   useEffect(() => {
-    let qs = window.location.search;
-    let urlParams = new URLSearchParams(qs);
-    let email = urlParams.get('email');
+    const qs = window.location.search;
+    const urlParams = new URLSearchParams(qs);
+    const email = urlParams.get("email");
 
     if (email) {
       setEmail(email);
       setDisabled(true);
     }
-
   }, []);
 
   useEffect(() => {
-
     // Get capabilities to case on login methods
-    api.getMetadata("", {}, {})
+    api
+      .getMetadata("", {}, {})
       .then((res) => {
         setHasBasic(res.data?.basic_login);
         setHasGithub(res.data?.github_login);
         setHasGoogle(res.data?.google_login);
       })
-      .catch((err) => console.log(err));
+      .catch((err) => {
+        console.log(err);
+      });
 
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
+    window.addEventListener("resize", handleResize);
+    return () => {
+      window.removeEventListener("resize", handleResize);
+    };
   }, []);
 
   const githubRedirect = () => {
-    let redirectUrl = `/api/oauth/login/github`;
+    const redirectUrl = `/api/oauth/login/github`;
     window.location.href = redirectUrl;
   };
 
   const googleRedirect = () => {
-    let redirectUrl = `/api/oauth/login/google`;
+    const redirectUrl = `/api/oauth/login/google`;
     window.location.href = redirectUrl;
   };
 
   return (
     <StyledRegister>
-      {windowDimensions.width > windowDimensions.height && (
-        <Wrapper>
-          <a href="https://porter.run">
-            <Logo src={logo} />
-          </a>
-          <Spacer y={2} />
-          <Jumbotron>
-            Deploy and scale <Shiny>effortlessly</Shiny> with Porter
-          </Jumbotron>
-          <Spacer y={2} />
-          <CheckRow>
-            <i className="material-icons">done</i> 14 day free trial
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i>  Generous startup program for seed-stage companies
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i> Bring your own cloud and use your credits
-          </CheckRow>
-        </Wrapper>
-      )}
+      {windowDimensions.width > windowDimensions.height && <InfoPanel />}
       <Wrapper>
         {windowDimensions.width <= windowDimensions.height && (
           <Flex>
@@ -237,11 +281,9 @@ const Register: React.FC<Props> = ({
             <Spacer y={2} />
           </Flex>
         )}
-        <Heading isAtTop>
-          Create your Porter account
-        </Heading>
+        <Heading isAtTop>Create your Porter account</Heading>
         <Spacer y={1} />
-        {((hasGithub || hasGoogle) && !disabled) && (
+        {(hasGithub || hasGoogle) && !disabled && (
           <>
             <Container row>
               {hasGithub && (
@@ -250,9 +292,7 @@ const Register: React.FC<Props> = ({
                   Sign up with GitHub
                 </OAuthButton>
               )}
-              {hasGithub && hasGoogle && (
-                <Spacer inline x={2} />
-              )}
+              {hasGithub && hasGoogle && <Spacer inline x={2} />}
               {hasGoogle && (
                 <OAuthButton onClick={googleRedirect}>
                   <StyledGoogleIcon />
@@ -270,57 +310,61 @@ const Register: React.FC<Props> = ({
         )}
         {hasBasic && (
           <>
-            <Container row>
-              <RowWrapper>
-                <Input
-                  placeholder="First name"
-                  label="First name"
-                  value={firstName}
-                  setValue={(x) => {
-                    setFirstName(x);
-                    setFirstNameError(false);
-                  }}
-                  width="100%"
-                  height="40px"
-                  error={(firstNameError && "First name cannot be blank")}
-                />
-                {!firstNameError && lastNameError && (
-                  <Spacer height="27px" />
-                )}
-              </RowWrapper>
-              <Spacer inline x={2} />
-              <RowWrapper>
+            {window.location.hostname !== "cloud.porter.run" && (
+              <>
+                <Container row>
+                  <RowWrapper>
+                    <Input
+                      placeholder="First name"
+                      label="First name"
+                      value={firstName}
+                      setValue={(x) => {
+                        setFirstName(x);
+                        setFirstNameError(false);
+                      }}
+                      width="100%"
+                      height="40px"
+                      error={firstNameError && "First name cannot be blank"}
+                    />
+                    {!firstNameError && lastNameError && (
+                      <Spacer height="27px" />
+                    )}
+                  </RowWrapper>
+                  <Spacer inline x={2} />
+                  <RowWrapper>
+                    <Input
+                      placeholder="Last name"
+                      label="Last name"
+                      value={lastName}
+                      setValue={(x) => {
+                        setLastName(x);
+                        setLastNameError(false);
+                      }}
+                      width="100%"
+                      height="40px"
+                      error={lastNameError && "Last name cannot be blank"}
+                    />
+                    {!lastNameError && firstNameError && (
+                      <Spacer height="27px" />
+                    )}
+                  </RowWrapper>
+                </Container>
+                <Spacer y={1} />
                 <Input
-                  placeholder="Last name"
-                  label="Last name"
-                  value={lastName}
+                  placeholder="Company name"
+                  label="Company name"
+                  value={companyName}
                   setValue={(x) => {
-                    setLastName(x);
-                    setLastNameError(false);
+                    setCompanyName(x);
+                    setCompanyNameError(false);
                   }}
                   width="100%"
                   height="40px"
-                  error={(lastNameError && "Last name cannot be blank")}
+                  error={companyNameError && ""}
                 />
-                {!lastNameError && firstNameError && (
-                  <Spacer height="27px" />
-                )}
-              </RowWrapper>
-            </Container>
-            <Spacer y={1} />
-            <Input
-              placeholder="Company name"
-              label="Company name"
-              value={companyName}
-              setValue={(x) => {
-                setCompanyName(x);
-                setCompanyNameError(false);
-              }}
-              width="100%"
-              height="40px"
-              error={(companyNameError && "")}
-            />
-            <Spacer y={1} />
+                <Spacer y={1} />
+              </>
+            )}
             <Input
               type="email"
               placeholder="Email"
@@ -332,7 +376,7 @@ const Register: React.FC<Props> = ({
               }}
               width="100%"
               height="40px"
-              error={(emailError && "Please enter a valid email")}
+              error={emailError && "Please enter a valid email"}
               disabled={disabled}
             />
             <Spacer y={1} />
@@ -344,13 +388,14 @@ const Register: React.FC<Props> = ({
               width="100%"
               height="40px"
               type="password"
-              error={(passwordError && "")}
+              error={passwordError && ""}
             />
             <Spacer y={1} />
             <Text color="helper">(Optional) How did you hear about us?</Text>
             <Spacer y={0.5} />
             <Select
               width="100%"
+              height="40px"
               options={referralOptions}
               setValue={setChosenReferralOption}
               value={chosenReferralOption}
@@ -361,13 +406,20 @@ const Register: React.FC<Props> = ({
                 <FeedbackInput
                   autoFocus={true}
                   value={referralOtherText}
-                  onChange={(e) => setReferralOtherText(e.target.value)}
+                  onChange={(e) => {
+                    setReferralOtherText(e.target.value);
+                  }}
                   placeholder="Tell us more..."
                 />
               </>
             )}
             <Spacer y={1} />
-            <Button disabled={buttonDisabled} onClick={handleRegister} width="100%" height="40px">
+            <Button
+              disabled={buttonDisabled}
+              onClick={handleRegister}
+              width="100%"
+              height="40px"
+            >
               Continue
             </Button>
           </>
@@ -375,11 +427,10 @@ const Register: React.FC<Props> = ({
         {!disabled && (
           <>
             <Spacer y={1} />
-            <Text
-              size={13}
-              color="helper"
-            >
-              Already have an account?<Spacer width="5px" inline /><Link to="/login">Log in</Link>
+            <Text size={13} color="helper">
+              Already have an account?
+              <Spacer width="5px" inline />
+              <Link to="/login">Log in</Link>
             </Text>
           </>
         )}

+ 49 - 78
dashboard/src/main/auth/SetInfo.tsx

@@ -1,21 +1,22 @@
-import React, { useEffect, useState, useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import github from "assets/github-icon.png";
-import logo from "assets/logo.png";
-import GoogleIcon from "assets/GoogleIcon";
-
-import api from "shared/api";
-import { emailRegex } from "shared/regex";
-import { Context } from "shared/Context";
-
 import Heading from "components/form-components/Heading";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Input from "components/porter/Input";
+import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import Link from "components/porter/Link";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { emailRegex } from "shared/regex";
+import github from "assets/github-icon.png";
+import GoogleIcon from "assets/GoogleIcon";
+import logo from "assets/logo.png";
+
+import InfoPanel from "./InfoPanel";
 
 type Props = {
   authenticate: () => void;
@@ -25,12 +26,9 @@ type Props = {
 const getWindowDimensions = () => {
   const { innerWidth: width, innerHeight: height } = window;
   return { width, height };
-}
+};
 
-const SetInfo: React.FC<Props> = ({
-  authenticate,
-  handleLogOut,
-}) => {
+const SetInfo: React.FC<Props> = ({ authenticate, handleLogOut }) => {
   const { user, setCurrentError } = useContext(Context);
   const [firstName, setFirstName] = useState("");
   const [firstNameError, setFirstNameError] = useState(false);
@@ -38,7 +36,9 @@ const SetInfo: React.FC<Props> = ({
   const [lastNameError, setLastNameError] = useState(false);
   const [companyName, setCompanyName] = useState("");
   const [companyNameError, setCompanyNameError] = useState(false);
-  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
+  const [windowDimensions, setWindowDimensions] = useState(
+    getWindowDimensions()
+  );
 
   const handleResize = () => {
     setWindowDimensions(getWindowDimensions());
@@ -57,43 +57,42 @@ const SetInfo: React.FC<Props> = ({
       setCompanyNameError(true);
     }
 
-    if (
-      firstName !== "" &&
-      lastName !== "" &&
-      companyName !== ""
-    ) {
-      api.updateUserInfo(
-        "",
-        { 
-          first_name: firstName,
-          last_name: lastName,
-          company_name: companyName,
-        },
-        { id: user.id }
-      )
+    if (firstName !== "" && lastName !== "" && companyName !== "") {
+      api
+        .updateUserInfo(
+          "",
+          {
+            first_name: firstName,
+            last_name: lastName,
+            company_name: companyName,
+          },
+          { id: user.id }
+        )
         .then((res: any) => {
           authenticate();
 
           try {
             window.dataLayer?.push({
-              event: 'sign-up',
+              event: "sign-up",
               data: {
-                method: 'github',
-                email: user?.email
-              }
+                method: "github",
+                email: user?.email,
+              },
             });
           } catch (err) {
             console.log(err);
           }
         })
-        .catch((err) => setCurrentError(err));
+        .catch((err) => {
+          setCurrentError(err);
+        });
     }
   };
 
   const handleKeyDown = (e: any) => {
     if (e.key === "Enter") {
       finishAccountSetup();
-    };
+    }
   };
 
   // Manually re-register event listener on email/password change
@@ -106,35 +105,15 @@ const SetInfo: React.FC<Props> = ({
   }, [firstName, lastName, companyName]);
 
   useEffect(() => {
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
+    window.addEventListener("resize", handleResize);
+    return () => {
+      window.removeEventListener("resize", handleResize);
+    };
   }, []);
 
   return (
     <StyledRegister>
-      {windowDimensions.width > windowDimensions.height && (
-        <Wrapper>
-          <Logo src={logo} onClick={() => {
-            window.location.href = "https://porter.run"
-          }}/>
-          <Spacer y={2} />
-          <Jumbotron>
-            Deploy and scale <Shiny>effortlessly</Shiny> with Porter
-          </Jumbotron>
-          <Spacer y={2} />
-          <CheckRow>
-            <i className="material-icons">done</i> Generous startup program for seed-stage companies
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i> Bring your own cloud (and cloud credits)
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i> Fully automated setup and deployment
-          </CheckRow>
-        </Wrapper>
-      )}
+      {windowDimensions.width > windowDimensions.height && <InfoPanel />}
       <Wrapper>
         {windowDimensions.width <= windowDimensions.height && (
           <Flex>
@@ -142,9 +121,7 @@ const SetInfo: React.FC<Props> = ({
             <Spacer y={2} />
           </Flex>
         )}
-        <Heading isAtTop>
-          Finish setting up your account
-        </Heading>
+        <Heading isAtTop>Finish setting up your account</Heading>
         <Spacer y={1} />
         <Container row>
           <RowWrapper>
@@ -158,11 +135,9 @@ const SetInfo: React.FC<Props> = ({
               }}
               width="100%"
               height="40px"
-              error={(firstNameError && "First name cannot be blank")}
+              error={firstNameError && "First name cannot be blank"}
             />
-            {!firstNameError && lastNameError && (
-              <Spacer height="27px" />
-            )}
+            {!firstNameError && lastNameError && <Spacer height="27px" />}
           </RowWrapper>
           <Spacer inline x={2} />
           <RowWrapper>
@@ -176,11 +151,9 @@ const SetInfo: React.FC<Props> = ({
               }}
               width="100%"
               height="40px"
-              error={(lastNameError && "Last name cannot be blank")}
+              error={lastNameError && "Last name cannot be blank"}
             />
-            {!lastNameError && firstNameError && (
-              <Spacer height="27px" />
-            )}
+            {!lastNameError && firstNameError && <Spacer height="27px" />}
           </RowWrapper>
         </Container>
         <Spacer y={1} />
@@ -194,18 +167,16 @@ const SetInfo: React.FC<Props> = ({
           }}
           width="100%"
           height="40px"
-          error={(companyNameError && "")}
+          error={companyNameError && ""}
         />
         <Spacer height="30px" />
         <Button onClick={finishAccountSetup} width="100%" height="40px">
           Continue
         </Button>
         <Spacer y={1} />
-        <Text 
-          size={13}
-          color="helper"
-        >
-          Want to use a different login method? <Link onClick={handleLogOut}>Log out</Link>
+        <Text size={13} color="helper">
+          Want to use a different login method?{" "}
+          <Link onClick={handleLogOut}>Log out</Link>
         </Text>
       </Wrapper>
     </StyledRegister>

+ 31 - 48
dashboard/src/main/auth/VerifyEmail.tsx

@@ -1,20 +1,21 @@
-import React, { useEffect, useState, useContext } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 
-import github from "assets/github-icon.png";
-import logo from "assets/logo.png";
-import GoogleIcon from "assets/GoogleIcon";
-
-import api from "shared/api";
-import { Context } from "shared/Context";
-
 import Heading from "components/form-components/Heading";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Input from "components/porter/Input";
+import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
-import Link from "components/porter/Link";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import github from "assets/github-icon.png";
+import GoogleIcon from "assets/GoogleIcon";
+import logo from "assets/logo.png";
+
+import InfoPanel from "./InfoPanel";
 
 type Props = {
   handleLogOut: () => void;
@@ -23,55 +24,40 @@ type Props = {
 const getWindowDimensions = () => {
   const { innerWidth: width, innerHeight: height } = window;
   return { width, height };
-}
+};
 
-const Register: React.FC<Props> = ({
-  handleLogOut,
-}) => {
+const Register: React.FC<Props> = ({ handleLogOut }) => {
   const { user, setCurrentError } = useContext(Context);
   const [submitted, setSubmitted] = useState(false);
-  const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
+  const [windowDimensions, setWindowDimensions] = useState(
+    getWindowDimensions()
+  );
 
   const handleResize = () => {
     setWindowDimensions(getWindowDimensions());
   };
 
   useEffect(() => {
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
+    window.addEventListener("resize", handleResize);
+    return () => {
+      window.removeEventListener("resize", handleResize);
+    };
   }, []);
 
   const handleSendEmail = (): void => {
-    api.createEmailVerification("", {}, {})
+    api
+      .createEmailVerification("", {}, {})
       .then((res) => {
         setSubmitted(true);
       })
-      .catch((err) => setCurrentError(err.response.data.error));
+      .catch((err) => {
+        setCurrentError(err.response.data.error);
+      });
   };
 
   return (
     <StyledRegister>
-      {windowDimensions.width > windowDimensions.height && (
-        <Wrapper>
-          <Logo src={logo} />
-          <Spacer y={2} />
-          <Jumbotron>
-            Deploy and scale <Shiny>effortlessly</Shiny> with Porter
-          </Jumbotron>
-          <Spacer y={2} />
-          <CheckRow>
-            <i className="material-icons">done</i> Generous startup program for seed-stage companies 
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i> Bring your own cloud (and cloud credits)
-          </CheckRow>
-          <Spacer y={0.5} />
-          <CheckRow>
-            <i className="material-icons">done</i> Fully automated setup and deployment
-          </CheckRow>
-        </Wrapper>
-      )}
+      {windowDimensions.width > windowDimensions.height && <InfoPanel />}
       <Wrapper>
         {windowDimensions.width <= windowDimensions.height && (
           <Flex>
@@ -79,9 +65,7 @@ const Register: React.FC<Props> = ({
             <Spacer y={2} />
           </Flex>
         )}
-        <Heading isAtTop>
-          Verify your email
-        </Heading>
+        <Heading isAtTop>Verify your email</Heading>
         <Spacer y={1} />
         {submitted ? (
           <>
@@ -121,11 +105,10 @@ const Register: React.FC<Props> = ({
           </>
         )}
         <Spacer y={1} />
-        <Text 
-          size={13}
-          color="helper"
-        >
-          Want to use a different email?<Spacer inline width="5px" /><Link onClick={handleLogOut}>Log out</Link>
+        <Text size={13} color="helper">
+          Want to use a different email?
+          <Spacer inline width="5px" />
+          <Link onClick={handleLogOut}>Log out</Link>
         </Text>
       </Wrapper>
     </StyledRegister>
@@ -256,4 +239,4 @@ const StyledRegister = styled.div`
   top: 0;
   left: 0;
   background: #111114;
-`;
+`;

+ 52 - 0
dashboard/src/main/home/project-settings/BillingDeleteConsent.tsx

@@ -0,0 +1,52 @@
+import React, { useContext, useState } from "react";
+
+import Button from "components/porter/Button";
+import Modal from "components/porter/Modal";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+
+import { Context } from "shared/Context";
+
+type Props = {
+  setShowModal: (show: boolean) => void;
+  show: boolean;
+  onDelete: () => void;
+};
+
+const BillingDeleteConsent: React.FC<Props> = ({
+  setShowModal,
+  show,
+  onDelete,
+}) => {
+  const [confirmDelete, setDeleteCost] = useState("");
+  const { currentProject } = useContext(Context);
+  return show ? (
+    <>
+      <Modal
+        closeModal={() => {
+          setDeleteCost("");
+          setShowModal(false);
+        }}
+      >
+        <Text size={16}>Delete payment method?</Text>
+        <Spacer y={1} />
+        <Button
+          disabled={confirmDelete}
+          onClick={() => {
+            setShowModal(false);
+            onDelete();
+          }}
+          status={
+            confirmDelete == currentProject?.name
+              ? "This action cannot be undone"
+              : ""
+          }
+        >
+          Confirm
+        </Button>
+      </Modal>
+    </>
+  ) : null;
+};
+
+export default BillingDeleteConsent;