Files
Momento/memento-note/app/layout.tsx
Antigravity a77f3ffb6e
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m15s
CI / Deploy production (on server) (push) Has been skipped
fix: batch priorités — thème, scroll, wizard, agents, slides, charts, packs
Anti-flash dark (color-scheme + applyDocumentTheme), scroll pages publiques,
prompts wizard/slides moins génériques, cron agents rattrapage nextRun null,
slides carnet via notebookId, graphiques à 2 crédits + hint, audit dashboard,
packs Stripe marqués non configurés sans price ID.
2026-07-16 20:48:55 +00:00

138 lines
5.0 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import "./globals.css";
import { Toaster } from "@/components/ui/toast";
import { SessionProviderWrapper } from "@/components/session-provider-wrapper";
import { getAISettings } from "@/app/actions/ai-settings";
import { getUserSettings } from "@/app/actions/user-settings";
import { ThemeInitializer } from "@/components/theme-initializer";
import { ThemeClassGuard } from "@/components/theme-class-guard";
import { DirectionInitializer } from "@/components/direction-initializer";
import { ErrorReporter } from "@/components/error-reporter";
import { auth } from "@/auth";
import { CookieConsentRoot } from "@/components/legal/cookie-consent-root";
import { LanguageProvider } from "@/lib/i18n/LanguageProvider";
import type { CSSProperties } from "react";
import { cookies } from "next/headers";
import { normalizeThemeId, THEME_COOKIE_NAME } from "@/lib/apply-document-theme";
import { THEME_INIT_SCRIPT, DIRECTION_INIT_SCRIPT } from "@/lib/inline-boot-scripts";
import { SwCleanup } from "@/components/sw-cleanup";
import { Inter, Manrope, Playfair_Display, JetBrains_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
const manrope = Manrope({
subsets: ["latin"],
variable: "--font-manrope",
});
const playfair = Playfair_Display({
subsets: ["latin"],
variable: "--font-memento-serif",
weight: ["400", "500", "600", "700"],
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
weight: ["400", "500"],
});
export const metadata: Metadata = {
title: "Memento - Your Digital Notepad",
description: "A beautiful note-taking app built with Next.js 16",
manifest: "/api/manifest",
icons: {
icon: "/icons/icon-512.svg",
apple: "/icons/icon-512.svg",
},
appleWebApp: {
capable: true,
statusBarStyle: "default",
title: "Memento",
},
};
export const viewport: Viewport = {
themeColor: "#1C1C1C",
};
function serverHtmlThemeState(theme?: string | null): { className?: string; dataTheme?: string } {
const t = normalizeThemeId(theme || 'light')
if (t === 'auto') return {}
if (t === 'dark') return { className: 'dark' }
if (t === 'light') return {}
if (t === 'midnight') return { className: 'dark', dataTheme: 'midnight' }
const named = ['sepia', 'rose', 'green', 'lavender', 'sand', 'ocean', 'sunset', 'blue'] as const
if ((named as readonly string[]).includes(t)) return { dataTheme: t }
return {}
}
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth();
const userId = session?.user?.id;
const [aiSettings, userSettings, cookieStore] = await Promise.all([
getAISettings(userId),
getUserSettings(userId),
cookies(),
])
// Cookie = préférence immédiate (bascule barre latérale) ; base = profil utilisateur.
// Sans cookie, un dark seulement en localStorage était écrasé par le serveur → flash clair.
const cookieTheme = cookieStore.get(THEME_COOKIE_NAME)?.value
const resolvedTheme = normalizeThemeId(cookieTheme || userSettings.theme || 'light')
const htmlTheme = serverHtmlThemeState(resolvedTheme)
const serverAccent = userSettings.accentColor ?? '#A47148'
const serverTheme = resolvedTheme
const wantsDarkClass =
resolvedTheme === 'dark' ||
resolvedTheme === 'midnight' ||
// auto : le serveur ne connaît pas prefers-color-scheme → laisser le script client
false
const htmlStyle = {
'--color-brand-accent': serverAccent,
// Aide le navigateur à peindre scrollbars/inputs dans le bon mode
colorScheme: wantsDarkClass || resolvedTheme === 'dark' || resolvedTheme === 'midnight' ? 'dark' : resolvedTheme === 'light' ? 'light' : undefined,
} as CSSProperties
return (
<html
suppressHydrationWarning
className={htmlTheme.className}
data-theme={htmlTheme.dataTheme}
data-server-theme={serverTheme}
data-server-accent={serverAccent}
style={htmlStyle}
>
<head>
{/* Scripts boot inline : anti-flash thème/RTL, sans next/script (React 19). */}
<script id="theme-init" dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
<script id="direction-init" dangerouslySetInnerHTML={{ __html: DIRECTION_INIT_SCRIPT }} />
</head>
<body className={`${inter.className} ${inter.variable} ${manrope.variable} ${playfair.variable} ${jetbrainsMono.variable} h-screen overflow-hidden bg-background text-foreground`}>
<SessionProviderWrapper>
<SwCleanup />
<ErrorReporter />
<DirectionInitializer />
<ThemeInitializer theme={resolvedTheme} fontSize={aiSettings.fontSize} fontFamily={aiSettings.fontFamily} accentColor={userSettings.accentColor} />
<ThemeClassGuard />
{children}
<LanguageProvider initialLanguage="en">
<CookieConsentRoot />
</LanguageProvider>
<Toaster />
</SessionProviderWrapper>
</body>
</html>
);
}