Files
Epicnabbo-Catalogus-Updated…/src/app/layout.tsx
T
openhands 10523e58ce Fix remaining security vulnerabilities
- H1: Add missing sanitize() to help center content rendering
- H2: Tighten CSP by removing unsafe-inline/unsafe-eval from script-src;
  move theme init to external JS file with meta tag for defaultDark
- M1: Add SSRF protection for radio API URLs (block private IPs)
- M2: Add rate limiting to SSO ticket endpoint (5 req/30s per user)
- M4: Document locale validation safety in i18n dynamic import
- L1: Truncate stacktraces in admin commandocentrum to first 20 lines
2026-07-04 19:10:43 +02:00

86 lines
3.0 KiB
TypeScript

import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";
import { Nunito, Pixelify_Sans } from "next/font/google";
import { headers } from "next/headers";
import type { ReactNode } from "react";
import { Navigation } from "@/components/navigation";
import { PwaRegister } from "@/components/pwa-register";
import RadioPlayerGate from "@/components/public/radio-player-gate";
import { SiteFooter } from "@/components/site-footer";
import { SiteHeader } from "@/components/site-header";
import { ThemeVars } from "@/components/theme-vars";
import { TopHeader } from "@/components/top-header";
import { enforceSiteAccess } from "@/lib/access-guard";
import { siteSettings } from "@/lib/services/site-settings";
import "./globals.css";
const nunito = Nunito({
subsets: ["latin"],
weight: ["400", "600", "700", "800"],
display: "swap",
variable: "--font-nunito",
});
const pixelFont = Pixelify_Sans({
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
variable: "--font-pixel",
});
export async function generateMetadata(): Promise<Metadata> {
const hotel = (await siteSettings.get("hotel_name", "Atom")) ?? "Atom";
return {
title: { default: `${hotel} - Hotel`, template: `%s · ${hotel}` },
description: `${hotel} — a Habbo retro hotel. Hang out, collect furni and meet friends.`,
};
}
export default async function RootLayout({ children }: { children: ReactNode }) {
await enforceSiteAccess();
const locale = await getLocale();
const messages = await getMessages();
// "Dusk" / dark-by-default: the site starts dark unless the visitor has picked
// light. The saved choice always wins over the default.
const defaultDark = await siteSettings.getBool("default_dark", false);
const headersList = await headers();
const pathname = headersList.get("x-pathname") || headersList.get("next-url") || "";
const isClient = pathname === "/client" || pathname.startsWith("/client/");
return (
<html lang={locale} className={`app ${nunito.variable} ${pixelFont.variable}`}>
<head>
<meta name="theme-default-dark" content={String(defaultDark)} />
<script src="/scripts/theme-init.js" />
</head>
<body
className="flex min-h-screen flex-col site-bg"
style={{ backgroundColor: "var(--color-background)" }}
>
<NextIntlClientProvider locale={locale} messages={messages}>
<ThemeVars />
{isClient ? (
children
) : (
<>
<TopHeader />
<SiteHeader />
<Navigation />
<main className="overflow-hidden">
<div className="mx-auto mt-10 grid max-w-7xl grid-cols-12 gap-x-3 gap-y-8 p-6 md:mt-0">
<div className="col-span-12">{children}</div>
</div>
</main>
<SiteFooter />
<RadioPlayerGate />
</>
)}
<PwaRegister />
</NextIntlClientProvider>
</body>
</html>
);
}