diff --git a/frontend/src/components/DashboardPage.jsx b/frontend/src/components/DashboardPage.jsx index 33e78e1..fff4931 100644 --- a/frontend/src/components/DashboardPage.jsx +++ b/frontend/src/components/DashboardPage.jsx @@ -1,10 +1,9 @@ import { - Bot, ChevronDown, Clock3, FileEdit, Inbox, - ListFilter, + ListChecks, LogOut, Mail, Menu, @@ -14,7 +13,6 @@ import { ShieldAlert, Sparkles, Star, - Tags, Trash2, X, } from "lucide-react"; @@ -30,26 +28,25 @@ const mailItems = [ [Star, "Starred", "starred"], [Clock3, "Snoozed"], [Send, "Sent"], - [FileEdit, "Drafts"], + [FileEdit, "Drafts", "drafts"], [ShieldAlert, "Spam"], [Trash2, "Trash"], [Clock3, "Scheduled"], ]; const labels = [ - ["Important", "rose"], - ["Projects", "violet"], - ["Clients", "blue"], - ["Finance", "amber"], - ["Personal", "green"], + ["Important", "rose", "Important"], + ["Projects", "violet", "Projects"], + ["Clients", "blue", "Clients"], + ["Finance", "amber", "Finance"], + ["Personal", "green", "Personal"], ]; const agentItems = [ - [Tags, "Tags"], - [ListFilter, "Filters"], - [Sparkles, "Summary"], - [Plug, "Integration"], - [Settings, "Settings"], + [Sparkles, "Ask Agent", "ask"], + [ListChecks, "Action Items", "actions"], + [Sparkles, "Summaries", "summaries"], + [Plug, "Integrations", "integrations"], ]; function clearSession() { @@ -57,28 +54,20 @@ function clearSession() { sessionStorage.removeItem("agmail_account"); } -function NavGroup({ icon: Icon, title, items, active, counts, onSelect }) { +function SidebarSection({ title, children, className = "" }) { return ( -
-

{title}

- +
+

{title}

+ {children}
); } +function NavBadge({ value }) { + if (!value) return null; + return {value}; +} + export default function DashboardPage() { const [account, setAccount] = useState(null); const [companyDomain, setCompanyDomain] = useState(null); @@ -90,9 +79,15 @@ export default function DashboardPage() { const [notice, setNotice] = useState(""); const [loading, setLoading] = useState(true); const [mailRefreshKey, setMailRefreshKey] = useState(0); - const [labelsOpen, setLabelsOpen] = useState(true); 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), []); useEffect(() => { @@ -128,16 +123,14 @@ export default function DashboardPage() { window.location.assign("/login"); } - function selectItem(item) { + function selectMailItem(item) { + setAgentNav(null); if (item === "Starred") { setActive("Inbox"); setLens("starred"); } else if (item === "Sent") { setActive("Outbox"); setLens("all"); - } else if (labels.some(([name]) => name === item)) { - setActive("Inbox"); - setLens(item); } else { setActive(item); setLens("all"); @@ -145,6 +138,20 @@ export default function DashboardPage() { 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"); @@ -191,6 +198,13 @@ export default function DashboardPage() { 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 (
@@ -207,34 +221,63 @@ export default function DashboardPage() {
+ - -
- - {labelsOpen && ( - - )} -
- + + + + + + + + + + + + +
{account.first_name[0]}{account.last_name[0]}
{account.first_name} {account.last_name}{account.email}
@@ -256,8 +299,9 @@ export default function DashboardPage() { refreshKey={mailRefreshKey} account={account} lens={lens} + agentNav={agentNav} onLogout={logout} - onOpenSettings={() => selectItem("Settings")} + onOpenSettings={() => selectMailItem("Settings")} onStats={handleStats} /> )} @@ -268,7 +312,7 @@ export default function DashboardPage() { refreshKey={mailRefreshKey} account={account} onLogout={logout} - onOpenSettings={() => selectItem("Settings")} + onOpenSettings={() => selectMailItem("Settings")} /> )} {active === "Trash" && ( @@ -278,7 +322,7 @@ export default function DashboardPage() { refreshKey={mailRefreshKey} account={account} onLogout={logout} - onOpenSettings={() => selectItem("Settings")} + onOpenSettings={() => selectMailItem("Settings")} /> )} {!["Inbox", "Outbox", "Trash"].includes(active) && ( diff --git a/frontend/src/components/MailboxView.jsx b/frontend/src/components/MailboxView.jsx index 3f4eaed..e23700a 100644 --- a/frontend/src/components/MailboxView.jsx +++ b/frontend/src/components/MailboxView.jsx @@ -29,6 +29,7 @@ import { categoryCounts, emailCategory, inboxIntelligence, + labelCounts, matchesLens, } from "../utils/inboxSignals"; @@ -49,6 +50,7 @@ export default function MailboxView({ refreshKey = 0, account, lens = "all", + agentNav = null, onLogout, onOpenSettings, onStats, @@ -135,6 +137,8 @@ export default function MailboxView({ unread: emails.filter((email) => email.is_read === false).length, starred: emails.filter((email) => email.is_starred).length, important: intel.important, + drafts: 0, + labels: labelCounts(emails), }); }, [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) { if (!detail) return; setDetailStatus((current) => ({ ...current, busy: true, error: "" })); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 22d291c..d770ba5 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -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-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; } -.dashboard { grid-template-columns:248px 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 { grid-template-columns:236px minmax(0,1fr); background:#f4f3f1; } +.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-sidebar .brand { color:#fff; font-size:18px; } +.dashboard-sidebar .brand { color:#fff; font-size:17px; } .dashboard-sidebar .brand-mark { background:#fff; } .dashboard-sidebar .robot-eyes i { background:#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:hover { background:linear-gradient(180deg,#ff9a82,#ff735c); } -.dashboard-nav-group h2 { color:#8d879c; } -.dashboard-nav-group button { min-height:36px; color:#c8c3d4; border-radius:10px; font-size:13px; } -.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-nav-group button:hover { background:#ffffff12; color:#fff; } -.dashboard-nav-group button.active { background:#ffffff14; color:#fff; font-weight:600; box-shadow:none; } -.dashboard-labels { margin:2px 0 18px; } -.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-labels-toggle svg:last-child { margin-left:auto; transition:transform .15s ease; } -.dashboard-labels-toggle svg.open { transform:rotate(180deg); } -.dashboard-labels nav { display:grid; gap:2px; margin-top:2px; } -.dashboard-labels button:hover,.dashboard-labels button.active { background:#ffffff12; color:#fff; } -.label-dot { width:8px; height:8px; border-radius:50%; } -.label-dot-rose { background:#ff8b78; } -.label-dot-violet { background:#a78bfa; } -.label-dot-blue { background:#7eb6ff; } -.label-dot-amber { background:#f0c14a; } -.label-dot-green { background:#7dcea0; } -.dashboard-profile { border-top-color:#ffffff14; } -.dashboard-profile>span { background:#ff8b78; color:#2a2038; } +.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 span { flex:1; text-align:left; } +.dashboard-compose-caret { margin-left:auto; opacity:.85; } +.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-sidebar-section { margin-top:18px; padding-top:16px; border-top:1px solid #ffffff0a; } +.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-sidebar-section nav { display:grid; gap:1px; } +.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-nav-item svg { flex-shrink:0; opacity:.92; } +.dashboard-nav-item span { flex:1; min-width:0; } +.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-nav-item:hover { background:#ffffff10; color:#f4f1fa; } +.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; } +.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; } +.dashboard-label-item { padding-left:10px; } +.dashboard-label-item .label-dot { width:10px; height:10px; flex-shrink:0; border-radius:50%; box-shadow:0 0 0 2px #ffffff10; } +.label-dot-rose { background:#ff8f7d; } +.label-dot-violet { background:#b79cff; } +.label-dot-blue { background:#8ec0ff; } +.label-dot-amber { background:#f5c85a; } +.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 small,.dashboard-profile button { color:#a39eb2; } +.dashboard-profile small,.dashboard-profile button { color:#9a95a8; } .dashboard-profile button:hover { background:#ffffff12; color:#fff; } .dashboard-content { padding:14px 16px 16px; background:#f7f6f4; } .dashboard-content-heading h1 { font-size:clamp(28px,3vw,36px); letter-spacing:-1.4px; } diff --git a/frontend/src/utils/inboxSignals.js b/frontend/src/utils/inboxSignals.js index 7265aea..fd466f9 100644 --- a/frontend/src/utils/inboxSignals.js +++ b/frontend/src/utils/inboxSignals.js @@ -87,3 +87,19 @@ export function attachmentCount(email) { if (Array.isArray(email.attachments)) return email.attachments.length; 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; +}