import type { Metadata } from 'next/types' import { Source_Serif_4, Source_Sans_3 } from 'next/font/google' import { ThemeProvider } from '~/app/providers/next-ui-providers' import { ToasterProviders } from '~/app/providers/toaster-providers' import { ProgressBarProviders } from '~/app/providers/progress-bar-providers' import { ButtonStoreProvider } from '~/app/providers/button-store-providers' import '~/style/globals.css' import { cachedConfigsByKeys } from '~/server/lib/cache' import { toCustomInfo } from '~/server/lib/config-transform' import { NextIntlClientProvider } from 'next-intl' import { getLocale, getMessages } from 'next-intl/server' import { ConfigStoreProvider } from '~/app/providers/config-store-providers' import Script from 'next/script' const sourceSerif4 = Source_Serif_4({ subsets: ['latin'], variable: '--font-display', display: 'swap', weight: ['400', '600', '700'], }) const sourceSans3 = Source_Sans_3({ subsets: ['latin'], variable: '--font-sans', display: 'swap', weight: ['400', '500', '600'], }) export async function generateMetadata(): Promise { const rows = await cachedConfigsByKeys([ 'custom_title', 'custom_favicon_url', 'custom_author', ]) const info = toCustomInfo(rows) const title = info.customTitle || 'PicImpact' const author = info.customAuthor const description = author ? `${author}'s photography portfolio — powered by PicImpact` : 'A photography portfolio powered by PicImpact' return { metadataBase: process.env.BETTER_AUTH_URL ? new URL(process.env.BETTER_AUTH_URL) : null, title, description, icons: { icon: info.customFaviconUrl || './favicon.ico' }, manifest: '/manifest.json', openGraph: { title, description, siteName: title, type: 'website', }, twitter: { card: 'summary_large_image', title, description, }, appleWebApp: { capable: true, statusBarStyle: 'default', title: 'PicImpact', } } } export const viewport = { width: 'device-width', initialScale: 1, maximumScale: 5, themeColor: '#2d2518', } export default async function RootLayout({ children, modal, }: Readonly<{ children: React.ReactNode; modal: React.ReactNode; }>) { const locale = await getLocale() const messages = await getMessages() const rows = await cachedConfigsByKeys([ 'default_theme', 'umami_analytics', 'umami_host' ]) const info = toCustomInfo(rows) // `toCustomInfo` runs `default_theme` through `normalizeDefaultTheme`, so the // value is already guaranteed to be one of 'light' | 'dark' | 'system'. Cast // here because the public-facing `CustomInfo` type keeps the field as `string`. const defaultTheme = info.defaultTheme as 'light' | 'dark' | 'system' const umamiHost = info.umamiHost || 'https://cloud.umami.is/script.js' const umamiAnalytics = info.umamiAnalytics return ( {children} {modal}