import React, { useEffect, useState } from 'react'; import { useNavigate, useLocation, Link } 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 { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../../components/ui/dropdown-menu'; import { toast } from 'sonner'; import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown } from 'lucide-react'; import PaymentActivationDialog from '../../components/PaymentActivationDialog'; import ConfirmationDialog from '../../components/ConfirmationDialog'; import CreateMemberDialog from '../../components/CreateMemberDialog'; import InviteStaffDialog from '../../components/InviteStaffDialog'; import WordPressImportWizard from '../../components/WordPressImportWizard'; 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 [paymentDialogOpen, setPaymentDialogOpen] = useState(false); const [selectedUserForPayment, setSelectedUserForPayment] = useState(null); const [statusChanging, setStatusChanging] = useState(null); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [pendingStatusChange, setPendingStatusChange] = useState(null); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [inviteDialogOpen, setInviteDialogOpen] = useState(false); 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); }; const handlePaymentSuccess = () => { fetchMembers(); // Refresh list }; const handleStatusChangeRequest = (userId, currentStatus, newStatus, user) => { // Skip confirmation if status didn't actually change if (currentStatus === newStatus) return; setPendingStatusChange({ userId, newStatus, user }); setConfirmDialogOpen(true); }; const confirmStatusChange = async () => { if (!pendingStatusChange) return; const { userId, newStatus } = pendingStatusChange; setStatusChanging(userId); setConfirmDialogOpen(false); try { await api.put(`/admin/users/${userId}/status`, { status: newStatus }); toast.success('Member status updated successfully'); fetchMembers(); // Refresh list } catch (error) { toast.error(error.response?.data?.detail || 'Failed to update status'); } finally { setStatusChanging(null); setPendingStatusChange(null); } }; const handleExport = async (filterType) => { setExporting(true); try { let params = {}; if (filterType === 'current') { if (statusFilter && statusFilter !== 'all') { params.status = statusFilter; } if (searchQuery) { params.search = searchQuery; } } // filterType === 'all' will export all members without filters const response = await api.get('/admin/users/export', { params, responseType: 'blob' }); // Create download link const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', `members_export_${new Date().toISOString().split('T')[0]}.csv`); document.body.appendChild(link); link.click(); link.remove(); toast.success('Members exported successfully'); } catch (error) { toast.error('Failed to export members'); } finally { setExporting(false); } }; const getStatusChangeMessage = () => { if (!pendingStatusChange) return {}; const { newStatus, user } = pendingStatusChange; const userName = `${user.first_name} ${user.last_name}`; const messages = { payment_pending: { title: 'Revert to Payment Pending?', description: `This will change ${userName}'s status back to Payment Pending. They will need to complete payment again to become active.`, variant: 'warning', confirmText: 'Yes, Revert Status', }, active: { title: 'Activate Member?', description: `This will activate ${userName}'s membership. They will gain full access to member features and resources.`, variant: 'success', confirmText: 'Yes, Activate', }, inactive: { title: 'Deactivate Member?', description: `This will deactivate ${userName}'s membership. They will lose access to member-only features but their data will be preserved.`, variant: 'warning', confirmText: 'Yes, Deactivate', }, canceled: { title: 'Cancel Membership?', description: `This will mark ${userName}'s membership as canceled. This indicates they voluntarily ended their membership. Their subscription will not auto-renew.`, variant: 'danger', confirmText: 'Yes, Cancel Membership', }, expired: { title: 'Mark Membership as Expired?', description: `This will mark ${userName}'s membership as expired. This indicates their subscription period has ended without renewal.`, variant: 'warning', confirmText: 'Yes, Mark as Expired', }, }; return messages[newStatus] || { title: 'Confirm Status Change', description: `Are you sure you want to change ${userName}'s status to ${newStatus}?`, variant: 'warning', confirmText: 'Confirm', }; }; const getStatusBadge = (status) => { const config = { pending_email: { label: 'Pending Email', className: 'bg-orange-100 text-orange-700' }, pending_validation: { label: 'Pending Validation', className: 'bg-gray-200 text-gray-700' }, pre_validated: { label: 'Pre-Validated', className: 'bg-[#81B29A] text-white' }, payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' }, active: { label: 'Active', className: 'bg-[#81B29A] text-white' }, inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' }, canceled: { label: 'Canceled', className: 'bg-red-100 text-red-700' }, expired: { label: 'Expired', className: 'bg-red-500 text-white' }, abandoned: { label: 'Abandoned', className: 'bg-gray-300 text-gray-600' } }; const statusConfig = config[status] || config.inactive; return ( {statusConfig.label} ); }; const getReminderInfo = (user) => { const emailReminders = user.email_verification_reminders_sent || 0; const eventReminders = user.event_attendance_reminders_sent || 0; const paymentReminders = user.payment_reminders_sent || 0; const renewalReminders = user.renewal_reminders_sent || 0; const totalReminders = emailReminders + eventReminders + paymentReminders + renewalReminders; return { emailReminders, eventReminders, paymentReminders, renewalReminders, totalReminders, lastReminderAt: user.last_email_verification_reminder_at || user.last_event_attendance_reminder_at || user.last_payment_reminder_at || user.last_renewal_reminder_at }; }; return ( <>

Members Management

Manage paying members and their subscriptions.

{hasPermission('users.export') && ( handleExport('all')} className="cursor-pointer"> Export All Members handleExport('current')} className="cursor-pointer"> Export Current View )} {hasPermission('users.import') && ( )} {hasPermission('users.invite') && ( )} {hasPermission('users.create') && ( )}
{/* Stats */}

