diff --git a/app/api/mail/route.ts b/app/api/mail/route.ts new file mode 100644 index 0000000..20682e8 --- /dev/null +++ b/app/api/mail/route.ts @@ -0,0 +1,141 @@ +import { NextResponse } from 'next/server'; +import Imap from 'imap'; +import { simpleParser } from 'mailparser'; + +// IMAP configuration +const imapConfig = { + user: 'contact@governance-labs.org', + password: 'K!376c$6H#kMknM', + host: 'mail.governance-labs.org', + port: 993, + tls: true, + tlsOptions: { rejectUnauthorized: false } +}; + +// Helper function to create a promise-based IMAP connection +function createImapConnection() { + return new Promise((resolve, reject) => { + const imap = new Imap(imapConfig); + + imap.once('ready', () => resolve(imap)); + imap.once('error', (err: Error) => reject(err)); + imap.connect(); + }); +} + +// Helper function to promisify the message fetching +function fetchMessages(imap: Imap, box: string) { + return new Promise((resolve, reject) => { + imap.openBox(box, false, (err, mailbox) => { + if (err) { + reject(err); + return; + } + + // Search for all messages + imap.search(['ALL'], (err, results) => { + if (err) { + reject(err); + return; + } + + // No messages found + if (!results || !results.length) { + resolve([]); + return; + } + + const fetch = imap.fetch(results, { + bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'], + struct: true + }); + + const messages: any[] = []; + + fetch.on('message', (msg) => { + const message: any = {}; + + msg.on('body', (stream, info) => { + let buffer = ''; + stream.on('data', (chunk) => { + buffer += chunk.toString('utf8'); + }); + stream.once('end', () => { + if (info.which === 'TEXT') { + message.body = buffer; + } else { + message.header = Imap.parseHeader(buffer); + } + }); + }); + + msg.once('attributes', (attrs) => { + message.attributes = attrs; + }); + + msg.once('end', () => { + messages.push(message); + }); + }); + + fetch.once('error', (err) => { + reject(err); + }); + + fetch.once('end', () => { + resolve(messages); + }); + }); + }); + }); +} + +export async function GET(request: Request) { + try { + const imap = await createImapConnection() as Imap; + const messages = await fetchMessages(imap, 'INBOX'); + + // Process messages into the format expected by the frontend + const processedMessages = messages.map((msg: any, index: number) => ({ + id: index + 1, + accountId: 1, + from: msg.header.from[0], + fromName: msg.header.from[0].split('<')[0].trim(), + to: msg.header.to[0], + subject: msg.header.subject[0], + body: msg.body, + date: msg.header.date[0], + read: !(msg.attributes.flags.indexOf('\\Seen') < 0), + starred: !(msg.attributes.flags.indexOf('\\Flagged') < 0), + category: 'inbox' + })); + + imap.end(); + return NextResponse.json({ messages: processedMessages }); + + } catch (error) { + console.error('Error fetching emails:', error); + return NextResponse.json({ error: 'Failed to fetch emails' }, { status: 500 }); + } +} + +// Add endpoint to get mailboxes +export async function POST(request: Request) { + try { + const imap = await createImapConnection() as Imap; + + const mailboxes = await new Promise((resolve, reject) => { + imap.getBoxes((err, boxes) => { + if (err) reject(err); + resolve(boxes); + }); + }); + + imap.end(); + return NextResponse.json({ mailboxes }); + + } catch (error) { + console.error('Error fetching mailboxes:', error); + return NextResponse.json({ error: 'Failed to fetch mailboxes' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/mail/page.tsx b/app/mail/page.tsx index 7c796c8..f31907e 100644 --- a/app/mail/page.tsx +++ b/app/mail/page.tsx @@ -1,856 +1,134 @@ 'use client'; -import { useState, useEffect } from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; -import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit, Mail, Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight, Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll, MoreHorizontal, FolderOpen, X } from 'lucide-react'; -import { Label } from "@/components/ui/label"; -import { Paperclip, Copy, EyeOff } from 'lucide-react'; - -interface Account { - id: number; - name: string; - email: string; - color: string; -} +import { Card } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Mail, Search, Star, Inbox, Send, Archive, Trash } from "lucide-react" +import { useState, useEffect } from "react" interface Email { - id: number; - accountId: number; - from: string; - fromName: string; - to: string; - subject: string; - body: string; - date: string; - read: boolean; - starred: boolean; - category: string; + id: number + accountId: number + from: string + fromName: string + to: string + subject: string + body: string + date: string + read: boolean + starred: boolean + category: string } export default function MailPage() { - // Mock data for email accounts - const [accounts, setAccounts] = useState([ - { id: 1, name: 'Work', email: 'john.doe@company.com', color: 'bg-blue-500' }, - { id: 2, name: 'Personal', email: 'johndoe@gmail.com', color: 'bg-green-500' }, - { id: 3, name: 'Side Project', email: 'john@sideproject.io', color: 'bg-purple-500' } - ]); + const [emails, setEmails] = useState([]) + const [loading, setLoading] = useState(true) - // Mock data for emails - const [emails, setEmails] = useState([ - { - id: 1, - accountId: 1, - from: 'sarah@company.com', - fromName: 'Sarah Johnson', - to: 'john.doe@company.com', - subject: 'Project Status Update', - body: 'Hi John, here is the latest update on the project. We have completed the first phase and are moving to the second phase.', - date: '2025-04-15T10:30:00', - read: false, - starred: true, - category: 'inbox' - }, - { - id: 2, - accountId: 1, - from: 'mike@company.com', - fromName: 'Mike Chen', - to: 'john.doe@company.com', - subject: 'Meeting Tomorrow', - body: 'Don\'t forget we have a team meeting tomorrow at 10am in Conference Room A.', - date: '2025-04-14T16:45:00', - read: true, - starred: false, - category: 'inbox' - }, - { - id: 3, - accountId: 2, - from: 'lisa@gmail.com', - fromName: 'Lisa Smith', - to: 'johndoe@gmail.com', - subject: 'Weekend Plans', - body: 'Hey, are you free this weekend? I was thinking we could go hiking at the national park.', - date: '2025-04-13T09:15:00', - read: false, - starred: false, - category: 'inbox' + useEffect(() => { + async function fetchEmails() { + try { + const res = await fetch('/api/mail') + if (!res.ok) throw new Error('Failed to fetch emails') + const data = await res.json() + setEmails(data.messages) + } catch (error) { + console.error('Error fetching emails:', error) + } finally { + setLoading(false) + } } - ]); - const [selectedAccount, setSelectedAccount] = useState(1); - const [currentView, setCurrentView] = useState('inbox'); - const [selectedEmail, setSelectedEmail] = useState(null); - const [sidebarOpen, setSidebarOpen] = useState(true); - const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); - const [composeOpen, setComposeOpen] = useState(false); - const [accountsDropdownOpen, setAccountsDropdownOpen] = useState(false); - const [foldersDropdownOpen, setFoldersDropdownOpen] = useState(false); - const [showAccountActions, setShowAccountActions] = useState(null); - const [showEmailActions, setShowEmailActions] = useState(false); - const [selectedEmails, setSelectedEmails] = useState([]); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - const [deleteType, setDeleteType] = useState<'email' | 'emails' | 'account'>('email'); - const [itemToDelete, setItemToDelete] = useState(null); - const [showBulkActions, setShowBulkActions] = useState(false); - const [showCc, setShowCc] = useState(false); - const [showBcc, setShowBcc] = useState(false); - - // Mock folders data - const folders = [ - { id: 1, name: 'Important' }, - { id: 2, name: 'Work' }, - { id: 3, name: 'Personal' }, - { id: 4, name: 'Archive' } - ]; - - // Modified accounts array with "All" option - const allAccounts = [ - { id: 0, name: 'All', email: '', color: 'bg-gray-500' }, - ...accounts - ]; - - // Filter emails based on selected account and view - const filteredEmails = emails.filter(email => - (selectedAccount === 0 || email.accountId === selectedAccount) && - (currentView === 'starred' ? email.starred : email.category === currentView) - ); - - // Handle email selection - const handleEmailClick = (emailId: number) => { - const updatedEmails = emails.map(email => - email.id === emailId ? { ...email, read: true } : email - ); - setEmails(updatedEmails); - setSelectedEmail(emailId); - }; - - // Toggle starred status - const toggleStarred = (emailId: number, e: React.MouseEvent) => { - e.stopPropagation(); - const updatedEmails = emails.map(email => - email.id === emailId ? { ...email, starred: !email.starred } : email - ); - setEmails(updatedEmails); - }; - - // Format date for display - const formatDate = (dateString: string) => { - const date = new Date(dateString); - const now = new Date(); - - if (date.toDateString() === now.toDateString()) { - return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } else { - return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); - } - }; - - // Get selected email - const getSelectedEmail = () => { - return emails.find(email => email.id === selectedEmail); - }; - - // Get account color - const getAccountColor = (accountId: number) => { - const account = accounts.find(acc => acc.id === accountId); - return account ? account.color : 'bg-gray-500'; - }; - - // Handle bulk selection - const toggleEmailSelection = (emailId: number, e: React.MouseEvent) => { - e.stopPropagation(); - setSelectedEmails(prev => - prev.includes(emailId) - ? prev.filter(id => id !== emailId) - : [...prev, emailId] - ); - setShowBulkActions(true); - }; - - // Handle select all - const toggleSelectAll = () => { - if (selectedEmails.length === filteredEmails.length) { - setSelectedEmails([]); - setShowBulkActions(false); - } else { - setSelectedEmails(filteredEmails.map(email => email.id)); - setShowBulkActions(true); - } - }; - - // Handle bulk delete - const handleBulkDelete = () => { - setDeleteType('emails'); - setShowDeleteConfirm(true); - }; - - // Handle delete confirmation - const handleDeleteConfirm = () => { - if (deleteType === 'email' && itemToDelete) { - setEmails(emails.filter(email => email.id !== itemToDelete)); - setSelectedEmail(null); - } else if (deleteType === 'emails') { - setEmails(emails.filter(email => !selectedEmails.includes(email.id))); - setSelectedEmails([]); - setShowBulkActions(false); - } else if (deleteType === 'account' && itemToDelete) { - handleAccountAction(itemToDelete, 'delete'); - } - setShowDeleteConfirm(false); - }; - - // Modified account action handler - const handleAccountAction = (accountId: number, action: 'edit' | 'delete') => { - setShowAccountActions(null); - if (action === 'delete') { - setDeleteType('account'); - setItemToDelete(accountId); - setShowDeleteConfirm(true); - } - // Handle edit in a real application - }; + fetchEmails() + }, []) return ( -
+
{/* Sidebar */} -
- {/* Logo and toggle */} -
- {sidebarOpen &&

Mail

} - -
- - {/* Account Selection */} -
-
- {sidebarOpen ? ( - - ) : ( - - )} -
- - {/* Accounts Dropdown */} - {accountsDropdownOpen && sidebarOpen && ( -
- {allAccounts.map(account => ( -
- - )} -
- - - {/* Account Actions Dropdown */} - {showAccountActions === account.id && account.id !== 0 && ( -
- - -
- )} -
- ))} - - {/* Add Account Button */} - -
- )} -
- - {/* Compose button */} - - - {/* Navigation */} -
- {/* Main content */} -
- {/* Email list */} -
-
-
- {filteredEmails.length > 0 && ( - +
+
+
+
+ + - )} -

