34 lines
3.0 KiB
TypeScript
34 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { FormEvent, useEffect, useState } from "react";
|
|
|
|
type AdminUser = { userId: string | null; email: string | null; createdAt: string | null; source: "env" | "database" };
|
|
|
|
async function loadUsers(): Promise<AdminUser[]> {
|
|
const response = await fetch("/api/admin/users", { cache: "no-store" });
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok) throw new Error(payload?.error || "暂时无法读取管理员列表");
|
|
return payload.users;
|
|
}
|
|
|
|
export default function AdminUsersPage() {
|
|
const [users, setUsers] = useState<AdminUser[]>([]);
|
|
const [email, setEmail] = useState("");
|
|
const [error, setError] = useState("");
|
|
useEffect(() => { void loadUsers().then(setUsers).catch((caught) => setError(caught.message)); }, []);
|
|
async function add(event: FormEvent) {
|
|
event.preventDefault(); setError("");
|
|
const response = await fetch("/api/admin/users", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email }) });
|
|
const payload = await response.json().catch(() => null);
|
|
if (!response.ok) { setError(payload?.error || "添加失败"); return; }
|
|
setEmail(""); setUsers(await loadUsers());
|
|
}
|
|
async function revoke(userId: string) {
|
|
const response = await fetch("/api/admin/users", { method: "DELETE", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId }) });
|
|
if (!response.ok) { const payload = await response.json().catch(() => null); setError(payload?.error || "撤销失败"); return; }
|
|
setUsers(await loadUsers());
|
|
}
|
|
return <main className="standalone-page admin-page"><header className="admin-header"><h1>管理员管理</h1><Link className="button-secondary" href="/admin/codes">兑换码管理</Link></header><div className="admin-scroll"><section className="admin-section"><div className="section-title"><div><h2>添加管理员</h2><p>仅能添加已经注册的 Supabase 用户。</p></div></div><form className="code-form" onSubmit={add}><label className="note-field"><span>邮箱</span><input type="email" required value={email} onChange={(event) => setEmail(event.target.value)} /></label><button className="button-primary" type="submit">添加</button></form>{error && <p className="form-error" role="alert">{error}</p>}</section><section className="admin-section"><div className="section-title"><div><h2>当前管理员</h2><p>{users.length} 位</p></div></div><div className="admin-table-wrap"><table className="admin-table"><thead><tr><th>邮箱</th><th>来源</th><th>添加时间</th><th>操作</th></tr></thead><tbody>{users.map((user) => <tr key={`${user.source}-${user.userId ?? user.email}`}><td>{user.email || "—"}</td><td>{user.source === "env" ? "环境配置" : "后台配置"}</td><td>{user.createdAt ? new Date(user.createdAt).toLocaleString("zh-CN") : "—"}</td><td>{user.userId && <button className="button-secondary" type="button" onClick={() => void revoke(user.userId!)}>撤销</button>}</td></tr>)}</tbody></table></div></section></div></main>;
|
|
}
|