import { useState, useMemo } from 'react'; import { toast } from 'sonner'; import { Users, ShieldCheck, Smartphone, Plus, Activity, Search, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; import { Skeleton } from '@/components/ui/skeleton'; import { StatCard } from './shared'; import UserDetailSection from './UserDetailSection'; import { useAdminUsers, useAdminDashboard, useAdminConfig, useUpdateConfig, getErrorMessage, } from '@/hooks/useAdmin'; import { useAuth } from '@/hooks/useAuth'; import { getRelativeTime } from '@/lib/date-utils'; import type { AdminUserDetail, UserRole } from '@/types'; import { cn } from '@/lib/utils'; import UserActionsMenu from './UserActionsMenu'; import CreateUserDialog from './CreateUserDialog'; // ── Role badge ──────────────────────────────────────────────────────────────── function RoleBadge({ role }: { role: UserRole }) { const styles: Record = { admin: 'bg-red-500/15 text-red-400', standard: 'bg-blue-500/15 text-blue-400', public_event_manager: 'bg-purple-500/15 text-purple-400', }; const labels: Record = { admin: 'Admin', standard: 'Standard', public_event_manager: 'Pub. Events', }; return ( {labels[role]} ); } // ── Main page ───────────────────────────────────────────────────────────────── export default function IAMPage() { const [createOpen, setCreateOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedUserId, setSelectedUserId] = useState(null); const { authStatus } = useAuth(); const { data: users, isLoading: usersLoading } = useAdminUsers(); const { data: dashboard } = useAdminDashboard(); const { data: config, isLoading: configLoading } = useAdminConfig(); const updateConfig = useUpdateConfig(); const filteredUsers = useMemo(() => { if (!users) return []; if (!searchQuery.trim()) return users; const q = searchQuery.toLowerCase(); return users.filter( (u) => u.username.toLowerCase().includes(q) || (u.email && u.email.toLowerCase().includes(q)) || (u.first_name && u.first_name.toLowerCase().includes(q)) || (u.last_name && u.last_name.toLowerCase().includes(q)) ); }, [users, searchQuery]); const handleConfigToggle = async (key: 'allow_registration' | 'enforce_mfa_new_users' | 'allow_passwordless', value: boolean) => { try { await updateConfig.mutateAsync({ [key]: value }); toast.success('System settings updated'); } catch (err) { toast.error(getErrorMessage(err, 'Failed to update settings')); } }; const mfaPct = dashboard ? Math.round(dashboard.mfa_adoption_rate * 100) : null; return (
{/* Stats row */}
} label="Total Users" value={dashboard?.total_users ?? '—'} /> } label="Active Sessions" value={dashboard?.active_sessions ?? '—'} iconBg="bg-green-500/10" /> } label="Admins" value={dashboard?.admin_count ?? '—'} iconBg="bg-red-500/10" /> } label="MFA Adoption" value={mfaPct !== null ? `${mfaPct}%` : '—'} iconBg="bg-purple-500/10" />
{/* User table */}
Users
setSearchQuery(e.target.value)} placeholder="Search users..." className="pl-8 h-8 w-32 sm:w-48 text-xs" />
{usersLoading ? (
{Array.from({ length: 4 }).map((_, i) => ( ))}
) : !filteredUsers.length ? (

{searchQuery ? 'No users match your search.' : 'No users found.'}

) : (
{filteredUsers.map((user: AdminUserDetail, idx) => ( setSelectedUserId(selectedUserId === user.id ? null : user.id)} className={cn( 'border-b border-border transition-colors cursor-pointer', selectedUserId === user.id ? 'bg-accent/5 border-l-2 border-l-accent' : cn( 'hover:bg-card-elevated/50', idx % 2 === 0 ? '' : 'bg-card-elevated/25' ) )} > ))}
Username Umbral Name Email Role Status Last Login MFA Sessions Created Actions
{user.username} {user.umbral_name || user.username} {user.email || '—'} {user.is_active ? 'Active' : 'Disabled'} {user.last_login_at ? getRelativeTime(user.last_login_at) : '—'} {user.totp_enabled ? ( On ) : user.mfa_enforce_pending ? ( Pending ) : ( )} {user.active_sessions} {getRelativeTime(user.created_at)} e.stopPropagation()}>
)}
{/* User detail section */} {selectedUserId !== null && ( setSelectedUserId(null)} /> )} {/* System settings */}
System Settings
{configLoading ? (
) : ( <>

When enabled, the /register page accepts new sign-ups.

handleConfigToggle('allow_registration', v)} disabled={updateConfig.isPending} />

Newly registered users will be required to set up TOTP before accessing the app.

handleConfigToggle('enforce_mfa_new_users', v)} disabled={updateConfig.isPending} />

Allow users to enable passkey-only login, skipping the password prompt entirely.

handleConfigToggle('allow_passwordless', v)} disabled={updateConfig.isPending} />
)}
); }