233 lines
7.9 KiB
TypeScript
233 lines
7.9 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
ChevronLeft, Reply, ReplyAll, Forward, Star, MoreHorizontal
|
|
} from 'lucide-react';
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Email } from '@/hooks/use-courrier';
|
|
|
|
interface EmailDetailViewProps {
|
|
email: Email & {
|
|
html?: string;
|
|
text?: string;
|
|
starred?: boolean; // Add starred property to interface
|
|
};
|
|
onBack: () => void;
|
|
onReply: () => void;
|
|
onReplyAll: () => void;
|
|
onForward: () => void;
|
|
onToggleStar: () => void;
|
|
}
|
|
|
|
export default function EmailDetailView({
|
|
email,
|
|
onBack,
|
|
onReply,
|
|
onReplyAll,
|
|
onForward,
|
|
onToggleStar
|
|
}: EmailDetailViewProps) {
|
|
|
|
// Format date for display
|
|
const formatDate = (dateString: string | Date) => {
|
|
// Convert to Date object if string
|
|
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
|
|
const now = new Date();
|
|
|
|
if (date.toDateString() === now.toDateString()) {
|
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
} else {
|
|
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
|
}
|
|
};
|
|
|
|
// Render email content based on the email body
|
|
const renderEmailContent = () => {
|
|
try {
|
|
console.log('EmailDetailView renderEmailContent', {
|
|
hasContent: !!email.content,
|
|
contentType: typeof email.content,
|
|
hasHtml: !!email.html,
|
|
hasText: !!email.text
|
|
});
|
|
|
|
// Determine what content to use and how to handle it
|
|
let contentToUse = '';
|
|
|
|
if (email.content) {
|
|
// If content is a string, use it directly
|
|
if (typeof email.content === 'string') {
|
|
contentToUse = email.content;
|
|
}
|
|
// If content is an object with html/text properties
|
|
else if (typeof email.content === 'object') {
|
|
contentToUse = email.content.html || email.content.text || '';
|
|
}
|
|
}
|
|
// Fall back to html or text properties if content is not available
|
|
else if (email.html) {
|
|
contentToUse = email.html;
|
|
}
|
|
else if (email.text) {
|
|
// Convert plain text to HTML with line breaks
|
|
contentToUse = email.text
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/\n/g, '<br>');
|
|
}
|
|
|
|
// Return content or fallback message
|
|
return contentToUse ?
|
|
<div dangerouslySetInnerHTML={{ __html: contentToUse }} /> :
|
|
<div className="text-gray-500">No content available</div>;
|
|
} catch (e) {
|
|
console.error('Error rendering email:', e);
|
|
return <div className="text-gray-500">Failed to render email content</div>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full overflow-hidden">
|
|
{/* Email actions header */}
|
|
<div className="flex-none px-4 py-3 border-b border-gray-100">
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={onBack}
|
|
className="md:hidden flex-shrink-0"
|
|
>
|
|
<ChevronLeft className="h-5 w-5" />
|
|
</Button>
|
|
<div className="min-w-0 max-w-[500px]">
|
|
<h2 className="text-lg font-semibold text-gray-900 truncate">
|
|
{email.subject}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-1 flex-shrink-0 ml-auto">
|
|
<div className="flex items-center border-l border-gray-200 pl-4">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-gray-400 hover:text-gray-900 h-9 w-9"
|
|
onClick={onReply}
|
|
>
|
|
<Reply className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-gray-400 hover:text-gray-900 h-9 w-9"
|
|
onClick={onReplyAll}
|
|
>
|
|
<ReplyAll className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-gray-400 hover:text-gray-900 h-9 w-9"
|
|
onClick={onForward}
|
|
>
|
|
<Forward className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="text-gray-400 hover:text-gray-900 h-9 w-9"
|
|
onClick={onToggleStar}
|
|
>
|
|
<Star className={`h-4 w-4 ${email.starred ? 'fill-yellow-400 text-yellow-400' : ''}`} />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Scrollable content area - enhanced for better scrolling */}
|
|
<ScrollArea className="flex-1 overflow-auto">
|
|
<div className="p-6">
|
|
{/* Email header info */}
|
|
<div className="flex items-center gap-4 mb-6">
|
|
<Avatar className="h-10 w-10">
|
|
<AvatarFallback>
|
|
{(email.from?.[0]?.name || '').charAt(0) || (email.from?.[0]?.address || '').charAt(0) || '?'}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="flex-1">
|
|
<p className="font-medium text-gray-900">
|
|
{email.from?.[0]?.name || ''} <span className="text-gray-500"><{email.from?.[0]?.address || ''}></span>
|
|
</p>
|
|
<p className="text-sm text-gray-500">
|
|
to {email.to?.[0]?.address || ''}
|
|
</p>
|
|
{email.cc && email.cc.length > 0 && (
|
|
<p className="text-sm text-gray-500">
|
|
cc {email.cc.map(c => c.address).join(', ')}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="text-sm text-gray-500 whitespace-nowrap">
|
|
{formatDate(email.date)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Email content with improved scrolling */}
|
|
<div className="prose prose-sm max-w-none email-content-wrapper">
|
|
{renderEmailContent()}
|
|
</div>
|
|
|
|
{/* Attachments (if any) */}
|
|
{email.hasAttachments && email.attachments && email.attachments.length > 0 && (
|
|
<div className="mt-6 border-t border-gray-100 pt-4">
|
|
<h3 className="text-sm font-medium text-gray-900 mb-2">Attachments</h3>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
{email.attachments.map((attachment, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="flex items-center gap-2 p-2 border border-gray-200 rounded-md"
|
|
>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-gray-700 truncate">{attachment.filename}</p>
|
|
<p className="text-xs text-gray-500">{(attachment.size / 1024).toFixed(1)} KB</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</ScrollArea>
|
|
|
|
{/* Add CSS for better email content display */}
|
|
<style jsx global>{`
|
|
.email-content-wrapper {
|
|
width: 100%;
|
|
overflow-wrap: break-word;
|
|
word-wrap: break-word;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.email-content-wrapper img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.email-content-wrapper table {
|
|
max-width: 100%;
|
|
overflow-x: auto;
|
|
display: block;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.email-content-wrapper {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|