feat: enable full section editing, background styling, animations and live file persistence
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"updated_at": "2026-08-09T21:48:17.969Z",
|
||||||
"siteName": "N&D IT Solutions",
|
"siteName": "N&D IT Solutions",
|
||||||
"metaDescription": "High-Performance Webdesign & Digital Solutions - 100% DSGVO-konform.",
|
"metaDescription": "High-Performance Webdesign & Digital Solutions - 100% DSGVO-konform.",
|
||||||
"is_demo_mode": false,
|
"is_demo_mode": false,
|
||||||
@@ -131,23 +132,7 @@
|
|||||||
"slug": "/",
|
"slug": "/",
|
||||||
"title": "Startseite (Hauptseite)",
|
"title": "Startseite (Hauptseite)",
|
||||||
"is_published": true,
|
"is_published": true,
|
||||||
"sections": [
|
"sections": []
|
||||||
{
|
|
||||||
"id": "hero",
|
|
||||||
"type": "HeroSection",
|
|
||||||
"settings": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bento",
|
|
||||||
"type": "BentoGrid",
|
|
||||||
"settings": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "contact",
|
|
||||||
"type": "ContactSection",
|
|
||||||
"settings": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "page_templates",
|
"id": "page_templates",
|
||||||
|
|||||||
@@ -5,18 +5,22 @@ interface Props {
|
|||||||
config: SiteConfig['bento'];
|
config: SiteConfig['bento'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { config } = Astro.props;
|
const props = Astro.props;
|
||||||
|
const config = props.config || props.settings || props;
|
||||||
|
const sectionTitle = config.sectionTitle || config.title || 'Unsere Leistungen';
|
||||||
|
const sectionSubtitle = config.sectionSubtitle || config.subtitle || '';
|
||||||
|
const cards = config.cards || config.items || [];
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="services" class={`py-20 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative ${config.animation ? `anim-${config.animation}` : ''}`} style={(config as any).bg_color ? `background-color: ${(config as any).bg_color};` : ''}>
|
<section id="services" class={`py-20 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative ${config.animation ? `anim-${config.animation}` : ''}`} style={(config as any).bg_color ? `background-color: ${(config as any).bg_color};` : ''}>
|
||||||
|
|
||||||
<div class="text-center max-w-3xl mx-auto mb-16 space-y-3">
|
<div class="text-center max-w-3xl mx-auto mb-16 space-y-3">
|
||||||
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{config.sectionTitle}</h2>
|
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{sectionTitle}</h2>
|
||||||
<p class="text-slate-400 text-xs sm:text-sm">{config.sectionSubtitle}</p>
|
<p class="text-slate-400 text-xs sm:text-sm">{sectionSubtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{config.cards.map((card) => (
|
{cards.map((card: any) => (
|
||||||
<div class={`relative glass-panel glass-panel-hover rounded-2xl p-8 flex flex-col justify-between overflow-hidden group ${card.colSpan === 2 ? 'md:col-span-2' : 'md:col-span-1'} ${card.animation ? `anim-${card.animation}` : ''}`}>
|
<div class={`relative glass-panel glass-panel-hover rounded-2xl p-8 flex flex-col justify-between overflow-hidden group ${card.colSpan === 2 ? 'md:col-span-2' : 'md:col-span-1'} ${card.animation ? `anim-${card.animation}` : ''}`}>
|
||||||
<div class="space-y-4 relative z-10">
|
<div class="space-y-4 relative z-10">
|
||||||
{card.badge && (
|
{card.badge && (
|
||||||
|
|||||||
@@ -6,15 +6,19 @@ interface Props {
|
|||||||
smtpConfig: SmtpConfig;
|
smtpConfig: SmtpConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { contactConfig, smtpConfig } = Astro.props;
|
const props = Astro.props;
|
||||||
|
const contactConfig = props.contactConfig || props.settings || props;
|
||||||
|
const smtpConfig = props.smtpConfig || { isConfigured: false };
|
||||||
|
const title = contactConfig.title || 'Kontaktieren Sie uns';
|
||||||
|
const subtitle = contactConfig.subtitle || 'Wir freuen uns auf Ihre Nachricht.';
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="contact" class="py-20 px-4 sm:px-6 lg:px-8 max-w-4xl mx-auto relative" style={(contactConfig as any).bg_color ? `background-color: ${(contactConfig as any).bg_color};` : ''}>
|
<section id="contact" class="py-20 px-4 sm:px-6 lg:px-8 max-w-4xl mx-auto relative" style={(contactConfig as any).bg_color ? `background-color: ${(contactConfig as any).bg_color};` : ''}>
|
||||||
|
|
||||||
<div class="glass-panel rounded-3xl p-8 sm:p-12 relative overflow-hidden">
|
<div class="glass-panel rounded-3xl p-8 sm:p-12 relative overflow-hidden">
|
||||||
<div class="text-center space-y-3 mb-10">
|
<div class="text-center space-y-3 mb-10">
|
||||||
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{contactConfig.title}</h2>
|
<h2 class="text-3xl font-extrabold text-white tracking-tight uppercase">{title}</h2>
|
||||||
<p class="text-slate-400 text-xs sm:text-sm">{contactConfig.subtitle}</p>
|
<p class="text-slate-400 text-xs sm:text-sm">{subtitle}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!smtpConfig.isConfigured && (
|
{!smtpConfig.isConfigured && (
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ interface Props {
|
|||||||
contextData?: Record<string, any>;
|
contextData?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { config, contextData = {} } = Astro.props;
|
const props = Astro.props;
|
||||||
|
const config = props.config || props.settings || props;
|
||||||
|
const contextData = props.contextData || {};
|
||||||
|
|
||||||
const heroTitle = resolveDynamicBinding(config.title, contextData);
|
const heroTitle = resolveDynamicBinding(config.title || config.heroTitle || 'Willkommen', contextData);
|
||||||
const heroSubtitle = resolveDynamicBinding(config.subtitle, contextData);
|
const heroSubtitle = resolveDynamicBinding(config.subtitle || config.heroSubtitle || '', contextData);
|
||||||
const heroBadge = resolveDynamicBinding(config.badge, contextData);
|
const heroBadge = resolveDynamicBinding(config.badge || config.heroBadge || '', contextData);
|
||||||
|
|
||||||
const elements = config.elements || [];
|
const elements = config.elements || [];
|
||||||
const badgeElements = elements.filter((el: UIComponent) => el.slotId === 'hero-badge-slot');
|
const badgeElements = elements.filter((el: UIComponent) => el.slotId === 'hero-badge-slot');
|
||||||
|
|||||||
@@ -285,9 +285,10 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
if (fieldsContainer) {
|
if (fieldsContainer) {
|
||||||
fieldsContainer.innerHTML = '';
|
fieldsContainer.innerHTML = '';
|
||||||
const allKeys = new Set(Object.keys(currentSettings));
|
const allKeys = new Set(Object.keys(currentSettings));
|
||||||
if (!allKeys.has('bg_color')) {
|
if (!allKeys.has('title')) allKeys.add('title');
|
||||||
allKeys.add('bg_color');
|
if (!allKeys.has('subtitle')) allKeys.add('subtitle');
|
||||||
}
|
if (!allKeys.has('bg_color')) allKeys.add('bg_color');
|
||||||
|
if (!allKeys.has('animation')) allKeys.add('animation');
|
||||||
|
|
||||||
allKeys.forEach((key) => {
|
allKeys.forEach((key) => {
|
||||||
const value = currentSettings[key] || '';
|
const value = currentSettings[key] || '';
|
||||||
@@ -312,6 +313,16 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
} else if (key === 'animation') {
|
||||||
|
fieldWrapper.innerHTML = `
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1 uppercase tracking-wider">✨ Animation / Effekt</label>
|
||||||
|
<select name="animation" class="w-full bg-slate-800 border border-slate-700 rounded-lg p-2 text-xs text-white focus:outline-none focus:border-sky-500">
|
||||||
|
<option value="" ${!value ? 'selected' : ''}>Keine Animation</option>
|
||||||
|
<option value="fade-in" ${value === 'fade-in' ? 'selected' : ''}>Fade In (Einblenden)</option>
|
||||||
|
<option value="slide-up" ${value === 'slide-up' ? 'selected' : ''}>Slide Up (Von unten)</option>
|
||||||
|
<option value="zoom-in" ${value === 'zoom-in' ? 'selected' : ''}>Zoom In (Heranzoomen)</option>
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
} else {
|
} else {
|
||||||
fieldWrapper.innerHTML = `
|
fieldWrapper.innerHTML = `
|
||||||
<label class="block text-xs font-semibold text-slate-400 mb-1 uppercase tracking-wider">${key}</label>
|
<label class="block text-xs font-semibold text-slate-400 mb-1 uppercase tracking-wider">${key}</label>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
targetPage.sections.push(newSection);
|
targetPage.sections.push(newSection);
|
||||||
|
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
|
await fs.writeFile(livePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true, section: newSection }), { status: 201 });
|
return new Response(JSON.stringify({ success: true, section: newSection }), { status: 201 });
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
targetPage.sections = targetPage.sections.filter((sec: any) => sec.id !== section_id);
|
targetPage.sections = targetPage.sections.filter((sec: any) => sec.id !== section_id);
|
||||||
|
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
|
await fs.writeFile(livePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true, message: 'Sektion gelöscht' }), { status: 200 });
|
return new Response(JSON.stringify({ success: true, message: 'Sektion gelöscht' }), { status: 200 });
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
targetSection.settings = { ...targetSection.settings, ...settings };
|
targetSection.settings = { ...targetSection.settings, ...settings };
|
||||||
|
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
|
await fs.writeFile(livePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true, settings: targetSection.settings }), { status: 200 });
|
return new Response(JSON.stringify({ success: true, settings: targetSection.settings }), { status: 200 });
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
// 3. Speichere ausschließlich in site.config.draft.json
|
// 3. Speichere ausschließlich in site.config.draft.json
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
|
await fs.writeFile(livePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
await fs.writeFile(draftPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
|
|||||||
Reference in New Issue
Block a user