[split-required] Split 700-870 LOC files across all services
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>
This commit is contained in:
173
website/app/admin/content/_components/useContentEditor.ts
Normal file
173
website/app/admin/content/_components/useContentEditor.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
'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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user