docs(webui): add user-management UI redesign spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# User Management UI Redesign — Design
|
||||
|
||||
Date: 2026-08-12
|
||||
Status: Approved (brainstorming 2026-08-12)
|
||||
|
||||
## Goal
|
||||
|
||||
The admin user-management UI (`UserManagementSection.tsx`, embedded in
|
||||
`AccountDialog`) is a plain list with native `<input>`/`<select>` elements and
|
||||
`window.prompt`/`window.confirm` interactions. Redesign it in place to match
|
||||
the rest of the app's visual quality and interaction patterns.
|
||||
|
||||
Decisions locked in during brainstorming:
|
||||
|
||||
- **Scope:** in-place beautification + interaction upgrade. The section stays
|
||||
inside `AccountDialog`; no standalone admin page.
|
||||
- **Features:** unchanged — list, create, promote/demote, reset password,
|
||||
delete. No new backend capabilities.
|
||||
- **Layout:** list rows with avatar + role badge + a `⋯` actions dropdown;
|
||||
"Add user" becomes a header button that opens a dialog.
|
||||
- **i18n:** all copy goes through next-intl (zh + en), replacing the current
|
||||
hardcoded English strings.
|
||||
|
||||
## Current state
|
||||
|
||||
`src/app/components/UserManagementSection.tsx` renders:
|
||||
|
||||
- a `<ul>` of users with three small outline buttons per row (Promote/Demote,
|
||||
Password, Delete),
|
||||
- a persistent bottom form (two native inputs + native `<select>` + Create
|
||||
button) for adding users,
|
||||
- `window.prompt` for password reset and `window.confirm` for deletion,
|
||||
- hardcoded English copy despite the app shipping zh/en i18n.
|
||||
|
||||
Available shadcn/ui primitives in `src/components/ui/`: `button`, `dialog`,
|
||||
`dropdown-menu`, `input`, `label`, `select`, `skeleton`, `scroll-area` —
|
||||
everything needed; no new dependencies.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Reworked `UserManagementSection`
|
||||
|
||||
Same file, same props (`{ self: string }`), same fetch logic and API calls.
|
||||
|
||||
**Header row:** section title + user count (`3 users`), and a primary-size-sm
|
||||
"Add user" button on the right that opens the create dialog.
|
||||
|
||||
**User rows** replace the `<ul>` styling:
|
||||
|
||||
- Left: circular avatar (`size-8 rounded-full bg-accent`) showing the
|
||||
username's first letter, uppercased.
|
||||
- Middle: username (`font-medium truncate`), a role badge, and a `(you)`
|
||||
marker for the self row. Badges: admin = `bg-primary/10 text-primary`,
|
||||
user = `bg-accent text-muted-foreground`; shared badge markup extracted to a
|
||||
small `RoleBadge` inline component.
|
||||
- Right: `DropdownMenu` triggered by a ghost icon button (`⋯`,
|
||||
`aria-label` = "Actions for {username}") with items:
|
||||
- "Make admin" / "Make user" (label depends on current role),
|
||||
- "Reset password",
|
||||
- "Delete" — `text-destructive`, separated by `DropdownMenuSeparator`,
|
||||
hidden on the self row.
|
||||
- Rows get `hover:bg-accent/50` and consistent `px-3 py-2` rhythm.
|
||||
|
||||
**States:**
|
||||
|
||||
- Loading: three `Skeleton` rows (`size-8` circle + two bars) instead of the
|
||||
"Loading users…" text.
|
||||
- Empty: centered muted placeholder line.
|
||||
- Error: existing `role="alert"` destructive paragraph, restyled into the
|
||||
section header area.
|
||||
|
||||
**Scrolling:** the list is wrapped in the existing `scroll-area` with a max
|
||||
height so the dialog doesn't grow unbounded with many users.
|
||||
|
||||
### 2. Three dialogs (all using `dialog.tsx`)
|
||||
|
||||
Managed by local state in `UserManagementSection`:
|
||||
|
||||
- **`CreateUserDialog`** — fields: username (`input.tsx` + `label.tsx`),
|
||||
password (`type="password"`, hint "8–256 characters"), role (`select.tsx`).
|
||||
Submit disabled until username matches the backend pattern
|
||||
(`/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/`) and password ≥ 8 chars. API errors
|
||||
render inline above the footer (destructive text), not just as a toast.
|
||||
On success: toast + close + refresh.
|
||||
- **`ResetPasswordDialog`** — single password field + hint, targeted at one
|
||||
user; title includes the username. Replaces `window.prompt`.
|
||||
- **`DeleteUserDialog`** — confirmation with the username repeated in the
|
||||
body; confirm button is `variant="destructive"`. Replaces
|
||||
`window.confirm`.
|
||||
|
||||
Only one dialog is open at a time; each receives `open`, `onOpenChange`, and
|
||||
the target user (create dialog: none).
|
||||
|
||||
### 3. i18n
|
||||
|
||||
New namespace `userManagement` in
|
||||
`src/i18n/messages/{zh,en}/userManagement.ts`, registered in both `index.ts`
|
||||
files (spread like the existing modules). Keys cover: section title, user
|
||||
count (ICU plural), badges, `(you)`, menu items, all three dialogs (titles,
|
||||
descriptions, labels, hints, submit/cancel), empty/loading states, and success
|
||||
toasts. Component calls `useTranslations("userManagement")`.
|
||||
|
||||
## Data flow
|
||||
|
||||
Unchanged: `GET /api/auth/users`, `POST /api/auth/users`,
|
||||
`PATCH/DELETE /api/auth/users/[username]`. The existing `run()` helper
|
||||
(busy-flag + toast + refresh) is kept; dialogs layer on top of it and surface
|
||||
API errors inline before dismissing.
|
||||
|
||||
## Error handling
|
||||
|
||||
- Client-side validation mirrors server rules (username pattern, password
|
||||
8–256) so most errors never hit the network.
|
||||
- Server errors (e.g. last-admin guard on demote/delete) show both in the
|
||||
open dialog (inline) and via the existing `errorToast("users.manage", …)`.
|
||||
- The self row hides Delete; demoting/deleting the last admin remains a
|
||||
server-enforced rule — the UI does not try to predict it.
|
||||
|
||||
## Testing
|
||||
|
||||
Vitest + Testing Library, following existing `*.test.ts(x)` conventions:
|
||||
|
||||
- List rendering: rows, badges, self marker, Delete hidden for self.
|
||||
- Create dialog: validation gating (bad username, short password), successful
|
||||
submit calls the right endpoint and closes.
|
||||
- Menu actions: promote/demote label flips with role; reset password and
|
||||
delete open their dialogs with the right username.
|
||||
- Delete flow: cancel does nothing; confirm sends DELETE and refreshes.
|
||||
- Loading skeleton and empty states render.
|
||||
|
||||
Fetch is mocked at the `fetch` boundary, as in existing component tests.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Backend changes of any kind.
|
||||
- A standalone `/users` page, search/filter/pagination, avatar uploads,
|
||||
disable/enable accounts.
|
||||
- Restyling `AccountDialog` itself beyond what the section needs.
|
||||
Reference in New Issue
Block a user