courrier preview

This commit is contained in:
alma 2025-05-01 19:55:05 +02:00
parent 3420c5be5c
commit 5a6036b0d5
10 changed files with 56 additions and 708 deletions

View File

@ -6,29 +6,29 @@ This document lists functions and files that have been deprecated and should not
### 1. `lib/email-formatter.ts` (REMOVED) ### 1. `lib/email-formatter.ts` (REMOVED)
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use `lib/utils/email-formatter.ts` instead - **Replacement**: Use `lib/utils/email-utils.ts` instead
- **Reason**: Consolidated email formatting to a single source of truth - **Reason**: Consolidated email formatting to a single source of truth
### 2. `lib/mail-parser-wrapper.ts` (REMOVED) ### 2. `lib/mail-parser-wrapper.ts` (REMOVED)
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use functions from `lib/utils/email-formatter.ts` instead - **Replacement**: Use functions from `lib/utils/email-utils.ts` instead
- **Reason**: Consolidated email formatting and sanitization to a single source of truth - **Reason**: Consolidated email formatting and sanitization to a single source of truth
### 3. `lib/email-parser.ts` (REMOVED) ### 3. `lib/email-parser.ts` (REMOVED)
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use `lib/server/email-parser.ts` for parsing and `lib/utils/email-formatter.ts` for sanitization - **Replacement**: Use `lib/server/email-parser.ts` for parsing and `lib/utils/email-utils.ts` for sanitization
- **Reason**: Consolidated email parsing and formatting to dedicated files - **Reason**: Consolidated email parsing and formatting to dedicated files
### 4. `lib/compose-mime-decoder.ts` (REMOVED) ### 4. `lib/compose-mime-decoder.ts` (REMOVED)
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use `decodeComposeContent` and `encodeComposeContent` functions from `lib/utils/email-formatter.ts` - **Replacement**: Use `decodeComposeContent` and `encodeComposeContent` functions from `lib/utils/email-utils.ts`
- **Reason**: Consolidated MIME handling into the centralized formatter - **Reason**: Consolidated MIME handling into the centralized formatter
## Deprecated Functions ## Deprecated Functions
### 1. `formatEmailForReplyOrForward` in `lib/services/email-service.ts` (REMOVED) ### 1. `formatEmailForReplyOrForward` in `lib/services/email-service.ts` (REMOVED)
- **Status**: Removed - **Status**: Removed
- **Replacement**: Use `formatEmailForReplyOrForward` from `lib/utils/email-formatter.ts` - **Replacement**: Use `formatEmailForReplyOrForward` from `lib/utils/email-utils.ts`
- **Reason**: Consolidated email formatting to a single source of truth - **Reason**: Consolidated email formatting to a single source of truth
### 2. `formatSubject` in `lib/services/email-service.ts` (REMOVED) ### 2. `formatSubject` in `lib/services/email-service.ts` (REMOVED)
@ -43,7 +43,7 @@ This document lists functions and files that have been deprecated and should not
## Centralized Email Formatting ## Centralized Email Formatting
All email formatting is now handled by the centralized formatter in `lib/utils/email-formatter.ts`. This file contains: All email formatting is now handled by the centralized formatter in `lib/utils/email-utils.ts`. This file contains:
1. `formatForwardedEmail`: Format emails for forwarding 1. `formatForwardedEmail`: Format emails for forwarding
2. `formatReplyEmail`: Format emails for replying or replying to all 2. `formatReplyEmail`: Format emails for replying or replying to all
@ -73,36 +73,36 @@ Use these functions for all email formatting needs.
### 4. `cleanHtml` (REMOVED) ### 4. `cleanHtml` (REMOVED)
- **Location**: Removed from `lib/server/email-parser.ts` - **Location**: Removed from `lib/server/email-parser.ts`
- **Reason**: HTML sanitization has been consolidated in `lib/utils/email-formatter.ts`. - **Reason**: HTML sanitization has been consolidated in `lib/utils/email-utils.ts`.
- **Replacement**: Use `sanitizeHtml` from `lib/utils/email-formatter.ts`. - **Replacement**: Use `sanitizeHtml` from `lib/utils/email-utils.ts`.
### 5. `processHtml` (REMOVED) ### 5. `processHtml` (REMOVED)
- **Location**: Removed from `app/api/parse-email/route.ts` - **Location**: Removed from `app/api/parse-email/route.ts`
- **Reason**: HTML processing has been consolidated in `lib/utils/email-formatter.ts`. - **Reason**: HTML processing has been consolidated in `lib/utils/email-utils.ts`.
- **Replacement**: Use `sanitizeHtml` from `lib/utils/email-formatter.ts`. - **Replacement**: Use `sanitizeHtml` from `lib/utils/email-utils.ts`.
## Deprecated API Routes ## Deprecated API Routes
### 1. `app/api/mail/[id]/route.ts` ### 1. `app/api/mail/[id]/route.ts` (REMOVED)
- **Status**: Deleted - **Status**: Removed
- **Replacement**: Use `app/api/courrier/[id]/route.ts` instead. - **Replacement**: Use `app/api/courrier/[id]/route.ts` instead.
### 2. `app/api/mail/route.ts` ### 2. `app/api/mail/route.ts` (REMOVED)
- **Status**: Deleted - **Status**: Removed
- **Replacement**: Use `app/api/courrier/route.ts` instead. - **Replacement**: Use `app/api/courrier/route.ts` instead.
### 3. `app/api/mail/send/route.ts` ### 3. `app/api/mail/send/route.ts` (REMOVED)
- **Status**: Deleted - **Status**: Removed
- **Replacement**: Use `app/api/courrier/send/route.ts` instead. - **Replacement**: Use `app/api/courrier/send/route.ts` instead.
## Deprecated Components ## Deprecated Components
### ComposeEmail (components/ComposeEmail.tsx) ### ComposeEmail (components/ComposeEmail.tsx) (REMOVED)
**Status:** Deprecated since November 2023 **Status:** Removed
**Replacement:** Use `components/email/ComposeEmail.tsx` instead **Replacement:** Use `components/email/ComposeEmail.tsx` instead
This component has been deprecated in favor of the more modular and better structured version in the email directory. The newer version has the following improvements: This component has been removed in favor of the more modular and better structured version in the email directory. The newer version has the following improvements:
- Better separation between user message and quoted content in replies/forwards - Better separation between user message and quoted content in replies/forwards
- Improved styling and visual hierarchy - Improved styling and visual hierarchy
@ -112,11 +112,6 @@ This component has been deprecated in favor of the more modular and better struc
A compatibility layer has been added to the new component to ensure backward compatibility with existing code that uses the old component. This allows for a smooth transition without breaking changes. A compatibility layer has been added to the new component to ensure backward compatibility with existing code that uses the old component. This allows for a smooth transition without breaking changes.
**Migration Plan:**
1. Update imports in files using the old component to import from `@/components/email/ComposeEmail`
2. Test to ensure functionality works with the new component
3. Delete the old component file once all usages have been migrated
## Migration Plan ## Migration Plan
### Phase 1: Deprecation (Completed) ### Phase 1: Deprecation (Completed)
@ -126,7 +121,7 @@ A compatibility layer has been added to the new component to ensure backward com
### Phase 2: Removal (Completed) ### Phase 2: Removal (Completed)
- Remove deprecated files: `lib/email-parser.ts` and `lib/mail-parser-wrapper.ts` - Remove deprecated files: `lib/email-parser.ts` and `lib/mail-parser-wrapper.ts`
- Consolidate all email formatting in `lib/utils/email-formatter.ts` - Consolidate all email formatting in `lib/utils/email-utils.ts`
- All email parsing now in `lib/server/email-parser.ts` - All email parsing now in `lib/server/email-parser.ts`
- Update documentation to point to the centralized utilities - Update documentation to point to the centralized utilities

