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:
@@ -105,6 +105,7 @@ export const EmailApi = {
|
||||
unstar: (id) => api.post(`/email/${id}/unstar`),
|
||||
trash: (id) => api.post(`/email/${id}/trash`),
|
||||
untrash: (id) => api.post(`/email/${id}/untrash`),
|
||||
unsubscribe:(id) => api.post(`/email/${id}/unsubscribe`).then((r) => r.data),
|
||||
};
|
||||
|
||||
export const ExportApi = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -256,6 +256,8 @@ input, select { background: var(--panel-2); border: 1px solid #2c3550; color: va
|
||||
.action-btn--danger:hover { color: var(--danger); }
|
||||
.email-row:hover .action-btn { opacity: 0.6; }
|
||||
.email-row--acting { opacity: 0.6; pointer-events: none; }
|
||||
.action-btn--unsub { font-size: 11px; }
|
||||
.action-btn--done { opacity: 1 !important; color: var(--ok); }
|
||||
|
||||
.fv-sentinel { height: 1px; }
|
||||
.fv-loading-more { color: var(--muted); font-size: 13px; padding: 16px 0; text-align: center; }
|
||||
|
||||
Reference in New Issue
Block a user