Merge pull request #3185 from llpingll/change-password

feat: Allow all users to change password (not just admins)
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-02-05 10:59:20 -08:00
commit 1e04345aa0
5 files changed
+51 -12

No files matched your search

+3 -1
View File
@@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Link as RouterLink } from "react-router-dom";
const TextLink = ({ text, linkText, href, target = "_self" }) => {
const TextLink = ({ text, linkText, href, state, target = "_self" }) => {
const theme = useTheme();
return (
@@ -19,6 +19,7 @@ const TextLink = ({ text, linkText, href, target = "_self" }) => {
to={href}
component={RouterLink}
target={target}
state={state}
>
{linkText}
</Link>
@@ -30,6 +31,7 @@ TextLink.propTypes = {
text: PropTypes.string,
linkText: PropTypes.string,
href: PropTypes.string,
state: PropTypes.object,
target: PropTypes.string,
};
@@ -11,6 +11,9 @@ import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../../Utils/toastUtils.jsx";
import { getTouchedFieldErrors } from "../../../Validation/error.js";
import { useTranslation } from "react-i18next";
import { useNavigate, useLocation } from "react-router-dom";
import { clearAuthState } from "../../../Features/Auth/authSlice.js";
import TextLink from "@/Components/v1/TextLink/index.jsx";
const defaultPasswordsState = {
password: "",
@@ -28,11 +31,13 @@ const PasswordPanel = () => {
const theme = useTheme();
const dispatch = useDispatch();
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const SPACING_GAP = theme.spacing(12);
//redux state
const { isLoading } = useSelector((state) => state.auth);
const { isLoading, authToken } = useSelector((state) => state.auth);
const idToName = {
"edit-current-password": "password",
@@ -79,6 +84,16 @@ const PasswordPanel = () => {
const handleSubmit = async (event) => {
event.preventDefault();
// Check for expired/invalid token before submitting
if (!authToken) {
createToast({
body: "Your session has expired. Please log in again.",
});
dispatch(clearAuthState());
navigate("/login");
return;
}
const { error } = newOrChangedCredentials.validate(localData, {
abortEarly: false,
context: { password: localData.newPassword },
@@ -249,8 +264,15 @@ const PasswordPanel = () => {
)}
<Stack
direction="row"
justifyContent="flex-end"
justifyContent="space-between"
alignItems="center"
>
<TextLink
text={"Forgot password?"}
linkText={"Reset password"}
state={{ from: location.pathname }}
href={"/forgot-password"}
/>
<Button
variant="contained"
color="accent"
@@ -263,7 +285,6 @@ const PasswordPanel = () => {
}
sx={{
px: theme.spacing(12),
mt: theme.spacing(20),
}}
>
{t("commonSave", "Save")}
+1 -1
View File
@@ -85,7 +85,7 @@ const Account = ({ open = "profile" }) => {
))}
</CustomTabList>
<ProfilePanel />
{user.role.includes("superadmin") && <PasswordPanel />}
<PasswordPanel />
{!hideTeams && <TeamPanel />}
</TabContext>
</Box>
+10 -3
View File
@@ -2,7 +2,7 @@ import { Box, Button, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { useNavigate, useLocation } from "react-router-dom";
import { createToast } from "../../Utils/toastUtils.jsx";
import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { Trans, useTranslation } from "react-i18next";
@@ -17,6 +17,7 @@ const CheckEmail = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { t } = useTranslation();
const location = useLocation();
const [email, setEmail] = useState();
const [disabled, setDisabled] = useState(false);
@@ -83,7 +84,9 @@ const CheckEmail = () => {
const handleNavigate = () => {
sessionStorage.removeItem("email");
navigate("/login");
location.state?.from
? navigate(location.state.from, { replace: true })
: navigate("/login");
};
return (
@@ -219,7 +222,11 @@ const CheckEmail = () => {
>
<Typography display="inline-block">
<Trans
i18nKey="auth.forgotPassword.links.login"
i18nKey={
location.state?.from === "/account/password"
? "Go back to <a>Account Password</a>"
: "auth.forgotPassword.links.login"
}
components={{
a: (
<Typography
+13 -4
View File
@@ -5,7 +5,7 @@ import { useDispatch, useSelector } from "react-redux";
import { forgotPassword } from "../../Features/Auth/authSlice.js";
import { useEffect, useState } from "react";
import { newOrChangedCredentials } from "../../Validation/validation.js";
import { useNavigate } from "react-router-dom";
import { useNavigate, useLocation } from "react-router-dom";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import Background from "@/assets/Images/background-grid.svg?react";
@@ -18,6 +18,7 @@ const ForgotPassword = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const theme = useTheme();
const location = useLocation();
const { isLoading } = useSelector((state) => state.auth);
const [errors, setErrors] = useState({});
@@ -51,7 +52,9 @@ const ForgotPassword = () => {
const action = await dispatch(forgotPassword(form));
if (action.payload.success) {
sessionStorage.setItem("email", form.email);
navigate("/check-email");
navigate("/check-email", {
state: location.state || { from: "/login" }, // Preserve original from, fallback to login
});
createToast({
body: t("auth.forgotPassword.toasts.sent").replace("<email/>", form.email),
});
@@ -86,7 +89,9 @@ const ForgotPassword = () => {
const handleNavigate = () => {
sessionStorage.removeItem("email");
navigate("/login");
location.state?.from
? navigate(location.state.from, { replace: true })
: navigate("/login");
};
return (
@@ -212,7 +217,11 @@ const ForgotPassword = () => {
>
<Typography display="inline-block">
<Trans
i18nKey="auth.forgotPassword.links.login"
i18nKey={
location.state?.from
? "Go back to <a>Account Password</a>"
: "auth.forgotPassword.links.login"
}
components={{
a: (
<Typography