import React from "react"; import "./styles.css"; import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/frontend/blocks/AppSidebar"; import { getPayload } from "payload"; import config from "@payload-config"; import { isPayloadUser } from "@/utils/access-control/isPayloadUser"; import { headers as nextHeaders } from "next/headers"; import { SiteHeader } from "@/components/frontend/SiteHeader"; import { Metadata } from "next"; import { type ResolvedLevel, resolveLevel } from "@/utils/xp/resolveLevel"; import { hasIntelligenceQualification } from "@/utils/access-control/hasIntelligenceQualification"; import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification"; import { ShipmentToasts } from "@/components/frontend/logistics/ShipmentToasts"; import { GameTickRealtime } from "@/components/frontend/realtime/GameTickRealtime"; import VersionOverlay from "@/components/static/VersionOverlay"; import { LandingPage } from "@/components/frontend/LandingPage"; import { Toaster } from "@/components/ui/sonner"; import { CommandPalette } from "@/components/command-palette/CommandPalette"; import { CommandPaletteProvider } from "@/components/command-palette/CommandPaletteContext"; import { KeyboardShortcutsProvider } from "@/components/command-palette/KeyboardShortcutsProvider"; import { isSuperuser } from "@/utils/access-control/hasPermission"; import { Rank } from "@/payload-types"; import { cookies } from "next/headers"; import { IMPERSONATION_ACTIVE_COOKIE } from "@/lib/impersonation"; import { ImpersonationBanner } from "@/components/frontend/impersonation/ImpersonationBanner"; // Skip static prerendering of this layout (and all pages under (frontend)/). // The layout calls `getPayload()` at render time to resolve the current user, // which connects to PostgreSQL. With force-dynamic, Next.js skips the // build-time prerender pass (which would try to init Payload and hit the DB) // and instead server-renders at request time. Without this, `next build` // inside a container with no reachable DATABASE_URI fails. export const dynamic = "force-dynamic"; export const metadata: Metadata = { description: "A gamified platform for Polaris Task Force, an Arma 3 unit.", title: "Polaris Task Force", icons: [ { url: "/favicon.webp", type: "image/webp", rel: "icon", }, ], }; export default async function RootLayout(props: { children: React.ReactNode }) { const { children } = props; const headers = await nextHeaders(); const payloadConfig = await config; const payload = await getPayload({ config }); const { user: authUser } = await payload.auth({ headers, canSetHeaders: false }); const user = isPayloadUser(authUser) ? authUser : null; const impersonating = (await cookies()).has(IMPERSONATION_ACTIVE_COOKIE); let xpLevel: ResolvedLevel = { levelName: "Recruit", progress: 0, currentLevelXP: 0, nextLevelXP: null, xp: 0, }; let isIntelligence = false; let isLogistics = false; let currentRank = ""; if (user) { [isIntelligence, isLogistics] = await Promise.all([ hasIntelligenceQualification(payload, user).catch(() => false), hasLogisticsQualification(payload, user).catch(() => false), ]); const [profileRes, experienceRes] = await Promise.all([ payload.find({ collection: "profiles", where: { user: { equals: user.id } }, select: { rank: true, progression: { awards: true, scoreboard: true, xp: true, minigames: true, }, }, }), payload.find({ collection: "experience", sort: "requiredPoints", limit: 100, select: { name: true, requiredPoints: true, }, }), ]); const userXp = profileRes.docs[0]?.progression?.xp ?? 0; xpLevel = resolveLevel(userXp, experienceRes.docs); currentRank = (profileRes.docs[0]?.rank as Rank | null | undefined)?.abbreviation ?? ""; } return ( {user ? ( {impersonating && } {children} ) : ( )} {(!user || user.preferences?.display?.showVersionOverlay !== false) && ( )} {user && } {isLogistics && } ); }