feat(mod): lite /mod panel + clarify ACL vs housekeeping legacy
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
a384c9a8bb
commit
084cca6ea4
18 files changed
+931
-77
No files matched your search
@@ -0,0 +1,114 @@
|
||||
import { Ban, Gavel, Radio, Shield } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { StatsCard } from "@/components/admin/stats-card";
|
||||
import { getMinStaffRank } from "@/lib/admin/min-staff-rank";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export default async function ModDashboardPage() {
|
||||
const t = await getTranslations("pages.mod");
|
||||
const { session, permissions } = await getAdminContext();
|
||||
const rank = session.user.rank;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const todayStart = now - (now % 86400);
|
||||
const minStaffRank = await getMinStaffRank();
|
||||
|
||||
const showCfh =
|
||||
canAccess(permissions, PERMS.MOD_CFH_VIEW, rank) ||
|
||||
canAccess(permissions, PERMS.MODERATION_VIEW, rank);
|
||||
const showActions =
|
||||
canAccess(permissions, PERMS.MOD_ACTIONS, rank) ||
|
||||
canAccess(permissions, PERMS.MODERATION_EDIT, rank);
|
||||
const showBans =
|
||||
canAccess(permissions, PERMS.MOD_BANS_VIEW, rank) ||
|
||||
canAccess(permissions, PERMS.BANS_VIEW, rank);
|
||||
|
||||
const [openCfh, todayBans, modsOnline] = await Promise.all([
|
||||
showCfh
|
||||
? prisma.supportTickets.count({ where: { state: 0 } }).catch(() => 0)
|
||||
: Promise.resolve(0),
|
||||
showBans
|
||||
? prisma.ban
|
||||
.count({ where: { timestamp: { gte: todayStart } } })
|
||||
.catch(() => 0)
|
||||
: Promise.resolve(0),
|
||||
prisma.user
|
||||
.count({ where: { online: "1", rank: { gte: minStaffRank } } })
|
||||
.catch(() => 0),
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="admin-section-title m-0">{t("overviewTitle")}</h2>
|
||||
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
|
||||
{t("overviewSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{showCfh ? (
|
||||
<StatsCard
|
||||
title={t("stats.openCfh")}
|
||||
value={openCfh}
|
||||
icon={<Radio className="h-4 w-4" />}
|
||||
href="/mod/cfh"
|
||||
/>
|
||||
) : null}
|
||||
{showBans ? (
|
||||
<StatsCard
|
||||
title={t("stats.bansToday")}
|
||||
value={todayBans}
|
||||
icon={<Ban className="h-4 w-4" />}
|
||||
href="/mod/bans"
|
||||
/>
|
||||
) : null}
|
||||
<StatsCard
|
||||
title={t("stats.modsOnline")}
|
||||
value={modsOnline}
|
||||
icon={<Shield className="h-4 w-4" />}
|
||||
/>
|
||||
{showActions ? (
|
||||
<StatsCard
|
||||
title={t("stats.quickActions")}
|
||||
value="→"
|
||||
description={t("stats.quickActionsHint")}
|
||||
icon={<Gavel className="h-4 w-4" />}
|
||||
href="/mod/actions"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<section className="admin-card space-y-2 p-4">
|
||||
<h3 className="m-0 text-sm font-semibold">{t("shortcutsTitle")}</h3>
|
||||
<ul className="m-0 list-none space-y-1 p-0 text-sm">
|
||||
{showCfh ? (
|
||||
<li>
|
||||
<Link href="/mod/cfh" className="text-primary hover:underline">
|
||||
{t("nav.cfh")}
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
{showActions ? (
|
||||
<li>
|
||||
<Link
|
||||
href="/mod/actions"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t("nav.actions")}
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
{showBans ? (
|
||||
<li>
|
||||
<Link href="/mod/bans" className="text-primary hover:underline">
|
||||
{t("nav.bans")}
|
||||
</Link>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user