From 554b5995998c3fd6aa11a6a25fff77c94ebcd7b2 Mon Sep 17 00:00:00 2001 From: kayela Date: Thu, 22 Jan 2026 14:47:34 -0600 Subject: [PATCH] feat: refactor AdminMembers and AdminStaff to utilize useMembers hook for improved member management --- src/hooks/use-members.js | 68 ++++++++++++++++++++++++++++ src/pages/admin/AdminMembers.js | 58 +++++------------------- src/pages/admin/AdminStaff.js | 78 ++++++++++----------------------- 3 files changed, 103 insertions(+), 101 deletions(-) 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 = () => {