194 lines
6.2 KiB
TypeScript
194 lines
6.2 KiB
TypeScript
import React from 'react';
|
|
import { AnnouncementBar } from '../layout/AnnouncementBar';
|
|
import { Header } from '../layout/Header';
|
|
import { Navbar } from '../layout/Navbar';
|
|
import { Footer } from '../layout/Footer';
|
|
import { FloatingButtons } from '../components/FloatingButtons';
|
|
import { StickyHeaderSpacer } from '../components/StickyHeaderSpacer';
|
|
|
|
import { HomeHeroRow } from '../sections/HomeHeroRow';
|
|
import { PromoCardsSection } from '../sections/PromoCardsSection';
|
|
import { CategoriesSection } from '../sections/CategoriesSection';
|
|
import { NewArrivalsSection } from '../sections/NewArrivalsSection';
|
|
import { PromoBannersSection } from '../sections/PromoBannersSection';
|
|
import { HotDealsSection } from '../sections/HotDealsSection';
|
|
import { RecentlyLaunchedSection } from '../sections/RecentlyLaunchedSection';
|
|
import { BrandsSection } from '../sections/BrandsSection';
|
|
import { UltimateTechSection } from '../sections/UltimateTechSection';
|
|
import { ArticlesSection } from '../sections/ArticlesSection';
|
|
import { FeaturesSection } from '../sections/FeaturesSection';
|
|
|
|
import { catalogServerService } from '../services/api/catalogServerService';
|
|
import { mapCatalogProductToProduct } from '../utils/productMapper';
|
|
import { Product } from '../types';
|
|
import {
|
|
buildHomepageSections,
|
|
isSectionVisible,
|
|
normalizeLayoutPayload,
|
|
} from '../lib/layoutConfig';
|
|
import { sortHomeSectionsByOrder } from '../lib/techshopHome';
|
|
|
|
/**
|
|
* Always fetch fresh layout so admin Visible/Hidden + display_order apply.
|
|
* Homepage stack matches TechShop demo:
|
|
* https://demo.templatemonster.com/demo/501755.html
|
|
*/
|
|
export const dynamic = 'force-dynamic';
|
|
export const revalidate = 0;
|
|
|
|
export default async function Home() {
|
|
let layoutData: any[] = [];
|
|
let dbProducts: any[] = [];
|
|
let categoriesData: any[] = [];
|
|
let brandsData: any[] = [];
|
|
let productsError = false;
|
|
|
|
const [productsResult, layoutResult, categoriesResult, brandsResult] =
|
|
await Promise.allSettled([
|
|
catalogServerService.getProducts(),
|
|
catalogServerService.fetchLayoutConfigOnServer(),
|
|
catalogServerService.getCategories(),
|
|
catalogServerService.getBrands(),
|
|
]);
|
|
|
|
if (productsResult.status === 'fulfilled') {
|
|
dbProducts = productsResult.value || [];
|
|
} else {
|
|
console.error('Homepage products fetch failed:', productsResult.reason);
|
|
productsError = true;
|
|
}
|
|
|
|
if (layoutResult.status === 'fulfilled') {
|
|
layoutData = normalizeLayoutPayload(layoutResult.value);
|
|
} else {
|
|
console.error('Homepage layout fetch failed:', layoutResult.reason);
|
|
}
|
|
|
|
if (categoriesResult.status === 'fulfilled') {
|
|
categoriesData = categoriesResult.value || [];
|
|
}
|
|
|
|
if (brandsResult.status === 'fulfilled') {
|
|
brandsData = brandsResult.value || [];
|
|
}
|
|
|
|
const catalogProducts: Product[] =
|
|
dbProducts.length > 0 ? dbProducts.map(mapCatalogProductToProduct) : [];
|
|
|
|
const { activeSections, sectionsData, layoutLoaded } =
|
|
buildHomepageSections(layoutData);
|
|
|
|
/** Honor dashboard Visible/Hidden (`metadata_json.is_active`) for every section. */
|
|
const show = (key: string) =>
|
|
isSectionVisible(activeSections, key, layoutLoaded);
|
|
|
|
const orderedKeys = sortHomeSectionsByOrder(sectionsData).map((s) => s.key);
|
|
const productsLoading = false;
|
|
|
|
const sectionNodes: Record<string, React.ReactNode> = {
|
|
hero_slider: show('hero_slider') ? (
|
|
<HomeHeroRow
|
|
key="hero_slider"
|
|
heroConfig={sectionsData.hero_slider}
|
|
showHero
|
|
initialCategories={categoriesData}
|
|
/>
|
|
) : null,
|
|
promo_cards: show('promo_cards') ? (
|
|
<PromoCardsSection
|
|
key="promo_cards"
|
|
config={sectionsData.promo_cards}
|
|
/>
|
|
) : null,
|
|
featured_categories: show('featured_categories') ? (
|
|
<CategoriesSection
|
|
key="featured_categories"
|
|
config={sectionsData.featured_categories}
|
|
initialCategories={categoriesData}
|
|
/>
|
|
) : null,
|
|
editorial_story: show('editorial_story') ? (
|
|
<NewArrivalsSection
|
|
key="editorial_story"
|
|
config={sectionsData.editorial_story}
|
|
products={catalogProducts}
|
|
loading={productsLoading}
|
|
error={productsError}
|
|
/>
|
|
) : null,
|
|
promo_banners: show('promo_banners') ? (
|
|
<PromoBannersSection
|
|
key="promo_banners"
|
|
config={sectionsData.promo_banners}
|
|
/>
|
|
) : null,
|
|
deal_of_the_day: show('deal_of_the_day') ? (
|
|
<HotDealsSection
|
|
key="deal_of_the_day"
|
|
config={sectionsData.deal_of_the_day}
|
|
products={catalogProducts}
|
|
loading={productsLoading}
|
|
error={productsError}
|
|
/>
|
|
) : null,
|
|
recently_launched: show('recently_launched') ? (
|
|
<RecentlyLaunchedSection
|
|
key="recently_launched"
|
|
config={sectionsData.recently_launched}
|
|
products={catalogProducts}
|
|
loading={productsLoading}
|
|
error={productsError}
|
|
/>
|
|
) : null,
|
|
official_brands: show('official_brands') ? (
|
|
<BrandsSection
|
|
key="official_brands"
|
|
config={sectionsData.official_brands}
|
|
initialBrands={brandsData}
|
|
/>
|
|
) : null,
|
|
ultimate_tech: show('ultimate_tech') ? (
|
|
<UltimateTechSection
|
|
key="ultimate_tech"
|
|
config={sectionsData.ultimate_tech}
|
|
products={catalogProducts}
|
|
loading={productsLoading}
|
|
error={productsError}
|
|
/>
|
|
) : null,
|
|
tech_blog: show('tech_blog') ? (
|
|
<ArticlesSection key="tech_blog" config={sectionsData.tech_blog} />
|
|
) : null,
|
|
trust_badges: show('trust_badges') ? (
|
|
<FeaturesSection
|
|
key="trust_badges"
|
|
config={sectionsData.trust_badges}
|
|
/>
|
|
) : null,
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#F8F9FB] text-[#1E293B] flex flex-col font-sans">
|
|
<AnnouncementBar
|
|
config={sectionsData.announcement_bar}
|
|
isActive={show('announcement_bar')}
|
|
/>
|
|
|
|
<Header />
|
|
|
|
<Navbar
|
|
config={sectionsData.nav_links}
|
|
isActive={show('nav_links')}
|
|
/>
|
|
{/* After Header + Navbar so sticky chrome reserves full height */}
|
|
<StickyHeaderSpacer />
|
|
|
|
<main className="flex-grow">
|
|
{orderedKeys.map((key) => sectionNodes[key] ?? null)}
|
|
</main>
|
|
|
|
<Footer />
|
|
<FloatingButtons />
|
|
</div>
|
|
);
|
|
}
|