courrier refactor rebuild preview

This commit is contained in:
alma 2025-04-27 00:20:02 +02:00
parent c7f5e31b23
commit a2a088cf0f

View File

@ -1,11 +1,14 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { Loader2, Paperclip } from 'lucide-react'; import { Loader2, Paperclip, User } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { sanitizeHtml } from '@/lib/utils/email-formatter'; import { sanitizeHtml } from '@/lib/utils/email-formatter';
import { Avatar } from '@/components/ui/avatar';
import { AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Card } from '@/components/ui/card';
interface EmailAddress { interface EmailAddress {
name: string; name: string;
@ -78,6 +81,19 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
).join(', '); ).join(', ');
}; };
// Get sender initials for avatar
const getSenderInitials = (sender: EmailAddress | undefined) => {
if (!sender) return 'U';
if (sender.name) {
const parts = sender.name.split(' ');
if (parts.length > 1) {
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
}
return sender.name[0].toUpperCase();
}
return sender.address[0].toUpperCase();
};
// Display loading state // Display loading state
if (loading) { if (loading) {
return ( return (
@ -101,70 +117,74 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
); );
} }
const sender = email.from && email.from.length > 0 ? email.from[0] : undefined;
return ( return (
<div className="flex flex-col h-full overflow-hidden"> <Card className="flex flex-col h-full overflow-hidden border-0 shadow-none">
{/* Email header */} {/* Email header */}
<div className="p-4 border-b"> <div className="p-6 border-b">
<div className="mb-3"> <div className="mb-4">
<h2 className="text-xl font-semibold mb-2">{email.subject}</h2> <h2 className="text-xl font-semibold mb-4">{email.subject}</h2>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center"> <div className="flex items-start gap-3 mb-4">
<span className="font-medium mr-1">From:</span> <Avatar className="h-10 w-10">
<span>{formatEmailAddresses(email.from)}</span> <AvatarFallback>{getSenderInitials(sender)}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<div className="font-medium">{sender?.name || sender?.address}</div>
<span className="text-sm text-muted-foreground">{formatDate(email.date)}</span>
</div>
<div className="text-sm text-muted-foreground truncate mt-1">
To: {formatEmailAddresses(email.to)}
</div>
{email.cc && email.cc.length > 0 && (
<div className="text-sm text-muted-foreground truncate mt-1">
Cc: {formatEmailAddresses(email.cc)}
</div>
)}
</div> </div>
<span className="text-muted-foreground">{formatDate(email.date)}</span>
</div> </div>
{email.to && email.to.length > 0 && ( {/* Action buttons */}
<div className="text-sm mt-1"> {onReply && (
<span className="font-medium mr-1">To:</span> <div className="flex gap-2 mt-4">
<span>{formatEmailAddresses(email.to)}</span> <Button
</div> size="sm"
)} variant="outline"
onClick={() => onReply('reply')}
{email.cc && email.cc.length > 0 && ( >
<div className="text-sm mt-1"> Reply
<span className="font-medium mr-1">Cc:</span> </Button>
<span>{formatEmailAddresses(email.cc)}</span> <Button
size="sm"
variant="outline"
onClick={() => onReply('reply-all')}
>
Reply All
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onReply('forward')}
>
Forward
</Button>
</div> </div>
)} )}
</div> </div>
{/* Action buttons */}
{onReply && (
<div className="flex gap-2 mt-4">
<Button
size="sm"
variant="outline"
onClick={() => onReply('reply')}
>
Reply
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onReply('reply-all')}
>
Reply All
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onReply('forward')}
>
Forward
</Button>
</div>
)}
{/* Attachments */} {/* Attachments */}
{email.attachments && email.attachments.length > 0 && ( {email.attachments && email.attachments.length > 0 && (
<div className="mt-4 border-t pt-2"> <div className="px-6 py-3 border-b bg-muted/30">
<div className="text-sm font-medium mb-2">Attachments:</div> <div className="text-sm font-medium mb-2">Attachments</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{email.attachments.map((attachment, index) => ( {email.attachments.map((attachment, index) => (
<Badge key={index} variant="outline" className="flex items-center gap-1"> <Badge key={index} variant="outline" className="flex items-center gap-1 px-2 py-1">
<Paperclip className="h-3 w-3" /> <Paperclip className="h-3.5 w-3.5" />
<span>{attachment.filename}</span> <span>{attachment.filename}</span>
<span className="text-xs text-muted-foreground ml-1"> <span className="text-xs text-muted-foreground ml-1">
({Math.round(attachment.size / 1024)}KB) ({Math.round(attachment.size / 1024)}KB)
@ -177,18 +197,20 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
</div> </div>
{/* Email content */} {/* Email content */}
<ScrollArea className="flex-1 px-4 py-3"> <ScrollArea className="flex-1">
{email.content ? ( <div className="px-6 py-5">
<div {email.content ? (
className="email-content-display" <div
dangerouslySetInnerHTML={{ className="email-content-display prose prose-sm max-w-none"
__html: sanitizeHtml(email.content) dangerouslySetInnerHTML={{
}} __html: sanitizeHtml(email.content)
/> }}
) : ( />
<p className="text-muted-foreground">No content available</p> ) : (
)} <p className="text-muted-foreground">No content available</p>
)}
</div>
</ScrollArea> </ScrollArea>
</div> </Card>
); );
} }