Skip to content

Commit 17c77fc

Browse files
🪟 🧪 [Experiment] Simplify signup left side (#22402)
* 🪟 🧪 [Experiment] Simplify signup left side We want to experiment if simplifying the left side on the sign up page and dividing the signup methods, increase conversion of corporate emails on the page. How? - Making Oauth the default method - Email/password method is still available but user will see an error for personal emails. They can still sign up though. Airtable details: https://airtable.com/appIuY0uKPVnk8TWT/tbl2SxXnUwf6fVCWS/viw9frYvld7ks7aNo/recnFzE4HBB8RP1uY?blocks=hide Demo: https://www.loom.com/share/9b706682d89845b1bf2455a1f3e1520d
1 parent f5e0f80 commit 17c77fc

File tree

8 files changed

+158
-15
lines changed

8 files changed

+158
-15
lines changed

airbyte-webapp/src/hooks/services/Experiment/experiments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface Experiments {
1818
"authPage.signup.hideName": boolean;
1919
"authPage.signup.hideCompanyName": boolean;
2020
"onboarding.speedyConnection": boolean;
21+
"authPage.signup.simplifyLeftSide": boolean;
2122
"connection.onboarding.sources": string;
2223
"connection.onboarding.destinations": string;
2324
"connection.autoDetectSchemaChanges": boolean;

airbyte-webapp/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"form.yourEmail": "Your email",
3232
"form.email.placeholder": "[email protected]",
3333
"form.email.error": "Enter a valid email",
34+
"form.workEmail.error": "Enter a valid work email",
3435
"form.empty.error": "Required",
3536
"form.selectConnector": "Type to search for a connector",
3637
"form.searchName": "search by name...",

airbyte-webapp/src/packages/cloud/locales/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"login.resendEmail": "Didn’t receive the email? Send it again",
99
"login.yourEmail": "Your work email*",
1010
"login.inviteEmail": "For security, re-enter your invite email*",
11-
"login.yourEmail.placeholder": "work.email@example.com",
11+
"login.yourEmail.placeholder": "name@company.com",
1212
"login.yourEmail.notFound": "User not found",
1313
"login.unknownError": "An unknown error has occurred",
1414
"login.password": "Enter your password*",
@@ -160,6 +160,12 @@
160160
"firebase.auth.error.default": "Confirmation email cannot be sent. Please try again later.",
161161

162162
"signup.password.minLength": "Password should be at least 12 characters",
163+
"signup.details.noCreditCard": "No credit card required",
164+
"signup.details.instantSetup": "Instant setup",
165+
"signup.details.freeTrial": "14-day free trial",
166+
"signup.title": "Create your Airbyte account",
167+
"signup.method.email": "Sign up using email",
168+
"signup.method.oauth": "Sign up using Google or GitHub",
163169
"email.duplicate": "Email already exists",
164170
"email.notfound": "Email not found",
165171
"email.disabled": "Your account is disabled",

airbyte-webapp/src/packages/cloud/views/auth/SignupPage/SignupPage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { FlexContainer } from "components/ui/Flex";
66
import { Heading } from "components/ui/Heading";
77

88
import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics";
9+
import { useExperiment } from "hooks/services/Experiment";
910

1011
import { Separator } from "./components/Separator";
1112
import { Disclaimer, SignupForm } from "./components/SignupForm";
13+
import { SimpleLeftSide } from "./components/SimpleLeftSide/SimpleLeftSide";
1214
import SpecialBlock from "./components/SpecialBlock";
1315
import styles from "./SignupPage.module.scss";
1416
import { OAuthLogin } from "../OAuthLogin";
@@ -19,7 +21,11 @@ interface SignupPageProps {
1921

2022
const SignupPage: React.FC<SignupPageProps> = ({ highlightStyle }) => {
2123
useTrackPage(PageTrackingCodes.SIGNUP);
24+
const isSimpleLeftSide = useExperiment("authPage.signup.simplifyLeftSide", false);
2225

26+
if (isSimpleLeftSide) {
27+
return <SimpleLeftSide />;
28+
}
2329
return (
2430
<FlexContainer direction="column" gap="xl">
2531
<HeadTitle titles={[{ id: "login.signup" }]} />
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
@use "../../../../../../scss/colors";
2-
@use "../../../../../../scss/variables";
1+
@use "scss/colors";
2+
@use "scss/variables";
33

44
.statusMessage {
55
margin-top: variables.$spacing-md;
@@ -9,3 +9,19 @@
99
.disclaimer {
1010
margin-top: variables.$spacing-xl;
1111
}
12+
13+
.passwordCheckContainer {
14+
margin-top: variables.$spacing-lg;
15+
}
16+
17+
.checkIcon {
18+
color: colors.$dark-blue-100;
19+
}
20+
21+
.error {
22+
color: colors.$red;
23+
}
24+
25+
.valid {
26+
color: colors.$green;
27+
}

airbyte-webapp/src/packages/cloud/views/auth/SignupPage/components/SignupForm.tsx

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { faCheckCircle, faXmarkCircle } from "@fortawesome/free-solid-svg-icons";
2+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3+
import classNames from "classnames";
14
import { Field, FieldProps, Formik, Form } from "formik";
25
import React, { useMemo } from "react";
36
import { FormattedMessage, useIntl } from "react-intl";
@@ -7,10 +10,13 @@ import * as yup from "yup";
710

811
import { LabeledInput, Link } from "components";
912
import { Button } from "components/ui/Button";
13+
import { FlexContainer } from "components/ui/Flex";
14+
import { Text } from "components/ui/Text";
1015

1116
import { useExperiment } from "hooks/services/Experiment";
1217
import { FieldError } from "packages/cloud/lib/errors/FieldError";
1318
import { useAuthService } from "packages/cloud/services/auth/AuthService";
19+
import { FREE_EMAIL_SERVICE_PROVIDERS } from "packages/cloud/services/auth/freeEmailProviders";
1420
import { isGdprCountry } from "utils/dataPrivacy";
1521
import { links } from "utils/links";
1622

@@ -75,6 +81,18 @@ export const CompanyNameField: React.FC = () => {
7581
export const EmailField: React.FC<{ label?: React.ReactNode }> = ({ label }) => {
7682
const { formatMessage } = useIntl();
7783

84+
const isCorporateEmail = (email?: string) =>
85+
!FREE_EMAIL_SERVICE_PROVIDERS.some((provider) => email?.endsWith(`@${provider}`));
86+
87+
const getMessage = ({ touched, error, value }: { touched: boolean; error?: string; value?: string }) => {
88+
if (touched && error) {
89+
return formatMessage({ id: error });
90+
}
91+
if (touched && !isCorporateEmail(value)) {
92+
return formatMessage({ id: "form.workEmail.error" });
93+
}
94+
return null;
95+
};
7896
return (
7997
<Field name="email">
8098
{({ field, meta }: FieldProps<string>) => (
@@ -85,8 +103,8 @@ export const EmailField: React.FC<{ label?: React.ReactNode }> = ({ label }) =>
85103
id: "login.yourEmail.placeholder",
86104
})}
87105
type="text"
88-
error={!!meta.error && meta.touched}
89-
message={meta.touched && meta.error && formatMessage({ id: meta.error })}
106+
error={(!!meta.error && meta.touched) || (meta.touched && !isCorporateEmail(field.value))}
107+
message={getMessage({ touched: meta.touched, error: meta.error, value: field.value })}
90108
/>
91109
)}
92110
</Field>
@@ -99,16 +117,36 @@ export const PasswordField: React.FC<{ label?: React.ReactNode }> = ({ label })
99117
return (
100118
<Field name="password">
101119
{({ field, meta }: FieldProps<string>) => (
102-
<LabeledInput
103-
{...field}
104-
label={label || <FormattedMessage id="login.password" />}
105-
placeholder={formatMessage({
106-
id: "login.password.placeholder",
107-
})}
108-
type="password"
109-
error={!!meta.error && meta.touched}
110-
message={meta.touched && meta.error && formatMessage({ id: meta.error })}
111-
/>
120+
<>
121+
<LabeledInput
122+
{...field}
123+
label={label || <FormattedMessage id="login.password" />}
124+
placeholder={formatMessage({
125+
id: "login.password.placeholder",
126+
})}
127+
type="password"
128+
error={!!meta.error && meta.touched}
129+
/>
130+
131+
<FlexContainer gap="sm" alignItems="center" className={styles.passwordCheckContainer}>
132+
<FontAwesomeIcon
133+
icon={Boolean(meta.error) && meta.touched ? faXmarkCircle : faCheckCircle}
134+
className={classNames(styles.checkIcon, {
135+
[styles.error]: Boolean(meta.error) && meta.touched,
136+
[styles.valid]: meta.touched && !meta.error,
137+
})}
138+
/>
139+
140+
<Text
141+
size="sm"
142+
className={classNames({
143+
[styles.error]: Boolean(meta.error) && meta.touched,
144+
})}
145+
>
146+
<FormattedMessage id="signup.password.minLength" />
147+
</Text>
148+
</FlexContainer>
149+
</>
112150
)}
113151
</Field>
114152
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@use "scss/colors";
2+
@use "scss/variables";
3+
4+
.checkIcon {
5+
color: colors.$dark-blue-100;
6+
}
7+
8+
.detailTextContainer {
9+
min-width: fit-content;
10+
color: colors.$dark-blue;
11+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { faGoogle } from "@fortawesome/free-brands-svg-icons";
2+
import { faCheckCircle, faEnvelope } from "@fortawesome/free-solid-svg-icons";
3+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4+
import { useState } from "react";
5+
import { FormattedMessage } from "react-intl";
6+
7+
import { HeadTitle } from "components/common/HeadTitle";
8+
import { Button } from "components/ui/Button";
9+
import { FlexContainer } from "components/ui/Flex";
10+
import { Heading } from "components/ui/Heading";
11+
12+
import styles from "./SimpleLeftSide.module.scss";
13+
import { OAuthLogin } from "../../../OAuthLogin";
14+
import { Disclaimer, SignupForm } from "../SignupForm";
15+
16+
const Detail: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
17+
return (
18+
<FlexContainer gap="sm" alignItems="center" className={styles.detailTextContainer}>
19+
<FontAwesomeIcon icon={faCheckCircle} className={styles.checkIcon} />
20+
{children}
21+
</FlexContainer>
22+
);
23+
};
24+
export const SimpleLeftSide: React.FC = () => {
25+
const [showOauth, setShowOauth] = useState(true);
26+
return (
27+
<FlexContainer direction="column" gap="xl">
28+
<HeadTitle titles={[{ id: "login.signup" }]} />
29+
<Heading as="h1" centered>
30+
<FormattedMessage id="signup.title" />
31+
</Heading>
32+
33+
<FlexContainer justifyContent="center" alignItems="center">
34+
<Detail>
35+
<FormattedMessage id="signup.details.noCreditCard" />
36+
</Detail>
37+
<Detail>
38+
<FormattedMessage id="signup.details.instantSetup" />
39+
</Detail>
40+
<Detail>
41+
<FormattedMessage id="signup.details.freeTrial" />
42+
</Detail>
43+
</FlexContainer>
44+
{showOauth ? <OAuthLogin /> : <SignupForm />}
45+
46+
{showOauth ? (
47+
<Button
48+
onClick={() => setShowOauth(false)}
49+
variant="clear"
50+
size="sm"
51+
icon={<FontAwesomeIcon icon={faEnvelope} />}
52+
>
53+
<FormattedMessage id="signup.method.email" />
54+
</Button>
55+
) : (
56+
<Button onClick={() => setShowOauth(true)} variant="clear" size="sm" icon={<FontAwesomeIcon icon={faGoogle} />}>
57+
<FormattedMessage id="signup.method.oauth" />
58+
</Button>
59+
)}
60+
61+
<Disclaimer />
62+
</FlexContainer>
63+
);
64+
};

0 commit comments

Comments
 (0)