`.
Add the `styles` import if not already present (it already imports from `./UserManagement.module.css` — wait, it doesn't currently. Add:
```tsx
import styles from './UserManagement.module.css'
```
- [ ] **Step 2: Commit**
```bash
git add src/pages/Admin/UserManagement/UserManagement.tsx
git commit -m "refactor: replace inline style with CSS module class"
```
---
### Task 6: Add toasts to all RBAC tabs
**Files:**
- Modify: `src/pages/Admin/UserManagement/UsersTab.tsx`
- Modify: `src/pages/Admin/UserManagement/GroupsTab.tsx`
- Modify: `src/pages/Admin/UserManagement/RolesTab.tsx`
- [ ] **Step 1: Add useToast to UsersTab**
Add import: `import { useToast } from '../../../design-system/composites/Toast/Toast'`
Add `const { toast } = useToast()` at the top of the `UsersTab` function body.
Add toast calls:
- After `setSelectedId(newUser.id)` in `handleCreate`: `toast({ title: 'User created', description: newUser.displayName, variant: 'success' })`
- After `setDeleteTarget(null)` in `handleDelete`: `toast({ title: 'User deleted', description: deleteTarget.username, variant: 'warning' })`
- [ ] **Step 2: Add useToast to GroupsTab**
Same pattern. Add toast calls:
- After create: `toast({ title: 'Group created', description: newGroup.name, variant: 'success' })`
- After delete: `toast({ title: 'Group deleted', description: deleteTarget.name, variant: 'warning' })`
- [ ] **Step 3: Add useToast to RolesTab**
Same pattern. Add toast calls:
- After create: `toast({ title: 'Role created', description: newRole.name, variant: 'success' })`
- After delete: `toast({ title: 'Role deleted', description: deleteTarget.name, variant: 'warning' })`
- [ ] **Step 4: Commit**
```bash
git add src/pages/Admin/UserManagement/UsersTab.tsx src/pages/Admin/UserManagement/GroupsTab.tsx src/pages/Admin/UserManagement/RolesTab.tsx
git commit -m "feat: add toast notifications to all RBAC mutations"
```
---
### Task 7: Rework user creation form + password management
**Files:**
- Modify: `src/pages/Admin/UserManagement/UsersTab.tsx`
This is the largest single task. It covers spec items 3.2 (provider-aware create form), 3.3 (password management in detail pane), and 3.6 (remove unused password field).
- [ ] **Step 1: Rework the create form section**
Replace the create form state variables (lines 23-26):
```tsx
const [newUsername, setNewUsername] = useState('')
const [newDisplay, setNewDisplay] = useState('')
const [newEmail, setNewEmail] = useState('')
const [newPassword, setNewPassword] = useState('')
```
with:
```tsx
const [newUsername, setNewUsername] = useState('')
const [newDisplay, setNewDisplay] = useState('')
const [newEmail, setNewEmail] = useState('')
const [newPassword, setNewPassword] = useState('')
const [newProvider, setNewProvider] = useState<'local' | 'oidc'>('local')
```
Add imports for RadioGroup, RadioItem, and InfoCallout:
```tsx
import { RadioGroup, RadioItem } from '../../../design-system/primitives/Radio/Radio'
import { InfoCallout } from '../../../design-system/primitives/InfoCallout/InfoCallout'
```
Update `handleCreate` to use the provider selection and validate password for local:
```tsx
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)
toast({ title: 'User created', description: newUser.displayName, variant: 'success' })
}
```
Replace the create form JSX (lines 100-114) with:
```tsx
{creating && (
)}
```
- [ ] **Step 2: Add Security section to detail pane**
Add password reset state at the top of the component:
```tsx
const [resettingPassword, setResettingPassword] = useState(false)
const [newPw, setNewPw] = useState('')
```
Add the Security section after the metadata grid (after the `
` closing the `.metaGrid`), before the "Group membership" SectionHeader:
```tsx
{selected.provider === 'local' ? (
<>
Password
••••••••
{!resettingPassword && (
{ setResettingPassword(true); setNewPw('') }}>
Reset password
)}
{resettingPassword && (
setNewPw(e.target.value)}
className={styles.resetInput}
/>
setResettingPassword(false)}>Cancel
{ setResettingPassword(false); toast({ title: 'Password updated', description: selected.username, variant: 'success' }) }}
disabled={!newPw.trim()}
>
Set
)}
>
) : (
<>
Authentication
OIDC ({selected.provider})
Password managed by the identity provider.
>
)}
```
- [ ] **Step 3: Verify build**
Run: `npx vite build 2>&1 | tail -5`
Expected: Build succeeds
- [ ] **Step 4: Commit**
```bash
git add src/pages/Admin/UserManagement/UsersTab.tsx
git commit -m "feat: rework user creation with provider selection, add password management"
```
---
### Task 8: Keyboard accessibility for entity lists
**Files:**
- Modify: `src/pages/Admin/UserManagement/UsersTab.tsx`
- Modify: `src/pages/Admin/UserManagement/GroupsTab.tsx`
- Modify: `src/pages/Admin/UserManagement/RolesTab.tsx`
- [ ] **Step 1: Add keyboard support to UsersTab entity list**
On the `.entityList` wrapper div, add:
```tsx
```
On each `.entityItem` div, add `role`, `tabIndex`, `aria-selected`, and `onKeyDown`:
```tsx
setSelectedId(user.id)}
role="option"
tabIndex={0}
aria-selected={selectedId === user.id}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedId(user.id) } }}
>
```
Add empty-search state after the list map:
```tsx
{filtered.length === 0 && (
No users match your search
)}
```
- [ ] **Step 2: Add keyboard support to GroupsTab entity list**
Same pattern — add `role="listbox"` to container, `role="option"` + `tabIndex={0}` + `aria-selected` + `onKeyDown` to items, and empty-search state.
- [ ] **Step 3: Add keyboard support to RolesTab entity list**
Same pattern. Also replace the lock emoji on line 113:
```tsx
{role.system &&
🔒 }
```
with:
```tsx
{role.system &&
}
```
Add empty-search state.
- [ ] **Step 4: Commit**
```bash
git add src/pages/Admin/UserManagement/UsersTab.tsx src/pages/Admin/UserManagement/GroupsTab.tsx src/pages/Admin/UserManagement/RolesTab.tsx
git commit -m "feat: add keyboard accessibility and empty states to entity lists"
```
---
### Task 9: Add confirmation for cascading removals
**Files:**
- Modify: `src/pages/Admin/UserManagement/UsersTab.tsx`
- Modify: `src/pages/Admin/UserManagement/GroupsTab.tsx`
- [ ] **Step 1: Add group removal confirmation in UsersTab**
Add state for tracking the removal target:
```tsx
const [removeGroupTarget, setRemoveGroupTarget] = useState
(null)
```
Replace the direct `onRemove` on group Tags (in the "Group membership" section) with:
```tsx
onRemove={() => {
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' })
}
}}
```
Add an AlertDialog (import from composites) for the confirmation:
```tsx
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"
/>
```
Add import: `import { AlertDialog } from '../../../design-system/composites/AlertDialog/AlertDialog'`
- [ ] **Step 2: Add role removal confirmation in GroupsTab**
Add state:
```tsx
const [removeRoleTarget, setRemoveRoleTarget] = useState(null)
```
Replace direct `onRemove` on role Tags with:
```tsx
onRemove={() => {
const memberCount = MOCK_USERS.filter((u) => u.directGroups.includes(selected.id)).length
if (memberCount > 0) {
setRemoveRoleTarget(r)
} else {
updateGroup(selected.id, { directRoles: selected.directRoles.filter((role) => role !== r) })
toast({ title: 'Role removed', variant: 'success' })
}
}}
```
Add AlertDialog:
```tsx
setRemoveRoleTarget(null)}
onConfirm={() => {
if (removeRoleTarget && selected) {
updateGroup(selected.id, { directRoles: selected.directRoles.filter((role) => role !== removeRoleTarget) })
toast({ title: 'Role removed', variant: 'success' })
}
setRemoveRoleTarget(null)
}}
title="Remove role from group"
description={`Removing ${removeRoleTarget} from ${selected?.name} will affect ${members.length} member(s) who inherit this role. Continue?`}
confirmLabel="Remove"
variant="warning"
/>
```
Add import: `import { AlertDialog } from '../../../design-system/composites/AlertDialog/AlertDialog'`
- [ ] **Step 3: Commit**
```bash
git add src/pages/Admin/UserManagement/UsersTab.tsx src/pages/Admin/UserManagement/GroupsTab.tsx
git commit -m "feat: add confirmation dialogs for cascading removals"
```
---
### Task 10: Add duplicate name validation to create forms
**Files:**
- Modify: `src/pages/Admin/UserManagement/UsersTab.tsx`
- Modify: `src/pages/Admin/UserManagement/GroupsTab.tsx`
- Modify: `src/pages/Admin/UserManagement/RolesTab.tsx`
- [ ] **Step 1: Add duplicate check in UsersTab**
Add a computed `duplicateUsername`:
```tsx
const duplicateUsername = newUsername.trim() && users.some((u) => u.username.toLowerCase() === newUsername.trim().toLowerCase())
```
Update the Create button `disabled` to include `|| duplicateUsername`.
Show error text below the username Input when duplicate:
```tsx
{duplicateUsername && Username already exists }
```
- [ ] **Step 2: Add duplicate check in GroupsTab**
Similar pattern with `duplicateGroupName` check. Disable Create button when duplicate.
- [ ] **Step 3: Add duplicate check in RolesTab**
Similar pattern with `duplicateRoleName` check (compare uppercase). Disable Create button when duplicate.
- [ ] **Step 4: Commit**
```bash
git add src/pages/Admin/UserManagement/UsersTab.tsx src/pages/Admin/UserManagement/GroupsTab.tsx src/pages/Admin/UserManagement/RolesTab.tsx
git commit -m "feat: add duplicate name validation to create forms"
```
---
### Task 11: Final verification
- [ ] **Step 1: Run full test suite**
Run: `npx vitest run`
Expected: All tests pass
- [ ] **Step 2: Build the project**
Run: `npx vite build`
Expected: Build succeeds
- [ ] **Step 3: Fix any issues**
If build fails, fix TypeScript errors. Common issues:
- Import path typos
- Missing props on components
- InfoCallout `variant` prop — check the actual prop name (may be `color` instead)
- [ ] **Step 4: Commit fixes if needed**
```bash
git add -A
git commit -m "fix: resolve build issues from admin redesign"
```