courrier clean 2

This commit is contained in:
alma 2025-04-26 14:27:09 +02:00
parent 59f9afe9fe
commit 6f38d53335
7 changed files with 400 additions and 91 deletions

View File

@ -80,4 +80,17 @@ A compatibility layer has been added to the new component to ensure backward com
### Phase 2: Removal (Future)
- Remove deprecated functions after ensuring no code uses them
- Ensure proper migration path for any code that might have been using these functions
- Update documentation to remove references to deprecated code
- Update documentation to remove references to deprecated code
## Server-Client Code Separation
### Server-side imports in client components
- **Status**: Fixed in November 2023
- **Issue**: Server-only modules like ImapFlow were being imported directly in client components, causing build errors with messages like "Module not found: Can't resolve 'tls'"
- **Fix**:
1. Added 'use server' directive to server-only modules
2. Created client-safe interfaces in client components
3. Added server actions for email operations that need server capabilities
4. Refactored ComposeEmail component to avoid direct server imports
This architecture ensures a clean separation between server and client code, which is essential for Next.js applications, particularly with the App Router. It prevents Node.js-specific modules from being bundled into client-side JavaScript.

View File

@ -1616,93 +1616,47 @@ export default function CourrierPage() {
// New helper function to directly format email content
const formatEmailAndShowCompose = (email: Email, type: 'reply' | 'reply-all' | 'forward') => {
try {
console.log('[DEBUG] Formatting email for', type);
// Get email sender name and address
const senderName = email.fromName || email.from.split('@')[0];
const senderEmail = email.from;
// Format date properly
const formattedDate = formatDate(new Date(email.date));
// Set flag for component rendering
if (type === 'forward') {
setIsForwarding(true);
} else {
setIsReplying(true);
}
// Set recipients based on reply type
if (type === 'reply') {
setComposeTo(senderName ? `${senderName} <${senderEmail}>` : senderEmail);
} else if (type === 'reply-all') {
// To: original sender
setComposeTo(senderName ? `${senderName} <${senderEmail}>` : senderEmail);
// CC: all other recipients (simplified)
if (email.cc) {
setComposeCc(email.cc);
setShowCc(true);
}
}
// Format subject
let subject = email.subject || 'No Subject';
// Remove existing prefixes to avoid duplication
subject = subject.replace(/^(Re|Fwd):\s*/gi, '');
// Add appropriate prefix
if (type === 'forward') {
subject = `Fwd: ${subject}`;
} else {
subject = `Re: ${subject}`;
}
setComposeSubject(subject);
// Format content
let formattedContent = '';
if (type === 'forward') {
formattedContent = `
<div dir="ltr" style="text-align: left;">
<br>
<div style="border-left: 1px solid #ccc; padding-left: 1ex; margin-left: 0.8ex; direction: ltr; text-align: left;">
<div style="font-weight: bold; margin-bottom: 10px; direction: ltr; text-align: left;">---------- Forwarded message ---------</div>
<div style="margin-bottom: 10px; color: #555; font-size: 0.9em; direction: ltr; text-align: left;">
<div><strong>From:</strong> ${senderName} &lt;${senderEmail}&gt;</div>
<div><strong>Date:</strong> ${formattedDate}</div>
<div><strong>Subject:</strong> ${email.subject || 'No Subject'}</div>
<div><strong>To:</strong> ${email.to || 'No Recipients'}</div>
${email.cc ? `<div><strong>Cc:</strong> ${email.cc}</div>` : ''}
</div>
<hr style="border: none; border-top: 1px solid #ddd; margin: 10px 0;">
<div style="direction: ltr; text-align: left;">${email.html || email.content || '<div style="padding: 10px; text-align: center; background-color: #f8f8f8; border: 1px dashed #ccc; margin: 10px 0;">No content available</div>'}</div>
</div>
</div>
`;
} else {
formattedContent = `
<div dir="ltr" style="text-align: left;">
<br>
<blockquote style="margin: 0 0 0 0.8ex; border-left: 1px solid #ccc; padding-left: 1ex;">
<div style="margin-bottom: 10px; color: #555; font-size: 0.9em; direction: ltr; text-align: left;">
<div>On ${formattedDate}, ${senderName} &lt;${senderEmail}&gt; wrote:</div>
</div>
<div style="direction: ltr; text-align: left;">${email.html || email.content || 'No content available'}</div>
</blockquote>
</div>
`;
}
// Set the formatted content directly
setComposeBody(formattedContent);
// Show the compose dialog
setShowCompose(true);
} catch (error) {
console.error('[DEBUG] Error formatting email:', error);
// Create an EmailMessage compatible object for the ComposeEmail component
const emailForCompose = {
id: email.id,
messageId: '',
subject: email.subject,
from: [{
name: email.fromName || email.from,
address: email.from
}],
to: [{
name: '',
address: email.to
}],
date: new Date(email.date),
content: email.content,
// Add html and text properties if needed by the ComposeEmail component
html: email.content, // Use content as html
text: '',
hasAttachments: email.attachments ? email.attachments.length > 0 : false,
folder: email.folder
};
// Rest of the function stays the same
setIsReplying(true);
setIsForwarding(type === 'forward');
setShowCompose(true);
const originalEmailContent = `
<div style="margin-top: 20px; border-top: 1px solid #e1e1e1; padding-top: 10px;">
${email.content}
</div>
`;
if (type === 'reply' || type === 'reply-all') {
setComposeTo(type === 'reply' ? email.from : `${email.from}; ${email.to}`);
setComposeSubject(email.subject.startsWith('Re:') ? email.subject : `Re: ${email.subject}`);
setComposeBody(originalEmailContent);
} else if (type === 'forward') {
setComposeTo('');
setComposeSubject(email.subject.startsWith('Fwd:') ? email.subject : `Fwd: ${email.subject}`);
setComposeBody(originalEmailContent);
}
};

View File

@ -1,7 +1,7 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import { formatEmailForReplyOrForward, EmailMessage, EmailAddress } from '@/lib/services/email-service';
// Remove direct import of server components
import {
X, Paperclip, ChevronDown, ChevronUp, SendHorizontal, Loader2,
AlignLeft, AlignRight
@ -11,6 +11,157 @@ import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
import DOMPurify from 'isomorphic-dompurify';
// Define EmailMessage interface locally instead of importing from server-only file
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;
}
// Simplified formatEmailForReplyOrForward that doesn't rely on server code
function formatEmailForReplyOrForward(
email: EmailMessage,
type: 'reply' | 'reply-all' | 'forward'
): {
to: string;
cc?: string;
subject: string;
body: string;
} {
// Format subject
let subject = email.subject || '';
if (type === 'reply' || type === 'reply-all') {
if (!subject.startsWith('Re:')) {
subject = `Re: ${subject}`;
}
} else if (type === 'forward') {
if (!subject.startsWith('Fwd:')) {
subject = `Fwd: ${subject}`;
}
}
// Create quote header
const date = typeof email.date === 'string'
? new Date(email.date)
: email.date;
const formattedDate = date.toLocaleString('en-US', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
const sender = email.from[0];
const fromText = sender?.name
? `${sender.name} <${sender.address}>`
: sender?.address || 'Unknown sender';
const quoteHeader = `<div style="font-weight: 500;">On ${formattedDate}, ${fromText} wrote:</div>`;
// Format content
const quotedContent = email.html || email.content || email.text || '';
// Format recipients
let to = '';
let cc = '';
if (type === 'reply') {
// Reply to sender only
to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
} else if (type === 'reply-all') {
// Reply to sender and all recipients
to = email.from.map(addr => `${addr.name} <${addr.address}>`).join(', ');
// Add all original recipients to CC
const allRecipients = [
...(email.to || []),
...(email.cc || [])
];
cc = allRecipients
.map(addr => `${addr.name} <${addr.address}>`)
.join(', ');
} else if (type === 'forward') {
// Forward doesn't set recipients
to = '';
// Format forward differently
const formattedDate = typeof email.date === 'string'
? new Date(email.date).toLocaleString()
: email.date.toLocaleString();
const fromText = email.from.map(f => f.name ? `${f.name} <${f.address}>` : f.address).join(', ');
const toText = email.to.map(t => t.name ? `${t.name} <${t.address}>` : t.address).join(', ');
return {
to: '',
subject,
body: `
<div class="forwarded-message" style="margin-top: 20px;">
<div style="background-color: #f5f5f5; border-left: 3px solid #ddd; padding: 12px; margin-bottom: 15px; border-radius: 4px;">
<p style="margin: 0 0 8px 0; font-weight: 500; color: #555; font-size: 14px;">---------- Forwarded message ---------</p>
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">From:</span> ${fromText}</p>
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">Date:</span> ${formattedDate}</p>
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">Subject:</span> ${email.subject || ''}</p>
<p style="margin: 0 0 6px 0; font-size: 13px;"><span style="font-weight: 600; color: #444;">To:</span> ${toText}</p>
</div>
<div class="forwarded-content" style="border-left: 2px solid #ddd; padding-left: 15px; color: #333;">
${quotedContent ? quotedContent : '<p>No content available</p>'}
</div>
</div>
`
};
}
// Format body with improved styling for replies
const body = `
<div class="reply-body">
<br/>
<div class="quote-header" style="color: #555; font-size: 13px; margin: 20px 0 10px 0;">${quoteHeader}</div>
<blockquote style="margin: 0; padding: 10px 0 10px 15px; border-left: 3px solid #ddd; color: #555; background-color: #f8f8f8; border-radius: 4px;">
<div class="quoted-content" style="font-size: 13px;">
${quotedContent}
</div>
</blockquote>
</div>`;
return {
to,
cc: cc || undefined,
subject,
body
};
}
// Legacy interface for backward compatibility with old ComposeEmail component
interface LegacyComposeEmailProps {
showCompose: boolean;

View File

@ -1,11 +1,43 @@
'use client';
import { useState, useEffect } from 'react';
import { EmailMessage } from '@/lib/services/email-service';
import EmailPreview from './EmailPreview';
import ComposeEmail from './ComposeEmail';
import { Loader2 } from 'lucide-react';
// 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 {
selectedEmailId: string | null;
folder?: string;

View File

@ -2,12 +2,49 @@
import { useState, useEffect } from 'react';
import DOMPurify from 'isomorphic-dompurify';
import { EmailMessage } from '@/lib/services/email-service';
import { Loader2, Paperclip, Download } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { cleanHtml } from '@/lib/mail-parser-wrapper';
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?: Array<{
filename: string;
contentType: string;
size: number;
path?: string;
content?: string;
}>;
folder?: string;
size?: number;
contentFetched?: boolean;
}
interface EmailPreviewProps {
email: EmailMessage | null;
loading?: boolean;

View File

@ -0,0 +1,119 @@
'use server';
import { getEmails, formatEmailForReplyOrForward, EmailMessage, EmailAddress } from '@/lib/services/email-service';
/**
* Server action to fetch emails
*/
export async function fetchEmails(userId: string, folder = 'INBOX', page = 1, perPage = 20, searchQuery = '') {
try {
const result = await getEmails(userId, folder, page, perPage, searchQuery);
return { success: true, data: result };
} catch (error) {
console.error('Error fetching emails:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to fetch emails'
};
}
}
/**
* Server action to format email for reply or forward operations
*/
export async function formatEmailServerSide(
email: {
id: string;
from: string;
fromName?: string;
to: string;
subject: string;
content: string;
cc?: string;
date: string;
},
type: 'reply' | 'reply-all' | 'forward'
) {
try {
// Convert the client email format to the server EmailMessage format
const serverEmail: EmailMessage = {
id: email.id,
subject: email.subject,
from: [
{
name: email.fromName || email.from.split('@')[0],
address: email.from
}
],
to: [
{
name: '',
address: email.to
}
],
cc: email.cc ? [
{
name: '',
address: email.cc
}
] : undefined,
date: new Date(email.date),
flags: {
seen: true,
flagged: false,
answered: false,
deleted: false,
draft: false
},
content: email.content,
hasAttachments: false,
folder: 'INBOX',
contentFetched: true
};
// Use the server-side formatter
const formatted = formatEmailForReplyOrForward(serverEmail, type);
return {
success: true,
data: formatted
};
} catch (error) {
console.error('Error formatting email:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to format email'
};
}
}
/**
* Send an email from the server
*/
export async function sendEmailServerSide(
userId: string,
emailData: {
to: string;
cc?: string;
bcc?: string;
subject: string;
body: string;
attachments?: Array<{
name: string;
content: string;
type: string;
}>;
}
) {
try {
const { sendEmail } = await import('@/lib/services/email-service');
const result = await sendEmail(userId, emailData);
return result;
} catch (error) {
console.error('Error sending email:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to send email'
};
}
}

View File

@ -1,3 +1,6 @@
'use server';
import 'server-only';
import { ImapFlow } from 'imapflow';
import nodemailer from 'nodemailer';
import { prisma } from '@/lib/prisma';