diff --git a/app/src/actions/userActions.ts b/app/src/actions/userActions.ts
deleted file mode 100644
index 6209a0e939..0000000000
--- a/app/src/actions/userActions.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/**
- * This file contains all redux actions that can be executed on users
- */
-
-// Constants of action types for fetching users from server
-export const LOAD_USERS_IN_PROGRESS = "LOAD_USERS_IN_PROGRESS";
-export const LOAD_USERS_SUCCESS = "LOAD_USERS_SUCCESS";
-export const LOAD_USERS_FAILURE = "LOAD_USERS_FAILURE";
-
-// Constants of action types affecting UI
-export const SET_USER_COLUMNS = "SET_USER_COLUMNS";
-
-// Actions affecting fetching users from server
-
-export const loadUsersInProgress = () => ({
- type: LOAD_USERS_IN_PROGRESS,
-});
-
-// @ts-expect-error TS(7006): Parameter 'users' implicitly has an 'any' type.
-export const loadUsersSuccess = (users) => ({
- type: LOAD_USERS_SUCCESS,
- payload: { users },
-});
-
-export const loadUsersFailure = () => ({
- type: LOAD_USERS_FAILURE,
-});
-
-// Action affecting UI
-
-// @ts-expect-error TS(7006): Parameter 'updatedColumns' implicitly has an 'any'... Remove this comment to see the full error message
-export const setUserColumns = (updatedColumns) => ({
- type: SET_USER_COLUMNS,
- payload: { updatedColumns },
-});
diff --git a/app/src/components/shared/MainNav.tsx b/app/src/components/shared/MainNav.tsx
index 23bc900d0f..cd3a13f9c8 100644
--- a/app/src/components/shared/MainNav.tsx
+++ b/app/src/components/shared/MainNav.tsx
@@ -16,7 +16,6 @@ import {
} from "../../thunks/tableThunks";
import { fetchEvents } from "../../slices/eventSlice";
import { fetchRecordings } from "../../thunks/recordingThunks";
-import { fetchUsers } from "../../thunks/userThunks";
import { fetchThemes } from "../../slices/themeSlice";
import { fetchFilters, fetchStats } from "../../thunks/tableFilterThunks";
import { setOffset } from "../../actions/tableActions";
@@ -28,6 +27,7 @@ import { GlobalHotKeys } from "react-hotkeys";
import { availableHotkeys } from "../../configs/hotkeysConfig";
import { fetchAcls } from "../../slices/aclSlice";
import { useAppDispatch } from "../../store";
+import { fetchUsers } from "../../slices/userSlice";
import { fetchServers } from "../../slices/serverSlice";
import { fetchSeries } from "../../slices/seriesSlice";
import { fetchJobs } from "../../slices/jobSlice";
@@ -56,8 +56,6 @@ const MainNav = ({
loadingServersIntoTable,
// @ts-expect-error TS(7031): Binding element 'loadingServicesIntoTable' implici... Remove this comment to see the full error message
loadingServicesIntoTable,
-// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message
- loadingUsers,
// @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message
loadingUsersIntoTable,
// @ts-expect-error TS(7031): Binding element 'loadingGroupsIntoTable' implicitl... Remove this comment to see the full error message
@@ -165,7 +163,7 @@ const MainNav = ({
resetOffset();
// Fetching users from server
- loadingUsers();
+ dispatch(fetchUsers());
// Load users into table
loadingUsersIntoTable();
@@ -338,7 +336,6 @@ const mapDispatchToProps = (dispatch) => ({
loadingJobsIntoTable: () => dispatch(loadJobsIntoTable()),
loadingServersIntoTable: () => dispatch(loadServersIntoTable()),
loadingServicesIntoTable: () => dispatch(loadServicesIntoTable()),
- loadingUsers: () => dispatch(fetchUsers()),
loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()),
loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()),
loadingAclsIntoTable: () => dispatch(loadAclsIntoTable()),
diff --git a/app/src/components/users/Acls.tsx b/app/src/components/users/Acls.tsx
index 44d096a636..c9d9e43bc3 100644
--- a/app/src/components/users/Acls.tsx
+++ b/app/src/components/users/Acls.tsx
@@ -9,7 +9,6 @@ import Notifications from "../shared/Notifications";
import NewResourceModal from "../shared/NewResourceModal";
import { aclsTemplateMap } from "../../configs/tableConfigs/aclsTableMap";
import { fetchFilters } from "../../thunks/tableFilterThunks";
-import { fetchUsers } from "../../thunks/userThunks";
import {
loadAclsIntoTable,
loadGroupsIntoTable,
@@ -26,6 +25,7 @@ import { getUserInformation } from "../../selectors/userInfoSelectors";
import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors";
import { useAppDispatch, useAppSelector } from "../../store";
import { fetchAcls } from "../../slices/aclSlice";
+import { fetchUsers } from "../../slices/userSlice";
import { fetchGroups } from "../../slices/groupSlice";
/**
@@ -36,10 +36,10 @@ const Acls: React.FC = () => {
const [displayNavigation, setNavigation] = useState(false);
const [displayNewAclModal, setNewAclModal] = useState(false);
- const dispatch = useAppDispatch();
- const acls = useAppSelector(state => getTotalAcls(state));
+ const dispatch = useAppDispatch();
+ const acls = useAppSelector(state => getTotalAcls(state));
const user = useAppSelector(state => getUserInformation(state));
- const currentFilterType = useAppSelector(state => getCurrentFilterResource(state));
+ const currentFilterType = useAppSelector(state => getCurrentFilterResource(state));
const loadAcls = async () => {
// Fetching acls from server
diff --git a/app/src/components/users/Groups.tsx b/app/src/components/users/Groups.tsx
index fe7cc026ca..74cc769b93 100644
--- a/app/src/components/users/Groups.tsx
+++ b/app/src/components/users/Groups.tsx
@@ -11,7 +11,6 @@ import NewResourceModal from "../shared/NewResourceModal";
import { getTotalGroups } from "../../selectors/groupSelectors";
import { groupsTemplateMap } from "../../configs/tableConfigs/groupsTableMap";
import { fetchFilters } from "../../thunks/tableFilterThunks";
-import { fetchUsers } from "../../thunks/userThunks";
import {
loadAclsIntoTable,
loadGroupsIntoTable,
@@ -27,6 +26,7 @@ import { hasAccess } from "../../utils/utils";
import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors";
import { fetchAcls } from "../../slices/aclSlice";
import { useAppDispatch, useAppSelector } from "../../store";
+import { fetchUsers } from "../../slices/userSlice";
import { fetchGroups } from "../../slices/groupSlice";
/**
@@ -37,8 +37,6 @@ const Groups = ({
loadingGroupsIntoTable,
// @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message
loadingFilters,
-// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message
- loadingUsers,
// @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message
loadingUsersIntoTable,
// @ts-expect-error TS(7031): Binding element 'loadingAclsIntoTable' implicitly ... Remove this comment to see the full error message
@@ -77,7 +75,7 @@ const Groups = ({
resetOffset();
// Fetching users from server
- loadingUsers();
+ dispatch(fetchUsers());
// Load users into table
loadingUsersIntoTable();
@@ -216,7 +214,6 @@ const mapDispatchToProps = (dispatch) => ({
// @ts-expect-error TS(7006): Parameter 'resource' implicitly has an 'any' type.
loadingFilters: (resource) => dispatch(fetchFilters(resource)),
loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()),
- loadingUsers: () => dispatch(fetchUsers()),
loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()),
loadingAclsIntoTable: () => dispatch(loadAclsIntoTable()),
resetTextFilter: () => dispatch(editTextFilter("")),
diff --git a/app/src/components/users/Users.tsx b/app/src/components/users/Users.tsx
index 98cc2f5302..20a36176e1 100644
--- a/app/src/components/users/Users.tsx
+++ b/app/src/components/users/Users.tsx
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import cn from "classnames";
-import { connect } from "react-redux";
import MainNav from "../shared/MainNav";
import TableFilters from "../shared/TableFilters";
import Table from "../shared/Table";
@@ -10,7 +9,6 @@ import Notifications from "../shared/Notifications";
import NewResourceModal from "../shared/NewResourceModal";
import { usersTemplateMap } from "../../configs/tableConfigs/usersTableMap";
import { getTotalUsers } from "../../selectors/userSelectors";
-import { fetchUsers } from "../../thunks/userThunks";
import {
loadAclsIntoTable,
loadGroupsIntoTable,
@@ -26,75 +24,64 @@ import { getUserInformation } from "../../selectors/userInfoSelectors";
import { hasAccess } from "../../utils/utils";
import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors";
import { fetchAcls } from "../../slices/aclSlice";
-import { useAppDispatch } from "../../store";
+import { useAppDispatch, useAppSelector } from "../../store";
+import { fetchUsers } from "../../slices/userSlice";
import { fetchGroups } from "../../slices/groupSlice";
/**
* This component renders the table view of users
*/
-const Users = ({
-// @ts-expect-error TS(7031): Binding element 'loadingUsers' implicitly has an '... Remove this comment to see the full error message
- loadingUsers,
-// @ts-expect-error TS(7031): Binding element 'loadingUsersIntoTable' implicitly... Remove this comment to see the full error message
- loadingUsersIntoTable,
-// @ts-expect-error TS(7031): Binding element 'users' implicitly has an 'any' ty... Remove this comment to see the full error message
- users,
-// @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message
- loadingFilters,
-// @ts-expect-error TS(7031): Binding element 'loadingGroupsIntoTable' implicitl... Remove this comment to see the full error message
- loadingGroupsIntoTable,
-// @ts-expect-error TS(7031): Binding element 'loadingAclsIntoTable' implicitly ... Remove this comment to see the full error message
- loadingAclsIntoTable,
-// @ts-expect-error TS(7031): Binding element 'resetTextFilter' implicitly has a... Remove this comment to see the full error message
- resetTextFilter,
-// @ts-expect-error TS(7031): Binding element 'resetOffset' implicitly has an 'a... Remove this comment to see the full error message
- resetOffset,
-// @ts-expect-error TS(7031): Binding element 'user' implicitly has an 'any' typ... Remove this comment to see the full error message
- user,
-// @ts-expect-error TS(7031): Binding element 'currentFilterType' implicitly has... Remove this comment to see the full error message
- currentFilterType,
-}) => {
+const Users: React.FC = () => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
+ const dispatch = useAppDispatch();
const [displayNavigation, setNavigation] = useState(false);
const [displayNewUserModal, setNewUserModal] = useState(false);
+ const users = useAppSelector(state => getTotalUsers(state));
+ const user = useAppSelector(state => getUserInformation(state));
+ const currentFilterType = useAppSelector(state => getCurrentFilterResource(state));
+
+ // TODO: Get rid of the wrappers when modernizing redux is done
+ const fetchUsersWrapper = () => {
+ dispatch(fetchUsers())
+ }
+
const loadUsers = async () => {
// Fetching users from server
- await loadingUsers();
+ await dispatch(fetchUsers());
// Load users into table
- loadingUsersIntoTable();
+ dispatch(loadUsersIntoTable());
};
const loadGroups = () => {
// Reset the current page to first page
- resetOffset();
+ dispatch(setOffset(0));
// Fetching groups from server
dispatch(fetchGroups());
// Load groups into table
- loadingGroupsIntoTable();
+ dispatch(loadGroupsIntoTable());
};
const loadAcls = () => {
// Reset the current page to first page
- resetOffset();
+ dispatch(setOffset(0));
// Fetching acls from server
dispatch(fetchAcls());
// Load acls into table
- loadingAclsIntoTable();
+ dispatch(loadAclsIntoTable());
};
useEffect(() => {
if ("users" !== currentFilterType) {
- loadingFilters("users");
+ dispatch(fetchFilters("users"));
}
- resetTextFilter();
+ dispatch(editTextFilter(""));
// Load users on mount
loadUsers().then((r) => console.info(r));
@@ -183,8 +170,8 @@ const Users = ({
{/* Include filters component */}
{t("USERS.USERS.TABLE.CAPTION")}
@@ -198,25 +185,4 @@ const Users = ({
);
};
-// Getting state data out of redux store
-// @ts-expect-error TS(7006): Parameter 'state' implicitly has an 'any' type.
-const mapStateToProps = (state) => ({
- users: getTotalUsers(state),
- user: getUserInformation(state),
- currentFilterType: getCurrentFilterResource(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 'resource' implicitly has an 'any' type.
- loadingFilters: (resource) => dispatch(fetchFilters(resource)),
- loadingUsers: () => dispatch(fetchUsers()),
- loadingUsersIntoTable: () => dispatch(loadUsersIntoTable()),
- loadingGroupsIntoTable: () => dispatch(loadGroupsIntoTable()),
- loadingAclsIntoTable: () => dispatch(loadAclsIntoTable()),
- resetTextFilter: () => dispatch(editTextFilter("")),
- resetOffset: () => dispatch(setOffset(0)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Users);
+export default Users;
diff --git a/app/src/components/users/partials/UsersActionsCell.tsx b/app/src/components/users/partials/UsersActionsCell.tsx
index c7ffb2fdd3..919d7f8ca3 100644
--- a/app/src/components/users/partials/UsersActionsCell.tsx
+++ b/app/src/components/users/partials/UsersActionsCell.tsx
@@ -2,10 +2,10 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import ConfirmModal from "../../shared/ConfirmModal";
-import { deleteUser } from "../../../thunks/userThunks";
import UserDetailsModal from "./modal/UserDetailsModal";
import { getUserInformation } from "../../../selectors/userInfoSelectors";
import { hasAccess } from "../../../utils/utils";
+import { deleteUser } from "../../../slices/userSlice";
import { useAppDispatch } from "../../../store";
import { fetchUserDetails } from "../../../slices/userDetailsSlice";
@@ -14,7 +14,6 @@ import { fetchUserDetails } from "../../../slices/userDetailsSlice";
*/
const UsersActionCell = ({
row,
- deleteUser,
user
}: any) => {
const { t } = useTranslation();
@@ -29,7 +28,7 @@ const UsersActionCell = ({
// @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type.
const deletingUser = (id) => {
- deleteUser(id);
+ dispatch(deleteUser(id));
};
const showUserDetails = async () => {
@@ -92,8 +91,6 @@ const mapStateToProps = (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 'id' implicitly has an 'any' type.
- deleteUser: (id) => dispatch(deleteUser(id)),
});
export default connect(mapStateToProps, mapDispatchToProps)(UsersActionCell);
diff --git a/app/src/components/users/partials/wizard/GroupUsersPage.tsx b/app/src/components/users/partials/wizard/GroupUsersPage.tsx
index 852cdf4780..4514eae008 100644
--- a/app/src/components/users/partials/wizard/GroupUsersPage.tsx
+++ b/app/src/components/users/partials/wizard/GroupUsersPage.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import SelectContainer from "../../../shared/wizard/SelectContainer";
import WizardNavigationButtons from "../../../shared/wizard/WizardNavigationButtons";
-import { fetchUsersAndUsernames } from "../../../../thunks/userThunks";
+import { fetchUsersAndUsernames } from "../../../../slices/userSlice";
/**
* This component renders the user selection page of the new group wizard and group details wizard
diff --git a/app/src/components/users/partials/wizard/NewUserWizard.tsx b/app/src/components/users/partials/wizard/NewUserWizard.tsx
index 2d97ddd3e0..40096f6cff 100644
--- a/app/src/components/users/partials/wizard/NewUserWizard.tsx
+++ b/app/src/components/users/partials/wizard/NewUserWizard.tsx
@@ -7,8 +7,9 @@ import NewUserGeneralTab from "./NewUserGeneralTab";
import UserRolesTab from "./UserRolesTab";
import { initialFormValuesNewUser } from "../../../../configs/modalConfig";
import { getUsernames } from "../../../../selectors/userSelectors";
-import { postNewUser } from "../../../../thunks/userThunks";
import { NewUserSchema } from "../../../../utils/validate";
+import { postNewUser } from "../../../../slices/userSlice";
+import { useAppDispatch } from "../../../../store";
/**
* This component renders the new user wizard
@@ -16,9 +17,9 @@ import { NewUserSchema } from "../../../../utils/validate";
const NewUserWizard = ({
close,
usernames,
- postNewUser
}: any) => {
const { t } = useTranslation();
+ const dispatch = useAppDispatch();
const navStyle = {
left: "0px",
@@ -35,7 +36,7 @@ const NewUserWizard = ({
// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type.
const handleSubmit = (values) => {
- const response = postNewUser(values);
+ const response = dispatch(postNewUser(values));
console.info(response);
close();
};
@@ -111,8 +112,6 @@ const mapStateToProps = (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.
- postNewUser: (values) => dispatch(postNewUser(values)),
});
export default connect(mapStateToProps, mapDispatchToProps)(NewUserWizard);
diff --git a/app/src/reducers/userReducers.ts b/app/src/reducers/userReducers.ts
deleted file mode 100644
index be71881bc7..0000000000
--- a/app/src/reducers/userReducers.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig";
-import {
- LOAD_USERS_FAILURE,
- LOAD_USERS_IN_PROGRESS,
- LOAD_USERS_SUCCESS,
- SET_USER_COLUMNS,
-} from "../actions/userActions";
-
-/**
- * This file contains redux reducer for actions affecting the state of users
- */
-
-// Fill columns initially with columns defined in usersTableConfig
-const initialColumns = usersTableConfig.columns.map((column) => ({
- ...column,
- deactivated: false,
-}));
-
-// Initial state of users in redux store
-const initialState = {
- isLoading: false,
- results: [],
- columns: initialColumns,
- total: 0,
- count: 0,
- offset: 0,
- limit: 0,
-};
-
-// Reducer for users
-// @ts-expect-error TS(7006): Parameter 'action' implicitly has an 'any' type.
-const users = (state = initialState, action) => {
- const { type, payload } = action;
- switch (type) {
- case LOAD_USERS_IN_PROGRESS: {
- return {
- ...state,
- isLoading: true,
- };
- }
- case LOAD_USERS_SUCCESS: {
- const { users } = payload;
- return {
- ...state,
- isLoading: false,
- total: users.total,
- count: users.count,
- limit: users.limit,
- offset: users.offset,
- results: users.results,
- };
- }
- case LOAD_USERS_FAILURE: {
- return {
- ...state,
- isLoading: false,
- };
- }
- case SET_USER_COLUMNS: {
- const { updatedColumns } = payload;
- return {
- ...state,
- columns: updatedColumns,
- };
- }
- default:
- return state;
- }
-};
-
-export default users;
diff --git a/app/src/selectors/userSelectors.ts b/app/src/selectors/userSelectors.ts
index fcdceeb3ba..a9624c054e 100644
--- a/app/src/selectors/userSelectors.ts
+++ b/app/src/selectors/userSelectors.ts
@@ -2,11 +2,11 @@
* This file contains selectors regarding users
*/
import { createSelector } from "reselect";
+import { RootState } from "../store";
-export const getUsers = (state: any) => state.users.results;
-export const getTotalUsers = (state: any) => state.users.total;
+export const getUsers = (state: RootState) => state.users.results;
+export const getTotalUsers = (state: RootState) => state.users.total;
export const getUsernames = createSelector(getUsers, (users) => {
-// @ts-expect-error TS(7006): Parameter 'user' implicitly has an 'any' type.
return users.map((user) => user.username);
});
diff --git a/app/src/slices/userSlice.ts b/app/src/slices/userSlice.ts
new file mode 100644
index 0000000000..ce0e7842df
--- /dev/null
+++ b/app/src/slices/userSlice.ts
@@ -0,0 +1,155 @@
+import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit'
+import { usersTableConfig } from "../configs/tableConfigs/usersTableConfig";
+import axios from 'axios';
+import { transformToIdValueArray } from "../utils/utils";
+import { buildUserBody, getURLParams } from "../utils/resourceUtils";
+import { addNotification } from '../thunks/notificationThunks';
+
+/**
+ * This file contains redux reducer for actions affecting the state of users
+ */
+type UserResult = {
+ email?: string,
+ manageable: boolean,
+ name: string,
+ provider: string,
+ roles: { name: string, type: string }[],
+ username: string,
+}
+
+type UsersState = {
+ status: 'uninitialized' | 'loading' | 'succeeded' | 'failed',
+ error: SerializedError | null,
+ results: UserResult[],
+ columns: any, // TODO: proper typing, derive from `initialColumns`
+ total: number,
+ count: number,
+ offset: number,
+ limit: number,
+};
+
+// Fill columns initially with columns defined in usersTableConfig
+const initialColumns = usersTableConfig.columns.map((column) => ({
+ ...column,
+ deactivated: false,
+}));
+
+// Initial state of users in redux store
+const initialState: UsersState = {
+ status: 'uninitialized',
+ error: null,
+ results: [],
+ columns: initialColumns,
+ total: 0,
+ count: 0,
+ offset: 0,
+ limit: 0,
+};
+
+// fetch users from server
+export const fetchUsers = createAsyncThunk('users/fetchUsers', async (_, { getState }) => {
+ const state = getState();
+ let params = getURLParams(state);
+ // 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/users.json", { params: params });
+ return res.data;
+});
+
+// new user to backend
+export const postNewUser = createAsyncThunk('users/postNewUser', async (values: any, {dispatch}) => {
+ // get URL params used for post request
+ let data = buildUserBody(values);
+
+ axios
+ .post("/admin-ng/users", data, {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ })
+ // Usually we would extraReducers for responses, but reducers are not allowed to dispatch
+ // (they need to be free of side effects)
+ // Since we want to dispatch, we have to handle responses in our thunk
+ .then((response) => {
+ console.info(response);
+ dispatch(addNotification("success", "USER_ADDED"));
+ })
+ .catch((response) => {
+ console.error(response);
+ dispatch(addNotification("error", "USER_NOT_SAVED"));
+ });
+});
+
+// delete user with provided id
+export const deleteUser = createAsyncThunk('users/deleteUser', async (id: any, {dispatch}) => {
+ // API call for deleting an user
+ axios
+ .delete(`/admin-ng/users/${id}.json`)
+ .then((res) => {
+ console.info(res);
+ // add success notification
+ dispatch(addNotification("success", "USER_DELETED"));
+ })
+ .catch((res) => {
+ console.error(res);
+ // add error notification
+ dispatch(addNotification("error", "USER_NOT_DELETED"));
+ });
+});
+
+// get users and their user names
+export const fetchUsersAndUsernames = async () => {
+ let data = await axios.get(
+ "/admin-ng/resources/USERS.NAME.AND.USERNAME.json"
+ );
+
+ const response = await data.data;
+
+ return transformToIdValueArray(response);
+};
+
+const usersSlice = createSlice({
+ name: 'users',
+ initialState,
+ reducers: {
+ setUserColumns(state, action: PayloadAction<
+ UsersState["columns"]
+ >) {
+ state.columns = action.payload;
+ },
+ },
+ // These are used for thunks
+ extraReducers: builder => {
+ builder
+ // fetchUsers
+ .addCase(fetchUsers.pending, (state) => {
+ state.status = 'loading';
+ })
+ .addCase(fetchUsers.fulfilled, (state, action: PayloadAction<{
+ total: UsersState["total"],
+ count: UsersState["count"],
+ limit: UsersState["limit"],
+ offset: UsersState["offset"],
+ results: UsersState["results"],
+ }>) => {
+ state.status = 'succeeded';
+ const users = action.payload;
+ state.total = users.total;
+ state.count = users.count;
+ state.limit = users.limit;
+ state.offset = users.offset;
+ state.results = users.results;
+ })
+ .addCase(fetchUsers.rejected, (state, action) => {
+ state.status = 'failed';
+ state.results = [];
+ state.error = action.error;
+ });
+ }
+});
+
+export const { setUserColumns } = usersSlice.actions;
+
+// Export the slice reducer as the default export
+export default usersSlice.reducer;
diff --git a/app/src/store.ts b/app/src/store.ts
index 0215b1f396..78aaeb5422 100644
--- a/app/src/store.ts
+++ b/app/src/store.ts
@@ -12,7 +12,7 @@ import recordings from "./reducers/recordingReducer";
import jobs from "./slices/jobSlice";
import servers from "./slices/serverSlice";
import services from "./slices/serviceSlice";
-import users from "./reducers/userReducers";
+import users from "./slices/userSlice";
import groups from "./slices/groupSlice";
import acls from "./slices/aclSlice";
import themes from "./slices/themeSlice";
diff --git a/app/src/thunks/tableThunks.ts b/app/src/thunks/tableThunks.ts
index 137a02450d..0b5a4726a2 100644
--- a/app/src/thunks/tableThunks.ts
+++ b/app/src/thunks/tableThunks.ts
@@ -38,11 +38,10 @@ import { fetchRecordings } from "./recordingThunks";
import { fetchJobs, setJobColumns } from "../slices/jobSlice";
import { fetchServers, setServerColumns } from "../slices/serverSlice";
import { fetchServices, setServiceColumns } from "../slices/serviceSlice";
-import { fetchUsers } from "./userThunks";
+import { fetchUsers, setUserColumns } from "../slices/userSlice";
import { fetchGroups } from "../slices/groupSlice";
import { fetchThemes, setThemeColumns } from "../slices/themeSlice";
import { setRecordingsColumns } from "../actions/recordingActions";
-import { setUserColumns } from "../actions/userActions";
import { setGroupColumns } from "../slices/groupSlice";
import { fetchAcls, setAclColumns } from "../slices/aclSlice";
diff --git a/app/src/thunks/userThunks.ts b/app/src/thunks/userThunks.ts
deleted file mode 100644
index 9ac6033c04..0000000000
--- a/app/src/thunks/userThunks.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import axios from "axios";
-import {
- loadUsersFailure,
- loadUsersInProgress,
- loadUsersSuccess,
-} from "../actions/userActions";
-import { buildUserBody, getURLParams } from "../utils/resourceUtils";
-import { transformToIdValueArray } from "../utils/utils";
-import { addNotification } from "./notificationThunks";
-
-// fetch users from server
-// @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type.
-export const fetchUsers = () => async (dispatch, getState) => {
- try {
- dispatch(loadUsersInProgress());
-
- const state = getState();
- let params = getURLParams(state);
-
- // /users.json?limit=0&offset=0&filter={filter}&sort={sort}
- let data = await axios.get("/admin-ng/users/users.json", {
- params: params,
- });
-
- const users = await data.data;
- dispatch(loadUsersSuccess(users));
- } catch (e) {
- dispatch(loadUsersFailure());
- }
-};
-
-// get users and their user names
-export const fetchUsersAndUsernames = async () => {
- let data = await axios.get(
- "/admin-ng/resources/USERS.NAME.AND.USERNAME.json"
- );
-
- const response = await data.data;
-
- return transformToIdValueArray(response);
-};
-
-// new user to backend
-// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type.
-export const postNewUser = (values) => async (dispatch) => {
- // get URL params used for post request
- let data = buildUserBody(values);
-
- // POST request
- axios
- .post("/admin-ng/users", data, {
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- })
- .then((response) => {
- console.info(response);
- dispatch(addNotification("success", "USER_ADDED"));
- })
- .catch((response) => {
- console.error(response);
- dispatch(addNotification("error", "USER_NOT_SAVED"));
- });
-};
-
-// delete user with provided id
-// @ts-expect-error TS(7006): Parameter 'id' implicitly has an 'any' type.
-export const deleteUser = (id) => async (dispatch) => {
- // API call for deleting an user
- axios
- .delete(`/admin-ng/users/${id}.json`)
- .then((res) => {
- console.info(res);
- // add success notification
- dispatch(addNotification("success", "USER_DELETED"));
- })
- .catch((res) => {
- console.error(res);
- // add error notification
- dispatch(addNotification("error", "USER_NOT_DELETED"));
- });
-};