Files
goodgo-platform/apps/web/app/[locale]/layout.tsx
Ho Ngoc Hai 759052a71f fix(web): update dashboard pages, layouts, and listing forms
Update 12 page/layout files across auth, dashboard, listings, and search
routes to improve type safety, fix component imports, and align with
latest API changes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-11 01:39:59 +07:00

132 lines
3.5 KiB
TypeScript

import type { Metadata, Viewport } from 'next';
import { Inter } from 'next/font/google';
import { notFound } from 'next/navigation';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, getTranslations } from 'next-intl/server';
import { AuthProvider } from '@/components/providers/auth-provider';
import { QueryProvider } from '@/components/providers/query-provider';
import { ThemeProvider } from '@/components/providers/theme-provider';
import { WebVitals } from '@/components/providers/web-vitals';
import { JsonLd, generateWebsiteJsonLd } from '@/components/seo/json-ld';
import type { Locale } from '@/i18n/config';
import { routing } from '@/i18n/routing';
import '../globals.css';
const inter = Inter({
subsets: ['latin', 'vietnamese'],
display: 'swap',
variable: '--font-inter',
});
const siteUrl = process.env['NEXT_PUBLIC_SITE_URL'] || 'https://goodgo.vn';
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#15803d',
};
export async function generateMetadata({
params: { locale },
}: {
params: { locale: string };
}): Promise<Metadata> {
const t = await getTranslations({ locale, namespace: 'metadata' });
return {
metadataBase: new URL(siteUrl),
title: {
default: t('title'),
template: '%s | GoodGo',
},
description: t('description'),
keywords: [
'bất động sản',
'mua bán nhà đất',
'cho thuê nhà',
'goodgo',
'nhà đất việt nam',
'real estate vietnam',
],
authors: [{ name: 'GoodGo' }],
creator: 'GoodGo',
openGraph: {
type: 'website',
locale: locale === 'vi' ? 'vi_VN' : 'en_US',
url: siteUrl,
siteName: 'GoodGo',
title: t('ogTitle'),
description: t('ogDescription'),
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: t('ogTitle'),
},
],
},
twitter: {
card: 'summary_large_image',
title: t('ogTitle'),
description: t('ogDescription'),
images: ['/og-image.png'],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
}
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
// Validate locale
if (!routing.locales.includes(locale as Locale)) {
notFound();
}
const messages = await getMessages();
const t = await getTranslations({ locale, namespace: 'common' });
return (
<html lang={locale} suppressHydrationWarning className={inter.variable}>
<body className={inter.className}>
<JsonLd data={generateWebsiteJsonLd(siteUrl)} />
<a
href="#main-content"
className="fixed left-2 top-2 z-[100] -translate-y-16 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-lg transition-transform focus:translate-y-0"
>
{t('skipToContent')}
</a>
<NextIntlClientProvider messages={messages}>
<ThemeProvider>
<QueryProvider>
<AuthProvider>
<WebVitals />
{children}
</AuthProvider>
</QueryProvider>
</ThemeProvider>
</NextIntlClientProvider>
</body>
</html>
);
}