courrier clean 2
This commit is contained in:
parent
59f9afe9fe
commit
6f38d53335
@ -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.
|
||||
@ -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} <${senderEmail}></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} <${senderEmail}> 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
119
lib/actions/email-actions.ts
Normal file
119
lib/actions/email-actions.ts
Normal 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'
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import 'server-only';
|
||||
import { ImapFlow } from 'imapflow';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user