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")); - }); -};