Neah/components/email/EmailPanel.tsx

203 lines
5.4 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';
// 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');
// Create a formatted version of the email content
const formattedEmail = useMemo(() => {
if (!email) return null;
try {
// Handle different content structures
let content = '';
if (email.formattedContent) {
// If we already have formatted content, use that
content = email.formattedContent;
} else if (typeof email.content === 'string') {
// Direct string content
content = email.content;
} else if (email.content && typeof email.content === 'object') {
// Object with text/html properties (new structure)
content = email.content.html || email.content.text || '';
} else {
// Fallback to html or text properties
content = email.html || email.text || '';
}
// Return a new email object with the formatted content
return {
...email,
content,
formattedContent: content
};
} catch (error) {
console.error('Error formatting email content:', error);
return email;
}
}, [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>
);
}