[split-required] Split 500-850 LOC files (batch 2)
backend-lehrer (10 files): - game/database.py (785 → 5), correction_api.py (683 → 4) - classroom_engine/antizipation.py (676 → 5) - llm_gateway schools/edu_search already done in prior batch klausur-service (12 files): - orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4) - zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5) - eh_templates.py (658 → 5), mail/api.py (651 → 5) - qdrant_service.py (638 → 5), training_api.py (625 → 4) website (6 pages): - middleware (696 → 8), mail (733 → 6), consent (628 → 8) - compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7) studio-v2 (3 components): - B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2) - dashboard-experimental (739 → 2) admin-lehrer (4 files): - uebersetzungen (769 → 4), manager (670 → 2) - ChunkBrowserQA (675 → 6), dsfa/page (674 → 5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
WebsiteContent,
|
||||
HeroContent,
|
||||
FeatureContent,
|
||||
} from '@/lib/content-types'
|
||||
|
||||
// Shared styles
|
||||
export const inputCls = 'w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-sky-500 focus:border-sky-500 transition-colors'
|
||||
export const labelCls = 'block text-xs font-medium text-slate-600 mb-1'
|
||||
|
||||
export interface EditorProps {
|
||||
content: WebsiteContent
|
||||
setContent: React.Dispatch<React.SetStateAction<WebsiteContent | null>>
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Hero Editor
|
||||
// =============================================================================
|
||||
|
||||
export function HeroEditor({ content, setContent }: EditorProps) {
|
||||
function update(field: keyof HeroContent, value: string) {
|
||||
setContent(c => c ? { ...c, hero: { ...c.hero, [field]: value } } : c)
|
||||
}
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<div><label className={labelCls}>Badge</label><input className={inputCls} value={content.hero.badge} onChange={e => update('badge', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Titel</label><input className={inputCls} value={content.hero.title} onChange={e => update('title', e.target.value)} /></div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div><label className={labelCls}>Highlight 1</label><input className={inputCls} value={content.hero.titleHighlight1} onChange={e => update('titleHighlight1', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Highlight 2</label><input className={inputCls} value={content.hero.titleHighlight2} onChange={e => update('titleHighlight2', e.target.value)} /></div>
|
||||
</div>
|
||||
<div><label className={labelCls}>Untertitel</label><textarea className={inputCls} rows={2} value={content.hero.subtitle} onChange={e => update('subtitle', e.target.value)} /></div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div><label className={labelCls}>CTA Primaer</label><input className={inputCls} value={content.hero.ctaPrimary} onChange={e => update('ctaPrimary', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>CTA Sekundaer</label><input className={inputCls} value={content.hero.ctaSecondary} onChange={e => update('ctaSecondary', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>CTA Hinweis</label><input className={inputCls} value={content.hero.ctaHint} onChange={e => update('ctaHint', e.target.value)} /></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Features Editor
|
||||
// =============================================================================
|
||||
|
||||
export function FeaturesEditor({ content, setContent }: EditorProps) {
|
||||
function update(index: number, field: keyof FeatureContent, value: string) {
|
||||
setContent(c => {
|
||||
if (!c) return c
|
||||
const features = [...c.features]
|
||||
features[index] = { ...features[index], [field]: value }
|
||||
return { ...c, features }
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{content.features.map((feature, i) => (
|
||||
<div key={feature.id} className="bg-slate-50 rounded-lg p-3 space-y-2">
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
<div><label className={labelCls}>Icon</label><input className={`${inputCls} text-center text-lg`} value={feature.icon} onChange={e => update(i, 'icon', e.target.value)} /></div>
|
||||
<div className="col-span-5"><label className={labelCls}>Titel</label><input className={inputCls} value={feature.title} onChange={e => update(i, 'title', e.target.value)} /></div>
|
||||
</div>
|
||||
<div><label className={labelCls}>Beschreibung</label><textarea className={inputCls} rows={2} value={feature.description} onChange={e => update(i, 'description', e.target.value)} /></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FAQ Editor
|
||||
// =============================================================================
|
||||
|
||||
export function FAQEditor({ content, setContent }: EditorProps) {
|
||||
function updateItem(index: number, field: 'question' | 'answer', value: string) {
|
||||
setContent(c => {
|
||||
if (!c) return c
|
||||
const faq = [...c.faq]
|
||||
if (field === 'answer') { faq[index] = { ...faq[index], answer: value.split('\n') } }
|
||||
else { faq[index] = { ...faq[index], question: value } }
|
||||
return { ...c, faq }
|
||||
})
|
||||
}
|
||||
function addItem() { setContent(c => c ? { ...c, faq: [...c.faq, { question: 'Neue Frage?', answer: ['Antwort hier...'] }] } : c) }
|
||||
function removeItem(index: number) { setContent(c => c ? { ...c, faq: c.faq.filter((_, i) => i !== index) } : c) }
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{content.faq.map((item, i) => (
|
||||
<div key={i} className="bg-slate-50 rounded-lg p-3 space-y-2 relative group">
|
||||
<button onClick={() => removeItem(i)} className="absolute top-2 right-2 p-1 text-red-400 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity" title="Entfernen">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
<div><label className={labelCls}>Frage {i + 1}</label><input className={inputCls} value={item.question} onChange={e => updateItem(i, 'question', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Antwort</label><textarea className={`${inputCls} font-mono`} rows={3} value={item.answer.join('\n')} onChange={e => updateItem(i, 'answer', e.target.value)} /></div>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={addItem} className="w-full py-2 border-2 border-dashed border-slate-300 rounded-lg text-sm text-slate-500 hover:border-sky-400 hover:text-sky-600 transition-colors">
|
||||
+ Frage hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pricing Editor
|
||||
// =============================================================================
|
||||
|
||||
export function PricingEditor({ content, setContent }: EditorProps) {
|
||||
function update(index: number, field: string, value: string | number | boolean) {
|
||||
setContent(c => {
|
||||
if (!c) return c
|
||||
const pricing = [...c.pricing]
|
||||
if (field === 'price') { pricing[index] = { ...pricing[index], price: Number(value) } }
|
||||
else if (field === 'popular') { pricing[index] = { ...pricing[index], popular: Boolean(value) } }
|
||||
else if (field.startsWith('features.')) {
|
||||
const sub = field.replace('features.', '')
|
||||
if (sub === 'included' && typeof value === 'string') { pricing[index] = { ...pricing[index], features: { ...pricing[index].features, included: value.split('\n') } } }
|
||||
else { pricing[index] = { ...pricing[index], features: { ...pricing[index].features, [sub]: value } } }
|
||||
} else { pricing[index] = { ...pricing[index], [field]: value } }
|
||||
return { ...c, pricing }
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{content.pricing.map((plan, i) => (
|
||||
<div key={plan.id} className="bg-slate-50 rounded-lg p-3 space-y-2">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-sm font-semibold text-slate-800">{plan.name}</span>
|
||||
{plan.popular && <span className="text-xs bg-sky-100 text-sky-700 px-1.5 py-0.5 rounded">Beliebt</span>}
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<div><label className={labelCls}>Name</label><input className={inputCls} value={plan.name} onChange={e => update(i, 'name', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Preis (EUR)</label><input className={inputCls} type="number" step="0.01" value={plan.price} onChange={e => update(i, 'price', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Intervall</label><input className={inputCls} value={plan.interval} onChange={e => update(i, 'interval', e.target.value)} /></div>
|
||||
<div className="flex items-end pb-1"><label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={plan.popular || false} onChange={e => update(i, 'popular', e.target.checked)} className="w-4 h-4 text-sky-600 rounded" /><span className="text-xs text-slate-600">Beliebt</span></label></div>
|
||||
</div>
|
||||
<div><label className={labelCls}>Beschreibung</label><input className={inputCls} value={plan.description} onChange={e => update(i, 'description', e.target.value)} /></div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div><label className={labelCls}>Aufgaben</label><input className={inputCls} value={plan.features.tasks} onChange={e => update(i, 'features.tasks', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Aufgaben-Beschreibung</label><input className={inputCls} value={plan.features.taskDescription} onChange={e => update(i, 'features.taskDescription', e.target.value)} /></div>
|
||||
</div>
|
||||
<div><label className={labelCls}>Features (eine pro Zeile)</label><textarea className={`${inputCls} font-mono`} rows={3} value={plan.features.included.join('\n')} onChange={e => update(i, 'features.included', e.target.value)} /></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Trust Editor
|
||||
// =============================================================================
|
||||
|
||||
export function TrustEditor({ content, setContent }: EditorProps) {
|
||||
function update(key: 'item1' | 'item2' | 'item3', field: 'value' | 'label', val: string) {
|
||||
setContent(c => c ? { ...c, trust: { ...c.trust, [key]: { ...c.trust[key], [field]: val } } } : c)
|
||||
}
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{(['item1', 'item2', 'item3'] as const).map((key, i) => (
|
||||
<div key={key} className="bg-slate-50 rounded-lg p-3 space-y-2">
|
||||
<div><label className={labelCls}>Wert {i + 1}</label><input className={inputCls} value={content.trust[key].value} onChange={e => update(key, 'value', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Label {i + 1}</label><input className={inputCls} value={content.trust[key].label} onChange={e => update(key, 'label', e.target.value)} /></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Testimonial Editor
|
||||
// =============================================================================
|
||||
|
||||
export function TestimonialEditor({ content, setContent }: EditorProps) {
|
||||
function update(field: 'quote' | 'author' | 'role', value: string) {
|
||||
setContent(c => c ? { ...c, testimonial: { ...c.testimonial, [field]: value } } : c)
|
||||
}
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div><label className={labelCls}>Zitat</label><textarea className={inputCls} rows={3} value={content.testimonial.quote} onChange={e => update('quote', e.target.value)} /></div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div><label className={labelCls}>Autor</label><input className={inputCls} value={content.testimonial.author} onChange={e => update('author', e.target.value)} /></div>
|
||||
<div><label className={labelCls}>Rolle</label><input className={inputCls} value={content.testimonial.role} onChange={e => update('role', e.target.value)} /></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user