View File

@ -13,70 +13,70 @@ The application handles email processing through a centralized workflow:
- API route: `/api/parse-email` provides a REST interface to the parser - API route: `/api/parse-email` provides a REST interface to the parser
3. **HTML Sanitization**: Email HTML content is sanitized and processed using: 3. **HTML Sanitization**: Email HTML content is sanitized and processed using:
- `sanitizeHtml` function in `lib/utils/email-formatter.ts` (centralized implementation) - `sanitizeHtml` function in `lib/utils/email-utils.ts` (centralized implementation)
- Text direction (RTL/LTR) is preserved automatically during sanitization - DOMPurify with specific configuration to handle email content safely
4. **Email Display**: Sanitized content is rendered in the UI with proper styling and security measures 4. **Email Display**: Sanitized content is rendered in the UI with proper styling and security measures
5. **Email Composition**: The `ComposeEmail` component handles email creation, replying, and forwarding 5. **Email Composition**: The `ComposeEmail` component handles email creation, replying, and forwarding
- Uses the centralized formatter functions to prepare content
- Email is sent through the `/api/courrier/send` endpoint - Email is sent through the `/api/courrier/send` endpoint
## Deprecated Functions ## Key Features
Several functions have been deprecated and removed in favor of centralized implementations: - **Email Fetching and Management**: Connect to IMAP servers and manage email fetching and caching logic
- **Email Composition**: Rich text editor with reply and forwarding capabilities
- Check the `DEPRECATED_FUNCTIONS.md` file for a complete list of deprecated functions and their replacements. - **Email Display**: Secure rendering of HTML emails
- **Attachment Handling**: View and download attachments
## Project Structure ## Project Structure
- `/app` - Main application routes and API endpoints The project follows a modular structure:
- `/components` - Reusable React components
- `/lib` - Utility functions and services - `/app` - Next.js App Router structure with routes and API endpoints
- `/components` - React components organized by domain
- `/lib` - Core library code:
- `/server` - Server-only code like email parsing
- `/services` - Domain-specific services, including email service - `/services` - Domain-specific services, including email service
- `/server` - Server-side utilities - `/reducers` - State management logic
- `/utils` - Utility functions including the centralized email formatter - `/utils` - Utility functions including the centralized email formatter
## Dependencies ## Technologies
- Next.js 15 - Next.js 14+ with App Router
- React 18 - React Server Components
- ImapFlow for IMAP interactions - TailwindCSS for styling
- Mailparser for email parsing - Mailparser for email parsing
- Prisma for database interactions - ImapFlow for email fetching
- Tailwind CSS for styling - DOMPurify for HTML sanitization
- Redis for caching
## Development ## State Management
```bash Email state is managed through React context and reducers, with server data fetched through React Server Components or client-side API calls as needed.
# Install dependencies
npm install
# Start the development server
npm run dev
# Build for production
npm run build
```
# Email Formatting # Email Formatting
## Centralized Email Formatter ## Centralized Email Formatter
All email formatting is now handled by a centralized formatter in `lib/utils/email-formatter.ts`. This ensures consistent handling of: All email formatting is now handled by a centralized formatter in `lib/utils/email-utils.ts`. This ensures consistent handling of:
- Text direction (RTL/LTR) - Reply and forward formatting
- HTML sanitization - HTML sanitization
- Content formatting for forwards and replies - RTL/LTR text direction
- MIME encoding and decoding for email composition - MIME encoding and decoding for email composition
### Key Functions Key functions include:
- `formatForwardedEmail`: Format emails for forwarding - `formatForwardedEmail`: Format emails for forwarding
- `formatReplyEmail`: Format emails for replying - `formatReplyEmail`: Format emails for replying
- `sanitizeHtml`: Sanitize HTML while preserving direction attributes - `sanitizeHtml`: Safely sanitize HTML email content
- `formatEmailForReplyOrForward`: Compatibility function for both - `formatEmailForReplyOrForward`: Compatibility function for both
- `decodeComposeContent`: Parse MIME content for email composition - `decodeComposeContent`: Parse MIME content for email composition
- `encodeComposeContent`: Create MIME-formatted content for sending emails - `encodeComposeContent`: Create MIME-formatted content for sending emails
This centralized approach prevents formatting inconsistencies and direction problems when dealing with emails in different languages. This centralized approach prevents formatting inconsistencies and direction problems when dealing with emails in different languages.
## Deprecated Functions
Several functions have been deprecated and removed in favor of centralized implementations:
- Check the `DEPRECATED_FUNCTIONS.md` file for a complete list of deprecated functions and their replacements.

