import React, { useEffect, useState } from 'react'; 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 { Table, TableHeader, TableBody, TableHead, TableRow, TableCell, } from '../../components/ui/table'; import { Pagination, PaginationContent, PaginationLink, PaginationItem, PaginationPrevious, PaginationNext, PaginationEllipsis, } from '../../components/ui/pagination'; import { toast } from 'sonner'; import { CheckCircle, Clock, Search, ArrowUp, ArrowDown } from 'lucide-react'; import PaymentActivationDialog from '../../components/PaymentActivationDialog'; import ConfirmationDialog from '../../components/ConfirmationDialog'; const AdminValidations = () => { const [pendingUsers, setPendingUsers] = useState([]); const [filteredUsers, setFilteredUsers] = useState([]); const [loading, setLoading] = useState(true); const [actionLoading, setActionLoading] = useState(null); const [paymentDialogOpen, setPaymentDialogOpen] = useState(false); const [selectedUserForPayment, setSelectedUserForPayment] = useState(null); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [pendingAction, setPendingAction] = useState(null); // Filtering state const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); // Pagination state const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(10); // Sorting state const [sortBy, setSortBy] = useState('created_at'); const [sortOrder, setSortOrder] = useState('desc'); useEffect(() => { fetchPendingUsers(); }, []); useEffect(() => { filterAndSortUsers(); }, [pendingUsers, searchQuery, statusFilter, sortBy, sortOrder]); useEffect(() => { setCurrentPage(1); }, [searchQuery, statusFilter]); const fetchPendingUsers = async () => { try { const response = await api.get('/admin/users'); const pending = response.data.filter(user => ['pending_email', 'pending_validation', 'pre_validated', 'payment_pending'].includes(user.status) ); setPendingUsers(pending); } catch (error) { toast.error('Failed to fetch pending users'); } finally { setLoading(false); } }; const filterAndSortUsers = () => { let filtered = [...pendingUsers]; // Apply status filter if (statusFilter !== 'all') { filtered = filtered.filter(user => user.status === statusFilter); } // Apply search query 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) || user.phone?.toLowerCase().includes(query) ); } // Apply sorting filtered.sort((a, b) => { let aVal = a[sortBy]; let bVal = b[sortBy]; if (sortBy === 'created_at') { aVal = new Date(aVal); bVal = new Date(bVal); } else if (sortBy === 'first_name') { aVal = `${a.first_name} ${a.last_name}`; bVal = `${b.first_name} ${b.last_name}`; } if (sortOrder === 'asc') { return aVal > bVal ? 1 : -1; } else { return aVal < bVal ? 1 : -1; } }); setFilteredUsers(filtered); }; const handleValidateRequest = (user) => { setPendingAction({ type: 'validate', user }); setConfirmDialogOpen(true); }; const handleBypassAndValidateRequest = (user) => { setPendingAction({ type: 'bypass_and_validate', user }); setConfirmDialogOpen(true); }; const confirmAction = async () => { if (!pendingAction) return; const { type, user } = pendingAction; setActionLoading(user.id); setConfirmDialogOpen(false); try { if (type === 'validate') { await api.put(`/admin/users/${user.id}/validate`); toast.success('User validated! Payment email sent.'); } else if (type === 'bypass_and_validate') { await api.put(`/admin/users/${user.id}/validate?bypass_email_verification=true`); toast.success('User email verified and validated! Payment email sent.'); } fetchPendingUsers(); } catch (error) { toast.error(error.response?.data?.detail || 'Failed to validate user'); } finally { setActionLoading(null); setPendingAction(null); } }; const getActionMessage = () => { if (!pendingAction) return {}; const { type, user } = pendingAction; const userName = `${user.first_name} ${user.last_name}`; if (type === 'validate') { return { title: 'Validate User?', description: `This will validate ${userName} and send them a payment link email. They will be able to complete payment and become an active member.`, variant: 'success', confirmText: 'Yes, Validate User', }; } if (type === 'bypass_and_validate') { return { title: 'Bypass Email & Validate User?', description: `This will bypass email verification for ${userName} and validate them immediately. A payment link email will be sent. Use this only if you've confirmed their email through other means.`, variant: 'warning', confirmText: 'Yes, Bypass & Validate', }; } return {}; }; const handleActivatePayment = (user) => { setSelectedUserForPayment(user); setPaymentDialogOpen(true); }; const handlePaymentSuccess = () => { fetchPendingUsers(); // Refresh list }; const getStatusBadge = (status) => { const config = { pending_email: { label: 'Awaiting 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' } }; const statusConfig = config[status]; return ( {statusConfig.label} ); }; const handleSort = (column) => { if (sortBy === column) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortBy(column); setSortOrder('asc'); } }; // Pagination calculations const totalPages = Math.ceil(filteredUsers.length / itemsPerPage); const paginatedUsers = filteredUsers.slice( (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage ); const renderSortIcon = (column) => { if (sortBy !== column) return null; return sortOrder === 'asc' ? : ; }; return ( <> {/* Header */}

Validation Queue

Review and validate pending membership applications.

{/* Stats Card */}

Total Pending

{pendingUsers.length}

Awaiting Email

{pendingUsers.filter(u => u.status === 'pending_email').length}

Pending Validation

{pendingUsers.filter(u => u.status === 'pending_validation').length}

Pre-Validated

{pendingUsers.filter(u => u.status === 'pre_validated').length}

Payment Pending

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

{/* Filter Card */}
setSearchQuery(e.target.value)} className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" />
{/* Table */} {loading ? (

Loading pending applications...

) : filteredUsers.length > 0 ? ( <> handleSort('first_name')} > Name {renderSortIcon('first_name')} Email Phone handleSort('status')} > Status {renderSortIcon('status')} handleSort('created_at')} > Registered {renderSortIcon('created_at')} Referred By Actions {paginatedUsers.map((user) => ( {user.first_name} {user.last_name} {user.email} {user.phone} {getStatusBadge(user.status)} {new Date(user.created_at).toLocaleDateString()} {user.referred_by_member_name || '-'}
{user.status === 'pending_email' ? ( ) : user.status === 'payment_pending' ? ( ) : ( )}
))}
{/* Pagination Controls */}
{/* Page size selector */}

Show

entries (showing {(currentPage - 1) * itemsPerPage + 1}- {Math.min(currentPage * itemsPerPage, filteredUsers.length)} of {filteredUsers.length})

{/* Pagination */} {totalPages > 1 && ( setCurrentPage(p => Math.max(1, p - 1))} className={currentPage === 1 ? 'pointer-events-none opacity-50' : 'cursor-pointer'} /> {[...Array(totalPages)].map((_, i) => { const showPage = i < 2 || i >= totalPages - 2 || Math.abs(i - currentPage + 1) <= 1; if (!showPage && i === 2) { return ( ); } if (!showPage) return null; return ( setCurrentPage(i + 1)} isActive={currentPage === i + 1} className="cursor-pointer" > {i + 1} ); })} setCurrentPage(p => Math.min(totalPages, p + 1))} className={currentPage === totalPages ? 'pointer-events-none opacity-50' : 'cursor-pointer'} /> )}
) : (

No Pending Validations

{searchQuery || statusFilter !== 'all' ? 'Try adjusting your filters' : 'All applications have been reviewed!'}

)} {/* Payment Activation Dialog */} {/* Validation Confirmation Dialog */} ); }; export default AdminValidations;