Merge pull request #3185 from llpingll/change-password
feat: Allow all users to change password (not just admins)
This commit is contained in:
5 files changed
+51
-12
No files matched your search
@@ -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")}
|
||||
|
||||
@@ -85,7 +85,7 @@ const Account = ({ open = "profile" }) => {
|
||||
))}
|
||||
</CustomTabList>
|
||||
<ProfilePanel />
|
||||
{user.role.includes("superadmin") && <PasswordPanel />}
|
||||
<PasswordPanel />
|
||||
{!hideTeams && <TeamPanel />}
|
||||
</TabContext>
|
||||
</Box>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user