first commit
This commit is contained in:
165
components/mail/ComposeModal.tsx
Normal file
165
components/mail/ComposeModal.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
"use client";
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import type { MailMessage } from "@/app/dashboard/mail/page";
|
||||
import { formatBytes } from "@/lib/format";
|
||||
|
||||
interface AttachmentFile {
|
||||
file: File;
|
||||
name: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export default function ComposeModal({ replyTo, onClose, onSent }: {
|
||||
replyTo: MailMessage | null;
|
||||
onClose: () => void;
|
||||
onSent: () => void;
|
||||
}) {
|
||||
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, "<")}</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 || "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, "<")}</pre>`,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "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 ? "Yanıtla" : "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">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">CC (isteğe bağlı)</label>
|
||||
<input type="text" className="input" placeholder="cc@domain.com" value={cc}
|
||||
onChange={(e) => setCc(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">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>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 }}>
|
||||
Toplam: {formatBytes(totalSize)} — {attachments.length} dosya
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-ghost" onClick={onClose}>İptal</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={sending}>
|
||||
{sending ? <span className="spinner" /> : <SendIcon />} 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>; }
|
||||
Reference in New Issue
Block a user