91 lines
2.5 KiB
JavaScript
91 lines
2.5 KiB
JavaScript
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([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchQuery, setSearchQuery] = useState(initialSearch);
|
|
const [filterValue, setFilterValue] = useState(initialFilter);
|
|
|
|
const fetchMembers = useCallback(async () => {
|
|
try {
|
|
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) {
|
|
if (typeof onFetchError === 'function') {
|
|
onFetchError(error);
|
|
} else {
|
|
toast.error(fetchErrorMessage);
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [allowedRoles, endpoint, fetchErrorMessage, onFetchError, transform]);
|
|
|
|
useEffect(() => {
|
|
fetchMembers();
|
|
}, [fetchMembers]);
|
|
|
|
useEffect(() => {
|
|
let filtered = users;
|
|
|
|
if (filterValue && filterValue !== 'all') {
|
|
filtered = filtered.filter(user => user[filterKey] === filterValue);
|
|
}
|
|
|
|
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));
|
|
});
|
|
}
|
|
|
|
setFilteredUsers(filtered);
|
|
}, [users, searchQuery, filterKey, filterValue, searchAccessor, searchFields]);
|
|
|
|
return {
|
|
users,
|
|
filteredUsers,
|
|
loading,
|
|
searchQuery,
|
|
setSearchQuery,
|
|
filterValue,
|
|
setFilterValue,
|
|
fetchMembers,
|
|
};
|
|
};
|
|
|
|
export default useMembers;
|