Compare commits

...

6 Commits

8 changed files with 190 additions and 70 deletions

View File

@@ -6,6 +6,16 @@
console.log('[N&D Editor Bridge] Active on preview canvas');
// Optischer Hinweis-Banner im Bearbeitungsmodus
document.addEventListener('DOMContentLoaded', () => {
const banner = document.createElement('div');
banner.id = 'editor-preview-banner';
banner.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; z-index: 99999; background: #d97706; color: #0f172a; text-align: center; padding: 6px 12px; font-size: 12px; font-weight: 700; font-family: monospace; letter-spacing: 0.5px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); pointer-events: none; opacity: 0.95;';
banner.innerHTML = '🛠️ BEARBEITUNGSMODUS (VORSCHAU / ENTWURF) — Änderungen sind noch nicht live!';
document.body.appendChild(banner);
document.body.style.paddingTop = (document.body.style.paddingTop ? parseInt(document.body.style.paddingTop) + 30 : 30) + 'px';
});
window.addEventListener('message', (event) => {
const { type, payload } = event.data || {};
@@ -14,7 +24,7 @@
const root = document.documentElement;
if (payload.colors) {
Object.entries(payload.colors).forEach(([key, val]) => {
root.style.setProperty(`--${key}`, val as string);
root.style.setProperty(`--${key}`, val);
});
}
}
@@ -25,3 +35,4 @@
}
});
})();

View File

@@ -0,0 +1,112 @@
---
// src/components/admin/AdminSidebar.astro
const { currentPath = '' } = Astro.props;
const navItems = [
{ href: '/admin', label: 'Allgemein & Konfig', icon: '🎛️' },
{ href: '/admin/pages', label: 'Seiten-Verwaltung', icon: '📄' },
{ href: '/admin/settings', label: 'Stammdaten & SEO', icon: '⚙️' },
];
---
<!-- Burger Button (Mobile / Sidebar Toggle) -->
<button
id="admin-menu-toggle"
type="button"
aria-label="Admin Menü öffnen"
class="fixed top-3 left-3 z-50 p-2.5 rounded-xl bg-slate-900/90 border border-white/10 text-white hover:border-sky-400 focus:outline-none focus:ring-2 focus:ring-sky-400 backdrop-blur-md transition-all"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
<!-- Backdrop Overlay for Mobile -->
<div
id="admin-sidebar-backdrop"
class="fixed inset-0 bg-slate-950/80 backdrop-blur-sm z-40 hidden transition-opacity"
></div>
<!-- Drawer Sidebar -->
<aside
id="admin-sidebar"
class="fixed top-0 left-0 h-full w-72 bg-slate-950 border-r border-white/10 p-6 z-50 transform -translate-x-full transition-transform duration-300 ease-in-out flex flex-col justify-between shadow-2xl"
>
<div class="space-y-6">
<!-- Header with Close Button -->
<div class="flex items-center justify-between border-b border-white/10 pb-4">
<div>
<span class="text-[10px] font-mono text-sky-400 uppercase tracking-widest block">N&D WaaS Admin</span>
<h2 class="text-lg font-bold text-white">Navigation</h2>
</div>
<button
id="admin-menu-close"
type="button"
class="p-2 text-slate-400 hover:text-white rounded-lg hover:bg-slate-900 transition-colors"
aria-label="Menü schließen"
>
</button>
</div>
<!-- Navigation Links -->
<nav class="space-y-1.5" aria-label="Admin Sidebar">
{navItems.map((item) => {
const isActive = currentPath === item.href || (item.href !== '/admin' && currentPath.startsWith(item.href));
return (
<a
href={item.href}
class={`flex items-center gap-3 px-4 py-3 rounded-xl text-xs font-semibold transition-all ${
isActive
? 'bg-sky-400/10 text-sky-400 border border-sky-400/30'
: 'text-slate-300 hover:bg-slate-900 hover:text-white border border-transparent'
}`}
>
<span class="text-base">{item.icon}</span>
<span>{item.label}</span>
</a>
);
})}
</nav>
</div>
<!-- Bottom Quick Links -->
<div class="pt-4 border-t border-white/10 space-y-2">
<a
href="/?preview=draft"
target="_blank"
class="flex items-center justify-center gap-2 w-full p-2.5 rounded-xl bg-amber-500/10 text-amber-400 border border-amber-500/20 text-xs font-semibold hover:bg-amber-500 hover:text-slate-950 transition-all"
>
🛠️ Entwurf-Vorschau
</a>
<a
href="/"
target="_blank"
class="flex items-center justify-center gap-2 w-full p-2.5 rounded-xl bg-slate-900 text-slate-300 border border-white/10 text-xs font-semibold hover:border-sky-400 hover:text-white transition-all"
>
🌐 Live-Website
</a>
</div>
</aside>
<script>
const toggleBtn = document.getElementById('admin-menu-toggle');
const closeBtn = document.getElementById('admin-menu-close');
const sidebar = document.getElementById('admin-sidebar');
const backdrop = document.getElementById('admin-sidebar-backdrop');
function openMenu() {
sidebar?.classList.remove('-translate-x-full');
backdrop?.classList.remove('hidden');
}
function closeMenu() {
sidebar?.classList.add('-translate-x-full');
backdrop?.classList.add('hidden');
}
toggleBtn?.addEventListener('click', openMenu);
closeBtn?.addEventListener('click', closeMenu);
backdrop?.addEventListener('click', closeMenu);
</script>

