@@ -2252,21 +2275,7 @@ export function EmailViewer({
)}
- {/* Unsubscribe Controls */}
- {shouldShowUnsubBanner && listHeaders?.listUnsubscribe && (
-
- {
- const messageId = email?.messageId || '';
- const newSet = new Set(dismissedUnsubBanners).add(messageId);
- setDismissedUnsubBanners(newSet);
- localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet]));
- }}
- />
-
- )}
+
{/* Calendar Invitation Banner */}
{hasCalendarInvitation && (
diff --git a/components/email/unsubscribe-banner.tsx b/components/email/unsubscribe-banner.tsx
index 331c47fc..1f63a0e4 100644
--- a/components/email/unsubscribe-banner.tsx
+++ b/components/email/unsubscribe-banner.tsx
@@ -1,9 +1,11 @@
'use client';
-import { useState } from 'react';
+import { useState, useRef, useEffect } from 'react';
import { Loader2, CheckCircle, AlertCircle } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { isValidUnsubscribeUrl } from '@/lib/validation';
+import { ConfirmDialog } from '@/components/ui/confirm-dialog';
+import { useIsDesktop } from '@/hooks/use-media-query';
interface UnsubscribeBannerProps {
listUnsubscribe: {
@@ -25,12 +27,31 @@ export function UnsubscribeBanner({
const [processing, setProcessing] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
+ const popoverRef = useRef
(null);
+ const isDesktop = useIsDesktop();
const unsubMethod = listUnsubscribe.preferred;
const unsubUrl = unsubMethod === 'http'
? listUnsubscribe.http
: listUnsubscribe.mailto;
+ useEffect(() => {
+ if (!showConfirm) return;
+ const handleClickOutside = (e: MouseEvent) => {
+ if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
+ setShowConfirm(false);
+ }
+ };
+ // Use setTimeout to avoid the opening click triggering immediate close
+ const timer = setTimeout(() => {
+ document.addEventListener('mousedown', handleClickOutside);
+ }, 0);
+ return () => {
+ clearTimeout(timer);
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [showConfirm]);
+
if (!unsubUrl || !unsubMethod) {
return null;
}
@@ -39,6 +60,7 @@ export function UnsubscribeBanner({
if (!isValidUnsubscribeUrl(unsubUrl)) {
setError(true);
setProcessing(false);
+ setShowConfirm(false);
return;
}
@@ -49,6 +71,7 @@ export function UnsubscribeBanner({
window.open(unsubUrl, '_blank', 'noopener,noreferrer');
setSuccess(true);
setProcessing(false);
+ setShowConfirm(false);
setTimeout(onDismiss, 3000);
} else {
const link = document.createElement('a');
@@ -60,76 +83,102 @@ export function UnsubscribeBanner({
setSuccess(true);
setProcessing(false);
+ setShowConfirm(false);
setTimeout(onDismiss, 3000);
}
} catch (err) {
console.error('Unsubscribe error:', err);
setError(true);
setProcessing(false);
+ setShowConfirm(false);
}
};
if (success) {
return (
-
-
-
+
+
+
{t(unsubMethod === 'http'
? 'email_viewer.unsubscribe_banner.success_http'
: 'email_viewer.unsubscribe_banner.success_mailto'
)}
-
+
);
}
if (error) {
return (
-
-
-
- {t('email_viewer.unsubscribe_banner.error')}
-
+
+
-
+
);
}
return (
-
- {showConfirm ? (
- <>
-
- {t('email_viewer.unsubscribe_banner.confirm_title')}
-
-
-
- >
- ) : (
+ <>
+
+ ·
+ {/* Desktop popover */}
+ {showConfirm && isDesktop && (
+
+
+ {t('email_viewer.unsubscribe_banner.confirm_title')}
+
+
+
+
+
+
+ )}
+
+ {/* Mobile/tablet: proper confirm dialog */}
+ {!isDesktop && (
+
setShowConfirm(false)}
+ onConfirm={() => {
+ setShowConfirm(false);
+ handleUnsubscribe();
+ }}
+ title={t('email_viewer.unsubscribe_banner.confirm_title')}
+ message={t(unsubMethod === 'http'
+ ? 'email_viewer.unsubscribe_banner.success_http'
+ : 'email_viewer.unsubscribe_banner.success_mailto'
+ )}
+ confirmText={t('email_viewer.unsubscribe_banner.confirm_button')}
+ cancelText={t('email_viewer.unsubscribe_banner.cancel')}
+ variant="destructive"
+ />
)}
-
+ >
);
}