import { useState, useCallback, useEffect, useRef } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Plus, Search, Users, Shield, Loader2, CheckCircle, AlertCircle, Crown, User as UserIcon, } from 'lucide-react'; import { userApi } from '@/services/userApi'; import { toast } from 'sonner'; import { UserTable } from './UserTable'; import { UserStatsCards } from './UserStatsCards'; // Simple debounce function function debounce any>( func: T, wait: number ): (...args: Parameters) => void { let timeout: NodeJS.Timeout | null = null; return function executedFunction(...args: Parameters) { const later = () => { timeout = null; func(...args); }; if (timeout) clearTimeout(timeout); timeout = setTimeout(later, wait); }; } interface OktaUser { userId: string; email: string; firstName?: string; lastName?: string; displayName?: string; department?: string; designation?: string; } export interface User { userId: string; email: string; displayName: string; role: 'USER' | 'MANAGEMENT' | 'ADMIN'; department?: string; designation?: string; isActive?: boolean; } export function UserManagement() { const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [searching, setSearching] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [selectedRole, setSelectedRole] = useState<'USER' | 'MANAGEMENT' | 'ADMIN'>('USER'); const [updating, setUpdating] = useState(false); const [fetchingRole, setFetchingRole] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); // Users list with filtering and pagination const [users, setUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(false); const [roleStats, setRoleStats] = useState({ admins: 0, management: 0, users: 0, total: 0, active: 0, inactive: 0 }); // Pagination and filtering const [roleFilter, setRoleFilter] = useState<'ELEVATED' | 'ALL' | 'ADMIN' | 'MANAGEMENT' | 'USER'>('ELEVATED'); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalUsers, setTotalUsers] = useState(0); const limit = 10; // Refs for search container const searchContainerRef = useRef(null); // Search users from Okta const searchUsers = useCallback( debounce(async (query: string) => { // Only trigger search when using @ sign if (!query || !query.startsWith('@') || query.length < 2) { setSearchResults([]); setSearching(false); return; } setSearching(true); try { const term = query.slice(1); // Remove @ prefix const response = await userApi.searchUsers(term, 20); const users = response.data?.data || []; setSearchResults(users); } catch (error: any) { console.error('Search failed:', error); setMessage({ type: 'error', text: error.response?.data?.message || 'Failed to search users' }); } finally { setSearching(false); } }, 300), [] ); // Handle search input const handleSearchChange = (e: React.ChangeEvent) => { const query = e.target.value; setSearchQuery(query); searchUsers(query); }; // Fetch user's current role const fetchUserRole = async (email: string): Promise<'USER' | 'MANAGEMENT' | 'ADMIN' | null> => { try { // First check if user exists in current users list const existingUser = users.find(u => u.email.toLowerCase() === email.toLowerCase()); if (existingUser) { return existingUser.role; } // If not found, try to fetch from backend by checking all users // We'll search with a broader filter to find the user const response = await userApi.getUsersByRole('ALL', 1, 1000); const allUsers = response.data?.data?.users || []; const foundUser = allUsers.find((u: any) => u.email?.toLowerCase() === email.toLowerCase() ); if (foundUser && foundUser.role) { return foundUser.role as 'USER' | 'MANAGEMENT' | 'ADMIN'; } return null; // User not found in system, no role assigned } catch (error) { console.error('Failed to fetch user role:', error); return null; } }; // Select user from search results const handleSelectUser = async (user: OktaUser) => { setSelectedUser(user); setSearchQuery(user.email); setSearchResults([]); setFetchingRole(true); try { // Fetch and set the user's current role if they have one const currentRole = await fetchUserRole(user.email); if (currentRole) { setSelectedRole(currentRole); } else { // Default to USER if no role found setSelectedRole('USER'); } } catch (error) { console.error('Failed to fetch user role:', error); setSelectedRole('USER'); // Default on error } finally { setFetchingRole(false); } }; // Assign role to user const handleAssignRole = async () => { if (!selectedUser || !selectedRole) { setMessage({ type: 'error', text: 'Please select a user and role' }); return; } setUpdating(true); setMessage(null); try { await userApi.assignRole(selectedUser.email, selectedRole); setMessage({ type: 'success', text: `Successfully assigned ${selectedRole} role to ${selectedUser.displayName || selectedUser.email}` }); // Reset form setSelectedUser(null); setSearchQuery(''); setSelectedRole('USER'); // Refresh the users list await fetchUsers(); await fetchRoleStatistics(); toast.success(`Role assigned successfully`); } catch (error: any) { console.error('Role assignment failed:', error); const errorMsg = error.response?.data?.error || 'Failed to assign role'; setMessage({ type: 'error', text: errorMsg }); toast.error(errorMsg); } finally { setUpdating(false); } }; // Fetch users with filtering and pagination const fetchUsers = async (page: number = currentPage) => { setLoadingUsers(true); try { const response = await userApi.getUsersByRole(roleFilter, page, limit); const usersData = response.data?.data?.users || []; const paginationData = response.data?.data?.pagination; const summaryData = response.data?.data?.summary; setUsers(usersData.map((u: any) => ({ userId: u.userId, email: u.email, displayName: u.displayName || u.email, role: u.role || 'USER', department: u.department, designation: u.designation, isActive: u.isActive !== false // Default to true if not specified }))); if (paginationData) { setCurrentPage(paginationData.currentPage); setTotalPages(paginationData.totalPages); setTotalUsers(paginationData.totalUsers); } // Update summary stats if available if (summaryData) { setRoleStats(prev => ({ ...prev, admins: summaryData.ADMIN || 0, management: summaryData.MANAGEMENT || 0, users: summaryData.USER || 0, total: (summaryData.ADMIN || 0) + (summaryData.MANAGEMENT || 0) + (summaryData.USER || 0) })); } } catch (error) { console.error('Failed to fetch users:', error); toast.error('Failed to load users'); } finally { setLoadingUsers(false); } }; // Fetch role statistics const fetchRoleStatistics = async () => { try { const response = await userApi.getRoleStatistics(); const statsData = response.data?.data?.statistics || response.data?.statistics || []; const stats = { admins: parseInt(statsData.find((s: any) => s.role === 'ADMIN')?.count || '0'), management: parseInt(statsData.find((s: any) => s.role === 'MANAGEMENT')?.count || '0'), users: parseInt(statsData.find((s: any) => s.role === 'USER')?.count || '0') }; setRoleStats(prev => ({ ...prev, ...stats, total: stats.admins + stats.management + stats.users, active: prev.active || stats.admins + stats.management + stats.users, inactive: prev.inactive || 0 })); } catch (error) { console.error('Failed to fetch statistics:', error); } }; // Load data on mount and when filter changes useEffect(() => { fetchUsers(1); // Reset to page 1 when filter changes fetchRoleStatistics(); }, [roleFilter]); // Handle filter change const handleFilterChange = (value: string) => { setRoleFilter(value as any); setCurrentPage(1); }; // Handle page change const handlePageChange = (page: number) => { fetchUsers(page); }; // Handle edit user role const handleEditUser = async (userId: string, newRole: 'USER' | 'MANAGEMENT' | 'ADMIN') => { try { await userApi.updateUserRole(userId, newRole); toast.success('User role updated successfully'); await fetchUsers(); await fetchRoleStatistics(); } catch (error: any) { console.error('Failed to update user role:', error); toast.error(error.response?.data?.error || 'Failed to update user role'); } }; // Handle toggle user status (placeholder - needs backend support) const handleToggleUserStatus = async (userId: string) => { const user = users.find(u => u.userId === userId); if (!user) return; toast.info('User status toggle functionality coming soon'); }; // Handle delete user (placeholder - needs backend support) const handleDeleteUser = async (userId: string) => { const user = users.find(u => u.userId === userId); if (!user) return; if (user.role === 'ADMIN') { toast.error('Cannot delete admin user'); return; } toast.info('User deletion functionality coming soon'); }; // Handle click outside to close search results useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (searchContainerRef.current && !searchContainerRef.current.contains(event.target as Node)) { setSearchResults([]); } }; if (searchResults.length > 0) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [searchResults]); // Calculate stats for UserStatsCards const stats = { total: roleStats.total, active: roleStats.active, inactive: roleStats.inactive, admins: roleStats.admins }; return (
{/* Statistics Cards */} {/* Assign Role Section */}
Assign User Role Search for a user in Okta and assign them a role
{/* Search Input */}
{searching && ( )}

