email box empty styling

This commit is contained in:
debu-2 2026-10-05 21:34:12 +05:30
parent 1ee8a58117
commit 5cb49f4cb1
19 changed files with 547 additions and 67 deletions

View file

@ -5,6 +5,7 @@ import {
ChevronDown,
ChevronRight,
ClipboardList,
CircleHelp,
FileSearch,
FileText,
ListChecks,
@ -12,9 +13,11 @@ import {
Paperclip,
PenLine,
Send,
Settings,
Sparkles,
} from "lucide-react";
import AgentInsight from "./AgentInsight";
import AgentSpark from "./AgentSpark";
import SenderAvatar from "./SenderAvatar";
import { inboxContext } from "../utils/inboxSignals";
@ -57,7 +60,7 @@ const cards = [
function greeting(name) {
const hour = new Date().getHours();
const period = hour < 12 ? "Good morning" : hour < 17 ? "Good afternoon" : "Good evening";
return `${period}, ${name}!`;
return `${period}, ${name}`;
}
function statCaption(card, stats) {
@ -79,6 +82,9 @@ export default function AgAgentPanel({
focus,
insight,
loading = false,
mailboxEmpty = false,
isBrandNewMailbox = false,
onCompose,
onQuickAction,
onOpenEmail,
onAgentAsk,
@ -89,6 +95,9 @@ export default function AgAgentPanel({
const firstName = account?.first_name || "there";
const allMetricsZero = metricTotal(stats) === 0;
const hasQuery = agentQuery.trim().length > 0;
const showOnboarding = !insight && !loading && mailboxEmpty && isBrandNewMailbox;
const showQuietEmpty = !insight && !loading && mailboxEmpty && !isBrandNewMailbox;
const showFullPanel = !insight && !loading && !mailboxEmpty;
function submitAgentQuery(event) {
event.preventDefault();
@ -126,7 +135,13 @@ export default function AgAgentPanel({
{!insight && (
<div className="agent-panel-hello">
<strong>{greeting(firstName)}</strong>
<span>Here&apos;s what needs your attention today.</span>
<span>
{showOnboarding
? "Your new inbox is ready."
: showQuietEmpty
? "Nothing needs your attention right now."
: "Here's what needs your attention today."}
</span>
</div>
)}
@ -137,16 +152,28 @@ export default function AgAgentPanel({
</div>
)}
{!insight && !loading && allMetricsZero && (
{showOnboarding && (
<div className="agent-ready-card">
<p className="agent-ready-lead">
<Check size={14} strokeWidth={2} /> AGMail is ready
</p>
<p>
No messages yet. I&apos;ll start organizing and surfacing important
items as email arrives.
</p>
</div>
)}
{showQuietEmpty && (
<div className="agent-caught-up">
<p className="agent-caught-up-lead">
<Check size={14} strokeWidth={2} /> You&apos;re all caught up.
</p>
<p>No urgent messages or outstanding actions.</p>
<p>Your inbox is clear for now.</p>
</div>
)}
{!insight && !loading && !allMetricsZero && (
{showFullPanel && !allMetricsZero && (
<div className="agent-stat-grid">
{cards.map((card) => {
const Icon = card.icon;
@ -173,42 +200,91 @@ export default function AgAgentPanel({
</div>
)}
{!insight && focus && !allMetricsZero && <p className="agent-panel-note">{focus}</p>}
{showFullPanel && allMetricsZero && (
<div className="agent-caught-up">
<p className="agent-caught-up-lead">
<Check size={14} strokeWidth={2} /> You&apos;re all caught up.
</p>
<p>No urgent messages or outstanding actions.</p>
</div>
)}
{!insight && focus && showFullPanel && !allMetricsZero && (
<p className="agent-panel-note">{focus}</p>
)}
<section className="agent-quick">
<h3>Quick actions</h3>
<h3>{showOnboarding ? "Quick start" : "Quick actions"}</h3>
<div className="agent-quick-grid">
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("summarize")}>
<Sparkles {...ICON_SM} className="agent-quick-icon agent-quick-icon-purple" />
<span>Summarize inbox</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("draft")}>
<PenLine {...ICON_SM} className="agent-quick-icon agent-quick-icon-purple" />
<span>Draft replies</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("attachments")}>
<FileSearch {...ICON_SM} className="agent-quick-icon agent-quick-icon-muted" />
<span>Find attachments</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("actions")}>
<ListChecks {...ICON_SM} className="agent-quick-icon agent-quick-icon-slate" />
<span>Show action items</span>
</button>
{showOnboarding ? (
<>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("learn")}>
<AgentSpark className="agent-quick-icon agent-quick-icon-purple" />
<span>Meet AG Agent</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onCompose?.()}>
<PenLine {...ICON_SM} className="agent-quick-icon agent-quick-icon-purple" />
<span>Compose email</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("preferences")}>
<Settings {...ICON_SM} className="agent-quick-icon agent-quick-icon-muted" />
<span>Set preferences</span>
</button>
<button type="button" className="agent-quick-btn" onClick={() => onQuickAction("learn")}>
<CircleHelp {...ICON_SM} className="agent-quick-icon agent-quick-icon-slate" />
<span>Learn AGMail</span>
</button>
</>
) : (
<>
<button
type="button"
className="agent-quick-btn"
onClick={() => onQuickAction("summarize")}
disabled={mailboxEmpty}
>
<Sparkles {...ICON_SM} className="agent-quick-icon agent-quick-icon-purple" />
<span>Summarize inbox</span>
</button>
<button
type="button"
className="agent-quick-btn"
onClick={() => onQuickAction("draft")}
disabled={mailboxEmpty}
>
<PenLine {...ICON_SM} className="agent-quick-icon agent-quick-icon-purple" />
<span>Draft replies</span>
</button>
<button
type="button"
className="agent-quick-btn"
onClick={() => onQuickAction("attachments")}
disabled={mailboxEmpty}
>
<FileSearch {...ICON_SM} className="agent-quick-icon agent-quick-icon-muted" />
<span>Find attachments</span>
</button>
<button
type="button"
className="agent-quick-btn"
onClick={() => onQuickAction("actions")}
disabled={mailboxEmpty}
>
<ListChecks {...ICON_SM} className="agent-quick-icon agent-quick-icon-slate" />
<span>Show action items</span>
</button>
</>
)}
</div>
</section>
{!insight && (
{showFullPanel && !insight && (
<section className="agent-context-block agent-agenda">
<div className="agent-context-head">
<h3>Today&apos;s agenda</h3>
<button type="button" className="agent-context-link">View calendar</button>
</div>
{loading ? (
<div className="agent-loading-rows" aria-hidden="true">
<span className="agent-loading-pulse" />
<span className="agent-loading-pulse short" />
</div>
) : today.length > 0 ? (
{today.length > 0 ? (
<ul className="agent-agenda-list">
{today.map((item) => (
<li key={`${item.time}-${item.label}`} className="agent-agenda-row">
@ -228,14 +304,10 @@ export default function AgAgentPanel({
</section>
)}
{!insight && (
{showFullPanel && !insight && (
<section className="agent-context-block agent-attention-block">
<h3>Needs attention</h3>
{loading ? (
<div className="agent-loading-rows" aria-hidden="true">
<span className="agent-loading-pulse" />
</div>
) : needsReply.length > 0 ? (
{needsReply.length > 0 ? (
<ul className="agent-attention-list">
{needsReply.slice(0, 3).map((item) => (
<li key={item.id}>
@ -259,7 +331,7 @@ export default function AgAgentPanel({
</section>
)}
{!insight && deadlines.length > 0 && (
{showFullPanel && !insight && deadlines.length > 0 && (
<section className="agent-context-block agent-deadlines">
<h3>Deadlines</h3>
<ul>

View file

@ -417,6 +417,7 @@ export default function DashboardPage() {
agentNav={agentNav}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
onCompose={() => setComposeOpen(true)}
onStats={handleStats}
/>
)}
@ -428,6 +429,7 @@ export default function DashboardPage() {
account={account}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
onCompose={() => setComposeOpen(true)}
/>
)}
{!companyActive && active === "Trash" && (
@ -438,6 +440,7 @@ export default function DashboardPage() {
account={account}
onLogout={logout}
onOpenSettings={() => selectMailItem("Settings")}
onCompose={() => setComposeOpen(true)}
/>
)}
{companyActive === "Overview" && isDomainOwner && !isOrgSetupComplete(account.id) && (

View file

@ -0,0 +1,18 @@
import { Mail } from "lucide-react";
import AgentSpark from "./AgentSpark";
export default function MailboxEmptyIllustration() {
return (
<div className="mailbox-empty-illustration" aria-hidden="true">
<span className="mailbox-empty-spark mailbox-empty-spark-top">
<AgentSpark />
</span>
<span className="mailbox-empty-icon-wrap">
<Mail size={22} strokeWidth={1.75} />
</span>
<span className="mailbox-empty-spark mailbox-empty-spark-bottom">
<AgentSpark />
</span>
</div>
);
}

View file

@ -0,0 +1,63 @@
import { Check } from "lucide-react";
import MailboxEmptyIllustration from "./MailboxEmptyIllustration";
import { getMailboxEmptyCopy } from "../utils/mailboxEmptyCopy";
export default function MailboxEmptyState({
mailbox,
title,
category,
isBrandNew,
hasFilters,
hasSearch,
lens,
onCompose,
onRefresh,
}) {
const copy = getMailboxEmptyCopy({
mailbox,
title,
category,
isBrandNew,
hasFilters,
hasSearch,
lens,
});
function handlePrimary() {
if (copy.primaryCta?.action === "compose") onCompose?.();
}
function handleSecondary() {
if (copy.secondaryCta?.action === "refresh") onRefresh?.();
}
return (
<div className="mailbox-empty-body">
<div className="mailbox-empty-content">
<MailboxEmptyIllustration />
<h3>{copy.heading}</h3>
<p>{copy.description}</p>
{(copy.primaryCta || copy.secondaryCta) && (
<div className="mailbox-empty-actions">
{copy.primaryCta && (
<button type="button" className="mailbox-empty-cta" onClick={handlePrimary}>
{copy.primaryCta.label}
</button>
)}
{copy.secondaryCta && (
<button type="button" className="mailbox-empty-link" onClick={handleSecondary}>
{copy.secondaryCta.label}
</button>
)}
</div>
)}
{copy.showActiveBadge && (
<p className="mailbox-empty-badge">
<Check size={14} strokeWidth={2} />
Email address is active
</p>
)}
</div>
</div>
);
}

View file

@ -5,7 +5,6 @@ import {
ChevronRight,
CircleHelp,
Filter,
Inbox,
LayoutGrid,
List,
Mail,
@ -31,8 +30,10 @@ import AgAgentPanel from "./AgAgentPanel";
import AgentSpark from "./AgentSpark";
import CommandPalette from "./CommandPalette";
import { useAgentInsight } from "../hooks/useAgentInsight";
import { useMailboxMaturity } from "../hooks/useMailboxMaturity";
import MailDetail from "./MailDetail";
import MailRow from "./MailRow";
import MailboxEmptyState from "./MailboxEmptyState";
import MailboxSelectionBar from "./MailboxSelectionBar";
import StickyNotesPanel from "./StickyNotesPanel";
import {
@ -70,6 +71,7 @@ export default function MailboxView({
agentNav = null,
onLogout,
onOpenSettings,
onCompose,
onStats,
}) {
const token = sessionStorage.getItem("agmail_access_token");
@ -165,6 +167,11 @@ export default function MailboxView({
[checked],
);
const selectedCount = selectedIds.length;
const hasFilters = filters.read !== "all" || Boolean(filters.sender.trim())
|| Boolean(filters.dateFrom) || Boolean(filters.dateTo);
const hasSearch = Boolean(filters.query.trim());
const { isBrandNew } = useMailboxMaturity(account, emails, status.loading);
const isEmpty = !status.loading && !status.error && listedEmails.length === 0;
useEffect(() => {
if (!onStats) return;
@ -495,8 +502,6 @@ export default function MailboxView({
setDetail(null);
}
const hasFilters =
filters.read !== "all" || filters.sender || filters.dateFrom || filters.dateTo;
const initials = `${account?.first_name?.[0] || ""}${account?.last_name?.[0] || ""}`.toUpperCase();
const rangeStart = listedEmails.length ? cursorHistory.length * PAGE_SIZE + 1 : 0;
const rangeEnd = cursorHistory.length * PAGE_SIZE + listedEmails.length;
@ -623,19 +628,25 @@ export default function MailboxView({
</div>
)}
<div className="mailbox-rows" aria-busy={status.loading}>
{status.loading && <div className="mailbox-state">Loading messages…</div>}
<div className={`mailbox-rows${isEmpty ? " is-empty" : ""}`} aria-busy={status.loading}>
{status.loading && <div className="mailbox-state loading">Loading messages…</div>}
{status.error && (
<div className="mailbox-state error" role="alert">
<p>{status.error}</p><button type="button" onClick={load}>Try again</button>
</div>
)}
{!status.loading && !status.error && listedEmails.length === 0 && (
<div className="mailbox-state empty">
<span><Inbox size={22} /></span>
<strong>No messages here</strong>
<p>{hasFilters || filters.query || lens !== "all" ? "Try another category, label, or search." : `Your ${title.toLowerCase()} is empty.`}</p>
</div>
{isEmpty && (
<MailboxEmptyState
mailbox={mailbox}
title={title}
category={mailbox === "inbox" ? category : null}
isBrandNew={isBrandNew}
hasFilters={hasFilters}
hasSearch={hasSearch}
lens={lens}
onCompose={onCompose}
onRefresh={load}
/>
)}
{!status.loading && !status.error && listedEmails.map((email) => (
<MailRow
@ -655,19 +666,24 @@ export default function MailboxView({
))}
</div>
{!status.loading && listedEmails.length > 0 && (
{!status.loading && (
<footer className="mailbox-pagination">
<span className="mailbox-pagination-range">
{`${rangeStart}–${rangeEnd}`}
{nextCursor ? "" : ` · ${rangeEnd} loaded`}
{listedEmails.length === 0
? "0 messages"
: `${rangeStart}–${rangeEnd}${nextCursor ? "" : ` · ${rangeEnd} loaded`}`}
</span>
<div className="mailbox-pagination-controls">
<button type="button" title="Previous page" aria-label="Previous page" disabled={!cursorHistory.length} onClick={previousPage}>
<ChevronLeft size={16} strokeWidth={1.75} />
</button>
<button type="button" title="Next page" aria-label="Next page" disabled={!nextCursor} onClick={nextPage}>
<ChevronRight size={16} strokeWidth={1.75} />
</button>
{listedEmails.length > 0 && (
<>
<button type="button" title="Previous page" aria-label="Previous page" disabled={!cursorHistory.length} onClick={previousPage}>
<ChevronLeft size={16} strokeWidth={1.75} />
</button>
<button type="button" title="Next page" aria-label="Next page" disabled={!nextCursor} onClick={nextPage}>
<ChevronRight size={16} strokeWidth={1.75} />
</button>
</>
)}
</div>
<div className="mailbox-pagination-density" aria-label="List density">
<button type="button" className="active" title="Comfortable density" aria-label="Comfortable density"><List size={15} strokeWidth={1.75} /></button>
@ -702,6 +718,9 @@ export default function MailboxView({
focus={agentFocus}
insight={insight}
loading={status.loading}
mailboxEmpty={isEmpty}
isBrandNewMailbox={isBrandNew && mailbox === "inbox"}
onCompose={onCompose}
onQuickAction={runQuickAction}
onOpenEmail={(emailId) => {
const email = listedEmails.find((item) => item.id === emailId);