'use client' /** * Website Manager - CMS Dashboard * * Visual CMS dashboard for the BreakPilot website (macmini:3000). * 60/40 split: Section cards with inline editors | Live iframe preview. * Status bar, content stats, reset, save. */ import { useState, useEffect, useRef, useCallback } from 'react' import type { WebsiteContent, HeroContent, FeatureContent, FAQItem, PricingPlan, } from '@/lib/content-types' const ADMIN_KEY = 'breakpilot-admin-2024' // Section metadata for cards const SECTIONS = [ { key: 'hero', name: 'Hero Section', icon: '🎯', scrollTo: 'hero' }, { key: 'features', name: 'Features', icon: '⚡', scrollTo: 'features' }, { key: 'faq', name: 'FAQ', icon: '❓', scrollTo: 'faq' }, { key: 'pricing', name: 'Pricing', icon: '💰', scrollTo: 'pricing' }, { key: 'trust', name: 'Trust Indicators', icon: '🛡️', scrollTo: 'trust' }, { key: 'testimonial', name: 'Testimonial', icon: '💬', scrollTo: 'trust' }, ] as const type SectionKey = (typeof SECTIONS)[number]['key'] // ─── Helpers ─────────────────────────────────────────────────────────────── function countWords(content: WebsiteContent): number { const texts: string[] = [] // Hero texts.push(content.hero.badge, content.hero.title, content.hero.titleHighlight1, content.hero.titleHighlight2, content.hero.subtitle, content.hero.ctaPrimary, content.hero.ctaSecondary, content.hero.ctaHint) // Features content.features.forEach(f => { texts.push(f.title, f.description) }) // FAQ content.faq.forEach(f => { texts.push(f.question, ...f.answer) }) // Pricing content.pricing.forEach(p => { texts.push(p.name, p.description, p.features.tasks, p.features.taskDescription, ...p.features.included) }) // Trust texts.push(content.trust.item1.value, content.trust.item1.label, content.trust.item2.value, content.trust.item2.label, content.trust.item3.value, content.trust.item3.label) // Testimonial texts.push(content.testimonial.quote, content.testimonial.author, content.testimonial.role) return texts.filter(Boolean).join(' ').split(/\s+/).filter(Boolean).length } function sectionComplete(content: WebsiteContent, section: SectionKey): boolean { switch (section) { case 'hero': return !!(content.hero.title && content.hero.subtitle && content.hero.ctaPrimary) case 'features': return content.features.length > 0 && content.features.every(f => f.title && f.description) case 'faq': return content.faq.length > 0 && content.faq.every(f => f.question && f.answer.length > 0) case 'pricing': return content.pricing.length > 0 && content.pricing.every(p => p.name && p.price > 0) case 'trust': return !!(content.trust.item1.value && content.trust.item2.value && content.trust.item3.value) case 'testimonial': return !!(content.testimonial.quote && content.testimonial.author) } } function sectionSummary(content: WebsiteContent, section: SectionKey): string { switch (section) { case 'hero': return `"${content.hero.title} ${content.hero.titleHighlight1}"`.slice(0, 50) case 'features': return `${content.features.length} Features` case 'faq': return `${content.faq.length} Fragen` case 'pricing': return `${content.pricing.length} Plaene` case 'trust': return `${content.trust.item1.value}, ${content.trust.item2.value}, ${content.trust.item3.value}` case 'testimonial': return `"${content.testimonial.quote.slice(0, 40)}..."` } } // ─── Main Component ──────────────────────────────────────────────────────── export default function WebsiteManagerPage() { const [content, setContent] = useState(null) const [originalContent, setOriginalContent] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const [expandedSection, setExpandedSection] = useState(null) const [websiteStatus, setWebsiteStatus] = useState<{ online: boolean; responseTime: number } | null>(null) const iframeRef = useRef(null) // Load content useEffect(() => { loadContent() checkWebsiteStatus() }, []) // Auto-dismiss messages useEffect(() => { if (message) { const t = setTimeout(() => setMessage(null), 4000) return () => clearTimeout(t) } }, [message]) async function loadContent() { try { const res = await fetch('/api/website/content') if (res.ok) { const data = await res.json() setContent(data) setOriginalContent(JSON.parse(JSON.stringify(data))) } else { setMessage({ type: 'error', text: 'Fehler beim Laden des Contents' }) } } catch { setMessage({ type: 'error', text: 'Verbindungsfehler beim Laden' }) } finally { setLoading(false) } } async function checkWebsiteStatus() { try { const res = await fetch('/api/website/status') if (res.ok) { const data = await res.json() setWebsiteStatus(data) } } catch { setWebsiteStatus({ online: false, responseTime: 0 }) } } async function saveChanges() { if (!content) return setSaving(true) setMessage(null) try { const res = await fetch('/api/website/content', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-admin-key': ADMIN_KEY }, body: JSON.stringify(content), }) if (res.ok) { setMessage({ type: 'success', text: 'Erfolgreich gespeichert!' }) setOriginalContent(JSON.parse(JSON.stringify(content))) // Reload iframe to reflect changes if (iframeRef.current) { iframeRef.current.src = iframeRef.current.src } } else { const err = await res.json() setMessage({ type: 'error', text: err.error || 'Fehler beim Speichern' }) } } catch { setMessage({ type: 'error', text: 'Verbindungsfehler beim Speichern' }) } finally { setSaving(false) } } function resetContent() { if (originalContent) { setContent(JSON.parse(JSON.stringify(originalContent))) setMessage({ type: 'success', text: 'Zurueckgesetzt auf letzten gespeicherten Stand' }) } } // Scroll iframe to section const scrollPreview = useCallback((scrollTo: string) => { if (!iframeRef.current?.contentWindow) return try { iframeRef.current.contentWindow.postMessage( { type: 'scrollTo', section: scrollTo }, '*' ) } catch { // cross-origin fallback } }, []) function toggleSection(key: SectionKey) { const newExpanded = expandedSection === key ? null : key setExpandedSection(newExpanded) if (newExpanded) { const section = SECTIONS.find(s => s.key === newExpanded) if (section) scrollPreview(section.scrollTo) } } // ─── Render ──────────────────────────────────────────────────────────────── if (loading) { return (
Lade Website-Content...
) } if (!content) { return (
Content konnte nicht geladen werden.
) } const wordCount = countWords(content) const completeSections = SECTIONS.filter(s => sectionComplete(content, s.key)).length const completionPct = Math.round((completeSections / SECTIONS.length) * 100) const hasChanges = JSON.stringify(content) !== JSON.stringify(originalContent) return (
{/* ── Status Bar ───────────────────────────────────────────────────── */}
{/* Website status */}
Website {websiteStatus?.online ? 'online' : 'offline'} {websiteStatus?.online && websiteStatus.responseTime > 0 && ( ({websiteStatus.responseTime}ms) )}
{/* Link */} Zur Website
{message && ( {message.text} )}
{/* ── Stats Bar ────────────────────────────────────────────────────── */}
{[ { label: 'Sektionen', value: `${SECTIONS.length}`, icon: '📄' }, { label: 'Woerter', value: wordCount.toLocaleString('de-DE'), icon: '📝' }, { label: 'Vollstaendig', value: `${completionPct}%`, icon: completionPct === 100 ? '✅' : '🔧' }, { label: 'Aenderungen', value: hasChanges ? 'Ungespeichert' : 'Aktuell', icon: hasChanges ? '🟡' : '🟢' }, ].map((stat) => (
{stat.icon}
{stat.value}
{stat.label}
))}
{/* ── Main Layout: 60/40 ───────────────────────────────────────────── */}
{/* ── Left: Section Cards (3/5 = 60%) ──────────────────────────── */}
{SECTIONS.map((section) => { const isExpanded = expandedSection === section.key const isComplete = sectionComplete(content, section.key) return (
{/* Card Header */} {/* Inline Editor */} {isExpanded && (
{section.key === 'hero' && } {section.key === 'features' && } {section.key === 'faq' && } {section.key === 'pricing' && } {section.key === 'trust' && } {section.key === 'testimonial' && }
)}
) })}
{/* ── Right: Live Preview (2/5 = 40%) ──────────────────────────── */}
{/* Preview Header */}
macmini:3000
{/* iframe */}