View File

@ -1,99 +0,0 @@
import { NextResponse } from 'next/server';
import { ImapFlow } from 'imapflow';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { prisma } from '@/lib/prisma';
export async function GET(request: Request, { params }: { params: { id: string } }) {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Get credentials from database
const credentials = await prisma.mailCredentials.findUnique({
where: {
userId: session.user.id
}
});
if (!credentials) {
return NextResponse.json(
{ error: 'No mail credentials found. Please configure your email account.' },
{ status: 401 }
);
}
// Get the current folder from the request URL
const url = new URL(request.url);
const folder = url.searchParams.get('folder') || 'INBOX';
// Connect to IMAP server
const client = new ImapFlow({
host: credentials.host,
port: credentials.port,
secure: true,
auth: {
user: credentials.email,
pass: credentials.password,
},
logger: false,
emitLogs: false,
tls: {
rejectUnauthorized: false
}
});
try {
await client.connect();
await client.mailboxOpen(folder);
// Fetch the full email content
const message = await client.fetchOne(params.id, {
source: true,
envelope: true,
flags: true
});
if (!message) {
return NextResponse.json(
{ error: 'Email not found' },
{ status: 404 }
);
}
// Extract email content
const result = {
id: message.uid.toString(),
from: message.envelope.from[0].address,
subject: message.envelope.subject || '(No subject)',
date: message.envelope.date.toISOString(),
read: message.flags.has('\\Seen'),
starred: message.flags.has('\\Flagged'),
folder: folder,
body: message.source.toString(),
to: message.envelope.to?.map(addr => addr.address).join(', ') || '',
cc: message.envelope.cc?.map(addr => addr.address).join(', ') || '',
bcc: message.envelope.bcc?.map(addr => addr.address).join(', ') || '',
};
return NextResponse.json(result);
} finally {
try {
await client.logout();
} catch (e) {
console.error('Error during logout:', e);
}
}
} catch (error) {
console.error('Error fetching email:', error);
return NextResponse.json(
{ error: 'An unexpected error occurred' },
{ status: 500 }
);
}
}

