2024-06-18 20:16:32 -04:00
import TabPanel from "@mui/lab/TabPanel" ;
2024-09-26 11:01:38 +08:00
import { useState } from "react" ;
2024-06-18 20:16:32 -04:00
import { useTheme } from "@emotion/react" ;
2024-08-27 12:18:25 -04:00
import { Box , Stack , Typography } from "@mui/material" ;
2024-08-31 18:10:50 -04:00
import LoadingButton from "@mui/lab/LoadingButton" ;
2024-11-26 13:56:36 +08:00
import { PasswordEndAdornment } from "../../Inputs/TextInput/Adornments" ;
import TextInput from "../../Inputs/TextInput" ;
2024-07-15 20:51:18 -04:00
import { credentials } from "../../../Validation/validation" ;
2024-07-06 14:33:36 -04:00
import Alert from "../../Alert" ;
2024-07-10 18:26:05 -04:00
import { update } from "../../../Features/Auth/authSlice" ;
import { useDispatch , useSelector } from "react-redux" ;
2024-07-11 19:00:31 -04:00
import { createToast } from "../../../Utils/toastUtils" ;
2024-12-04 23:52:38 +08:00
import { getTouchedFieldErrors } from "../../../Validation/error" ;
const defaultPasswordsState = {
password : "" ,
newPassword : "" ,
confirm : "" ,
};
2024-06-18 20:16:32 -04:00
/**
* PasswordPanel component manages the form for editing password.
*
* @returns {JSX.Element}
*/
const PasswordPanel = () => {
2024-10-17 12:05:06 +08:00
const theme = useTheme ();
const dispatch = useDispatch ();
2024-07-10 18:26:05 -04:00
2025-01-07 21:27:52 -05:00
const SPACING_GAP = theme . spacing ( 12 );
2024-12-04 15:27:02 -07:00
2024-10-17 12:05:06 +08:00
//redux state
const { authToken , isLoading } = useSelector (( state ) => state . auth );
2024-06-18 20:16:32 -04:00
2024-10-17 12:05:06 +08:00
const idToName = {
"edit-current-password" : "password" ,
"edit-new-password" : "newPassword" ,
"edit-confirm-password" : "confirm" ,
};
2024-07-10 18:26:05 -04:00
2024-12-04 23:52:38 +08:00
const [ localData , setLocalData ] = useState ( defaultPasswordsState );
const [ errors , setErrors ] = useState ( defaultPasswordsState );
const [ touchedFields , setTouchedFields ] = useState ({
password : false ,
newPassword : false ,
confirm : false ,
2024-10-17 12:05:06 +08:00
});
2024-12-04 23:52:38 +08:00
2024-10-17 12:05:06 +08:00
const handleChange = ( event ) => {
const { value , id } = event . target ;
const name = idToName [ id ];
2024-12-04 23:52:38 +08:00
const updatedData = {
... localData ,
2024-10-17 12:05:06 +08:00
[ name ] : value ,
2024-12-04 23:52:38 +08:00
};
const updatedTouchedFields = {
... touchedFields ,
[ name ] : true ,
};
2024-06-22 18:39:11 -04:00
2024-10-17 12:05:06 +08:00
const validation = credentials . validate (
2024-12-04 23:52:38 +08:00
{ ... updatedData },
{ abortEarly : false , context : { password : updatedData . newPassword } }
2024-10-17 12:05:06 +08:00
);
2024-06-22 18:39:11 -04:00
2024-12-04 23:52:38 +08:00
const updatedErrors = getTouchedFieldErrors ( validation , updatedTouchedFields );
2024-06-23 17:41:40 -04:00
2024-12-04 23:52:38 +08:00
if ( ! touchedFields [ name ]) {
setTouchedFields ( updatedTouchedFields );
}
setLocalData ( updatedData );
setErrors ( updatedErrors );
2024-10-17 12:05:06 +08:00
};
2024-06-22 18:39:11 -04:00
2024-10-17 12:05:06 +08:00
const handleSubmit = async ( event ) => {
event . preventDefault ();
2024-06-23 17:41:40 -04:00
2024-10-17 12:05:06 +08:00
const { error } = credentials . validate ( localData , {
abortEarly : false ,
context : { password : localData . newPassword },
});
2024-06-23 17:41:40 -04:00
2024-10-17 12:05:06 +08:00
if ( error ) {
const newErrors = {};
error . details . forEach (( err ) => {
newErrors [ err . path [ 0 ]] = err . message ;
});
setErrors ( newErrors );
} else {
const action = await dispatch ( update ({ authToken , localData }));
if ( action . payload . success ) {
createToast ({
body : "Your password was changed successfully." ,
});
setLocalData ({
password : "" ,
newPassword : "" ,
confirm : "" ,
});
} else {
// TODO: Check for other errors?
createToast ({
body : "Your password input was incorrect." ,
});
setErrors ({ password : "*" + action . payload . msg + "." });
}
}
};
2024-06-23 17:41:40 -04:00
2024-10-17 12:05:06 +08:00
return (
< TabPanel
value = "password"
sx = {{
"& h1, & input" : {
2025-01-12 09:39:31 -05:00
color : theme . palette . primary . contrastTextTertiary ,
2024-10-17 12:05:06 +08:00
},
}}
>
< Stack
component = "form"
onSubmit = { handleSubmit }
noValidate
spellCheck = "false"
2024-10-31 14:43:09 -04:00
gap = { theme . spacing ( 26 )}
2025-01-07 21:27:52 -05:00
maxWidth = { "80ch" } // Keep maxWidth
2024-10-17 12:05:06 +08:00
>
2024-11-26 13:56:36 +08:00
< TextInput
2024-10-31 14:43:09 -04:00
type = "text"
id = "hidden-username"
name = "username"
autoComplete = "username"
hidden = { true }
value = ""
/>
< Stack
direction = "row"
justifyContent = { "flex-start" }
alignItems = { "center" }
2025-01-07 21:27:52 -05:00
gap = { SPACING_GAP }
2024-10-31 14:43:09 -04:00
flexWrap = { "wrap" }
>
< Typography
component = "h1"
width = "20ch"
>
Current password
</ Typography >
2024-11-26 13:56:36 +08:00
< TextInput
2024-10-17 12:05:06 +08:00
type = "password"
id = "edit-current-password"
placeholder = "Enter your current password"
autoComplete = "current-password"
value = { localData . password }
onChange = { handleChange }
2024-11-26 13:56:36 +08:00
error = { errors [ idToName [ "edit-current-password" ]] ? true : false }
helperText = { errors [ idToName [ "edit-current-password" ]]}
endAdornment = {< PasswordEndAdornment />}
flex = { 1 }
2024-10-17 12:05:06 +08:00
/>
</ Stack >
2024-10-31 14:43:09 -04:00
< Stack
direction = "row"
2025-01-03 14:30:12 -05:00
alignItems = { "flex-start" }
2025-01-07 21:27:52 -05:00
gap = { SPACING_GAP }
2024-10-31 14:43:09 -04:00
flexWrap = { "wrap" }
>
< Typography
component = "h1"
width = "20ch"
>
New password
</ Typography >
2024-11-26 13:56:36 +08:00
< TextInput
2024-10-17 12:05:06 +08:00
type = "password"
id = "edit-new-password"
placeholder = "Enter your new password"
autoComplete = "new-password"
value = { localData . newPassword }
onChange = { handleChange }
2024-11-26 13:56:36 +08:00
error = { errors [ idToName [ "edit-new-password" ]] ? true : false }
helperText = { errors [ idToName [ "edit-new-password" ]]}
endAdornment = {< PasswordEndAdornment />}
flex = { 1 }
2024-10-17 12:05:06 +08:00
/>
</ Stack >
2024-10-31 14:43:09 -04:00
< Stack
direction = "row"
2025-01-03 14:30:12 -05:00
alignItems = { "flex-start" }
2025-01-07 21:27:52 -05:00
gap = { SPACING_GAP }
2024-10-31 14:43:09 -04:00
flexWrap = { "wrap" }
>
< Typography
component = "h1"
width = "20ch"
>
Confirm new password
</ Typography >
2024-11-26 13:56:36 +08:00
< TextInput
2024-10-17 12:05:06 +08:00
type = "password"
id = "edit-confirm-password"
placeholder = "Reenter your new password"
autoComplete = "new-password"
value = { localData . confirm }
onChange = { handleChange }
2024-11-26 13:56:36 +08:00
error = { errors [ idToName [ "edit-confirm-password" ]] ? true : false }
helperText = { errors [ idToName [ "edit-confirm-password" ]]}
endAdornment = {< PasswordEndAdornment />}
flex = { 1 }
2024-10-17 12:05:06 +08:00
/>
</ Stack >
2024-10-31 14:43:09 -04:00
{ Object . keys ( errors ). length > 0 && (
< Box sx = {{ maxWidth : "70ch" }}>
2024-10-17 12:05:06 +08:00
< Alert
variant = "warning"
2024-10-31 14:43:09 -04:00
body = "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character."
2024-10-17 12:05:06 +08:00
/>
</ Box >
2024-10-31 14:43:09 -04:00
)}
2024-10-17 12:05:06 +08:00
< Stack
direction = "row"
justifyContent = "flex-end"
>
< LoadingButton
variant = "contained"
2025-01-12 13:03:09 -05:00
color = "accent"
2024-10-31 14:43:09 -04:00
type = "submit"
2024-10-17 12:05:06 +08:00
loading = { isLoading }
loadingIndicator = "Saving..."
2024-11-03 06:50:49 -05:00
disabled = {
2024-12-04 23:52:38 +08:00
Object . keys ( errors ). length > 0 ||
Object . values ( localData ). filter (( value ) => value === "" ). length > 0
2024-11-03 06:50:49 -05:00
}
2024-10-17 12:05:06 +08:00
sx = {{
px : theme . spacing ( 12 ),
mt : theme . spacing ( 20 ),
}}
>
Save
</ LoadingButton >
</ Stack >
</ Stack >
</ TabPanel >
);
2024-06-18 20:16:32 -04:00
};
PasswordPanel . propTypes = {
2024-10-17 12:05:06 +08:00
// No props are being passed to this component, hence no specific PropTypes are defined.
2024-06-21 19:08:42 -04:00
};
2024-06-18 20:16:32 -04:00
2025-01-07 21:27:52 -05:00
export default PasswordPanel ;