Skip to content

Commit

Permalink
OP-1043 | Implement user / user group soft deletion (#692)
Browse files Browse the repository at this point in the history
* feature: Implement user / user group soft deletion

* update: Add deleted field in user and user group forms

* chore: Align with develop

* fix: Fix user group edit

* chore: Align with deveop

* fix: Fix error messages

* fix: Fix user / usergroup delete/soft delete

* chore: Update translations

* fix: Fix translations and error display

* fix: Fix actions handlers

---------

Co-authored-by: SteveGT96 <[email protected]>
  • Loading branch information
SteveGT96 and SteveGT96 authored Nov 14, 2024
1 parent 72918b4 commit 1e9f092
Show file tree
Hide file tree
Showing 31 changed files with 2,759 additions and 2,583 deletions.
4,436 changes: 2,225 additions & 2,211 deletions api/oh.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useFormik } from "formik";
import { OperationDTOOpeForEnum } from "generated";
import { OperationDTOOpeForEnum } from "generated/models/OperationDTO";
import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import { get, has } from "lodash";
import React, {
Expand Down
3 changes: 2 additions & 1 deletion src/components/accessories/admin/users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ export const Users = () => {
const setTab = (tab: TabOptions) =>
navigate(PATHS.admin_users, { state: { tab } });

const handleEditGroup = (row: UserGroupDTO) =>
const handleEditGroup = (row: UserGroupDTO) => {
navigate(PATHS.admin_usergroups_edit.replace(":id", row.code!), {
state: row,
});
};

const handleEditUser = (row: UserDTO) =>
navigate(PATHS.admin_users_edit.replace(":id", row.userName!), {
Expand Down
54 changes: 38 additions & 16 deletions src/components/accessories/admin/users/editGroup/EditGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useFormik } from "formik";
import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Navigate, useLocation, useParams } from "react-router";
import { useParams } from "react-router";
import { useNavigate } from "react-router-dom";

import checkIcon from "../../../../../assets/check-icon.png";
Expand All @@ -12,13 +12,16 @@ import InfoBox from "../../../infoBox/InfoBox";
import TextField from "../../../textField/TextField";

import { PATHS } from "../../../../../consts";
import { PermissionDTO, UserGroupDTO } from "../../../../../generated";
import { usePermission } from "../../../../../libraries/permissionUtils/usePermission";

import { CircularProgress } from "@mui/material";
import CheckboxField from "components/accessories/checkboxField/CheckboxField";
import { PermissionDTO } from "generated/models/PermissionDTO";
import { UserGroupDTO } from "generated/models/UserGroupDTO";
import { getAllPermissions } from "../../../../../state/permissions";
import {
getUserGroup,
getUserGroupReset,
updateUserGroup,
updateUserGroupReset,
} from "../../../../../state/usergroups";
Expand All @@ -36,7 +39,6 @@ export const EditGroup = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const navigate = useNavigate();
const { state }: { state: UserGroupDTO } = useLocation();
const { id } = useParams();
const canUpdatePermissions = usePermission("grouppermission.update");

Expand Down Expand Up @@ -82,11 +84,13 @@ export const EditGroup = () => {
resetForm,
errors,
touched,
values,
setFieldValue,
setValues,
} = useFormik({
initialValues: state,
initialValues: group.data ?? { code: "" },
validationSchema: userGroupSchema(t),
onSubmit: (values: UserGroupDTO) => {
values.permissions = groupPermissions;
const dto: UserGroupDTO = { ...values, permissions: groupPermissions };

dispatch(updateUserGroup(dto));
Expand All @@ -96,19 +100,26 @@ export const EditGroup = () => {
// load permissions and group on mount
useEffect(() => {
dispatch(getAllPermissions());
dispatch(getUserGroup(state.code));
dispatch(getUserGroup(id!!));
return () => {
dispatch(updateUserGroupReset());
};
}, [dispatch, state.code]);
}, [dispatch, id]);

// update group permissions on group load
useEffect(() => {
if (group.data) {
setGroupPermissions(group.data.permissions ?? []);
setValues(group.data);
}
}, [group.data]);

useEffect(() => {
return () => {
dispatch(getUserGroupReset());
};
}, []);

// compare permissions to update the update stack
// and display permissions when ready
useEffect(() => {
Expand All @@ -125,15 +136,18 @@ export const EditGroup = () => {
}
}, [canUpdatePermissions, group.data, permissions.data, groupPermissions]);

if (state?.code !== id) {
return <Navigate to={PATHS.admin_users} state={{ tab: "groups" }} />;
}

const handleFormReset = () => {
resetForm();
setGroupPermissions(group.data?.permissions ?? []);
};

const handleCheckboxChange = useCallback(
(fieldName: string) => (value: boolean) => {
setFieldValue(fieldName, value);
},
[setFieldValue]
);

if (permissions.hasFailed)
return (
<InfoBox
Expand All @@ -151,7 +165,7 @@ export const EditGroup = () => {

return (
<>
{group.status === "LOADING" || permissions.status === "LOADING" ? (
{group.isLoading || group.status === "IDLE" || permissions.isLoading ? (
<CircularProgress style={{ marginLeft: "50%", position: "relative" }} />
) : (
<div className="newGroupForm">
Expand Down Expand Up @@ -180,6 +194,14 @@ export const EditGroup = () => {
/>
</div>
</div>
<div className="newGroupForm__item fullWidth">
<CheckboxField
fieldName={"deleted"}
checked={!!values.deleted}
label={t("common.deleted")}
onChange={handleCheckboxChange("deleted")}
/>
</div>

{isPermissionEditorAvailable && (
<GroupPermissionsEditor
Expand Down Expand Up @@ -219,9 +241,9 @@ export const EditGroup = () => {
<div className="info-box-container">
<InfoBox
type="error"
message={
update.error?.message ?? t("common.somethingwrong")
}
message={t(
update.error?.message ?? "common.somethingwrong"
)}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { object, string } from "yup";
import { UserGroupDTO } from "../../../../../generated";
import { TFunction } from "react-i18next";
import { boolean, object, string } from "yup";
import { UserGroupDTO } from "../../../../../generated";

export const userGroupSchema = (t: TFunction<"translation">) =>
object().shape<UserGroupDTO>({
code: string().min(2).required(t("user.validateGroupCode")),
desc: string(),
deleted: boolean(),
});
18 changes: 17 additions & 1 deletion src/components/accessories/admin/users/editUser/EditUserForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
TextField as MuiTextField,
} from "@mui/material";
import { useFormik } from "formik";
import React, { ReactNode } from "react";
import React, { ReactNode, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";

Expand All @@ -17,6 +17,7 @@ import ConfirmationDialog from "../../../confirmationDialog/ConfirmationDialog";
import InfoBox from "../../../infoBox/InfoBox";
import TextField from "../../../textField/TextField";

import CheckboxField from "components/accessories/checkboxField/CheckboxField";
import { PATHS } from "../../../../../consts";
import "./styles.scss";
import { userSchema } from "./validation";
Expand Down Expand Up @@ -70,6 +71,13 @@ export const EditUserForm = ({
onSubmit: handleFormSubmit,
});

const handleCheckboxChange = useCallback(
(fieldName: string) => (value: boolean) => {
setFieldValue(fieldName, value);
},
[setFieldValue]
);

return (
<div className="editUserForm">
<form className="editUserForm__form" onSubmit={handleSubmit}>
Expand Down Expand Up @@ -152,6 +160,14 @@ export const EditUserForm = ({
onBlur={handleBlur}
/>
</div>
<div className="editUserForm__item fullWidth">
<CheckboxField
fieldName={"deleted"}
checked={!!values.deleted}
label={t("common.deleted")}
onChange={handleCheckboxChange("deleted")}
/>
</div>
</div>

<div className="editUserForm__item fullWidth">
Expand Down
5 changes: 3 additions & 2 deletions src/components/accessories/admin/users/editUser/validation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { TFunction } from "react-i18next";
import { object, ref, string } from "yup";
import { boolean, object, ref, string } from "yup";
import { UserDTO, UserGroupDTO } from "../../../../../generated";

// min 5 characters, 1 upper case letter, 1 lower case letter, 1 numeric digit.
export const passwordRules = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{5,}$/;

export const userSchema = (t: TFunction<"translation">) =>
object().shape<
Pick<UserDTO, "userGroupName" | "desc"> & {
Pick<UserDTO, "userGroupName" | "deleted" | "desc"> & {
passwd: string | undefined;
passwd2: string | undefined;
}
Expand All @@ -24,6 +24,7 @@ export const userSchema = (t: TFunction<"translation">) =>
.matches(passwordRules, {
message: t("user.validatePasswordTooWeak"),
}),
deleted: boolean(),
passwd2: string()
.oneOf([ref("passwd")], t("user.validatePasswordMustMatch"))
.notRequired()
Expand Down
22 changes: 20 additions & 2 deletions src/components/accessories/admin/users/newGroup/NewGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useFormik } from "formik";
import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import React, { useEffect } from "react";
import React, { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";

Expand All @@ -13,6 +13,7 @@ import TextField from "../../../textField/TextField";
import { PATHS } from "../../../../../consts";
import { UserGroupDTO } from "../../../../../generated";

import CheckboxField from "components/accessories/checkboxField/CheckboxField";
import {
createUserGroup,
createUserGroupReset,
Expand Down Expand Up @@ -42,6 +43,8 @@ export const NewGroup = () => {
resetForm,
errors,
touched,
values,
setFieldValue,
} = useFormik({
initialValues,
validationSchema: userGroupSchema(t),
Expand All @@ -56,6 +59,13 @@ export const NewGroup = () => {
};
}, [dispatch]);

const handleCheckboxChange = useCallback(
(fieldName: string) => (value: boolean) => {
setFieldValue(fieldName, value);
},
[setFieldValue]
);

return (
<div className="newGroupForm">
<form className="newGroupForm__form" onSubmit={handleSubmit}>
Expand All @@ -81,14 +91,22 @@ export const NewGroup = () => {
onBlur={handleBlur}
/>
</div>
<div className="newGroupForm__item fullWidth">
<CheckboxField
fieldName={"deleted"}
checked={!!values.deleted}
label={t("common.deleted")}
onChange={handleCheckboxChange("deleted")}
/>
</div>
</div>
<div className="newGroupForm__item fullWidth">
<p>{t("user.groupPermissionsOnlyOnUpdate")}</p>
{create.hasFailed && (
<div className="info-box-container">
<InfoBox
type="error"
message={create.error?.message ?? t("common.somethingwrong")}
message={t(create.error?.message ?? "common.somethingwrong")}
/>
</div>
)}
Expand Down
21 changes: 19 additions & 2 deletions src/components/accessories/admin/users/newUser/NewUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "@mui/material";
import { useFormik } from "formik";
import { useAppDispatch, useAppSelector } from "libraries/hooks/redux";
import React, { ReactNode, useEffect } from "react";
import React, { ReactNode, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import checkIcon from "../../../../../assets/check-icon.png";
Expand All @@ -15,9 +15,11 @@ import Button from "../../../button/Button";
import ConfirmationDialog from "../../../confirmationDialog/ConfirmationDialog";
import TextField from "../../../textField/TextField";

import { UserDTO, UserGroupDTO } from "../../../../../generated";
import { IState } from "../../../../../types";

import CheckboxField from "components/accessories/checkboxField/CheckboxField";
import { UserDTO } from "generated/models/UserDTO";
import { UserGroupDTO } from "generated/models/UserGroupDTO";
import { PATHS } from "../../../../../consts";
import { getUserGroups } from "../../../../../state/usergroups";
import { createUser, createUserReset } from "../../../../../state/users";
Expand Down Expand Up @@ -76,6 +78,13 @@ export const NewUser = () => {
};
}, [create.hasSucceeded, dispatch, navigate]);

const handleCheckboxChange = useCallback(
(fieldName: string) => (value: boolean) => {
setFieldValue(fieldName, value);
},
[setFieldValue]
);

return (
<div className="newUserForm">
<form className="newUserForm__form" onSubmit={handleSubmit}>
Expand Down Expand Up @@ -167,6 +176,14 @@ export const NewUser = () => {
multiline
/>
</div>
<div className="newUserForm__item fullWidth">
<CheckboxField
fieldName={"deleted"}
checked={!!values.deleted}
label={t("common.deleted")}
onChange={handleCheckboxChange("deleted")}
/>
</div>
</div>
<div className="newUserForm__buttonSet">
<div className="submit_button">
Expand Down
3 changes: 2 additions & 1 deletion src/components/accessories/admin/users/newUser/validation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TFunction } from "react-i18next";
import { object, ref, string } from "yup";
import { boolean, object, ref, string } from "yup";
import { UserGroupDTO } from "../../../../../generated";
import { FormProps } from "./NewUser";
// min 5 characters, 1 upper case letter, 1 lower case letter, 1 numeric digit.
Expand Down Expand Up @@ -30,4 +30,5 @@ export const userSchema = (t: TFunction<"translation">) =>
.required(t("user.validatePasswordNeeded"))
.oneOf([ref("passwd")], t("user.validatePasswordMustMatch")),
desc: string(),
deleted: boolean(),
});
Loading

0 comments on commit 1e9f092

Please sign in to comment.