214 lines
5.5 KiB
TypeScript
214 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useMemo, useCallback } 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';
|
|
import { useEmailFetch } from '@/hooks/use-email-fetch';
|
|
import { debounce } from '@/lib/utils/debounce';
|
|
import { formatEmailContent } from '@/lib/utils/email-content';
|
|
|
|
// 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: (email: any) => void;
|
|
}
|
|
|
|
export default function EmailPanel({
|
|
selectedEmail,
|
|
onSendEmail
|
|
}: EmailPanelProps) {
|
|
// Use the new email fetch hook
|
|
const { email, loading, error, fetchEmail } = useEmailFetch({
|
|
onEmailLoaded: (email) => {
|
|
// Handle any post-load actions
|
|
console.log('Email loaded:', email.id);
|
|
},
|
|
onError: (error) => {
|
|
console.error('Error loading email:', error);
|
|
}
|
|
});
|
|
|
|
// Compose mode state
|
|
const [isComposing, setIsComposing] = useState<boolean>(false);
|
|
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
|
|
|
|
// Format the email content
|
|
const formattedEmail = useMemo(() => {
|
|
if (!email) {
|
|
console.log('EmailPanel: No email provided');
|
|
return null;
|
|
}
|
|
|
|
console.log('EmailPanel: Raw email:', email);
|
|
|
|
// If content is already an object with html/text, use it directly
|
|
if (email.content && typeof email.content === 'object') {
|
|
console.log('EmailPanel: Using existing content object');
|
|
return {
|
|
...email,
|
|
content: {
|
|
text: email.content.text || '',
|
|
html: email.content.html || ''
|
|
}
|
|
};
|
|
}
|
|
|
|
// If content is a string, convert it to object format
|
|
if (typeof email.content === 'string') {
|
|
console.log('EmailPanel: Converting string content to object');
|
|
return {
|
|
...email,
|
|
content: {
|
|
text: email.text || email.content,
|
|
html: email.html || email.content
|
|
}
|
|
};
|
|
}
|
|
|
|
// Fallback to html/text properties
|
|
console.log('EmailPanel: Using html/text properties');
|
|
return {
|
|
...email,
|
|
content: {
|
|
text: email.text || '',
|
|
html: email.html || ''
|
|
}
|
|
};
|
|
}, [email]);
|
|
|
|
// Debounced email fetch
|
|
const debouncedFetchEmail = useCallback(
|
|
debounce((emailId: string, accountId: string, folder: string) => {
|
|
fetchEmail(emailId, accountId, folder);
|
|
}, 300),
|
|
[fetchEmail]
|
|
);
|
|
|
|
// Load email content when selectedEmail changes
|
|
useEffect(() => {
|
|
if (selectedEmail) {
|
|
debouncedFetchEmail(selectedEmail.emailId, selectedEmail.accountId, selectedEmail.folder);
|
|
setIsComposing(false);
|
|
}
|
|
}, [selectedEmail, debouncedFetchEmail]);
|
|
|
|
// 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">
|
|
{isComposing ? (
|
|
<ComposeEmail
|
|
initialEmail={formattedEmail}
|
|
type={composeType}
|
|
onClose={handleComposeClose}
|
|
onSend={onSendEmail}
|
|
/>
|
|
) : (
|
|
<div className="max-w-4xl mx-auto h-full">
|
|
<EmailPreview
|
|
email={formattedEmail}
|
|
onReply={handleReply}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|