View File

@@ -73,8 +73,15 @@ const { title, description = "Professionelles Webdesign & moderne Websites." } =
</script>
</head>
<body class="font-sans transition-colors duration-500 antialiased">
{(siteConfig as any).is_demo_mode && (
<div class="bg-gradient-to-r from-amber-600 via-amber-500 to-amber-600 text-slate-950 text-center py-2 px-4 text-xs font-bold font-mono tracking-wide shadow-md flex items-center justify-center gap-2">
<span>🎭 DEMO-MODUS AKTIV</span>
<span class="opacity-75 font-sans font-normal">— Dies ist eine interaktive Vorschau-Instanz der N&D WaaS Engine.</span>
</div>
)}
<slot />
</body>
</html>
<style is:global>

View File

@@ -71,11 +71,13 @@ let page = pages.find((p: any) => {
return p.slug === currentSlug || p.slug === `/${currentSlug}`;
});
// 5. Zero-Crash Fallback Guard: Wenn Seite fehlt oder unveröffentlicht ist
if (!page || (page.is_published === false && import.meta.env.PROD)) {
return Astro.redirect('/404');
// 5. Zero-Crash Fallback Guard: Wenn Seite nicht existiert oder nicht veröffentlicht ist -> 404 Status
if (!page || (page.is_published === false && !isDraft)) {
Astro.response.status = 404;
return Astro.rewrite('/404');
}
// 6. Dynamic Component Registry Mapping
const ComponentRegistry: Record<string, any> = {
hero: HeroSection,

View File

@@ -1,6 +1,7 @@
---
// src/pages/admin/editor.astro
import Layout from '../../layouts/Layout.astro';
import AdminSidebar from '../../components/admin/AdminSidebar.astro';
import { loadWaasConfigs } from '../../utils/configLoader';
import { THEME_PRESETS } from '../../utils/themePresets';
@@ -18,10 +19,12 @@ const previewUrl = `${targetPage?.slug || '/'}?preview=draft`;
---
<Layout title={`Editor: ${targetPage?.title || 'Baukasten'} | N&D Admin`}>
<div class="h-screen w-screen flex overflow-hidden bg-slate-950 font-sans">
<AdminSidebar currentPath="/admin/editor" />
<div class="h-screen w-screen flex overflow-hidden bg-slate-950 font-sans pl-12">
<aside class="w-80 border-r border-slate-800 bg-slate-900 flex flex-col justify-between z-20">
<div class="p-4 space-y-6 overflow-y-auto">
<div class="flex items-center justify-between border-b border-slate-800 pb-4">
<div>

View File

@@ -1,5 +1,6 @@
---
import Layout from '../../layouts/Layout.astro';
import AdminSidebar from '../../components/admin/AdminSidebar.astro';
import { loadWaasConfigs } from '../../utils/configLoader';
import { THEME_PRESETS } from '../../utils/themePresets';
@@ -8,7 +9,9 @@ const presets = Object.values(THEME_PRESETS);
---
<Layout title="Waas Admin Dashboard | Layout and Config Management">
<div class="min-h-screen bg-[#0B0F19] text-white p-6 sm:p-10 font-sans">
<AdminSidebar currentPath="/admin" />
<div class="min-h-screen bg-[#0B0F19] text-white p-6 sm:p-10 pl-16 sm:pl-20 font-sans">
<div class="max-w-6xl mx-auto space-y-10">
<!-- Admin Header & Quick Navigation -->
@@ -33,31 +36,17 @@ const presets = Object.values(THEME_PRESETS);
</div>
<!-- Quick Admin Nav Cards / Links -->
<nav aria-label="Admin Navigation" class="grid grid-cols-1 sm:grid-cols-3 gap-3 pt-2">
<a href="/admin/pages" class="p-3 bg-slate-900/80 border border-white/10 hover:border-sky-400/50 rounded-xl flex items-center justify-between text-xs transition-all group">
<nav aria-label="Admin Navigation" class="pt-2">
<a href="/admin/pages" class="p-4 bg-slate-900/80 border border-white/10 hover:border-sky-400/50 rounded-xl flex items-center justify-between text-xs transition-all group">
<div>
<span class="font-bold text-white block group-hover:text-sky-400">📄 Seiten-Verwaltung</span>
<span class="text-[10px] text-slate-400">Routen, Slugs & Startseite festlegen</span>
<span class="font-bold text-white block group-hover:text-sky-400 text-sm">📄 Seiten-Verwaltung & Visueller Editor</span>
<span class="text-[11px] text-slate-400">Routen, Slugs, Startseite & Seiten im Editor öffnen</span>
</div>
<span class="text-sky-400 text-sm font-bold">→</span>
</a>
<a href="/admin/editor" class="p-3 bg-slate-900/80 border border-white/10 hover:border-sky-400/50 rounded-xl flex items-center justify-between text-xs transition-all group">
<div>
<span class="font-bold text-white block group-hover:text-sky-400">🎨 Visueller Drag & Drop Editor</span>
<span class="text-[10px] text-slate-400">Sektionen & Inhalts-Vorschau</span>
</div>
<span class="text-sky-400 text-sm font-bold">→</span>
</a>
<a href="/admin/settings" class="p-3 bg-slate-900/80 border border-white/10 hover:border-sky-400/50 rounded-xl flex items-center justify-between text-xs transition-all group">
<div>
<span class="font-bold text-white block group-hover:text-sky-400">⚙️ Stammdaten & SEO</span>
<span class="text-[10px] text-slate-400">Firmen-Daten & Dynamic Data Binding</span>
</div>
<span class="text-sky-400 text-sm font-bold">→</span>
<span class="text-sky-400 text-base font-bold">→</span>
</a>
</nav>
</div>
@@ -68,61 +57,50 @@ const presets = Object.values(THEME_PRESETS);
<!-- Main Form Column -->
<div class="lg:col-span-2 space-y-8">
<!-- Site Info -->
<div class="glass-panel rounded-2xl p-6 space-y-4">
<!-- Site Info & Stammdaten -->
<div class="glass-panel rounded-2xl p-6 space-y-6">
<h2 class="text-lg font-bold text-sky-400 uppercase tracking-wider flex items-center gap-2">
Allgemeine Informationen
Allgemeine Informationen & SEO
</h2>
<div class="space-y-4">
<div>
<label class="block text-xs font-bold text-slate-400 mb-1">Webseiten-Name</label>
<label class="block text-xs font-bold text-slate-400 mb-1">Webseiten-Name (Titel)</label>
<input type="text" id="siteName" value={siteConfig.siteName} 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-bold text-slate-400 mb-1">Meta Beschreibung</label>
<label class="block text-xs font-bold text-slate-400 mb-1">Meta Beschreibung (SEO)</label>
<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>
<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" />
<div>
<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>
</div>
</label>
</div>
<!-- Hero Config -->
<div class="glass-panel rounded-3xl p-6 space-y-4">
<h2 class="text-lg font-bold text-sky-400 uppercase tracking-wider flex items-center gap-2">
Hero Sektion
</h2>
<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>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-slate-400 mb-1">Badge</label>
<input type="text" id="heroBadge" value={siteConfig.hero.badge} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
<label class="block text-xs font-semibold text-slate-400 mb-1">Firmenname / Brand</label>
<input type="text" id="infoTitle" value={(siteConfig as any).site_info?.title || siteConfig.siteName} 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-bold text-slate-400 mb-1">Hauptueberschrift (Title)</label>
<input type="text" id="heroTitle" value={siteConfig.hero.title} 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 class="sm:col-span-2">
<label class="block text-xs font-bold text-slate-400 mb-1">Unterueberschrift (Subtitle)</label>
<textarea id="heroSubtitle" 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.hero.subtitle}</textarea>
<label class="block text-xs font-semibold text-slate-400 mb-1">E-Mail Adresse</label>
<input type="email" id="infoEmail" value={(siteConfig as any).site_info?.email || ''} 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-bold text-slate-400 mb-1">CTA2 Text</label>
<input type="text" id="ctaPrimaryText" value={siteConfig.hero.ctaPrimaryText} class="w-full glass-panel bg-slate-950-60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
<label class="block text-xs font-semibold text-slate-400 mb-1">Telefonnummer</label>
<input type="text" id="infoPhone" value={(siteConfig as any).site_info?.phone || ''} 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-bold text-slate-400 mb-1">CTA2 Link</label>
<input type="text" id="ctaPrimaryLink" value={siteConfig.hero.ctaPrimaryLink} class="w-full glass-panel bg-slate-950-60 rounded-xl p-3 text-xs text-white focus:outline-none focus:border-sky-400" />
<label class="block text-xs font-semibold text-slate-400 mb-1">Adresse / Standort</label>
<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>
<!-- Section Order Management -->
<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">
Sektionen Reihenfolge (Layout)
</h2>
<p class="text-xs text-slate-400">Kommagetrennte Liste der aktiv gerenderten Sektionen:</p>
<input type="text" id="sectionsOrder" value={siteConfig.sectionsOrder.join(', ')} class="w-full glass-panel bg-slate-950/60 rounded-xl p-3 text-xs text-white font-mono focus:outline-none focus:border-sky-400" />
</div>
</div>
<!-- Sidebar Column -->
@@ -199,7 +177,6 @@ const presets = Object.values(THEME_PRESETS);
saveBtn.textContent = 'Wird gespeichert...';
const selectedPreset = document.querySelector('input[name="activePreset"]:checked')?.value || 'corporateDark';
const sections = document.getElementById('sectionsOrder').value.split(',').map(s => s.trim()).filter(Boolean);
const getVal = (id) => (document.getElementById(id))?.value || '';
const getNum = (id) => parseInt(document.getElementById(id)?.value || '0') || 587;
@@ -209,15 +186,15 @@ const presets = Object.values(THEME_PRESETS);
siteConfig: {
siteName: getVal('siteName'),
metaDescription: getVal('metaDescription'),
hero: {
badge: getVal('heroBadge'),
title: getVal('heroTitle'),
subtitle: getVal('heroSubtitle'),
ctaPrimaryText: getVal('ctaPrimaryText'),
ctaPrimaryLink: getVal('ctaPrimaryLink')
},
sectionsOrder: sections
is_demo_mode: getBool('isDemoMode'),
site_info: {
title: getVal('infoTitle'),
email: getVal('infoEmail'),
phone: getVal('infoPhone'),
address: getVal('infoAddress')
}
},
themeConfig: {
activePreset: selectedPreset
},
@@ -231,6 +208,8 @@ const presets = Object.values(THEME_PRESETS);
};
try {
const res = await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },

