import React, { useState, useEffect, useMemo } from 'react'; import { User, UserRole } from '../../types'; import { AccountStatus } from '../../services/auth/auth.constants'; import { databaseService } from '../../services/database'; import { useUser } from '../../contexts/UserContext'; import { logger } from '../../services/logging'; interface AdminInterfaceProps { onClose: () => void; } const AdminInterface: React.FC = ({ onClose }) => { const { user: currentUser } = useUser(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [selectedUser, setSelectedUser] = useState(null); const [newPassword, setNewPassword] = useState(''); const [userPendingDeletion, setUserPendingDeletion] = useState( null ); const [isDeletingUser, setIsDeletingUser] = useState(false); const [toasts, setToasts] = useState< Array<{ id: string; message: string; tone: 'success' | 'error' | 'info' }> >([]); const [searchTerm, setSearchTerm] = useState(''); const [sortField, setSortField] = useState< 'createdAt' | 'status' | 'role' | 'username' >('createdAt'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); const createToastId = () => typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; const removeToast = (id: string) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; const pushToast = ( message: string, tone: 'success' | 'error' | 'info' = 'info' ) => { const id = createToastId(); setToasts(prev => [...prev, { id, message, tone }]); window.setTimeout(() => removeToast(id), 4000); }; const toastStyles: Record<'success' | 'error' | 'info', string> = { success: 'bg-green-50 border border-green-200 text-green-800', error: 'bg-red-50 border border-red-200 text-red-800', info: 'bg-blue-50 border border-blue-200 text-blue-800', }; useEffect(() => { loadUsers(); }, []); const loadUsers = async () => { setLoading(true); setError(''); try { const users = await databaseService.getAllUsers(); setUsers(users); } catch (error) { setError('Failed to load users'); logger.ui.error('Error loading users', error as Error); pushToast('Failed to load users', 'error'); } finally { setLoading(false); } }; const handleSuspendUser = async (user: User) => { try { await databaseService.suspendUser(user._id); pushToast(`${user.username} suspended`, 'info'); await loadUsers(); } catch (error) { setError('Failed to suspend user'); logger.ui.error('Error suspending user', error as Error); pushToast('Failed to suspend user', 'error'); } }; const handleActivateUser = async (user: User) => { try { await databaseService.activateUser(user._id); pushToast(`${user.username} reactivated`, 'success'); await loadUsers(); } catch (error) { setError('Failed to activate user'); logger.ui.error('Error activating user', error as Error); pushToast('Failed to activate user', 'error'); } }; const confirmDeleteUser = (user: User) => { setUserPendingDeletion(user); setError(''); }; const executeDeleteUser = async () => { if (!userPendingDeletion) return; setIsDeletingUser(true); try { await databaseService.deleteUser(userPendingDeletion._id); pushToast(`${userPendingDeletion.username} deleted`, 'info'); await loadUsers(); } catch (error) { setError('Failed to delete user'); logger.ui.error('Error deleting user', error as Error); pushToast('Failed to delete user', 'error'); } finally { setIsDeletingUser(false); setUserPendingDeletion(null); } }; const closeDeleteDialog = () => { if (isDeletingUser) return; setUserPendingDeletion(null); }; const handleChangePassword = async (userId: string) => { if (!newPassword || newPassword.length < 6) { setError('Password must be at least 6 characters long'); return; } try { await databaseService.changeUserPassword(userId, newPassword); setNewPassword(''); setSelectedUser(null); setError(''); pushToast('Password changed successfully', 'success'); } catch (error) { setError('Failed to change password'); logger.ui.error('Error changing password', error as Error); pushToast('Failed to change password', 'error'); } }; const getStatusColor = (status?: AccountStatus) => { switch (status) { case AccountStatus.ACTIVE: return 'text-green-600 bg-green-100'; case AccountStatus.SUSPENDED: return 'text-red-600 bg-red-100'; case AccountStatus.PENDING: return 'text-yellow-600 bg-yellow-100'; default: return 'text-gray-600 bg-gray-100'; } }; const getRoleColor = (role?: UserRole) => { return role === UserRole.ADMIN ? 'text-purple-600 bg-purple-100' : 'text-blue-600 bg-blue-100'; }; const statusPriority: Record = { [AccountStatus.ACTIVE]: 0, [AccountStatus.PENDING]: 1, [AccountStatus.SUSPENDED]: 2, }; const rolePriority: Record = { [UserRole.ADMIN]: 0, [UserRole.USER]: 1, }; const getStatusPriority = (status?: AccountStatus) => statusPriority[status as AccountStatus] ?? Number.MAX_SAFE_INTEGER; const getRolePriority = (role?: UserRole) => rolePriority[role as UserRole] ?? Number.MAX_SAFE_INTEGER; const sortedUsers = useMemo(() => { const copy = [...users]; copy.sort((a, b) => { let result = 0; switch (sortField) { case 'status': result = getStatusPriority(a.status) - getStatusPriority(b.status); break; case 'role': result = getRolePriority(a.role) - getRolePriority(b.role); break; case 'username': result = (a.username || '').localeCompare( b.username || '', undefined, { sensitivity: 'base', } ); break; case 'createdAt': default: { const timeA = a.createdAt ? new Date(a.createdAt).getTime() : 0; const timeB = b.createdAt ? new Date(b.createdAt).getTime() : 0; result = timeA - timeB; break; } } if (result === 0) { result = (a.username || '').localeCompare(b.username || '', undefined, { sensitivity: 'base', }); } if (result === 0) { result = (a.email || '').localeCompare(b.email || '', undefined, { sensitivity: 'base', }); } return sortDirection === 'asc' ? result : -result; }); return copy; }, [users, sortField, sortDirection]); const filteredUsers = useMemo(() => { if (!searchTerm.trim()) { return sortedUsers; } const query = searchTerm.trim().toLowerCase(); return sortedUsers.filter(user => { const username = user.username?.toLowerCase() ?? ''; const email = user.email?.toLowerCase() ?? ''; return username.includes(query) || email.includes(query); }); }, [sortedUsers, searchTerm]); const visibleUsersLabel = searchTerm.trim() ? `${filteredUsers.length} of ${users.length} users` : `${filteredUsers.length} users`; if (currentUser?.role !== UserRole.ADMIN) { return (

Access Denied

You don't have permission to access the admin interface.

); } return ( <>
{toasts.map(toast => (
{toast.message}
))}

Admin Interface

{error && (
{error}
)} {loading ? (

Loading users...

) : (

User Management ({visibleUsersLabel})

setSearchTerm(e.target.value)} placeholder='Search by username or email' className='w-full px-3 py-2 border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 text-sm bg-white dark:bg-slate-700 dark:border-slate-600 dark:text-slate-100' />
{filteredUsers.length === 0 ? (

No users match the current filters.

) : ( {filteredUsers.map(user => ( ))}
User Email Status Role Created Actions
{user.avatar ? ( {user.username} ) : (
{user.username.charAt(0).toUpperCase()}
)}
{user.username}
ID: {user._id.slice(-8)}
{user.email} {user.emailVerified && ( )} {user.status || 'Unknown'} {user.role || 'USER'} {user.createdAt ? new Date(user.createdAt).toLocaleDateString() : 'Unknown'}
{user.status === AccountStatus.ACTIVE ? ( ) : ( )} {user.password && ( )}
)}
)}
{/* Password Change Modal */} {selectedUser && (

Change Password for {selectedUser.username}

setNewPassword(e.target.value)} className='w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-slate-700 dark:border-slate-600 dark:text-white' placeholder='Enter new password (min 6 characters)' />
)} {userPendingDeletion && (

Delete {userPendingDeletion.username}?

This action cannot be undone. The user's data will be permanently removed.

)}
); }; export default AdminInterface;