panel 2 courier api restore
This commit is contained in:
parent
3aae79e76b
commit
522683b599
@ -11,6 +11,7 @@ import { ImapFlow } from 'imapflow';
|
|||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
|
||||||
import { prisma } from '@/lib/prisma';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
import { simpleParser } from 'mailparser';
|
||||||
|
|
||||||
// Simple in-memory cache for email content
|
// Simple in-memory cache for email content
|
||||||
const emailContentCache = new Map<string, any>();
|
const emailContentCache = new Map<string, any>();
|
||||||
@ -20,108 +21,207 @@ export async function GET(
|
|||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { id } = await Promise.resolve(params);
|
|
||||||
|
|
||||||
// Authentication check
|
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
if (!session?.user?.id) {
|
if (!session || !session.user?.id) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: "Not authenticated" },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache first
|
const { id } = params;
|
||||||
const cacheKey = `email:${session.user.id}:${id}`;
|
if (!id) {
|
||||||
if (emailContentCache.has(cacheKey)) {
|
return NextResponse.json(
|
||||||
return NextResponse.json(emailContentCache.get(cacheKey));
|
{ error: "Missing email ID" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get credentials from database
|
// Get mail credentials
|
||||||
const credentials = await prisma.mailCredentials.findUnique({
|
const credentials = await prisma.mailCredentials.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId: session.user.id
|
userId: session.user.id,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'No mail credentials found. Please configure your email account.' },
|
{ error: "No mail credentials found" },
|
||||||
{ status: 401 }
|
{ status: 404 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const folder = searchParams.get("folder") || "INBOX";
|
||||||
|
|
||||||
// Create IMAP client
|
// Create IMAP client
|
||||||
const client = new ImapFlow({
|
let imapClient: any = null;
|
||||||
host: credentials.host,
|
|
||||||
port: credentials.port,
|
|
||||||
secure: true,
|
|
||||||
auth: {
|
|
||||||
user: credentials.email,
|
|
||||||
pass: credentials.password,
|
|
||||||
},
|
|
||||||
logger: false,
|
|
||||||
emitLogs: false,
|
|
||||||
tls: {
|
|
||||||
rejectUnauthorized: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.connect();
|
imapClient = new ImapFlow({
|
||||||
|
host: credentials.host,
|
||||||
// Open INBOX
|
port: credentials.port,
|
||||||
await client.mailboxOpen('INBOX');
|
secure: true,
|
||||||
|
auth: {
|
||||||
// Fetch the email with UID search
|
user: credentials.email,
|
||||||
const message = await client.fetchOne(id, {
|
pass: credentials.password,
|
||||||
uid: true,
|
},
|
||||||
source: true,
|
logger: false,
|
||||||
envelope: true,
|
|
||||||
bodyStructure: true,
|
|
||||||
flags: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await imapClient.connect();
|
||||||
|
console.log(`Connected to IMAP server to fetch full email ${id}`);
|
||||||
|
|
||||||
|
// Select mailbox
|
||||||
|
const mailboxData = await imapClient.mailboxOpen(folder);
|
||||||
|
console.log(`Opened mailbox ${folder} to fetch email ${id}`);
|
||||||
|
|
||||||
|
// Fetch the complete email with its source
|
||||||
|
const message = await imapClient.fetchOne(Number(id), {
|
||||||
|
source: true,
|
||||||
|
envelope: true
|
||||||
|
});
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Email not found' },
|
{ error: "Email not found" },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the email content
|
const { source, envelope } = message;
|
||||||
const emailContent = {
|
|
||||||
id: message.uid.toString(),
|
// Parse the full email content
|
||||||
from: message.envelope.from?.[0]?.address || '',
|
const parsedEmail = await simpleParser(source.toString());
|
||||||
fromName: message.envelope.from?.[0]?.name ||
|
|
||||||
message.envelope.from?.[0]?.address?.split('@')[0] || '',
|
// Return only the content
|
||||||
to: message.envelope.to?.map((addr: any) => addr.address).join(', ') || '',
|
return NextResponse.json({
|
||||||
subject: message.envelope.subject || '(No subject)',
|
id,
|
||||||
date: message.envelope.date?.toISOString() || new Date().toISOString(),
|
subject: envelope.subject,
|
||||||
content: message.source?.toString() || '',
|
content: parsedEmail.html || parsedEmail.textAsHtml || parsedEmail.text || '',
|
||||||
read: message.flags.has('\\Seen'),
|
contentFetched: true
|
||||||
starred: message.flags.has('\\Flagged'),
|
});
|
||||||
flags: Array.from(message.flags),
|
} catch (error: any) {
|
||||||
hasAttachments: message.bodyStructure?.type === 'multipart'
|
console.error("Error fetching email content:", error);
|
||||||
};
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to fetch email content", message: error.message },
|
||||||
// Cache the email content (with a 15-minute expiry)
|
{ status: 500 }
|
||||||
emailContentCache.set(cacheKey, emailContent);
|
);
|
||||||
setTimeout(() => emailContentCache.delete(cacheKey), 15 * 60 * 1000);
|
|
||||||
|
|
||||||
// Return the email content
|
|
||||||
return NextResponse.json(emailContent);
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
// Close the mailbox and connection
|
||||||
await client.logout();
|
if (imapClient) {
|
||||||
} catch (e) {
|
try {
|
||||||
console.error('Error during IMAP logout:', e);
|
await imapClient.mailboxClose();
|
||||||
|
await imapClient.logout();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error closing IMAP connection:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Error fetching email:', error);
|
console.error("Error in GET:", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to fetch email content' },
|
{ error: "Internal server error", message: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a route to mark email as read
|
||||||
|
export async function POST(
|
||||||
|
request: Request,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session || !session.user?.id) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Not authenticated" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = params;
|
||||||
|
if (!id) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Missing email ID" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { action } = await request.json();
|
||||||
|
|
||||||
|
if (action !== 'mark-read' && action !== 'mark-unread') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Invalid action. Supported actions: mark-read, mark-unread" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get mail credentials
|
||||||
|
const credentials = await prisma.mailCredentials.findUnique({
|
||||||
|
where: {
|
||||||
|
userId: session.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!credentials) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "No mail credentials found" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const folder = searchParams.get("folder") || "INBOX";
|
||||||
|
|
||||||
|
// Create IMAP client
|
||||||
|
let imapClient: any = null;
|
||||||
|
try {
|
||||||
|
imapClient = new ImapFlow({
|
||||||
|
host: credentials.host,
|
||||||
|
port: credentials.port,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: credentials.email,
|
||||||
|
pass: credentials.password,
|
||||||
|
},
|
||||||
|
logger: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await imapClient.connect();
|
||||||
|
|
||||||
|
// Select mailbox
|
||||||
|
await imapClient.mailboxOpen(folder);
|
||||||
|
|
||||||
|
// Set flag based on action
|
||||||
|
if (action === 'mark-read') {
|
||||||
|
await imapClient.messageFlagsAdd(Number(id), ['\\Seen']);
|
||||||
|
} else {
|
||||||
|
await imapClient.messageFlagsRemove(Number(id), ['\\Seen']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Error ${action === 'mark-read' ? 'marking email as read' : 'marking email as unread'}:`, error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Failed to ${action === 'mark-read' ? 'mark email as read' : 'mark email as unread'}`, message: error.message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// Close the mailbox and connection
|
||||||
|
if (imapClient) {
|
||||||
|
try {
|
||||||
|
await imapClient.mailboxClose();
|
||||||
|
await imapClient.logout();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error closing IMAP connection:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error in POST:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Internal server error", message: error.message },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -253,19 +253,34 @@ export async function GET(request: Request) {
|
|||||||
bodyStructure: true,
|
bodyStructure: true,
|
||||||
internalDate: true,
|
internalDate: true,
|
||||||
size: true,
|
size: true,
|
||||||
source: true // Include full message source to get content
|
// Only fetch a preview of the body initially for faster loading
|
||||||
|
bodyParts: [
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
limit: 5000, // Limit to first 5KB of text
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!message) continue;
|
if (!message) continue;
|
||||||
|
|
||||||
const { envelope, flags, bodyStructure, internalDate, size, source } = message;
|
const { envelope, flags, bodyStructure, internalDate, size, bodyParts } = message;
|
||||||
|
|
||||||
// Extract content from the message source
|
// Extract content from the body parts for a preview
|
||||||
let content = '';
|
let preview = '';
|
||||||
if (source) {
|
if (bodyParts && bodyParts.length > 0) {
|
||||||
const parsedEmail = await simpleParser(source.toString());
|
const textPart = bodyParts.find((part: any) => part.type === 'text/plain');
|
||||||
// Get HTML or text content
|
const htmlPart = bodyParts.find((part: any) => part.type === 'text/html');
|
||||||
content = parsedEmail.html || parsedEmail.text || '';
|
// Prefer text for preview as it's smaller and faster to process
|
||||||
|
const content = textPart?.content || htmlPart?.content || '';
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
preview = content.substring(0, 150) + '...';
|
||||||
|
} else if (Buffer.isBuffer(content)) {
|
||||||
|
preview = content.toString('utf-8', 0, 150) + '...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert attachments to our format
|
// Convert attachments to our format
|
||||||
@ -335,7 +350,10 @@ export async function GET(request: Request) {
|
|||||||
hasAttachments: attachments.length > 0,
|
hasAttachments: attachments.length > 0,
|
||||||
attachments,
|
attachments,
|
||||||
size,
|
size,
|
||||||
content // Include content directly in email object
|
// Just include the preview instead of the full content initially
|
||||||
|
preview,
|
||||||
|
// Store the fetched state to know we only have preview
|
||||||
|
contentFetched: false
|
||||||
});
|
});
|
||||||
} catch (messageError) {
|
} catch (messageError) {
|
||||||
console.error(`Error fetching message ${id}:`, messageError);
|
console.error(`Error fetching message ${id}:`, messageError);
|
||||||
|
|||||||
@ -49,13 +49,16 @@ export interface Email {
|
|||||||
to: string;
|
to: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
content: string;
|
content: string;
|
||||||
body?: string; // For backward compatibility
|
preview?: string; // Preview content for list view
|
||||||
|
body?: string; // For backward compatibility
|
||||||
date: string;
|
date: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
starred: boolean;
|
starred: boolean;
|
||||||
attachments?: { name: string; url: string }[];
|
attachments?: { name: string; url: string }[];
|
||||||
folder: string;
|
folder: string;
|
||||||
cc?: string;
|
cc?: string;
|
||||||
|
bcc?: string;
|
||||||
|
contentFetched?: boolean; // Track if full content has been fetched
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Attachment {
|
interface Attachment {
|
||||||
@ -115,22 +118,42 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
setDebugInfo(null);
|
setDebugInfo(null);
|
||||||
try {
|
try {
|
||||||
console.log('Loading content for email:', email.id);
|
console.log('Loading content for email:', email.id);
|
||||||
console.log('Email content length:', email.content?.length || 0);
|
|
||||||
|
|
||||||
// Check if content is available in either content property or body property (for backward compatibility)
|
// Check if we need to fetch full content
|
||||||
const emailContent = email.content || email.body || '';
|
if (!email.content || email.content.length === 0) {
|
||||||
|
console.log('Fetching full content for email:', email.id);
|
||||||
if (!emailContent) {
|
|
||||||
console.log('No content available for email:', email.id);
|
const response = await fetch(`/api/courrier/${email.id}?folder=${encodeURIComponent(email.folder || 'INBOX')}`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch email content: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullContent = await response.json();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setContent(<div className="text-gray-500">No content available</div>);
|
// Update the email content with the fetched full content
|
||||||
setDebugInfo('No content available for this email');
|
email.content = fullContent.content;
|
||||||
|
|
||||||
|
// Render the content
|
||||||
|
const sanitizedHtml = DOMPurify.sanitize(fullContent.content);
|
||||||
|
setContent(
|
||||||
|
<div
|
||||||
|
className="email-content prose prose-sm max-w-none dark:prose-invert"
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
setDebugInfo('Rendered fetched HTML content');
|
||||||
|
setError(null);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedEmail = emailContent.trim();
|
// Use existing content if available
|
||||||
|
console.log('Using existing content for email');
|
||||||
|
|
||||||
|
const formattedEmail = email.content.trim();
|
||||||
if (!formattedEmail) {
|
if (!formattedEmail) {
|
||||||
console.log('Empty content for email:', email.id);
|
console.log('Empty content for email:', email.id);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -141,16 +164,22 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Parsing email content:', formattedEmail.substring(0, 100) + '...');
|
// Check if content is already HTML
|
||||||
const parsedEmail = await decodeEmail(formattedEmail);
|
if (formattedEmail.startsWith('<') && formattedEmail.endsWith('>')) {
|
||||||
console.log('Parsed email result:', {
|
// Content is likely HTML, sanitize and display directly
|
||||||
hasHtml: !!parsedEmail.html,
|
const sanitizedHtml = DOMPurify.sanitize(formattedEmail);
|
||||||
hasText: !!parsedEmail.text,
|
setContent(
|
||||||
htmlLength: parsedEmail.html?.length || 0,
|
<div
|
||||||
textLength: parsedEmail.text?.length || 0
|
className="email-content prose prose-sm max-w-none dark:prose-invert"
|
||||||
});
|
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
||||||
|
/>
|
||||||
if (mounted) {
|
);
|
||||||
|
setDebugInfo('Rendered existing HTML content');
|
||||||
|
} else {
|
||||||
|
// Use mailparser for more complex formats
|
||||||
|
console.log('Parsing email content');
|
||||||
|
const parsedEmail = await decodeEmail(formattedEmail);
|
||||||
|
|
||||||
if (parsedEmail.html) {
|
if (parsedEmail.html) {
|
||||||
const sanitizedHtml = DOMPurify.sanitize(parsedEmail.html);
|
const sanitizedHtml = DOMPurify.sanitize(parsedEmail.html);
|
||||||
setContent(
|
setContent(
|
||||||
@ -159,27 +188,30 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
setDebugInfo('Rendered HTML content');
|
setDebugInfo('Rendered HTML content from parser');
|
||||||
} else if (parsedEmail.text) {
|
} else if (parsedEmail.text) {
|
||||||
setContent(
|
setContent(
|
||||||
<div className="email-content whitespace-pre-wrap">
|
<div className="email-content whitespace-pre-wrap">
|
||||||
{parsedEmail.text}
|
{parsedEmail.text}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
setDebugInfo('Rendered text content');
|
setDebugInfo('Rendered text content from parser');
|
||||||
} else {
|
} else {
|
||||||
setContent(<div className="text-gray-500">No displayable content available</div>);
|
setContent(<div className="text-gray-500">No displayable content available</div>);
|
||||||
setDebugInfo('No HTML or text content in parsed email');
|
setDebugInfo('No HTML or text content in parsed email');
|
||||||
}
|
}
|
||||||
setError(null);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error rendering email content:', err);
|
console.error('Error rendering email content:', err);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setError('Error rendering email content. Please try again.');
|
setError('Error rendering email content. Please try again.');
|
||||||
setDebugInfo(err instanceof Error ? err.message : 'Unknown error');
|
setDebugInfo(err instanceof Error ? err.message : 'Unknown error');
|
||||||
setContent(null);
|
setContent(null);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,12 +222,13 @@ function EmailContent({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.id, email?.content, email?.body]);
|
}, [email?.id, email?.content, email?.folder]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-8">
|
<div className="flex items-center justify-center py-8">
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
||||||
|
<span className="ml-3 text-gray-500">Loading email content...</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -370,23 +403,41 @@ function EmailPreview({ email }: { email: Email }) {
|
|||||||
let mounted = true;
|
let mounted = true;
|
||||||
|
|
||||||
async function loadPreview() {
|
async function loadPreview() {
|
||||||
if (!email?.content) {
|
if (!email) {
|
||||||
if (mounted) setPreview('No content available');
|
if (mounted) setPreview('No content available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If email already has a preview, use it directly
|
||||||
|
if (email.preview) {
|
||||||
|
if (mounted) setPreview(email.preview);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const decoded = await decodeEmail(email.content);
|
// If we have the content already, extract preview from it
|
||||||
if (mounted) {
|
if (email.content) {
|
||||||
if (decoded.text) {
|
const plainText = email.content.replace(/<[^>]*>/g, ' ').trim();
|
||||||
setPreview(decoded.text.substring(0, 150) + '...');
|
if (mounted) {
|
||||||
} else if (decoded.html) {
|
setPreview(plainText.substring(0, 150) + '...');
|
||||||
const cleanText = decoded.html.replace(/<[^>]*>/g, ' ').trim();
|
|
||||||
setPreview(cleanText.substring(0, 150) + '...');
|
|
||||||
} else {
|
|
||||||
setPreview('No preview available');
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to using parser for older emails
|
||||||
|
const decoded = await decodeEmail(email.content || '');
|
||||||
|
if (mounted) {
|
||||||
|
if (decoded.text) {
|
||||||
|
setPreview(decoded.text.substring(0, 150) + '...');
|
||||||
|
} else if (decoded.html) {
|
||||||
|
const cleanText = decoded.html.replace(/<[^>]*>/g, ' ').trim();
|
||||||
|
setPreview(cleanText.substring(0, 150) + '...');
|
||||||
|
} else {
|
||||||
|
setPreview('No preview available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
setError(null);
|
setError(null);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -405,7 +456,7 @@ function EmailPreview({ email }: { email: Email }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [email?.content]);
|
}, [email]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <span className="text-gray-400">Loading preview...</span>;
|
return <span className="text-gray-400">Loading preview...</span>;
|
||||||
@ -795,22 +846,29 @@ export default function CourrierPage() {
|
|||||||
// Set selected email from our existing data (which now includes full content)
|
// Set selected email from our existing data (which now includes full content)
|
||||||
setSelectedEmail(selectedEmail);
|
setSelectedEmail(selectedEmail);
|
||||||
|
|
||||||
// Try to mark as read in the background
|
// Try to mark as read in the background if not already read
|
||||||
try {
|
if (!selectedEmail.read) {
|
||||||
await fetch(`/api/courrier/${emailId}/mark-read`, {
|
try {
|
||||||
method: 'POST',
|
// Use the new API endpoint
|
||||||
});
|
await fetch(`/api/courrier/${emailId}`, {
|
||||||
|
method: 'POST',
|
||||||
// Update read status in the list
|
headers: {
|
||||||
setEmails(prevEmails =>
|
'Content-Type': 'application/json',
|
||||||
prevEmails.map(email =>
|
},
|
||||||
email.id === emailId
|
body: JSON.stringify({ action: 'mark-read' }),
|
||||||
? { ...email, read: true }
|
});
|
||||||
: email
|
|
||||||
)
|
// Update read status in the list
|
||||||
);
|
setEmails(prevEmails =>
|
||||||
} catch (error) {
|
prevEmails.map(email =>
|
||||||
console.error('Error marking email as read:', error);
|
email.id === emailId
|
||||||
|
? { ...email, read: true }
|
||||||
|
: email
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error marking email as read:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error selecting email:', error);
|
console.error('Error selecting email:', error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user