Files
Epicnabbo-Catalogus-Updated…/src/app/mod/layout.tsx
T

134 lines
4.9 KiB
TypeScript

import { Ban, Gavel, LogOut, Radio, Shield, Ticket, Users } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import type { ReactNode } from "react";
import { LanguageSwitcher } from "@/components/language-switcher";
import { ThemeSwitcher } from "@/components/theme-switcher";
import { requireMod } from "@/lib/admin/guard";
import { setCsrfCookie } from "@/lib/foundation/security";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
export const dynamic = "force-dynamic";
export default async function ModLayout({ children }: { children: ReactNode }) {
const staff = await requireMod();
let csrfToken = "";
try {
csrfToken = await setCsrfCookie();
} catch {
csrfToken = "";
}
if (await siteSettings.getBool("force_staff_2fa", false)) {
const u = await prisma.user
.findUnique({
where: { id: staff.id },
select: { twoFactorConfirmedAt: true },
})
.catch(() => null);
if (!u?.twoFactorConfirmedAt) redirect("/settings/2fa?error=staffrequired");
}
const t = await getTranslations("pages.mod");
const { permissions } = await getAdminContext();
const showCfh =
canAccess(permissions, PERMS.MOD_CFH_VIEW, staff.rank) ||
canAccess(permissions, PERMS.MODERATION_VIEW, staff.rank);
const showActions =
canAccess(permissions, PERMS.MOD_ACTIONS, staff.rank) ||
canAccess(permissions, PERMS.MODERATION_EDIT, staff.rank);
const showBans =
canAccess(permissions, PERMS.MOD_BANS_VIEW, staff.rank) ||
canAccess(permissions, PERMS.BANS_VIEW, staff.rank);
const showTickets =
canAccess(permissions, PERMS.MOD_TICKETS_VIEW, staff.rank) ||
canAccess(permissions, PERMS.TICKETS_VIEW, staff.rank);
const showTeam =
canAccess(permissions, PERMS.MOD_TEAM_VIEW, staff.rank) ||
canAccess(permissions, PERMS.MODERATION_VIEW, staff.rank);
const showAdmin = canAccess(permissions, PERMS.ADMIN_DASHBOARD, staff.rank);
const nav = [
{ href: "/mod", label: t("nav.overview"), icon: Shield, show: true },
{ href: "/mod/cfh", label: t("nav.cfh"), icon: Radio, show: showCfh },
{
href: "/mod/tickets",
label: t("nav.tickets"),
icon: Ticket,
show: showTickets,
},
{
href: "/mod/actions",
label: t("nav.actions"),
icon: Gavel,
show: showActions,
},
{ href: "/mod/bans", label: t("nav.bans"), icon: Ban, show: showBans },
{ href: "/mod/team", label: t("nav.team"), icon: Users, show: showTeam },
].filter((item) => item.show);
return (
<>
{csrfToken ? <meta name="csrf-token" content={csrfToken} /> : null}
<div
data-admin
className="min-h-screen"
style={{ backgroundColor: "var(--admin-canvas)" }}
>
<header className="border-b border-[var(--admin-border)] bg-[var(--admin-sidebar-background)]">
<div className="mx-auto flex max-w-6xl flex-wrap items-center gap-3 px-4 py-3">
<div className="min-w-0 flex-1">
<p className="m-0 text-xs font-bold uppercase tracking-widest text-[var(--admin-sidebar-muted-readable)]">
{t("badge")}
</p>
<h1 className="m-0 truncate text-lg font-semibold text-[var(--admin-sidebar-text-readable)]">
{t("title")}
</h1>
<p className="m-0 text-xs text-[var(--admin-sidebar-muted-readable)]">
{staff.username} · rank {staff.rank}
</p>
</div>
<div className="flex items-center gap-1.5">
<LanguageSwitcher variant="admin" />
<ThemeSwitcher variant="admin" />
{showAdmin ? (
<Link
href="/admin"
className="rounded-lg px-2.5 py-1.5 text-xs font-medium text-[var(--admin-sidebar-muted-readable)] no-underline hover:bg-[var(--admin-accent)]/10 hover:text-[var(--admin-sidebar-text-readable)]"
>
{t("nav.fullAdmin")}
</Link>
) : null}
<Link
href="/"
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-[var(--admin-sidebar-muted-readable)] no-underline hover:bg-[var(--admin-accent)]/10 hover:text-[var(--admin-sidebar-text-readable)]"
>
<LogOut size={14} />
{t("nav.backToSite")}
</Link>
</div>
</div>
<nav className="mx-auto flex max-w-6xl gap-1 overflow-x-auto px-4 pb-3">
{nav.map((item) => {
const Icon = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-[var(--admin-sidebar-muted-readable)] no-underline hover:bg-[var(--admin-accent)]/10 hover:text-[var(--admin-sidebar-text-readable)]"
>
<Icon size={14} />
{item.label}
</Link>
);
})}
</nav>
</header>
<main className="mx-auto max-w-6xl p-4 sm:p-6">{children}</main>
</div>
</>
);
}