feat: sync status indicator, unsubscribe queue UI, email body preview
SyncStatus widget polls /sync/status and shows a live progress bar in
the topbar while syncing, or last-synced time / error otherwise.
Unsubscribe page reworked into a proper queue: status filter tabs
(All/Pending/Succeeded/Failed), select-all, sorted by volume, status
badges, and a result toast after processing.
GET /email/{id} returns full BodyText; Senders detail view fetches
and renders it instead of just the snippet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,21 @@ function SenderList({ senders, selectedId, onSelect, search, onSearch }) {
|
||||
|
||||
// ── EmailDetail (full single-email view) ──────────────────────────────────────
|
||||
|
||||
function EmailDetail({ email, onBack }) {
|
||||
function EmailDetail({ email: summary, onBack }) {
|
||||
const [detail, setDetail] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setDetail(null);
|
||||
setLoading(true);
|
||||
EmailApi.get(summary.id)
|
||||
.then(setDetail)
|
||||
.catch(() => setDetail(null))
|
||||
.finally(() => setLoading(false));
|
||||
}, [summary.id]);
|
||||
|
||||
const email = detail ?? summary;
|
||||
|
||||
return (
|
||||
<div className="sd-detail">
|
||||
<button className="sd-back" onClick={onBack}>← Back to list</button>
|
||||
@@ -83,9 +97,17 @@ function EmailDetail({ email, onBack }) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{email.snippet && (
|
||||
|
||||
{loading && <div className="sd-body-loading muted">Loading message…</div>}
|
||||
|
||||
{!loading && detail?.bodyText && (
|
||||
<div className="sd-body">{detail.bodyText}</div>
|
||||
)}
|
||||
|
||||
{!loading && !detail?.bodyText && email.snippet && (
|
||||
<div className="sd-snippet">{email.snippet}</div>
|
||||
)}
|
||||
|
||||
<div className="sd-detail-actions">
|
||||
<button
|
||||
className="btn-sm"
|
||||
|
||||
Reference in New Issue
Block a user