import { useState, useRef, useEffect } from 'react'; import { Send, Paperclip, X, FileText } from 'lucide-react'; import toast from 'react-hot-toast'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; import { stressfreiEmailApi, CachedEmail, MailboxAccount, EmailAttachment } from '../../services/api'; import { useMutation } from '@tanstack/react-query'; interface ComposeEmailModalProps { isOpen: boolean; onClose: () => void; account: MailboxAccount; replyTo?: CachedEmail; forwardOf?: CachedEmail; // Weiterleiten: Body vorausgefüllt, To leer onSuccess?: () => void; contractId?: number; // Optional: Vertrag dem die gesendete E-Mail zugeordnet wird } export default function ComposeEmailModal({ isOpen, onClose, account, replyTo, forwardOf, onSuccess, contractId, }: ComposeEmailModalProps) { const [to, setTo] = useState(''); const [cc, setCc] = useState(''); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); const [attachments, setAttachments] = useState([]); const [error, setError] = useState(null); const fileInputRef = useRef(null); // Formular bei Modal-Öffnung initialisieren useEffect(() => { if (isOpen) { if (replyTo) { // Antwort: Felder vorausfüllen setTo(replyTo.fromAddress || ''); // Betreff: "Re:" nur hinzufügen wenn nicht schon vorhanden const existingSubject = replyTo.subject || ''; const hasRePrefix = /^(Re|Aw|Fwd|Wg):\s*/i.test(existingSubject); setSubject(hasRePrefix ? existingSubject : `Re: ${existingSubject}`); // Ursprüngliche Nachricht zitieren const originalDate = new Date(replyTo.receivedAt).toLocaleString('de-DE'); const quotedText = replyTo.textBody ? `\n\n--- Ursprüngliche Nachricht ---\nVon: ${replyTo.fromName || replyTo.fromAddress}\nAm: ${originalDate}\n\n${replyTo.textBody}` : ''; setBody(quotedText); } else if (forwardOf) { // Weiterleiten: To leer (User trägt selbst ein), Betreff mit „Fwd:" setTo(''); const existingSubject = forwardOf.subject || ''; const hasFwdPrefix = /^(Fwd|Wg):\s*/i.test(existingSubject); setSubject(hasFwdPrefix ? existingSubject : `Fwd: ${existingSubject}`); // Original-Header + Body zitieren (mehr Felder als bei Reply, damit // der weitergeleitete Kontext erhalten bleibt) const originalDate = new Date(forwardOf.receivedAt).toLocaleString('de-DE'); let toLine = ''; try { const parsed = JSON.parse(forwardOf.toAddresses || '[]'); if (Array.isArray(parsed) && parsed.length > 0) { toLine = `\nAn: ${parsed.join(', ')}`; } } catch { // toAddresses war kein JSON – ignorieren } const quotedText = forwardOf.textBody ? `\n\n--- Weitergeleitete Nachricht ---\nVon: ${ forwardOf.fromName || forwardOf.fromAddress }${toLine}\nDatum: ${originalDate}\nBetreff: ${existingSubject}\n\n${forwardOf.textBody}` : ''; setBody(quotedText); } else { // Neue E-Mail: Felder leer setTo(''); setSubject(''); setBody(''); } setCc(''); setAttachments([]); setError(null); } }, [isOpen, replyTo, forwardOf]); // Maximale Dateigröße: 10 MB const MAX_FILE_SIZE = 10 * 1024 * 1024; // Maximale Gesamtgröße aller Anhänge: 25 MB const MAX_TOTAL_SIZE = 25 * 1024 * 1024; // Datei zu Base64 konvertieren const fileToBase64 = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { // data:application/pdf;base64,JVBERi0... -> JVBERi0... const result = reader.result as string; const base64 = result.split(',')[1]; resolve(base64); }; reader.onerror = reject; }); }; // Dateien hinzufügen const handleFileSelect = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files) return; const newAttachments: EmailAttachment[] = []; let currentTotalSize = attachments.reduce( (sum, att) => sum + (att.content.length * 0.75), // Base64 ist ~33% größer 0 ); for (const file of Array.from(files)) { // Einzelne Dateigröße prüfen if (file.size > MAX_FILE_SIZE) { setError(`Datei "${file.name}" ist zu groß (max. 10 MB)`); continue; } // Gesamtgröße prüfen if (currentTotalSize + file.size > MAX_TOTAL_SIZE) { setError('Maximale Gesamtgröße der Anhänge erreicht (25 MB)'); break; } try { const content = await fileToBase64(file); newAttachments.push({ filename: file.name, content, contentType: file.type || 'application/octet-stream', }); currentTotalSize += file.size; } catch { setError(`Fehler beim Lesen von "${file.name}"`); } } if (newAttachments.length > 0) { setAttachments((prev) => [...prev, ...newAttachments]); } // Input zurücksetzen damit gleiche Datei erneut gewählt werden kann if (fileInputRef.current) { fileInputRef.current.value = ''; } }; // Anhang entfernen const removeAttachment = (index: number) => { setAttachments((prev) => prev.filter((_, i) => i !== index)); }; // Dateigröße formatieren const formatFileSize = (base64Content: string): string => { const bytes = base64Content.length * 0.75; // Base64 Dekodierung if (bytes < 1024) return `${Math.round(bytes)} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; const sendMutation = useMutation({ mutationFn: () => stressfreiEmailApi.sendEmail(account.id, { to: to.split(',').map((e) => e.trim()).filter(Boolean), cc: cc ? cc.split(',').map((e) => e.trim()).filter(Boolean) : undefined, subject, text: body, inReplyTo: replyTo?.messageId, references: replyTo?.messageId ? [replyTo.messageId] : undefined, attachments: attachments.length > 0 ? attachments : undefined, contractId, }), onSuccess: (result) => { // Backend kann success=false zurückgeben auch bei HTTP 200 if (result && (result as any).success === false) { const msg = (result as any).error || 'E-Mail-Versand fehlgeschlagen'; setError(msg); toast.error(`SMTP-Fehler: ${msg}`, { duration: 8000 }); return; } toast.success('E-Mail versendet'); onSuccess?.(); handleClose(); }, onError: (err: any) => { const msg = err?.response?.data?.error || (err instanceof Error ? err.message : 'Fehler beim Senden'); setError(msg); toast.error(`SMTP-Fehler: ${msg}`, { duration: 8000 }); }, }); const handleClose = () => { // Formular wird beim nächsten Öffnen durch useEffect initialisiert onClose(); }; const handleSend = () => { if (!to.trim()) { setError('Bitte Empfänger angeben'); return; } if (!subject.trim()) { setError('Bitte Betreff angeben'); return; } setError(null); sendMutation.mutate(); }; return (
{/* From */}
{account.email}
{/* To */}
setTo(e.target.value)} placeholder="empfaenger@example.com" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />

Mehrere Empfänger mit Komma trennen

{/* CC */}
setCc(e.target.value)} placeholder="cc@example.com" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Subject */}
setSubject(e.target.value)} placeholder="Betreff eingeben" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Body */}