Files
erika/app/(ui)/admin/layout.tsx
T
2026-08-27 14:11:20 +00:00

148 lines
4.8 KiB
TypeScript

import { Suspense } from "react";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import {
Activity,
Database,
MessageCircle,
Music2,
RadioTower,
} from "lucide-react";
import { AdminShellServer } from "@/components/admin-shell-server";
import { Skeleton } from "@/components/ui/skeleton";
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { getAdminDiscordIds, getSessionDiscordId } from "@/lib/auth/auth";
import { authOptions } from "@/lib/auth/auth-options";
import { getAdminServiceHealth } from "@/lib/admin/service-health";
import { RobloxIcon, YoutubeIcon } from "@/lib/icons";
import { cn } from "@/lib/utils";
const iconByService = {
database: Database,
redis: RadioTower,
youtube: YoutubeIcon,
tiktok: Music2,
discord: MessageCircle,
roblox: RobloxIcon,
} as const;
async function AdminHealthStatus() {
const snapshot = await getAdminServiceHealth();
const unavailable = snapshot.services.filter(
(service) => service.status === "unavailable",
).length;
const summary = unavailable
? `${unavailable} ${unavailable === 1 ? "service" : "services"} unavailable`
: "All systems operational";
return (
<SidebarGroup className="pb-0">
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" tooltip={summary} aria-label={summary}>
<Activity
className={cn(
"hidden group-data-[collapsible=icon]:block",
unavailable ? "text-destructive" : "text-emerald-500",
)}
/>
<div className="flex min-w-0 flex-1 items-center justify-between gap-2 group-data-[collapsible=icon]:hidden">
<div className="flex flex-col gap-1">
{(["core", "integration"] as const).map((category) => (
<div key={category} className="flex items-center gap-1">
{snapshot.services
.filter((service) => service.category === category)
.map((service) => {
const Icon =
iconByService[
service.id as keyof typeof iconByService
];
return (
<Tooltip key={service.id}>
<TooltipTrigger asChild>
<span
className={cn(
"flex [&>svg]:shrink-0",
category === "core"
? "[&>svg]:size-5"
: "[&>svg]:size-4",
service.status === "operational"
? "text-emerald-500"
: "text-destructive",
)}
aria-label={`${service.label}: ${service.status}`}
>
<Icon />
</span>
</TooltipTrigger>
<TooltipContent side="bottom">
{service.label}: {service.status}
</TooltipContent>
</Tooltip>
);
})}
</div>
))}
</div>
<span className="truncate text-xs">{summary}</span>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
);
}
function AdminHealthStatusSkeleton() {
return (
<SidebarGroup className="pb-0">
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem className="flex h-12 items-center gap-2 px-2">
<Skeleton className="size-4" />
<Skeleton className="h-3 flex-1" />
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
);
}
export default async function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(authOptions);
const discordId = getSessionDiscordId(session);
if (!discordId || !getAdminDiscordIds().includes(discordId)) {
redirect("/");
}
return (
<AdminShellServer
healthStatus={
<Suspense fallback={<AdminHealthStatusSkeleton />}>
<AdminHealthStatus />
</Suspense>
}
>
{children}
</AdminShellServer>
);
}