From 96e464d54eb4ef9c126c46fe379123bd92cb2a2c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 9 Jun 2025 16:07:30 -0400 Subject: [PATCH 1/8] [Login, Signup, PasswordReset] Migrate from deprecated Grid --- src/components/InvalidLink/index.tsx | 72 +++--- src/components/Login/Captcha.tsx | 2 - src/components/Login/Login.tsx | 236 +++++++++--------- src/components/Login/Signup.tsx | 159 ++++++------ src/components/PasswordReset/Request.tsx | 74 +++--- src/components/PasswordReset/ResetPage.tsx | 195 ++++++--------- .../PasswordReset/tests/ResetPage.test.tsx | 89 ++----- 7 files changed, 364 insertions(+), 463 deletions(-) diff --git a/src/components/InvalidLink/index.tsx b/src/components/InvalidLink/index.tsx index 2ba1c9402e..5ce22d8e5b 100644 --- a/src/components/InvalidLink/index.tsx +++ b/src/components/InvalidLink/index.tsx @@ -1,5 +1,12 @@ import { Help } from "@mui/icons-material"; -import { Button, Card, CardContent, Grid, Typography } from "@mui/material"; +import { + Button, + Card, + CardContent, + CardHeader, + Grid2, + Typography, +} from "@mui/material"; import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; @@ -17,44 +24,47 @@ export default function InvalidLink(props: InvalidLinkProps): ReactElement { const idAffix = "invalid-link"; return ( - - + + + + {t(props.textId)} + + } + /> + - - {t(props.textId)} - {/* User Guide, Sign Up, and Log In buttons */} - - + + - + - - - + - - - - + + - + ); } diff --git a/src/components/Login/Captcha.tsx b/src/components/Login/Captcha.tsx index ca2ae8cd88..8e740176dd 100644 --- a/src/components/Login/Captcha.tsx +++ b/src/components/Login/Captcha.tsx @@ -6,7 +6,6 @@ import { toast } from "react-toastify"; import { verifyCaptchaToken } from "backend"; import i18n from "i18n"; import { RuntimeConfig } from "types/runtimeConfig"; -import theme from "types/theme"; export interface CaptchaProps { /** Parent function to call when CAPTCHA succeeds or fails. */ @@ -43,7 +42,6 @@ export default function Captcha(props: CaptchaProps): ReactElement { onSuccess={verify} options={{ language: i18n.resolvedLanguage, theme: "light" }} siteKey={RuntimeConfig.getInstance().captchaSiteKey()} - style={{ marginBottom: theme.spacing(1) }} /> ) : ( diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index bb5d43ce84..d862fbfd07 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -3,8 +3,10 @@ import { Button, Card, CardContent, - Grid, + CardHeader, + Grid2, Link, + Stack, TextFieldProps, Typography, } from "@mui/material"; @@ -29,9 +31,9 @@ import { type StoreState } from "rootRedux/types"; import router from "router/browserRouter"; import { Path } from "types/path"; import { RuntimeConfig } from "types/runtimeConfig"; -import theme from "types/theme"; import { NormalizedTextField } from "utilities/fontComponents"; import { openUserGuide } from "utilities/pathUtilities"; +import { meetsPasswordRequirements } from "utilities/utilities"; export enum LoginId { ButtonLogIn = "login-log-in-button", @@ -46,6 +48,7 @@ export enum LoginTextId { ButtonLogin = "login.login", ButtonSignUp = "login.signUp", Error401 = "login.failed", + ErrorPassword = "login.passwordRequirements", ErrorUnknown = "login.failedUnknownReason", FieldError = "login.required", LabelPassword = "login.password", @@ -90,10 +93,11 @@ export default function Login(): ReactElement { const logIn = (e: FormEvent): void => { e.preventDefault(); const p = password.trim(); + const pOk = meetsPasswordRequirements(p); const u = username.trim(); - setPasswordError(!p); + setPasswordError(!pOk); setUsernameError(!u); - if (p && u) { + if (pOk && u) { dispatch(asyncLogIn(u, p)); } }; @@ -108,129 +112,119 @@ export default function Login(): ReactElement { }); return ( - - -
- - {/* Title */} - + + + {/* Title */} + {t(LoginTextId.Title)} + } + /> + + + + + {/* Username field */} + + + {/* Password field */} + + + {/* "Forgot password?" link to reset password */} + {RuntimeConfig.getInstance().emailServicesEnabled() && ( + + router.navigate(Path.PwRequest)} + underline="hover" + variant="subtitle2" + > + {t(LoginTextId.LinkForgotPassword)} + + + )} + + {/* "Failed to log in" */} + {status === LoginStatus.Failure && ( + + {t( + loginError.includes("401") + ? LoginTextId.Error401 + : LoginTextId.ErrorUnknown + )} + + )} + + + + {/* User Guide, Sign Up, and Log In buttons */} + + + + - {/* Username field */} - - - {/* Password field */} - - - {/* "Forgot password?" link to reset password */} - {RuntimeConfig.getInstance().emailServicesEnabled() && ( - - router.navigate(Path.PwRequest)} - underline="hover" - variant="subtitle2" - > - {t(LoginTextId.LinkForgotPassword)} - - - )} - - {/* "Failed to log in" */} - {status === LoginStatus.Failure && ( - - {t( - loginError.includes("401") - ? LoginTextId.Error401 - : LoginTextId.ErrorUnknown - )} - - )} - - - - {/* User Guide, Sign Up, and Log In buttons */} - - - - - - - - - - - - {t(LoginTextId.ButtonLogin)} - - - - - - {/* Login announcement banner */} - {!!banner && ( - - {banner} - - )} - - + + + {t(LoginTextId.ButtonLogin)} + + + + {/* Login announcement banner */} + {!!banner && {banner}} + + +
-
+ ); } diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index 35ff3f1b3e..5c1756028d 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -2,7 +2,9 @@ import { Button, Card, CardContent, - Grid, + CardHeader, + Grid2, + Stack, TextField, TextFieldProps, Typography, @@ -187,90 +189,89 @@ export default function Signup(props: SignupProps): ReactElement { }); return ( - + -
- - {/* Title */} - + {/* Title */} + {t("login.signUpNew")} - - {/* Name field */} - - - {/* Username field */} - checkUsername()} - /> - - {/* Email field */} - {/* Don't use NormalizedTextField for type="email". - At best, it doesn't normalize, because of the punycode. */} - - - {/* Password field */} - checkPassword1()} - type="password" - /> - - {/* Confirm Password field */} - checkPassword2()} - type="password" - /> - - {/* "Failed to sign up" */} - {!!error && ( - - {t(error)} - - )} - - - - {/* Sign Up and Log In buttons */} - - + } + /> + + + + + {/* Name field */} + + + {/* Username field */} + checkUsername()} + /> + + {/* Email field */} + {/* Don't use NormalizedTextField for type="email". + At best, it doesn't normalize, because of the punycode. */} + + + {/* Password field */} + checkPassword1()} + type="password" + /> + + {/* Confirm Password field */} + checkPassword2()} + type="password" + /> + + {/* "Failed to sign up" */} + {!!error && ( + + {t(error)} + + )} + + + + {/* Sign Up and Log In buttons */} + - - + {t("login.signUp")} - - - -
+ + + +
-
+ ); } diff --git a/src/components/PasswordReset/Request.tsx b/src/components/PasswordReset/Request.tsx index f8acf7d088..ce6ee34c5a 100644 --- a/src/components/PasswordReset/Request.tsx +++ b/src/components/PasswordReset/Request.tsx @@ -1,4 +1,12 @@ -import { Button, Card, Grid, Typography } from "@mui/material"; +import { + Button, + Card, + CardContent, + CardHeader, + Grid2, + Stack, + Typography, +} from "@mui/material"; import { FormEvent, ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; @@ -40,59 +48,57 @@ export default function ResetRequest(): ReactElement { }; return ( - - - - {t("passwordReset.resetRequestTitle")} - - {isDone ? ( - <> - {t("passwordReset.resetDone")} - + + + + {t("passwordReset.resetRequestTitle")} + + } + /> + + + {isDone ? ( + + {t("passwordReset.resetDone")} + - - - ) : ( - <> - - {t("passwordReset.resetRequestInstructions")} - + + ) : (
- + + + {t("passwordReset.resetRequestInstructions")} + + setEmailOrUsername(e.target.value)} required - type="text" - style={{ width: "100%" }} value={emailOrUsername} - variant="outlined" /> - - + - - + onSubmit, + onClick: onSubmit, variant: "contained", }} disabled={!emailOrUsername || !isVerified} @@ -100,11 +106,11 @@ export default function ResetRequest(): ReactElement { > {t("passwordReset.submit")} - +
- - )} + )} +
-
+ ); } diff --git a/src/components/PasswordReset/ResetPage.tsx b/src/components/PasswordReset/ResetPage.tsx index ae4f6955de..ca5e7b79d0 100644 --- a/src/components/PasswordReset/ResetPage.tsx +++ b/src/components/PasswordReset/ResetPage.tsx @@ -1,5 +1,12 @@ -import ExitToAppIcon from "@mui/icons-material/ExitToApp"; -import { Button, Card, Grid, Typography } from "@mui/material"; +import { + Button, + Card, + CardContent, + CardHeader, + Grid2, + Stack, + Typography, +} from "@mui/material"; import { type FormEvent, type ReactElement, @@ -9,6 +16,7 @@ import { } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router"; +import { toast } from "react-toastify"; import { resetPassword, validateResetToken } from "backend"; import InvalidLink from "components/InvalidLink"; @@ -16,23 +24,12 @@ import { Path } from "types/path"; import { NormalizedTextField } from "utilities/fontComponents"; import { meetsPasswordRequirements } from "utilities/utilities"; -export enum PasswordResetTestIds { +export enum PasswordResetIds { Password = "PasswordReset.password", - PasswordReqError = "PasswordReset.requirements-error", ConfirmPassword = "PasswordReset.confirm-password", - PasswordMatchError = "PasswordReset.match-error", - PasswordResetFail = "PasswordReset.reset-fail", - BackToLoginButton = "PasswordReset.button.back-to-login", SubmitButton = "PasswordReset.button.submit", } -enum RequestState { - None, - Attempt, - Fail, - Success, -} - export default function PasswordReset(): ReactElement { const navigate = useNavigate(); const { token } = useParams(); @@ -44,7 +41,6 @@ export default function PasswordReset(): ReactElement { const [passwordConfirm, setPasswordConfirm] = useState(""); const [passwordFitsRequirements, setPasswordFitsRequirements] = useState(false); - const [requestState, setRequestState] = useState(RequestState.None); const validateLink = useCallback(async (): Promise => { if (token) { @@ -56,14 +52,8 @@ export default function PasswordReset(): ReactElement { validateLink(); }); - const backToLogin = (e: FormEvent): void => { - e.preventDefault(); - navigate(Path.Login); - }; - const onSubmit = async (e: FormEvent): Promise => { if (token) { - setRequestState(RequestState.Attempt); await asyncReset(token, password); e.preventDefault(); } @@ -81,114 +71,75 @@ export default function PasswordReset(): ReactElement { const asyncReset = async (token: string, password: string): Promise => { if (await resetPassword(token, password)) { - setRequestState(RequestState.Success); - navigate(Path.Login); + toast.success(t("passwordReset.resetSuccess")); } else { - setRequestState(RequestState.Fail); + toast.error(t("passwordReset.resetFail")); } + navigate(Path.Login); }; return isValidLink ? ( - - -
- - {t("passwordReset.resetTitle")} - - - - onChangePassword(e.target.value, passwordConfirm) - } - /> - {!passwordFitsRequirements && ( - - {t("login.passwordRequirements")} - - )} - - - 0} - onChange={(e) => onChangePassword(password, e.target.value)} - /> - {!isPasswordConfirmed && passwordConfirm.length > 0 && ( - - {t("login.confirmPasswordError")} - - )} - + + + + {t("passwordReset.resetTitle")} + + } + /> - - - {requestState === RequestState.Fail ? ( - <> - - {t("passwordReset.resetFail")} - - - - ) : ( - - )} - - - + +
+ + + onChangePassword(e.target.value, passwordConfirm) + } + type="password" + value={password} + /> + + 0} + fullWidth + helperText={ + !isPasswordConfirmed && + passwordConfirm.length > 0 && + t("login.confirmPasswordError") + } + id={PasswordResetIds.ConfirmPassword} + inputProps={{ + "data-testid": PasswordResetIds.ConfirmPassword, + }} + label={t("login.confirmPassword")} + onChange={(e) => onChangePassword(password, e.target.value)} + type="password" + value={passwordConfirm} + /> + + + +
+
-
+ ) : ( ); diff --git a/src/components/PasswordReset/tests/ResetPage.test.tsx b/src/components/PasswordReset/tests/ResetPage.test.tsx index c75acf6967..23a31a5d9e 100644 --- a/src/components/PasswordReset/tests/ResetPage.test.tsx +++ b/src/components/PasswordReset/tests/ResetPage.test.tsx @@ -13,7 +13,7 @@ import { MemoryRouter, Route, Routes } from "react-router"; import configureMockStore from "redux-mock-store"; import PasswordReset, { - PasswordResetTestIds, + PasswordResetIds, } from "components/PasswordReset/ResetPage"; import { Path } from "types/path"; @@ -65,115 +65,56 @@ const customRender = async ( }; describe("PasswordReset", () => { - it("renders with password length error", async () => { + it("disables button when password too short", async () => { const user = userEvent.setup(); await customRender(); const shortPassword = "foo"; - const passwdField = screen.getByTestId(PasswordResetTestIds.Password); - const passwdConfirm = screen.getByTestId( - PasswordResetTestIds.ConfirmPassword - ); + const passwdField = screen.getByTestId(PasswordResetIds.Password); + const passwdConfirm = screen.getByTestId(PasswordResetIds.ConfirmPassword); await user.type(passwdField, shortPassword); await user.type(passwdConfirm, shortPassword); - const reqErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordReqError - ); - const confirmErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordMatchError - ); - const submitButton = screen.getByTestId(PasswordResetTestIds.SubmitButton); - - expect(reqErrors.length).toBeGreaterThan(0); - expect(confirmErrors.length).toBe(0); + const submitButton = screen.getByTestId(PasswordResetIds.SubmitButton); expect(submitButton.closest("button")).toBeDisabled(); }); - it("renders with password match error", async () => { + it("disables button when passwords don't match", async () => { const user = userEvent.setup(); await customRender(); const passwordEntry = "password"; const confirmEntry = "passward"; - const passwdField = screen.getByTestId(PasswordResetTestIds.Password); - const passwdConfirm = screen.getByTestId( - PasswordResetTestIds.ConfirmPassword - ); + const passwdField = screen.getByTestId(PasswordResetIds.Password); + const passwdConfirm = screen.getByTestId(PasswordResetIds.ConfirmPassword); await user.type(passwdField, passwordEntry); await user.type(passwdConfirm, confirmEntry); - const reqErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordReqError - ); - const confirmErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordMatchError - ); - const submitButton = screen.getByTestId(PasswordResetTestIds.SubmitButton); - - expect(reqErrors.length).toBe(0); - expect(confirmErrors.length).toBeGreaterThan(0); + const submitButton = screen.getByTestId(PasswordResetIds.SubmitButton); expect(submitButton.closest("button")).toBeDisabled(); }); - it("renders with no password errors", async () => { + it("enables button when passwords are long enough and match", async () => { const user = userEvent.setup(); await customRender(); const passwordEntry = "password"; - const confirmEntry = "password"; - const passwdField = screen.getByTestId(PasswordResetTestIds.Password); - const passwdConfirm = screen.getByTestId( - PasswordResetTestIds.ConfirmPassword - ); + const passwdField = screen.getByTestId(PasswordResetIds.Password); + const passwdConfirm = screen.getByTestId(PasswordResetIds.ConfirmPassword); await user.type(passwdField, passwordEntry); - await user.type(passwdConfirm, confirmEntry); - - const reqErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordReqError - ); - const confirmErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordMatchError - ); - const submitButton = screen.getByTestId(PasswordResetTestIds.SubmitButton); + await user.type(passwdConfirm, passwordEntry); - expect(reqErrors.length).toBe(0); - expect(confirmErrors.length).toBe(0); + const submitButton = screen.getByTestId(PasswordResetIds.SubmitButton); expect(submitButton.closest("button")).toBeEnabled(); }); - it("renders with expire error", async () => { - // rerender the component with the resetFailure prop set. - const user = userEvent.setup(); - await customRender(); - - const passwordEntry = "password"; - const confirmEntry = "password"; - const passwdField = screen.getByTestId(PasswordResetTestIds.Password); - const passwdConfirm = screen.getByTestId( - PasswordResetTestIds.ConfirmPassword - ); - - await user.type(passwdField, passwordEntry); - await user.type(passwdConfirm, confirmEntry); - - const submitButton = screen.getByTestId(PasswordResetTestIds.SubmitButton); - mockPasswordReset.mockResolvedValueOnce(false); - await user.click(submitButton); - - const resetErrors = screen.queryAllByTestId( - PasswordResetTestIds.PasswordResetFail - ); - expect(resetErrors.length).toBeGreaterThan(0); - }); - it("renders the InvalidLink component if token not valid", async () => { mockValidateResetToken.mockResolvedValueOnce(false); await customRender(); - for (const id of Object.values(PasswordResetTestIds)) { + for (const id of Object.values(PasswordResetIds)) { expect(screen.queryAllByTestId(id)).toHaveLength(0); } // The textId will show up as text because t() is mocked to return its input. From 964fdea6262c5ce5474243b10cd01014d8e353df Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 9 Jun 2025 17:46:33 -0400 Subject: [PATCH 2/8] Add localization string --- public/locales/en/translation.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 20850a277f..0c43a7bd44 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -82,6 +82,7 @@ "resetRequestInstructions": "We will send a one-time reset link for your account to your email.", "submit": "Submit", "resetFail": "Password reset error", + "resetSuccess": "Password reset successful", "resetDone": "If you have correctly entered your email or username, a password reset link has been sent to your email address.", "backToLogin": "Back To Login" }, From b59d3e5e668f5fb97dfeee8718a14d5c7e5967f5 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 10 Jun 2025 15:38:14 -0400 Subject: [PATCH 3/8] Use sx for consistancy --- src/components/Login/Signup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index 5c1756028d..5553c1e40c 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -190,7 +190,7 @@ export default function Signup(props: SignupProps): ReactElement { return ( - + {/* Title */} Date: Tue, 10 Jun 2025 16:24:32 -0400 Subject: [PATCH 4/8] [ResetPage] Remove form that interacts poorly with token --- src/components/Login/Signup.tsx | 2 +- src/components/PasswordReset/ResetPage.tsx | 104 +++++++++------------ 2 files changed, 47 insertions(+), 59 deletions(-) diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index 5553c1e40c..f55912ce04 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -274,9 +274,9 @@ export default function Signup(props: SignupProps): ReactElement { => { + useEffect(() => { if (token) { - setIsValidLink(await validateResetToken(token)); + validateResetToken(token).then(setIsValidLink); } }, [token]); - useEffect(() => { - validateLink(); - }); - const onSubmit = async (e: FormEvent): Promise => { if (token) { await asyncReset(token, password); @@ -90,53 +80,51 @@ export default function PasswordReset(): ReactElement { /> -
- - - onChangePassword(e.target.value, passwordConfirm) - } - type="password" - value={password} - /> + + + onChangePassword(e.target.value, passwordConfirm) + } + type="password" + value={password} + /> - 0} - fullWidth - helperText={ - !isPasswordConfirmed && - passwordConfirm.length > 0 && - t("login.confirmPasswordError") - } - id={PasswordResetIds.ConfirmPassword} - inputProps={{ - "data-testid": PasswordResetIds.ConfirmPassword, - }} - label={t("login.confirmPassword")} - onChange={(e) => onChangePassword(password, e.target.value)} - type="password" - value={passwordConfirm} - /> + 0} + fullWidth + helperText={ + !isPasswordConfirmed && + passwordConfirm.length > 0 && + t("login.confirmPasswordError") + } + id={PasswordResetIds.ConfirmPassword} + inputProps={{ + "data-testid": PasswordResetIds.ConfirmPassword, + }} + label={t("login.confirmPassword")} + onChange={(e) => onChangePassword(password, e.target.value)} + type="password" + value={passwordConfirm} + /> - - - + +
From 5a1385e5b6734cb6b69da416b2189a0882aadfef Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 10 Jun 2025 16:32:43 -0400 Subject: [PATCH 5/8] Remove unused form --- src/components/PasswordReset/Request.tsx | 62 +++++++++++----------- src/components/PasswordReset/ResetPage.tsx | 4 +- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/components/PasswordReset/Request.tsx b/src/components/PasswordReset/Request.tsx index ce6ee34c5a..9ac6e4f5d3 100644 --- a/src/components/PasswordReset/Request.tsx +++ b/src/components/PasswordReset/Request.tsx @@ -73,41 +73,39 @@ export default function ResetRequest(): ReactElement { ) : ( -
- - - {t("passwordReset.resetRequestInstructions")} - + + + {t("passwordReset.resetRequestInstructions")} + - setEmailOrUsername(e.target.value)} - required - value={emailOrUsername} - /> + setEmailOrUsername(e.target.value)} + required + value={emailOrUsername} + /> - + - - {t("passwordReset.submit")} - - - + + {t("passwordReset.submit")} + +
)} diff --git a/src/components/PasswordReset/ResetPage.tsx b/src/components/PasswordReset/ResetPage.tsx index 1e5a2d896e..fca0f30ad0 100644 --- a/src/components/PasswordReset/ResetPage.tsx +++ b/src/components/PasswordReset/ResetPage.tsx @@ -106,9 +106,7 @@ export default function PasswordReset(): ReactElement { t("login.confirmPasswordError") } id={PasswordResetIds.ConfirmPassword} - inputProps={{ - "data-testid": PasswordResetIds.ConfirmPassword, - }} + inputProps={{ "data-testid": PasswordResetIds.ConfirmPassword }} label={t("login.confirmPassword")} onChange={(e) => onChangePassword(password, e.target.value)} type="password" From 5c5431c7b44983adecb640fd813d63c570eb4c1c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 10 Jun 2025 16:40:21 -0400 Subject: [PATCH 6/8] Restore useful form --- src/components/PasswordReset/Request.tsx | 62 ++++++++++++------------ 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/components/PasswordReset/Request.tsx b/src/components/PasswordReset/Request.tsx index 9ac6e4f5d3..4ec9698e8d 100644 --- a/src/components/PasswordReset/Request.tsx +++ b/src/components/PasswordReset/Request.tsx @@ -73,39 +73,41 @@ export default function ResetRequest(): ReactElement { ) : ( - - - {t("passwordReset.resetRequestInstructions")} - +
+ + + {t("passwordReset.resetRequestInstructions")} + - setEmailOrUsername(e.target.value)} - required - value={emailOrUsername} - /> + setEmailOrUsername(e.target.value)} + required + value={emailOrUsername} + /> - + - - {t("passwordReset.submit")} - - + + {t("passwordReset.submit")} + + +
)} From f3556822355d5d5f25a053a665ca982af1a1990c Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 10 Jun 2025 17:06:40 -0400 Subject: [PATCH 7/8] Get PasswordReset buttons to match Login buttons --- src/components/Login/Signup.tsx | 2 +- src/components/PasswordReset/Request.tsx | 40 ++++++++++++------- .../PasswordReset/tests/Request.test.tsx | 17 ++++---- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index f55912ce04..357bd0cd19 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -261,7 +261,7 @@ export default function Signup(props: SignupProps): ReactElement { - {/* Sign Up and Log In buttons */} + {/* Back-to-login and Sign-up buttons */} + + + {t("passwordReset.submit")} + +
)} diff --git a/src/components/PasswordReset/tests/Request.test.tsx b/src/components/PasswordReset/tests/Request.test.tsx index 44f4d16359..dd8a43479e 100644 --- a/src/components/PasswordReset/tests/Request.test.tsx +++ b/src/components/PasswordReset/tests/Request.test.tsx @@ -46,18 +46,21 @@ describe("ResetRequest", () => { await renderUserSettings(); // Before - const button = screen.getByRole("button"); - expect(button).toBeDisabled(); + const login = screen.getByTestId(PasswordRequestIds.ButtonLogin); + const submit = screen.getByTestId(PasswordRequestIds.ButtonSubmit); + expect(login).toBeEnabled(); + expect(submit).toBeDisabled(); // Agent const field = screen.getByTestId(PasswordRequestIds.FieldEmailOrUsername); await agent.type(field, "a"); // After - expect(button).toBeEnabled(); + expect(login).toBeEnabled(); + expect(submit).toBeEnabled(); }); - it("after submit, removes text field and submit button and reveals login button", async () => { + it("after submit, removes text field and submit button", async () => { // Setup const agent = userEvent.setup(); await renderUserSettings(); @@ -66,19 +69,19 @@ describe("ResetRequest", () => { expect( screen.queryByTestId(PasswordRequestIds.FieldEmailOrUsername) ).toBeTruthy(); + expect(screen.queryByTestId(PasswordRequestIds.ButtonLogin)).toBeTruthy(); expect(screen.queryByTestId(PasswordRequestIds.ButtonSubmit)).toBeTruthy(); - expect(screen.queryByTestId(PasswordRequestIds.ButtonLogin)).toBeNull(); // Agent const field = screen.getByTestId(PasswordRequestIds.FieldEmailOrUsername); await agent.type(field, "a"); - await agent.click(screen.getByRole("button")); + await agent.click(screen.getByTestId(PasswordRequestIds.ButtonSubmit)); // After expect( screen.queryByTestId(PasswordRequestIds.FieldEmailOrUsername) ).toBeNull(); - expect(screen.queryByTestId(PasswordRequestIds.ButtonSubmit)).toBeNull(); expect(screen.queryByTestId(PasswordRequestIds.ButtonLogin)).toBeTruthy(); + expect(screen.queryByTestId(PasswordRequestIds.ButtonSubmit)).toBeNull(); }); }); From 1180a54c0223a0c6b6a21c380f9729022a3118a8 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Thu, 19 Jun 2025 13:26:37 -0400 Subject: [PATCH 8/8] Use theme colors --- src/components/Login/Login.tsx | 2 +- src/components/Login/Signup.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index d862fbfd07..d4b1af267e 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -174,7 +174,7 @@ export default function Login(): ReactElement { {/* "Failed to log in" */} {status === LoginStatus.Failure && ( - + {t( loginError.includes("401") ? LoginTextId.Error401 diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index 357bd0cd19..f491a57124 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -254,7 +254,7 @@ export default function Signup(props: SignupProps): ReactElement { {/* "Failed to sign up" */} {!!error && ( - + {t(error)} )}