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);
@@ -18,6 +18,24 @@ public record EmailSummaryDto(
bool HasListUnsubscribe,
bool SupportsOneClick);
/// <summary>Full single-email view, including body text, for the detail pane.</summary>
public record EmailDetailDto(
Guid Id,
string GmailMessageId,
string? Subject,
string? Snippet,
string? BodyText,
string SenderAddress,
string? SenderDisplayName,
DateTimeOffset SentAtUtc,
bool IsUnread,
bool IsStarred,
bool HasAttachments,
long SizeEstimateBytes,
EmailCategory Category,
bool HasListUnsubscribe,
bool SupportsOneClick);
public record SenderStatDto(
Guid SenderId,
string Address,