// ============================================================================ // Users management page (PR-S.17c · admin-only) // ============================================================================ // // Admin-only:列所有 user,提供 role toggle(is_admin)跟 ban/unban 按钮。 // 进入流程: // 1. mount → GET /v1/auth/me 拿 is_admin // · 非 admin → 渲染 "Forbidden — 需要 admin 权限" 占位 + 5s 后跳回 /playground // · is_admin=true → 进真页 // 2. GET /v1/users 拉表 // 3. 每行 actions: // · Toggle admin: PATCH /v1/users/{id} { is_admin: !current } // · Ban / Unban: PATCH /v1/users/{id} { status: 'banned' | 'active' } // 4. Self-row 标 "(you)",所有 action 钮 disabled —— backend 也 enforce // self_modification_blocked 422,前端先挡免得 round-trip // // 设计跟 manage-app.jsx 视觉对齐(topbar + brand + tabs),不用单独 css。 const { useState, useEffect } = React; function UsersApp() { const [me, setMe] = useState(null); // null = loading; obj = loaded const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [busyUserId, setBusyUserId] = useState(null); // 防双击,行级 disable // Step 1: load /v1/auth/me first to gate useEffect(() => { (async () => { try { const m = await window.api.authMe(); setMe(m); } catch (err) { setError(`auth check failed: ${err.message || err}`); setLoading(false); } })(); }, []); // Step 2: 拿到 me 之后才拉 users(否则 admin 才 fetch,避免给 non-admin 一次 403) useEffect(() => { if (!me || !me.is_admin) { setLoading(false); return; } (async () => { try { const items = await window.api.listUsers(); setUsers(items); } catch (err) { setError(`load users failed: ${err.message || err}`); } finally { setLoading(false); } })(); }, [me]); async function patchUser(userId, patch) { setBusyUserId(userId); try { const updated = await window.api.updateUser(userId, patch); // 用 updated 替换该行,保证 UI 跟后端同步 setUsers((prev) => prev.map((u) => (u.user_id === userId ? { ...u, ...updated } : u)) ); } catch (err) { alert(`update failed: ${err.message || err}`); } finally { setBusyUserId(null); } } // -------- 非 admin / loading / error 占位 -------- if (loading) { return (
This page is admin-only.
{me && (you are signed in as {me.user_id} {me.is_admin ? "" : " (not admin)"}
)}{users.length} registered. Toggle admin role or ban/unban below. You can't modify your own row — ask another admin.
| User ID | Created | Role | Status | Actions | |
|---|---|---|---|---|---|
| {u.email} {isSelf && ( you )} | {u.user_id} | {u.created_at ? new Date(u.created_at * 1000).toLocaleString() : "—"} |
|
|