Add generate_page_content functionality for structured page content generation
- Introduced a new AI function `generate_page_content` to create structured content for website pages using JSON blocks. - Updated `AIEngine` to handle the new function and return appropriate messages for content generation. - Enhanced `PageGenerationService` to utilize the new AI function for generating page content based on blueprints. - Modified `prompts.py` to include detailed content generation requirements for the new function. - Updated site rendering logic to accommodate structured content blocks in various layouts.
This commit is contained in:
@@ -41,36 +41,32 @@ function SiteRenderer() {
|
||||
}
|
||||
|
||||
// Build navigation from site definition
|
||||
// Show pages that are published, ready, or in navigation (excluding home and draft/generating)
|
||||
const navigation = siteDefinition.navigation || siteDefinition.pages
|
||||
.filter(p =>
|
||||
p.slug !== 'home' &&
|
||||
(p.status === 'published' || p.status === 'ready' || siteDefinition.navigation?.some(n => n.slug === p.slug))
|
||||
)
|
||||
.sort((a, b) => {
|
||||
// Try to get order from navigation or use page order
|
||||
const navA = siteDefinition.navigation?.find(n => n.slug === a.slug);
|
||||
const navB = siteDefinition.navigation?.find(n => n.slug === b.slug);
|
||||
return (navA?.order ?? a.order ?? 0) - (navB?.order ?? b.order ?? 0);
|
||||
})
|
||||
.map(page => ({
|
||||
label: page.title,
|
||||
slug: page.slug,
|
||||
order: page.order || 0
|
||||
}));
|
||||
// Show all published/ready pages (excluding home and draft/generating)
|
||||
// Use explicit navigation if available, otherwise auto-generate from pages
|
||||
const navigation = siteDefinition.navigation && siteDefinition.navigation.length > 0
|
||||
? siteDefinition.navigation
|
||||
: siteDefinition.pages
|
||||
.filter(p =>
|
||||
p.slug !== 'home' &&
|
||||
(p.status === 'published' || p.status === 'ready') &&
|
||||
p.status !== 'draft' &&
|
||||
p.status !== 'generating'
|
||||
)
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map(page => ({
|
||||
label: page.title,
|
||||
slug: page.slug,
|
||||
order: page.order || 0
|
||||
}));
|
||||
|
||||
// Filter pages based on current route
|
||||
const currentPageSlug = pageSlug || 'home';
|
||||
const currentPage = siteDefinition.pages.find(p => p.slug === currentPageSlug);
|
||||
|
||||
// If specific page requested, show only that page; otherwise show all published/ready pages
|
||||
const pagesToRender = currentPageSlug && currentPageSlug !== 'home' && currentPage
|
||||
// Show only the current page (home page on home route, specific page on page route)
|
||||
const pagesToRender = currentPage
|
||||
? [currentPage]
|
||||
: siteDefinition.pages.filter(p =>
|
||||
p.status === 'published' ||
|
||||
p.status === 'ready' ||
|
||||
(p.slug === 'home' && p.status !== 'draft' && p.status !== 'generating')
|
||||
);
|
||||
: []; // Fallback: no page found
|
||||
|
||||
return (
|
||||
<div className="site-renderer" style={{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import React from 'react';
|
||||
import type { SiteDefinition } from '../types';
|
||||
import { renderTemplate } from './templateEngine';
|
||||
import { renderPageByType } from './pageTypeRenderer';
|
||||
import {
|
||||
DefaultLayout,
|
||||
MinimalLayout,
|
||||
@@ -65,28 +66,20 @@ function renderDefaultLayout(siteDefinition: SiteDefinition): React.ReactElement
|
||||
const showHero = isHomePage || (homePage && siteDefinition.pages.length > 1);
|
||||
const hero: React.ReactNode = (showHero && heroBlock) ? (renderTemplate(heroBlock) as React.ReactNode) : undefined;
|
||||
|
||||
// Render all pages as sections (excluding hero from home page if it exists)
|
||||
// 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) => {
|
||||
// Filter out hero block if it's the home page (already rendered as hero)
|
||||
// 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 (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug} style={{ textAlign: 'center' }}>
|
||||
{page.slug !== 'home' && <h2 style={{ textAlign: 'center', marginBottom: '1.5rem' }}>{page.title}</h2>}
|
||||
{blocksToRender.length > 0 ? (
|
||||
blocksToRender.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type} style={{ textAlign: 'center' }}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : page.slug !== 'home' ? (
|
||||
<p>No content available for this page.</p>
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, blocksToRender)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -107,17 +100,11 @@ function renderMinimalLayout(siteDefinition: SiteDefinition): React.ReactElement
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
<h2>{page.title}</h2>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -138,16 +125,11 @@ function renderMagazineLayout(siteDefinition: SiteDefinition): React.ReactElemen
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -168,16 +150,11 @@ function renderEcommerceLayout(siteDefinition: SiteDefinition): React.ReactEleme
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -198,16 +175,11 @@ function renderPortfolioLayout(siteDefinition: SiteDefinition): React.ReactEleme
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -228,16 +200,11 @@ function renderBlogLayout(siteDefinition: SiteDefinition): React.ReactElement {
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -258,16 +225,11 @@ function renderCorporateLayout(siteDefinition: SiteDefinition): React.ReactEleme
|
||||
const mainContent = (
|
||||
<>
|
||||
{siteDefinition.pages
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.filter((page) => page.status !== 'draft' && page.status !== 'generating')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
<div key={page.id} className="page-wrapper" data-page-slug={page.slug}>
|
||||
{renderPageByType(page, page.blocks || [])}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
||||
374
sites/src/utils/pageTypeRenderer.tsx
Normal file
374
sites/src/utils/pageTypeRenderer.tsx
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Page Type Renderer
|
||||
* Renders page-type-specific templates for different page types.
|
||||
*
|
||||
* Each page type has its own template structure optimized for that content type.
|
||||
*/
|
||||
import React from 'react';
|
||||
import type { PageDefinition, Block } from '../types';
|
||||
import { renderTemplate } from './templateEngine';
|
||||
|
||||
/**
|
||||
* Render a page based on its type.
|
||||
* Falls back to default rendering if no specific template exists.
|
||||
*/
|
||||
export function renderPageByType(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
// Determine page type - use page.type if available, otherwise infer from slug
|
||||
let pageType = page.type;
|
||||
if (!pageType || pageType === 'custom') {
|
||||
// Infer type from slug
|
||||
if (page.slug === 'home') pageType = 'home';
|
||||
else if (page.slug === 'products') pageType = 'products';
|
||||
else if (page.slug === 'blog') pageType = 'blog';
|
||||
else if (page.slug === 'contact') pageType = 'contact';
|
||||
else if (page.slug === 'about') pageType = 'about';
|
||||
else if (page.slug === 'services') pageType = 'services';
|
||||
else pageType = 'custom';
|
||||
}
|
||||
|
||||
switch (pageType) {
|
||||
case 'home':
|
||||
return renderHomePage(page, blocks);
|
||||
case 'products':
|
||||
return renderProductsPage(page, blocks);
|
||||
case 'blog':
|
||||
return renderBlogPage(page, blocks);
|
||||
case 'contact':
|
||||
return renderContactPage(page, blocks);
|
||||
case 'about':
|
||||
return renderAboutPage(page, blocks);
|
||||
case 'services':
|
||||
return renderServicesPage(page, blocks);
|
||||
case 'custom':
|
||||
default:
|
||||
return renderCustomPage(page, blocks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Home Page Template
|
||||
* Structure: Hero → Features → Testimonials → CTA
|
||||
*/
|
||||
function renderHomePage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
// Find specific blocks
|
||||
const heroBlock = blocks.find(b => b.type === 'hero');
|
||||
const featuresBlock = blocks.find(b => b.type === 'features' || b.type === 'grid');
|
||||
const testimonialsBlock = blocks.find(b => b.type === 'testimonials');
|
||||
const ctaBlock = blocks.find(b => b.type === 'cta');
|
||||
const otherBlocks = blocks.filter(b =>
|
||||
!['hero', 'features', 'grid', 'testimonials', 'cta'].includes(b.type)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-home" data-page-slug={page.slug}>
|
||||
{/* Hero Section */}
|
||||
{heroBlock && (
|
||||
<section className="home-hero">
|
||||
{renderTemplate(heroBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Features Section */}
|
||||
{featuresBlock && (
|
||||
<section className="home-features" style={{ padding: '3rem 2rem', background: '#f9fafb' }}>
|
||||
{renderTemplate(featuresBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Other Content Blocks */}
|
||||
{otherBlocks.length > 0 && (
|
||||
<section className="home-content" style={{ padding: '3rem 2rem' }}>
|
||||
{otherBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Testimonials Section */}
|
||||
{testimonialsBlock && (
|
||||
<section className="home-testimonials" style={{ padding: '3rem 2rem', background: '#f9fafb' }}>
|
||||
{renderTemplate(testimonialsBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* CTA Section */}
|
||||
{ctaBlock && (
|
||||
<section className="home-cta">
|
||||
{renderTemplate(ctaBlock)}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Products Page Template
|
||||
* Structure: Title → Product Grid → Filters (if available)
|
||||
*/
|
||||
function renderProductsPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
const productsBlock = blocks.find(b => b.type === 'products' || b.type === 'grid');
|
||||
const otherBlocks = blocks.filter(b => b.type !== 'products' && b.type !== 'grid');
|
||||
|
||||
return (
|
||||
<div className="page-products" data-page-slug={page.slug}>
|
||||
<header style={{ marginBottom: '2rem', textAlign: 'center' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '1rem' }}>
|
||||
{page.title}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{/* Product Grid */}
|
||||
{productsBlock && (
|
||||
<section className="products-grid" style={{ padding: '2rem 0' }}>
|
||||
{renderTemplate(productsBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Other Content */}
|
||||
{otherBlocks.length > 0 && (
|
||||
<section className="products-content" style={{ padding: '2rem 0' }}>
|
||||
{otherBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blog Page Template
|
||||
* Structure: Title → Post List/Grid → Sidebar (if available)
|
||||
*/
|
||||
function renderBlogPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
const heroBlock = blocks.find(b => b.type === 'hero');
|
||||
const contentBlocks = blocks.filter(b => b.type !== 'hero');
|
||||
|
||||
return (
|
||||
<div className="page-blog" data-page-slug={page.slug}>
|
||||
{/* Hero/Header */}
|
||||
{heroBlock ? (
|
||||
<section className="blog-hero">
|
||||
{renderTemplate(heroBlock)}
|
||||
</section>
|
||||
) : (
|
||||
<header style={{ marginBottom: '2rem', textAlign: 'center', padding: '2rem 0' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '0.5rem' }}>
|
||||
{page.title}
|
||||
</h1>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Blog Content - Grid Layout for Posts */}
|
||||
<section className="blog-content" style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
|
||||
gap: '2rem',
|
||||
padding: '2rem 0'
|
||||
}}>
|
||||
{contentBlocks.map((block, index) => (
|
||||
<article key={index} className="blog-post" style={{
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '8px',
|
||||
padding: '1.5rem',
|
||||
background: '#fff'
|
||||
}}>
|
||||
{renderTemplate(block)}
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contact Page Template
|
||||
* Structure: Title → Contact Info → Form → Map (if available)
|
||||
*/
|
||||
function renderContactPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
const formBlock = blocks.find(b => b.type === 'form');
|
||||
const textBlocks = blocks.filter(b => b.type === 'text' || b.type === 'section');
|
||||
const mapBlock = blocks.find(b => b.type === 'image' && b.data?.caption?.toLowerCase().includes('map'));
|
||||
const otherBlocks = blocks.filter(b =>
|
||||
b.type !== 'form' &&
|
||||
b.type !== 'text' &&
|
||||
b.type !== 'section' &&
|
||||
!(b.type === 'image' && b.data?.caption?.toLowerCase().includes('map'))
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-contact" data-page-slug={page.slug}>
|
||||
<header style={{ marginBottom: '2rem', textAlign: 'center' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '1rem' }}>
|
||||
{page.title}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '2rem',
|
||||
padding: '2rem 0'
|
||||
}}>
|
||||
{/* Contact Information */}
|
||||
{textBlocks.length > 0 && (
|
||||
<section className="contact-info">
|
||||
{textBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Contact Form */}
|
||||
{formBlock && (
|
||||
<section className="contact-form">
|
||||
{renderTemplate(formBlock)}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Map */}
|
||||
{mapBlock && (
|
||||
<section className="contact-map" style={{ marginTop: '2rem' }}>
|
||||
{renderTemplate(mapBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Other Content */}
|
||||
{otherBlocks.length > 0 && (
|
||||
<section className="contact-other" style={{ marginTop: '2rem' }}>
|
||||
{otherBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* About Page Template
|
||||
* Structure: Hero → Mission → Team/Values → Stats
|
||||
*/
|
||||
function renderAboutPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
const heroBlock = blocks.find(b => b.type === 'hero');
|
||||
const statsBlock = blocks.find(b => b.type === 'stats');
|
||||
const servicesBlock = blocks.find(b => b.type === 'services' || b.type === 'features');
|
||||
const otherBlocks = blocks.filter(b =>
|
||||
!['hero', 'stats', 'services', 'features'].includes(b.type)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-about" data-page-slug={page.slug}>
|
||||
{/* Hero Section */}
|
||||
{heroBlock && (
|
||||
<section className="about-hero">
|
||||
{renderTemplate(heroBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<section className="about-content" style={{ padding: '3rem 2rem' }}>
|
||||
{otherBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type} style={{ marginBottom: '2rem' }}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
{/* Services/Features Section */}
|
||||
{servicesBlock && (
|
||||
<section className="about-services" style={{ padding: '3rem 2rem', background: '#f9fafb' }}>
|
||||
{renderTemplate(servicesBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Stats Section */}
|
||||
{statsBlock && (
|
||||
<section className="about-stats" style={{ padding: '3rem 2rem' }}>
|
||||
{renderTemplate(statsBlock)}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Services Page Template
|
||||
* Structure: Title → Service Cards → Details
|
||||
*/
|
||||
function renderServicesPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
const heroBlock = blocks.find(b => b.type === 'hero');
|
||||
const servicesBlock = blocks.find(b => b.type === 'services' || b.type === 'features');
|
||||
const otherBlocks = blocks.filter(b =>
|
||||
b.type !== 'hero' && b.type !== 'services' && b.type !== 'features'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-services" data-page-slug={page.slug}>
|
||||
{/* Hero/Header */}
|
||||
{heroBlock ? (
|
||||
<section className="services-hero">
|
||||
{renderTemplate(heroBlock)}
|
||||
</section>
|
||||
) : (
|
||||
<header style={{ marginBottom: '2rem', textAlign: 'center', padding: '2rem 0' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '0.5rem' }}>
|
||||
{page.title}
|
||||
</h1>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Services Grid */}
|
||||
{servicesBlock && (
|
||||
<section className="services-grid" style={{ padding: '2rem 0' }}>
|
||||
{renderTemplate(servicesBlock)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Additional Content */}
|
||||
{otherBlocks.length > 0 && (
|
||||
<section className="services-content" style={{ padding: '2rem 0' }}>
|
||||
{otherBlocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Page Template
|
||||
* Default rendering for custom pages - renders all blocks in order
|
||||
*/
|
||||
function renderCustomPage(page: PageDefinition, blocks: Block[]): React.ReactElement {
|
||||
return (
|
||||
<div className="page-custom" data-page-slug={page.slug}>
|
||||
<header style={{ marginBottom: '2rem', textAlign: 'center' }}>
|
||||
<h1 style={{ fontSize: '2.5rem', fontWeight: 'bold', marginBottom: '1rem' }}>
|
||||
{page.title}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<section className="custom-content" style={{ padding: '2rem 0' }}>
|
||||
{blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type} style={{ marginBottom: '2rem' }}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user