'use client' /** * Admin Panel fuer Website-Content * * Erlaubt das Bearbeiten aller Website-Texte: * - Hero Section * - Features * - FAQ * - Pricing * - Trust Indicators * - Testimonial * * NEU: Live-Preview der Website zeigt Kontext beim Bearbeiten */ import { useState, useEffect, useRef, useCallback } from 'react' import { WebsiteContent, HeroContent, FeatureContent, FAQItem, PricingPlan } from '@/lib/content-types' import { useLanguage } from '@/lib/LanguageContext' import LanguageSelector from '@/components/LanguageSelector' import AdminLayout from '@/components/admin/AdminLayout' // Admin Key (in Produktion via Login) const ADMIN_KEY = 'breakpilot-admin-2024' // Mapping von Tabs zu Website-Sektionen (CSS Selektoren und Scroll-Positionen) const SECTION_MAP: Record = { hero: { selector: '#hero', scrollTo: 'hero' }, features: { selector: '#features', scrollTo: 'features' }, faq: { selector: '#faq', scrollTo: 'faq' }, pricing: { selector: '#pricing', scrollTo: 'pricing' }, other: { selector: '#trust', scrollTo: 'trust' }, } export default function AdminPage() { const { language, setLanguage, t, isRTL } = useLanguage() const [content, setContent] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const [activeTab, setActiveTab] = useState<'hero' | 'features' | 'faq' | 'pricing' | 'other'>('hero') const [showPreview, setShowPreview] = useState(true) const iframeRef = useRef(null) // Scrollt die Preview zur entsprechenden Sektion const scrollToSection = useCallback((tab: string) => { if (!iframeRef.current?.contentWindow) return const section = SECTION_MAP[tab] if (section) { try { iframeRef.current.contentWindow.postMessage( { type: 'scrollTo', section: section.scrollTo }, '*' ) } catch { // Same-origin policy - fallback } } }, []) // Bei Tab-Wechsel zur Sektion scrollen useEffect(() => { scrollToSection(activeTab) }, [activeTab, scrollToSection]) // Content laden useEffect(() => { loadContent() }, []) async function loadContent() { try { const res = await fetch('/api/content') if (res.ok) { const data = await res.json() setContent(data) } else { setMessage({ type: 'error', text: t('admin_error') }) } } catch (error) { setMessage({ type: 'error', text: t('admin_error') }) } finally { setLoading(false) } } async function saveChanges() { if (!content) return setSaving(true) setMessage(null) try { const res = await fetch('/api/content', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-admin-key': ADMIN_KEY, }, body: JSON.stringify(content), }) if (res.ok) { setMessage({ type: 'success', text: t('admin_saved') }) } else { const error = await res.json() setMessage({ type: 'error', text: error.error || t('admin_error') }) } } catch (error) { setMessage({ type: 'error', text: t('admin_error') }) } finally { setSaving(false) } } // Hero Section updaten function updateHero(field: keyof HeroContent, value: string) { if (!content) return setContent({ ...content, hero: { ...content.hero, [field]: value }, }) } // Feature updaten function updateFeature(index: number, field: keyof FeatureContent, value: string) { if (!content) return const newFeatures = [...content.features] newFeatures[index] = { ...newFeatures[index], [field]: value } setContent({ ...content, features: newFeatures }) } // FAQ updaten function updateFAQ(index: number, field: 'question' | 'answer', value: string | string[]) { if (!content) return const newFAQ = [...content.faq] if (field === 'answer' && typeof value === 'string') { // Split by newlines for array newFAQ[index] = { ...newFAQ[index], answer: value.split('\n') } } else if (field === 'question' && typeof value === 'string') { newFAQ[index] = { ...newFAQ[index], question: value } } setContent({ ...content, faq: newFAQ }) } // FAQ hinzufuegen function addFAQ() { if (!content) return setContent({ ...content, faq: [...content.faq, { question: 'Neue Frage?', answer: ['Antwort hier...'] }], }) } // FAQ entfernen function removeFAQ(index: number) { if (!content) return const newFAQ = content.faq.filter((_, i) => i !== index) setContent({ ...content, faq: newFAQ }) } // Pricing updaten function updatePricing(index: number, field: string, value: string | number | boolean) { if (!content) return const newPricing = [...content.pricing] if (field === 'price') { newPricing[index] = { ...newPricing[index], price: Number(value) } } else if (field === 'popular') { newPricing[index] = { ...newPricing[index], popular: Boolean(value) } } else if (field.startsWith('features.')) { const subField = field.replace('features.', '') if (subField === 'included' && typeof value === 'string') { newPricing[index] = { ...newPricing[index], features: { ...newPricing[index].features, included: value.split('\n'), }, } } else { newPricing[index] = { ...newPricing[index], features: { ...newPricing[index].features, [subField]: value, }, } } } else { newPricing[index] = { ...newPricing[index], [field]: value } } setContent({ ...content, pricing: newPricing }) } if (loading) { return (
{t('admin_loading')}
) } if (!content) { return (
{t('admin_error')}
) } return (
{/* Toolbar */}
{/* Preview Toggle */}
{message && ( {message.text} )}
{/* Tabs */}
{(['hero', 'features', 'faq', 'pricing', 'other'] as const).map((tab) => ( ))}
{/* Split Layout: Editor + Preview */}
{/* Editor Panel */}
{/* Hero Tab */} {activeTab === 'hero' && (

{t('admin_tab_hero')} Section

updateHero('badge', e.target.value)} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" dir={isRTL ? 'rtl' : 'ltr'} />
updateHero('title', e.target.value)} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" dir={isRTL ? 'rtl' : 'ltr'} />
updateHero('titleHighlight1', e.target.value)} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" dir={isRTL ? 'rtl' : 'ltr'} />
updateHero('titleHighlight2', e.target.value)} className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500" dir={isRTL ? 'rtl' : 'ltr'} />