The CookieConsentBanner uses useLanguage() hook but was rendered outside of LanguageProvider in RootLayout. Added LanguageProvider wrapper to fix the runtime error.
137 lines
4.4 KiB
TypeScript
137 lines
4.4 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 { 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 Script from "next/script";
|
|
import { getThemeScript } from "@/lib/theme-script";
|
|
import { normalizeThemeId } from "@/lib/apply-document-theme";
|
|
|
|
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 {}
|
|
}
|
|
|
|
/**
|
|
* Inline script that runs BEFORE React hydrates.
|
|
* Reads the user's saved language from localStorage and sets
|
|
* `dir` on <html> immediately — prevents RTL/LTR flash.
|
|
*/
|
|
const directionScript = `
|
|
(function(){
|
|
try {
|
|
var lang = localStorage.getItem('user-language');
|
|
if (!lang) {
|
|
var c = document.cookie.split(';').map(function(s){return s.trim()}).find(function(s){return s.startsWith('user-language=')});
|
|
if (c) lang = c.split('=')[1];
|
|
}
|
|
if (lang === 'fa' || lang === 'ar') {
|
|
document.documentElement.dir = 'rtl';
|
|
document.documentElement.lang = lang;
|
|
}
|
|
} catch(e) {}
|
|
})();
|
|
`;
|
|
|
|
export default async function RootLayout({
|
|
children,
|
|
}: Readonly<{
|
|
children: React.ReactNode;
|
|
}>) {
|
|
const session = await auth();
|
|
const userId = session?.user?.id;
|
|
|
|
const [aiSettings, userSettings] = await Promise.all([
|
|
getAISettings(userId),
|
|
getUserSettings(userId),
|
|
])
|
|
|
|
const htmlTheme = serverHtmlThemeState(userSettings.theme)
|
|
|
|
return (
|
|
<html
|
|
suppressHydrationWarning
|
|
className={htmlTheme.className}
|
|
data-theme={htmlTheme.dataTheme}
|
|
>
|
|
<head />
|
|
<body className={`${inter.className} ${inter.variable} ${manrope.variable} ${playfair.variable} ${jetbrainsMono.variable}`}>
|
|
<script
|
|
dangerouslySetInnerHTML={{ __html: getThemeScript(userSettings.theme, userSettings.accentColor) }}
|
|
/>
|
|
<Script
|
|
id="sw-cleanup"
|
|
strategy="afterInteractive"
|
|
dangerouslySetInnerHTML={{ __html: `if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then(function(rs){rs.forEach(function(r){r.unregister()})})}` }}
|
|
/>
|
|
<SessionProviderWrapper>
|
|
<ErrorReporter />
|
|
<DirectionInitializer />
|
|
<ThemeInitializer theme={userSettings.theme} fontSize={aiSettings.fontSize} fontFamily={aiSettings.fontFamily} accentColor={userSettings.accentColor} />
|
|
{children}
|
|
<LanguageProvider initialLanguage="en">
|
|
<CookieConsentRoot initialAnonymousAnalytics={aiSettings?.anonymousAnalytics} />
|
|
</LanguageProvider>
|
|
<Toaster />
|
|
</SessionProviderWrapper>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|