Neah/components/email/EmailPanel.tsx

253 lines
7.1 KiB
TypeScript

'use client';
import { useState, useEffect, useMemo } from 'react';
import EmailPreview from './EmailPreview';
import ComposeEmail from './ComposeEmail';
import { Loader2 } from 'lucide-react';
import { formatReplyEmail, EmailMessage as FormatterEmailMessage } from '@/lib/utils/email-formatter';
// Add local EmailMessage interface
interface EmailAddress {
name: string;
address: string;
}
interface EmailMessage {
id: string;
messageId?: string;
subject: string;
from: EmailAddress[];
to: EmailAddress[];
cc?: EmailAddress[];
bcc?: EmailAddress[];
date: Date | string;
flags?: {
seen: boolean;
flagged: boolean;
answered: boolean;
deleted: boolean;
draft: boolean;
};
preview?: string;
content?: string;
html?: string;
text?: string;
hasAttachments?: boolean;
attachments?: any[];
folder?: string;
size?: number;
contentFetched?: boolean;
}
interface EmailPanelProps {
selectedEmail: { emailId: string; accountId: string; folder: string } | null;
onSendEmail: (emailData: {
to: string;
cc?: string;
bcc?: string;
subject: string;
body: string;
attachments?: Array<{
name: string;
content: string;
type: string;
}>;
}) => Promise<void>;
}
export default function EmailPanel({
selectedEmail,
onSendEmail
}: EmailPanelProps) {
const [email, setEmail] = useState<EmailMessage | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// Compose mode state
const [isComposing, setIsComposing] = useState<boolean>(false);
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
// Create a formatted version of the email content using the same formatter as ComposeEmail
const formattedEmail = useMemo(() => {
if (!email) return null;
try {
// Convert to the formatter message format - this is what ComposeEmail does
const formatterEmail: FormatterEmailMessage = {
id: email.id,
messageId: email.messageId,
subject: email.subject,
from: email.from || [],
to: email.to || [],
cc: email.cc || [],
bcc: email.bcc || [],
date: email.date,
content: email.content,
html: email.html,
text: email.text,
hasAttachments: email.hasAttachments || false
};
// Try both formatting approaches to match what ComposeEmail would display
// This handles preview, reply and forward cases
let formattedContent: string;
// ComposeEmail switches based on type - we need to do the same
const { content: replyContent } = formatReplyEmail(formatterEmail, 'reply');
// Set the formatted content
formattedContent = replyContent;
console.log("Generated formatted content for email preview");
// Return a new email object with the formatted content
return {
...email,
formattedContent
};
} catch (error) {
console.error('Error formatting email content:', error);
return email;
}
}, [email]);
// Load email content when selectedEmail changes
useEffect(() => {
if (selectedEmail) {
fetchEmail(selectedEmail.emailId, selectedEmail.accountId, selectedEmail.folder);
setIsComposing(false);
} else {
setEmail(null);
}
}, [selectedEmail]);
// Fetch the email content
const fetchEmail = async (emailId: string, accountId: string, folder: string) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/courrier/${emailId}?accountId=${encodeURIComponent(accountId)}&folder=${encodeURIComponent(folder)}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch email');
}
const data = await response.json();
if (!data) {
throw new Error('Email not found');
}
// Mark as read if not already
if (!data.flags?.seen) {
markAsRead(emailId);
}
setEmail(data);
} catch (err) {
console.error('Error fetching email:', err);
setError(err instanceof Error ? err.message : 'Failed to load email');
} finally {
setLoading(false);
}
};
// Mark email as read
const markAsRead = async (id: string) => {
try {
await fetch(`/api/courrier/${id}/mark-read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'mark-read' }),
});
} catch (err) {
console.error('Error marking email as read:', err);
}
};
// Handle reply/forward actions
const handleReply = (type: 'reply' | 'reply-all' | 'forward') => {
setComposeType(type);
setIsComposing(true);
};
// Handle compose mode close
const handleComposeClose = () => {
setIsComposing(false);
setComposeType('new');
};
// If no email is selected and not composing
if (!selectedEmail && !isComposing) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center text-muted-foreground">
<p>Select an email to view or</p>
<button
className="text-primary mt-2 hover:underline"
onClick={() => {
setComposeType('new');
setIsComposing(true);
}}
>
Compose a new message
</button>
</div>
</div>
);
}
// Show loading state
if (loading) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-primary" />
<p>Loading email...</p>
</div>
</div>
);
}
// Show error state
if (error) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-center text-destructive">
<p>{error}</p>
<button
className="text-primary mt-2 hover:underline"
onClick={() => selectedEmail && fetchEmail(selectedEmail.emailId, selectedEmail.accountId, selectedEmail.folder)}
>
Try again
</button>
</div>
</div>
);
}
// Show compose mode or email preview
return (
<div className="h-full">
{/* Remove ComposeEmail overlay/modal logic. Only show preview or a button to trigger compose in parent. */}
{isComposing ? (
<div className="h-full flex items-center justify-center">
<div className="text-center text-muted-foreground">
<p>Compose mode is now handled by the main modal.</p>
<button
className="text-primary mt-2 hover:underline"
onClick={handleComposeClose}
>
Close
</button>
</div>
</div>
) : (
<div className="max-w-4xl mx-auto h-full">
<EmailPreview
email={formattedEmail}
onReply={handleReply}
/>
</div>
)}
</div>
);
}