Merge GitHub upstream into Gitea primary
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
import "@refinedev/antd/dist/reset.css";
|
||||
import "antd/dist/reset.css";
|
||||
import type { ReactNode } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { AdminApp } from "@/components/admin/admin-app";
|
||||
import { AdminAuthorizationError, requireAdminSession } from "@/lib/admin/auth";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function AdminLayout({ children }: { children: ReactNode }) {
|
||||
export default async function AdminLayout({ children }: { children: ReactNode }) {
|
||||
try {
|
||||
await requireAdminSession("read");
|
||||
} catch (error) {
|
||||
if (error instanceof AdminAuthorizationError) {
|
||||
redirect(error.status === 401 ? "/login" : "/");
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return <AdminApp>{children}</AdminApp>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PackageManagement } from "@/components/admin/package-management";
|
||||
|
||||
export default function AdminPackagesPage() {
|
||||
return <PackageManagement />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import PaymentManagement from "@/components/admin/payment-management";
|
||||
|
||||
export default function AdminPaymentsPage() {
|
||||
return <PaymentManagement />;
|
||||
}
|
||||
@@ -1,6 +1,19 @@
|
||||
export function GET() {
|
||||
return new Response(null, {
|
||||
status: 307,
|
||||
headers: { location: "/admin/codes" },
|
||||
});
|
||||
import { AdminAuthorizationError, requireAdminSession } from "@/lib/admin/auth";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requireAdminSession("read");
|
||||
return new Response(null, {
|
||||
status: 307,
|
||||
headers: { location: "/admin/codes" },
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AdminAuthorizationError) {
|
||||
return new Response(null, {
|
||||
status: 307,
|
||||
headers: { location: error.status === 401 ? "/login" : "/" },
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"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>;
|
||||
}
|
||||
Reference in New Issue
Block a user