first commit

This commit is contained in:
AyrisAI
2026-05-14 01:57:52 +03:00
parent 863a32cd35
commit 4a9196f483
47 changed files with 12043 additions and 102 deletions

200
app/dashboard/mail/page.tsx Normal file
View File

@@ -0,0 +1,200 @@
"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>;
}