diff --git a/src/hooks/use-members.js b/src/hooks/use-members.js
index e69de29..91c98d3 100644
--- a/src/hooks/use-members.js
+++ b/src/hooks/use-members.js
@@ -0,0 +1,68 @@
+import { useCallback, useEffect, useState } from 'react';
+import { toast } from 'sonner';
+import api from '../utils/api';
+
+const useMembers = ({
+ initialFilter = 'active',
+ initialSearch = '',
+ filterKey = 'status',
+ allowedRoles = ['member'],
+ fetchErrorMessage = 'Failed to fetch members',
+} = {}) => {
+ 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('/admin/users');
+ let filtered = response.data;
+ if (allowedRoles && allowedRoles.length) {
+ filtered = filtered.filter(user => allowedRoles.includes(user.role));
+ }
+ setUsers(filtered);
+ } catch (error) {
+ toast.error(fetchErrorMessage);
+ } finally {
+ setLoading(false);
+ }
+ }, [allowedRoles, fetchErrorMessage]);
+
+ 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 =>
+ user.first_name.toLowerCase().includes(query) ||
+ user.last_name.toLowerCase().includes(query) ||
+ user.email.toLowerCase().includes(query)
+ );
+ }
+
+ setFilteredUsers(filtered);
+ }, [users, searchQuery, filterKey, filterValue]);
+
+ return {
+ users,
+ filteredUsers,
+ loading,
+ searchQuery,
+ setSearchQuery,
+ filterValue,
+ setFilterValue,
+ fetchMembers,
+ };
+};
+
+export default useMembers;
diff --git a/src/pages/admin/AdminMembers.js b/src/pages/admin/AdminMembers.js
index 3397927..25d2e3d 100644
--- a/src/pages/admin/AdminMembers.js
+++ b/src/pages/admin/AdminMembers.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { useNavigate, useLocation, Link } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import api from '../../utils/api';
@@ -21,16 +21,22 @@ import InviteStaffDialog from '../../components/InviteStaffDialog';
import WordPressImportWizard from '../../components/WordPressImportWizard';
import StatusBadge from '../../components/StatusBadge';
import { StatCard } from '@/components/StatCard';
+import useMembers from '../../hooks/use-members';
const AdminMembers = () => {
const navigate = useNavigate();
const location = useLocation();
const { hasPermission } = useAuth();
- const [users, setUsers] = useState([]);
- const [filteredUsers, setFilteredUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- const [searchQuery, setSearchQuery] = useState('');
- const [statusFilter, setStatusFilter] = useState('active');
+ const {
+ users,
+ filteredUsers,
+ loading,
+ searchQuery,
+ setSearchQuery,
+ filterValue: statusFilter,
+ setFilterValue: setStatusFilter,
+ fetchMembers,
+ } = useMembers();
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [selectedUserForPayment, setSelectedUserForPayment] = useState(null);
const [statusChanging, setStatusChanging] = useState(null);
@@ -41,46 +47,6 @@ const AdminMembers = () => {
const [importDialogOpen, setImportDialogOpen] = useState(false);
const [exporting, setExporting] = useState(false);
- useEffect(() => {
- fetchMembers();
- }, []);
-
- useEffect(() => {
- filterUsers();
- }, [users, searchQuery, statusFilter]);
-
- const fetchMembers = async () => {
- try {
- const response = await api.get('/admin/users');
- // Filter to only members
- const members = response.data.filter(user => user.role === 'member');
- setUsers(members);
- } catch (error) {
- toast.error('Failed to fetch members');
- } finally {
- setLoading(false);
- }
- };
-
- const filterUsers = () => {
- let filtered = users;
-
- if (statusFilter && statusFilter !== 'all') {
- filtered = filtered.filter(user => user.status === statusFilter);
- }
-
- 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)
- );
- }
-
- setFilteredUsers(filtered);
- };
-
const handleActivatePayment = (user) => {
setSelectedUserForPayment(user);
setPaymentDialogOpen(true);
diff --git a/src/pages/admin/AdminStaff.js b/src/pages/admin/AdminStaff.js
index f4465bb..46a461f 100644
--- a/src/pages/admin/AdminStaff.js
+++ b/src/pages/admin/AdminStaff.js
@@ -1,10 +1,9 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import api from '../../utils/api';
import { Card } from '../../components/ui/card';
import { Button } from '../../components/ui/button';
-import { Badge } from '../../components/ui/badge';
import { Input } from '../../components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../components/ui/tabs';
@@ -16,71 +15,40 @@ import { UserCog, Search, Shield, UserPlus, Mail, Edit, Eye, Trash2, UserCheck,
import StatusBadge from '../../components/StatusBadge';
import { StatCard } from '@/components/StatCard';
import { CircleMinus, CreditCard, Users } from 'lucide-react';
+import useMembers from '../../hooks/use-members';
+
+// Staff roles (non-guest, non-member) - includes all admin-type roles
+const STAFF_ROLES = ['admin', 'superadmin', 'finance'];
const AdminStaff = () => {
const navigate = useNavigate();
const { hasPermission, user } = useAuth();
- const [users, setUsers] = useState([]);
- const [filteredUsers, setFilteredUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- const [searchQuery, setSearchQuery] = useState('');
- const [roleFilter, setRoleFilter] = useState('all');
+ const {
+ users,
+ filteredUsers,
+ loading,
+ searchQuery,
+ setSearchQuery,
+ filterValue: roleFilter,
+ setFilterValue: setRoleFilter,
+ fetchMembers,
+ } = useMembers({
+ initialFilter: 'all',
+ filterKey: 'role',
+ allowedRoles: STAFF_ROLES,
+ fetchErrorMessage: 'Failed to fetch staff',
+ });
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
const [activeTab, setActiveTab] = useState('staff-list');
- // Staff roles (non-guest, non-member) - includes all admin-type roles
- const STAFF_ROLES = ['admin', 'superadmin', 'finance'];
-
- useEffect(() => {
- fetchStaff();
- }, []);
-
- useEffect(() => {
- filterUsers();
- }, [users, searchQuery, roleFilter]);
-
- const fetchStaff = async () => {
- try {
- const response = await api.get('/admin/users');
- // Filter to only staff roles
- const staffUsers = response.data.filter(user =>
- STAFF_ROLES.includes(user.role)
- );
- setUsers(staffUsers);
- } catch (error) {
- toast.error('Failed to fetch staff');
- } finally {
- setLoading(false);
- }
- };
-
- const filterUsers = () => {
- let filtered = users;
-
- if (roleFilter && roleFilter !== 'all') {
- filtered = filtered.filter(user => user.role === roleFilter);
- }
-
- 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)
- );
- }
-
- setFilteredUsers(filtered);
- };
-
const handleToggleStatus = async (userId, currentStatus) => {
const newStatus = currentStatus === 'active' ? 'inactive' : 'active';
try {
await api.put(`/admin/users/${userId}/status`, { status: newStatus });
toast.success(`User ${newStatus === 'active' ? 'activated' : 'deactivated'} successfully`);
- fetchStaff(); // Refresh list
+ fetchMembers(); // Refresh list
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to update user status');
}
@@ -94,7 +62,7 @@ const AdminStaff = () => {
try {
await api.delete(`/admin/users/${userId}`);
toast.success('User deleted successfully');
- fetchStaff(); // Refresh list
+ fetchMembers(); // Refresh list
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to delete user');
}
@@ -336,7 +304,7 @@ const AdminStaff = () => {