2026-03-30 17:17:20 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2026-04-01 15:11:11 +09:00
|
|
|
import { useEffect, useState } from "react";
|
2026-03-30 17:17:20 +09:00
|
|
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
|
|
|
import StarterKit from "@tiptap/starter-kit";
|
|
|
|
|
import LinkExtension from "@tiptap/extension-link";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
import { Loader2, Send } from "lucide-react";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogFooter,
|
|
|
|
|
} from "@/components/ui/dialog";
|
2026-04-01 15:11:11 +09:00
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from "@/components/ui/select";
|
|
|
|
|
import { toast } from "sonner";
|
|
|
|
|
import { sendUserMail, UserMailAccount } from "@/lib/api/userMail";
|
2026-03-30 17:17:20 +09:00
|
|
|
|
|
|
|
|
interface ComposeDialogProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
mode: "new" | "reply" | "forward";
|
|
|
|
|
to: string;
|
|
|
|
|
setTo: (v: string) => void;
|
|
|
|
|
cc: string;
|
|
|
|
|
setCc: (v: string) => void;
|
|
|
|
|
subject: string;
|
|
|
|
|
setSubject: (v: string) => void;
|
|
|
|
|
initialHtml: string;
|
|
|
|
|
setInitialHtml: (v: string) => void;
|
|
|
|
|
inReplyTo: string;
|
|
|
|
|
references: string;
|
|
|
|
|
sending: boolean;
|
|
|
|
|
setSending: (v: boolean) => void;
|
|
|
|
|
accountId: number | null;
|
2026-04-01 15:11:11 +09:00
|
|
|
accounts: UserMailAccount[];
|
2026-03-30 17:17:20 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function ComposeDialog({
|
|
|
|
|
open, onOpenChange, mode,
|
|
|
|
|
to, setTo, cc, setCc,
|
|
|
|
|
subject, setSubject,
|
|
|
|
|
initialHtml, setInitialHtml,
|
|
|
|
|
inReplyTo, references,
|
|
|
|
|
sending, setSending,
|
2026-04-01 15:11:11 +09:00
|
|
|
accountId, accounts,
|
2026-03-30 17:17:20 +09:00
|
|
|
}: ComposeDialogProps) {
|
2026-04-01 15:11:11 +09:00
|
|
|
const [fromAccountId, setFromAccountId] = useState<number | null>(accountId);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setFromAccountId(accountId ?? (accounts[0]?.id ?? null));
|
|
|
|
|
}, [accountId, accounts, open]);
|
|
|
|
|
|
2026-03-30 17:17:20 +09:00
|
|
|
const editor = useEditor({
|
|
|
|
|
extensions: [StarterKit, LinkExtension.configure({ openOnClick: false })],
|
|
|
|
|
content: initialHtml,
|
|
|
|
|
editorProps: {
|
|
|
|
|
attributes: { class: "min-h-[200px] p-2 border rounded focus:outline-none prose max-w-none" },
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (editor) editor.commands.setContent(initialHtml);
|
|
|
|
|
}, [initialHtml, editor]);
|
|
|
|
|
|
|
|
|
|
async function handleSend() {
|
2026-04-01 15:11:11 +09:00
|
|
|
if (!fromAccountId || !editor) return;
|
2026-03-30 17:17:20 +09:00
|
|
|
setSending(true);
|
|
|
|
|
try {
|
|
|
|
|
const html = editor.getHTML();
|
2026-04-01 15:11:11 +09:00
|
|
|
const result = await sendUserMail(fromAccountId, {
|
2026-03-30 17:17:20 +09:00
|
|
|
to,
|
|
|
|
|
cc: cc || undefined,
|
|
|
|
|
subject,
|
|
|
|
|
html,
|
|
|
|
|
inReplyTo: inReplyTo || undefined,
|
|
|
|
|
references: references || undefined,
|
|
|
|
|
});
|
|
|
|
|
if (result.success) {
|
|
|
|
|
onOpenChange(false);
|
|
|
|
|
setTo(""); setCc(""); setSubject(""); setInitialHtml("");
|
|
|
|
|
} else {
|
2026-04-01 15:11:11 +09:00
|
|
|
toast.error(result.message);
|
2026-03-30 17:17:20 +09:00
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
setSending(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
|
|
<DialogContent className="max-w-2xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>
|
|
|
|
|
{mode === "reply" ? "답장" : mode === "forward" ? "전달" : "새 메일"}
|
|
|
|
|
</DialogTitle>
|
|
|
|
|
</DialogHeader>
|
2026-04-01 15:11:11 +09:00
|
|
|
<div className="space-y-4">
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<Label>보내는 사람</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={fromAccountId?.toString() ?? ""}
|
|
|
|
|
onValueChange={(v) => setFromAccountId(Number(v))}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="계정을 선택하세요" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent className="z-[10002]">
|
|
|
|
|
{accounts.map((acc) => (
|
|
|
|
|
<SelectItem key={acc.id} value={acc.id.toString()}>
|
|
|
|
|
{acc.displayName} ({acc.email})
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1.5">
|
2026-03-30 17:17:20 +09:00
|
|
|
<Label>받는사람</Label>
|
|
|
|
|
<Input value={to} onChange={(e) => setTo(e.target.value)} placeholder="to@example.com" />
|
|
|
|
|
</div>
|
2026-04-01 15:11:11 +09:00
|
|
|
<div className="space-y-1.5">
|
2026-03-30 17:17:20 +09:00
|
|
|
<Label>참조(CC)</Label>
|
|
|
|
|
<Input value={cc} onChange={(e) => setCc(e.target.value)} placeholder="cc@example.com (선택)" />
|
|
|
|
|
</div>
|
2026-04-01 15:11:11 +09:00
|
|
|
<div className="space-y-1.5">
|
2026-03-30 17:17:20 +09:00
|
|
|
<Label>제목</Label>
|
|
|
|
|
<Input value={subject} onChange={(e) => setSubject(e.target.value)} />
|
|
|
|
|
</div>
|
2026-04-01 15:11:11 +09:00
|
|
|
<div className="space-y-1.5">
|
2026-03-30 17:17:20 +09:00
|
|
|
<Label>내용</Label>
|
|
|
|
|
<EditorContent editor={editor} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>취소</Button>
|
2026-04-01 15:11:11 +09:00
|
|
|
<Button onClick={handleSend} disabled={sending || !to || !subject || !fromAccountId}>
|
2026-03-30 17:17:20 +09:00
|
|
|
{sending ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <Send className="w-4 h-4 mr-2" />}
|
|
|
|
|
보내기
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|