import { useState, useMemo } from 'react' import { Avatar } from '../../../design-system/primitives/Avatar/Avatar' import { Badge } from '../../../design-system/primitives/Badge/Badge' import { Button } from '../../../design-system/primitives/Button/Button' import { Input } from '../../../design-system/primitives/Input/Input' import { MonoText } from '../../../design-system/primitives/MonoText/MonoText' import { SectionHeader } from '../../../design-system/primitives/SectionHeader/SectionHeader' import { Tag } from '../../../design-system/primitives/Tag/Tag' import { InlineEdit } from '../../../design-system/primitives/InlineEdit/InlineEdit' import { RadioGroup, RadioItem } from '../../../design-system/primitives/Radio/Radio' import { InfoCallout } from '../../../design-system/primitives/InfoCallout/InfoCallout' import { MultiSelect } from '../../../design-system/composites/MultiSelect/MultiSelect' import { ConfirmDialog } from '../../../design-system/composites/ConfirmDialog/ConfirmDialog' import { AlertDialog } from '../../../design-system/composites/AlertDialog/AlertDialog' import { useToast } from '../../../design-system/composites/Toast/Toast' import { MOCK_USERS, MOCK_GROUPS, MOCK_ROLES, getEffectiveRoles, type MockUser } from './rbacMocks' import styles from './UserManagement.module.css' export function UsersTab() { const { toast } = useToast() const [users, setUsers] = useState(MOCK_USERS) const [search, setSearch] = useState('') const [selectedId, setSelectedId] = useState(null) const [creating, setCreating] = useState(false) const [deleteTarget, setDeleteTarget] = useState(null) const [removeGroupTarget, setRemoveGroupTarget] = useState(null) // Create form state const [newUsername, setNewUsername] = useState('') const [newDisplay, setNewDisplay] = useState('') const [newEmail, setNewEmail] = useState('') const [newPassword, setNewPassword] = useState('') const [newProvider, setNewProvider] = useState<'local' | 'oidc'>('local') const [resettingPassword, setResettingPassword] = useState(false) const [newPw, setNewPw] = useState('') const filtered = useMemo(() => { if (!search) return users const q = search.toLowerCase() return users.filter((u) => u.displayName.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) || u.username.toLowerCase().includes(q) ) }, [users, search]) const selected = users.find((u) => u.id === selectedId) ?? null function handleCreate() { if (!newUsername.trim()) return if (newProvider === 'local' && !newPassword.trim()) return const newUser: MockUser = { id: `usr-${Date.now()}`, username: newUsername.trim(), displayName: newDisplay.trim() || newUsername.trim(), email: newEmail.trim(), provider: newProvider, createdAt: new Date().toISOString(), directRoles: [], directGroups: [], } setUsers((prev) => [...prev, newUser]) setCreating(false) setNewUsername(''); setNewDisplay(''); setNewEmail(''); setNewPassword(''); setNewProvider('local') setSelectedId(newUser.id) setResettingPassword(false) toast({ title: 'User created', description: newUser.displayName, variant: 'success' }) } function handleDelete() { if (!deleteTarget) return setUsers((prev) => prev.filter((u) => u.id !== deleteTarget.id)) if (selectedId === deleteTarget.id) setSelectedId(null) setDeleteTarget(null) toast({ title: 'User deleted', description: deleteTarget.username, variant: 'warning' }) } function updateUser(id: string, patch: Partial) { setUsers((prev) => prev.map((u) => u.id === id ? { ...u, ...patch } : u)) } const duplicateUsername = newUsername.trim() !== '' && users.some((u) => u.username.toLowerCase() === newUsername.trim().toLowerCase()) const effectiveRoles = selected ? getEffectiveRoles(selected) : [] const availableGroups = MOCK_GROUPS.filter((g) => !selected?.directGroups.includes(g.id)) .map((g) => ({ value: g.id, label: g.name })) const availableRoles = MOCK_ROLES.filter((r) => !selected?.directRoles.includes(r.name)) .map((r) => ({ value: r.name, label: r.name })) function getUserGroupPath(user: MockUser): string { if (user.directGroups.length === 0) return 'no groups' const group = MOCK_GROUPS.find((g) => g.id === user.directGroups[0]) if (!group) return 'no groups' const parent = group.parentId ? MOCK_GROUPS.find((g) => g.id === group.parentId) : null return parent ? `${parent.name} > ${group.name}` : group.name } return ( <>
setSearch(e.target.value)} onClear={() => setSearch('')} className={styles.listHeaderSearch} />
{creating && (
setNewProvider(v as 'local' | 'oidc')} orientation="horizontal">
setNewUsername(e.target.value)} /> setNewDisplay(e.target.value)} />
{duplicateUsername && Username already exists} setNewEmail(e.target.value)} /> {newProvider === 'local' && ( setNewPassword(e.target.value)} /> )} {newProvider === 'oidc' && ( OIDC users authenticate via the configured identity provider. Pre-register to assign roles/groups before their first login. )}
)}
{filtered.map((user) => (
{ setSelectedId(user.id); setResettingPassword(false) }} role="option" tabIndex={0} aria-selected={selectedId === user.id} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedId(user.id); setResettingPassword(false) } }} >
{user.displayName} {user.provider !== 'local' && ( )}
{user.email} · {getUserGroupPath(user)}
{user.directRoles.map((r) => )} {user.directGroups.map((gId) => { const g = MOCK_GROUPS.find((gr) => gr.id === gId) return g ? : null })}
))} {filtered.length === 0 && (
No users match your search
)}
{selected ? ( <>
updateUser(selected.id, { displayName: v })} />
{selected.email}
Status
ID {selected.id} Created {new Date(selected.createdAt).toLocaleDateString()} Provider {selected.provider}
Security
{selected.provider === 'local' ? ( <>
Password •••••••• {!resettingPassword && ( )}
{resettingPassword && (
setNewPw(e.target.value)} className={styles.resetInput} />
)} ) : ( <>
Authentication OIDC ({selected.provider})
Password managed by the identity provider. )}
Group membership (direct only)
{selected.directGroups.map((gId) => { const g = MOCK_GROUPS.find((gr) => gr.id === gId) return g ? ( { const group = MOCK_GROUPS.find((gr) => gr.id === gId) if (group && group.directRoles.length > 0) { setRemoveGroupTarget(gId) } else { updateUser(selected.id, { directGroups: selected.directGroups.filter((id) => id !== gId) }) toast({ title: 'Group removed', variant: 'success' }) } }} /> ) : null })} {selected.directGroups.length === 0 && ( (no groups) )} { updateUser(selected.id, { directGroups: [...selected.directGroups, ...ids] }) toast({ title: `${ids.length} group(s) added`, variant: 'success' }) }} placeholder="+ Add" />
Effective roles (direct + inherited)
{effectiveRoles.map(({ role, source }) => source === 'direct' ? ( { updateUser(selected.id, { directRoles: selected.directRoles.filter((r) => r !== role) }) toast({ title: 'Role removed', description: role, variant: 'success' }) }} /> ) : ( ) )} {effectiveRoles.length === 0 && ( (no roles) )} { updateUser(selected.id, { directRoles: [...selected.directRoles, ...roles] }) toast({ title: `${roles.length} role(s) added`, variant: 'success' }) }} placeholder="+ Add" />
{effectiveRoles.some((r) => r.source !== 'direct') && ( Roles with ↑ are inherited through group membership )} ) : (
Select a user to view details
)}
setDeleteTarget(null)} onConfirm={handleDelete} message={`Delete user "${deleteTarget?.username}"? This cannot be undone.`} confirmText={deleteTarget?.username ?? ''} /> setRemoveGroupTarget(null)} onConfirm={() => { if (removeGroupTarget && selected) { updateUser(selected.id, { directGroups: selected.directGroups.filter((id) => id !== removeGroupTarget) }) toast({ title: 'Group removed', variant: 'success' }) } setRemoveGroupTarget(null) }} title="Remove group membership" description={`Removing this group will also revoke inherited roles: ${MOCK_GROUPS.find((g) => g.id === removeGroupTarget)?.directRoles.join(', ') ?? ''}. Continue?`} confirmLabel="Remove" variant="warning" /> ) }