'use client' import { useMemo, useState, useEffect, useCallback } from 'react' import PageLayout from '../../components/PageLayout' import UserDetailModal from '../../components/UserDetailModal' import { MagnifyingGlassIcon, PencilSquareIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline' import { useAdminUsers } from '../../hooks/useAdminUsers' import { AdminAPI } from '../../utils/api' import useAuthStore from '../../store/authStore' type UserType = 'personal' | 'company' type UserStatus = 'active' | 'pending' | 'disabled' | 'inactive' | 'suspended' | 'archived' type UserRole = 'user' | 'admin' interface User { id: number email: string user_type: UserType role: UserRole created_at: string last_login_at: string | null status: string is_admin_verified: number first_name?: string last_name?: string company_name?: string } const STATUSES: UserStatus[] = ['active','pending','disabled','inactive'] const TYPES: UserType[] = ['personal','company'] const ROLES: UserRole[] = ['user','admin'] export default function AdminUserManagementPage() { const { isAdmin } = useAdminUsers() const token = useAuthStore(state => state.accessToken) const [isClient, setIsClient] = useState(false) // State for all users (not just pending) const [allUsers, setAllUsers] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) // Handle client-side mounting useEffect(() => { setIsClient(true) }, []) // Fetch all users from backend const fetchAllUsers = useCallback(async () => { if (!token || !isAdmin) return setLoading(true) setError(null) try { const response = await AdminAPI.getUserList(token) if (response.success) { setAllUsers(response.users || []) } else { throw new Error(response.message || 'Failed to fetch users') } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fetch users' setError(errorMessage) console.error('AdminUserManagement.fetchAllUsers error:', err) } finally { setLoading(false) } }, [token, isAdmin]) // Load users on mount useEffect(() => { if (isClient && isAdmin && token) { fetchAllUsers() } }, [fetchAllUsers, isClient]) // Filter hooks - must be declared before conditional returns const [search, setSearch] = useState('') const [fType, setFType] = useState<'all'|UserType>('all') const [fStatus, setFStatus] = useState<'all'|UserStatus>('all') const [fRole, setFRole] = useState<'all'|UserRole>('all') const [page, setPage] = useState(1) const PAGE_SIZE = 10 // Modal state const [isDetailModalOpen, setIsDetailModalOpen] = useState(false) const [selectedUserId, setSelectedUserId] = useState(null) const filtered = useMemo(() => { return allUsers.filter(u => { const firstName = u.first_name || '' const lastName = u.last_name || '' const companyName = u.company_name || '' const fullName = u.user_type === 'company' ? companyName : `${firstName} ${lastName}` // Use backend status directly for filtering const allowedStatuses: UserStatus[] = ['pending','active','suspended','inactive','archived'] const userStatus: UserStatus = (allowedStatuses.includes(u.status as UserStatus) ? u.status : 'pending') as UserStatus return ( (fType === 'all' || u.user_type === fType) && (fStatus === 'all' || userStatus === fStatus) && (fRole === 'all' || u.role === fRole) && ( !search.trim() || u.email.toLowerCase().includes(search.toLowerCase()) || fullName.toLowerCase().includes(search.toLowerCase()) ) ) }) }, [allUsers, search, fType, fStatus, fRole]) const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)) const current = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE) // Move stats calculation above all conditional returns to avoid hook order errors const stats = useMemo(() => ({ total: allUsers.length, admins: allUsers.filter(u => u.role === 'admin').length, personal: allUsers.filter(u => u.user_type === 'personal').length, company: allUsers.filter(u => u.user_type === 'company').length, active: allUsers.filter(u => u.status === 'active').length, pending: allUsers.filter(u => u.status === 'pending').length, }), [allUsers]) // Show loading during SSR/initial client render if (!isClient) { return (

Loading...

) } // Access check (only after client-side hydration) if (!isAdmin) { return (

Access Denied

