backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
186 lines
6.9 KiB
TypeScript
186 lines
6.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { API_BASE } from '../constants'
|
|
|
|
export default function AddAccountModal({
|
|
onClose,
|
|
onSuccess
|
|
}: {
|
|
onClose: () => void
|
|
onSuccess: () => void
|
|
}) {
|
|
const [formData, setFormData] = useState({
|
|
email: '',
|
|
displayName: '',
|
|
imapHost: '',
|
|
imapPort: 993,
|
|
smtpHost: '',
|
|
smtpPort: 587,
|
|
username: '',
|
|
password: '',
|
|
})
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setSubmitting(true)
|
|
setError(null)
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/v1/mail/accounts`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: formData.email,
|
|
display_name: formData.displayName,
|
|
imap_host: formData.imapHost,
|
|
imap_port: formData.imapPort,
|
|
smtp_host: formData.smtpHost,
|
|
smtp_port: formData.smtpPort,
|
|
username: formData.username,
|
|
password: formData.password,
|
|
}),
|
|
})
|
|
|
|
if (res.ok) {
|
|
onSuccess()
|
|
} else {
|
|
const data = await res.json()
|
|
setError(data.detail || 'Fehler beim Hinzufügen des Kontos')
|
|
}
|
|
} catch (err) {
|
|
setError('Netzwerkfehler')
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div className="bg-white rounded-lg shadow-xl max-w-lg w-full mx-4 max-h-[90vh] overflow-y-auto">
|
|
<div className="p-6 border-b border-slate-200">
|
|
<h2 className="text-lg font-semibold text-slate-900">E-Mail-Konto hinzufügen</h2>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-6 space-y-4">
|
|
{error && (
|
|
<div className="p-3 bg-red-50 text-red-700 rounded-lg text-sm">{error}</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">E-Mail-Adresse</label>
|
|
<input
|
|
type="email"
|
|
required
|
|
value={formData.email}
|
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
placeholder="schulleitung@grundschule-xy.de"
|
|
/>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Anzeigename</label>
|
|
<input
|
|
type="text"
|
|
value={formData.displayName}
|
|
onChange={(e) => setFormData({ ...formData, displayName: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
placeholder="Schulleitung"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">IMAP Server</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={formData.imapHost}
|
|
onChange={(e) => setFormData({ ...formData, imapHost: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
placeholder="imap.example.com"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">IMAP Port</label>
|
|
<input
|
|
type="number"
|
|
required
|
|
value={formData.imapPort}
|
|
onChange={(e) => setFormData({ ...formData, imapPort: parseInt(e.target.value) })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">SMTP Server</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={formData.smtpHost}
|
|
onChange={(e) => setFormData({ ...formData, smtpHost: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
placeholder="smtp.example.com"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">SMTP Port</label>
|
|
<input
|
|
type="number"
|
|
required
|
|
value={formData.smtpPort}
|
|
onChange={(e) => setFormData({ ...formData, smtpPort: parseInt(e.target.value) })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Benutzername</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={formData.username}
|
|
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-slate-700 mb-1">Passwort</label>
|
|
<input
|
|
type="password"
|
|
required
|
|
value={formData.password}
|
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
|
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
|
/>
|
|
<p className="text-xs text-slate-500 mt-1">
|
|
Das Passwort wird verschlüsselt in Vault gespeichert.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-3 pt-4 border-t border-slate-200">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100 rounded-lg"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={submitting}
|
|
className="px-4 py-2 text-sm font-medium text-white bg-primary-600 rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
|
>
|
|
{submitting ? 'Speichern...' : 'Konto hinzufügen'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|