Compare commits
7 Commits
15b2a34957
...
4355bb8406
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4355bb8406 | ||
|
|
30b407d2e8 | ||
|
|
591eba43c1 | ||
|
|
895fcd4323 | ||
|
|
adefc89ab2 | ||
|
|
aec7c78fb0 | ||
|
|
10d534e836 |
@@ -20,7 +20,10 @@ interface Props {
|
|||||||
|
|
||||||
const { siteConfig, themeConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
const { siteConfig, themeConfig } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
||||||
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
const activePreset = THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark;
|
||||||
const colors = activePreset.colors;
|
const colors = {
|
||||||
|
...activePreset.colors,
|
||||||
|
...(themeConfig.customColors || {})
|
||||||
|
};
|
||||||
|
|
||||||
const { title, description = "Professionelles Webdesign & moderne Websites.", ogImage } = Astro.props;
|
const { title, description = "Professionelles Webdesign & moderne Websites.", ogImage } = Astro.props;
|
||||||
|
|
||||||
@@ -54,10 +57,11 @@ const { title, description = "Professionelles Webdesign & moderne Websites.", og
|
|||||||
|
|
||||||
html.light {
|
html.light {
|
||||||
--theme-bg: #F8FAFC;
|
--theme-bg: #F8FAFC;
|
||||||
--theme-card-bg: rgba(255, 255, 255, 0.7);
|
--theme-card-bg: rgba(255, 255, 255, 0.85);
|
||||||
--theme-text: #0F172A;
|
--theme-text: #0F172A;
|
||||||
--theme-border: rgba(0, 0, 0, 0.1);
|
--theme-border: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Inline theme initialization to prevent FOUC -->
|
<!-- Inline theme initialization to prevent FOUC -->
|
||||||
|
|||||||
@@ -169,15 +169,81 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 4. Preset Wechsel
|
// 4. Preset Wechsel
|
||||||
document.getElementById('preset-selector')?.addEventListener('change', (e: any) => {
|
document.getElementById('preset-selector')?.addEventListener('change', async (e: any) => {
|
||||||
sendToIframe('THEME_UPDATE', { preset: e.target.value });
|
const presetKey = e.target.value;
|
||||||
|
sendToIframe('THEME_UPDATE', { preset: presetKey });
|
||||||
|
|
||||||
|
// Speichere Preset in theme.config.json
|
||||||
|
await fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ themeConfig: { activePreset: presetKey } })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper zum Verschieben & Speichern der Sektionen-Reihenfolge
|
||||||
|
async function updateSectionsOrder(newSections: any[]) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const pageId = urlParams.get('page_id') || 'page_home';
|
||||||
|
|
||||||
|
const res = await fetch('/api/editor/update-section', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ page_id: pageId, sections: newSections })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSectionsFromDOM() {
|
||||||
|
const list: any[] = [];
|
||||||
|
document.querySelectorAll('#section-list > div').forEach(card => {
|
||||||
|
const id = card.getAttribute('data-section-id');
|
||||||
|
const type = card.getAttribute('data-section-type');
|
||||||
|
const settings = JSON.parse(card.getAttribute('data-settings') || '{}');
|
||||||
|
list.push({ id, type, settings });
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event: Sektion nach oben / unten verschieben
|
||||||
|
document.querySelectorAll('.btn-move-up').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e: any) => {
|
||||||
|
const sections = getSectionsFromDOM();
|
||||||
|
const card = e.target.closest('[data-section-id]');
|
||||||
|
const sectionId = card.getAttribute('data-section-id');
|
||||||
|
const index = sections.findIndex(s => s.id === sectionId);
|
||||||
|
if (index > 0) {
|
||||||
|
const temp = sections[index];
|
||||||
|
sections[index] = sections[index - 1];
|
||||||
|
sections[index - 1] = temp;
|
||||||
|
await updateSectionsOrder(sections);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.btn-move-down').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (e: any) => {
|
||||||
|
const sections = getSectionsFromDOM();
|
||||||
|
const card = e.target.closest('[data-section-id]');
|
||||||
|
const sectionId = card.getAttribute('data-section-id');
|
||||||
|
const index = sections.findIndex(s => s.id === sectionId);
|
||||||
|
if (index >= 0 && index < sections.length - 1) {
|
||||||
|
const temp = sections[index];
|
||||||
|
sections[index] = sections[index + 1];
|
||||||
|
sections[index + 1] = temp;
|
||||||
|
await updateSectionsOrder(sections);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. Event: Sektion hinzufügen
|
// 5. Event: Sektion hinzufügen
|
||||||
document.getElementById('btn-add-section')?.addEventListener('click', async () => {
|
document.getElementById('btn-add-section')?.addEventListener('click', async () => {
|
||||||
const sectionType = (document.getElementById('select-new-section-type') as HTMLSelectElement).value;
|
const sectionType = (document.getElementById('select-new-section-type') as HTMLSelectElement).value;
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const pageId = urlParams.get('page_id');
|
const pageId = urlParams.get('page_id') || 'page_home';
|
||||||
|
|
||||||
const res = await fetch('/api/editor/add-section', {
|
const res = await fetch('/api/editor/add-section', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -196,7 +262,7 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
const card = e.target.closest('[data-section-id]');
|
const card = e.target.closest('[data-section-id]');
|
||||||
const sectionId = card?.getAttribute('data-section-id');
|
const sectionId = card?.getAttribute('data-section-id');
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const pageId = urlParams.get('page_id');
|
const pageId = urlParams.get('page_id') || 'page_home';
|
||||||
|
|
||||||
const res = await fetch('/api/editor/delete-section', {
|
const res = await fetch('/api/editor/delete-section', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -259,7 +325,7 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
formData.forEach((val, key) => { settings[key] = val; });
|
formData.forEach((val, key) => { settings[key] = val; });
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const pageId = urlParams.get('page_id');
|
const pageId = urlParams.get('page_id') || 'page_home';
|
||||||
|
|
||||||
const res = await fetch('/api/editor/update-section-settings', {
|
const res = await fetch('/api/editor/update-section-settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -269,9 +335,8 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
|
|||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
editModal?.close();
|
editModal?.close();
|
||||||
// Schicke Live-Reload Signal an Iframe Bridge
|
window.location.reload();
|
||||||
const iframeElement = document.getElementById('preview-iframe') as HTMLIFrameElement;
|
|
||||||
iframeElement?.contentWindow?.postMessage({ type: 'RELOAD_CANVAS' }, '*');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -72,12 +72,13 @@ const presets = Object.values(THEME_PRESETS);
|
|||||||
<textarea id="metaDescription" rows="2" class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 resize-none">{siteConfig.metaDescription}</textarea>
|
<textarea id="metaDescription" rows="2" class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 resize-none">{siteConfig.metaDescription}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<label class="flex items-center gap-3 pt-2 p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl cursor-pointer">
|
<label class="flex items-center gap-3 pt-2 p-3 bg-amber-500/10 border border-amber-500/20 rounded-xl cursor-pointer">
|
||||||
<input type="checkbox" id="isDemoMode" checked={(siteConfig as any).is_demo_mode ?? false} class="rounded text-amber-400 focus:ring-0 h-4 w-4" />
|
<input type="checkbox" id="isDemoMode" checked={!!siteConfig.is_demo_mode} class="rounded text-amber-400 focus:ring-0 h-4 w-4" />
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-amber-400 font-bold block">🎭 Demo-Modus aktivieren</span>
|
<span class="text-xs text-amber-400 font-bold block">🎭 Demo-Modus aktivieren</span>
|
||||||
<span class="text-[10px] text-slate-400">Zeigt einen festen Demo-Hinweis für Interessenten auf der Live-Website</span>
|
<span class="text-[10px] text-slate-400">Zeigt einen festen Demo-Hinweis für Interessenten auf der Live-Website</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider pt-2 border-t border-white/10">Unternehmens-Stammdaten (Dynamic Data Binding)</h3>
|
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider pt-2 border-t border-white/10">Unternehmens-Stammdaten (Dynamic Data Binding)</h3>
|
||||||
@@ -99,19 +100,66 @@ const presets = Object.values(THEME_PRESETS);
|
|||||||
<input type="text" id="infoAddress" value={(siteConfig as any).site_info?.address || ''} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
<input type="text" id="infoAddress" value={(siteConfig as any).site_info?.address || ''} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Global Header Config -->
|
||||||
|
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider pt-4 border-t border-white/10">Globaler Header / Navigation Bar</h3>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">Brand Name (z.B. N&D)</label>
|
||||||
|
<input type="text" id="headerBrandName" value={siteConfig.header?.brandName || 'N&D'} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">Brand Highlight (z.B. i-t SOLUTIONS)</label>
|
||||||
|
<input type="text" id="headerBrandHighlight" value={siteConfig.header?.brandHighlight || 'i-t SOLUTIONS'} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">CTA Button Text</label>
|
||||||
|
<input type="text" id="headerCtaText" value={siteConfig.header?.ctaText || 'Erstgespräch buchen'} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">CTA Button Link</label>
|
||||||
|
<input type="text" id="headerCtaLink" value={siteConfig.header?.ctaLink || '#contact'} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
|
</div>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer sm:col-span-2">
|
||||||
|
<input type="checkbox" id="headerShowThemeToggle" checked={siteConfig.header?.showThemeToggle !== false} class="rounded text-sky-400 focus:ring-0" />
|
||||||
|
<span class="text-xs text-slate-300 font-semibold">Theme-Toggle Button (☀️/🌙) anzeigen</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer sm:col-span-2">
|
||||||
|
<input type="checkbox" id="headerGlassEffect" checked={siteConfig.header?.glassEffect !== false} class="rounded text-sky-400 focus:ring-0" />
|
||||||
|
<span class="text-xs text-slate-300 font-semibold">Liquid Glassmorphism Effekt aktivieren</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Global Footer Config -->
|
||||||
|
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider pt-4 border-t border-white/10">Globaler Footer</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">Footer Beschreibungstext</label>
|
||||||
|
<textarea id="footerBrandDescription" rows="2" class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400 resize-none">{siteConfig.footer?.brandDescription || ''}</textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-slate-400 mb-1">Copyright Text</label>
|
||||||
|
<input type="text" id="footerCopyrightText" value={siteConfig.footer?.copyrightText || ''} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
|
||||||
|
</div>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" id="footerShowLegalLinks" checked={siteConfig.footer?.showLegalLinks !== false} class="rounded text-sky-400 focus:ring-0" />
|
||||||
|
<span class="text-xs text-slate-300 font-semibold">Rechtliche Links (Datenschutz & Impressum) im Footer anzeigen</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar Column -->
|
<!-- Sidebar Column -->
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
|
|
||||||
<!-- Theme Preset Picker -->
|
<!-- Theme Preset Picker & Custom Colors -->
|
||||||
<div class="glass-panel rounded-2xl p-6 space-y-4">
|
<div class="glass-panel rounded-2xl p-6 space-y-4">
|
||||||
<h2 class="text-lg font-bold text-sky-400 uppercase tracking-wider flex items-center gap-2">
|
<h2 class="text-lg font-bold text-sky-400 uppercase tracking-wider flex items-center gap-2">
|
||||||
Theme Preset (10 Styles)
|
Theme Preset (10 Styles)
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-2 max-h-[320px] overflow-y-auto pr-1">
|
<div class="space-y-2 max-h-[220px] overflow-y-auto pr-1">
|
||||||
{presets.map((p) => (
|
{presets.map((p) => (
|
||||||
<label class={`flex items-center justify-between p-3 rounded-xl border transition-all cursor-pointer ${themeConfig.activePreset === p.id ? 'border-sky-400 bg-sky-400/10' : 'border-white/5 hover:border-white/20 bg-slate-950/40'}`}>
|
<label class={`flex items-center justify-between p-3 rounded-xl border transition-all cursor-pointer ${themeConfig.activePreset === p.id ? 'border-sky-400 bg-sky-400/10' : 'border-white/5 hover:border-white/20 bg-slate-950/40'}`}>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -125,7 +173,30 @@ const presets = Object.values(THEME_PRESETS);
|
|||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Colors Section -->
|
||||||
|
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider pt-3 border-t border-white/10">Individuelle Farbvergabe (Overrides)</h3>
|
||||||
|
<p class="text-[10px] text-slate-400">Überschreibe hier gezielt einzelne Preset-Farben:</p>
|
||||||
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] text-slate-400 mb-1">Hintergrund (Bg)</label>
|
||||||
|
<input type="color" id="customColorBg" value={themeConfig.customColors?.bg || (THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark).colors.bg} class="w-full h-8 rounded bg-slate-900 border border-white/10 cursor-pointer p-0.5" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] text-slate-400 mb-1">Akzentfarbe</label>
|
||||||
|
<input type="color" id="customColorAccent" value={themeConfig.customColors?.accent || (THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark).colors.accent} class="w-full h-8 rounded bg-slate-900 border border-white/10 cursor-pointer p-0.5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] text-slate-400 mb-1">Textfarbe</label>
|
||||||
|
<input type="color" id="customColorText" value={themeConfig.customColors?.text || (THEME_PRESETS[themeConfig.activePreset] || THEME_PRESETS.corporateDark).colors.text} class="w-full h-8 rounded bg-slate-900 border border-white/10 cursor-pointer p-0.5" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-[10px] text-slate-400 mb-1">Rahmenfarbe (Border)</label>
|
||||||
|
<input type="text" id="customColorBorder" value={themeConfig.customColors?.border || ''} placeholder="rgba(255,255,255,0.1)" class="w-full h-8 rounded bg-slate-900 border border-white/10 text-[10px] px-2 text-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- SMTP Config -->
|
<!-- SMTP Config -->
|
||||||
<div class="glass-panel rounded-3xl p-6 space-y-4">
|
<div class="glass-panel rounded-3xl p-6 space-y-4">
|
||||||
@@ -192,12 +263,33 @@ const presets = Object.values(THEME_PRESETS);
|
|||||||
email: getVal('infoEmail'),
|
email: getVal('infoEmail'),
|
||||||
phone: getVal('infoPhone'),
|
phone: getVal('infoPhone'),
|
||||||
address: getVal('infoAddress')
|
address: getVal('infoAddress')
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
brandName: getVal('headerBrandName'),
|
||||||
|
brandHighlight: getVal('headerBrandHighlight'),
|
||||||
|
ctaText: getVal('headerCtaText'),
|
||||||
|
ctaLink: getVal('headerCtaLink'),
|
||||||
|
showThemeToggle: getBool('headerShowThemeToggle'),
|
||||||
|
glassEffect: getBool('headerGlassEffect')
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
brandDescription: getVal('footerBrandDescription'),
|
||||||
|
copyrightText: getVal('footerCopyrightText'),
|
||||||
|
showLegalLinks: getBool('footerShowLegalLinks')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
activePreset: selectedPreset
|
activePreset: selectedPreset,
|
||||||
|
customColors: {
|
||||||
|
bg: getVal('customColorBg'),
|
||||||
|
accent: getVal('customColorAccent'),
|
||||||
|
text: getVal('customColorText'),
|
||||||
|
border: getVal('customColorBorder')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
smtpConfig: {
|
smtpConfig: {
|
||||||
host: getVal('smtpHost'),
|
host: getVal('smtpHost'),
|
||||||
port: getNum('smtpPort'),
|
port: getNum('smtpPort'),
|
||||||
|
|||||||
@@ -11,16 +11,35 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
fs.mkdirSync(dataDir, { recursive: true });
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { siteConfig: currentSite, themeConfig: currentTheme, smtpConfig: currentSmtp } = loadWaasConfigs(Astro.request.headers.get('user-agent') ?? '');
|
const userAgent = request.headers.get('user-agent') ?? '';
|
||||||
|
const { siteConfig: currentSite, themeConfig: currentTheme, smtpConfig: currentSmtp } = loadWaasConfigs(userAgent);
|
||||||
|
|
||||||
|
|
||||||
if (body.siteConfig) {
|
if (body.siteConfig) {
|
||||||
const newSite = {
|
const newSite = {
|
||||||
...currentSite,
|
...currentSite,
|
||||||
...body.siteConfig,
|
...body.siteConfig,
|
||||||
hero: { ...currentSite.hero, ...body.siteConfig.hero }
|
hero: { ...(currentSite.hero || {}), ...(body.siteConfig.hero || {}) },
|
||||||
|
site_info: { ...((currentSite as any).site_info || {}), ...(body.siteConfig.site_info || {}) },
|
||||||
|
header: { ...(currentSite.header || {}), ...(body.siteConfig.header || {}) },
|
||||||
|
footer: { ...(currentSite.footer || {}), ...(body.siteConfig.footer || {}) }
|
||||||
};
|
};
|
||||||
fs.writeFileSync(path.join(dataDir, 'site.config.json'), JSON.stringify(newSite, null, 2), 'utf8');
|
|
||||||
|
const sitePath = path.join(dataDir, 'site.config.json');
|
||||||
|
const draftPath = path.join(dataDir, 'site.config.draft.json');
|
||||||
|
fs.writeFileSync(sitePath, JSON.stringify(newSite, null, 2), 'utf8');
|
||||||
|
|
||||||
|
// Falls bereits ein Draft existiert, site_info & is_demo_mode dort ebenfalls aktualisieren
|
||||||
|
if (fs.existsSync(draftPath)) {
|
||||||
|
try {
|
||||||
|
const draftContent = JSON.parse(fs.readFileSync(draftPath, 'utf8'));
|
||||||
|
const updatedDraft = { ...draftContent, ...newSite };
|
||||||
|
fs.writeFileSync(draftPath, JSON.stringify(updatedDraft, null, 2), 'utf8');
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (body.themeConfig) {
|
if (body.themeConfig) {
|
||||||
const newTheme = { ...currentTheme, ...body.themeConfig };
|
const newTheme = { ...currentTheme, ...body.themeConfig };
|
||||||
|
|||||||
@@ -8,18 +8,29 @@ const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'app', 'data')
|
|||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
const draftConfig = await request.json();
|
const draftBody = await request.json();
|
||||||
if (!draftConfig || typeof draftConfig !== 'object') {
|
const draftPath = path.join(DATA_DIR, 'site.config.draft.json');
|
||||||
return new Response(JSON.stringify({ error: 'Ungültige Konfigurationsdaten' }), { status: 400 });
|
const livePath = path.join(DATA_DIR, 'site.config.json');
|
||||||
|
|
||||||
|
let baseConfig: any = {};
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(draftPath, 'utf-8');
|
||||||
|
baseConfig = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
const rawLive = await fs.readFile(livePath, 'utf-8');
|
||||||
|
baseConfig = JSON.parse(rawLive);
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const draftPath = path.join(DATA_DIR, 'site.config.draft.json');
|
const updatedConfig = { ...baseConfig, ...draftBody, updated_at: new Date().toISOString() };
|
||||||
|
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
await fs.writeFile(draftPath, JSON.stringify(draftConfig, null, 2), 'utf-8');
|
await fs.writeFile(draftPath, JSON.stringify(updatedConfig, null, 2), 'utf-8');
|
||||||
|
|
||||||
return new Response(JSON.stringify({ success: true, message: 'Entwurf erfolgreich gespeichert' }), { status: 200 });
|
return new Response(JSON.stringify({ success: true, message: 'Entwurf erfolgreich gespeichert' }), { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return new Response(JSON.stringify({ error: 'Fehler beim Speichern des Entwurfs' }), { status: 500 });
|
return new Response(JSON.stringify({ error: 'Fehler beim Speichern des Entwurfs' }), { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export interface SiteConfig {
|
|||||||
sectionsOrder: string[];
|
sectionsOrder: string[];
|
||||||
|
|
||||||
sectionsDetails?: SectionConfig[];
|
sectionsDetails?: SectionConfig[];
|
||||||
|
is_demo_mode?: boolean;
|
||||||
site_info?: {
|
site_info?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
company_name?: string;
|
company_name?: string;
|
||||||
@@ -99,10 +100,14 @@ export interface ThemeConfig {
|
|||||||
glassBorderRadius?: string; // 'rounded-lg' | 'rounded-xl' | 'rounded-2xl'
|
glassBorderRadius?: string; // 'rounded-lg' | 'rounded-xl' | 'rounded-2xl'
|
||||||
customColors?: {
|
customColors?: {
|
||||||
bg?: string;
|
bg?: string;
|
||||||
|
cardBg?: string;
|
||||||
|
text?: string;
|
||||||
accent?: string;
|
accent?: string;
|
||||||
|
border?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface SmtpConfig {
|
export interface SmtpConfig {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
@@ -117,6 +122,8 @@ export interface SmtpConfig {
|
|||||||
export const FALLBACK_SITE_CONFIG: SiteConfig = {
|
export const FALLBACK_SITE_CONFIG: SiteConfig = {
|
||||||
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,
|
||||||
|
|
||||||
header: {
|
header: {
|
||||||
brandName: 'N&D',
|
brandName: 'N&D',
|
||||||
brandHighlight: 'i-t SOLUTIONS',
|
brandHighlight: 'i-t SOLUTIONS',
|
||||||
|
|||||||
Reference in New Issue
Block a user