feat: inline unsubscribe button on email rows

- ✉✕ button appears on rows where the email has a List-Unsubscribe header
- One-click POST / HTTP GET executed automatically; shows ✓ on success
- mailto: targets open the user mail client (we never auto-send email)
- POST /email/{id}/unsubscribe: detects sender, then processes — UserId scoped
- EmailSummaryDto gains IsStarred, HasListUnsubscribe, SupportsOneClick fields
- Both SearchService and CleanupService updated to populate new DTO fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-06-30 20:22:28 +02:00
parent 80c2167b89
commit 2c9b402b08
7 changed files with 72 additions and 4 deletions
+24
View File
@@ -32,6 +32,22 @@ export default function EmailRow({ email: initial, onRemove }) {
}
};
const handleUnsub = async (e) => {
e.stopPropagation();
if (acting) return;
setActing(true);
try {
const res = await EmailApi.unsubscribe(email.id);
if (res.method === 'mailto') {
window.location.href = res.target;
} else {
setEmail((prev) => ({ ...prev, _unsubDone: true }));
}
} finally {
setActing(false);
}
};
const handleTrash = async (e) => {
e.stopPropagation();
if (acting) return;
@@ -84,6 +100,14 @@ export default function EmailRow({ email: initial, onRemove }) {
? act(EmailApi.unstar, { isStarred: false })
: act(EmailApi.star, { isStarred: true })}
></button>
{email.hasListUnsubscribe && (
<button
className={`action-btn action-btn--unsub${email._unsubDone ? ' action-btn--done' : ''}`}
title={email._unsubDone ? 'Unsubscribed' : 'Unsubscribe'}
onClick={handleUnsub}
disabled={email._unsubDone}
>{email._unsubDone ? '✓' : '✉✕'}</button>
)}
<button
className="action-btn action-btn--danger"
title="Move to trash"