Skip to content

Commit

Permalink
Merge pull request #243 from Arnei/to-redux-toolkit-userDetails
Browse files Browse the repository at this point in the history
Modernize redux: userDetailsSlice
  • Loading branch information
Arnei authored Feb 28, 2024
2 parents f952571 + 6a75c62 commit c7154b9
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 178 deletions.
24 changes: 0 additions & 24 deletions app/src/actions/userDetailsActions.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/components/users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import TableFilters from "../shared/TableFilters";
import Table from "../shared/Table";
import Notifications from "../shared/Notifications";
import NewResourceModal from "../shared/NewResourceModal";
import { usersTemplateMap } from "../../configs/tableConfigs/usersTableConfig";
import { usersTemplateMap } from "../../configs/tableConfigs/usersTableMap";
import { getTotalUsers } from "../../selectors/userSelectors";
import { fetchUsers } from "../../thunks/userThunks";
import {
Expand Down
9 changes: 4 additions & 5 deletions app/src/components/users/partials/UsersActionsCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import { connect } from "react-redux";
import ConfirmModal from "../../shared/ConfirmModal";
import { deleteUser } from "../../../thunks/userThunks";
import UserDetailsModal from "./modal/UserDetailsModal";
import { fetchUserDetails } from "../../../thunks/userDetailsThunks";
import { getUserInformation } from "../../../selectors/userInfoSelectors";
import { hasAccess } from "../../../utils/utils";
import { useAppDispatch } from "../../../store";
import { fetchUserDetails } from "../../../slices/userDetailsSlice";

/**
* This component renders the action cells of users in the table view
*/
const UsersActionCell = ({
row,
deleteUser,
fetchUserDetails,
user
}: any) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();

const [displayDeleteConfirmation, setDeleteConfirmation] = useState(false);
const [displayUserDetails, setUserDetails] = useState(false);
Expand All @@ -32,7 +33,7 @@ const UsersActionCell = ({
};

const showUserDetails = async () => {
await fetchUserDetails(row.username);
await dispatch(fetchUserDetails(row.username));

setUserDetails(true);
};
Expand Down Expand Up @@ -93,8 +94,6 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({
// @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type.
deleteUser: (id) => dispatch(deleteUser(id)),
// @ts-expect-error TS(7006): Parameter 'username' implicitly has an 'any' type.
fetchUserDetails: (username) => dispatch(fetchUserDetails(username)),
});

export default connect(mapStateToProps, mapDispatchToProps)(UsersActionCell);
35 changes: 12 additions & 23 deletions app/src/components/users/partials/modal/UserDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,28 @@ import { Formik } from "formik";
import cn from "classnames";
import { EditUserSchema } from "../../../../utils/validate";
import UserRolesTab from "../wizard/UserRolesTab";
import { connect } from "react-redux";
import { getUserDetails } from "../../../../selectors/userDetailsSelectors";
import EditUserGeneralTab from "../wizard/EditUserGeneralTab";
import UserEffectiveRolesTab from "../wizard/UserEffectiveRolesTab";
import { updateUserDetails } from "../../../../thunks/userDetailsThunks";
import ModalNavigation from "../../../shared/modals/ModalNavigation";
import { useAppDispatch, useAppSelector } from "../../../../store";
import { updateUserDetails } from "../../../../slices/userDetailsSlice";

/**
* This component manages the pages of the user details
*/
const UserDetails = ({
close,
userDetails,
updateUserDetails
}: any) => {
const UserDetails: React.FC<{
close: () => void
}> = ({
close,
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();

const [page, setPage] = useState(0);

const userDetails = useAppSelector(state => getUserDetails(state));

const initialValues = {
...userDetails,
password: "",
Expand Down Expand Up @@ -54,7 +57,7 @@ const UserDetails = ({

// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type.
const handleSubmit = (values) => {
updateUserDetails(values, userDetails.username);
dispatch(updateUserDetails({values: values, username: userDetails.username}));
close();
};

Expand Down Expand Up @@ -101,18 +104,4 @@ const UserDetails = ({
);
};

// Getting state data out of redux store
// @ts-expect-error TS(7006): Parameter 'state' implicitly has an 'any' type.
const mapStateToProps = (state) => ({
userDetails: getUserDetails(state),
});

// Mapping actions to dispatch
// @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type.
const mapDispatchToProps = (dispatch) => ({
// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type.
updateUserDetails: (values, username) =>
dispatch(updateUserDetails(values, username)),
});

export default connect(mapStateToProps, mapDispatchToProps)(UserDetails);
export default UserDetails;
12 changes: 0 additions & 12 deletions app/src/configs/tableConfigs/usersTableConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import UsersActionCell from "../../components/users/partials/UsersActionsCell";
import UsersRolesCell from "../../components/users/partials/UsersRolesCell";

/**
* Config that contains the columns and further information regarding users. These are the information that never or hardly changes.
* That's why it is hard coded here and not fetched from server.
Expand Down Expand Up @@ -51,12 +48,3 @@ export const usersTableConfig = {
multiSelect: false,
};

/**
* This map contains the mapping between the template strings above and the corresponding react component.
* This helps to render different templates of cells more dynamically. Even empty needed, because Table component
* uses template map.
*/
export const usersTemplateMap = {
UsersActionsCell: UsersActionCell,
UsersRolesCell: UsersRolesCell,
};
12 changes: 12 additions & 0 deletions app/src/configs/tableConfigs/usersTableMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import UsersActionCell from "../../components/users/partials/UsersActionsCell";
import UsersRolesCell from "../../components/users/partials/UsersRolesCell";

/**
* This map contains the mapping between the template strings above and the corresponding react component.
* This helps to render different templates of cells more dynamically. Even empty needed, because Table component
* uses template map.
*/
export const usersTemplateMap = {
UsersActionsCell: UsersActionCell,
UsersRolesCell: UsersRolesCell,
};
62 changes: 0 additions & 62 deletions app/src/reducers/userDetailsReducer.ts

This file was deleted.

4 changes: 3 additions & 1 deletion app/src/selectors/userDetailsSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { RootState } from "../store";

/**
* This file contains selectors regarding details of a certain user
*/
export const getUserDetails = (state: any) => state.userDetails;
export const getUserDetails = (state: RootState) => state.userDetails;
104 changes: 104 additions & 0 deletions app/src/slices/userDetailsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import axios from 'axios';
import { addNotification } from "../thunks/notificationThunks";
import { buildUserBody } from "../utils/resourceUtils";

/**
* This file contains redux reducer for actions affecting the state of details of a user
*/
export type UserDetailsState = {
status: 'uninitialized' | 'loading' | 'succeeded' | 'failed',
error: SerializedError | null,
provider: string,
roles: string[],
name: string,
username: string,
email: string,
manageable: boolean,
};

// Initial state of userDetails in redux store
const initialState: UserDetailsState = {
status: 'uninitialized',
error: null,
provider: "",
roles: [],
name: "",
username: "",
email: "",
manageable: false,
};

// fetch details about certain user from server
export const fetchUserDetails = createAsyncThunk('userDetails/fetchUserDetails', async (username: any) => {
// Just make the async request here, and return the response.
// This will automatically dispatch a `pending` action first,
// and then `fulfilled` or `rejected` actions based on the promise.
const res = await axios.get(`/admin-ng/users/${username}.json`);
return res.data;
});

// update existing user with changed values
export const updateUserDetails = createAsyncThunk('userDetails/updateUserDetails', async (params: {values: any, username: string}, {dispatch}) => {
const { username, values } = params

// get URL params used for put request
let data = buildUserBody(values);

// PUT request
axios
.put(`/admin-ng/users/${username}.json`, data)
.then((response) => {
console.info(response);
dispatch(addNotification("success", "USER_UPDATED"));
})
.catch((response) => {
console.error(response);
dispatch(addNotification("error", "USER_NOT_SAVED"));
});
});

const userDetailsSlice = createSlice({
name: 'userDetails',
initialState,
reducers: {},
// These are used for thunks
extraReducers: builder => {
builder
.addCase(fetchUserDetails.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserDetails.fulfilled, (state, action: PayloadAction<{
provider: UserDetailsState["provider"],
roles: UserDetailsState["roles"],
name: UserDetailsState["name"],
username: UserDetailsState["username"],
email: UserDetailsState["email"],
manageable: UserDetailsState["manageable"],
}>) => {
state.status = 'succeeded';
const userDetails = action.payload;
state.provider = userDetails.provider;
state.roles = userDetails.roles;
state.name = userDetails.name;
state.username = userDetails.username;
state.email = !!userDetails.email ? userDetails.email : "";
state.manageable = userDetails.manageable;
})
.addCase(fetchUserDetails.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error;
state.provider = "";
state.roles = [];
state.name = "";
state.username = "";
state.email = "";
state.manageable = false;
});
}
});

// export const {} = userDetailsSlice.actions;

// Export the slice reducer as the default export
export default userDetailsSlice.reducer;
2 changes: 1 addition & 1 deletion app/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { notifications } from "./reducers/notificationReducers";
import workflows from "./slices/workflowSlice";
import eventDetails from "./reducers/eventDeatilsReducers";
import seriesDetails from "./reducers/seriesDetailsReducers";
import userDetails from "./slices/userDetailsSlice";
import recordingDetails from "./slices/recordingDetailsSlice";
import userDetails from "./reducers/userDetailsReducer";
import groupDetails from "./slices/groupDetailsSlice";
import aclDetails from "./slices/aclDetailsSlice";
import themeDetails from "./slices/themeDetailsSlice";
Expand Down
Loading

0 comments on commit c7154b9

Please sign in to comment.