Files
goodgo-platform/apps/web/app/[locale]/layout.tsx
Ho Ngoc Hai 44533a88f4 fix(web): wire up inquiry modal toast notification on listing detail page
The "Nhắn tin" button's inquiry modal now shows a success toast via
sonner after submission instead of an in-dialog success state, and
closes the modal automatically. Added sonner as a dependency and
mounted <Toaster> in the root locale layout.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 10:56:56 +07:00

140 lines
3.9 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 { Toaster } from 'sonner';
import { AuthProvider } from '@/components/providers/auth-provider';
import { NotificationsProvider } from '@/components/providers/notifications-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,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
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,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// 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>
<NotificationsProvider>
<Toaster position="top-right" richColors closeButton />
<WebVitals />
{children}
</NotificationsProvider>
</AuthProvider>
</QueryProvider>
</ThemeProvider>
</NextIntlClientProvider>
</body>
</html>
);
}