agentic.email/frontend/src/components/DashboardPage.jsx
2026-10-04 00:23:52 +05:30

357 lines
11 KiB
JavaScript

import {
ChevronDown,
Clock3,
FileEdit,
Inbox,
ListChecks,
LogOut,
Mail,
Menu,
Plug,
Send,
Settings,
ShieldAlert,
Sparkles,
Star,
Trash2,
X,
} from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { getCurrentAccount, getMyCompanyDomain } from "../api";
import Brand from "./Brand";
import CompanyDnsSetup from "./CompanyDnsSetup";
import ComposeModal from "./ComposeModal";
import MailboxView from "./MailboxView";
const mailItems = [
[Inbox, "Inbox", "unread"],
[Star, "Starred", "starred"],
[Clock3, "Snoozed"],
[Send, "Sent"],
[FileEdit, "Drafts", "drafts"],
[ShieldAlert, "Spam"],
[Trash2, "Trash"],
[Clock3, "Scheduled"],
];
const labels = [
["Important", "rose", "Important"],
["Projects", "violet", "Projects"],
["Clients", "blue", "Clients"],
["Finance", "amber", "Finance"],
["Personal", "green", "Personal"],
];
const agentItems = [
[Sparkles, "Ask Agent", "ask"],
[ListChecks, "Action Items", "actions"],
[Sparkles, "Summaries", "summaries"],
[Plug, "Integrations", "integrations"],
];
function clearSession() {
sessionStorage.removeItem("agmail_access_token");
sessionStorage.removeItem("agmail_account");
}
function SidebarSection({ title, children, className = "" }) {
return (
<section className={`dashboard-sidebar-section ${className}`}>
<h2>{title}</h2>
{children}
</section>
);
}
function NavBadge({ value, variant = "coral" }) {
if (!value || value <= 0) return null;
return <em className={variant === "neutral" ? "nav-badge-neutral" : "nav-badge-coral"}>{value}</em>;
}
export default function DashboardPage() {
const [account, setAccount] = useState(null);
const [companyDomain, setCompanyDomain] = useState(null);
const [needsDnsSetup, setNeedsDnsSetup] = useState(false);
const [domainError, setDomainError] = useState("");
const [active, setActive] = useState("Inbox");
const [menuOpen, setMenuOpen] = useState(false);
const [composeOpen, setComposeOpen] = useState(false);
const [notice, setNotice] = useState("");
const [loading, setLoading] = useState(true);
const [mailRefreshKey, setMailRefreshKey] = useState(0);
const [lens, setLens] = useState("all");
const [agentNav, setAgentNav] = useState(null);
const [labelsExpanded, setLabelsExpanded] = useState(false);
const [mailStats, setMailStats] = useState({
unread: 0,
starred: 0,
important: 0,
drafts: 0,
actionItems: 0,
labels: {},
});
const handleStats = useCallback((stats) => setMailStats(stats), []);
useEffect(() => {
const token = sessionStorage.getItem("agmail_access_token");
if (!token) {
window.location.replace("/login");
return;
}
getCurrentAccount(token)
.then(async (currentAccount) => {
sessionStorage.setItem("agmail_account", JSON.stringify(currentAccount));
setAccount(currentAccount);
if (currentAccount.account_type === "company") {
try {
const domain = await getMyCompanyDomain(token);
setCompanyDomain(domain);
setNeedsDnsSetup(domain.status === "pending_dns");
} catch (error) {
setDomainError(error.message || "Could not load your company domain.");
}
}
setLoading(false);
})
.catch(() => {
clearSession();
window.location.replace("/login");
});
}, []);
function logout() {
clearSession();
window.location.assign("/login");
}
function selectMailItem(item) {
setAgentNav(null);
if (item === "Starred") {
setActive("Inbox");
setLens("starred");
} else if (item === "Sent") {
setActive("Outbox");
setLens("all");
} else {
setActive(item);
setLens("all");
}
setMenuOpen(false);
}
function selectLabel(name) {
setAgentNav(null);
setActive("Inbox");
setLens(name);
setMenuOpen(false);
}
function selectAgentItem(item, actionKey) {
setActive("Inbox");
setLens("all");
setAgentNav(actionKey);
setMenuOpen(false);
}
function emailSent() {
setComposeOpen(false);
setActive("Outbox");
setMailRefreshKey((key) => key + 1);
setNotice("Message accepted by the mail server. Delivery can take a few minutes — check spam if it does not arrive.");
window.setTimeout(() => setNotice(""), 4000);
}
if (loading || !account) {
return <main className="dashboard-loading"><Brand /><span>Opening your workspace…</span></main>;
}
if (account.account_type === "company" && domainError) {
return (
<main className="company-dns-setup">
<header className="company-dns-header">
<Brand />
<button type="button" onClick={logout}>Log out</button>
</header>
<section className="company-dns-card">
<p className="eyebrow">Company setup</p>
<h1>Domain not ready</h1>
<p className="form-error" role="alert">{domainError}</p>
</section>
</main>
);
}
if (needsDnsSetup && companyDomain) {
return (
<CompanyDnsSetup
account={account}
domain={companyDomain}
onComplete={() => {
setCompanyDomain({ ...companyDomain, status: "active" });
setNeedsDnsSetup(false);
}}
onLogout={logout}
/>
);
}
const workspaceLabel = account.account_type === "company" ? "Company" : "Individual";
const workspaceTitle = account.account_type === "company" && companyDomain
? companyDomain.domain_name
: "Personal workspace";
const mailActive = lens === "starred"
? "Starred"
: active === "Outbox"
? "Sent"
: labels.some(([name]) => lens === name)
? "Inbox"
: active;
return (
<div className="dashboard">
<header className="dashboard-mobile-header">
<Brand />
<button aria-label="Open navigation" type="button" onClick={() => setMenuOpen(true)}>
<Menu size={21} />
</button>
</header>
{menuOpen && <button className="dashboard-scrim" aria-label="Close navigation" onClick={() => setMenuOpen(false)} />}
<aside className={menuOpen ? "dashboard-sidebar open" : "dashboard-sidebar"}>
<div className="dashboard-brand-row">
<Brand />
<button aria-label="Close navigation" type="button" onClick={() => setMenuOpen(false)}><X size={19} /></button>
</div>
<div className="dashboard-compose-wrap">
<button className="dashboard-compose" type="button" onClick={() => setComposeOpen(true)}>
<span className="dashboard-compose-main">
<Mail size={16} strokeWidth={1.75} />
<span>Compose</span>
</span>
</button>
<button className="dashboard-compose-menu" type="button" aria-label="Compose options">
<ChevronDown size={14} strokeWidth={1.75} />
</button>
</div>
<SidebarSection title="Mail" className="dashboard-sidebar-mail">
<nav aria-label="Mail">
{mailItems.map(([ItemIcon, label, countKey]) => (
<button
className={`dashboard-nav-item${mailActive === label ? " active" : ""}`}
key={label}
type="button"
onClick={() => selectMailItem(label)}
>
<ItemIcon size={16} strokeWidth={1.75} />
<span>{label}</span>
<NavBadge value={countKey ? mailStats[countKey] : 0} />
</button>
))}
</nav>
</SidebarSection>
<SidebarSection title="Labels" className="dashboard-sidebar-labels">
<nav aria-label="Labels">
{labels.map(([name, tone, countKey]) => (
<button
className={`dashboard-nav-item dashboard-label-item${lens === name ? " active" : ""}`}
key={name}
type="button"
onClick={() => selectLabel(name)}
>
<i className={`label-dot label-dot-${tone}`} />
<span>{name}</span>
<NavBadge variant="neutral" value={mailStats.labels?.[countKey]} />
</button>
))}
<button
type="button"
className="dashboard-nav-item dashboard-label-more"
onClick={() => setLabelsExpanded((open) => !open)}
>
<ChevronDown size={14} strokeWidth={1.75} className={labelsExpanded ? "open" : ""} />
<span>{labelsExpanded ? "Less" : "More"}</span>
</button>
</nav>
</SidebarSection>
<SidebarSection title="Agent" className="dashboard-sidebar-agent">
<nav aria-label="Agent">
{agentItems.map(([ItemIcon, label, actionKey]) => (
<button
className={`dashboard-nav-item dashboard-agent-item${agentNav === actionKey ? " active" : ""}`}
key={label}
type="button"
onClick={() => selectAgentItem(label, actionKey)}
>
<ItemIcon size={16} strokeWidth={1.75} />
<span>{label}</span>
<NavBadge value={actionKey === "actions" ? mailStats.actionItems : 0} />
</button>
))}
</nav>
</SidebarSection>
<div className="dashboard-profile">
<span>{account.first_name[0]}{account.last_name[0]}</span>
<div><strong>{account.first_name} {account.last_name}</strong><small>{account.email}</small></div>
<button aria-label="Log out" title="Log out" type="button" onClick={logout}><LogOut size={17} /></button>
</div>
</aside>
<main className="dashboard-content">
{!["Inbox", "Outbox", "Trash"].includes(active) && (
<div className="dashboard-content-heading">
<div><p>{workspaceTitle}</p><h1>{active}</h1></div>
<span className="dashboard-account-pill">{workspaceLabel}</span>
</div>
)}
{active === "Inbox" && (
<MailboxView
mailbox="inbox"
title="Inbox"
refreshKey={mailRefreshKey}
account={account}
lens={lens}
agentNav={agentNav}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
onStats={handleStats}
/>
)}
{active === "Outbox" && (
<MailboxView
mailbox="sent"
title="Sent"
refreshKey={mailRefreshKey}
account={account}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
/>
)}
{active === "Trash" && (
<MailboxView
mailbox="trash"
title="Trash"
refreshKey={mailRefreshKey}
account={account}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
/>
)}
{!["Inbox", "Outbox", "Trash"].includes(active) && (
<section className="dashboard-empty">
<span><Sparkles size={24} /></span>
<h2>{active} is ready for you</h2>
<p>Your personal mail and agent tools will appear here.</p>
</section>
)}
</main>
{notice && <div className="dashboard-notice" role="status">{notice}</div>}
{composeOpen && <ComposeModal account={account} onClose={() => setComposeOpen(false)} onSent={emailSent} />}
</div>
);
}