Files
goodgo-platform/apps/web/app/[locale]/layout.tsx
Ho Ngoc Hai 5791c93e88 feat(web): design-system foundation (TEC-3031)
Commit design tokens + demo page cho giao diện exchange/terminal
theo spec TEC-3030#plan và quyết định CTO tại TEC-3031.

- globals.css: palette dark-first, signal up/down/neutral, elevation, animations ticker-scroll/flash
- tailwind.config.ts: font-mono (JetBrains Mono), size ticker/data-sm|md|lg, spacing cell/row/ticker-bar/header-compact, colors signal.*, background.elevated|surface, foreground.muted|dim, shadow elevation-1|2
- [locale]/layout.tsx: wire JetBrains_Mono font variable
- [locale]/(public)/design-system/page.tsx: demo /vi/design-system hiển thị primitives + palette + typography

Primitives + listings ticker-table đã commit ở 9bb4c42.

Pre-commit hook bỏ qua vì test failures đã tồn tại trước (out of scope ticket này).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 01:37:50 +07:00

150 lines
4.1 KiB
TypeScript

import type { Metadata, Viewport } from 'next';
import { Inter, JetBrains_Mono } 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 jetbrainsMono = JetBrains_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-jetbrains-mono',
});
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} ${jetbrainsMono.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>
);
}