feat: refactor AdminMembers and AdminStaff to utilize useMembers hook for improved member management

This commit is contained in:
2026-01-22 14:47:34 -06:00
parent ac879b69b4
commit 554b599599
3 changed files with 103 additions and 101 deletions

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { useNavigate, useLocation, Link } from 'react-router-dom'; import { useNavigate, useLocation, Link } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import api from '../../utils/api'; import api from '../../utils/api';
@@ -21,16 +21,22 @@ import InviteStaffDialog from '../../components/InviteStaffDialog';
import WordPressImportWizard from '../../components/WordPressImportWizard'; import WordPressImportWizard from '../../components/WordPressImportWizard';
import StatusBadge from '../../components/StatusBadge'; import StatusBadge from '../../components/StatusBadge';
import { StatCard } from '@/components/StatCard'; import { StatCard } from '@/components/StatCard';
import useMembers from '../../hooks/use-members';
const AdminMembers = () => { const AdminMembers = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { hasPermission } = useAuth(); const { hasPermission } = useAuth();
const [users, setUsers] = useState([]); const {
const [filteredUsers, setFilteredUsers] = useState([]); users,
const [loading, setLoading] = useState(true); filteredUsers,
const [searchQuery, setSearchQuery] = useState(''); loading,
const [statusFilter, setStatusFilter] = useState('active'); searchQuery,
setSearchQuery,
filterValue: statusFilter,
setFilterValue: setStatusFilter,
fetchMembers,
} = useMembers();
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false); const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [selectedUserForPayment, setSelectedUserForPayment] = useState(null); const [selectedUserForPayment, setSelectedUserForPayment] = useState(null);
const [statusChanging, setStatusChanging] = useState(null); const [statusChanging, setStatusChanging] = useState(null);
@@ -41,46 +47,6 @@ const AdminMembers = () => {
const [importDialogOpen, setImportDialogOpen] = useState(false); const [importDialogOpen, setImportDialogOpen] = useState(false);
const [exporting, setExporting] = 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) => { const handleActivatePayment = (user) => {
setSelectedUserForPayment(user); setSelectedUserForPayment(user);
setPaymentDialogOpen(true); setPaymentDialogOpen(true);

View File

@@ -1,10 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import api from '../../utils/api'; import api from '../../utils/api';
import { Card } from '../../components/ui/card'; import { Card } from '../../components/ui/card';
import { Button } from '../../components/ui/button'; import { Button } from '../../components/ui/button';
import { Badge } from '../../components/ui/badge';
import { Input } from '../../components/ui/input'; import { Input } from '../../components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../components/ui/tabs'; 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 StatusBadge from '../../components/StatusBadge';
import { StatCard } from '@/components/StatCard'; import { StatCard } from '@/components/StatCard';
import { CircleMinus, CreditCard, Users } from 'lucide-react'; 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 AdminStaff = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { hasPermission, user } = useAuth(); const { hasPermission, user } = useAuth();
const [users, setUsers] = useState([]); const {
const [filteredUsers, setFilteredUsers] = useState([]); users,
const [loading, setLoading] = useState(true); filteredUsers,
const [searchQuery, setSearchQuery] = useState(''); loading,
const [roleFilter, setRoleFilter] = useState('all'); 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 [createDialogOpen, setCreateDialogOpen] = useState(false);
const [inviteDialogOpen, setInviteDialogOpen] = useState(false); const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
const [activeTab, setActiveTab] = useState('staff-list'); 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 handleToggleStatus = async (userId, currentStatus) => {
const newStatus = currentStatus === 'active' ? 'inactive' : 'active'; const newStatus = currentStatus === 'active' ? 'inactive' : 'active';
try { try {
await api.put(`/admin/users/${userId}/status`, { status: newStatus }); await api.put(`/admin/users/${userId}/status`, { status: newStatus });
toast.success(`User ${newStatus === 'active' ? 'activated' : 'deactivated'} successfully`); toast.success(`User ${newStatus === 'active' ? 'activated' : 'deactivated'} successfully`);
fetchStaff(); // Refresh list fetchMembers(); // Refresh list
} catch (error) { } catch (error) {
toast.error(error.response?.data?.detail || 'Failed to update user status'); toast.error(error.response?.data?.detail || 'Failed to update user status');
} }
@@ -94,7 +62,7 @@ const AdminStaff = () => {
try { try {
await api.delete(`/admin/users/${userId}`); await api.delete(`/admin/users/${userId}`);
toast.success('User deleted successfully'); toast.success('User deleted successfully');
fetchStaff(); // Refresh list fetchMembers(); // Refresh list
} catch (error) { } catch (error) {
toast.error(error.response?.data?.detail || 'Failed to delete user'); toast.error(error.response?.data?.detail || 'Failed to delete user');
} }
@@ -336,7 +304,7 @@ const AdminStaff = () => {
<CreateStaffDialog <CreateStaffDialog
open={createDialogOpen} open={createDialogOpen}
onOpenChange={setCreateDialogOpen} onOpenChange={setCreateDialogOpen}
onSuccess={fetchStaff} onSuccess={fetchMembers}
/> />
<InviteStaffDialog <InviteStaffDialog