feat: implement UsersContext and refactor user management hooks for improved user data handling
This commit is contained in:
@@ -2,12 +2,23 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import api from '../utils/api';
|
||||
|
||||
const DEFAULT_SEARCH_FIELDS = ['first_name', 'last_name', 'email'];
|
||||
|
||||
/**
|
||||
* Hook for fetching users from a custom endpoint (e.g., member-facing directory).
|
||||
* For admin pages, use hooks from use-users.js instead which share a centralized context.
|
||||
*/
|
||||
const useMembers = ({
|
||||
endpoint = '/admin/users',
|
||||
initialFilter = 'active',
|
||||
initialSearch = '',
|
||||
filterKey = 'status',
|
||||
allowedRoles = ['member'],
|
||||
searchFields = DEFAULT_SEARCH_FIELDS,
|
||||
fetchErrorMessage = 'Failed to fetch members',
|
||||
searchAccessor,
|
||||
transform,
|
||||
onFetchError,
|
||||
} = {}) => {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [filteredUsers, setFilteredUsers] = useState([]);
|
||||
@@ -17,18 +28,25 @@ const useMembers = ({
|
||||
|
||||
const fetchMembers = useCallback(async () => {
|
||||
try {
|
||||
const response = await api.get('/admin/users');
|
||||
const response = await api.get(endpoint);
|
||||
let filtered = response.data;
|
||||
if (typeof transform === 'function') {
|
||||
filtered = transform(filtered);
|
||||
}
|
||||
if (allowedRoles && allowedRoles.length) {
|
||||
filtered = filtered.filter(user => allowedRoles.includes(user.role));
|
||||
}
|
||||
setUsers(filtered);
|
||||
} catch (error) {
|
||||
toast.error(fetchErrorMessage);
|
||||
if (typeof onFetchError === 'function') {
|
||||
onFetchError(error);
|
||||
} else {
|
||||
toast.error(fetchErrorMessage);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [allowedRoles, fetchErrorMessage]);
|
||||
}, [allowedRoles, endpoint, fetchErrorMessage, onFetchError, transform]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMembers();
|
||||
@@ -43,15 +61,19 @@ const useMembers = ({
|
||||
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(user =>
|
||||
user.first_name.toLowerCase().includes(query) ||
|
||||
user.last_name.toLowerCase().includes(query) ||
|
||||
user.email.toLowerCase().includes(query)
|
||||
);
|
||||
filtered = filtered.filter(user => {
|
||||
const values = typeof searchAccessor === 'function'
|
||||
? searchAccessor(user)
|
||||
: searchFields.map(field => user?.[field]);
|
||||
|
||||
return values
|
||||
.filter(Boolean)
|
||||
.some(value => value.toString().toLowerCase().includes(query));
|
||||
});
|
||||
}
|
||||
|
||||
setFilteredUsers(filtered);
|
||||
}, [users, searchQuery, filterKey, filterValue]);
|
||||
}, [users, searchQuery, filterKey, filterValue, searchAccessor, searchFields]);
|
||||
|
||||
return {
|
||||
users,
|
||||
|
||||
171
src/hooks/use-users.js
Normal file
171
src/hooks/use-users.js
Normal file
@@ -0,0 +1,171 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useUsers } from '../context/UsersContext';
|
||||
|
||||
const DEFAULT_SEARCH_FIELDS = ['first_name', 'last_name', 'email'];
|
||||
|
||||
/**
|
||||
* Base hook that adds search and filter functionality to any user list
|
||||
*/
|
||||
const useFilteredUsers = ({
|
||||
users,
|
||||
initialFilter = 'all',
|
||||
filterKey = 'status',
|
||||
searchFields = DEFAULT_SEARCH_FIELDS,
|
||||
searchAccessor,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [filterValue, setFilterValue] = useState(initialFilter);
|
||||
|
||||
const filteredUsers = useMemo(() => {
|
||||
let filtered = users;
|
||||
|
||||
// Apply filter
|
||||
if (filterValue && filterValue !== 'all') {
|
||||
filtered = filtered.filter(user => user[filterKey] === filterValue);
|
||||
}
|
||||
|
||||
// Apply search
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(user => {
|
||||
const values = typeof searchAccessor === 'function'
|
||||
? searchAccessor(user)
|
||||
: searchFields.map(field => user?.[field]);
|
||||
|
||||
return values
|
||||
.filter(Boolean)
|
||||
.some(value => value.toString().toLowerCase().includes(query));
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [users, searchQuery, filterKey, filterValue, searchAccessor, searchFields]);
|
||||
|
||||
return {
|
||||
filteredUsers,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for staff users (admin, superadmin, finance roles)
|
||||
*/
|
||||
export const useStaff = ({
|
||||
initialFilter = 'all',
|
||||
filterKey = 'role',
|
||||
searchFields = DEFAULT_SEARCH_FIELDS,
|
||||
searchAccessor,
|
||||
} = {}) => {
|
||||
const { staff, loading, error, refetch, updateUser, removeUser } = useUsers();
|
||||
|
||||
const {
|
||||
filteredUsers,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
} = useFilteredUsers({
|
||||
users: staff,
|
||||
initialFilter,
|
||||
filterKey,
|
||||
searchFields,
|
||||
searchAccessor,
|
||||
});
|
||||
|
||||
return {
|
||||
users: staff,
|
||||
filteredUsers,
|
||||
loading,
|
||||
error,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
refetch,
|
||||
updateUser,
|
||||
removeUser,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for member users (non-admin roles)
|
||||
*/
|
||||
export const useMembers = ({
|
||||
initialFilter = 'active',
|
||||
filterKey = 'status',
|
||||
searchFields = DEFAULT_SEARCH_FIELDS,
|
||||
searchAccessor,
|
||||
} = {}) => {
|
||||
const { members, loading, error, refetch, updateUser, removeUser } = useUsers();
|
||||
|
||||
const {
|
||||
filteredUsers,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
} = useFilteredUsers({
|
||||
users: members,
|
||||
initialFilter,
|
||||
filterKey,
|
||||
searchFields,
|
||||
searchAccessor,
|
||||
});
|
||||
|
||||
return {
|
||||
users: members,
|
||||
filteredUsers,
|
||||
loading,
|
||||
error,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
refetch,
|
||||
updateUser,
|
||||
removeUser,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for all users (both staff and members)
|
||||
*/
|
||||
export const useAllUsers = ({
|
||||
initialFilter = 'all',
|
||||
filterKey = 'status',
|
||||
searchFields = DEFAULT_SEARCH_FIELDS,
|
||||
searchAccessor,
|
||||
} = {}) => {
|
||||
const { users, loading, error, refetch, updateUser, removeUser } = useUsers();
|
||||
|
||||
const {
|
||||
filteredUsers,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
} = useFilteredUsers({
|
||||
users,
|
||||
initialFilter,
|
||||
filterKey,
|
||||
searchFields,
|
||||
searchAccessor,
|
||||
});
|
||||
|
||||
return {
|
||||
users,
|
||||
filteredUsers,
|
||||
loading,
|
||||
error,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
refetch,
|
||||
updateUser,
|
||||
removeUser,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user