Files
OmniRoute/src/app/layout.tsx
oyi77 1d47cadae8 feat(favicon): add custom favicon support
- Add customFaviconUrl and customFaviconBase64 to Zod schema
- Add favicon customization UI (URL input + file upload)
- Create /api/settings/favicon endpoint for dynamic favicon
- Update layout.tsx to use generateMetadata for dynamic favicon
- Add i18n keys for favicon customization
- Favicon updates browser tab icon in real-time
2026-04-01 03:54:02 +07:00

61 lines
2.3 KiB
TypeScript

import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/shared/components/ThemeProvider";
import "@/lib/initCloudSync"; // Auto-initialize cloud sync
import { NextIntlClientProvider } from "next-intl";
import { getMessages, getLocale } from "next-intl/server";
import { RTL_LOCALES } from "@/i18n/config";
import { getSettings } from "@/lib/db/settings";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
export async function generateMetadata() {
const settings = await getSettings();
const instanceName = settings?.instanceName || "OmniRoute";
const customFaviconUrl = settings?.customFaviconUrl || settings?.customFaviconBase64;
return {
title: `${instanceName} — AI Gateway for Multi-Provider LLMs`,
description:
"OmniRoute is an AI gateway for multi-provider LLMs. One endpoint for all your AI providers.",
icons: {
icon: customFaviconUrl ? "/api/settings/favicon" : "/favicon.svg",
apple: "/apple-touch-icon.svg",
},
};
}
export default async function RootLayout({ children }) {
const locale = await getLocale();
const messages = await getMessages();
const isRtl = RTL_LOCALES.includes(locale as (typeof RTL_LOCALES)[number]);
return (
<html lang={locale} dir={isRtl ? "rtl" : "ltr"} suppressHydrationWarning>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
{/* eslint-disable-next-line @next/next/no-page-custom-font */}
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
rel="stylesheet"
/>
</head>
<body className={`${inter.variable} font-sans antialiased`} suppressHydrationWarning>
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-[#6366f1] focus:text-white focus:rounded-lg focus:text-sm focus:font-semibold focus:shadow-lg"
>
Skip to content
</a>
<NextIntlClientProvider locale={locale} messages={messages}>
<ThemeProvider>{children}</ThemeProvider>
</NextIntlClientProvider>
</body>
</html>
);
}