first commit

This commit is contained in:
debu-2 2026-10-03 21:14:44 +05:30
commit cce9a8c9ba
73 changed files with 8665 additions and 0 deletions

View file

@ -0,0 +1,110 @@
import {
ArrowLeft,
Mail,
MailOpen,
RotateCcw,
Trash2,
X,
} from "lucide-react";
import SenderAvatar from "./SenderAvatar";
import { senderDisplayName } from "../utils/senderAvatar";
function joinAddresses(value) {
if (!value) return "";
const addresses = Array.isArray(value) ? value : [value];
return addresses
.map((item) => (typeof item === "string" ? item : item?.email || item?.address))
.filter(Boolean)
.join(", ");
}
function detailDate(email) {
const value =
email?.received_at || email?.sent_at || email?.created_at || email?.updated_at;
if (!value) return "";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "";
return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(date);
}
export default function MailDetail({
email,
loading,
error,
mailbox,
busy,
onClose,
onToggleRead,
onTrash,
onRestore,
onDelete,
}) {
return (
<article className={`mail-detail${email || loading ? " open" : ""}`} aria-live="polite">
<header className="mail-detail-toolbar">
<button type="button" onClick={onClose} aria-label="Back to message list">
<ArrowLeft size={18} />
</button>
<div>
{email && (
<button
type="button"
disabled={busy}
onClick={() => onToggleRead(!email.is_read)}
title={email.is_read ? "Mark unread" : "Mark read"}
aria-label={email.is_read ? "Mark unread" : "Mark read"}
>
{email.is_read ? <Mail size={17} /> : <MailOpen size={17} />}
</button>
)}
{email && mailbox === "trash" && (
<button type="button" disabled={busy} onClick={onRestore} title="Restore" aria-label="Restore message">
<RotateCcw size={17} />
</button>
)}
{email && (
<button
className={mailbox === "trash" ? "danger" : ""}
type="button"
disabled={busy}
onClick={mailbox === "trash" ? onDelete : onTrash}
title={mailbox === "trash" ? "Delete permanently" : "Move to trash"}
aria-label={mailbox === "trash" ? "Delete message permanently" : "Move message to trash"}
>
<Trash2 size={17} />
</button>
)}
<button className="mail-detail-close" type="button" onClick={onClose} aria-label="Close message">
<X size={18} />
</button>
</div>
</header>
{loading && <div className="mail-detail-state">Opening message…</div>}
{error && <div className="mail-detail-state error" role="alert">{error}</div>}
{email && !loading && (
<div className="mail-detail-content">
<h2>{email.subject?.trim() || "(No subject)"}</h2>
<div className="mail-detail-meta">
<div>
<div className="mail-detail-sender">
<SenderAvatar email={email} />
<strong>{senderDisplayName(email)}</strong>
</div>
<small>From: {joinAddresses(email.sender) || "Unknown"}</small>
<small>To: {joinAddresses(email.recipients || email.to) || "Undisclosed"}</small>
{joinAddresses(email.cc) && <small>Cc: {joinAddresses(email.cc)}</small>}
</div>
<time>{detailDate(email)}</time>
</div>
<div className="mail-detail-body">
{email.body_text || email.text_body || email.plain_text || "This message has no plain-text content."}
</div>
</div>
)}
</article>
);
}