Update CORS settings, enhance API URL detection, and improve template rendering
- Added new CORS origins for local development and specific IP addresses in `settings.py`. - Refactored API URL retrieval logic in `loadSiteDefinition.ts` and `fileAccess.ts` to auto-detect based on the current origin. - Enhanced error handling in API calls and improved logging for better debugging. - Updated `renderTemplate` function to support additional block types and improved rendering logic for various components in `templateEngine.tsx`.
This commit is contained in:
@@ -7,7 +7,30 @@
|
||||
*/
|
||||
|
||||
const SITES_DATA_PATH = import.meta.env.SITES_DATA_PATH || '/sites';
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'https://api.igny8.com/api';
|
||||
|
||||
/**
|
||||
* Get API base URL - auto-detect based on current origin
|
||||
*/
|
||||
function getApiBaseUrl(): string {
|
||||
const envUrl = import.meta.env.VITE_API_URL;
|
||||
if (envUrl) {
|
||||
return envUrl.endsWith('/api') ? envUrl : `${envUrl}/api`;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const origin = window.location.origin;
|
||||
if (/^\d+\.\d+\.\d+\.\d+/.test(origin) || origin.includes('localhost') || origin.includes('127.0.0.1')) {
|
||||
if (origin.includes(':8024')) {
|
||||
return origin.replace(':8024', ':8011') + '/api';
|
||||
}
|
||||
return origin.split(':')[0] + ':8011/api';
|
||||
}
|
||||
}
|
||||
|
||||
return 'https://api.igny8.com/api';
|
||||
}
|
||||
|
||||
const API_URL = getApiBaseUrl();
|
||||
|
||||
/**
|
||||
* Get file URL for a site asset.
|
||||
|
||||
@@ -189,16 +189,22 @@ function renderNavigation(navigation: SiteDefinition['navigation']): React.React
|
||||
* Render pages.
|
||||
*/
|
||||
function renderPages(pages: SiteDefinition['pages']): React.ReactElement[] {
|
||||
// Filter pages - include ready, generating, and deployed statuses
|
||||
// Only exclude draft status
|
||||
return pages
|
||||
.filter((page) => page.status === 'ready')
|
||||
.filter((page) => page.status !== 'draft')
|
||||
.map((page) => (
|
||||
<div key={page.id} className="page" data-page-slug={page.slug}>
|
||||
<h2>{page.title}</h2>
|
||||
{page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))}
|
||||
{page.blocks && page.blocks.length > 0 ? (
|
||||
page.blocks.map((block, index) => (
|
||||
<div key={index} className="block" data-block-type={block.type}>
|
||||
{renderTemplate(block)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p>No content available for this page.</p>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
@@ -11,8 +11,20 @@ import type { Block } from '../types';
|
||||
* Render a block using the template engine.
|
||||
* Imports shared components dynamically.
|
||||
*/
|
||||
export function renderTemplate(block: Block): React.ReactElement {
|
||||
const { type, data } = block;
|
||||
export function renderTemplate(block: Block | any): React.ReactElement {
|
||||
// Handle both formats: { type, data } or { type, ...properties }
|
||||
let type: string;
|
||||
let data: Record<string, any>;
|
||||
|
||||
if (block.type && block.data) {
|
||||
// Standard format: { type, data }
|
||||
type = block.type;
|
||||
data = block.data;
|
||||
} else {
|
||||
// API format: { type, heading, subheading, content, ... }
|
||||
type = block.type;
|
||||
data = block;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to import shared component
|
||||
@@ -20,6 +32,10 @@ export function renderTemplate(block: Block): React.ReactElement {
|
||||
switch (type) {
|
||||
case 'hero':
|
||||
return renderHeroBlock(data);
|
||||
case 'features':
|
||||
return renderFeaturesBlock(data);
|
||||
case 'testimonials':
|
||||
return renderTestimonialsBlock(data);
|
||||
case 'text':
|
||||
return renderTextBlock(data);
|
||||
case 'image':
|
||||
@@ -42,6 +58,14 @@ export function renderTemplate(block: Block): React.ReactElement {
|
||||
return renderFormBlock(data);
|
||||
case 'accordion':
|
||||
return renderAccordionBlock(data);
|
||||
case 'faq':
|
||||
return renderFAQBlock(data);
|
||||
case 'cta':
|
||||
return renderCTABlock(data);
|
||||
case 'services':
|
||||
return renderServicesBlock(data);
|
||||
case 'stats':
|
||||
return renderStatsBlock(data);
|
||||
default:
|
||||
return <div className="block-unknown">Unknown block type: {type}</div>;
|
||||
}
|
||||
@@ -55,12 +79,18 @@ export function renderTemplate(block: Block): React.ReactElement {
|
||||
* Render hero block.
|
||||
*/
|
||||
function renderHeroBlock(data: Record<string, any>): React.ReactElement {
|
||||
// Handle both API format (heading/subheading) and template format (title/subtitle)
|
||||
const title = data.heading || data.title || 'Hero Title';
|
||||
const subtitle = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content.join(' ') : data.content;
|
||||
|
||||
return (
|
||||
<section className="block-hero" style={{ padding: '4rem 2rem', textAlign: 'center', background: data.background || '#f0f0f0' }}>
|
||||
<h1>{data.title || 'Hero Title'}</h1>
|
||||
{data.subtitle && <p>{data.subtitle}</p>}
|
||||
<h1>{title}</h1>
|
||||
{subtitle && <p style={{ fontSize: '1.2rem', color: '#666', marginTop: '1rem' }}>{subtitle}</p>}
|
||||
{content && <p style={{ marginTop: '1rem' }}>{content}</p>}
|
||||
{data.buttonText && (
|
||||
<a href={data.buttonLink || '#'} className="button">
|
||||
<a href={data.buttonLink || '#'} className="button" style={{ display: 'inline-block', marginTop: '2rem', padding: '0.75rem 1.5rem', background: '#007bff', color: 'white', textDecoration: 'none', borderRadius: '4px' }}>
|
||||
{data.buttonText}
|
||||
</a>
|
||||
)}
|
||||
@@ -243,3 +273,154 @@ function renderAccordionBlock(data: Record<string, any>): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render features block.
|
||||
*/
|
||||
function renderFeaturesBlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'Features';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content : [];
|
||||
const layout = data.layout || 'two-column';
|
||||
|
||||
return (
|
||||
<section className="block-features" style={{ padding: '3rem 2rem', background: data.background || 'transparent' }}>
|
||||
{heading && <h2 style={{ textAlign: 'center', marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ textAlign: 'center', color: '#666', marginBottom: '2rem' }}>{subheading}</p>}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: layout === 'two-column' ? 'repeat(2, 1fr)' : 'repeat(3, 1fr)',
|
||||
gap: '2rem',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
{content.map((item: string, index: number) => (
|
||||
<div key={index} style={{ padding: '1.5rem', border: '1px solid #eee', borderRadius: '8px' }}>
|
||||
<p>{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render testimonials block.
|
||||
*/
|
||||
function renderTestimonialsBlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'Testimonials';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content : [];
|
||||
const layout = data.layout || 'cards';
|
||||
|
||||
return (
|
||||
<section className="block-testimonials" style={{ padding: '3rem 2rem', background: data.background || '#f9f9f9' }}>
|
||||
{heading && <h2 style={{ textAlign: 'center', marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ textAlign: 'center', color: '#666', marginBottom: '2rem' }}>{subheading}</p>}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: layout === 'cards' ? 'repeat(auto-fit, minmax(300px, 1fr))' : '1fr',
|
||||
gap: '2rem',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
{content.map((item: string, index: number) => (
|
||||
<div key={index} style={{ padding: '2rem', background: 'white', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
|
||||
<p style={{ fontStyle: 'italic', marginBottom: '1rem' }}>"{item}"</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render FAQ block.
|
||||
*/
|
||||
function renderFAQBlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'FAQ';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content : [];
|
||||
|
||||
return (
|
||||
<section className="block-faq" style={{ padding: '3rem 2rem' }}>
|
||||
{heading && <h2 style={{ marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ color: '#666', marginBottom: '2rem' }}>{subheading}</p>}
|
||||
<div>
|
||||
{content.map((item: string, index: number) => (
|
||||
<div key={index} style={{ marginBottom: '1rem', padding: '1rem', borderBottom: '1px solid #eee' }}>
|
||||
<p><strong>Q:</strong> {item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render CTA (Call to Action) block.
|
||||
*/
|
||||
function renderCTABlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'Call to Action';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content[0] : data.content;
|
||||
|
||||
return (
|
||||
<section className="block-cta" style={{ padding: '4rem 2rem', textAlign: 'center', background: data.background || '#007bff', color: 'white' }}>
|
||||
{heading && <h2 style={{ marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ marginBottom: '2rem', fontSize: '1.1rem' }}>{subheading}</p>}
|
||||
{content && <p style={{ marginBottom: '2rem' }}>{content}</p>}
|
||||
{data.buttonText && (
|
||||
<a href={data.buttonLink || '#'} style={{ display: 'inline-block', padding: '1rem 2rem', background: 'white', color: '#007bff', textDecoration: 'none', borderRadius: '4px', fontWeight: 'bold' }}>
|
||||
{data.buttonText}
|
||||
</a>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render services block.
|
||||
*/
|
||||
function renderServicesBlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'Services';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content : [];
|
||||
|
||||
return (
|
||||
<section className="block-services" style={{ padding: '3rem 2rem' }}>
|
||||
{heading && <h2 style={{ marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ color: '#666', marginBottom: '2rem' }}>{subheading}</p>}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '2rem' }}>
|
||||
{content.map((item: string, index: number) => (
|
||||
<div key={index} style={{ padding: '1.5rem', border: '1px solid #ddd', borderRadius: '8px' }}>
|
||||
<p>{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render stats block.
|
||||
*/
|
||||
function renderStatsBlock(data: Record<string, any>): React.ReactElement {
|
||||
const heading = data.heading || data.title || 'Statistics';
|
||||
const subheading = data.subheading || data.subtitle;
|
||||
const content = Array.isArray(data.content) ? data.content : [];
|
||||
|
||||
return (
|
||||
<section className="block-stats" style={{ padding: '3rem 2rem', background: data.background || '#f9f9f9' }}>
|
||||
{heading && <h2 style={{ textAlign: 'center', marginBottom: '1rem' }}>{heading}</h2>}
|
||||
{subheading && <p style={{ textAlign: 'center', color: '#666', marginBottom: '2rem' }}>{subheading}</p>}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{content.map((item: string, index: number) => (
|
||||
<div key={index} style={{ textAlign: 'center', padding: '1.5rem' }}>
|
||||
<p style={{ fontSize: '1.1rem' }}>{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user