From e45dd4240e2983c22be1ea1b2ffb372fe5eea4c4 Mon Sep 17 00:00:00 2001 From: karenvicent Date: Sun, 31 Aug 2025 10:18:15 -0400 Subject: [PATCH] fix errors --- client/src/Components/Check/Check.jsx | 2 +- .../src/Components/Dialog/genericDialog.jsx | 2 +- client/src/Hooks/inviteHooks.js | 42 ++++++++++---- .../AddTeamMember/hooks/useAddTeamMember.jsx | 44 ++++++++++++++ .../components/AddTeamMember/index.jsx | 58 +++++-------------- .../Pages/Auth/components/PasswordTooltip.jsx | 17 +----- 6 files changed, 95 insertions(+), 70 deletions(-) create mode 100644 client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx diff --git a/client/src/Components/Check/Check.jsx b/client/src/Components/Check/Check.jsx index 7f37a4dc1..02b9fd78a 100644 --- a/client/src/Components/Check/Check.jsx +++ b/client/src/Components/Check/Check.jsx @@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => }; Check.propTypes = { - text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, + text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), noHighlightText: PropTypes.string, variant: PropTypes.oneOf(["info", "error", "success"]), outlined: PropTypes.bool, diff --git a/client/src/Components/Dialog/genericDialog.jsx b/client/src/Components/Dialog/genericDialog.jsx index a06ef2732..a13eba849 100644 --- a/client/src/Components/Dialog/genericDialog.jsx +++ b/client/src/Components/Dialog/genericDialog.jsx @@ -67,7 +67,7 @@ GenericDialog.propTypes = { theme: PropTypes.object.isRequired, children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) .isRequired, - width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]), }; export { GenericDialog }; diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js index 810534454..fb577272a 100644 --- a/client/src/Hooks/inviteHooks.js +++ b/client/src/Hooks/inviteHooks.js @@ -14,24 +14,23 @@ const useGetInviteToken = () => { const clearToken = () => { setToken(undefined); }; - + const fetchToken = async (email, role) => { + const response = await networkService.requestInvitationToken({ email, role }); + const token = response?.data?.data?.token; + if (typeof token === "undefined") { + throw new Error(t("inviteNoTokenFound")); + } + return token; + }; const getInviteToken = async ({ email, role }) => { try { - const response = await networkService.requestInvitationToken({ - email, - role, - }); - const token = response?.data?.data?.token; - if (typeof token === "undefined") { - throw new Error(t("inviteNoTokenFound")); - } - + setIsLoading(true); + const token = await fetchToken(email, role); let inviteLink = token; if (typeof CLIENT_HOST !== "undefined") { inviteLink = `${CLIENT_HOST}/register/${token}`; } - setToken(inviteLink); return token; } catch (error) { @@ -41,7 +40,26 @@ const useGetInviteToken = () => { } }; - return [getInviteToken, clearToken, isLoading, error, token]; + const addTeamMember = async (formData, role) => { + try { + setIsLoading(true); + const token = await fetchToken(formData.email, role); + const toSubmit = { + ...formData, + inviteToken: token, + }; + delete toSubmit.confirm; + const responseRegister = await networkService.registerUser(toSubmit); + return responseRegister; + } catch (error) { + setError(error); + throw error; + } finally { + setIsLoading(false); + } + }; + + return [getInviteToken, clearToken, isLoading, error, token, addTeamMember]; }; export { useGetInviteToken }; diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx new file mode 100644 index 000000000..a6e049d4d --- /dev/null +++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import { newOrChangedCredentials } from "../../../../../Validation/validation"; +const useAddTeamMember = () => { + const [errors, setErrors] = useState({}); + + const clearErrors = () => setErrors({}); + + const validateFields = (name, value, formData) => { + const { error } = newOrChangedCredentials.validate( + { [name]: value }, + { abortEarly: false, context: { password: formData.password } } + ); + + setErrors((prev) => ({ + ...prev, + [name]: error?.details?.[0]?.message || "", + })); + }; + + const validateForm = (formData, role) => { + const { error } = newOrChangedCredentials.validate(formData, { + abortEarly: false, + context: { password: formData.password }, + }); + const formErrors = {}; + if (error) { + for (const err of error.details) { + formErrors[err.path[0]] = err.message; + } + } + if (!role[0] || role.length === 0) { + formErrors.role = "Role is required"; + } + if (Object.keys(formErrors).length > 0) { + setErrors(formErrors); + return false; + } + setErrors({}); + return true; + }; + + return { errors, setErrors, clearErrors, validateFields, validateForm }; +}; +export default useAddTeamMember; diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx index 25991cb32..80918e7eb 100644 --- a/client/src/Pages/Account/components/AddTeamMember/index.jsx +++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx @@ -2,14 +2,13 @@ import { Button, Stack } from "@mui/material"; import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; import TextInput from "../../../../Components/Inputs/TextInput"; import Select from "../../../../Components/Inputs/Select"; -import { newOrChangedCredentials } from "../../../../Validation/validation"; import { useGetInviteToken } from "../../../../Hooks/inviteHooks"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { createToast } from "../../../../Utils/toastUtils"; import { useState } from "react"; -import { networkService } from "../../../../main"; import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import useAddTeamMember from "./hooks/useAddTeamMember"; import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; import PropTypes from "prop-types"; @@ -26,16 +25,19 @@ const INITIAL_ROLE_STATE = ["user"]; const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => { const theme = useTheme(); const { t } = useTranslation(); + const { errors, setErrors, clearErrors, validateFields, validateForm } = + useAddTeamMember(); const { feedback, handlePasswordFeedback } = usePasswordFeedback(); - const [getInviteToken, clearToken] = useGetInviteToken(); + const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] = + useGetInviteToken(); const [form, setForm] = useState(INITIAL_FORM_STATE); const [role, setRole] = useState(INITIAL_ROLE_STATE); - const [errors, setErrors] = useState({}); + const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const closeAddMemberModal = () => { handleIsRegisterOpen(false); setForm(INITIAL_FORM_STATE); setRole(INITIAL_ROLE_STATE); - setErrors({}); + clearErrors(); clearToken(); }; @@ -43,17 +45,7 @@ const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) let { name, value } = e.target; if (name === "email") value = value.toLowerCase(); const updatedForm = { ...form, [name]: value }; - - const { error } = newOrChangedCredentials.validate( - { [name]: value }, - { abortEarly: false, context: { password: form.password } } - ); - - setErrors((prev) => ({ - ...prev, - [name]: error?.details?.[0]?.message || "", - })); - + validateFields(name, value, updatedForm); setForm(updatedForm); if (name === "password" || name === "confirm") { @@ -63,42 +55,23 @@ const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) const onsubmitAddMember = async (event) => { event.preventDefault(); - - const { error } = newOrChangedCredentials.validate(form, { - abortEarly: false, - context: { password: form.password }, - }); - if (error) { - const formErrors = {}; - for (const err of error.details) { - formErrors[err.path[0]] = err.message; - } - setErrors(formErrors); - return; - } - - const token = await getInviteToken({ email: form.email, role: role }); - - const toSubmit = { - ...form, - inviteToken: token, - }; - delete toSubmit.confirm; - + if (!validateForm(form, role)) return; + setIsLoadingSubmit(true); try { - await networkService.registerUser(toSubmit); - + await addTeamMember(form, role); createToast({ body: t("teamPanel.registerToast.success"), }); - closeAddMemberModal(); onMemberAdded(); + closeAddMemberModal(); } catch (error) { const errorMsg = error.response?.data?.msg || error.message || "unknownError"; createToast({ type: "error", body: t(errorMsg), }); + } finally { + setIsLoadingSubmit(false); } }; const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : ""); @@ -172,7 +145,6 @@ const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) {t("teamPanel.cancel")} @@ -221,6 +194,7 @@ const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) variant="contained" color="accent" onClick={onsubmitAddMember} + disabled={isLoadingSubmit} > {t("teamPanel.addTeamMember.addButton")} diff --git a/client/src/Pages/Auth/components/PasswordTooltip.jsx b/client/src/Pages/Auth/components/PasswordTooltip.jsx index 01490437d..bc2f28ec7 100644 --- a/client/src/Pages/Auth/components/PasswordTooltip.jsx +++ b/client/src/Pages/Auth/components/PasswordTooltip.jsx @@ -4,7 +4,7 @@ import { Tooltip, useTheme } from "@mui/material"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; -const PasswordTooltip = ({ feedback, form, children, offset = [0, 0] }) => { +const PasswordTooltip = ({ feedback, form, children }) => { const theme = useTheme(); const { t } = useTranslation(); const hasPassword = form.password.length > 0; @@ -73,16 +73,6 @@ const PasswordTooltip = ({ feedback, form, children, offset = [0, 0] }) => { } slotProps={{ - popper: { - modifiers: [ - { - name: "offset", - options: { - offset: offset, - }, - }, - ], - }, tooltip: { sx: { backgroundColor: theme.palette.tertiary.background, @@ -103,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children, offset = [0, 0] }) => { }, }} > - {children} + {children} ); }; PasswordTooltip.propTypes = { feedback: PropTypes.shape({ - length: PropTypes.string.isRequired, + length: PropTypes.string, special: PropTypes.string, number: PropTypes.string, uppercase: PropTypes.string, @@ -121,7 +111,6 @@ PasswordTooltip.propTypes = { password: PropTypes.string.isRequired, }), children: PropTypes.node, - offset: PropTypes.arrayOf(PropTypes.number), }; export default PasswordTooltip;