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:
cesnimda
2026-06-30 21:50:59 +02:00
parent a24c48179a
commit 8c92263cc3
8 changed files with 293 additions and 18 deletions
@@ -27,6 +27,21 @@ public class EmailController : ApiControllerBase
_db = db;
}
/// <summary>Full email detail including body text, for the inline detail view.</summary>
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id, CancellationToken ct)
{
var e = await _db.Emails
.Where(e => e.Id == id && e.UserId == UserId)
.Select(e => new EmailDetailDto(
e.Id, e.GmailMessageId, e.Subject, e.Snippet, e.BodyText,
e.Sender!.Address, e.Sender.DisplayName, e.SentAtUtc,
e.IsUnread, e.IsStarred, e.HasAttachments, e.SizeEstimateBytes,
e.Category, e.HasListUnsubscribe, e.SupportsOneClickUnsubscribe))
.FirstOrDefaultAsync(ct);
return e is null ? NotFound() : Ok(e);
}
[HttpPost("{id:guid}/read")]
public Task<IActionResult> MarkRead(Guid id, CancellationToken ct) => Act(id, CleanupActionType.MarkRead, ct);