View File

@@ -1,6 +1,7 @@
---
// src/pages/admin/pages.astro
import Layout from '../../layouts/Layout.astro';
import AdminSidebar from '../../components/admin/AdminSidebar.astro';
import { loadWaasConfigs } from '../../utils/configLoader';
export const prerender = false;
@@ -12,7 +13,9 @@ const currentHomepageId = (siteConfig as any).site_info?.homepage_id;
---
<Layout title="Seiten-Verwaltung | Admin Dashboard">
<div class="max-w-6xl mx-auto px-4 py-8">
<AdminSidebar currentPath="/admin/pages" />
<div class="max-w-6xl mx-auto px-4 py-8 pl-16 sm:pl-20">
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-2xl font-bold text-white">Seiten-Verwaltung</h1>

View File

@@ -1,6 +1,7 @@
---
// src/pages/admin/settings.astro
import Layout from '../../layouts/Layout.astro';
import AdminSidebar from '../../components/admin/AdminSidebar.astro';
import { loadWaasConfigs } from '../../utils/configLoader';
export const prerender = false;
@@ -11,7 +12,9 @@ const info = (siteConfig as any).site_info || {};
---
<Layout title="Globale Einstellungen | N&D Admin">
<div class="max-w-4xl mx-auto px-4 py-8">
<AdminSidebar currentPath="/admin/settings" />
<div class="max-w-4xl mx-auto px-4 py-8 pl-16 sm:pl-20">
<div class="flex items-center justify-between mb-8 border-b border-slate-800 pb-4">
<div>
<a href="/admin/pages" class="text-xs text-slate-400 hover:text-sky-400 transition-colors">← Zurück zum Seiten-Dashboard</a>