Files
vexplor_dev/frontend/app/(main)/mail/imap/ComposeDialog.tsx
2026-04-01 15:29:00 +09:00

154 lines
4.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
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";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
import { sendUserMail, UserMailAccount } from "@/lib/api/userMail";
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;
accounts: UserMailAccount[];
}
export default function ComposeDialog({
open, onOpenChange, mode,
to, setTo, cc, setCc,
subject, setSubject,
initialHtml, setInitialHtml,
inReplyTo, references,
sending, setSending,
accountId, accounts,
}: ComposeDialogProps) {
const [fromAccountId, setFromAccountId] = useState<number | null>(accountId);
useEffect(() => {
setFromAccountId(accountId ?? (accounts[0]?.id ?? null));
}, [accountId, accounts, open]);
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() {
if (!fromAccountId || !editor) return;
setSending(true);
try {
const html = editor.getHTML();
const result = await sendUserMail(fromAccountId, {
to,
cc: cc || undefined,
subject,
html,
inReplyTo: inReplyTo || undefined,
references: references || undefined,
});
if (result.success) {
onOpenChange(false);
setTo(""); setCc(""); setSubject(""); setInitialHtml("");
} else {
toast.error(result.message);
}
} finally {
setSending(false);
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>
{mode === "reply" ? "답장" : mode === "forward" ? "전달" : "새 메일"}
</DialogTitle>
</DialogHeader>
<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">
<Label></Label>
<Input value={to} onChange={(e) => setTo(e.target.value)} placeholder="to@example.com" />
</div>
<div className="space-y-1.5">
<Label>(CC)</Label>
<Input value={cc} onChange={(e) => setCc(e.target.value)} placeholder="cc@example.com (선택)" />
</div>
<div className="space-y-1.5">
<Label></Label>
<Input value={subject} onChange={(e) => setSubject(e.target.value)} />
</div>
<div className="space-y-1.5">
<Label></Label>
<EditorContent editor={editor} />
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}></Button>
<Button onClick={handleSend} disabled={sending || !to || !subject || !fromAccountId}>
{sending ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <Send className="w-4 h-4 mr-2" />}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}