View File

@ -1,172 +0,0 @@
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import Imap from 'imap';
interface StoredCredentials {
email: string;
password: string;
host: string;
port: number;
}
function getStoredCredentials(): StoredCredentials | null {
const cookieStore = cookies();
const credentialsCookie = cookieStore.get('imap_credentials');
if (!credentialsCookie?.value) {
return null;
}
try {
const credentials = JSON.parse(credentialsCookie.value);
if (!credentials.email || !credentials.password || !credentials.host || !credentials.port) {
return null;
}
return credentials;
} catch (error) {
return null;
}
}
export async function POST(request: Request) {
try {
const { emailIds, action } = await request.json();
if (!emailIds || !Array.isArray(emailIds) || !action) {
return NextResponse.json(
{ error: 'Invalid request parameters' },
{ status: 400 }
);
}
// Get the current folder from the request URL
const url = new URL(request.url);
const folder = url.searchParams.get('folder') || 'INBOX';
// Get stored credentials
const credentials = getStoredCredentials();
if (!credentials) {
return NextResponse.json(
{ error: 'No stored credentials found' },
{ status: 401 }
);
}
return new Promise((resolve) => {
const imap = new Imap({
user: credentials.email,
password: credentials.password,
host: credentials.host,
port: credentials.port,
tls: true,
tlsOptions: { rejectUnauthorized: false },
authTimeout: 30000,
connTimeout: 30000
});
const timeout = setTimeout(() => {
console.error('IMAP connection timeout');
imap.end();
resolve(NextResponse.json({ error: 'Connection timeout' }));
}, 30000);
imap.once('error', (err: Error) => {
console.error('IMAP error:', err);
clearTimeout(timeout);
resolve(NextResponse.json({ error: 'IMAP connection error' }));
});
imap.once('ready', () => {
imap.openBox(folder, false, (err, box) => {
if (err) {
console.error(`Error opening box ${folder}:`, err);
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({ error: `Failed to open folder ${folder}` }));
return;
}
// Convert string IDs to numbers
const numericIds = emailIds.map(id => parseInt(id, 10));
// Process each email
let processedCount = 0;
const totalEmails = numericIds.length;
const processNextEmail = (index: number) => {
if (index >= totalEmails) {
clearTimeout(timeout);
imap.end();
resolve(NextResponse.json({ success: true }));
return;
}
const id = numericIds[index];
const fetch = imap.fetch(id.toString(), {
bodies: '',
struct: true
});
fetch.on('message', (msg) => {
msg.once('attributes', (attrs) => {
const uid = attrs.uid;
if (!uid) {
processedCount++;
processNextEmail(index + 1);
return;
}
switch (action) {
case 'delete':
imap.move(uid, 'Trash', (err) => {
if (err) console.error('Error moving to trash:', err);
processedCount++;
processNextEmail(index + 1);
});
break;
case 'mark-read':
imap.addFlags(uid, ['\\Seen'], (err) => {
if (err) console.error('Error marking as read:', err);
processedCount++;
processNextEmail(index + 1);
});
break;
case 'mark-unread':
imap.removeFlags(uid, ['\\Seen'], (err) => {
if (err) console.error('Error marking as unread:', err);
processedCount++;
processNextEmail(index + 1);
});
break;
case 'archive':
imap.move(uid, 'Archive', (err) => {
if (err) console.error('Error moving to archive:', err);
processedCount++;
processNextEmail(index + 1);
});
break;
}
});
});
fetch.on('error', (err) => {
console.error('Error fetching email:', err);
processedCount++;
processNextEmail(index + 1);
});
};
processNextEmail(0);
});
});
imap.connect();
});
} catch (error) {
console.error('Error in bulk actions:', error);
return NextResponse.json(
{ error: 'Failed to perform bulk action' },
{ status: 500 }
);
}
}

