feat: implement UsersContext and refactor user management hooks for improved user data handling
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }}>
|
||||
|
||||
Reference in New Issue
Block a user