diff --git a/apps/web/modules/auth/login-view.tsx b/apps/web/modules/auth/login-view.tsx index 2ea689b8373dae..9059a61be3d28d 100644 --- a/apps/web/modules/auth/login-view.tsx +++ b/apps/web/modules/auth/login-view.tsx @@ -39,7 +39,7 @@ interface LoginValues { } const GoogleIcon = () => ( - + ); export type PageProps = inferSSRProps; export default function Login({ @@ -95,12 +95,6 @@ PageProps & WithNonceProps<{}>) { callbackUrl = safeCallbackUrl || ""; - const LoginFooter = ( - - {t("dont_have_an_account")} - - ); - const TwoFactorFooter = ( <> + )} + + {isGoogleLoginEnabled && ( +
+
+
+ + {t("or").toLocaleLowerCase()} + +
+
+
+ )} + + )} +
@@ -233,7 +284,7 @@ PageProps & WithNonceProps<{}>) { {errorMessage && }
- {!twoFactorRequired && ( - <> - {(isGoogleLoginEnabled || displaySSOLogin) &&
} -
- {isGoogleLoginEnabled && ( - - )} - {displaySSOLogin && ( - - )} -
- - )} diff --git a/apps/web/modules/signup-view.tsx b/apps/web/modules/signup-view.tsx index c77e837262d452..39d773df223099 100644 --- a/apps/web/modules/signup-view.tsx +++ b/apps/web/modules/signup-view.tsx @@ -46,9 +46,9 @@ import { TextField, Form, Alert, - showToast, CheckboxField, Icon, + showToast, } from "@calcom/ui"; import type { getServerSideProps } from "@lib/signup/getServerSideProps"; @@ -179,9 +179,12 @@ export default function Signup({ redirectUrl, emailVerificationEnabled, }: SignupProps) { + const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username; + const displayMiddleDivider = isGoogleLoginEnabled; // Add the isOutlookLoginEnabled flag here when Outlook login is added const [premiumUsername, setPremiumUsername] = useState(false); const [usernameTaken, setUsernameTaken] = useState(false); const [isGoogleLoading, setIsGoogleLoading] = useState(false); + const [displayEmailForm, setDisplayEmailForm] = useState(token); const searchParams = useCompatSearchParams(); const telemetry = useTelemetry(); const { t, i18n } = useLocale(); @@ -210,6 +213,7 @@ export default function Signup({ } const loadingSubmitState = isSubmitSuccessful || isSubmitting; + const displayBackButton = token ? false : displayEmailForm; const handleErrorsAndStripe = async (resp: Response) => { if (!resp.ok) { @@ -229,7 +233,6 @@ export default function Signup({ } }; - const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username; const isPlatformUser = redirectUrl?.includes("platform") && redirectUrl?.includes("new"); const signUp: SubmitHandler = async (_data) => { @@ -333,6 +336,19 @@ export default function Signup({ {/* Left side */}
+ {displayBackButton && ( +
+ +
+ )}

{IS_CALCOM ? t("create_your_calcom_account") : t("create_your_account")} @@ -347,197 +363,220 @@ export default function Signup({

)}

+ {/* Form Container */} -
-
{ - let updatedValues = values; - if (!formMethods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) { - updatedValues = { - ...values, - username: getOrgUsernameFromEmail(values.email, orgAutoAcceptEmail), - }; - } - await signUp(updatedValues); - }}> - {/* Username */} - {!isOrgInviteByLink ? ( - setUsernameTaken(value)} - data-testid="signup-usernamefield" - setPremium={(value) => setPremiumUsername(value)} - addOnLeading={ - orgSlug - ? `${getOrgFullOrigin(orgSlug, { protocol: true }).replace(URL_PROTOCOL_REGEX, "")}/` - : `${process.env.NEXT_PUBLIC_WEBSITE_URL.replace(URL_PROTOCOL_REGEX, "")}/` + {displayEmailForm && ( +
+ { + let updatedValues = values; + if (!formMethods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) { + updatedValues = { + ...values, + username: getOrgUsernameFromEmail(values.email, orgAutoAcceptEmail), + }; } + await signUp(updatedValues); + }}> + {/* Username */} + {!isOrgInviteByLink ? ( + setUsernameTaken(value)} + data-testid="signup-usernamefield" + setPremium={(value) => setPremiumUsername(value)} + addOnLeading={ + orgSlug + ? `${getOrgFullOrigin(orgSlug, { protocol: true }).replace( + URL_PROTOCOL_REGEX, + "" + )}/` + : `${process.env.NEXT_PUBLIC_WEBSITE_URL.replace(URL_PROTOCOL_REGEX, "")}/` + } + /> + ) : null} + {/* Email */} + - ) : null} - {/* Email */} - - - {/* Password */} - - {/* Cloudflare Turnstile Captcha */} - {CLOUDFLARE_SITE_ID ? ( - { - formMethods.setValue("cfToken", token); - }} - /> - ) : null} - - handleConsentChange(COOKIE_CONSENT)} - description={t("cookie_consent_checkbox")} - /> - {errors.apiError && ( - - )} - - - {!isGoogleLoginEnabled && !isSAMLLoginEnabled ? null : ( -
-
-
- - {t("or_continue_with")} - -
-
-
- )} -
- {isGoogleLoginEnabled ? ( - - ) : null} - {isSAMLLoginEnabled ? ( + handleConsentChange(COOKIE_CONSENT)} + description={t("cookie_consent_checkbox")} + /> + {errors.apiError && ( + + )} + +
+ )} + {!displayEmailForm && ( +
+ {/* Upper Row */} +
+ {isGoogleLoginEnabled ? ( + + ) : null} +
- return; - } - const username = formMethods.getValues("username"); - if (!username) { - showToast("error", t("username_required")); - return; - } - localStorage.setItem("username", username); - const sp = new URLSearchParams(); - // @NOTE: don't remove username query param as it's required right now for stripe payment page - sp.set("username", username); - sp.set("email", formMethods.getValues("email")); - router.push( - `${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/sso/saml` + `?${sp.toString()}` - ); - }}> - - {t("saml_sso")} + {displayMiddleDivider && ( +
+
+
+ + {t("or").toLocaleLowerCase()} + +
+
+
+ )} + + {/* Lower Row */} +
+ - ) : null} + {isSAMLLoginEnabled ? ( + + ) : null} +
-
+ )} + {/* Already have an account & T&C */}
diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts index 82f783fa098fa2..43701190ee4a5c 100644 --- a/apps/web/playwright/organization/organization-invitation.e2e.ts +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -296,6 +296,8 @@ test.describe("Organization", () => { await test.step("Signing up with the previous username of the migrated user - shouldn't be allowed", async () => { await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); const usernameInput = page.locator('input[name="username"]'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/apps/web/playwright/signup.e2e.ts b/apps/web/playwright/signup.e2e.ts index 348994717c6dc7..48dbcdb4244b23 100644 --- a/apps/web/playwright/signup.e2e.ts +++ b/apps/web/playwright/signup.e2e.ts @@ -10,7 +10,27 @@ import { expectInvitationEmailToBeReceived } from "./team/expects"; test.describe.configure({ mode: "parallel" }); -test.describe("Signup Flow Test", async () => { +test.describe("Signup Main Page Test", async () => { + test.beforeEach(async ({ features }) => { + features.reset(); + }); + + test("Continue with email button must exist / work", async ({ page }) => { + await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); + await expect(page.locator("text=Create your account")).toBeVisible(); + }); + + test("Continue with google button must exist / work", async ({ page }) => { + await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-google-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-google-button"]').click(); + await page.waitForURL("/auth/sso/google"); + }); +}); + +test.describe("Email Signup Flow Test", async () => { test.beforeEach(async ({ features }) => { features.reset(); // This resets to the inital state not an empt yarray }); @@ -25,6 +45,8 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); const alertMessage = "Username or email is already taken"; @@ -52,7 +74,8 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); - + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); const alertMessage = "Username or email is already taken"; @@ -84,6 +107,8 @@ test.describe("Signup Flow Test", async () => { // Signup with premium username name await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); // Fill form @@ -112,6 +137,8 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); // Fill form @@ -131,6 +158,8 @@ test.describe("Signup Flow Test", async () => { test("Signup fields prefilled with query params", async ({ page, users }) => { const signupUrlWithParams = "/signup?username=rick-jones&email=rick-jones%40example.com"; await page.goto(signupUrlWithParams); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); // Fill form @@ -212,6 +241,8 @@ test.describe("Signup Flow Test", async () => { }); await page.goto("/signup"); + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); await expect(page.locator("text=Create your account")).toBeVisible(); // Fill form @@ -283,7 +314,8 @@ test.describe("Signup Flow Test", async () => { const url = new URL(newPage.url()); expect(url.pathname).toBe("/signup"); - + await expect(page.locator('[data-testid="continue-with-email-button"]')).toBeVisible(); + await page.locator('[data-testid="continue-with-email-button"]').click(); // Check required fields await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); await newPage.locator("button[type=submit]").click(); diff --git a/apps/web/public/google-icon-colored.svg b/apps/web/public/google-icon-colored.svg new file mode 100644 index 00000000000000..3f8813d5cafd37 --- /dev/null +++ b/apps/web/public/google-icon-colored.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/packages/features/auth/SAMLLogin.tsx b/packages/features/auth/SAMLLogin.tsx index 598d14719b4093..6094b015c7ac2d 100644 --- a/packages/features/auth/SAMLLogin.tsx +++ b/packages/features/auth/SAMLLogin.tsx @@ -36,10 +36,9 @@ export function SAMLLogin({ samlTenantID, samlProductID, setErrorMessage }: Prop return ( ); }