backend-lehrer (11 files): - llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6) - messenger_api.py (840 → 5), print_generator.py (824 → 5) - unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4) - llm_gateway/routes/edu_search_seeds.py (710 → 4) klausur-service (12 files): - ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4) - legal_corpus_api.py (790 → 4), page_crop.py (758 → 3) - mail/ai_service.py (747 → 4), github_crawler.py (767 → 3) - trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4) - dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4) website (6 pages): - audit-checklist (867 → 8), content (806 → 6) - screen-flow (790 → 4), scraper (789 → 5) - zeugnisse (776 → 5), modules (745 → 4) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
|
import { WebsiteContent, HeroContent, FeatureContent } from '@/lib/content-types'
|
|
import { useLanguage } from '@/lib/LanguageContext'
|
|
import { ADMIN_KEY, SECTION_MAP, ContentTab } from './types'
|
|
|
|
export function useContentEditor() {
|
|
const { language, setLanguage, t, isRTL } = useLanguage()
|
|
const [content, setContent] = useState<WebsiteContent | null>(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<ContentTab>('hero')
|
|
const [showPreview, setShowPreview] = useState(true)
|
|
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
|
|
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
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
scrollToSection(activeTab)
|
|
}, [activeTab, scrollToSection])
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
function updateHero(field: keyof HeroContent, value: string) {
|
|
if (!content) return
|
|
setContent({ ...content, hero: { ...content.hero, [field]: value } })
|
|
}
|
|
|
|
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 })
|
|
}
|
|
|
|
function updateFAQ(index: number, field: 'question' | 'answer', value: string | string[]) {
|
|
if (!content) return
|
|
const newFAQ = [...content.faq]
|
|
if (field === 'answer' && typeof value === 'string') {
|
|
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 })
|
|
}
|
|
|
|
function addFAQ() {
|
|
if (!content) return
|
|
setContent({
|
|
...content,
|
|
faq: [...content.faq, { question: 'Neue Frage?', answer: ['Antwort hier...'] }],
|
|
})
|
|
}
|
|
|
|
function removeFAQ(index: number) {
|
|
if (!content) return
|
|
const newFAQ = content.faq.filter((_, i) => i !== index)
|
|
setContent({ ...content, faq: newFAQ })
|
|
}
|
|
|
|
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 })
|
|
}
|
|
|
|
function updateTrust(key: 'item1' | 'item2' | 'item3', field: 'value' | 'label', value: string) {
|
|
if (!content) return
|
|
setContent({
|
|
...content,
|
|
trust: { ...content.trust, [key]: { ...content.trust[key], [field]: value } },
|
|
})
|
|
}
|
|
|
|
function updateTestimonial(field: 'quote' | 'author' | 'role', value: string) {
|
|
if (!content) return
|
|
setContent({
|
|
...content,
|
|
testimonial: { ...content.testimonial, [field]: value },
|
|
})
|
|
}
|
|
|
|
return {
|
|
language, setLanguage, t, isRTL,
|
|
content, loading, saving, message,
|
|
activeTab, setActiveTab,
|
|
showPreview, setShowPreview,
|
|
iframeRef,
|
|
saveChanges,
|
|
updateHero, updateFeature, updateFAQ,
|
|
addFAQ, removeFAQ, updatePricing,
|
|
updateTrust, updateTestimonial,
|
|
}
|
|
}
|