feat: implement UsersContext and refactor user management hooks for improved user data handling

This commit is contained in:
2026-01-25 12:17:30 -06:00
parent f2dd053320
commit 4548d959d7
10 changed files with 576 additions and 120 deletions

View File

@@ -21,7 +21,7 @@ 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';
import { useMembers } from '../../hooks/use-users';
const AdminMembers = () => {
const navigate = useNavigate();
@@ -35,7 +35,7 @@ const AdminMembers = () => {
setSearchQuery,
filterValue: statusFilter,
setFilterValue: setStatusFilter,
fetchMembers,
refetch,
} = useMembers();
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
const [selectedUserForPayment, setSelectedUserForPayment] = useState(null);
@@ -53,7 +53,7 @@ const AdminMembers = () => {
};
const handlePaymentSuccess = () => {
fetchMembers(); // Refresh list
refetch(); // Refresh list
};
const handleStatusChangeRequest = (userId, currentStatus, newStatus, user) => {
@@ -74,7 +74,7 @@ const AdminMembers = () => {
try {
await api.put(`/admin/users/${userId}/status`, { status: newStatus });
toast.success('Member status updated successfully');
fetchMembers(); // Refresh list
refetch(); // Refresh list
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to update status');
} finally {
@@ -520,19 +520,19 @@ const AdminMembers = () => {
<CreateMemberDialog
open={createDialogOpen}
onOpenChange={setCreateDialogOpen}
onSuccess={fetchMembers}
onSuccess={refetch}
/>
<InviteStaffDialog
open={inviteDialogOpen}
onOpenChange={setInviteDialogOpen}
onSuccess={fetchMembers}
onSuccess={refetch}
/>
<WordPressImportWizard
open={importDialogOpen}
onOpenChange={setImportDialogOpen}
onSuccess={fetchMembers}
onSuccess={refetch}
/>
</>
);

View File

@@ -15,10 +15,7 @@ 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'];
import { useStaff } from '../../hooks/use-users';
const AdminStaff = () => {
const navigate = useNavigate();
@@ -31,12 +28,10 @@ const AdminStaff = () => {
setSearchQuery,
filterValue: roleFilter,
setFilterValue: setRoleFilter,
fetchMembers,
} = useMembers({
refetch,
} = useStaff({
initialFilter: 'all',
filterKey: 'role',
allowedRoles: STAFF_ROLES,
fetchErrorMessage: 'Failed to fetch staff',
});
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
@@ -48,7 +43,7 @@ const AdminStaff = () => {
try {
await api.put(`/admin/users/${userId}/status`, { status: newStatus });
toast.success(`User ${newStatus === 'active' ? 'activated' : 'deactivated'} successfully`);
fetchMembers(); // Refresh list
refetch(); // Refresh list
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to update user status');
}
@@ -62,7 +57,7 @@ const AdminStaff = () => {
try {
await api.delete(`/admin/users/${userId}`);
toast.success('User deleted successfully');
fetchMembers(); // Refresh list
refetch(); // Refresh list
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to delete user');
}
@@ -114,7 +109,7 @@ const AdminStaff = () => {
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
<StatCard
title="Total Members"
title="Total Staff"
//TODO: refractor codebase to have a central admin and user roles config - when user adds roles, they should be added to the config
value={users.filter(u => ['admin', 'superadmin', 'finance', 'staff', 'media', 'moderator'].includes(u.role)).length}
icon={Users}
@@ -304,7 +299,7 @@ const AdminStaff = () => {
<CreateStaffDialog
open={createDialogOpen}
onOpenChange={setCreateDialogOpen}
onSuccess={fetchMembers}
onSuccess={refetch}
/>
<InviteStaffDialog

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import api from '../../utils/api';
import Navbar from '../../components/Navbar';
import MemberFooter from '../../components/MemberFooter';
@@ -17,63 +17,50 @@ import { User, Search, Mail, MapPin, Phone, Heart, Facebook, Instagram, Twitter,
import { useToast } from '../../hooks/use-toast';
import StatusBadge from '@/components/StatusBadge';
import MemberCard from '../../components/MemberCard';
import useMembers from '../../hooks/use-members';
const MembersDirectory = () => {
const [members, setMembers] = useState([]);
const [filteredMembers, setFilteredMembers] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(true);
const [selectedMember, setSelectedMember] = useState(null);
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
const { toast } = useToast();
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 12;
const allowedRoles = useMemo(() => [], []);
const searchAccessor = useCallback(
(member) => [
`${member.first_name} ${member.last_name}`,
member.directory_bio || ''
],
[]
);
const handleFetchError = useCallback(() => {
toast({
title: "Error",
description: "Failed to load members directory. Please try again.",
variant: "destructive"
});
}, [toast]);
useEffect(() => {
fetchMembers();
}, []);
useEffect(() => {
filterMembers();
}, [searchQuery, members]);
const {
users: members,
filteredUsers: filteredMembers,
loading,
searchQuery,
setSearchQuery,
} = useMembers({
endpoint: '/members/directory',
initialFilter: 'active',
filterKey: 'status',
allowedRoles,
searchAccessor,
fetchErrorMessage: 'Failed to load members directory. Please try again.',
onFetchError: handleFetchError
});
useEffect(() => {
setCurrentPage(1);
}, [searchQuery, members]);
const fetchMembers = async () => {
try {
const response = await api.get('/members/directory');
setMembers(response.data);
setFilteredMembers(response.data);
} catch (error) {
console.error('Failed to fetch members:', error);
toast({
title: "Error",
description: "Failed to load members directory. Please try again.",
variant: "destructive"
});
} finally {
setLoading(false);
}
};
const filterMembers = () => {
if (!searchQuery.trim()) {
setFilteredMembers(members);
return;
}
const query = searchQuery.toLowerCase();
const filtered = members.filter(member => {
const fullName = `${member.first_name} ${member.last_name}`.toLowerCase();
const bio = (member.directory_bio || '').toLowerCase();
return fullName.includes(query) || bio.includes(query);
});
setFilteredMembers(filtered);
};
const totalPages = Math.max(1, Math.ceil(filteredMembers.length / pageSize));
const pageStart = (currentPage - 1) * pageSize;
@@ -171,7 +158,7 @@ const MembersDirectory = () => {
) : filteredMembers.length > 0 ? (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{paginatedMembers.map((member) => (
<MemberCard key={member.id} member={member} />
<MemberCard key={member.id} member={member} onViewProfile={handleViewProfile} />
))}
</div>
) : (
@@ -223,8 +210,7 @@ const MembersDirectory = () => {
<DialogHeader>
<DialogTitle className="text-3xl font-semibold text-[var(--purple-ink)] flex items-center justify-between mr-8" style={{ fontFamily: "'Inter', sans-serif" }}>
{selectedMember.first_name} {selectedMember.last_name}
{/* todo: figure out the correct selection to get the status of the user and pass into badge */}
<StatusBadge status={selectedMember.status} />
<StatusBadge status={selectedMember.membership_status || selectedMember.status} />
</DialogTitle>
{selectedMember.directory_partner_name && (
<DialogDescription className="flex items-center gap-2 text-lg" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>