Total Members

{users.length}

Active

{users.filter(u => u.status === 'active').length}

Payment Pending

{users.filter(u => u.status === 'payment_pending').length}

Inactive

{users.filter(u => u.status === 'inactive').length}

{/* Filters */}
setSearchQuery(e.target.value)} className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" data-testid="search-members-input" />
{/* Members List */} {loading ? (

Loading members...

) : filteredUsers.length > 0 ? (
{filteredUsers.map((user) => (
{/* Avatar */}
{user.first_name?.[0]}{user.last_name?.[0]}
{/* Info */}

{user.first_name} {user.last_name}

{getStatusBadge(user.status)}

Email: {user.email}

Phone: {user.phone}

Joined: {new Date(user.created_at).toLocaleDateString()}

{user.referred_by_member_name && (

Referred by: {user.referred_by_member_name}

)}
{/* Reminder Info */} {(() => { const reminderInfo = getReminderInfo(user); if (reminderInfo.totalReminders > 0) { return (
{reminderInfo.totalReminders} reminder{reminderInfo.totalReminders !== 1 ? 's' : ''} sent {reminderInfo.totalReminders >= 3 && ( Needs attention )}
{reminderInfo.emailReminders > 0 && (

{reminderInfo.emailReminders} email verification

)} {reminderInfo.eventReminders > 0 && (

{reminderInfo.eventReminders} event attendance

)} {reminderInfo.paymentReminders > 0 && (

{reminderInfo.paymentReminders} payment

)} {reminderInfo.renewalReminders > 0 && (

{reminderInfo.renewalReminders} renewal

)}
{reminderInfo.lastReminderAt && (

Last reminder: {new Date(reminderInfo.lastReminderAt).toLocaleDateString()} at {new Date(reminderInfo.lastReminderAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

)}
); } return null; })()}
{/* Actions */}
{/* Show Activate Payment button for payment_pending users */} {user.status === 'payment_pending' && ( )}
{/* Status Management */}
Change Status:
))}
) : (

No Members Found

{searchQuery || statusFilter !== 'all' ? 'Try adjusting your filters' : 'No members yet'}

)} {/* Payment Activation Dialog */} {/* Status Change Confirmation Dialog */} {/* Create/Invite/Import Dialogs */} ); }; export default AdminMembers;