diff --git a/apps/web/modules/auth/login-view.tsx b/apps/web/modules/auth/login-view.tsx index 2ea689b837..9059a61be3 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 c77e837262..39d773df22 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), + }; } - /> - ) : 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 ? ( + {/* Password */} + + {/* Cloudflare Turnstile Captcha */} + {CLOUDFLARE_SITE_ID ? ( + { + formMethods.setValue("cfToken", token); + }} + /> + ) : null} + + handleConsentChange(COOKIE_CONSENT)} + description={t("cookie_consent_checkbox")} + /> + {errors.apiError && ( + + )} - ) : null} +
-
+ )} + {!displayEmailForm && ( +
+ {/* Upper Row */} +
+ {isGoogleLoginEnabled ? ( + + ) : null} +
+ + {displayMiddleDivider && ( +
+
+
+ + {t("or").toLocaleLowerCase()} + +
+
+
+ )} + + {/* Lower Row */} +
+ + {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 82f783fa09..43701190ee 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 348994717c..48dbcdb424 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 0000000000..3f8813d5ca --- /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 598d14719b..6094b015c7 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 ( ); }