106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import './globals.css';
|
|
import Navbar from '../components/Navbar';
|
|
import Footer from '../components/Footer';
|
|
import ClientProviders from '../components/ClientProviders';
|
|
import { API_BASE } from '../lib/api';
|
|
import type { MegaMenuPayload } from '../components/navigation/menuTypes';
|
|
import type { MenuItem, StoreSettings } from '@homaroma/types';
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'HomAroma',
|
|
description: 'Pure Origins, Artisan Craft, Zero Compromise',
|
|
};
|
|
|
|
// Fetch categories for Navbar and Footer on the server (lang=all for client-side switching)
|
|
async function getCategories() {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/categories?lang=all`, { next: { revalidate: 3600 } });
|
|
if (!res.ok) return [];
|
|
return await res.json();
|
|
} catch (error) {
|
|
console.error('Failed to fetch categories in layout', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Fetch mega menu on the server — eliminates client-side flash
|
|
async function getMegaMenu(): Promise<MegaMenuPayload[]> {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/menus/main`, { next: { revalidate: 300 } });
|
|
if (!res.ok) return [];
|
|
const data = await res.json() as { payload?: MegaMenuPayload[] };
|
|
return Array.isArray(data.payload) ? data.payload : [];
|
|
} catch (error) {
|
|
console.error('Failed to fetch mega menu in layout', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Fetch footer menus (explore + support) on the server
|
|
async function getFooterMenus(): Promise<{ exploreMenu: MenuItem[]; supportMenu: MenuItem[] }> {
|
|
const fetchMenu = async (slug: string): Promise<MenuItem[]> => {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/menus/${slug}`, { next: { revalidate: 300 } });
|
|
if (!res.ok) return [];
|
|
const data = await res.json() as { items?: MenuItem[] };
|
|
return Array.isArray(data.items)
|
|
? data.items.sort((a, b) => a.order - b.order)
|
|
: [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const [exploreMenu, supportMenu] = await Promise.all([
|
|
fetchMenu('footer-explore'),
|
|
fetchMenu('footer-support'),
|
|
]);
|
|
|
|
return { exploreMenu, supportMenu };
|
|
}
|
|
|
|
// Fetch store settings (social links, footer texts) on the server
|
|
async function getStoreSettings(): Promise<StoreSettings | null> {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/settings`, { next: { revalidate: 600 } });
|
|
if (!res.ok) return null;
|
|
return await res.json() as StoreSettings;
|
|
} catch (error) {
|
|
console.error('Failed to fetch store settings in layout', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export default async function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [categories, initialMenu, footerMenus, settings] = await Promise.all([
|
|
getCategories(),
|
|
getMegaMenu(),
|
|
getFooterMenus(),
|
|
getStoreSettings(),
|
|
]);
|
|
|
|
return (
|
|
<html lang="th" className="scroll-smooth">
|
|
<body className="min-h-screen bg-pop-bg text-pop-black font-sans antialiased">
|
|
<ClientProviders>
|
|
<Navbar categories={categories} initialMenu={initialMenu} settings={settings} />
|
|
<main className="global-main pt-[72px]">
|
|
{children}
|
|
</main>
|
|
<Footer
|
|
categories={categories}
|
|
exploreMenu={footerMenus.exploreMenu}
|
|
supportMenu={footerMenus.supportMenu}
|
|
settings={settings}
|
|
/>
|
|
</ClientProviders>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|