This commit is contained in:
Alex Holliday committed 2026-02-09 20:48:12 +00:00
1 parent ece6970fa7
commit 48081d85ee
4 files changed
-431

No files matched your search

@@ -1,102 +0,0 @@
import { useState, useEffect } from "react";
import { ROLES } from "../../../../Utils/roleUtils.js";
import { editUserValidation } from "../../../../Validation/validation.js";
import Joi from "joi";
import { createToast } from "../../../../Utils/toastUtils.jsx";
import { useTranslation } from "react-i18next";
export const useEditUserForm = (user) => {
const [searchInput, setSearchInput] = useState("");
const [form, setForm] = useState({
firstName: "",
lastName: "",
email: "",
role: [],
});
// Effect to set user
useEffect(() => {
if (user) {
setForm({
firstName: user?.firstName,
lastName: user?.lastName,
email: user?.email,
role: user?.role,
});
}
}, [user]);
const handleRoleChange = (value) => {
const hasSuperAdmin = form.role.includes(ROLES.SUPERADMIN);
const newRoles = value.map((item) => item.role);
if (hasSuperAdmin && !newRoles.includes(ROLES.SUPERADMIN)) {
newRoles.push(ROLES.SUPERADMIN);
}
setForm({
...form,
role: newRoles,
});
};
const handleDeleteRole = (role) => {
if (role === ROLES.SUPERADMIN) return;
setForm({ ...form, role: form?.role?.filter((r) => r !== role) });
};
const handleSearchInput = (value) => {
setSearchInput(value);
};
return [
form,
setForm,
handleRoleChange,
handleDeleteRole,
searchInput,
handleSearchInput,
];
};
export const useValidateEditUserForm = () => {
const [errors, setErrors] = useState({});
const { t } = useTranslation();
const validateForm = (form) => {
const { error } = editUserValidation.validate(form, {
abortEarly: false,
});
const errs = error?.details;
const errsObj = errs?.reduce((acc, curr) => {
acc[curr.path[0]] = curr.message;
return acc;
}, {});
setErrors(errsObj);
if (errs?.length > 0) {
createToast({
variant: "warning",
hasIcon: true,
title: t("editUserPage.toast.validationErrors"),
body: errs.map((err) => t(err.message)),
});
return false;
}
return true;
};
const validateField = (name, value) => {
const fieldSchema = Joi.object({
[name]: editUserValidation.extract(name),
});
const { error } = fieldSchema.validate({ [name]: value });
setErrors((prev) => {
const prevErrors = { ...prev };
if (error) prevErrors[name] = error.details[0].message;
else delete prevErrors[name];
return prevErrors;
});
};
return [errors, validateForm, validateField];
};
-136
View File
@@ -1,136 +0,0 @@
// Components
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
import Search from "@/Components/v1/Inputs/Search/index.jsx";
import Button from "@mui/material/Button";
import RoleTable from "../components/RoleTable/index.jsx";
import ChangePasswordModal from "@/Pages/Account/components/ChangePasswordModal/index.jsx";
// Utils
import { useParams } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useGetUser, useEditUser } from "../../../Hooks/userHooks.js";
import { EDITABLE_ROLES, ROLES } from "../../../Utils/roleUtils.js";
import { useEditUserForm, useValidateEditUserForm } from "./hooks/editUser.js";
import { useSelector } from "react-redux";
const EditUser = () => {
const { user } = useSelector((state) => state.auth);
const { userId } = useParams();
const isSameUser = user?._id === userId;
const theme = useTheme();
const { t } = useTranslation();
const BREADCRUMBS = [
{ name: t("menu.team"), path: "/account/team" },
{ name: t("editUserPage.title"), path: "" },
];
const [userToEdit, isLoading, error] = useGetUser(userId);
const [editUser, isSaving, saveError, changePassword] = useEditUser(userId);
const [
form,
setForm,
handleRoleChange,
handleDeleteRole,
searchInput,
handleSearchInput,
] = useEditUserForm(userToEdit);
const [errors, validateForm, validateField] = useValidateEditUserForm();
const onChange = (e) => {
const name = e.target.name;
const value = e.target.value;
validateField(name, value);
setForm({ ...form, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
const valid = validateForm(form);
if (valid) {
editUser(form);
}
};
return (
<Stack gap={theme.spacing(20)}>
<Breadcrumbs list={BREADCRUMBS} />
<Typography variant="h2">{t("editUserPage.title")}</Typography>
<Stack
component="form"
onSubmit={handleSubmit}
gap={theme.spacing(12)}
maxWidth="50%"
>
<TextInput
name="firstName"
label={t("editUserPage.form.firstName")}
value={form?.firstName}
onChange={onChange}
error={errors?.firstName ? true : false}
helperText={t(errors?.firstName)}
/>
<TextInput
name="lastName"
label={t("editUserPage.form.lastName")}
value={form?.lastName}
onChange={onChange}
error={errors?.lastName ? true : false}
helperText={t(errors?.lastName)}
/>
<TextInput
name="email"
label={t("editUserPage.form.email")}
value={form?.email}
disabled={true}
error={errors?.email ? true : false}
helperText={t(errors?.email)}
/>
<Search
label={t("editUserPage.form.role")}
filteredBy="role"
inputValue={searchInput}
handleInputChange={handleSearchInput}
value={
form?.role
?.filter((role) => role !== ROLES.SUPERADMIN)
.map((role) => ({ role, _id: role })) || []
}
options={EDITABLE_ROLES}
multiple={true}
handleChange={handleRoleChange}
/>
<RoleTable
roles={form?.role}
handleDeleteRole={handleDeleteRole}
/>
<Stack
direction="row"
spacing={theme.spacing(10)}
mt={theme.spacing(8)}
//justifyContent="flex-end"
>
<Button
type="submit"
variant="contained"
color="accent"
loading={isLoading || isSaving}
>
{t("editUserPage.form.save")}
</Button>
{!isSameUser && (
<ChangePasswordModal
isSaving={isSaving}
isLoading={isLoading}
changePassword={changePassword}
/>
)}
</Stack>
</Stack>
</Stack>
);
};
export default EditUser;
@@ -1,149 +0,0 @@
import { useState } from "react";
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "@/Components/v1/Dialog/genericDialog";
import TextInput from "@/Components/v1/Inputs/TextInput";
import PasswordTooltip from "@/Pages/Auth/components/PasswordTooltip";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../../Utils/toastUtils";
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments";
import usePasswordFeedback from "@/Pages/Auth/hooks/usePasswordFeedback";
import PropTypes from "prop-types";
const ChangePasswordModal = ({ isSaving, isLoading, changePassword }) => {
const INITIAL_FORM_STATE = {
password: "",
confirm: "",
};
const theme = useTheme();
const { t } = useTranslation();
const { feedback, handlePasswordFeedback } = usePasswordFeedback();
const [form, setForm] = useState(INITIAL_FORM_STATE);
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
const [errors, setErrors] = useState({});
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const closeChangePasswordModal = () => {
setIsChangePasswordOpen(false);
setForm(INITIAL_FORM_STATE);
};
const onChange = (e) => {
let { name, value } = e.target;
const updatedForm = { ...form, [name]: value };
setForm(updatedForm);
handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
};
const isFormValid =
form.password.length > 1 &&
form.confirm.length > 1 &&
!errors.password &&
!errors.confirm;
const onsubmitChangePassword = async (event) => {
event.preventDefault();
if (!isFormValid) return;
const newPasswordForm = {
password: form.password,
};
try {
setIsLoadingSubmit(true);
await changePassword(newPasswordForm);
closeChangePasswordModal();
} catch (error) {
const errorMsg = error.response?.data?.msg || error.message || "unknownError";
createToast({
type: "error",
body: t(errorMsg),
});
} finally {
setIsLoadingSubmit(false);
}
};
return (
<>
<Button
variant="contained"
color="error"
onClick={() => setIsChangePasswordOpen(true)}
disabled={isLoading || isSaving}
>
{t("teamPanel.changeTeamPassword.changePasswordMenu")}
</Button>
<GenericDialog
title={t("teamPanel.changeTeamPassword.title")}
description={t("teamPanel.changeTeamPassword.description")}
open={isChangePasswordOpen}
onClose={closeChangePasswordModal}
theme={theme}
width={{ sm: "55%", md: "50%", lg: "40%", xl: "30%" }}
>
<PasswordTooltip
feedback={feedback}
form={form}
>
<TextInput
type="password"
id="register-password-input"
name="password"
label={t("auth.common.inputs.password.label")}
isRequired={true}
placeholder="••••••••••"
value={form.password}
onChange={onChange}
error={errors.password && errors.password[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
</PasswordTooltip>
<TextInput
type="password"
id="register-confirm-input"
name="confirm"
label={t("auth.common.inputs.passwordConfirm.label")}
gap={theme.spacing(4)}
isRequired={true}
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
autoComplete="current-password"
value={form.confirm}
onChange={onChange}
error={errors.confirm && errors.confirm[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
<Stack
direction="row"
spacing={theme.spacing(10)}
mt={theme.spacing(8)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="error"
onClick={closeChangePasswordModal}
disabled={isLoadingSubmit}
>
{t("teamPanel.cancel")}
</Button>
<Button
variant="contained"
color="accent"
onClick={onsubmitChangePassword}
disabled={isLoadingSubmit || !isFormValid}
>
{t("save")}
</Button>
</Stack>
</GenericDialog>
</>
);
};
ChangePasswordModal.propTypes = {
isSaving: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
changePassword: PropTypes.func.isRequired,
};
export default ChangePasswordModal;
@@ -1,44 +0,0 @@
import Typography from "@mui/material/Typography";
import DataTable from "@/Components/v1/Table/index.jsx";
import Icon from "@/Components/v1/Icon";
import { useTranslation } from "react-i18next";
import { ROLES } from "../../../../Utils/roleUtils.js";
const RoleTable = ({ roles, handleDeleteRole }) => {
const { t } = useTranslation();
const HEADERS = [
{
id: "name",
content: <Typography>{t("editUserPage.table.roleHeader")}</Typography>,
render: (row) => {
return row;
},
},
{
id: "delete",
content: <Typography>{t("editUserPage.table.actionHeader")}</Typography>,
render: (row) => {
if (row === ROLES.SUPERADMIN) return null;
return (
<Icon
name="Trash2"
size={20}
onClick={() => {
handleDeleteRole(row);
}}
style={{ cursor: "pointer" }}
/>
);
},
},
];
return (
<DataTable
headers={HEADERS}
data={roles}
/>
);
};
export default RoleTable;