Merge GitHub upstream into Gitea primary

This commit is contained in:
Jesse_Chen
2026-08-05 11:13:34 +08:00
105 changed files with 3612 additions and 886 deletions
+11 -1
View File
@@ -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>;
}
+5
View File
@@ -0,0 +1,5 @@
import { PackageManagement } from "@/components/admin/package-management";
export default function AdminPackagesPage() {
return <PackageManagement />;
}
+5
View File
@@ -0,0 +1,5 @@
import PaymentManagement from "@/components/admin/payment-management";
export default function AdminPaymentsPage() {
return <PaymentManagement />;
}
+18 -5
View File
@@ -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;
}
}
+33
View File
@@ -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>;
}