Files
webmailserver/components/mail/ComposeModal.tsx

168 lines
7.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useRef, useCallback } from "react";
import type { MailMessage } from "@/app/[lang]/dashboard/mail/page";
import { formatBytes } from "@/lib/format";
import { useDictionary } from "@/components/DictionaryContext";
interface AttachmentFile {
file: File;
name: string;
size: number;
}
export default function ComposeModal({ replyTo, onClose, onSent }: {
replyTo: MailMessage | null;
onClose: () => void;
onSent: () => void;
}) {
const dict = useDictionary();
const [to, setTo] = useState(replyTo ? replyTo.from[0]?.address ?? "" : "");
const [cc, setCc] = useState("");
const [subject, setSubject] = useState(replyTo ? `Re: ${replyTo.subject.replace(/^Re:\s*/i, "")}` : "");
const [body, setBody] = useState(replyTo ? `\n\n---\n${replyTo.text?.slice(0, 500) ?? ""}` : "");
const [attachments, setAttachments] = useState<AttachmentFile[]>([]);
const [sending, setSending] = useState(false);
const [error, setError] = useState("");
const [dragOver, setDragOver] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const addFiles = useCallback((files: FileList | null) => {
if (!files) return;
const newFiles = Array.from(files).map((f) => ({ file: f, name: f.name, size: f.size }));
setAttachments((prev) => [...prev, ...newFiles]);
}, []);
const removeAttachment = (index: number) => {
setAttachments((prev) => prev.filter((_, i) => i !== index));
};
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
addFiles(e.dataTransfer.files);
}, [addFiles]);
const handleSend = async (e: React.FormEvent) => {
e.preventDefault();
setSending(true);
setError("");
try {
if (attachments.length > 0) {
// Use FormData for attachments
const formData = new FormData();
formData.append("to", to);
if (cc) formData.append("cc", cc);
formData.append("subject", subject);
formData.append("text", body);
formData.append("html", `<pre style="font-family:inherit;white-space:pre-wrap">${body.replace(/</g, "&lt;")}</pre>`);
attachments.forEach((att) => {
formData.append("attachments", att.file, att.name);
});
const res = await fetch("/api/mail/send", { method: "POST", body: formData });
const data = await res.json();
if (!res.ok) throw new Error(data.error || dict.mailClient.sendError || "Gönderilemedi");
} else {
// JSON for simple messages
const res = await fetch("/api/mail/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to, cc: cc || undefined, subject, text: body,
html: `<pre style="font-family:inherit;white-space:pre-wrap">${body.replace(/</g, "&lt;")}</pre>`,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || dict.mailClient.sendError || "Gönderilemedi");
}
onSent();
} catch (err: any) {
setError(err.message);
}
setSending(false);
};
const totalSize = attachments.reduce((sum, a) => sum + a.size, 0);
return (
<div className="modal-overlay" onClick={(e) => e.target === e.currentTarget && onClose()}>
<div className="modal" style={{ maxWidth: 620 }}>
<div className="modal-header">
<h2 className="modal-title">{replyTo ? (dict.mailClient.reply || "Yanıtla") : (dict.mailClient.composeTitle || "Yeni Mail")}</h2>
<button className="modal-close" onClick={onClose}></button>
</div>
<form onSubmit={handleSend}>
<div className="modal-body form-group">
{error && <div className="error-msg">{error}</div>}
<div>
<label className="label">{dict.mailClient.to || "Alıcı"}</label>
<input type="email" className="input" placeholder="alici@domain.com" value={to}
onChange={(e) => setTo(e.target.value)} required autoFocus />
</div>
<div>
<label className="label">{dict.mailClient.cc || "CC"}</label>
<input type="text" className="input" placeholder="cc@domain.com" value={cc}
onChange={(e) => setCc(e.target.value)} />
</div>
<div>
<label className="label">{dict.mailClient.subject || "Konu"}</label>
<input type="text" className="input" value={subject}
onChange={(e) => setSubject(e.target.value)} required />
</div>
<div>
<label className="label">Mesaj</label>
<textarea className="input" rows={8} value={body}
onChange={(e) => setBody(e.target.value)}
style={{ resize: "vertical", fontFamily: "inherit" }} />
</div>
{/* Attachment zone */}
<div
className={`compose-dropzone ${dragOver ? "active" : ""}`}
onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
onDragLeave={() => setDragOver(false)}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
<input
ref={fileInputRef}
type="file"
multiple
style={{ display: "none" }}
onChange={(e) => { addFiles(e.target.files); e.target.value = ""; }}
/>
<span style={{ fontSize: 20 }}>📎</span>
<span>{dict.mailClient.dropFiles || "Dosya sürükleyin veya tıklayın"}</span>
</div>
{/* Attachment list */}
{attachments.length > 0 && (
<div className="compose-attachments">
{attachments.map((att, i) => (
<div key={i} className="compose-att-item">
<span>📎 {att.name}</span>
<span style={{ color: "var(--text-muted)", fontSize: 11 }}>{formatBytes(att.size)}</span>
<button type="button" className="att-remove" onClick={() => removeAttachment(i)}></button>
</div>
))}
<div style={{ fontSize: 11, color: "var(--text-muted)", marginTop: 4 }}>
{formatBytes(totalSize)} {attachments.length} {dict.mailClient.attachments || "dosya"}
</div>
</div>
)}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-ghost" onClick={onClose}>{dict.mailClient.cancel || "İptal"}</button>
<button type="submit" className="btn btn-primary" disabled={sending}>
{sending ? <span className="spinner" /> : <SendIcon />} {dict.mailClient.send || "Gönder"}
</button>
</div>
</form>
</div>
</div>
);
}
function SendIcon() { return <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" x2="11" y1="2" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>; }