// frontend/components/mail/Compose.tsx — 작성 슬라이드오버 (계정 사이클 · AI 작성 도우미 · 보내기) "use client"; import { useEffect, useRef, useState } from "react"; import { Icon } from "@/components/Icon"; import type { ComposeAttachment, MailAccount } from "@/lib/types"; export type ComposeState = { from: string; to?: string; cc?: string; bcc?: string; subject?: string; body?: string; inReplyTo?: string | null; attachments?: ComposeAttachment[]; }; function fmtSize(n: number): string { if (n < 1024) return `${n} B`; if (n < 1024 * 1024) return `${(n / 1024).toFixed(0)} KB`; return `${(n / 1024 / 1024).toFixed(1)} MB`; } const AI_DEFAULT = "안녕하세요,\n\n메일 잘 받았습니다. 내용 확인하고 빠른 시일 내 회신드리겠습니다.\n\n감사합니다.\n지우 드림"; export function Compose({ init, accounts, onClose, onSend, }: { init: ComposeState; accounts: MailAccount[]; onClose: () => void; onSend: (c: Required> & ComposeState) => void; }) { const [from, setFrom] = useState(init.from || accounts[0]?.id || ""); const [to, setTo] = useState(init.to || ""); const [cc, setCc] = useState(init.cc || ""); const [bcc, setBcc] = useState(init.bcc || ""); const [showCc, setShowCc] = useState(!!(init.cc || init.bcc)); const [subject, setSubject] = useState(init.subject || ""); const [body, setBody] = useState(init.body || ""); const [attachments, setAttachments] = useState(init.attachments || []); const [busy, setBusy] = useState(false); const fileRef = useRef(null); const onFiles = async (files: FileList | null) => { if (!files) return; const read = (f: File): Promise => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve({ name: f.name, mime: f.type || "application/octet-stream", data_b64: String(reader.result).split(",")[1] ?? "", }); reader.onerror = () => reject(reader.error); reader.readAsDataURL(f); }); const added = await Promise.all(Array.from(files).map(read)); setAttachments((prev) => [...prev, ...added]); if (fileRef.current) fileRef.current.value = ""; }; const removeAttachment = (i: number) => setAttachments((prev) => prev.filter((_, idx) => idx !== i)); const accMap: Record = {}; accounts.forEach((a) => (accMap[a.id] = a)); const a = accMap[from]; const accTone = a?.tone ?? "ink"; const accName = a?.name ?? from; const accEmail = a?.email ?? ""; const cycleFrom = () => { const ids = accounts.map((x) => x.id); if (!ids.length) return; setFrom(ids[(ids.indexOf(from) + 1) % ids.length]); }; const aiAction = (fn: (b: string) => string) => { setBusy(true); setTimeout(() => { setBody(fn(body)); setBusy(false); }, 650); }; useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", h); return () => window.removeEventListener("keydown", h); }, [onClose]); return ( <>