feat: show details toggle and panel on mobile sender info
This commit is contained in:
+237
-218
@@ -4143,7 +4143,240 @@ export function EmailViewer({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Expandable Details */}
|
||||
|
||||
</div>
|
||||
{/* Attachments on the right (beside-sender mode) */}
|
||||
{attachmentPosition === 'beside-sender' && effectiveAttachments.length > 0 && (
|
||||
<div className="relative flex flex-col items-end justify-start gap-1 flex-shrink-0 max-w-[50%]">
|
||||
{effectiveAttachments.slice(0, 2).map((attachment) => {
|
||||
const FileIcon = getFileIcon(attachment.name || undefined, attachment.type);
|
||||
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
||||
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
|
||||
const thumbUrl = imageThumbUrls[attachment.id];
|
||||
return (
|
||||
<DraggableAttachmentChip key={attachment.id} attachment={attachment} client={client} enabled={dragOutActive}>
|
||||
{(dragProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-muted/60 hover:bg-muted rounded-md border border-border/50 group relative cursor-pointer overflow-hidden",
|
||||
thumbUrl
|
||||
? "inline-flex flex-col w-40"
|
||||
: "inline-flex items-center gap-1.5 px-2 py-1",
|
||||
)}
|
||||
title={`${opensPreview ? tFiles('preview') : t('download')} ${getAttachmentDisplayName(attachment.name, attachment.type)}`}
|
||||
onClick={() => handleEffectiveAttachmentOpen(attachment)}
|
||||
draggable={dragProps.draggable}
|
||||
onPointerEnter={dragProps.onPointerEnter}
|
||||
onDragStart={dragProps.onDragStart}
|
||||
onDragEnd={dragProps.onDragEnd}
|
||||
>
|
||||
{thumbUrl && (
|
||||
<div className="w-full h-16 bg-background/40 flex items-center justify-center overflow-hidden">
|
||||
<img src={thumbUrl} alt="" className="w-full h-full object-cover" loading="lazy" />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(
|
||||
"flex items-center gap-1.5",
|
||||
thumbUrl && "px-2 py-1 border-t border-border/50 w-full",
|
||||
)}>
|
||||
<FileIcon className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className={cn(
|
||||
"text-xs text-foreground truncate",
|
||||
thumbUrl ? "flex-1 min-w-0" : "max-w-[140px]",
|
||||
)}>
|
||||
{getAttachmentDisplayName(attachment.name, attachment.type)}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground flex-shrink-0">
|
||||
{formatFileSize(attachment.size)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"absolute bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5 rounded-md",
|
||||
thumbUrl ? "top-1 right-1" : "inset-y-0 right-0 rounded-l-none rounded-r-md",
|
||||
)}>
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={t('download')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); }}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
{opensPreview && (
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={tFiles('preview')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); }}
|
||||
>
|
||||
<Eye className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DraggableAttachmentChip>
|
||||
);
|
||||
})}
|
||||
{effectiveAttachments.length > 2 && (
|
||||
<button
|
||||
onClick={() => setShowAllBesideAttachments(!showAllBesideAttachments)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-0.5"
|
||||
>
|
||||
+{effectiveAttachments.length - 2} {t('more')}
|
||||
</button>
|
||||
)}
|
||||
{/* Floating popup for remaining attachments */}
|
||||
{showAllBesideAttachments && effectiveAttachments.length > 2 && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowAllBesideAttachments(false)} />
|
||||
<div className="absolute top-full right-0 mt-1 z-50 bg-background border border-border rounded-lg shadow-lg p-2 flex flex-col gap-1 min-w-[220px]">
|
||||
{effectiveAttachments.slice(2).map((attachment) => {
|
||||
const FileIcon = getFileIcon(attachment.name || undefined, attachment.type);
|
||||
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
||||
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
|
||||
return (
|
||||
<DraggableAttachmentChip key={attachment.id} attachment={attachment} client={client} enabled={dragOutActive}>
|
||||
{(dragProps) => (
|
||||
<div
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-muted/60 group relative cursor-pointer w-full"
|
||||
title={`${opensPreview ? tFiles('preview') : t('download')} ${getAttachmentDisplayName(attachment.name, attachment.type)}`}
|
||||
onClick={() => { handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
|
||||
draggable={dragProps.draggable}
|
||||
onPointerEnter={dragProps.onPointerEnter}
|
||||
onDragStart={dragProps.onDragStart}
|
||||
onDragEnd={dragProps.onDragEnd}
|
||||
>
|
||||
<FileIcon className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-xs text-foreground truncate max-w-[180px]">
|
||||
{getAttachmentDisplayName(attachment.name, attachment.type)}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
|
||||
{formatFileSize(attachment.size)}
|
||||
</span>
|
||||
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={t('download')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); setShowAllBesideAttachments(false); }}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
{opensPreview && (
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={tFiles('preview')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
|
||||
>
|
||||
<Eye className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DraggableAttachmentChip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile/Tablet Sender Info - scrolls with content */}
|
||||
<div className="lg:hidden bg-background border-b border-border px-4" style={{ paddingBlock: 'var(--density-header-py)' }}>
|
||||
<div className="flex items-start" style={{ gap: 'var(--density-item-gap)' }}>
|
||||
<button
|
||||
onClick={() => sender?.email && handleViewContactSidebar(null, sender.email)}
|
||||
className="cursor-pointer group flex-shrink-0"
|
||||
title={sender?.email || undefined}
|
||||
>
|
||||
<Avatar
|
||||
name={sender?.name}
|
||||
email={sender?.email}
|
||||
size="lg"
|
||||
className="shadow-sm w-10 h-10 group-hover:ring-2 group-hover:ring-primary/30 transition-all"
|
||||
/>
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Row 1: Sender name + badges */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<button
|
||||
onClick={() => sender?.email && handleViewContactSidebar(null, sender.email)}
|
||||
className="text-sm font-semibold text-foreground hover:text-primary hover:underline transition-colors cursor-pointer text-left"
|
||||
>
|
||||
{sender?.name || sender?.email || t('unknown_sender')}
|
||||
</button>
|
||||
<EmailIdentityBadge email={email} identities={identities} />
|
||||
{shouldShowUnsubBanner && listHeaders?.listUnsubscribe && (
|
||||
<UnsubscribeBanner
|
||||
listUnsubscribe={listHeaders.listUnsubscribe}
|
||||
senderEmail={email?.from?.[0]?.email || ''}
|
||||
onDismiss={() => {
|
||||
const messageId = email?.messageId || '';
|
||||
const newSet = new Set(dismissedUnsubBanners).add(messageId);
|
||||
setDismissedUnsubBanners(newSet);
|
||||
localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet]));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Email address under name */}
|
||||
{sender?.email && sender?.name && (
|
||||
<div className="text-xs text-muted-foreground mt-0.5 truncate">{sender.email}</div>
|
||||
)}
|
||||
{/* Row 2: Recipients */}
|
||||
<div className="mt-0.5 flex items-center gap-1 text-sm text-muted-foreground flex-wrap">
|
||||
{email.to && email.to.length > 0 && (
|
||||
<>
|
||||
<span>→ {t('recipient_to_prefix')}</span>
|
||||
{renderClickableRecipients(email.to, currentUserEmail, t, handleViewContactSidebar)}
|
||||
</>
|
||||
)}
|
||||
{email.cc && email.cc.length > 0 && (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">|</span>
|
||||
<span>CC:</span>
|
||||
{renderClickableRecipients(email.cc, currentUserEmail, t, handleViewContactSidebar)}
|
||||
{email.cc.length > 2 && (
|
||||
<span>+{email.cc.length - 2}</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowFullHeaders(!showFullHeaders)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-0.5 transition-colors ml-1"
|
||||
>
|
||||
{showFullHeaders ? (
|
||||
<>
|
||||
<ChevronUp className="w-3 h-3" />
|
||||
{t('hide_details')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
{t('show_details')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Date/time + size on the right (mobile) */}
|
||||
<div className="sm:hidden flex-shrink-0 text-right ml-2">
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
|
||||
</span>
|
||||
{email.size > 0 && (
|
||||
<div className="text-xs text-muted-foreground/60">
|
||||
{formatFileSize(email.size)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable Details (shared across mobile/tablet/desktop) */}
|
||||
{showFullHeaders && (() => {
|
||||
const translateAuthResult = (result?: string) => {
|
||||
const r = (result || '').toLowerCase();
|
||||
@@ -4234,7 +4467,8 @@ export function EmailViewer({
|
||||
const hasAuthSection = !!(auth?.spf || auth?.dkim || auth?.dmarc || auth?.iprev || email.spamScore !== undefined || email.spamLLM);
|
||||
|
||||
return (
|
||||
<div className="mt-3 pt-3 border-t border-border grid grid-cols-1 lg:grid-cols-2 gap-x-10 gap-y-5">
|
||||
<div className="bg-background border-b border-border px-4 lg:px-6" style={{ paddingBlock: 'var(--density-header-py)' }}>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-x-10 gap-y-5">
|
||||
<section className="min-w-0">
|
||||
<SectionHeader>{t('details.recipients_routing')}</SectionHeader>
|
||||
<dl className="grid grid-cols-[7rem_1fr] gap-x-4 gap-y-1.5">
|
||||
@@ -4445,225 +4679,10 @@ export function EmailViewer({
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
</div>
|
||||
{/* Attachments on the right (beside-sender mode) */}
|
||||
{attachmentPosition === 'beside-sender' && effectiveAttachments.length > 0 && (
|
||||
<div className="relative flex flex-col items-end justify-start gap-1 flex-shrink-0 max-w-[50%]">
|
||||
{effectiveAttachments.slice(0, 2).map((attachment) => {
|
||||
const FileIcon = getFileIcon(attachment.name || undefined, attachment.type);
|
||||
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
||||
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
|
||||
const thumbUrl = imageThumbUrls[attachment.id];
|
||||
return (
|
||||
<DraggableAttachmentChip key={attachment.id} attachment={attachment} client={client} enabled={dragOutActive}>
|
||||
{(dragProps) => (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-muted/60 hover:bg-muted rounded-md border border-border/50 group relative cursor-pointer overflow-hidden",
|
||||
thumbUrl
|
||||
? "inline-flex flex-col w-40"
|
||||
: "inline-flex items-center gap-1.5 px-2 py-1",
|
||||
)}
|
||||
title={`${opensPreview ? tFiles('preview') : t('download')} ${getAttachmentDisplayName(attachment.name, attachment.type)}`}
|
||||
onClick={() => handleEffectiveAttachmentOpen(attachment)}
|
||||
draggable={dragProps.draggable}
|
||||
onPointerEnter={dragProps.onPointerEnter}
|
||||
onDragStart={dragProps.onDragStart}
|
||||
onDragEnd={dragProps.onDragEnd}
|
||||
>
|
||||
{thumbUrl && (
|
||||
<div className="w-full h-16 bg-background/40 flex items-center justify-center overflow-hidden">
|
||||
<img src={thumbUrl} alt="" className="w-full h-full object-cover" loading="lazy" />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(
|
||||
"flex items-center gap-1.5",
|
||||
thumbUrl && "px-2 py-1 border-t border-border/50 w-full",
|
||||
)}>
|
||||
<FileIcon className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className={cn(
|
||||
"text-xs text-foreground truncate",
|
||||
thumbUrl ? "flex-1 min-w-0" : "max-w-[140px]",
|
||||
)}>
|
||||
{getAttachmentDisplayName(attachment.name, attachment.type)}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground flex-shrink-0">
|
||||
{formatFileSize(attachment.size)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={cn(
|
||||
"absolute bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5 rounded-md",
|
||||
thumbUrl ? "top-1 right-1" : "inset-y-0 right-0 rounded-l-none rounded-r-md",
|
||||
)}>
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={t('download')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); }}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
{opensPreview && (
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={tFiles('preview')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); }}
|
||||
>
|
||||
<Eye className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DraggableAttachmentChip>
|
||||
);
|
||||
})}
|
||||
{effectiveAttachments.length > 2 && (
|
||||
<button
|
||||
onClick={() => setShowAllBesideAttachments(!showAllBesideAttachments)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-0.5"
|
||||
>
|
||||
+{effectiveAttachments.length - 2} {t('more')}
|
||||
</button>
|
||||
)}
|
||||
{/* Floating popup for remaining attachments */}
|
||||
{showAllBesideAttachments && effectiveAttachments.length > 2 && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowAllBesideAttachments(false)} />
|
||||
<div className="absolute top-full right-0 mt-1 z-50 bg-background border border-border rounded-lg shadow-lg p-2 flex flex-col gap-1 min-w-[220px]">
|
||||
{effectiveAttachments.slice(2).map((attachment) => {
|
||||
const FileIcon = getFileIcon(attachment.name || undefined, attachment.type);
|
||||
const isPreviewable = isFilePreviewable(attachment.name || undefined, attachment.type);
|
||||
const opensPreview = isPreviewable && mailAttachmentAction === 'preview';
|
||||
return (
|
||||
<DraggableAttachmentChip key={attachment.id} attachment={attachment} client={client} enabled={dragOutActive}>
|
||||
{(dragProps) => (
|
||||
<div
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-muted/60 group relative cursor-pointer w-full"
|
||||
title={`${opensPreview ? tFiles('preview') : t('download')} ${getAttachmentDisplayName(attachment.name, attachment.type)}`}
|
||||
onClick={() => { handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
|
||||
draggable={dragProps.draggable}
|
||||
onPointerEnter={dragProps.onPointerEnter}
|
||||
onDragStart={dragProps.onDragStart}
|
||||
onDragEnd={dragProps.onDragEnd}
|
||||
>
|
||||
<FileIcon className="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-xs text-foreground truncate max-w-[180px]">
|
||||
{getAttachmentDisplayName(attachment.name, attachment.type)}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground ml-auto flex-shrink-0">
|
||||
{formatFileSize(attachment.size)}
|
||||
</span>
|
||||
<div className="absolute inset-y-0 right-0 rounded-r-md bg-background/95 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1 px-1.5">
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={t('download')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentDownload(attachment); setShowAllBesideAttachments(false); }}
|
||||
>
|
||||
<Download className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
{opensPreview && (
|
||||
<button
|
||||
className="p-1 hover:bg-accent rounded transition-colors"
|
||||
title={tFiles('preview')}
|
||||
onClick={(e) => { e.stopPropagation(); handleEffectiveAttachmentOpen(attachment); setShowAllBesideAttachments(false); }}
|
||||
>
|
||||
<Eye className="w-3.5 h-3.5 text-foreground" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DraggableAttachmentChip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile/Tablet Sender Info - scrolls with content */}
|
||||
<div className="lg:hidden bg-background border-b border-border px-4" style={{ paddingBlock: 'var(--density-header-py)' }}>
|
||||
<div className="flex items-start" style={{ gap: 'var(--density-item-gap)' }}>
|
||||
<button
|
||||
onClick={() => sender?.email && handleViewContactSidebar(null, sender.email)}
|
||||
className="cursor-pointer group flex-shrink-0"
|
||||
title={sender?.email || undefined}
|
||||
>
|
||||
<Avatar
|
||||
name={sender?.name}
|
||||
email={sender?.email}
|
||||
size="lg"
|
||||
className="shadow-sm w-10 h-10 group-hover:ring-2 group-hover:ring-primary/30 transition-all"
|
||||
/>
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* Row 1: Sender name + badges */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<button
|
||||
onClick={() => sender?.email && handleViewContactSidebar(null, sender.email)}
|
||||
className="text-sm font-semibold text-foreground hover:text-primary hover:underline transition-colors cursor-pointer text-left"
|
||||
>
|
||||
{sender?.name || sender?.email || t('unknown_sender')}
|
||||
</button>
|
||||
<EmailIdentityBadge email={email} identities={identities} />
|
||||
{shouldShowUnsubBanner && listHeaders?.listUnsubscribe && (
|
||||
<UnsubscribeBanner
|
||||
listUnsubscribe={listHeaders.listUnsubscribe}
|
||||
senderEmail={email?.from?.[0]?.email || ''}
|
||||
onDismiss={() => {
|
||||
const messageId = email?.messageId || '';
|
||||
const newSet = new Set(dismissedUnsubBanners).add(messageId);
|
||||
setDismissedUnsubBanners(newSet);
|
||||
localStorage.setItem('dismissed-unsub-banners', JSON.stringify([...newSet]));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Email address under name */}
|
||||
{sender?.email && sender?.name && (
|
||||
<div className="text-xs text-muted-foreground mt-0.5 truncate">{sender.email}</div>
|
||||
)}
|
||||
{/* Row 2: Recipients */}
|
||||
<div className="mt-0.5 flex items-center gap-1 text-sm text-muted-foreground flex-wrap">
|
||||
{email.to && email.to.length > 0 && (
|
||||
<>
|
||||
<span>→ {t('recipient_to_prefix')}</span>
|
||||
{renderClickableRecipients(email.to, currentUserEmail, t, handleViewContactSidebar)}
|
||||
</>
|
||||
)}
|
||||
{email.cc && email.cc.length > 0 && (
|
||||
<>
|
||||
<span className="text-muted-foreground/50">|</span>
|
||||
<span>CC:</span>
|
||||
{renderClickableRecipients(email.cc, currentUserEmail, t, handleViewContactSidebar)}
|
||||
{email.cc.length > 2 && (
|
||||
<span>+{email.cc.length - 2}</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Date/time + size on the right (mobile) */}
|
||||
<div className="sm:hidden flex-shrink-0 text-right ml-2">
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{formatDateTime(email.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })}
|
||||
</span>
|
||||
{email.size > 0 && (
|
||||
<div className="text-xs text-muted-foreground/60">
|
||||
{formatFileSize(email.size)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* S/MIME Status Banner */}
|
||||
{smimeStatus && (
|
||||
<div className="border-b border-border bg-muted/30">
|
||||
|
||||
Reference in New Issue
Block a user