201 lines
6.5 KiB
TypeScript
201 lines
6.5 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect, useCallback } from "react";
|
||
import MailLogin from "@/components/mail/MailLogin";
|
||
import FolderList from "@/components/mail/FolderList";
|
||
import MessageList from "@/components/mail/MessageList";
|
||
import MessageView from "@/components/mail/MessageView";
|
||
import ComposeModal from "@/components/mail/ComposeModal";
|
||
|
||
export interface MailFolder {
|
||
name: string;
|
||
path: string;
|
||
specialUse?: string;
|
||
messages: number;
|
||
unseen: number;
|
||
}
|
||
|
||
export interface MailEnvelope {
|
||
uid: number;
|
||
subject: string;
|
||
from: { name: string; address: string }[];
|
||
to: { name: string; address: string }[];
|
||
date: string;
|
||
seen: boolean;
|
||
flagged: boolean;
|
||
hasAttachments: boolean;
|
||
}
|
||
|
||
export interface MailMessage extends MailEnvelope {
|
||
cc: { name: string; address: string }[];
|
||
html: string;
|
||
text: string;
|
||
attachments: { filename: string; contentType: string; size: number }[];
|
||
}
|
||
|
||
export default function MailPage() {
|
||
const [connected, setConnected] = useState<boolean | null>(null);
|
||
const [email, setEmail] = useState("");
|
||
const [folders, setFolders] = useState<MailFolder[]>([]);
|
||
const [activeFolder, setActiveFolder] = useState("INBOX");
|
||
const [messages, setMessages] = useState<MailEnvelope[]>([]);
|
||
const [selectedUid, setSelectedUid] = useState<number | null>(null);
|
||
const [openMessage, setOpenMessage] = useState<MailMessage | null>(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [showCompose, setShowCompose] = useState(false);
|
||
const [replyTo, setReplyTo] = useState<MailMessage | null>(null);
|
||
|
||
// Check connection
|
||
useEffect(() => {
|
||
fetch("/api/mail/auth")
|
||
.then((r) => r.json())
|
||
.then((d) => {
|
||
setConnected(d.connected);
|
||
if (d.email) setEmail(d.email);
|
||
});
|
||
}, []);
|
||
|
||
// Load folders
|
||
const loadFolders = useCallback(async () => {
|
||
const res = await fetch("/api/mail/folders");
|
||
if (res.ok) setFolders(await res.json());
|
||
}, []);
|
||
|
||
// Load messages
|
||
const loadMessages = useCallback(async (folder: string) => {
|
||
setLoading(true);
|
||
const res = await fetch(`/api/mail/messages?folder=${encodeURIComponent(folder)}`);
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
setMessages(data.messages ?? []);
|
||
}
|
||
setLoading(false);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (connected) {
|
||
loadFolders();
|
||
loadMessages(activeFolder);
|
||
}
|
||
}, [connected, activeFolder, loadFolders, loadMessages]);
|
||
|
||
// Open message
|
||
const openMsg = async (uid: number) => {
|
||
setSelectedUid(uid);
|
||
const res = await fetch(`/api/mail/messages/${uid}?folder=${encodeURIComponent(activeFolder)}`);
|
||
if (res.ok) {
|
||
const msg = await res.json();
|
||
setOpenMessage(msg);
|
||
// Mark as read in list
|
||
setMessages((prev) => prev.map((m) => m.uid === uid ? { ...m, seen: true } : m));
|
||
}
|
||
};
|
||
|
||
// Delete
|
||
const handleDelete = async (uid: number) => {
|
||
await fetch("/api/mail/messages", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ action: "delete", folder: activeFolder, uid }),
|
||
});
|
||
setMessages((prev) => prev.filter((m) => m.uid !== uid));
|
||
if (selectedUid === uid) { setSelectedUid(null); setOpenMessage(null); }
|
||
};
|
||
|
||
// Reply
|
||
const handleReply = (msg: MailMessage) => {
|
||
setReplyTo(msg);
|
||
setShowCompose(true);
|
||
};
|
||
|
||
// Disconnect
|
||
const handleDisconnect = async () => {
|
||
await fetch("/api/mail/auth", { method: "DELETE" });
|
||
setConnected(false);
|
||
setEmail("");
|
||
setFolders([]);
|
||
setMessages([]);
|
||
setOpenMessage(null);
|
||
};
|
||
|
||
if (connected === null) {
|
||
return <div className="empty-state"><span className="spinner" style={{ width: 24, height: 24 }} /></div>;
|
||
}
|
||
|
||
if (!connected) {
|
||
return <MailLogin onSuccess={(e) => { setConnected(true); setEmail(e); }} />;
|
||
}
|
||
|
||
return (
|
||
<div className="mail-layout">
|
||
<div className="mail-sidebar">
|
||
<button className="btn btn-primary" style={{ width: "100%" }} onClick={() => { setReplyTo(null); setShowCompose(true); }}>
|
||
<ComposeIcon /> Yeni Mail
|
||
</button>
|
||
<FolderList
|
||
folders={folders}
|
||
active={activeFolder}
|
||
onSelect={(f) => { setActiveFolder(f); setSelectedUid(null); setOpenMessage(null); }}
|
||
/>
|
||
<div className="mail-account">
|
||
<div className="mail-account-email">{email}</div>
|
||
<button className="btn btn-ghost btn-sm" onClick={handleDisconnect} style={{ fontSize: 11 }}>
|
||
Çıkış
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mail-list">
|
||
<div className="mail-list-header">
|
||
<h2>
|
||
{folders.find((f) => f.path === activeFolder)?.name ?? activeFolder}
|
||
</h2>
|
||
<button className="btn btn-ghost btn-sm" onClick={() => loadMessages(activeFolder)}>↻</button>
|
||
</div>
|
||
<MessageList
|
||
messages={messages}
|
||
loading={loading}
|
||
selectedUid={selectedUid}
|
||
onSelect={openMsg}
|
||
onDelete={handleDelete}
|
||
/>
|
||
</div>
|
||
|
||
<div className="mail-detail">
|
||
{openMessage ? (
|
||
<MessageView
|
||
message={openMessage}
|
||
onReply={() => handleReply(openMessage)}
|
||
onDelete={() => handleDelete(openMessage.uid)}
|
||
folder={activeFolder}
|
||
/>
|
||
) : (
|
||
<div className="mail-empty">
|
||
<div className="mail-empty-icon">
|
||
<MailBigIcon />
|
||
</div>
|
||
<div style={{ fontWeight: 600, fontSize: 14, color: "var(--text-secondary)" }}>Bir mail seçin</div>
|
||
<div style={{ fontSize: 12 }}>Okumak için soldaki listeden bir mail seçin</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{showCompose && (
|
||
<ComposeModal
|
||
replyTo={replyTo}
|
||
onClose={() => { setShowCompose(false); setReplyTo(null); }}
|
||
onSent={() => { setShowCompose(false); setReplyTo(null); loadMessages(activeFolder); }}
|
||
/>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function MailBigIcon() {
|
||
return <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.5 }}><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>;
|
||
}
|
||
|
||
function ComposeIcon() {
|
||
return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>;
|
||
}
|