110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
import './globals.css'
|
|
import {ReactNode} from 'react'
|
|
import {Metadata, Viewport} from 'next'
|
|
import {Toaster} from '@/components/ui/sonner'
|
|
import Effects from '@/app/effects'
|
|
import {ProfileStoreProvider} from '@/components/stores/profile'
|
|
import {LayoutStoreProvider} from '@/components/stores/layout'
|
|
import {ClientStoreProvider} from '@/components/stores/client'
|
|
import {getProfile} from '@/actions/auth'
|
|
import {AppStoreProvider} from '@/components/stores/app'
|
|
import {getApiUrl} from '@/actions/base'
|
|
import {siteConfig} from '@/config/site'
|
|
import {JsonLd} from '@/components/seo/json-ld'
|
|
|
|
export const viewport: Viewport = {
|
|
width: 'device-width',
|
|
initialScale: 1,
|
|
themeColor: '#3b82f6',
|
|
}
|
|
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
return {
|
|
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',
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function RootLayout(props: Readonly<{
|
|
children: ReactNode
|
|
}>) {
|
|
return (
|
|
<html lang="zh-CN">
|
|
<body>
|
|
<StoreProviders>
|
|
<Effects>{props.children}</Effects>
|
|
</StoreProviders>
|
|
<Toaster position="top-center" richColors expand/>
|
|
<JsonLd
|
|
schema={{
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Organization',
|
|
'@id': `${siteConfig.url}/#organization`,
|
|
'name': siteConfig.name,
|
|
'url': siteConfig.url,
|
|
'description': siteConfig.description,
|
|
}}
|
|
/>
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|
|
|
|
async function StoreProviders(props: {children: ReactNode}) {
|
|
return (
|
|
<ProfileStoreProvider profile={getProfile().then(resp => resp.success ? resp.data : null)}>
|
|
<LayoutStoreProvider>
|
|
<ClientStoreProvider>
|
|
<AppStoreProvider url={await getApiUrl().then(r => r.data)}>
|
|
{props.children}
|
|
</AppStoreProvider>
|
|
</ClientStoreProvider>
|
|
</LayoutStoreProvider>
|
|
</ProfileStoreProvider>
|
|
)
|
|
}
|