import {
Award,
Ban,
Bell,
Calendar,
ClipboardList,
Cog,
Compass,
CreditCard,
DoorOpen,
FileText,
Filter,
Gem,
Globe,
Group,
HelpCircle,
History,
Image,
Key,
Languages,
LayoutDashboard,
LogOut,
Mail,
Megaphone,
MessageSquare,
Monitor,
Music,
Newspaper,
Palette,
Radio,
Receipt,
Settings,
Shield,
ShoppingCart,
SquarePen,
Store,
Tags,
Terminal,
Ticket,
Trophy,
Upload,
Users,
Wrench,
} 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 { AdminMobileWrapper } from "@/components/admin/admin-mobile-wrapper";
import { AdminNavLink } from "@/components/admin/admin-nav-link";
import { requireStaff } from "@/lib/admin/guard";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
export const dynamic = "force-dynamic";
function getNavGroups(t: (key: string) => string) {
return [
{
label: t("overview"),
icon: ,
items: [
{
href: "/admin",
label: t("dashboard"),
icon: ,
},
],
},
{
label: t("content"),
icon: ,
items: [
{
href: "/admin/articles",
label: t("articles"),
icon: ,
},
{ href: "/admin/tags", label: t("tags"), icon: },
{
href: "/admin/navigation",
label: t("navigator"),
icon: ,
},
{
href: "/admin/help-questions",
label: t("helpCenter"),
icon: ,
},
{
href: "/admin/ads",
label: t("advertisements"),
icon: ,
},
{
href: "/admin/writeable-boxes",
label: t("writeableBoxes"),
icon: ,
},
{
href: "/admin/photos",
label: t("photos"),
icon: ,
},
{
href: "/admin/tickets",
label: t("tickets"),
icon: ,
},
{
href: "/admin/events",
label: t("events"),
icon: ,
},
{
href: "/admin/polls",
label: t("polls"),
icon: ,
},
{ href: "/admin/banners", label: t("banners"), icon: },
{
href: "/admin/prefixes",
label: t("prefixes"),
icon: ,
},
],
},
{
label: t("usersAndAccess"),
icon: ,
items: [
{ href: "/admin/users", label: t("users"), icon: },
{ href: "/admin/bans", label: t("bans"), icon: },
{
href: "/admin/ip",
label: t("ipManagement"),
icon: ,
},
{ href: "/admin/vpn", label: t("vpn"), icon: },
{
href: "/admin/applications",
label: t("applications"),
icon: ,
},
{ href: "/admin/teams", label: t("teams"), icon: },
{
href: "/admin/permissions",
label: t("permissions"),
icon: ,
},
{
href: "/admin/online",
label: t("onlineUsers"),
icon: ,
},
{
href: "/admin/moderation",
label: t("moderation"),
icon: ,
},
],
},
{
label: t("economy"),
icon: ,
items: [
{
href: "/admin/catalog",
label: t("catalog"),
icon: ,
},
{
href: "/admin/shop",
label: t("shop"),
icon: ,
},
{
href: "/admin/transactions",
label: t("transactions"),
icon: ,
},
{
href: "/admin/vouchers",
label: t("vouchers"),
icon: ,
},
{
href: "/admin/subscriptions",
label: t("subscriptions"),
icon: ,
},
{
href: "/admin/rare-values",
label: t("rareValues"),
icon: ,
},
{
href: "/admin/badges",
label: t("badges"),
icon: ,
},
{
href: "/admin/achievements",
label: t("achievements"),
icon: ,
},
{
href: "/admin/calendar",
label: t("calendar"),
icon: ,
},
],
},
{
label: t("radio"),
icon: ,
items: [
{ href: "/admin/radio", label: t("radio"), icon: },
{
href: "/admin/radio/history",
label: t("radioHistory"),
icon: ,
},
{
href: "/admin/radio/banners",
label: t("radioBanners"),
icon: ,
},
{
href: "/admin/radio/embed",
label: t("radioEmbed"),
icon: ,
},
{
href: "/admin/radio/api-keys",
label: t("radioApiKeys"),
icon: ,
},
{
href: "/admin/radio/moderation",
label: t("radioModeration"),
icon: ,
},
{
href: "/admin/radio/settings",
label: t("radioSettings"),
icon: ,
},
{
href: "/admin/radio/monitoring",
label: t("radioMonitoring"),
icon: ,
},
{
href: "/admin/radio/points",
label: t("radioPoints"),
icon: ,
},
{
href: "/admin/radio/ranks",
label: t("radioRanks"),
icon: ,
},
{
href: "/admin/radio/autodj",
label: t("radioAutodj"),
icon: ,
},
],
},
{
label: t("system"),
icon: ,
items: [
{
href: "/admin/commandocentrum",
label: t("commandocentrum"),
icon: ,
},
{
href: "/admin/emulator",
label: t("emulator"),
icon: ,
},
{ href: "/admin/alerts", label: t("alerts"), icon: },
{ href: "/admin/logs", label: t("logs"), icon: },
{
href: "/admin/logs/audit",
label: t("auditLog"),
icon: ,
},
{
href: "/admin/analytics",
label: t("analytics"),
icon: ,
},
{ href: "/admin/devops", label: t("devops"), icon: },
{
href: "/admin/wordfilter",
label: t("wordFilter"),
icon: ,
},
{
href: "/admin/maintenance",
label: t("maintenance"),
icon: ,
},
{
href: "/admin/housekeeping",
label: t("housekeeping"),
icon: ,
},
{
href: "/admin/rooms",
label: t("rooms"),
icon: ,
},
{
href: "/admin/email-templates",
label: t("emailTemplates"),
icon: ,
},
{
href: "/admin/theme",
label: t("theme"),
icon: ,
},
{ href: "/admin/media", label: t("media"), icon: },
{
href: "/admin/favicon",
label: t("favicon"),
icon: ,
},
{
href: "/admin/sounds",
label: t("sounds"),
icon: ,
},
{
href: "/admin/translations",
label: t("translations"),
icon: ,
},
{
href: "/admin/import",
label: t("import"),
icon: ,
},
{
href: "/admin/settings",
label: t("settings"),
icon: ,
},
],
},
];
}
export default async function AdminLayout({
children,
}: {
children: ReactNode;
}) {
const staff = await requireStaff();
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");
}
return (
}>
);
}
async function Sidebar({
staff,
}: {
staff: { id: number; username: string; rank: number };
}) {
const t = await getTranslations("pages.admin.nav");
const initial = staff.username.charAt(0).toUpperCase();
const navGroups = getNavGroups(t);
return (
);
}
async function Topbar({
staff,
}: {
staff: { id: number; username: string; rank: number };
}) {
const t = await getTranslations("pages.admin.nav");
return (
{t("housekeeping")}
{t("hotelManagementPanel")}
{staff.username}
{t("rankLabel", { rank: staff.rank })}
);
}