/** * Layout Renderer * Phase 5: Sites Renderer & Publishing * * Renders different layout types for sites using shared components. */ import React from 'react'; import type { SiteDefinition } from '../types'; import { renderTemplate } from './templateEngine'; import { renderPageByType } from './pageTypeRenderer'; import { DefaultLayout, MinimalLayout, MagazineLayout, EcommerceLayout, PortfolioLayout, BlogLayout, CorporateLayout, } from '@shared/layouts'; export type LayoutType = | 'default' | 'minimal' | 'magazine' | 'ecommerce' | 'portfolio' | 'blog' | 'corporate'; /** * Render site layout based on site definition. */ export function renderLayout(siteDefinition: SiteDefinition): React.ReactElement { const layoutType = siteDefinition.layout as LayoutType; switch (layoutType) { case 'minimal': return renderMinimalLayout(siteDefinition); case 'magazine': return renderMagazineLayout(siteDefinition); case 'ecommerce': return renderEcommerceLayout(siteDefinition); case 'portfolio': return renderPortfolioLayout(siteDefinition); case 'blog': return renderBlogLayout(siteDefinition); case 'corporate': return renderCorporateLayout(siteDefinition); case 'default': default: return renderDefaultLayout(siteDefinition); } } /** * Default layout: Standard header, content, footer. * Uses shared DefaultLayout component with fully styled modern design. */ function renderDefaultLayout(siteDefinition: SiteDefinition): React.ReactElement { // Find home page for hero (only show hero on home page or when showing all pages) const homePage = siteDefinition.pages.find(p => p.slug === 'home'); const heroBlock = homePage?.blocks?.find(b => b.type === 'hero'); // Only show hero if we're on home page or showing all pages const isHomePage = siteDefinition.pages.length === 1 && siteDefinition.pages[0]?.slug === 'home'; const showHero = isHomePage || (homePage && siteDefinition.pages.length > 1); const hero: React.ReactNode = (showHero && heroBlock) ? (renderTemplate(heroBlock) as React.ReactNode) : undefined; // Render all pages using page-type-specific templates const sections = siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => { // Use page-type-specific renderer if available const blocksToRender = page.slug === 'home' && heroBlock && showHero ? page.blocks?.filter(b => b.type !== 'hero') || [] : page.blocks || []; // Render using page-type template return (
{renderPageByType(page, blocksToRender)}
); }); return ( ); } /** * Minimal layout: Clean, minimal design. * Uses shared MinimalLayout component. */ function renderMinimalLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( {mainContent} ); } /** * Magazine layout: Editorial, content-focused. * Uses shared MagazineLayout component. */ function renderMagazineLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( ); } /** * Ecommerce layout: Product-focused. * Uses shared EcommerceLayout component. */ function renderEcommerceLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( ); } /** * Portfolio layout: Showcase. * Uses shared PortfolioLayout component. */ function renderPortfolioLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( ); } /** * Blog layout: Content-first. * Uses shared BlogLayout component. */ function renderBlogLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( ); } /** * Corporate layout: Business. * Uses shared CorporateLayout component. */ function renderCorporateLayout(siteDefinition: SiteDefinition): React.ReactElement { const mainContent = ( <> {siteDefinition.pages .filter((page) => page.status !== 'draft' && page.status !== 'generating') .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((page) => (
{renderPageByType(page, page.blocks || [])}
))} ); return ( ); } // Note: Navigation and page rendering are now handled within each layout component