View File

@ -1,135 +0,0 @@
import { NextResponse } from 'next/server';
import { ImapFlow } from 'imapflow';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { prisma } from '@/lib/prisma';
import { saveUserEmailCredentials, testEmailConnection } from '@/lib/services/email-service';
import { prefetchUserEmailData } from '@/lib/services/prefetch-service';
import { invalidateUserEmailCache, getCachedEmailCredentials } from '@/lib/redis';
export async function POST(request: Request) {
try {
const session = await getServerSession(authOptions);
console.log('Session in mail login:', session);
if (!session?.user?.id) {
console.error('No user ID in session');
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Verify user exists
console.log('Checking for user with ID:', session.user.id);
const user = await prisma.user.findUnique({
where: { id: session.user.id }
});
console.log('User found in database:', user);
if (!user) {
console.error('User not found in database');
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
const { email, password, host, port } = await request.json();
if (!email || !password || !host || !port) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// Test connection before saving
const connectionSuccess = await testEmailConnection({
email,
password,
host,
port: parseInt(port)
});
if (!connectionSuccess) {
return NextResponse.json(
{ error: 'Failed to connect to email server. Please check your credentials.' },
{ status: 401 }
);
}
// Invalidate all cached data for this user
await invalidateUserEmailCache(session.user.id);
// Save credentials using the service that handles both database and Redis
await saveUserEmailCredentials(session.user.id, {
email,
password,
host,
port: parseInt(port)
});
// Start prefetching email data in the background
prefetchUserEmailData(session.user.id).catch(err => {
console.error('Background prefetch error:', err);
});
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error in login handler:', error);
return NextResponse.json(
{ error: 'An unexpected error occurred' },
{ status: 500 }
);
}
}
export async function GET() {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// First try to get from Redis cache
let credentials = await getCachedEmailCredentials(session.user.id);
// If not in cache, get from database
if (!credentials) {
credentials = await prisma.mailCredentials.findUnique({
where: {
userId: session.user.id
},
select: {
email: true,
host: true,
port: true
}
});
} else {
// Remove password from response
const { password, ...safeCredentials } = credentials;
credentials = safeCredentials;
}
if (!credentials) {
return NextResponse.json(
{ error: 'No stored credentials found' },
{ status: 404 }
);
}
return NextResponse.json(credentials);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to retrieve credentials' },
{ status: 500 }
);
}
}

View File

