From 9b3f73da0e4c5db34a069650e024788ec95da5a2 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:11:06 -0400 Subject: [PATCH 01/21] feat: getting rid of unnecessary ids and changing validation format --- .chores.md | 12 ++ Client/src/App.jsx | 16 +-- Client/src/Components/Check/Check.jsx | 5 +- Client/src/Pages/Auth/SetNewPassword.jsx | 133 ++++++++++++----------- Client/src/Validation/validation.js | 27 +++-- 5 files changed, 112 insertions(+), 81 deletions(-) create mode 100644 .chores.md diff --git a/.chores.md b/.chores.md new file mode 100644 index 000000000..ba0fd4dd7 --- /dev/null +++ b/.chores.md @@ -0,0 +1,12 @@ +- [ ] Take validation out from the component. + - [ ] SetNewPassword + - [ ] Register +- [ ] Create Check with highlight component. Check receives: status and message +- [ ] Split some of these user feedbacks? Like: + - [ ] Must contain at least one special character, + - [ ] Must contain at least one number, + - [ ] Must contain at least one upper character, + - [ ] Must contain at least one lower character +- [ ] Pass mismatching password user feedback as the other messages, for consistency +- [ ] Get rid of toast message +- [ ] Do it on blur diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 98772ecca..6cb2f433a 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -1,7 +1,10 @@ +import { useEffect } from "react"; import { Routes, Route } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useDispatch } from "react-redux"; +// import "./App.css"; import "react-toastify/dist/ReactToastify.css"; import { ToastContainer } from "react-toastify"; -// import "./App.css"; import NotFound from "./Pages/NotFound"; import Login from "./Pages/Auth/Login"; import Register from "./Pages/Auth/Register/Register"; @@ -21,21 +24,18 @@ import ProtectedRoute from "./Components/ProtectedRoute"; import Details from "./Pages/Monitors/Details"; import AdvancedSettings from "./Pages/AdvancedSettings"; import Maintenance from "./Pages/Maintenance"; -import withAdminCheck from "./HOC/withAdminCheck"; -import withAdminProp from "./HOC/withAdminProp"; import Configure from "./Pages/Monitors/Configure"; import PageSpeed from "./Pages/PageSpeed"; import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed"; import CreateNewMaintenanceWindow from "./Pages/Maintenance/CreateMaintenance"; import PageSpeedDetails from "./Pages/PageSpeed/Details"; import PageSpeedConfigure from "./Pages/PageSpeed/Configure"; +import withAdminCheck from "./HOC/withAdminCheck"; +import withAdminProp from "./HOC/withAdminProp"; import { ThemeProvider } from "@emotion/react"; import lightTheme from "./Utils/Theme/lightTheme"; import darkTheme from "./Utils/Theme/darkTheme"; -import { useSelector } from "react-redux"; import { CssBaseline } from "@mui/material"; -import { useEffect } from "react"; -import { useDispatch } from "react-redux"; import { getAppSettings } from "./Features/Settings/settingsSlice"; import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; @@ -181,16 +181,18 @@ function App() { path="/register" element={} /> + } /> - {/* } /> */} + } /> + } diff --git a/Client/src/Components/Check/Check.jsx b/Client/src/Components/Check/Check.jsx index 7ef51137e..9c1ec57fb 100644 --- a/Client/src/Components/Check/Check.jsx +++ b/Client/src/Components/Check/Check.jsx @@ -20,7 +20,7 @@ import { useTheme } from "@emotion/react"; * * @returns {React.Element} The `Check` component with a check icon and a label, defined by the `text` prop. */ -const Check = ({ text, variant = "info", outlined = false }) => { +const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => { const theme = useTheme(); const colors = { success: theme.palette.success.main, @@ -54,7 +54,7 @@ const Check = ({ text, variant = "info", outlined = false }) => { opacity: 0.8, }} > - {text} + {noHighlightText} {text} ); @@ -62,6 +62,7 @@ const Check = ({ text, variant = "info", outlined = false }) => { Check.propTypes = { text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, + noHighlightText: PropTypes.string, variant: PropTypes.oneOf(["info", "error", "success"]), outlined: PropTypes.bool, }; diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 6c8f7418e..9d1497b47 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,20 +1,20 @@ +import { useState } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; import { setNewPassword } from "../../Features/Auth/authSlice"; import { createToast } from "../../Utils/toastUtils"; -import { Box, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useParams } from "react-router-dom"; -import { useState } from "react"; import { credentials } from "../../Validation/validation"; -import { useNavigate } from "react-router-dom"; import Check from "../../Components/Check/Check"; import Field from "../../Components/Inputs/Field"; -import LockIcon from "../../assets/icons/lock.svg?react"; -import Background from "../../assets/Images/background-grid.svg?react"; -import Logo from "../../assets/icons/bwu-icon.svg?react"; -import LoadingButton from "@mui/lab/LoadingButton"; -import "./index.css"; import { IconBox } from "./styled"; +import LockIcon from "../../assets/icons/lock.svg?react"; +import Logo from "../../assets/icons/bwu-icon.svg?react"; +import Background from "../../assets/Images/background-grid.svg?react"; +import "./index.css"; const SetNewPassword = () => { const navigate = useNavigate(); @@ -27,11 +27,6 @@ const SetNewPassword = () => { confirm: "", }); - const idMap = { - "register-password-input": "password", - "confirm-password-input": "confirm", - }; - const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -41,7 +36,7 @@ const SetNewPassword = () => { const passwordForm = { ...form }; const { error } = credentials.validate(passwordForm, { abortEarly: false, - context: { password: form.password }, + /* context: { password: form.password }, */ }); if (error) { @@ -82,8 +77,8 @@ const SetNewPassword = () => { }; const handleChange = (event) => { - const { value, id } = event.target; - const name = idMap[id]; + //TODO Change from id to name + const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value, @@ -91,15 +86,35 @@ const SetNewPassword = () => { const { error } = credentials.validate( { [name]: value }, - { abortEarly: false, context: { password: form.password } } + { + abortEarly: false, + context: { password: form.password }, + } ); - setErrors((prev) => { - const prevErrors = { ...prev }; - if (error) prevErrors[name] = error.details[0].message; - else delete prevErrors[name]; - return prevErrors; - }); + const errors = error.details.map((error) => error.message); + + setErrors((prev) => ({ ...prev, [name]: errors })); + }; + + console.log(errors); + + /* function getCheckStatus() */ + + const feedbacks = { + length: "info" /* !errors.password + ? "info" + : errors.password === "length" || errors.password === "empty" + ? "error" + : "success", */, + special: "info" /* !errors + ? "info" + : errors.password === "length" || errors.password === "empty" + ? "error" + : "success", */, + number: "info", + upper: "success", + lower: "error", }; return ( @@ -189,7 +204,7 @@ const SetNewPassword = () => { > { > { characters long } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : form.password.length < 8 - ? "error" - : "success" - } - /> - - Must contain one special - character and a number - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[!@#$%^&*(),.?":{}|])(?=.*\d).+$/.test(form.password) - ? "error" - : "success" - } + variant={feedbacks.length} /> Must contain at least one - upper and lower character + special character } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[A-Z])(?=.*[a-z]).+$/.test(form.password) - ? "error" - : "success" + variant={feedbacks.special} + /> + + Must contain at least one + one number + } + variant={feedbacks.number} + /> + + Must contain at least one + upper character + + } + variant={feedbacks.upper} + /> + + Must contain at least one + lower character + + } + variant={feedbacks.lower} /> diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index c620bdf43..82dee8541 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -17,26 +17,33 @@ const passwordSchema = joi .trim() .min(8) .messages({ - "string.empty": "Password is required", - "string.min": "Password must be at least 8 characters long", + "string.empty": "empty", + "string.min": "length", }) .custom((value, helpers) => { if (!/[A-Z]/.test(value)) { - return helpers.message("Password must contain at least one uppercase letter"); + return helpers.message("uppercase"); } + return value; + }) + .custom((value, helpers) => { if (!/[a-z]/.test(value)) { - return helpers.message("Password must contain at least one lowercase letter"); + return helpers.message("lowercase"); } + return value; + }) + .custom((value, helpers) => { if (!/\d/.test(value)) { - return helpers.message("Password must contain at least one number"); + return helpers.message("number"); } + return value; + }) + .custom((value, helpers) => { if (!/[!@#$%^&*]/.test(value)) { - return helpers.message("Password must contain at least one special character"); + return helpers.message("special"); } - return value; }); - const credentials = joi.object({ firstName: nameSchema, lastName: nameSchema, @@ -61,12 +68,12 @@ const credentials = joi.object({ .string() .trim() .messages({ - "string.empty": "Password confirmation is required", + "string.empty": "empty", }) .custom((value, helpers) => { const { password } = helpers.prefs.context; if (value !== password) { - return helpers.message("Passwords do not match"); + return helpers.message("different"); } return value; }), From 6905f49d3b21f724269a76d49518f845a01224ff Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:51:52 -0400 Subject: [PATCH 02/21] feat: validation on change working for SetNewPassword --- Client/src/Pages/Auth/SetNewPassword.jsx | 84 ++++++++++++++---------- Client/src/Validation/validation.js | 2 +- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 9d1497b47..9edd13d97 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useId, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; @@ -21,6 +21,9 @@ const SetNewPassword = () => { const dispatch = useDispatch(); const theme = useTheme(); + const passwordId = useId(); + const confirmPasswordId = useId(); + const [errors, setErrors] = useState({}); const [form, setForm] = useState({ password: "", @@ -36,11 +39,10 @@ const SetNewPassword = () => { const passwordForm = { ...form }; const { error } = credentials.validate(passwordForm, { abortEarly: false, - /* context: { password: form.password }, */ + context: { password: form.password }, }); if (error) { - // validation errors const newErrors = {}; error.details.forEach((err) => { newErrors[err.path[0]] = err.message; @@ -77,46 +79,43 @@ const SetNewPassword = () => { }; const handleChange = (event) => { - //TODO Change from id to name const { value, name } = event.target; - setForm((prev) => ({ - ...prev, - [name]: value, - })); + setForm((prev) => ({ ...prev, [name]: value })); - const { error } = credentials.validate( - { [name]: value }, - { - abortEarly: false, - context: { password: form.password }, - } - ); - - const errors = error.details.map((error) => error.message); + const validateValue = { [name]: value }; + const validateOptions = { abortEarly: false, context: { password: form.password } }; + const { error } = credentials.validate(validateValue, validateOptions); + const errors = error?.details.map((error) => error.message); setErrors((prev) => ({ ...prev, [name]: errors })); }; - console.log(errors); + const getFeedbackStatus = (field, criteria) => { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; - /* function getCheckStatus() */ + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } + }; const feedbacks = { - length: "info" /* !errors.password - ? "info" - : errors.password === "length" || errors.password === "empty" - ? "error" - : "success", */, - special: "info" /* !errors - ? "info" - : errors.password === "length" || errors.password === "empty" - ? "error" - : "success", */, - number: "info", - upper: "success", - lower: "error", + length: getFeedbackStatus("password", "length"), + special: getFeedbackStatus("password", "special"), + number: getFeedbackStatus("password", "number"), + uppercase: getFeedbackStatus("password", "uppercase"), + lowercase: getFeedbackStatus("password", "lowercase"), + confirm: getFeedbackStatus("confirm", "different"), }; + console.log({ feedbacks }); + return ( { onSubmit={handleSubmit} > { placeholder="••••••••" value={form.password} onChange={handleChange} - error={errors.password} + /* error={errors.password} */ /> { onSubmit={handleSubmit} > { placeholder="••••••••" value={form.confirm} onChange={handleChange} - error={errors.confirm} + /* error={errors.confirm} */ /> { upper character } - variant={feedbacks.upper} + variant={feedbacks.uppercase} /> { lower character } - variant={feedbacks.lower} + variant={feedbacks.lowercase} + /> + + + Confirm password and password + {" "} + must match + + } + variant={feedbacks.confirm} /> diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index 82dee8541..79533ee1b 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -39,7 +39,7 @@ const passwordSchema = joi return value; }) .custom((value, helpers) => { - if (!/[!@#$%^&*]/.test(value)) { + if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) { return helpers.message("special"); } return value; From bd059720def99d9328ab69af8628949f4005e7c7 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Wed, 23 Oct 2024 19:57:21 -0400 Subject: [PATCH 03/21] feat: sanitizing checks to reduce code repetition --- Client/src/Pages/Auth/SetNewPassword.jsx | 52 ++++++------------------ 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 9edd13d97..63ba3af1c 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -114,8 +114,6 @@ const SetNewPassword = () => { confirm: getFeedbackStatus("confirm", "different"), }; - console.log({ feedbacks }); - return ( { mb={theme.spacing(12)} > - Must be at least 8 - characters long - - } + noHighlightText={"Must be at least"} + text={"8 characters long"} variant={feedbacks.length} /> - Must contain at least one - special character - - } + noHighlightText={"Must contain at least"} + text={"one special character"} variant={feedbacks.special} /> - Must contain at least one - one number - - } + noHighlightText={"Must contain at least"} + text={"one number"} variant={feedbacks.number} /> - Must contain at least one - upper character - - } + noHighlightText={"Must contain at least"} + text={"one upper character"} variant={feedbacks.uppercase} /> - Must contain at least one - lower character - - } + noHighlightText={"Must contain at least"} + text={"one lower character"} variant={feedbacks.lowercase} /> - - Confirm password and password - {" "} - must match - - } + noHighlightText={"Confirm password and password"} + text={"must match"} variant={feedbacks.confirm} /> From ea4284a363f352ab728f6c9a4bf8d5f2aba3ae78 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Sat, 26 Oct 2024 19:20:53 -0400 Subject: [PATCH 04/21] feat: Check for confirm password if password changes and confirm is filled, and they do not match anymore --- .chores.md | 2 ++ Client/src/Pages/Auth/SetNewPassword.jsx | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.chores.md b/.chores.md index 1b45b2361..b10e56264 100644 --- a/.chores.md +++ b/.chores.md @@ -11,3 +11,5 @@ - [x] Must contain at least one lower character - [x] Pass mismatching password user feedback as the other messages, for consistency - [ ] Get rid of toast message +- [x] Check for confirm password if password changes and confirm is filled, and they do not match anymore +- [ ] Check for confirm password if password changes, and they now match diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 63ba3af1c..103dff4d5 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -79,15 +79,35 @@ const SetNewPassword = () => { }; const handleChange = (event) => { + //update form state const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value })); + //validate const validateValue = { [name]: value }; const validateOptions = { abortEarly: false, context: { password: form.password } }; + if (name === "password" && form.confirm.length > 0) { + validateValue.confirm = form.confirm; + validateOptions.context = value; + } const { error } = credentials.validate(validateValue, validateOptions); - const errors = error?.details.map((error) => error.message); - setErrors((prev) => ({ ...prev, [name]: errors })); + //update errors + //fazer o map com path e message + const errors = error?.details.map((error) => ({ + message: error.message, + path: error.path[0], + })); + const errorsByPath = errors + ? errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}) + : { [name]: [] }; + setErrors((prev) => ({ ...prev, ...errorsByPath })); }; const getFeedbackStatus = (field, criteria) => { From 686217944d8fd243db820df5ca1adaece7f9e246 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 06:09:59 -0400 Subject: [PATCH 05/21] feat: breaking step components and adding custom hook for password validation --- .chores.md | 18 +- Client/src/Pages/Auth/Register/Register.jsx | 475 ++---------------- .../src/Pages/Auth/Register/StepOne/index.jsx | 121 +++++ .../Pages/Auth/Register/StepThree/index.jsx | 162 ++++++ .../src/Pages/Auth/Register/StepTwo/index.jsx | 104 ++++ Client/src/Pages/Auth/SetNewPassword.jsx | 69 ++- .../Pages/Auth/hooks/useValidatePassword.jsx | 67 +++ 7 files changed, 545 insertions(+), 471 deletions(-) create mode 100644 Client/src/Pages/Auth/Register/StepOne/index.jsx create mode 100644 Client/src/Pages/Auth/Register/StepThree/index.jsx create mode 100644 Client/src/Pages/Auth/Register/StepTwo/index.jsx create mode 100644 Client/src/Pages/Auth/hooks/useValidatePassword.jsx diff --git a/.chores.md b/.chores.md index b10e56264..6d45e9a11 100644 --- a/.chores.md +++ b/.chores.md @@ -1,8 +1,14 @@ - [x] Change schema to fix validations - [x] Refactor to apply Single Responsibility Principle to validation (validation is done on joi only) +- [ ] Validation on change +- [ ] Validation on submit - [ ] Take validation out from the component. - [x] SetNewPassword - [ ] Register + - [ ] Refactor on change to deal with step three specificity + - [ ] Fix Input enter not working + - [ ] PasswordPanel + - [ ] Login - [x] Update check component to accept not highlighted text - [x] Split some of these user feedbacks? Like: - [x] Must contain at least one special character, @@ -11,5 +17,13 @@ - [x] Must contain at least one lower character - [x] Pass mismatching password user feedback as the other messages, for consistency - [ ] Get rid of toast message -- [x] Check for confirm password if password changes and confirm is filled, and they do not match anymore -- [ ] Check for confirm password if password changes, and they now match +- [x] Tests + - [x] Valid Password and Matching Confirmation + - [x] Valid Password, Incorrect Confirmation, then Corrects confirmation + - [x] Password Update to Match Confirm Password + - [x] Short Password with Matching Confirmation + - [x] Invalid Password Format, Correct Confirmation + - [x] Empty Password Field with Confirmation Filled + - [x] Confirm Password Left Blank + - [x] Password Change After Successful Match +- [x] Avoid submission in new password if password or confirm empty, and on error diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index c5c6bd85a..b85cf89c0 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -1,20 +1,19 @@ +import { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useDispatch } from "react-redux"; import PropTypes from "prop-types"; -import { useState, useEffect, useRef } from "react"; -import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; import { Box, Button, Stack, Typography } from "@mui/material"; -import { useDispatch } from "react-redux"; +import { StepOne } from "./StepOne"; +import { StepTwo } from "./StepTwo"; +import { StepThree } from "./StepThree"; +import { networkService } from "../../../main"; import { credentials } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; import { register } from "../../../Features/Auth/authSlice"; -import { useParams } from "react-router-dom"; import Background from "../../../assets/Images/background-grid.svg?react"; import Logo from "../../../assets/icons/bwu-icon.svg?react"; import Mail from "../../../assets/icons/mail.svg?react"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import Check from "../../../Components/Check/Check"; -import Field from "../../../Components/Inputs/Field"; -import { networkService } from "../../../main"; import "../index.css"; /** @@ -110,389 +109,6 @@ LandingPage.propTypes = { onSignup: PropTypes.func, }; -/** - * Renders the first step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Enter your personal details - - - - - - - - - - - - - - - - ); -}; - -StepOne.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - -/** - * Renders the second step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Enter your email address - - - - (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email} - ref={inputRef} - /> - - - - - - - - - ); -}; - -StepTwo.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - -/** - * Renders the third step of the sign up process. - * - * @param {Object} props - * @param {Object} props.form - Form state object. - * @param {Object} props.errors - Object containing form validation errors. - * @param {Function} props.onSubmit - Callback function to handle form submission. - * @param {Function} props.onChange - Callback function to handle form input changes. - * @param {Function} props.onBack - Callback function to handle "Back" button click. - * @returns {JSX.Element} - */ -const StepThree = ({ form, errors, onSubmit, onChange, onBack }) => { - const theme = useTheme(); - const inputRef = useRef(null); - - useEffect(() => { - if (inputRef.current) { - inputRef.current.focus(); - } - }, []); - - return ( - <> - - - Sign Up - Create your password - - - - - - - - - - - Must be at least 8 characters - long - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : form.password.length < 8 - ? "error" - : "success" - } - /> - - Must contain one special - character and a number - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[!@#$%^&*(),.?":{}|])(?=.*\d).+$/.test(form.password) - ? "error" - : "success" - } - /> - - Must contain at least one - upper and lower character - - } - variant={ - errors?.password === "Password is required" - ? "error" - : form.password === "" - ? "info" - : !/^(?=.*[A-Z])(?=.*[a-z]).+$/.test(form.password) - ? "error" - : "success" - } - /> - - - - - - - - - ); -}; - -StepThree.propTypes = { - form: PropTypes.object, - errors: PropTypes.object, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - onBack: PropTypes.func, -}; - const Register = ({ isSuperAdmin }) => { const dispatch = useDispatch(); const navigate = useNavigate(); @@ -596,42 +212,45 @@ const Register = ({ isSuperAdmin }) => { // Attempts account registration const handleStepThree = async (e) => { e.preventDefault(); + const { password, confirm } = e.target.elements; + console.log(password.value); + console.log(confirm.value); + // let registerForm = { + // ...form, - let registerForm = { - ...form, - role: isSuperAdmin ? ["superadmin"] : form.role, - inviteToken: token ? token : "", // Add the token to the request for verification - }; - let error = validateForm(registerForm, { - context: { password: form.password }, - }); - if (error) { - handleError(error); - return; - } + // role: isSuperAdmin ? ["superadmin"] : form.role, + // inviteToken: token ? token : "", // Add the token to the request for verification + // }; + // let error = validateForm(registerForm, { + // context: { password: form.password }, + // }); + // if (error) { + // handleError(error); + // return; + // } - delete registerForm.confirm; - const action = await dispatch(register(registerForm)); - if (action.payload.success) { - const authToken = action.payload.data; - localStorage.setItem("token", authToken); - navigate("/"); - createToast({ - body: "Welcome! Your account was created successfully.", - }); - } else { - if (action.payload) { - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } - } + // delete registerForm.confirm; + // const action = await dispatch(register(registerForm)); + // if (action.payload.success) { + // const authToken = action.payload.data; + // localStorage.setItem("token", authToken); + // navigate("/"); + // createToast({ + // body: "Welcome! Your account was created successfully.", + // }); + // } else { + // if (action.payload) { + // // dispatch errors + // createToast({ + // body: action.payload.msg, + // }); + // } else { + // // unknown errors + // createToast({ + // body: "Unknown error.", + // }); + // } + // } }; const handleChange = (event) => { @@ -732,10 +351,10 @@ const Register = ({ isSuperAdmin }) => { /> ) : step === 3 ? ( setStep(2)} /> ) : ( diff --git a/Client/src/Pages/Auth/Register/StepOne/index.jsx b/Client/src/Pages/Auth/Register/StepOne/index.jsx new file mode 100644 index 000000000..8867b2794 --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepOne/index.jsx @@ -0,0 +1,121 @@ +import { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; + +StepOne.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + onChange: PropTypes.func, + onBack: PropTypes.func, +}; + +/** + * Renders the first step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ + +function StepOne({ form, errors, onSubmit, onChange, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + <> + + + Sign Up + Enter your personal details + + + + + + + + + + + + + + + + ); +} + +export { StepOne }; diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx new file mode 100644 index 000000000..cde86c2db --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -0,0 +1,162 @@ +import { useEffect, useRef /* , useState */ } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; +import Check from "../../../../Components/Check/Check"; +/* import { credentials } from "../../../../Validation/validation"; */ +import { useValidatePassword } from "../../hooks/useValidatePassword"; + +StepThree.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + /* onChange: PropTypes.func, */ + onBack: PropTypes.func, +}; + +/** + * Renders the third step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ +function StepThree({ onSubmit, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + const { handleChange, feedbacks, form, errors } = useValidatePassword(); + + return ( + <> + + + Sign Up + Create your password + + + + + + + + + + + + + + + + + + + + ); +} + +export { StepThree }; diff --git a/Client/src/Pages/Auth/Register/StepTwo/index.jsx b/Client/src/Pages/Auth/Register/StepTwo/index.jsx new file mode 100644 index 000000000..567a82818 --- /dev/null +++ b/Client/src/Pages/Auth/Register/StepTwo/index.jsx @@ -0,0 +1,104 @@ +import { useEffect, useRef } from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Button, Stack, Typography } from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import Field from "../../../../Components/Inputs/Field"; + +StepTwo.propTypes = { + form: PropTypes.object, + errors: PropTypes.object, + onSubmit: PropTypes.func, + onChange: PropTypes.func, + onBack: PropTypes.func, +}; + +/** + * Renders the second step of the sign up process. + * + * @param {Object} props + * @param {Object} props.form - Form state object. + * @param {Object} props.errors - Object containing form validation errors. + * @param {Function} props.onSubmit - Callback function to handle form submission. + * @param {Function} props.onChange - Callback function to handle form input changes. + * @param {Function} props.onBack - Callback function to handle "Back" button click. + * @returns {JSX.Element} + */ +function StepTwo({ form, errors, onSubmit, onChange, onBack }) { + const theme = useTheme(); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + return ( + <> + + + Sign Up + Enter your email address + + + + (e.target.value = e.target.value.toLowerCase())} + onChange={onChange} + error={errors.email} + ref={inputRef} + /> + + + + + + + + + ); +} + +export { StepTwo }; diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 103dff4d5..1cb858e11 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -35,19 +35,12 @@ const SetNewPassword = () => { const handleSubmit = async (e) => { e.preventDefault(); - - const passwordForm = { ...form }; - const { error } = credentials.validate(passwordForm, { + const { error } = credentials.validate(form, { abortEarly: false, context: { password: form.password }, }); if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); createToast({ body: error.details && error.details.length > 0 @@ -55,59 +48,49 @@ const SetNewPassword = () => { : "Error validating data.", }); } else { - delete passwordForm.confirm; - const action = await dispatch(setNewPassword({ token: token, form: passwordForm })); + const action = await dispatch(setNewPassword({ token, form })); if (action.payload.success) { navigate("/new-password-confirmed"); createToast({ body: "Your password was reset successfully.", }); } else { - if (action.payload) { - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } + const errorMessage = action.payload ? action.payload.msg : "Unknown error"; + createToast({ + body: errorMessage, + }); } } }; const handleChange = (event) => { - //update form state const { value, name } = event.target; setForm((prev) => ({ ...prev, [name]: value })); - //validate const validateValue = { [name]: value }; const validateOptions = { abortEarly: false, context: { password: form.password } }; if (name === "password" && form.confirm.length > 0) { validateValue.confirm = form.confirm; - validateOptions.context = value; + validateOptions.context = { password: value }; + } else if (name === "confirm") { + validateValue.password = form.password; } - const { error } = credentials.validate(validateValue, validateOptions); - //update errors - //fazer o map com path e message + const { error } = credentials.validate(validateValue, validateOptions); const errors = error?.details.map((error) => ({ - message: error.message, path: error.path[0], + message: error.message, })); - const errorsByPath = errors - ? errors.reduce((acc, { path, message }) => { - if (!acc[path]) { - acc[path] = []; - } - acc[path].push(message); - return acc; - }, {}) - : { [name]: [] }; - setErrors((prev) => ({ ...prev, ...errorsByPath })); + const errorsByPath = + errors && + errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}); + setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); }; const getFeedbackStatus = (field, criteria) => { @@ -228,7 +211,7 @@ const SetNewPassword = () => { placeholder="••••••••" value={form.password} onChange={handleChange} - /* error={errors.password} */ + error={errors.password} /> { placeholder="••••••••" value={form.confirm} onChange={handleChange} - /* error={errors.confirm} */ + error={errors.confirm} /> { color="primary" loading={isLoading} onClick={handleSubmit} - disabled={Object.keys(errors).length !== 0} + disabled={ + form.password.length === 0 || + form.confirm.length === 0 || + Object.keys(errors).length !== 0 + } sx={{ width: "100%", maxWidth: 400 }} > Reset password diff --git a/Client/src/Pages/Auth/hooks/useValidatePassword.jsx b/Client/src/Pages/Auth/hooks/useValidatePassword.jsx new file mode 100644 index 000000000..043fc6424 --- /dev/null +++ b/Client/src/Pages/Auth/hooks/useValidatePassword.jsx @@ -0,0 +1,67 @@ +import { useState } from "react"; +import { credentials } from "../../../Validation/validation"; + +function useValidatePassword() { + const [errors, setErrors] = useState({}); + const [form, setForm] = useState({ + password: "", + confirm: "", + }); + + const handleChange = (event) => { + const { value, name } = event.target; + setForm((prev) => ({ ...prev, [name]: value })); + + const validateValue = { [name]: value }; + const validateOptions = { abortEarly: false, context: { password: form.password } }; + if (name === "password" && form.confirm.length > 0) { + validateValue.confirm = form.confirm; + validateOptions.context = { password: value }; + } else if (name === "confirm") { + validateValue.password = form.password; + } + + const { error } = credentials.validate(validateValue, validateOptions); + const errors = error?.details.map((error) => ({ + path: error.path[0], + message: error.message, + })); + const errorsByPath = + errors && + errors.reduce((acc, { path, message }) => { + if (!acc[path]) { + acc[path] = []; + } + acc[path].push(message); + return acc; + }, {}); + setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); + }; + + const getFeedbackStatus = (field, criteria) => { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; + + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } + }; + + const feedbacks = { + length: getFeedbackStatus("password", "length"), + special: getFeedbackStatus("password", "special"), + number: getFeedbackStatus("password", "number"), + uppercase: getFeedbackStatus("password", "uppercase"), + lowercase: getFeedbackStatus("password", "lowercase"), + confirm: getFeedbackStatus("confirm", "different"), + }; + + return { handleChange, feedbacks, form, errors }; +} +export { useValidatePassword }; From fd38a9d7eb3383b84eb455bd754db2be7f01786b Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 09:30:43 -0400 Subject: [PATCH 06/21] fix: backend validation password --- Client/src/Pages/Auth/Register/Register.jsx | 72 ++++++------ .../Pages/Auth/Register/StepThree/index.jsx | 10 +- Client/src/Pages/Auth/SetNewPassword.jsx | 108 +++++++++--------- .../Pages/Auth/hooks/useValidatePassword.jsx | 51 +++++---- Server/validation/joi.js | 2 +- 5 files changed, 127 insertions(+), 116 deletions(-) diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index b85cf89c0..628365bdf 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -213,44 +213,42 @@ const Register = ({ isSuperAdmin }) => { const handleStepThree = async (e) => { e.preventDefault(); const { password, confirm } = e.target.elements; - console.log(password.value); - console.log(confirm.value); - // let registerForm = { - // ...form, + let registerForm = { + ...form, + password: password.value, + confirm: confirm.value, + role: isSuperAdmin ? ["superadmin"] : form.role, + inviteToken: token ? token : "", // Add the token to the request for verification + }; + let error = validateForm(registerForm, { + context: { password: registerForm.password }, + }); + if (error) { + handleError(error); + return; + } - // role: isSuperAdmin ? ["superadmin"] : form.role, - // inviteToken: token ? token : "", // Add the token to the request for verification - // }; - // let error = validateForm(registerForm, { - // context: { password: form.password }, - // }); - // if (error) { - // handleError(error); - // return; - // } - - // delete registerForm.confirm; - // const action = await dispatch(register(registerForm)); - // if (action.payload.success) { - // const authToken = action.payload.data; - // localStorage.setItem("token", authToken); - // navigate("/"); - // createToast({ - // body: "Welcome! Your account was created successfully.", - // }); - // } else { - // if (action.payload) { - // // dispatch errors - // createToast({ - // body: action.payload.msg, - // }); - // } else { - // // unknown errors - // createToast({ - // body: "Unknown error.", - // }); - // } - // } + delete registerForm.confirm; + const action = await dispatch(register(registerForm)); + if (action.payload.success) { + const authToken = action.payload.data; + localStorage.setItem("token", authToken); + /* navigate("/"); */ + navigate("/monitors"); + createToast({ + body: "Welcome! Your account was created successfully.", + }); + } else { + if (action.payload) { + createToast({ + body: action.payload.msg, + }); + } else { + createToast({ + body: "Unknown error.", + }); + } + } }; const handleChange = (event) => { diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx index cde86c2db..0f1e540f7 100644 --- a/Client/src/Pages/Auth/Register/StepThree/index.jsx +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -136,6 +136,10 @@ function StepThree({ onSubmit, onBack }) { "& svg.MuiSvgIcon-root": { mr: theme.spacing(3), }, + ":focus-visible": { + outline: `2px solid ${theme.palette.primary.dark}`, + outlineOffset: "4px", + }, }} props={{ tabIndex: -1 }} > @@ -147,7 +151,11 @@ function StepThree({ onSubmit, onBack }) { variant="contained" color="primary" /* onClick={onSubmit} */ - disabled={errors.email && true} + disabled={ + form.password.length === 0 || + form.confirm.length === 0 || + Object.keys(errors).length !== 0 + } sx={{ width: "30%" }} > Continue diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 1cb858e11..5f0acef15 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -15,6 +15,7 @@ import LockIcon from "../../assets/icons/lock.svg?react"; import Logo from "../../assets/icons/bwu-icon.svg?react"; import Background from "../../assets/Images/background-grid.svg?react"; import "./index.css"; +import { useValidatePassword } from "./hooks/useValidatePassword"; const SetNewPassword = () => { const navigate = useNavigate(); @@ -24,11 +25,12 @@ const SetNewPassword = () => { const passwordId = useId(); const confirmPasswordId = useId(); - const [errors, setErrors] = useState({}); - const [form, setForm] = useState({ - password: "", - confirm: "", - }); + const { form, errors, handleChange, feedbacks } = useValidatePassword(); + // const [errors, setErrors] = useState({}); + // const [form, setForm] = useState({ + // password: "", + // confirm: "", + // }); const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -63,59 +65,59 @@ const SetNewPassword = () => { } }; - const handleChange = (event) => { - const { value, name } = event.target; - setForm((prev) => ({ ...prev, [name]: value })); + // const handleChange = (event) => { + // const { value, name } = event.target; + // setForm((prev) => ({ ...prev, [name]: value })); - const validateValue = { [name]: value }; - const validateOptions = { abortEarly: false, context: { password: form.password } }; - if (name === "password" && form.confirm.length > 0) { - validateValue.confirm = form.confirm; - validateOptions.context = { password: value }; - } else if (name === "confirm") { - validateValue.password = form.password; - } + // const validateValue = { [name]: value }; + // const validateOptions = { abortEarly: false, context: { password: form.password } }; + // if (name === "password" && form.confirm.length > 0) { + // validateValue.confirm = form.confirm; + // validateOptions.context = { password: value }; + // } else if (name === "confirm") { + // validateValue.password = form.password; + // } - const { error } = credentials.validate(validateValue, validateOptions); - const errors = error?.details.map((error) => ({ - path: error.path[0], - message: error.message, - })); - const errorsByPath = - errors && - errors.reduce((acc, { path, message }) => { - if (!acc[path]) { - acc[path] = []; - } - acc[path].push(message); - return acc; - }, {}); - setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); - }; + // const { error } = credentials.validate(validateValue, validateOptions); + // const errors = error?.details.map((error) => ({ + // path: error.path[0], + // message: error.message, + // })); + // const errorsByPath = + // errors && + // errors.reduce((acc, { path, message }) => { + // if (!acc[path]) { + // acc[path] = []; + // } + // acc[path].push(message); + // return acc; + // }, {}); + // setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); + // }; - const getFeedbackStatus = (field, criteria) => { - const fieldErrors = errors[field]; - const isFieldEmpty = form[field].length === 0; - const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - const isCorrect = !isFieldEmpty && !hasError; + // const getFeedbackStatus = (field, criteria) => { + // const fieldErrors = errors[field]; + // const isFieldEmpty = form[field].length === 0; + // const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + // const isCorrect = !isFieldEmpty && !hasError; - if (isCorrect) { - return "success"; - } else if (hasError) { - return "error"; - } else { - return "info"; - } - }; + // if (isCorrect) { + // return "success"; + // } else if (hasError) { + // return "error"; + // } else { + // return "info"; + // } + // }; - const feedbacks = { - length: getFeedbackStatus("password", "length"), - special: getFeedbackStatus("password", "special"), - number: getFeedbackStatus("password", "number"), - uppercase: getFeedbackStatus("password", "uppercase"), - lowercase: getFeedbackStatus("password", "lowercase"), - confirm: getFeedbackStatus("confirm", "different"), - }; + // const feedbacks = { + // length: getFeedbackStatus("password", "length"), + // special: getFeedbackStatus("password", "special"), + // number: getFeedbackStatus("password", "number"), + // uppercase: getFeedbackStatus("password", "uppercase"), + // lowercase: getFeedbackStatus("password", "lowercase"), + // confirm: getFeedbackStatus("confirm", "different"), + // }; return ( { + const fieldErrors = errors[field]; + const isFieldEmpty = form[field].length === 0; + const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); + const isCorrect = !isFieldEmpty && !hasError; + + if (isCorrect) { + return "success"; + } else if (hasError) { + return "error"; + } else { + return "info"; + } +}; + function useValidatePassword() { const [errors, setErrors] = useState({}); const [form, setForm] = useState({ @@ -38,29 +53,17 @@ function useValidatePassword() { setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); }; - const getFeedbackStatus = (field, criteria) => { - const fieldErrors = errors[field]; - const isFieldEmpty = form[field].length === 0; - const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - const isCorrect = !isFieldEmpty && !hasError; - - if (isCorrect) { - return "success"; - } else if (hasError) { - return "error"; - } else { - return "info"; - } - }; - - const feedbacks = { - length: getFeedbackStatus("password", "length"), - special: getFeedbackStatus("password", "special"), - number: getFeedbackStatus("password", "number"), - uppercase: getFeedbackStatus("password", "uppercase"), - lowercase: getFeedbackStatus("password", "lowercase"), - confirm: getFeedbackStatus("confirm", "different"), - }; + const feedbacks = useMemo( + () => ({ + length: getFeedbackStatus(form, errors, "password", "length"), + special: getFeedbackStatus(form, errors, "password", "special"), + number: getFeedbackStatus(form, errors, "password", "number"), + uppercase: getFeedbackStatus(form, errors, "password", "uppercase"), + lowercase: getFeedbackStatus(form, errors, "password", "lowercase"), + confirm: getFeedbackStatus(form, errors, "confirm", "different"), + }), + [form, errors] + ); return { handleChange, feedbacks, form, errors }; } diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d1ba107bc..299acebb6 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -118,7 +118,7 @@ const newPasswordValidation = joi.object({ .min(8) .required() .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/ ), confirm: joi.string(), }); From 40ef5c9be48be1511c046f8216dfcc41abba6ce9 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 11:46:52 -0400 Subject: [PATCH 07/21] fix: login validation --- .chores.md | 7 +- Client/src/Pages/Auth/Login.jsx | 117 ++++++++++-------- Client/src/Pages/Auth/Register/Register.jsx | 1 - .../Pages/Auth/Register/StepThree/index.jsx | 3 +- Client/src/Pages/Auth/SetNewPassword.jsx | 61 +-------- Server/validation/joi.js | 41 ++---- 6 files changed, 79 insertions(+), 151 deletions(-) diff --git a/.chores.md b/.chores.md index 6d45e9a11..9786ce569 100644 --- a/.chores.md +++ b/.chores.md @@ -4,7 +4,7 @@ - [ ] Validation on submit - [ ] Take validation out from the component. - [x] SetNewPassword - - [ ] Register + - [x] Register - [ ] Refactor on change to deal with step three specificity - [ ] Fix Input enter not working - [ ] PasswordPanel @@ -27,3 +27,8 @@ - [x] Confirm Password Left Blank - [x] Password Change After Successful Match - [x] Avoid submission in new password if password or confirm empty, and on error +- [ ] Update validation for password on backend + +//TODO Apply custom hook to SetNewPassword +// Enter on input triggers back action on steps one and two +// When user registers, we see sign up page for half a second diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index c0975f2d0..687abb7e6 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -130,17 +130,14 @@ const StepOne = ({ form, errors, onSubmit, onChange, onBack }) => { return ( <> Log In Enter your email address - +
{ const navigate = useNavigate(); const inputRef = useRef(null); + console.log({ errors }); + + const errorsMap = { + length: "Must be at least 8 characters long", + uppercase: "Must contain at least one upper character", + lowercase: "Must contain at least one lower character", + number: "Must contain at least one number", + special: "Must contain at least one special character", + }; + useEffect(() => { if (inputRef.current) { inputRef.current.focus(); @@ -235,7 +242,7 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { return ( <> @@ -244,64 +251,66 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { Enter your password - + - - + + + - - - - { if (action.payload.success) { const authToken = action.payload.data; localStorage.setItem("token", authToken); - /* navigate("/"); */ navigate("/monitors"); createToast({ body: "Welcome! Your account was created successfully.", diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx index 0f1e540f7..b225f09e8 100644 --- a/Client/src/Pages/Auth/Register/StepThree/index.jsx +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -1,11 +1,10 @@ -import { useEffect, useRef /* , useState */ } from "react"; +import { useEffect, useRef } from "react"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Box, Button, Stack, Typography } from "@mui/material"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import Field from "../../../../Components/Inputs/Field"; import Check from "../../../../Components/Check/Check"; -/* import { credentials } from "../../../../Validation/validation"; */ import { useValidatePassword } from "../../hooks/useValidatePassword"; StepThree.propTypes = { diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 5f0acef15..92d6973f1 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -1,4 +1,4 @@ -import { useId, useState } from "react"; +import { useId } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { useParams } from "react-router-dom"; @@ -26,11 +26,6 @@ const SetNewPassword = () => { const confirmPasswordId = useId(); const { form, errors, handleChange, feedbacks } = useValidatePassword(); - // const [errors, setErrors] = useState({}); - // const [form, setForm] = useState({ - // password: "", - // confirm: "", - // }); const { isLoading } = useSelector((state) => state.auth); const { token } = useParams(); @@ -65,60 +60,6 @@ const SetNewPassword = () => { } }; - // const handleChange = (event) => { - // const { value, name } = event.target; - // setForm((prev) => ({ ...prev, [name]: value })); - - // const validateValue = { [name]: value }; - // const validateOptions = { abortEarly: false, context: { password: form.password } }; - // if (name === "password" && form.confirm.length > 0) { - // validateValue.confirm = form.confirm; - // validateOptions.context = { password: value }; - // } else if (name === "confirm") { - // validateValue.password = form.password; - // } - - // const { error } = credentials.validate(validateValue, validateOptions); - // const errors = error?.details.map((error) => ({ - // path: error.path[0], - // message: error.message, - // })); - // const errorsByPath = - // errors && - // errors.reduce((acc, { path, message }) => { - // if (!acc[path]) { - // acc[path] = []; - // } - // acc[path].push(message); - // return acc; - // }, {}); - // setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); - // }; - - // const getFeedbackStatus = (field, criteria) => { - // const fieldErrors = errors[field]; - // const isFieldEmpty = form[field].length === 0; - // const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty"); - // const isCorrect = !isFieldEmpty && !hasError; - - // if (isCorrect) { - // return "success"; - // } else if (hasError) { - // return "error"; - // } else { - // return "info"; - // } - // }; - - // const feedbacks = { - // length: getFeedbackStatus("password", "length"), - // special: getFeedbackStatus("password", "special"), - // number: getFeedbackStatus("password", "number"), - // uppercase: getFeedbackStatus("password", "uppercase"), - // lowercase: getFeedbackStatus("password", "lowercase"), - // confirm: getFeedbackStatus("confirm", "different"), - // }; - return ( (value, helpers) => { // Auth //**************************************** +const passwordPattern = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/; + const loginValidation = joi.object({ email: joi .string() @@ -30,13 +33,7 @@ const loginValidation = joi.object({ } return lowercasedValue; }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), }); const registrationBodyValidation = joi.object({ @@ -59,13 +56,7 @@ const registrationBodyValidation = joi.object({ } return lowercasedValue; }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), profileImage: joi.any(), role: joi .array() @@ -84,18 +75,8 @@ const editUserBodyValidation = joi.object({ firstName: joi.string().pattern(/^[A-Za-z]+$/), lastName: joi.string().pattern(/^[A-Za-z]+$/), profileImage: joi.any(), - newPassword: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - password: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + newPassword: joi.string().min(8).pattern(passwordPattern), + password: joi.string().min(8).pattern(passwordPattern), deleteProfileImage: joi.boolean(), role: joi.array(), }); @@ -113,13 +94,7 @@ const recoveryTokenValidation = joi.object({ const newPasswordValidation = joi.object({ recoveryToken: joi.string().required(), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/])[A-Za-z0-9!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]+$/ - ), + password: joi.string().min(8).required().pattern(passwordPattern), confirm: joi.string(), }); From b36a5d028e6534dceebdfd000b3fe49958dc3093 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Thu, 31 Oct 2024 14:43:09 -0400 Subject: [PATCH 08/21] feat: fixing PAssword Panel --- .chores.md | 34 ------- .../TabPanels/Account/PasswordPanel.jsx | 99 +++++++++++++------ .../Pages/Auth/Register/StepThree/index.jsx | 1 - 3 files changed, 68 insertions(+), 66 deletions(-) delete mode 100644 .chores.md diff --git a/.chores.md b/.chores.md deleted file mode 100644 index 9786ce569..000000000 --- a/.chores.md +++ /dev/null @@ -1,34 +0,0 @@ -- [x] Change schema to fix validations -- [x] Refactor to apply Single Responsibility Principle to validation (validation is done on joi only) -- [ ] Validation on change -- [ ] Validation on submit -- [ ] Take validation out from the component. - - [x] SetNewPassword - - [x] Register - - [ ] Refactor on change to deal with step three specificity - - [ ] Fix Input enter not working - - [ ] PasswordPanel - - [ ] Login -- [x] Update check component to accept not highlighted text -- [x] Split some of these user feedbacks? Like: - - [x] Must contain at least one special character, - - [x] Must contain at least one number, - - [x] Must contain at least one upper character, - - [x] Must contain at least one lower character -- [x] Pass mismatching password user feedback as the other messages, for consistency -- [ ] Get rid of toast message -- [x] Tests - - [x] Valid Password and Matching Confirmation - - [x] Valid Password, Incorrect Confirmation, then Corrects confirmation - - [x] Password Update to Match Confirm Password - - [x] Short Password with Matching Confirmation - - [x] Invalid Password Format, Correct Confirmation - - [x] Empty Password Field with Confirmation Filled - - [x] Confirm Password Left Blank - - [x] Password Change After Successful Match -- [x] Avoid submission in new password if password or confirm empty, and on error -- [ ] Update validation for password on backend - -//TODO Apply custom hook to SetNewPassword -// Enter on input triggers back action on steps one and two -// When user registers, we see sign up page for half a second diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 700e27fb9..c2e4c1cf8 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -29,13 +29,21 @@ const PasswordPanel = () => { "edit-confirm-password": "confirm", }; + const errorsMap = { + length: "Must be at least 8 characters long", + uppercase: "Must contain at least one upper character", + lowercase: "Must contain at least one lower character", + number: "Must contain at least one number", + special: "Must contain at least one special character", + different: "Confirm password and password must match", + }; + const [localData, setLocalData] = useState({ password: "", newPassword: "", confirm: "", }); const [errors, setErrors] = useState({}); - const handleChange = (event) => { const { value, id } = event.target; const name = idToName[id]; @@ -110,20 +118,32 @@ const PasswordPanel = () => { onSubmit={handleSubmit} noValidate spellCheck="false" - gap={theme.spacing(20)} + gap={theme.spacing(26)} + maxWidth={"80ch"} + marginInline={"auto"} > - - - Current password - - - -
- (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email} - ref={inputRef} - /> - -
- - - - + + +
+
); @@ -285,8 +301,11 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { "& svg.MuiSvgIcon-root": { mr: theme.spacing(3), }, + "&:focus-visible": { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: `2px`, + }, }} - props={{ tabIndex: -1 }} > Back @@ -294,9 +313,16 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index abb933d57..2f4c26040 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -53,6 +53,10 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => { stroke: theme.palette.other.icon, }, }, + "&:focus-visible": { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: `2px`, + }, }} > diff --git a/Client/src/Pages/Auth/Register/StepOne/index.jsx b/Client/src/Pages/Auth/Register/StepOne/index.jsx index 8867b2794..4a6997ef4 100644 --- a/Client/src/Pages/Auth/Register/StepOne/index.jsx +++ b/Client/src/Pages/Auth/Register/StepOne/index.jsx @@ -37,21 +37,28 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) { return ( <> + {/* TODO this stack should be a component */} Sign Up Enter your personal details - + + - - + + {/* TODO buttons should be a component should be a component */} + + + - - - - ); diff --git a/Client/src/Pages/Auth/Register/StepThree/index.jsx b/Client/src/Pages/Auth/Register/StepThree/index.jsx index fb72969f0..13d6a130f 100644 --- a/Client/src/Pages/Auth/Register/StepThree/index.jsx +++ b/Client/src/Pages/Auth/Register/StepThree/index.jsx @@ -47,39 +47,44 @@ function StepThree({ onSubmit, onBack }) { spellCheck={false} onSubmit={onSubmit} textAlign="left" + display="grid" + gap={{ xs: theme.spacing(12), sm: theme.spacing(16) }} sx={{ - display: "grid", - gap: theme.spacing(8), "& .input-error": { display: "none", }, }} > - - + + + + Back @@ -147,7 +151,14 @@ function StepThree({ onSubmit, onBack }) { form.confirm.length === 0 || Object.keys(errors).length !== 0 } - sx={{ width: "30%" }} + sx={{ + width: "30%", + "&.Mui-focusVisible": { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: `2px`, + boxShadow: `none`, + }, + }} > Continue diff --git a/Client/src/Pages/Auth/Register/StepTwo/index.jsx b/Client/src/Pages/Auth/Register/StepTwo/index.jsx index 567a82818..e4236c0bc 100644 --- a/Client/src/Pages/Auth/Register/StepTwo/index.jsx +++ b/Client/src/Pages/Auth/Register/StepTwo/index.jsx @@ -44,58 +44,70 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) { Sign Up Enter your email address - - - (e.target.value = e.target.value.toLowerCase())} - onChange={onChange} - error={errors.email} - ref={inputRef} - /> - - - - - - + + + +
); From f61a5ca2e44a4397d379b2f91647c171666f16cd Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 2 Nov 2024 21:49:39 +0000 Subject: [PATCH 18/21] fix(deps): update dependency bullmq to v5.23.0 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index 0e165fda3..7ec737059 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -12,7 +12,7 @@ "@sendgrid/mail": "^8.1.3", "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.22.0", + "bullmq": "5.23.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -1164,9 +1164,9 @@ "license": "MIT" }, "node_modules/bullmq": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.22.0.tgz", - "integrity": "sha512-nwjJSQt/kpO4bIfAznyKKz3+m5OZ6YSaz2Vg7oNoZWTD5wCnJJJy6b9iWM5QIF0bADhDWyorLCO0hU3de+iKMA==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.23.0.tgz", + "integrity": "sha512-VILKTIOwo9AopMyVqvDhQ1qyLrOtBSfu+G2bntgauQfxYzT7ETj+h2HeUe7a9i9AU/+OXJGYYm49NHJedEz7VQ==", "license": "MIT", "dependencies": { "cron-parser": "^4.6.0", diff --git a/Server/package.json b/Server/package.json index 12ceaa407..3b28602a9 100644 --- a/Server/package.json +++ b/Server/package.json @@ -15,7 +15,7 @@ "@sendgrid/mail": "^8.1.3", "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.22.0", + "bullmq": "5.23.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", From 3709e09ef7e6dbde4c3cc18d5505ab5c9079bad9 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Sun, 3 Nov 2024 06:50:49 -0500 Subject: [PATCH 19/21] fix: erros on console on dialogs --- .../src/Components/Dialog/genericDialog.jsx | 5 +-- Client/src/Components/Dialog/index.jsx | 1 - .../TabPanels/Account/PasswordPanel.jsx | 20 ++++-------- Client/src/Pages/Auth/Login.jsx | 10 +----- .../Pages/Auth/hooks/useValidatePassword.jsx | 8 ++--- Client/src/Validation/validation.js | 32 +++++++++++-------- 6 files changed, 34 insertions(+), 42 deletions(-) diff --git a/Client/src/Components/Dialog/genericDialog.jsx b/Client/src/Components/Dialog/genericDialog.jsx index 0b37d75e8..e3ebd51b1 100644 --- a/Client/src/Components/Dialog/genericDialog.jsx +++ b/Client/src/Components/Dialog/genericDialog.jsx @@ -12,7 +12,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = aria-describedby={ariaDescribedBy} open={open} onClose={onClose} - onClick={(e)=>e.stopPropagation()} + onClick={(e) => e.stopPropagation()} > { "edit-confirm-password": "confirm", }; - const errorsMap = { - length: "Must be at least 8 characters long", - uppercase: "Must contain at least one upper character", - lowercase: "Must contain at least one lower character", - number: "Must contain at least one number", - special: "Must contain at least one special character", - different: "Confirm password and password must match", - }; - const [localData, setLocalData] = useState({ password: "", newPassword: "", @@ -151,7 +142,7 @@ const PasswordPanel = () => { autoComplete="current-password" value={localData.password} onChange={handleChange} - error={errorsMap[errors[idToName["edit-current-password"]]]} + error={errors[idToName["edit-current-password"]]} /> { autoComplete="new-password" value={localData.newPassword} onChange={handleChange} - error={errorsMap[errors[idToName["edit-new-password"]]]} + error={errors[idToName["edit-new-password"]]} /> { autoComplete="new-password" value={localData.confirm} onChange={handleChange} - error={errorsMap[errors[idToName["edit-confirm-password"]]]} + error={errors[idToName["edit-confirm-password"]]} /> {Object.keys(errors).length > 0 && ( @@ -218,7 +209,10 @@ const PasswordPanel = () => { type="submit" loading={isLoading} loadingIndicator="Saving..." - disabled={Object.keys(errors).length !== 0 && true} + disabled={ + Object.keys(errors).length !== 0 || + Object.values(localData).filter((value) => value !== "").length === 0 + } sx={{ px: theme.spacing(12), mt: theme.spacing(20), diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index 7014e2113..0554ad579 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -232,14 +232,6 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { const navigate = useNavigate(); const inputRef = useRef(null); - const errorsMap = { - length: "Must be at least 8 characters long", - uppercase: "Must contain at least one upper character", - lowercase: "Must contain at least one lower character", - number: "Must contain at least one number", - special: "Must contain at least one special character", - }; - useEffect(() => { if (inputRef.current) { inputRef.current.focus(); @@ -285,7 +277,7 @@ const StepTwo = ({ form, errors, onSubmit, onChange, onBack }) => { autoComplete="current-password" value={form.password} onChange={onChange} - error={errorsMap[errors.password]} + error={errors.password} ref={inputRef} /> ({ path: error.path[0], - message: error.message, + type: error.type, })); const errorsByPath = errors && - errors.reduce((acc, { path, message }) => { + errors.reduce((acc, { path, type }) => { if (!acc[path]) { acc[path] = []; } - acc[path].push(message); + acc[path].push(type); return acc; }, {}); setErrors(() => (errorsByPath ? { ...errorsByPath } : {})); @@ -55,7 +55,7 @@ function useValidatePassword() { const feedbacks = useMemo( () => ({ - length: getFeedbackStatus(form, errors, "password", "length"), + length: getFeedbackStatus(form, errors, "password", "string.min"), special: getFeedbackStatus(form, errors, "password", "special"), number: getFeedbackStatus(form, errors, "password", "number"), uppercase: getFeedbackStatus(form, errors, "password", "uppercase"), diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index acc9ea523..7ac5ae40c 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -16,34 +16,39 @@ const passwordSchema = joi .string() .trim() .min(8) - .messages({ - "string.empty": "empty", - "string.min": "length", - }) .custom((value, helpers) => { if (!/[A-Z]/.test(value)) { - return helpers.message("uppercase"); + return helpers.error("uppercase"); } return value; }) .custom((value, helpers) => { if (!/[a-z]/.test(value)) { - return helpers.message("lowercase"); + return helpers.error("lowercase"); } return value; }) .custom((value, helpers) => { if (!/\d/.test(value)) { - return helpers.message("number"); + return helpers.error("number"); } return value; }) .custom((value, helpers) => { if (!/[!?@#$%^&*()\-_=+[\]{};:'",.<>~`|\\/]/.test(value)) { - return helpers.message("special"); + return helpers.error("special"); } return value; + }) + .messages({ + "string.empty": "Password is required", + "string.min": "Password must be at least 8 characters long", + uppercase: "Password must contain at least one uppercase letter", + lowercase: "Password must contain at least one lowercase letter", + number: "Password must contain at least one number", + special: "Password must contain at least one special character", }); + const credentials = joi.object({ firstName: nameSchema, lastName: nameSchema, @@ -67,15 +72,16 @@ const credentials = joi.object({ confirm: joi .string() .trim() - .messages({ - "string.empty": "empty", - }) .custom((value, helpers) => { const { password } = helpers.prefs.context; if (value !== password) { - return helpers.message("different"); + return helpers.error("different"); } return value; + }) + .messages({ + "string.empty": "empty", + different: "Passwords do not match", }), role: joi.array(), teamId: joi.string().allow("").optional(), @@ -173,5 +179,5 @@ export { monitorValidation, settingsValidation, maintenanceWindowValidation, - advancedSettingsValidation + advancedSettingsValidation, }; From b4f5b68114344951ecbc311ef5b9da0b72b2a794 Mon Sep 17 00:00:00 2001 From: Caio Cabral Date: Sun, 3 Nov 2024 06:51:45 -0500 Subject: [PATCH 20/21] chore: cleaning consoles --- Client/src/Components/TabPanels/Account/ProfilePanel.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx index 710d6a59f..0875891f5 100644 --- a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx +++ b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx @@ -370,8 +370,6 @@ const ProfilePanel = () => { )} - {/* TODO - Update ModalPopup Component with @mui for reusability */} - Date: Sun, 3 Nov 2024 07:01:38 -0500 Subject: [PATCH 21/21] chore: applying some of code rabit's implementation --- Client/src/Components/Check/Check.jsx | 3 ++- Client/src/Pages/Auth/SetNewPassword.jsx | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Client/src/Components/Check/Check.jsx b/Client/src/Components/Check/Check.jsx index 9c1ec57fb..01a023345 100644 --- a/Client/src/Components/Check/Check.jsx +++ b/Client/src/Components/Check/Check.jsx @@ -54,7 +54,8 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => opacity: 0.8, }} > - {noHighlightText} {text} + {noHighlightText && {noHighlightText}}{" "} + {text} ); diff --git a/Client/src/Pages/Auth/SetNewPassword.jsx b/Client/src/Pages/Auth/SetNewPassword.jsx index 92d6973f1..77e621acd 100644 --- a/Client/src/Pages/Auth/SetNewPassword.jsx +++ b/Client/src/Pages/Auth/SetNewPassword.jsx @@ -52,7 +52,9 @@ const SetNewPassword = () => { body: "Your password was reset successfully.", }); } else { - const errorMessage = action.payload ? action.payload.msg : "Unknown error"; + const errorMessage = action.payload + ? action.payload.msg + : "Unable to reset password. Please try again later or contact support."; createToast({ body: errorMessage, });