ui enhancement

This commit is contained in:
debu-2 2026-10-03 22:13:49 +05:30
parent 78aac5a925
commit 45e3898acb
4 changed files with 175 additions and 91 deletions

View file

@ -1,10 +1,9 @@
import { import {
Bot,
ChevronDown, ChevronDown,
Clock3, Clock3,
FileEdit, FileEdit,
Inbox, Inbox,
ListFilter, ListChecks,
LogOut, LogOut,
Mail, Mail,
Menu, Menu,
@ -14,7 +13,6 @@ import {
ShieldAlert, ShieldAlert,
Sparkles, Sparkles,
Star, Star,
Tags,
Trash2, Trash2,
X, X,
} from "lucide-react"; } from "lucide-react";
@ -30,26 +28,25 @@ const mailItems = [
[Star, "Starred", "starred"], [Star, "Starred", "starred"],
[Clock3, "Snoozed"], [Clock3, "Snoozed"],
[Send, "Sent"], [Send, "Sent"],
[FileEdit, "Drafts"], [FileEdit, "Drafts", "drafts"],
[ShieldAlert, "Spam"], [ShieldAlert, "Spam"],
[Trash2, "Trash"], [Trash2, "Trash"],
[Clock3, "Scheduled"], [Clock3, "Scheduled"],
]; ];
const labels = [ const labels = [
["Important", "rose"], ["Important", "rose", "Important"],
["Projects", "violet"], ["Projects", "violet", "Projects"],
["Clients", "blue"], ["Clients", "blue", "Clients"],
["Finance", "amber"], ["Finance", "amber", "Finance"],
["Personal", "green"], ["Personal", "green", "Personal"],
]; ];
const agentItems = [ const agentItems = [
[Tags, "Tags"], [Sparkles, "Ask Agent", "ask"],
[ListFilter, "Filters"], [ListChecks, "Action Items", "actions"],
[Sparkles, "Summary"], [Sparkles, "Summaries", "summaries"],
[Plug, "Integration"], [Plug, "Integrations", "integrations"],
[Settings, "Settings"],
]; ];
function clearSession() { function clearSession() {
@ -57,28 +54,20 @@ function clearSession() {
sessionStorage.removeItem("agmail_account"); sessionStorage.removeItem("agmail_account");
} }
function NavGroup({ icon: Icon, title, items, active, counts, onSelect }) { function SidebarSection({ title, children, className = "" }) {
return ( return (
<section className="dashboard-nav-group"> <section className={`dashboard-sidebar-section ${className}`}>
<h2><Icon size={13} />{title}</h2> <h2>{title}</h2>
<nav aria-label={title}> {children}
{items.map(([ItemIcon, label, countKey]) => (
<button
className={active === label ? "active" : ""}
key={label}
onClick={() => onSelect(label)}
type="button"
>
<ItemIcon size={16} />
<span>{label}</span>
{countKey && counts?.[countKey] > 0 && <em>{counts[countKey]}</em>}
</button>
))}
</nav>
</section> </section>
); );
} }
function NavBadge({ value }) {
if (!value) return null;
return <em>{value}</em>;
}
export default function DashboardPage() { export default function DashboardPage() {
const [account, setAccount] = useState(null); const [account, setAccount] = useState(null);
const [companyDomain, setCompanyDomain] = useState(null); const [companyDomain, setCompanyDomain] = useState(null);
@ -90,9 +79,15 @@ export default function DashboardPage() {
const [notice, setNotice] = useState(""); const [notice, setNotice] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [mailRefreshKey, setMailRefreshKey] = useState(0); const [mailRefreshKey, setMailRefreshKey] = useState(0);
const [labelsOpen, setLabelsOpen] = useState(true);
const [lens, setLens] = useState("all"); const [lens, setLens] = useState("all");
const [mailStats, setMailStats] = useState({ unread: 0, starred: 0, important: 0 }); const [agentNav, setAgentNav] = useState(null);
const [mailStats, setMailStats] = useState({
unread: 0,
starred: 0,
important: 0,
drafts: 0,
labels: {},
});
const handleStats = useCallback((stats) => setMailStats(stats), []); const handleStats = useCallback((stats) => setMailStats(stats), []);
useEffect(() => { useEffect(() => {
@ -128,16 +123,14 @@ export default function DashboardPage() {
window.location.assign("/login"); window.location.assign("/login");
} }
function selectItem(item) { function selectMailItem(item) {
setAgentNav(null);
if (item === "Starred") { if (item === "Starred") {
setActive("Inbox"); setActive("Inbox");
setLens("starred"); setLens("starred");
} else if (item === "Sent") { } else if (item === "Sent") {
setActive("Outbox"); setActive("Outbox");
setLens("all"); setLens("all");
} else if (labels.some(([name]) => name === item)) {
setActive("Inbox");
setLens(item);
} else { } else {
setActive(item); setActive(item);
setLens("all"); setLens("all");
@ -145,6 +138,20 @@ export default function DashboardPage() {
setMenuOpen(false); 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() { function emailSent() {
setComposeOpen(false); setComposeOpen(false);
setActive("Outbox"); setActive("Outbox");
@ -191,6 +198,13 @@ export default function DashboardPage() {
const workspaceTitle = account.account_type === "company" && companyDomain const workspaceTitle = account.account_type === "company" && companyDomain
? companyDomain.domain_name ? companyDomain.domain_name
: "Personal workspace"; : "Personal workspace";
const mailActive = lens === "starred"
? "Starred"
: active === "Outbox"
? "Sent"
: labels.some(([name]) => lens === name)
? "Inbox"
: active;
return ( return (
<div className="dashboard"> <div className="dashboard">
@ -207,34 +221,63 @@ export default function DashboardPage() {
<Brand /> <Brand />
<button aria-label="Close navigation" type="button" onClick={() => setMenuOpen(false)}><X size={19} /></button> <button aria-label="Close navigation" type="button" onClick={() => setMenuOpen(false)}><X size={19} /></button>
</div> </div>
<button className="dashboard-compose" type="button" onClick={() => setComposeOpen(true)}> <button className="dashboard-compose" type="button" onClick={() => setComposeOpen(true)}>
<Mail size={17} />Compose <Mail size={17} />
<span>Compose</span>
<ChevronDown size={14} className="dashboard-compose-caret" />
</button> </button>
<NavGroup icon={Mail} title="Mail" items={mailItems} active={lens === "starred" ? "Starred" : active === "Outbox" ? "Sent" : active} counts={mailStats} onSelect={selectItem} />
<section className="dashboard-labels"> <SidebarSection title="Mail" className="dashboard-sidebar-mail">
<button type="button" className="dashboard-labels-toggle" onClick={() => setLabelsOpen((open) => !open)} aria-expanded={labelsOpen}> <nav aria-label="Mail">
<Tags size={16} /> {mailItems.map(([ItemIcon, label, countKey]) => (
<span>Labels</span>
<ChevronDown size={14} className={labelsOpen ? "open" : ""} />
</button>
{labelsOpen && (
<nav aria-label="Labels">
{labels.map(([name, tone]) => (
<button <button
className={lens === name ? "active" : ""} className={`dashboard-nav-item${mailActive === label ? " active" : ""}`}
key={name} key={label}
type="button" type="button"
onClick={() => selectItem(name)} onClick={() => selectMailItem(label)}
> >
<i className={`label-dot label-dot-${tone}`} /> <ItemIcon size={15} />
<span>{name}</span> <span>{label}</span>
{name === "Important" && mailStats.important > 0 && <em>{mailStats.important}</em>} <NavBadge value={countKey ? mailStats[countKey] : 0} />
</button> </button>
))} ))}
</nav> </nav>
)} </SidebarSection>
</section>
<NavGroup icon={Bot} title="Agent" items={agentItems} active={active} onSelect={selectItem} /> <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 value={mailStats.labels?.[countKey]} />
</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={15} />
<span>{label}</span>
</button>
))}
</nav>
</SidebarSection>
<div className="dashboard-profile"> <div className="dashboard-profile">
<span>{account.first_name[0]}{account.last_name[0]}</span> <span>{account.first_name[0]}{account.last_name[0]}</span>
<div><strong>{account.first_name} {account.last_name}</strong><small>{account.email}</small></div> <div><strong>{account.first_name} {account.last_name}</strong><small>{account.email}</small></div>
@ -256,8 +299,9 @@ export default function DashboardPage() {
refreshKey={mailRefreshKey} refreshKey={mailRefreshKey}
account={account} account={account}
lens={lens} lens={lens}
agentNav={agentNav}
onLogout={logout} onLogout={logout}
onOpenSettings={() => selectItem("Settings")} onOpenSettings={() => selectMailItem("Settings")}
onStats={handleStats} onStats={handleStats}
/> />
)} )}
@ -268,7 +312,7 @@ export default function DashboardPage() {
refreshKey={mailRefreshKey} refreshKey={mailRefreshKey}
account={account} account={account}
onLogout={logout} onLogout={logout}
onOpenSettings={() => selectItem("Settings")} onOpenSettings={() => selectMailItem("Settings")}
/> />
)} )}
{active === "Trash" && ( {active === "Trash" && (
@ -278,7 +322,7 @@ export default function DashboardPage() {
refreshKey={mailRefreshKey} refreshKey={mailRefreshKey}
account={account} account={account}
onLogout={logout} onLogout={logout}
onOpenSettings={() => selectItem("Settings")} onOpenSettings={() => selectMailItem("Settings")}
/> />
)} )}
{!["Inbox", "Outbox", "Trash"].includes(active) && ( {!["Inbox", "Outbox", "Trash"].includes(active) && (

View file

@ -29,6 +29,7 @@ import {
categoryCounts, categoryCounts,
emailCategory, emailCategory,
inboxIntelligence, inboxIntelligence,
labelCounts,
matchesLens, matchesLens,
} from "../utils/inboxSignals"; } from "../utils/inboxSignals";
@ -49,6 +50,7 @@ export default function MailboxView({
refreshKey = 0, refreshKey = 0,
account, account,
lens = "all", lens = "all",
agentNav = null,
onLogout, onLogout,
onOpenSettings, onOpenSettings,
onStats, onStats,
@ -135,6 +137,8 @@ export default function MailboxView({
unread: emails.filter((email) => email.is_read === false).length, unread: emails.filter((email) => email.is_read === false).length,
starred: emails.filter((email) => email.is_starred).length, starred: emails.filter((email) => email.is_starred).length,
important: intel.important, important: intel.important,
drafts: 0,
labels: labelCounts(emails),
}); });
}, [emails, onStats]); }, [emails, onStats]);
@ -263,6 +267,21 @@ export default function MailboxView({
} }
} }
useEffect(() => {
if (!agentNav || status.loading) return;
if (agentNav === "integrations") {
setAgentFocus("Connect CRM, calendar, and billing tools from Integrations.");
return;
}
if (agentNav === "ask" || agentNav === "summaries") {
runQuickAction("summarize");
return;
}
if (agentNav === "actions") {
runQuickAction("actions");
}
}, [agentNav, status.loading, listedEmails.length, category]);
async function toggleRead(isRead) { async function toggleRead(isRead) {
if (!detail) return; if (!detail) return;
setDetailStatus((current) => ({ ...current, busy: true, error: "" })); setDetailStatus((current) => ({ ...current, busy: true, error: "" }));

View file

@ -450,36 +450,41 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.dns-value-cell { display:flex; align-items:flex-start; gap:8px; } .dns-value-cell { display:flex; align-items:flex-start; gap:8px; }
.dns-copy { width:28px; height:28px; flex:0 0 auto; display:grid; place-items:center; border:1px solid #ece7e3; border-radius:8px; background:white; color:#67677a; } .dns-copy { width:28px; height:28px; flex:0 0 auto; display:grid; place-items:center; border:1px solid #ece7e3; border-radius:8px; background:white; color:#67677a; }
.link-button { border:0; background:transparent; color:var(--coral); font:inherit; font-weight:600; cursor:pointer; padding:0; } .link-button { border:0; background:transparent; color:var(--coral); font:inherit; font-weight:600; cursor:pointer; padding:0; }
.dashboard { grid-template-columns:248px minmax(0,1fr); background:#f4f3f1; } .dashboard { grid-template-columns:236px minmax(0,1fr); background:#f4f3f1; }
.dashboard-sidebar { padding:22px 14px 16px; overflow:auto; border-right:0; color:#d7d3e2; background:linear-gradient(180deg,#1c1730 0%,#161226 55%,#221833 100%); box-shadow:none; } .dashboard-sidebar { padding:18px 12px 14px; overflow:auto; border-right:0; color:#d7d3e2; background:linear-gradient(180deg,#1b162d 0%,#141024 52%,#1d1528 100%); box-shadow:inset -1px 0 0 #ffffff08; }
.dashboard-brand-row { padding:0 6px 2px; }
.dashboard-brand-row>button { color:#c8c3d4; } .dashboard-brand-row>button { color:#c8c3d4; }
.dashboard-sidebar .brand { color:#fff; font-size:18px; } .dashboard-sidebar .brand { color:#fff; font-size:17px; }
.dashboard-sidebar .brand-mark { background:#fff; } .dashboard-sidebar .brand-mark { background:#fff; }
.dashboard-sidebar .robot-eyes i { background:#1c1730; } .dashboard-sidebar .robot-eyes i { background:#1c1730; }
.dashboard-sidebar .robot-smile { border-color:#1c1730; } .dashboard-sidebar .robot-smile { border-color:#1c1730; }
.dashboard-compose { margin:22px 4px 18px; color:#fff; background:linear-gradient(180deg,#ff8d72,#ff6a52); box-shadow:0 10px 22px #ff6a5240; border-radius:12px; } .dashboard-compose { min-height:42px; margin:16px 4px 0; padding:0 14px; display:flex; align-items:center; justify-content:center; gap:8px; color:#fff; background:linear-gradient(180deg,#ff9478 0%,#ff6a52 48%,#f2553f 100%); border:1px solid #ff8a7240; border-radius:12px; box-shadow:0 10px 24px #ff6a5247, inset 0 1px 0 #ffffff30; font-weight:700; }
.dashboard-compose:hover { background:linear-gradient(180deg,#ff9a82,#ff735c); } .dashboard-compose span { flex:1; text-align:left; }
.dashboard-nav-group h2 { color:#8d879c; } .dashboard-compose-caret { margin-left:auto; opacity:.85; }
.dashboard-nav-group button { min-height:36px; color:#c8c3d4; border-radius:10px; font-size:13px; } .dashboard-compose:hover { background:linear-gradient(180deg,#ffa287 0%,#ff7659 48%,#f35f48 100%); box-shadow:0 12px 28px #ff6a5252, inset 0 1px 0 #ffffff38; }
.dashboard-nav-group button em,.dashboard-labels button em { margin-left:auto; min-width:18px; height:18px; padding:0 6px; display:grid; place-items:center; border-radius:99px; background:#ff6a52; color:#fff; font-size:10px; font-style:normal; font-weight:700; } .dashboard-sidebar-section { margin-top:18px; padding-top:16px; border-top:1px solid #ffffff0a; }
.dashboard-nav-group button:hover { background:#ffffff12; color:#fff; } .dashboard-sidebar-section h2 { margin:0 0 6px; padding:0 8px; color:#6f6a7d; font-size:9px; font-weight:700; letter-spacing:1.4px; text-transform:uppercase; }
.dashboard-nav-group button.active { background:#ffffff14; color:#fff; font-weight:600; box-shadow:none; } .dashboard-sidebar-section nav { display:grid; gap:1px; }
.dashboard-labels { margin:2px 0 18px; } .dashboard-nav-item { min-height:32px; padding:0 8px; display:flex; align-items:center; gap:10px; border:0; border-radius:9px; background:transparent; color:#b9b4c6; font-size:12.5px; font-weight:500; text-align:left; transition:background .14s ease,color .14s ease,box-shadow .14s ease; }
.dashboard-labels-toggle,.dashboard-labels button { width:100%; min-height:36px; padding:0 11px; display:flex; align-items:center; gap:11px; border:0; border-radius:10px; background:transparent; color:#c8c3d4; font-size:13px; text-align:left; } .dashboard-nav-item svg { flex-shrink:0; opacity:.92; }
.dashboard-labels-toggle svg:last-child { margin-left:auto; transition:transform .15s ease; } .dashboard-nav-item span { flex:1; min-width:0; }
.dashboard-labels-toggle svg.open { transform:rotate(180deg); } .dashboard-nav-item em { margin-left:auto; min-width:17px; height:17px; padding:0 5px; display:grid; place-items:center; border-radius:99px; background:#ff6a52; color:#fff; font-size:9px; font-style:normal; font-weight:700; line-height:1; box-shadow:0 1px 4px #ff6a5238; }
.dashboard-labels nav { display:grid; gap:2px; margin-top:2px; } .dashboard-nav-item:hover { background:#ffffff10; color:#f4f1fa; }
.dashboard-labels button:hover,.dashboard-labels button.active { background:#ffffff12; color:#fff; } .dashboard-nav-item.active { color:#fff; font-weight:600; background:linear-gradient(90deg,#ffffff1f 0%,#ffffff10 100%); box-shadow:inset 3px 0 0 #ff7a62, 0 1px 0 #ffffff10; }
.label-dot { width:8px; height:8px; border-radius:50%; } .dashboard-sidebar-mail .dashboard-nav-item.active { background:linear-gradient(90deg,#ff7a6228 0%,#ffffff12 100%); box-shadow:inset 3px 0 0 #ff8a72, 0 4px 14px #00000018; }
.label-dot-rose { background:#ff8b78; } .dashboard-label-item { padding-left:10px; }
.label-dot-violet { background:#a78bfa; } .dashboard-label-item .label-dot { width:10px; height:10px; flex-shrink:0; border-radius:50%; box-shadow:0 0 0 2px #ffffff10; }
.label-dot-blue { background:#7eb6ff; } .label-dot-rose { background:#ff8f7d; }
.label-dot-amber { background:#f0c14a; } .label-dot-violet { background:#b79cff; }
.label-dot-green { background:#7dcea0; } .label-dot-blue { background:#8ec0ff; }
.dashboard-profile { border-top-color:#ffffff14; } .label-dot-amber { background:#f5c85a; }
.dashboard-profile>span { background:#ff8b78; color:#2a2038; } .label-dot-green { background:#8ed9ad; }
.dashboard-agent-item { color:#c3bed0; }
.dashboard-agent-item.active { background:linear-gradient(90deg,#8b7cff22 0%,#ffffff10 100%); box-shadow:inset 3px 0 0 #9f8cff; }
.dashboard-profile { margin-top:14px; padding-top:12px; border-top:1px solid #ffffff10; }
.dashboard-profile>span { background:linear-gradient(180deg,#ff9a82,#ff6f57); color:#2a2038; }
.dashboard-profile strong { color:#fff; } .dashboard-profile strong { color:#fff; }
.dashboard-profile small,.dashboard-profile button { color:#a39eb2; } .dashboard-profile small,.dashboard-profile button { color:#9a95a8; }
.dashboard-profile button:hover { background:#ffffff12; color:#fff; } .dashboard-profile button:hover { background:#ffffff12; color:#fff; }
.dashboard-content { padding:14px 16px 16px; background:#f7f6f4; } .dashboard-content { padding:14px 16px 16px; background:#f7f6f4; }
.dashboard-content-heading h1 { font-size:clamp(28px,3vw,36px); letter-spacing:-1.4px; } .dashboard-content-heading h1 { font-size:clamp(28px,3vw,36px); letter-spacing:-1.4px; }

View file

@ -87,3 +87,19 @@ export function attachmentCount(email) {
if (Array.isArray(email.attachments)) return email.attachments.length; if (Array.isArray(email.attachments)) return email.attachments.length;
return Number(email.attachment_count || 0); return Number(email.attachment_count || 0);
} }
export function labelCounts(emails) {
const counts = {
Important: 0,
Projects: 0,
Clients: 0,
Finance: 0,
Personal: 0,
};
emails.forEach((email) => {
emailLabels(email).forEach((label) => {
counts[label.name] = (counts[label.name] || 0) + 1;
});
});
return counts;
}