feat(mod): lite /mod panel + clarify ACL vs housekeeping legacy
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m31s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-22 21:29:32 +02:00
1 parent a384c9a8bb
commit 084cca6ea4
18 files changed
+931 -77

No files matched your search

+16
View File
@@ -106,6 +106,22 @@ export default async function AdminHousekeeping({
return (
<main>
<div
className="mb-4 rounded-lg border border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)] px-4 py-3 text-sm text-[var(--admin-text)]"
role="status"
>
<p className="m-0 font-semibold">{t("legacyBannerTitle")}</p>
<p className="m-0 mt-1 text-[var(--admin-text-muted)]">
{t("legacyBannerBody")}{" "}
<Link
href="/admin/permissions"
className="font-medium text-[var(--admin-accent)] underline-offset-2 hover:underline"
>
{t("legacyBannerCta")}
</Link>
</p>
</div>
<div className="admin-tabs mb-4">
<Link
href="/admin/housekeeping?tab=overview"
@@ -74,6 +74,8 @@ export function CfhDetail({
canEdit,
// biome-ignore lint/correctness/noUnusedFunctionParameters: part of the public component API
currentUserId,
listHref = "/admin/moderation/cfh",
userLinkMode = "admin",
}: {
ticket: {
id: number;
@@ -91,6 +93,8 @@ export function CfhDetail({
banHistory: { count: number; recent: BanRecord[] };
canEdit: boolean;
currentUserId: number;
listHref?: string;
userLinkMode?: "admin" | "public";
}) {
const { run, isPending } = useServerAction();
const [alertMsg, setAlertMsg] = useState("");
@@ -98,11 +102,18 @@ export function CfhDetail({
const state = CFH_STATES[ticket.state] ?? CFH_STATES[0];
function userHref(user: UserInfo) {
if (userLinkMode === "public") {
return `/u/${encodeURIComponent(user.username)}`;
}
return `/admin/users/show/${user.id}`;
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-start gap-4">
<Link href="/admin/moderation/cfh">
<Link href={listHref}>
<Button variant="ghost" size="icon">
<ArrowLeft className="h-4 w-4" />
</Button>
@@ -155,7 +166,7 @@ export function CfhDetail({
<CardContent>
{sender ? (
<Link
href={`/admin/users/show/${sender.id}`}
href={userHref(sender)}
className="flex items-center gap-3 hover:text-primary no-underline"
>
<User className="h-4 w-4" />
@@ -184,7 +195,7 @@ export function CfhDetail({
{reported ? (
<div>
<Link
href={`/admin/users/show/${reported.id}`}
href={userHref(reported)}
className="flex items-center gap-3 hover:text-primary no-underline"
>
<AlertTriangle className="h-4 w-4 text-[var(--admin-error)]" />
@@ -400,7 +411,7 @@ export function CfhDetail({
<CardContent>
{moderator ? (
<Link
href={`/admin/users/show/${moderator.id}`}
href={userHref(moderator)}
className="flex items-center gap-2 text-sm hover:text-primary no-underline"
>
<Shield className="h-3.5 w-3.5 text-primary" />
+21 -4
View File
@@ -20,9 +20,26 @@ export interface CfhRow {
stateLabel: string;
}
export function CfhTable({ data }: { data: PaginatedResult<CfhRow> }) {
export function CfhTable({
data,
basePath = "/admin/moderation/cfh",
userLinkMode = "admin",
}: {
data: PaginatedResult<CfhRow>;
/** Detail URL prefix (no trailing slash). */
basePath?: string;
/** Admin user show vs public profile. */
userLinkMode?: "admin" | "public";
}) {
const t = useTranslations("pages.admin.cfh");
function userHref(id: number, username: string) {
if (userLinkMode === "public" && username && username !== "—") {
return `/u/${encodeURIComponent(username)}`;
}
return `/admin/users/${id}`;
}
const columns: DataTableColumn<CfhRow>[] = [
{ key: "id", label: t("colId"), sortable: true },
{
@@ -32,7 +49,7 @@ export function CfhTable({ data }: { data: PaginatedResult<CfhRow> }) {
render: (_value, row) => (
<div className="min-w-0">
<Link
href={`/admin/moderation/cfh/${row.id}`}
href={`${basePath}/${row.id}`}
className="text-sm font-medium truncate block hover:underline"
>
{row.issue || t("noDescription")}
@@ -50,7 +67,7 @@ export function CfhTable({ data }: { data: PaginatedResult<CfhRow> }) {
render: (_value, row) =>
row.senderId ? (
<Link
href={`/admin/users/${row.senderId}`}
href={userHref(row.senderId, row.sender)}
className="hover:underline"
>
{row.sender}
@@ -66,7 +83,7 @@ export function CfhTable({ data }: { data: PaginatedResult<CfhRow> }) {
render: (_value, row) =>
row.reportedId ? (
<Link
href={`/admin/users/${row.reportedId}`}
href={userHref(row.reportedId, row.reported)}
className="font-medium hover:underline"
>
{row.reported}
@@ -98,6 +98,17 @@ export function PermissionsList({ ranks: initialRanks }: Props) {
return (
<div className="space-y-6">
<div
className="rounded-lg border border-[var(--admin-info-border)] bg-[var(--admin-info-subtle)] px-4 py-3 text-sm text-[var(--admin-text)]"
role="status"
>
<p className="m-0 font-semibold">Live ACL</p>
<p className="m-0 mt-1 text-muted-foreground">
Rank ACL here controls real admin access. The Housekeeping table is
legacy reference only and does not gate routes.
</p>
</div>
{/* Header */}
<div className="flex items-center justify-between">
<p className="text-muted-foreground">