You need admin privileges to access this page.

) } const applyFilter = (e: React.FormEvent) => { e.preventDefault() setPage(1) } // NEW: CSV export utilities (exports all filtered results, not only current page) const toCsvValue = (v: unknown) => { if (v === null || v === undefined) return '""' const s = String(v).replace(/"/g, '""') return `"${s}"` } const exportCsv = () => { const headers = [ 'ID','Email','Type','Role','Status','Admin Verified', 'First Name','Last Name','Company Name','Created At','Last Login At' ] const rows = filtered.map(u => { // Use backend status directly const allowedStatuses: UserStatus[] = ['active','pending','suspended','inactive','archived'] const userStatus: UserStatus = (allowedStatuses.includes(u.status as UserStatus) ? u.status : 'pending') as UserStatus return [ u.id, u.email, u.user_type, u.role, userStatus, u.is_admin_verified === 1 ? 'yes' : 'no', u.first_name || '', u.last_name || '', u.company_name || '', new Date(u.created_at).toISOString(), u.last_login_at ? new Date(u.last_login_at).toISOString() : '' ].map(toCsvValue).join(',') }) const csv = [headers.join(','), ...rows].join('\r\n') const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `users_${new Date().toISOString().slice(0,10)}.csv` document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(url) } const badge = (text: string, color: 'blue'|'amber'|'green'|'gray'|'rose'|'indigo'|'purple') => { const base = 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium tracking-wide' const map: Record = { blue: 'bg-blue-100 text-blue-700', amber: 'bg-amber-100 text-amber-700', green: 'bg-green-100 text-green-700', gray: 'bg-gray-100 text-gray-700', rose: 'bg-rose-100 text-rose-700', indigo: 'bg-indigo-100 text-indigo-700', purple: 'bg-purple-100 text-purple-700' } return {text} } const statusBadge = (s: UserStatus) => s==='active' ? badge('Active','green') : s==='pending' ? badge('Pending','amber') : s==='suspended' ? badge('Suspended','rose') : s==='archived' ? badge('Archived','gray') : s==='inactive' ? badge('Inactive','gray') : badge('Unknown','gray') const typeBadge = (t: UserType) => t==='personal' ? badge('Personal','blue') : badge('Company','purple') const roleBadge = (r: UserRole) => r==='admin' ? badge('Admin','indigo') : badge('User','gray') // Action handler for opening edit modal const onEdit = (id: string) => { setSelectedUserId(id) setIsDetailModalOpen(true) } return (
{/* Header */}

User Management

Manage all users, view statistics, and handle verification.

{/* Statistic Section + Verify Button */}
Total Users
{stats.total}
Admins
{stats.admins}
Personal
{stats.personal}
Company
{stats.company}
Active
{stats.active}
Pending
{stats.pending}
{/* Error Message */} {error && (

Error loading users

{error}

)} {/* Filter Card */}

Search & Filter Users

{/* Search */}
setSearch(e.target.value)} placeholder="Email, name, company..." className="w-full rounded-lg border border-gray-300 pl-10 pr-3 py-3 text-sm focus:ring-2 focus:ring-blue-900 focus:border-transparent shadow" />
{/* Type */}
{/* Status */}
{/* Role */}
{/* Users Table */}
All Users
Showing {current.length} of {filtered.length} users
{loading ? ( ) : current.map(u => { const displayName = u.user_type === 'company' ? u.company_name || 'Unknown Company' : `${u.first_name || 'Unknown'} ${u.last_name || 'User'}` const initials = u.user_type === 'company' ? (u.company_name?.[0] || 'C').toUpperCase() : `${u.first_name?.[0] || 'U'}${u.last_name?.[0] || 'U'}`.toUpperCase() // Use backend status directly for display to avoid desync const allowedStatuses: UserStatus[] = ['active','pending','suspended','inactive','archived'] const userStatus: UserStatus = (allowedStatuses.includes(u.status as UserStatus) ? u.status : 'pending') as UserStatus const createdDate = new Date(u.created_at).toLocaleDateString() const lastLoginDate = u.last_login_at ? new Date(u.last_login_at).toLocaleDateString() : 'Never' return ( ) })} {current.length === 0 && ( )}
User Type Status Role Created Last Login Actions
Loading users...
{initials}
{displayName}
{u.email}
{typeBadge(u.user_type)} {statusBadge(userStatus)} {roleBadge(u.role)} {createdDate} {lastLoginDate}
No users match current filters.
{/* Pagination */}
Page {page} of {totalPages} ({filtered.length} total users)
{/* User Detail Modal */} { setIsDetailModalOpen(false) setSelectedUserId(null) }} userId={selectedUserId} onUserUpdated={fetchAllUsers} />
) }