Files
web/src/app/layout.tsx

110 lines
2.9 KiB
TypeScript
Raw Normal View History

2025-12-11 14:10:52 +08:00
import './globals.css'
2025-03-11 14:57:23 +08:00
import {ReactNode} from 'react'
2026-05-15 16:56:05 +08:00
import {Metadata, Viewport} from 'next'
2025-03-19 15:49:18 +08:00
import {Toaster} from '@/components/ui/sonner'
2025-06-18 17:57:12 +08:00
import Effects from '@/app/effects'
2025-12-11 14:10:52 +08:00
import {ProfileStoreProvider} from '@/components/stores/profile'
import {LayoutStoreProvider} from '@/components/stores/layout'
import {ClientStoreProvider} from '@/components/stores/client'
import {getProfile} from '@/actions/auth'
2026-04-14 11:34:28 +08:00
import {AppStoreProvider} from '@/components/stores/app'
import {getApiUrl} from '@/actions/base'
2026-05-15 16:56:05 +08:00
import {siteConfig} from '@/config/site'
import {JsonLd} from '@/components/seo/json-ld'
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#3b82f6',
}
2025-03-04 10:10:35 +08:00
export async function generateMetadata(): Promise<Metadata> {
return {
2026-05-15 16:56:05 +08:00
metadataBase: new URL(siteConfig.url),
title: {
default: siteConfig.name,
template: `%s`,
},
description: siteConfig.description,
keywords: siteConfig.keywords,
robots: {
index: true,
follow: true,
googleBot: {
'index': true,
'follow': true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
openGraph: {
type: 'website',
locale: siteConfig.locale,
url: siteConfig.url,
siteName: siteConfig.name,
title: siteConfig.name,
description: siteConfig.description,
images: [
{
url: siteConfig.ogImage.url,
width: siteConfig.ogImage.width,
height: siteConfig.ogImage.height,
alt: siteConfig.name,
},
],
},
twitter: {
card: 'summary_large_image',
title: siteConfig.name,
description: siteConfig.description,
images: [siteConfig.ogImage.url],
},
alternates: {
canonical: siteConfig.url,
},
icons: {
icon: '/favicon.ico',
},
}
2025-03-11 14:57:23 +08:00
}
2025-03-04 10:10:35 +08:00
2025-12-11 14:10:52 +08:00
export default async function RootLayout(props: Readonly<{
children: ReactNode
2025-03-04 10:10:35 +08:00
}>) {
return (
<html lang="zh-CN">
<body>
2025-12-11 14:10:52 +08:00
<StoreProviders>
<Effects>{props.children}</Effects>
</StoreProviders>
<Toaster position="top-center" richColors expand/>
2026-05-15 16:56:05 +08:00
<JsonLd
schema={{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${siteConfig.url}/#organization`,
'name': siteConfig.name,
'url': siteConfig.url,
'description': siteConfig.description,
}}
/>
2025-03-04 10:10:35 +08:00
</body>
</html>
2025-03-11 14:57:23 +08:00
)
2025-03-04 10:10:35 +08:00
}
2025-12-11 14:10:52 +08:00
2026-04-14 11:34:28 +08:00
async function StoreProviders(props: {children: ReactNode}) {
2025-12-11 14:10:52 +08:00
return (
<ProfileStoreProvider profile={getProfile().then(resp => resp.success ? resp.data : null)}>
<LayoutStoreProvider>
<ClientStoreProvider>
2026-04-14 11:34:28 +08:00
<AppStoreProvider url={await getApiUrl().then(r => r.data)}>
{props.children}
</AppStoreProvider>
2025-12-11 14:10:52 +08:00
</ClientStoreProvider>
</LayoutStoreProvider>
</ProfileStoreProvider>
)
}