cleanup
This commit is contained in:
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];
|
||||
};
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user