diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index ae322156c..5e66667cb 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -10,6 +10,13 @@ import Alert from "../../Alert"; import { update } from "../../../Features/Auth/authSlice"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../../Utils/toastUtils"; +import { getTouchedFieldErrors } from "../../../Validation/error"; + +const defaultPasswordsState = { + password: "", + newPassword: "", + confirm: "", +}; /** * PasswordPanel component manages the form for editing password. @@ -30,35 +37,40 @@ const PasswordPanel = () => { "edit-confirm-password": "confirm", }; - const [localData, setLocalData] = useState({ - password: "", - newPassword: "", - confirm: "", + const [localData, setLocalData] = useState(defaultPasswordsState); + const [errors, setErrors] = useState(defaultPasswordsState); + const [touchedFields, setTouchedFields] = useState({ + password: false, + newPassword: false, + confirm: false, }); - const [errors, setErrors] = useState({}); + const handleChange = (event) => { const { value, id } = event.target; const name = idToName[id]; - setLocalData((prev) => ({ - ...prev, + + const updatedData = { + ...localData, [name]: value, - })); + }; + const updatedTouchedFields = { + ...touchedFields, + [name]: true, + }; const validation = credentials.validate( - { [name]: value }, - { abortEarly: false, context: { password: localData.newPassword } } + { ...updatedData }, + { abortEarly: false, context: { password: updatedData.newPassword } } ); - setErrors((prev) => { - const updatedErrors = { ...prev }; + const updatedErrors = getTouchedFieldErrors(validation, updatedTouchedFields); - if (validation.error) { - updatedErrors[name] = validation.error.details[0].message; - } else { - delete updatedErrors[name]; - } - return updatedErrors; - }); + if (!touchedFields[name]) { + setTouchedFields(updatedTouchedFields); + } + + setLocalData(updatedData); + setErrors(updatedErrors); }; const handleSubmit = async (event) => { @@ -220,8 +232,8 @@ const PasswordPanel = () => { loading={isLoading} loadingIndicator="Saving..." disabled={ - Object.keys(errors).length !== 0 || - Object.values(localData).filter((value) => value !== "").length === 0 + Object.keys(errors).length > 0 || + Object.values(localData).filter((value) => value === "").length > 0 } sx={{ px: theme.spacing(12), diff --git a/Client/src/Validation/error.js b/Client/src/Validation/error.js index 0d1359ad5..017fa9a46 100644 --- a/Client/src/Validation/error.js +++ b/Client/src/Validation/error.js @@ -8,6 +8,31 @@ const buildErrors = (prev, id, error) => { return updatedErrors; }; +/** + * Processes Joi validation errors and returns a filtered object of error messages for fields that have been touched. + * + * @param {Object} validation - The Joi validation result object. + * @param {Object} validation.error - The error property of the validation result containing details of validation failures. + * @param {Object[]} validation.error.details - An array of error details from the Joi validation. Each item contains information about the path and the message. + * @param {Object} touchedErrors - An object representing which fields have been interacted with. Keys are field IDs (field names), and values are booleans indicating whether the field has been touched. + * @returns {Object} - An object where keys are the field IDs (if they exist in `touchedErrors` and are in the error details) and values are their corresponding error messages. + */ +const getTouchedFieldErrors = (validation, touchedErrors) => { + let newErrors = {}; + + if (validation?.error) { + newErrors = validation.error.details.reduce((errors, detail) => { + const fieldId = detail.path[0]; + if (touchedErrors[fieldId] && !(fieldId in errors)) { + errors[fieldId] = detail.message; + } + return errors; + }, {}); + } + + return newErrors; +}; + const hasValidationErrors = (form, validation, setErrors) => { const { error } = validation.validate(form, { abortEarly: false, @@ -53,4 +78,4 @@ const hasValidationErrors = (form, validation, setErrors) => { } return false; }; -export { buildErrors, hasValidationErrors }; +export { buildErrors, hasValidationErrors, getTouchedFieldErrors };