Enhance public access and error handling in site-related views and loaders
- Updated `DebugScopedRateThrottle` to allow public access for blueprint list requests with site filters. - Modified `SiteViewSet` and `SiteBlueprintViewSet` to permit public read access for list requests. - Enhanced `loadSiteDefinition` to resolve site slugs to IDs, improving the loading process for site definitions. - Improved error handling in `SiteDefinitionView` and `loadSiteDefinition` for better user feedback. - Adjusted CSS styles for better layout and alignment in shared components.
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { loadSiteDefinition } from '../loaders/loadSiteDefinition';
|
||||
import { renderLayout } from '../utils/layoutRenderer';
|
||||
import type { SiteDefinition } from '../types';
|
||||
|
||||
function SiteRenderer() {
|
||||
const { siteId } = useParams<{ siteId: string }>();
|
||||
const { siteSlug, pageSlug } = useParams<{ siteSlug: string; pageSlug?: string }>();
|
||||
const [siteDefinition, setSiteDefinition] = useState<SiteDefinition | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!siteId) {
|
||||
setError('Site ID is required');
|
||||
if (!siteSlug) {
|
||||
setError('Site slug is required');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
loadSiteDefinition(siteId)
|
||||
loadSiteDefinition(siteSlug)
|
||||
.then((definition) => {
|
||||
setSiteDefinition(definition);
|
||||
setLoading(false);
|
||||
@@ -26,7 +26,7 @@ function SiteRenderer() {
|
||||
setError(err.message || 'Failed to load site');
|
||||
setLoading(false);
|
||||
});
|
||||
}, [siteId]);
|
||||
}, [siteSlug, pageSlug]);
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading site...</div>;
|
||||
@@ -40,9 +40,122 @@ function SiteRenderer() {
|
||||
return <div>Site not found</div>;
|
||||
}
|
||||
|
||||
// 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
|
||||
}));
|
||||
|
||||
// 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
|
||||
? [currentPage]
|
||||
: siteDefinition.pages.filter(p =>
|
||||
p.status === 'published' ||
|
||||
p.status === 'ready' ||
|
||||
(p.slug === 'home' && p.status !== 'draft' && p.status !== 'generating')
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="site-renderer">
|
||||
{renderLayout(siteDefinition)}
|
||||
<div className="site-renderer" style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '0 1rem',
|
||||
width: '100%'
|
||||
}}>
|
||||
{/* Navigation Menu */}
|
||||
<nav style={{
|
||||
padding: '1rem 0',
|
||||
borderBottom: '1px solid #e5e7eb',
|
||||
marginBottom: '2rem'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
<Link
|
||||
to={`/${siteSlug}`}
|
||||
style={{
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
textDecoration: 'none',
|
||||
color: '#0f172a'
|
||||
}}
|
||||
>
|
||||
{siteDefinition.name}
|
||||
</Link>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: '1.5rem',
|
||||
flexWrap: 'wrap'
|
||||
}}>
|
||||
<Link
|
||||
to={`/${siteSlug}`}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: currentPageSlug === 'home' ? '#4c1d95' : '#64748b',
|
||||
fontWeight: currentPageSlug === 'home' ? 600 : 400
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.slug}
|
||||
to={`/${siteSlug}/${item.slug}`}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: currentPageSlug === item.slug ? '#4c1d95' : '#64748b',
|
||||
fontWeight: currentPageSlug === item.slug ? 600 : 400
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Main Content */}
|
||||
{renderLayout({ ...siteDefinition, pages: pagesToRender })}
|
||||
|
||||
{/* Footer */}
|
||||
<footer style={{
|
||||
marginTop: '4rem',
|
||||
padding: '2rem 0',
|
||||
borderTop: '1px solid #e5e7eb',
|
||||
textAlign: 'center',
|
||||
color: '#64748b',
|
||||
fontSize: '0.875rem'
|
||||
}}>
|
||||
<p style={{ margin: 0 }}>
|
||||
© {new Date().getFullYear()} {siteDefinition.name}. All rights reserved.
|
||||
</p>
|
||||
{siteDefinition.description && (
|
||||
<p style={{ margin: '0.5rem 0 0', fontSize: '0.75rem' }}>
|
||||
{siteDefinition.description}
|
||||
</p>
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user