231 lines
6.4 KiB
TypeScript
231 lines
6.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
import EmailPreview from './EmailPreview';
|
|
import ComposeEmailAdapter from './ComposeEmailAdapter';
|
|
import { Loader2 } from 'lucide-react';
|
|
import { useEmailFetch } from '@/hooks/use-email-fetch';
|
|
import { debounce } from '@/lib/utils/debounce';
|
|
import { EmailMessage } from '@/types/email';
|
|
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
|
|
|
interface EmailPanelProps {
|
|
selectedEmail: {
|
|
emailId: string;
|
|
accountId: string;
|
|
folder: string;
|
|
} | null;
|
|
onSendEmail: (email: any) => Promise<void>;
|
|
accounts?: Array<{
|
|
id: string;
|
|
email: string;
|
|
display_name?: string;
|
|
}>;
|
|
}
|
|
|
|
// Type for the legacy ComposeEmail component props
|
|
interface ComposeEmailProps {
|
|
initialEmail?: any;
|
|
type?: 'new' | 'reply' | 'reply-all' | 'forward';
|
|
onClose: () => void;
|
|
onSend: (emailData: {
|
|
to: string;
|
|
cc?: string;
|
|
bcc?: string;
|
|
subject: string;
|
|
body: string;
|
|
fromAccount?: string;
|
|
attachments?: Array<{
|
|
name: string;
|
|
content: string;
|
|
type: string;
|
|
}>;
|
|
}) => Promise<void>;
|
|
}
|
|
|
|
export default function EmailPanel({
|
|
selectedEmail,
|
|
onSendEmail,
|
|
accounts = []
|
|
}: 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');
|
|
|
|
// Convert the email to the standardized format
|
|
const standardizedEmail = useMemo(() => {
|
|
if (!email) {
|
|
return null;
|
|
}
|
|
|
|
// The useEmailFetch hook now provides fully formatted email
|
|
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) {
|
|
// CRITICAL FIX: Store the current account ID to check for changes
|
|
// This helps prevent race conditions during account switching
|
|
const currentAccountId = selectedEmail.accountId;
|
|
console.log(`EmailPanel: Loading email ${selectedEmail.emailId} from account ${currentAccountId}`);
|
|
|
|
debouncedFetchEmail(selectedEmail.emailId, selectedEmail.accountId, selectedEmail.folder);
|
|
setIsComposing(false);
|
|
|
|
// Return a cleanup function that can detect and handle account changes
|
|
return () => {
|
|
console.log(`EmailPanel: Cleaning up email fetch for account ${currentAccountId}`);
|
|
};
|
|
}
|
|
}, [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');
|
|
};
|
|
|
|
// Wrap the onSendEmail function to ensure it returns a Promise
|
|
const handleSendEmail = async (emailData: any) => {
|
|
try {
|
|
return await onSendEmail(emailData);
|
|
} catch (error) {
|
|
console.error('Error sending email:', error);
|
|
throw error; // Re-throw to let ComposeEmail handle it
|
|
}
|
|
};
|
|
|
|
// 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 ? (
|
|
<ComposeEmailAdapter
|
|
initialEmail={standardizedEmail}
|
|
type={composeType}
|
|
onClose={handleComposeClose}
|
|
onSend={handleSendEmail}
|
|
accounts={accounts}
|
|
/>
|
|
) : (
|
|
<div className="max-w-4xl mx-auto h-full">
|
|
<EmailPreview
|
|
email={standardizedEmail}
|
|
onReply={handleReply}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Helper function to normalize address to string format
|
|
function normalizeAddress(address: any): string {
|
|
if (!address) return '';
|
|
|
|
// If already a string, return as is
|
|
if (typeof address === 'string') {
|
|
return address;
|
|
}
|
|
|
|
// If array of EmailAddress objects
|
|
if (Array.isArray(address)) {
|
|
return address.map(addr => {
|
|
if (typeof addr === 'object' && addr !== null) {
|
|
// Handle EmailAddress object
|
|
if ('name' in addr && 'address' in addr) {
|
|
return addr.name && addr.name !== addr.address
|
|
? `${addr.name} <${addr.address}>`
|
|
: addr.address;
|
|
}
|
|
}
|
|
return String(addr || '');
|
|
}).join(', ');
|
|
}
|
|
|
|
// Handle single EmailAddress object
|
|
if (typeof address === 'object' && address !== null) {
|
|
if ('name' in address && 'address' in address) {
|
|
return address.name && address.name !== address.address
|
|
? `${address.name} <${address.address}>`
|
|
: address.address;
|
|
}
|
|
}
|
|
|
|
// Fallback
|
|
return String(address);
|
|
}
|