@ -1,91 +0,0 @@
import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { getImapClient } from '@/lib/imap';
import { ImapFlow } from 'imapflow';
export async function POST(request: Request) {
let client: ImapFlow | null = null;
try {
// Get the session and validate it
const session = await getServerSession(authOptions);
console.log('Session:', session); // Debug log
if (!session?.user?.id) {
console.error('No session or user ID found');
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Get the request body
const { emailId, isRead } = await request.json();
console.log('Request body:', { emailId, isRead }); // Debug log
if (!emailId || typeof isRead !== 'boolean') {
console.error('Invalid request parameters:', { emailId, isRead });
return NextResponse.json(
{ error: 'Invalid request parameters' },
{ status: 400 }
);
}
// Get the current folder from the request URL
const url = new URL(request.url);
const folder = url.searchParams.get('folder') || 'INBOX';
console.log('Folder:', folder); // Debug log
try {
// Initialize IMAP client with user credentials
client = await getImapClient();
if (!client) {
console.error('Failed to initialize IMAP client');
return NextResponse.json(
{ error: 'Failed to initialize email client' },
{ status: 500 }
);
}
await client.connect();
await client.mailboxOpen(folder);
// Fetch the email to get its UID
const message = await client.fetchOne(emailId.toString(), {
uid: true,
flags: true
});
if (!message) {
console.error('Email not found:', emailId);
return NextResponse.json(
{ error: 'Email not found' },
{ status: 404 }
);
}
// Update the flags
if (isRead) {
await client.messageFlagsAdd(message.uid.toString(), ['\\Seen'], { uid: true });
} else {
await client.messageFlagsRemove(message.uid.toString(), ['\\Seen'], { uid: true });
}
return NextResponse.json({ success: true });
} finally {
if (client) {
try {
await client.logout();
} catch (e) {
console.error('Error during logout:', e);
}
}
}
} catch (error) {
console.error('Error marking email as read:', error);
return NextResponse.json(
{ error: 'Failed to mark email as read' },
{ status: 500 }
);
}
}

View File

@ -1,56 +0,0 @@
import { NextResponse } from 'next/server';
import Imap from 'imap';
export async function POST(request: Request) {
try {
const { email, password, host, port } = await request.json();
if (!email || !password || !host || !port) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
const imapConfig = {
user: email,
password,
host,
port: parseInt(port),
tls: true,
authTimeout: 10000,
connTimeout: 10000,
debug: (info: string) => console.log('IMAP Debug:', info)
};
console.log('Testing IMAP connection with config:', {
...imapConfig,
password: '***',
email
});
const imap = new Imap(imapConfig);
const connectPromise = new Promise((resolve, reject) => {
imap.once('ready', () => {
imap.end();
resolve(true);
});
imap.once('error', (err: Error) => {
imap.end();
reject(err);
});
imap.connect();
});
await connectPromise;
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error testing connection:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to connect to email server' },
{ status: 500 }
);
}
}

View File

@ -1,59 +0,0 @@
import { Mail } from "@/types/mail";
import { Star, StarOff, Paperclip } from "lucide-react";
import { format } from "date-fns";
interface MailListProps {
mails: Mail[];
onMailClick: (mail: Mail) => void;
}
export function MailList({ mails, onMailClick }: MailListProps) {
if (!mails || mails.length === 0) {
return (
<div className="flex items-center justify-center h-64">
<p className="text-gray-500">No emails found</p>
</div>
);
}
return (
<div className="flex-1 overflow-auto">
{mails.map((mail) => (
<div
key={mail.id}
className={`p-4 border-b cursor-pointer hover:bg-muted ${
!mail.read ? "bg-muted/50" : ""
}`}
onClick={() => onMailClick(mail)}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{mail.starred ? (
<Star className="h-4 w-4 text-yellow-400" />
) : (
<StarOff className="h-4 w-4 text-muted-foreground" />
)}
<span className="font-medium">{mail.from}</span>
</div>
<span className="text-sm text-muted-foreground">
{format(new Date(mail.date), "MMM d, yyyy")}
</span>
</div>
<div className="mt-2">
<h3 className="font-medium">{mail.subject}</h3>
<p className="text-sm text-muted-foreground line-clamp-2">
{mail.body}
</p>
</div>
{mail.attachments && mail.attachments.length > 0 && (
<div className="mt-2 flex items-center text-sm text-muted-foreground">
<Paperclip className="h-3 w-3 mr-1" />
{mail.attachments.length} attachment
{mail.attachments.length > 1 ? "s" : ""}
</div>
)}
</div>
))}
</div>
);
}

View File

@ -1,35 +0,0 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { RefreshCw, Plus, Search } from "lucide-react";
interface MailToolbarProps {
onRefresh: () => void;
onCompose: () => void;
onSearch: (query: string) => void;
}
export function MailToolbar({ onRefresh, onCompose, onSearch }: MailToolbarProps) {
return (
<div className="flex items-center justify-between p-4 border-b">
<div className="flex items-center space-x-2">
<Button variant="ghost" size="icon" onClick={onRefresh}>
<RefreshCw className="h-4 w-4" />
</Button>
<Button onClick={onCompose}>
<Plus className="h-4 w-4 mr-2" />
Compose
</Button>
</div>
<div className="flex-1 max-w-md mx-4">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search emails..."
className="pl-8"
onChange={(e) => onSearch(e.target.value)}
/>
</div>
</div>
</div>
);
}

View File

@ -12,7 +12,7 @@ export const useMail = () => {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
try { try {
const response = await fetch('/api/mail'); const response = await fetch('/api/courrier');
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch mails'); throw new Error(errorData.error || 'Failed to fetch mails');