- {currentView === 'starred' ? 'Starred' : currentView} -

-
-
- {filteredEmails.length} emails +
+
- {/* Bulk Actions Bar */} - {showBulkActions && selectedEmails.length > 0 && ( -
- {selectedEmails.length} selected -
- - -
+
+ {loading ? ( +
+

Loading emails...

- )} - - {filteredEmails.length > 0 ? ( -
    - {filteredEmails.map(email => ( -
  • handleEmailClick(email.id)} - > -
    -
    -
    - toggleEmailSelection(email.id, e)} - /> -
    -
    - {email.fromName} -
    + ) : ( +
    + {emails.map((email) => ( + +
    + +
    +
    +

    {email.fromName}

    + + {new Date(email.date).toLocaleDateString()} +
    -
    {formatDate(email.date)}
    +

    {email.subject}

    +

    {email.body}

    -
    -

    {email.subject}

    - -
    -

    - {email.body} -

    -
  • + ))} -
- ) : ( -
- -

No emails in this folder

)} -
- - {/* Email detail view - Always visible */} -
- {selectedEmail ? ( -
- {/* Email actions header */} -
-
- - - -
-
- - {showEmailActions && ( -
- - -
- )} -
-
- - {/* Email content */} -
-
- {getSelectedEmail() && ( - <> -
-
-

{getSelectedEmail()?.subject}

- -
- -
- - - {getSelectedEmail()?.fromName.charAt(0)} - - -
-
{getSelectedEmail()?.fromName}
-
- {getSelectedEmail()?.from} - - {new Date(getSelectedEmail()!.date).toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' })} -
-
-
-
- -
-

{getSelectedEmail()?.body}

-
- - )} -
-
-
- ) : ( -
-
- -

No email selected

-

Choose an email from the list to read its contents

-
-
- )} -
+
- - {/* Compose email modal */} - {composeOpen && ( -
- - - New Message - - - -
-
- - -
-
-
- -
- {!showCc && ( - - )} - {!showBcc && ( - - )} -
-
- -
- {showCc && ( -
-
- - -
- -
- )} - {showBcc && ( -
-
- - -
- -
- )} -
- - -
-
- -
- -