- Create ThemeSessionBridge.tsx: useEffect compares session.user.theme to
next-themes value; calls setTheme(serverTheme) on mismatch; validates
against 3-string allowlist ('light'|'dark'|'system'); renders null
- Mount <ThemeSessionBridge /> as first child of <AuthProvider> in app/layout.tsx
- Modify ThemeToggle: writeTheme() calls setTheme() then fire-and-forget
PUT /api/me/theme; silent catch for network errors (best-effort desktop UX)
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
|
import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
|
|
import "./globals.css";
|
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
import { AppNavigation } from "@/components/navigation/app-navigation";
|
|
import { CommandPalette } from "@/components/navigation/command-palette";
|
|
import { TaglineFooter } from "@/components/branding/tagline-footer";
|
|
import { Toaster } from "sonner";
|
|
import { AuthProvider } from "@/components/auth/auth-provider";
|
|
import { ThemeSessionBridge } from "@/components/mobile/profile/ThemeSessionBridge";
|
|
|
|
// IBM Plex Sans replaces the brand-mandated Helvetica/Arial. The 2013
|
|
// standards guide called for Helvetica Bold for headers and Helvetica
|
|
// Light for the tagline; Plex Sans honors the spirit (clean engineered
|
|
// sans) while loading reliably from Google Fonts. Weight 300 covers the
|
|
// "Light" usage in the tagline footer.
|
|
const plexSans = IBM_Plex_Sans({
|
|
subsets: ["latin"],
|
|
weight: ["300", "400", "500", "600", "700"],
|
|
variable: "--font-plex-sans",
|
|
});
|
|
|
|
const plexMono = IBM_Plex_Mono({
|
|
subsets: ["latin"],
|
|
weight: ["400", "500", "600"],
|
|
variable: "--font-plex-mono",
|
|
});
|
|
|
|
export const metadata: Metadata = {
|
|
title: "Pulse · Operations console",
|
|
description: "Wulf Consulting operations console — tickets, RMM, IT Glue, backups, and analytics in one place.",
|
|
manifest: "/manifest.json",
|
|
icons: {
|
|
icon: [
|
|
{ url: "/favicon.png", sizes: "any" },
|
|
{ url: "/wulff-logo.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
shortcut: "/favicon.png",
|
|
apple: "/wulff-logo.png",
|
|
},
|
|
appleWebApp: {
|
|
capable: true,
|
|
statusBarStyle: "default",
|
|
title: "Pulse",
|
|
},
|
|
};
|
|
|
|
export const viewport: Viewport = {
|
|
width: "device-width",
|
|
initialScale: 1,
|
|
viewportFit: "cover",
|
|
themeColor: [
|
|
{ media: "(prefers-color-scheme: light)", color: "#FFFFFF" },
|
|
{ media: "(prefers-color-scheme: dark)", color: "#0A0A0A" },
|
|
],
|
|
};
|
|
|
|
export default function RootLayout({
|
|
children,
|
|
}: Readonly<{
|
|
children: React.ReactNode;
|
|
}>) {
|
|
return (
|
|
<html lang="en" suppressHydrationWarning className={`${plexSans.variable} ${plexMono.variable}`}>
|
|
<body className="font-sans antialiased">
|
|
<ThemeProvider
|
|
attribute="class"
|
|
defaultTheme="system"
|
|
enableSystem
|
|
disableTransitionOnChange
|
|
>
|
|
<AuthProvider>
|
|
<ThemeSessionBridge />
|
|
<div className="min-h-screen bg-background flex flex-col">
|
|
<AppNavigation />
|
|
<main className="flex-1">{children}</main>
|
|
<TaglineFooter />
|
|
</div>
|
|
<CommandPalette />
|
|
</AuthProvider>
|
|
<Toaster position="top-right" richColors />
|
|
</ThemeProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|