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

@@ -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" }}>