diff --git a/components/email/EmailContentDisplay.tsx b/components/email/EmailContentDisplay.tsx index a856f186..46e915b8 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -5,7 +5,7 @@ import { renderEmailContent } from '@/lib/utils/email-utils'; import { EmailContent } from '@/types/email'; interface EmailContentDisplayProps { - content: EmailContent | any; + content: EmailContent; className?: string; showQuotedText?: boolean; type?: 'html' | 'text' | 'auto'; @@ -23,81 +23,26 @@ const EmailContentDisplay: React.FC = ({ type = 'auto', debug = false }) => { - // Normalize the content to our standard format if needed - const normalizedContent = useMemo(() => { - try { - // Handle different input types - if (!content) { - return { - html: undefined, - text: 'No content available', - isHtml: false, - direction: 'ltr' - } as EmailContent; - } - - // If content is already in our EmailContent format - if (content && - typeof content === 'object' && - 'text' in content && - 'isHtml' in content) { - return content as EmailContent; - } - - // Special case for email message with content property - if (content && typeof content === 'object' && content.content && - typeof content.content === 'object' && - 'text' in content.content && - 'isHtml' in content.content) { - return content.content as EmailContent; - } - - // Special case for simple string content - if (typeof content === 'string') { - return { - text: content, - isHtml: content.trim().startsWith('<'), - direction: 'ltr' - } as EmailContent; - } - - // For HTML/text properties - if (content && typeof content === 'object') { - if (content.html || content.text) { - return { - html: content.html, - text: content.text || '', - isHtml: !!content.html, - direction: 'ltr' - } as EmailContent; - } - } - - // Fallback - console.warn('EmailContentDisplay: Unable to properly normalize content format'); - return { - text: 'Content format not supported', - isHtml: false, - direction: 'ltr' - } as EmailContent; - } catch (error) { - console.error('Error normalizing content in EmailContentDisplay:', error); + // Ensure we have valid content to work with + const safeContent = useMemo(() => { + if (!content) { return { html: undefined, - text: `Error processing email content: ${error instanceof Error ? error.message : 'Unknown error'}`, + text: 'No content available', isHtml: false, direction: 'ltr' } as EmailContent; } + return content; }, [content]); - // Render the normalized content + // Render the content with proper formatting const htmlContent = useMemo(() => { - if (!normalizedContent) return ''; + if (!safeContent) return ''; try { // Override content type if specified - let contentToRender: EmailContent = { ...normalizedContent }; + let contentToRender: EmailContent = { ...safeContent }; if (type === 'html' && !contentToRender.isHtml) { // Force HTML rendering for text content @@ -116,10 +61,10 @@ const EmailContentDisplay: React.FC = ({ return renderEmailContent(contentToRender); } catch (error) { - console.error('Error rendering content in EmailContentDisplay:', error); + console.error('Error rendering content:', error); return `
Error rendering email content: ${error instanceof Error ? error.message : 'Unknown error'}
`; } - }, [normalizedContent, type]); + }, [safeContent, type]); // Apply quoted text styling if needed const containerStyle: CSSProperties = showQuotedText @@ -139,14 +84,10 @@ const EmailContentDisplay: React.FC = ({ {/* Debug output if enabled */} {debug && (
-

Content Type: {typeof content}

- {typeof content === 'object' && ( -

Keys: {Object.keys(content).join(', ')}

- )} -

Normalized: {normalizedContent?.isHtml ? 'HTML' : 'Text'}

-

Direction: {normalizedContent?.direction}

-

Has HTML: {!!normalizedContent?.html}

-

Text Length: {normalizedContent?.text?.length || 0}

+

Content Type: {safeContent.isHtml ? 'HTML' : 'Text'}

+

Direction: {safeContent.direction}

+

Has HTML: {!!safeContent.html}

+

Text Length: {safeContent.text?.length || 0}

)} diff --git a/components/email/EmailPanel.tsx b/components/email/EmailPanel.tsx index 40496056..ba5fbbc6 100644 --- a/components/email/EmailPanel.tsx +++ b/components/email/EmailPanel.tsx @@ -59,73 +59,11 @@ export default function EmailPanel({ // Convert the email to the standardized format const standardizedEmail = useMemo(() => { if (!email) { - console.log('EmailPanel: No email provided'); return null; } - console.log('EmailPanel: Raw email:', email); - console.log('EmailPanel: Raw email content type:', typeof email.content); - - if (email.content) { - // Log detailed content structure for debugging - if (typeof email.content === 'object') { - console.log('EmailPanel: Content object keys:', Object.keys(email.content)); - console.log('EmailPanel: Content has isHtml?', 'isHtml' in email.content); - console.log('EmailPanel: Content has text?', 'text' in email.content); - console.log('EmailPanel: Content has direction?', 'direction' in email.content); - } else if (typeof email.content === 'string') { - console.log('EmailPanel: Content is string, first 100 chars:', email.content.substring(0, 100)); - } - } - - try { - // Check if it's already in standardized format - if (email.content && - typeof email.content === 'object' && - 'isHtml' in email.content && - 'text' in email.content && - 'direction' in email.content) { - console.log('EmailPanel: Email already in standardized format'); - - // Normalize address format before returning - const normalizedEmail = { - ...email, - // Ensure from, to, cc are strings as expected by the EmailMessage interface - from: normalizeAddress(email.from), - to: normalizeAddress(email.to), - cc: email.cc ? normalizeAddress(email.cc) : undefined, - bcc: email.bcc ? normalizeAddress(email.bcc) : undefined - }; - - return normalizedEmail as EmailMessage; - } - - // Use the adapter utility to convert to the standardized format - console.log('EmailPanel: Adapting email to standardized format'); - const adapted = adaptLegacyEmail(email); - - // Log adapted email for debugging - console.log('EmailPanel: Adapted email content:', adapted.content); - - return adapted; - } catch (error) { - console.error('EmailPanel: Error adapting email:', error); - // If adaptation fails, create a minimal valid email for display - return { - id: email.id || 'unknown', - subject: email.subject || 'Error displaying email', - from: normalizeAddress(email.from) || '', - to: normalizeAddress(email.to) || '', - date: email.date || new Date().toISOString(), - flags: [], - content: { - text: `Error processing email: ${error instanceof Error ? error.message : 'Unknown error'}`, - html: undefined, - isHtml: false, - direction: 'ltr' - } - } as EmailMessage; - } + // The useEmailFetch hook now provides fully formatted email + return email; }, [email]); // Debounced email fetch diff --git a/components/email/EmailPreview.tsx b/components/email/EmailPreview.tsx index b08d7fd4..9f082610 100644 --- a/components/email/EmailPreview.tsx +++ b/components/email/EmailPreview.tsx @@ -1,118 +1,24 @@ 'use client'; -import { useRef, useMemo } from 'react'; +import { useRef } from 'react'; import { Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Card } from '@/components/ui/card'; -import { EmailMessage, EmailAddress } from '@/types/email'; -import { formatEmailAddresses, formatEmailDate } from '@/lib/utils/email-utils'; -import { adaptLegacyEmail } from '@/lib/utils/email-adapters'; +import { EmailMessage } from '@/types/email'; +import { formatEmailDate } from '@/lib/utils/email-utils'; import EmailContentDisplay from './EmailContentDisplay'; interface EmailPreviewProps { - email: EmailMessage | any; + email: EmailMessage | null; loading?: boolean; onReply?: (type: 'reply' | 'reply-all' | 'forward') => void; } export default function EmailPreview({ email, loading = false, onReply }: EmailPreviewProps) { - // Add editorRef to match ComposeEmail exactly const editorRef = useRef(null); - // Convert legacy email to standardized format if needed - const standardizedEmail = useMemo(() => { - if (!email) return null; - - try { - // Log input email details for debugging - console.log('EmailPreview: Input email type:', typeof email); - console.log('EmailPreview: Input email properties:', Object.keys(email)); - - // Check if from field is an array or string and log it - if (email.from) { - console.log('EmailPreview: From field type:', Array.isArray(email.from) ? - 'array' : typeof email.from); - - if (Array.isArray(email.from)) { - console.log('EmailPreview: From array length:', email.from.length); - if (email.from.length > 0) { - console.log('EmailPreview: First from item type:', typeof email.from[0]); - } - } - } - - if (email.content) { - console.log('EmailPreview: Content type:', typeof email.content); - if (typeof email.content === 'object') { - console.log('EmailPreview: Content properties:', Object.keys(email.content)); - } else { - console.log('EmailPreview: Content first 100 chars:', email.content.substring(0, 100)); - } - } - - // Check if the email is already in the standardized format - if ( - email.content && - typeof email.content === 'object' && - 'isHtml' in email.content && - 'text' in email.content - ) { - console.log('EmailPreview: Email is already in standardized format'); - return email as EmailMessage; - } - - // Otherwise, adapt it - console.log('EmailPreview: Adapting legacy email format'); - const adapted = adaptLegacyEmail(email); - - // Log the adapted email structure for debugging - console.log('EmailPreview: Adapted email:', { - id: adapted.id, - subject: adapted.subject, - from: adapted.from, - fromType: typeof adapted.from, - isFromArray: Array.isArray(adapted.from), - content: adapted.content ? { - isHtml: adapted.content.isHtml, - direction: adapted.content.direction, - textLength: adapted.content.text?.length, - htmlExists: !!adapted.content.html - } : 'No content' - }); - - return adapted; - } catch (error) { - console.error('Error adapting email:', error); - // Instead of returning null, try to create a minimal valid email to display the error - return { - id: email?.id || 'error', - subject: email?.subject || 'Error processing email', - from: email?.from || '', - to: email?.to || '', - date: email?.date || new Date().toISOString(), - flags: [], - content: { - text: `Error processing email: ${error instanceof Error ? error.message : 'Unknown error'}`, - isHtml: false, - direction: 'ltr' - } - } as EmailMessage; - } - }, [email]); - - // Get sender initials for avatar - const getSenderInitials = (name: string) => { - if (!name) return ''; - return name - .split(" ") - .map((n) => n?.[0] || '') - .join("") - .toUpperCase() - .slice(0, 2); - }; - // Display loading state if (loading) { return ( @@ -126,7 +32,7 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP } // No email selected - if (!standardizedEmail) { + if (!email) { return (
@@ -136,45 +42,37 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP ); } - // Debug output for content structure - console.log('EmailPreview: Standardized Email Content:', standardizedEmail.content); - - // Extract sender from various possible formats - handle both string and array formats + // Extract sender name from email.from (which is a string in our standardized format) let senderName = ''; let senderEmail = ''; - // Handle 'from' field which might be a string or an array of EmailAddress objects - if (standardizedEmail.from) { - if (Array.isArray(standardizedEmail.from)) { - // If it's an array of EmailAddress objects - if (standardizedEmail.from.length > 0) { - const sender = standardizedEmail.from[0]; - if (typeof sender === 'object') { - senderName = sender.name || sender.address || ''; - senderEmail = sender.address || ''; - } else { - // Handle case where array contains strings - senderName = String(sender); - senderEmail = String(sender); - } - } - } else if (typeof standardizedEmail.from === 'string') { - // If it's a string, try to extract name and email with regex - const senderInfo = standardizedEmail.from.match(/^(?:"?([^"]*)"?\s)?]+@[^\s>]+)>?$/); - senderName = senderInfo ? senderInfo[1] || senderInfo[2] : standardizedEmail.from; - senderEmail = senderInfo ? senderInfo[2] : standardizedEmail.from; - } + if (email.from) { + // If it's a string, try to extract name and email with regex + const senderInfo = email.from.match(/^(?:"?([^"]*)"?\s)?]+@[^\s>]+)>?$/); + senderName = senderInfo ? senderInfo[1] || senderInfo[2] : email.from; + senderEmail = senderInfo ? senderInfo[2] : email.from; } // Check for attachments - const hasAttachments = standardizedEmail.attachments && standardizedEmail.attachments.length > 0; + const hasAttachments = email.attachments && email.attachments.length > 0; + + // Get sender initials for avatar + const getSenderInitials = (name: string) => { + if (!name) return ''; + return name + .split(" ") + .map((n) => n?.[0] || '') + .join("") + .toUpperCase() + .slice(0, 2); + }; return ( {/* Email header */}
-

{standardizedEmail.subject}

+

{email.subject}

@@ -184,16 +82,16 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
{senderName}
- {formatEmailDate(standardizedEmail.date)} + {formatEmailDate(email.date)}
- To: {standardizedEmail.to} + To: {email.to}
- {standardizedEmail.cc && ( + {email.cc && (
- Cc: {standardizedEmail.cc} + Cc: {email.cc}
)}
@@ -228,11 +126,11 @@ export default function EmailPreview({ email, loading = false, onReply }: EmailP
{/* Attachments list */} - {hasAttachments && standardizedEmail.attachments && ( + {hasAttachments && email.attachments && (
-

Attachments ({standardizedEmail.attachments.length})

+

Attachments ({email.attachments.length})

- {standardizedEmail.attachments.map((attachment, index) => ( + {email.attachments.map((attachment, index) => (
- {/* Render the email content using the new standardized component */}
- {/* Always show debugging info in development mode */} + {/* Debugging info - simplified */} {process.env.NODE_ENV === 'development' && ( -
+
Email Debug Info
-

Email ID: {standardizedEmail.id}

-

Content Type: {standardizedEmail.content.isHtml ? 'HTML' : 'Plain Text'}

-

Text Direction: {standardizedEmail.content.direction || 'ltr'}

+

Email ID: {email.id}

+

Content Type: {email.content.isHtml ? 'HTML' : 'Plain Text'}

+

Text Direction: {email.content.direction}

Content Size: - HTML: {standardizedEmail.content.html?.length || 0} chars, - Text: {standardizedEmail.content.text?.length || 0} chars + HTML: {email.content.html?.length || 0} chars, + Text: {email.content.text?.length || 0} chars

-

Content Structure: {JSON.stringify(standardizedEmail.content, null, 2)}

-
-

Original Email Type: {typeof email}

-

Original Content Type: {typeof email.content}

- {email && typeof email.content === 'object' && ( -

Original Content Keys: {Object.keys(email.content).join(', ')}

- )} - {email && email.html && ( -

Has HTML property: {email.html.length} chars

- )} - {email && email.text && ( -

Has Text property: {email.text.length} chars

- )}
)} diff --git a/hooks/use-email-fetch.ts b/hooks/use-email-fetch.ts index d078b94c..684be855 100644 --- a/hooks/use-email-fetch.ts +++ b/hooks/use-email-fetch.ts @@ -1,14 +1,15 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { useToast } from './use-toast'; +import { EmailMessage, EmailContent } from '@/types/email'; interface EmailFetchState { - email: any | null; + email: EmailMessage | null; loading: boolean; error: string | null; } interface UseEmailFetchProps { - onEmailLoaded?: (email: any) => void; + onEmailLoaded?: (email: EmailMessage) => void; onError?: (error: string) => void; } @@ -49,20 +50,15 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { return; } - // CRITICAL FIX: Always abort any previous request when fetching a new email - // This prevents race conditions when switching accounts or folders + // Abort any previous request if (abortControllerRef.current) { - console.log(`useEmailFetch: Aborting previous request to fetch email ${emailId} from account ${accountId}`); abortControllerRef.current.abort(); } - // Create a new abort controller for this request abortControllerRef.current = new AbortController(); - setState(prev => ({ ...prev, loading: true, error: null })); try { - console.log('useEmailFetch: Fetching email with params:', { emailId, accountId, folder }); const response = await fetch( `/api/courrier/${emailId}?accountId=${encodeURIComponent(accountId)}&folder=${encodeURIComponent(folder)}`, { @@ -75,24 +71,24 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { } const data = await response.json(); - console.log('useEmailFetch: Raw API response:', JSON.stringify(data, null, 2)); - // Use the data directly as it already has the standardized format from the server - const transformedEmail = data; - - console.log('useEmailFetch: Email from API ready to use:', JSON.stringify({ - id: transformedEmail.id, - subject: transformedEmail.subject, - contentType: typeof transformedEmail.content, - hasIsHtml: transformedEmail.content && 'isHtml' in transformedEmail.content, - hasDirection: transformedEmail.content && 'direction' in transformedEmail.content - }, null, 2)); + // Create properly formatted email with all required fields + const transformedEmail: EmailMessage = { + ...data, + // Ensure content is properly formatted + content: { + text: data.content?.text || '', + html: data.content?.html || undefined, + isHtml: data.content?.html ? true : false, + direction: data.content?.direction || 'ltr' + } + }; setState({ email: transformedEmail, loading: false, error: null }); onEmailLoaded?.(transformedEmail); // Mark as read if not already - if (!transformedEmail.flags?.seen) { + if (!transformedEmail.flags?.includes('seen')) { try { await fetch(`/api/courrier/${emailId}/mark-read`, { method: 'POST', @@ -109,12 +105,11 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = { return; } - console.error('useEmailFetch: Error fetching email:', err); + console.error('Error fetching email:', err); const errorMessage = err instanceof Error ? err.message : 'Failed to load email'; setState(prev => ({ ...prev, loading: false, error: errorMessage })); onError?.(errorMessage); - // Show toast for user feedback toast({ title: 'Error', description: errorMessage,