diff --git a/apps/web/components/ui/AuthContainer.tsx b/apps/web/components/ui/AuthContainer.tsx index d7baf8155f17a..03750a87a9cf9 100644 --- a/apps/web/components/ui/AuthContainer.tsx +++ b/apps/web/components/ui/AuthContainer.tsx @@ -17,7 +17,7 @@ interface Props { export default function AuthContainer(props: React.PropsWithChildren) { return ( -
+
{props.showLogo && ( @@ -34,7 +34,7 @@ export default function AuthContainer(props: React.PropsWithChildren) {
)}
-
+
{props.children}
{props.footerText}
diff --git a/apps/web/components/v2/ui/AuthContainer.tsx b/apps/web/components/v2/ui/AuthContainer.tsx index c593b4275787b..e74a03d982339 100644 --- a/apps/web/components/v2/ui/AuthContainer.tsx +++ b/apps/web/components/v2/ui/AuthContainer.tsx @@ -17,23 +17,21 @@ interface Props { export default function AuthContainer(props: React.PropsWithChildren) { return ( -
+
+ {props.showLogo && ( + // eslint-disable-next-line @next/next/no-img-element + Cal.com Logo + )}
- {props.showLogo && ( - // eslint-disable-next-line @next/next/no-img-element - Cal.com Logo - )} - {props.heading && ( -

{props.heading}

- )} + {props.heading &&

{props.heading}

}
{props.loading && (
)} -
+
{props.children}
diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 37a4a92532335..452ea549b675f 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -28,7 +28,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }); if (!maybeUser) { - return res.status(400).json({ message: "Couldn't find an account for this email" }); + // Don't leak information about whether an email is registered or not + return res + .status(200) + .json({ message: "If this email exists in our system, you should receive a Reset email." }); } const maybePreviousRequest = await prisma.resetPasswordRequest.findMany({ @@ -64,9 +67,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) /** So we can test the password reset flow on CI */ if (process.env.NEXT_PUBLIC_IS_E2E) { - return res.status(201).json({ message: "Reset Requested", resetLink }); + return res.status(201).json({ + message: "If this email exists in our system, you should receive a Reset email.", + resetLink, + }); } else { - return res.status(201).json({ message: "Reset Requested" }); + return res + .status(201) + .json({ message: "If this email exists in our system, you should receive a Reset email." }); } } catch (reason) { // console.error(reason); diff --git a/apps/web/pages/auth/forgot-password/[id].tsx b/apps/web/pages/auth/forgot-password/[id].tsx index 20592a685ea2b..d0d34297e6b77 100644 --- a/apps/web/pages/auth/forgot-password/[id].tsx +++ b/apps/web/pages/auth/forgot-password/[id].tsx @@ -7,10 +7,12 @@ import React, { useMemo } from "react"; import dayjs from "@calcom/dayjs"; import prisma from "@calcom/prisma"; +import { Button, Input, TextField } from "@calcom/ui/v2"; import { useLocale } from "@lib/hooks/useLocale"; import { HeadSeo } from "@components/seo/head-seo"; +import AuthContainer from "@components/v2/ui/AuthContainer"; type Props = { id: string; @@ -60,17 +62,12 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {

- {t("success")} + {t("password_updated")}

-

{t("password_has_been_reset_login")}

- - - +
); @@ -103,102 +100,68 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) { }, [resetPasswordRequest]); return ( -
- -
-
- {isRequestExpired && } - {!isRequestExpired && !success && ( - <> -
-

- {t("reset_password")} -

-

{t("enter_new_password")}

- {error &&

{error.message}

} -
-
{ - e.preventDefault(); - - if (!password) { - return; - } - - if (loading) { - return; - } - - setLoading(true); - setError(null); - setSuccess(false); - - await debouncedChangePassword({ password, requestId: resetPasswordRequest.id }); + + {isRequestExpired && } + {!isRequestExpired && !success && ( + <> + { + e.preventDefault(); + + if (!password) { + return; + } + + if (loading) { + return; + } + + setLoading(true); + setError(null); + setSuccess(false); + + await debouncedChangePassword({ password, requestId: resetPasswordRequest.id }); + }} + action="#"> + +
+ { + setPassword(e.target.value); }} - action="#"> - -
- -
- { - setPassword(e.target.value); - }} - id="password" - name="password" - type="password" - autoComplete="password" - required - className="focus:border-brand block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-sm placeholder-gray-400 focus:outline-none focus:ring-black" - /> -
-
- -
- -
- - - )} - {!isRequestExpired && success && ( - <> - - - )} -
-
-
+ id="password" + name="password" + type="password" + autoComplete="password" + required + /> +
+ +
+ +
+ + + )} + {!isRequestExpired && success && ( + <> + + + )} + ); } diff --git a/apps/web/pages/auth/forgot-password/index.tsx b/apps/web/pages/auth/forgot-password/index.tsx index 9c7fc6916b319..51b23a208ef46 100644 --- a/apps/web/pages/auth/forgot-password/index.tsx +++ b/apps/web/pages/auth/forgot-password/index.tsx @@ -5,13 +5,12 @@ import Link from "next/link"; import { useRouter } from "next/router"; import React, { SyntheticEvent } from "react"; -import Button from "@calcom/ui/Button"; -import { EmailField } from "@calcom/ui/form/fields"; +import { EmailField, Button } from "@calcom/ui/v2"; import { getSession } from "@lib/auth"; import { useLocale } from "@lib/hooks/useLocale"; -import AuthContainer from "@components/ui/AuthContainer"; +import AuthContainer from "@components/v2/ui/AuthContainer"; export default function ForgotPassword({ csrfToken }: { csrfToken: string }) { const { t, i18n } = useLocale(); @@ -75,33 +74,36 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) { const Success = () => { return ( -
-

{t("check_email_reset_password")}

+
+

{t("password_reset_email", { email })}

+

{t("password_reset_leading")}

{error &&

{error.message}

} +
); }; return ( - {t("already_have_an_account")}{" "} - - {t("login_instead")} - - + !success && ( + <> + + {t("back_to_signin")} + + + ) }> {success && } {!success && ( <> -
-

{t("reset_instructions")}

- {error &&

{error.message}

} -
+
{error &&

{error.message}

}
{ // Press Enter await Promise.all([ page.waitForNavigation({ - url: "/auth/forgot-password/*", + url: (u) => u.pathname.startsWith("/auth/forgot-password/"), }), page.press('input[name="email"]', "Enter"), ]); @@ -28,14 +28,16 @@ test("Can reset forgotten password", async ({ page, users }) => { // Click text=Submit await page.click('button[type="submit"]'); - await page.waitForSelector("text=Success", { + await page.waitForSelector("text=Password updated", { timeout: 3000, }); - await expect(page.locator(`text=Success`)).toBeVisible(); - + await expect(page.locator(`text=Password updated`)).toBeVisible(); // Click button:has-text("Login") - await Promise.all([page.waitForNavigation({ url: "/auth/login" }), page.click('button:has-text("Login")')]); + await Promise.all([ + page.waitForNavigation({ url: (u) => u.pathname.startsWith("/auth/login") }), + page.click('a:has-text("Login")'), + ]); // Fill input[name="email"] await page.fill('input[name="email"]', `${user.username}@example.com`); diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 056b454e3434c..d0cd0a5148b36 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -211,7 +211,7 @@ "sign_in": "Sign in", "go_back_login": "Go back to the login page", "error_during_login": "An error occurred when logging you in. Head back to the login screen and try again.", - "request_password_reset": "Request Password Reset", + "request_password_reset": "Send reset email", "forgot_password": "Forgot Password?", "forgot": "Forgot?", "done": "Done", @@ -1187,5 +1187,10 @@ "add_new_form": "Add new form", "form_description": "Create your form to route a booker", "copy_link_to_form": "Copy link to form", + "back_to_signin": "Back to sign in", + "reset_link_sent": "Reset link sent", + "password_reset_email":"An email is on it’s way to {{email}} with instructions to reset your password.", + "password_reset_leading":"If you did not receive the email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.", + "password_updated":"Password updated!", "pending_payment": "Pending payment" }