Start with @ to search users (e.g., @john)

{/* Search Results Dropdown */} {searchResults.length > 0 && (

{searchResults.length} user{searchResults.length > 1 ? 's' : ''} found

{searchResults.map((user) => ( ))}
)}
{/* Selected User */} {selectedUser && (
{(selectedUser.displayName || selectedUser.email).charAt(0).toUpperCase()}

{selectedUser.displayName || selectedUser.email}

{selectedUser.email}

{selectedUser.department && (

{selectedUser.department}{selectedUser.designation ? ` • ${selectedUser.designation}` : ''}

)} {fetchingRole && (

Checking current role...

)}
)} {/* Role Selection */}
{/* Message */} {message && (
{message.type === 'success' ? ( ) : ( )}

{message.text}

)}
{/* Users List with Filter and Pagination */}
User Management View and manage user accounts and roles ({totalUsers} {roleFilter !== 'ALL' && roleFilter !== 'ELEVATED' ? roleFilter.toLowerCase() : ''} users)
{loadingUsers ? (

Loading users...

) : users.length === 0 ? (

No users found

{roleFilter === 'ELEVATED' ? 'Assign ADMIN or MANAGEMENT roles to see users here' : 'No users match the selected filter' }

) : ( <> ({ id: u.userId, name: u.displayName, email: u.email, role: u.role, // Keep as ADMIN, MANAGEMENT, or USER department: u.department || 'N/A', status: u.isActive ? 'active' : 'inactive' }))} onEdit={(userId) => { const user = users.find(u => u.userId === userId); if (user) { // Open role selection dialog const newRole = user.role === 'USER' ? 'MANAGEMENT' : user.role === 'MANAGEMENT' ? 'ADMIN' : 'USER'; handleEditUser(userId, newRole); } }} onToggleStatus={handleToggleUserStatus} onDelete={handleDeleteUser} /> {/* Pagination Controls */} {totalPages > 1 && (
Showing {((currentPage - 1) * limit) + 1} to {Math.min(currentPage * limit, totalUsers)} of {totalUsers} users
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => { let pageNum; if (totalPages <= 5) { pageNum = i + 1; } else if (currentPage <= 3) { pageNum = i + 1; } else if (currentPage >= totalPages - 2) { pageNum = totalPages - 4 + i; } else { pageNum = currentPage - 2 + i; } return ( ); })}
)} )}
); }