mail page imap connection

This commit is contained in:
alma 2025-04-15 20:04:38 +02:00
parent ba0a5873b2
commit 4836353591
5 changed files with 921 additions and 840 deletions

141
app/api/mail/route.ts Normal file
View File

@ -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 });
}
}

View File

@ -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<Account[]>([
{ 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<Email[]>([])
const [loading, setLoading] = useState(true)
// Mock data for emails
const [emails, setEmails] = useState<Email[]>([
{
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<number | null>(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<number | null>(null);
const [showEmailActions, setShowEmailActions] = useState(false);
const [selectedEmails, setSelectedEmails] = useState<number[]>([]);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleteType, setDeleteType] = useState<'email' | 'emails' | 'account'>('email');
const [itemToDelete, setItemToDelete] = useState<number | null>(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 (
<div className="flex h-[calc(100vh-theme(spacing.12))] bg-gray-50 text-gray-900 overflow-hidden mt-12">
<div className="flex h-screen bg-gray-100">
{/* Sidebar */}
<div className={`${sidebarOpen ? 'w-72' : 'w-20'} bg-white/95 backdrop-blur-sm border-0 shadow-lg flex flex-col transition-all duration-300 ease-in-out
${mobileSidebarOpen ? 'fixed inset-y-0 left-0 z-40' : 'hidden'} md:block`}>
{/* Logo and toggle */}
<div className="p-3 flex items-center justify-between border-b border-gray-100">
{sidebarOpen && <h1 className="text-lg font-semibold text-gray-800">Mail</h1>}
<Button
variant="ghost"
size="icon"
className="hidden md:flex text-gray-600"
onClick={() => setSidebarOpen(!sidebarOpen)}
>
{sidebarOpen ? <ChevronLeft className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
</Button>
</div>
{/* Account Selection */}
<div className="relative">
<div className="p-3 border-b border-gray-100">
{sidebarOpen ? (
<Button
variant="ghost"
className="w-full justify-between text-gray-600 hover:text-gray-900"
onClick={() => setAccountsDropdownOpen(!accountsDropdownOpen)}
>
<div className="flex items-center gap-2">
<div className={`w-2.5 h-2.5 rounded-full ${getAccountColor(selectedAccount)}`}></div>
<span>{accounts.find(acc => acc.id === selectedAccount)?.name || 'All accounts'}</span>
</div>
{accountsDropdownOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
) : (
<Button
variant="ghost"
size="icon"
className="w-full aspect-square"
onClick={() => setAccountsDropdownOpen(!accountsDropdownOpen)}
>
<div className={`w-2.5 h-2.5 rounded-full ${getAccountColor(selectedAccount)}`}></div>
</Button>
)}
</div>
{/* Accounts Dropdown */}
{accountsDropdownOpen && sidebarOpen && (
<div className="absolute top-full left-0 w-full bg-white border border-gray-100 shadow-lg rounded-b-lg z-50">
{allAccounts.map(account => (
<div key={account.id} className="relative group">
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm"
onClick={() => {
setSelectedAccount(account.id);
setAccountsDropdownOpen(false);
}}
>
<div className="flex items-center gap-2 w-full">
<div className={`w-2.5 h-2.5 rounded-full ${account.color}`}></div>
<div className="flex flex-col items-start flex-1">
<span className="font-medium">{account.name}</span>
{account.email && <span className="text-xs text-gray-500">{account.email}</span>}
</div>
{account.id !== 0 && (
<Button
variant="ghost"
size="icon"
className="opacity-0 group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setShowAccountActions(account.id);
}}
>
<MoreVertical className="h-4 w-4 text-gray-500" />
</Button>
)}
</div>
</Button>
{/* Account Actions Dropdown */}
{showAccountActions === account.id && account.id !== 0 && (
<div className="absolute right-0 mt-1 w-48 bg-white border border-gray-100 shadow-lg rounded-lg z-50">
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm text-gray-600 hover:text-gray-900"
onClick={() => handleAccountAction(account.id, 'edit')}
>
<Edit className="h-4 w-4 mr-2" />
Edit account
</Button>
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm text-red-600 hover:text-red-700"
onClick={() => handleAccountAction(account.id, 'delete')}
>
<Trash2 className="h-4 w-4 mr-2" />
Remove account
</Button>
</div>
)}
</div>
))}
{/* Add Account Button */}
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm text-blue-600 hover:text-blue-700 border-t border-gray-100"
onClick={() => {
setAccountsDropdownOpen(false);
// Handle add account in a real application
}}
>
<PlusIcon className="h-4 w-4 mr-2" />
Add account
</Button>
</div>
)}
</div>
{/* Compose button */}
<Button
className={`mx-3 mt-3 mb-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center justify-center transition-all ${sidebarOpen ? 'py-2 px-4' : 'p-2'}`}
onClick={() => setComposeOpen(true)}
>
{sidebarOpen ? (
<div className="flex items-center">
<PlusIcon className="h-4 w-4" />
<span className="ml-2">Compose</span>
</div>
) : (
<PlusIcon className="h-4 w-4" />
)}
<div className="w-64 bg-white shadow-sm border-r border-gray-200 p-4">
<Button className="w-full mb-4 gap-2">
<Mail className="h-4 w-4" />
Compose
</Button>
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-2">
<ul className="space-y-0.5 px-2">
<li>
<Button
variant={currentView === 'inbox' ? 'secondary' : 'ghost'}
className={`w-full justify-start py-2 ${currentView === 'inbox' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'}`}
onClick={() => {setCurrentView('inbox'); setSelectedEmail(null);}}
>
<Inbox className="h-4 w-4 mr-2" />
{sidebarOpen && <span>Inbox</span>}
</Button>
</li>
<li>
<Button
variant={currentView === 'starred' ? 'secondary' : 'ghost'}
className={`w-full justify-start py-2 ${currentView === 'starred' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'}`}
onClick={() => {setCurrentView('starred'); setSelectedEmail(null);}}
>
<Star className="h-4 w-4 mr-2" />
{sidebarOpen && <span>Starred</span>}
</Button>
</li>
<li>
<Button
variant={currentView === 'sent' ? 'secondary' : 'ghost'}
className={`w-full justify-start py-2 ${currentView === 'sent' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'}`}
onClick={() => {setCurrentView('sent'); setSelectedEmail(null);}}
>
<Send className="h-4 w-4 mr-2" />
{sidebarOpen && <span>Sent</span>}
</Button>
</li>
<li>
<Button
variant={currentView === 'trash' ? 'secondary' : 'ghost'}
className={`w-full justify-start py-2 ${currentView === 'trash' ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'}`}
onClick={() => {setCurrentView('trash'); setSelectedEmail(null);}}
>
<Trash className="h-4 w-4 mr-2" />
{sidebarOpen && <span>Trash</span>}
</Button>
</li>
{/* Folders Section */}
<li className="mt-4">
<Button
variant="ghost"
className="w-full justify-between py-2 text-gray-600 hover:text-gray-900"
onClick={() => setFoldersDropdownOpen(!foldersDropdownOpen)}
>
<div className="flex items-center">
<Folder className="h-4 w-4 mr-2" />
{sidebarOpen && <span>Folders</span>}
</div>
{sidebarOpen && (foldersDropdownOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />)}
</Button>
{/* Folders Dropdown */}
{foldersDropdownOpen && sidebarOpen && (
<ul className="mt-1 space-y-1">
{folders.map(folder => (
<li key={folder.id}>
<Button
variant="ghost"
className="w-full justify-start py-1.5 pl-8 text-sm text-gray-600 hover:text-gray-900"
onClick={() => {
setCurrentView(folder.name.toLowerCase());
setSelectedEmail(null);
}}
>
{folder.name}
</Button>
</li>
))}
</ul>
)}
</li>
</ul>
<nav className="space-y-1">
<Button variant="ghost" className="w-full justify-start gap-2">
<Inbox className="h-4 w-4" />
Inbox
</Button>
<Button variant="ghost" className="w-full justify-start gap-2">
<Star className="h-4 w-4" />
Starred
</Button>
<Button variant="ghost" className="w-full justify-start gap-2">
<Send className="h-4 w-4" />
Sent
</Button>
<Button variant="ghost" className="w-full justify-start gap-2">
<Archive className="h-4 w-4" />
Archive
</Button>
<Button variant="ghost" className="w-full justify-start gap-2">
<Trash className="h-4 w-4" />
Trash
</Button>
</nav>
</div>
{/* Main content */}
<div className="flex-1 flex overflow-hidden">
{/* Email list */}
<div className="w-[380px] bg-white/95 backdrop-blur-sm border-r border-gray-100 overflow-y-auto">
<div className="p-4 border-b border-gray-100 flex justify-between items-center">
<div className="flex items-center gap-4">
{filteredEmails.length > 0 && (
<Checkbox
checked={selectedEmails.length === filteredEmails.length}
onCheckedChange={toggleSelectAll}
{/* Main Content */}
<div className="flex-1 flex flex-col">
<header className="bg-white shadow-sm border-b border-gray-200 p-4">
<div className="flex items-center">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
type="search"
placeholder="Search mail..."
className="w-full pl-10"
/>
)}
<h2 className="text-lg font-semibold text-gray-800 capitalize">
{currentView === 'starred' ? 'Starred' : currentView}
</h2>
</div>
<div className="text-sm text-gray-500">
{filteredEmails.length} emails
</div>
</div>
</div>
</header>
{/* Bulk Actions Bar */}
{showBulkActions && selectedEmails.length > 0 && (
<div className="p-2 bg-gray-50 border-b border-gray-100 flex items-center justify-between">
<span className="text-sm text-gray-600">{selectedEmails.length} selected</span>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="text-gray-600 hover:text-gray-900"
onClick={() => {
// Handle move to folder
}}
>
<FolderOpen className="h-4 w-4 mr-2" />
Move to
</Button>
<Button
variant="ghost"
size="sm"
className="text-red-600 hover:text-red-700"
onClick={handleBulkDelete}
>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</Button>
</div>
<main className="flex-1 overflow-auto p-4">
{loading ? (
<div className="flex items-center justify-center h-full">
<p className="text-gray-500">Loading emails...</p>
</div>
)}
{filteredEmails.length > 0 ? (
<ul>
{filteredEmails.map(email => (
<li
key={email.id}
className={`border-b border-gray-100 cursor-pointer ${email.read ? 'bg-white/95' : 'bg-blue-50/95'} hover:bg-gray-50/95`}
onClick={() => handleEmailClick(email.id)}
>
<div className="p-4">
<div className="flex justify-between items-start mb-1">
<div className="flex items-center gap-3">
<Checkbox
checked={selectedEmails.includes(email.id)}
onClick={(e) => toggleEmailSelection(email.id, e)}
/>
<div className="flex items-center">
<div className={`w-2 h-2 rounded-full ${!email.read ? 'bg-blue-600' : 'bg-transparent'} mr-2`}></div>
<span className={`font-medium ${!email.read ? 'font-semibold' : ''} text-gray-900`}>{email.fromName}</span>
</div>
) : (
<div className="space-y-2">
{emails.map((email) => (
<Card key={email.id} className={`p-4 hover:bg-gray-50 cursor-pointer ${!email.read ? 'bg-blue-50' : ''}`}>
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
className="h-4 w-4 p-0"
onClick={(e) => {
e.stopPropagation()
// TODO: Implement star functionality
}}
>
<Star className={`h-4 w-4 ${email.starred ? 'fill-yellow-400 text-yellow-400' : 'text-gray-400'}`} />
</Button>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<p className="font-medium truncate">{email.fromName}</p>
<span className="text-sm text-gray-500">
{new Date(email.date).toLocaleDateString()}
</span>
</div>
<div className="text-xs text-gray-500">{formatDate(email.date)}</div>
<p className="text-sm font-medium truncate">{email.subject}</p>
<p className="text-sm text-gray-500 truncate">{email.body}</p>
</div>
<div className="flex justify-between items-center mb-1">
<h3 className={`${!email.read ? 'font-semibold' : ''} text-gray-800`}>{email.subject}</h3>
<Button
variant="ghost"
size="sm"
className="text-gray-400 hover:text-yellow-500"
onClick={(e) => toggleStarred(email.id, e)}
>
<Star className="h-4 w-4" fill={email.starred ? 'currentColor' : 'none'} color={email.starred ? '#F59E0B' : 'currentColor'} />
</Button>
</div>
<p className="text-sm text-gray-600 truncate">
{email.body}
</p>
</div>
</li>
</Card>
))}
</ul>
) : (
<div className="flex flex-col items-center justify-center h-64 text-gray-500">
<Mail className="h-12 w-12 mb-4 opacity-30" />
<p>No emails in this folder</p>
</div>
)}
</div>
{/* Email detail view - Always visible */}
<div className="flex-1 overflow-y-auto bg-white">
{selectedEmail ? (
<div className="h-full flex flex-col">
{/* Email actions header */}
<div className="p-4 border-b border-gray-100 flex items-center justify-between">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
className="text-gray-700 hover:bg-gray-100 hover:text-gray-900"
>
<Reply className="h-4 w-4 mr-2" />
Reply
</Button>
<Button
variant="ghost"
size="sm"
className="text-gray-700 hover:bg-gray-100 hover:text-gray-900"
>
<ReplyAll className="h-4 w-4 mr-2" />
Reply all
</Button>
<Button
variant="ghost"
size="sm"
className="text-gray-700 hover:bg-gray-100 hover:text-gray-900"
>
<Forward className="h-4 w-4 mr-2" />
Forward
</Button>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="text-gray-500 hover:text-gray-700"
onClick={() => setShowEmailActions(!showEmailActions)}
>
<MoreHorizontal className="h-4 w-4" />
</Button>
{showEmailActions && (
<div className="absolute right-4 mt-32 w-48 bg-white border border-gray-100 shadow-lg rounded-lg z-50">
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm text-gray-600 hover:text-gray-900"
onClick={() => {
// Handle move to folder
setShowEmailActions(false);
}}
>
<FolderOpen className="h-4 w-4 mr-2" />
Move to folder
</Button>
<Button
variant="ghost"
className="w-full justify-start px-4 py-2 text-sm text-red-600 hover:text-red-700"
onClick={() => {
// Handle delete email
setShowEmailActions(false);
}}
>
<Trash2 className="h-4 w-4 mr-2" />
Delete
</Button>
</div>
)}
</div>
</div>
{/* Email content */}
<div className="flex-1 p-6">
<div className="max-w-3xl mx-auto">
{getSelectedEmail() && (
<>
<div className="mb-6">
<div className="flex justify-between items-center mb-4">
<h1 className="text-xl font-bold">{getSelectedEmail()?.subject}</h1>
<Button
variant="ghost"
size="sm"
className="text-gray-400 hover:text-yellow-400"
onClick={(e) => getSelectedEmail() && toggleStarred(getSelectedEmail()!.id, e)}
>
<Star className="h-5 w-5" fill={getSelectedEmail()?.starred ? 'currentColor' : 'none'} />
</Button>
</div>
<div className="flex items-center">
<Avatar>
<AvatarFallback className={`${getAccountColor(getSelectedEmail()!.accountId)}`}>
{getSelectedEmail()?.fromName.charAt(0)}
</AvatarFallback>
</Avatar>
<div className="ml-3">
<div className="font-medium">{getSelectedEmail()?.fromName}</div>
<div className="text-sm text-gray-500 flex items-center">
<span>{getSelectedEmail()?.from}</span>
<span className="mx-2"></span>
<span>{new Date(getSelectedEmail()!.date).toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' })}</span>
</div>
</div>
</div>
</div>
<div className="border-t border-gray-200 pt-6 prose max-w-none">
<p>{getSelectedEmail()?.body}</p>
</div>
</>
)}
</div>
</div>
</div>
) : (
<div className="h-full flex items-center justify-center">
<div className="text-center">
<Mail className="h-16 w-16 mx-auto mb-4 text-gray-300" />
<p className="text-gray-500">No email selected</p>
<p className="text-sm text-gray-400">Choose an email from the list to read its contents</p>
</div>
</div>
)}
</div>
</main>
</div>
{/* Compose email modal */}
{composeOpen && (
<div className="fixed inset-0 bg-black/25 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<Card className="w-full max-w-2xl bg-white max-h-[90vh] flex flex-col">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 border-b">
<CardTitle className="text-xl">New Message</CardTitle>
<Button
variant="ghost"
size="icon"
className="text-gray-500 hover:text-gray-700"
onClick={() => setComposeOpen(false)}
>
<X className="h-4 w-4" />
</Button>
</CardHeader>
<CardContent className="space-y-3 overflow-y-auto flex-1 pt-2">
<div className="space-y-2 border-b border-gray-100 pb-3">
<div>
<Label className="text-sm text-gray-700 mb-0.5">From</Label>
<select className="w-full bg-white border border-gray-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
{accounts.map(account => (
<option key={account.id} value={account.id}>
{account.name} ({account.email})
</option>
))}
</select>
</div>
<div>
<div className="flex items-center justify-between mb-0.5">
<Label className="text-sm text-gray-700">To</Label>
<div className="flex items-center gap-2">
{!showCc && (
<Button
variant="ghost"
size="sm"
className="h-6 text-xs text-gray-500 hover:text-gray-700"
onClick={() => setShowCc(true)}
>
Add Cc
</Button>
)}
{!showBcc && (
<Button
variant="ghost"
size="sm"
className="h-6 text-xs text-gray-500 hover:text-gray-700"
onClick={() => setShowBcc(true)}
>
Add Bcc
</Button>
)}
</div>
</div>
<Input
type="email"
placeholder="email@example.com"
className="bg-white border-gray-200 py-1.5"
/>
</div>
{showCc && (
<div>
<div className="flex items-center justify-between mb-0.5">
<Label className="text-sm text-gray-700">Cc</Label>
<Button
variant="ghost"
size="sm"
className="h-6 text-xs text-gray-500 hover:text-gray-700"
onClick={() => setShowCc(false)}
>
Remove
</Button>
</div>
<Input
type="email"
placeholder="cc@example.com"
className="bg-white border-gray-200 py-1.5"
/>
</div>
)}
{showBcc && (
<div>
<div className="flex items-center justify-between mb-0.5">
<Label className="text-sm text-gray-700">Bcc</Label>
<Button
variant="ghost"
size="sm"
className="h-6 text-xs text-gray-500 hover:text-gray-700"
onClick={() => setShowBcc(false)}
>
Remove
</Button>
</div>
<Input
type="email"
placeholder="bcc@example.com"
className="bg-white border-gray-200 py-1.5"
/>
</div>
)}
<div>
<Label className="text-sm text-gray-700 mb-0.5">Subject</Label>
<Input
type="text"
placeholder="Subject"
className="bg-white border-gray-200 py-1.5"
/>
</div>
</div>
<div className="flex-1">
<Label className="text-sm text-gray-700 mb-0.5">Message</Label>
<Textarea
className="mt-0.5 h-[calc(100%-1.5rem)] min-h-[200px] bg-white border-gray-200 resize-none"
placeholder="Write your message here..."
/>
</div>
{/* File Attachment Section */}
<div className="border-t border-gray-100 pt-3">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="sm"
className="border border-gray-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700"
onClick={() => document.getElementById('file-upload')?.click()}
>
<Paperclip className="h-4 w-4 mr-2" />
Attach files
</Button>
<span className="text-xs text-gray-500">Maximum file size: 25 MB</span>
</div>
<input
type="file"
id="file-upload"
multiple
className="hidden"
onChange={(e) => {
// Handle file upload
console.log(e.target.files);
}}
/>
</div>
<div className="flex justify-end gap-3 pt-3 border-t border-gray-100">
<Button
variant="ghost"
className="border border-gray-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700"
onClick={() => setComposeOpen(false)}
>
Cancel
</Button>
<Button
className="bg-blue-600 text-white hover:bg-blue-700"
onClick={() => setComposeOpen(false)}
>
Send
</Button>
</div>
</CardContent>
</Card>
</div>
)}
{/* Delete Confirmation Dialog */}
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
{deleteType === 'email' && "This email will be moved to trash."}
{deleteType === 'emails' && `${selectedEmails.length} emails will be moved to trash.`}
{deleteType === 'account' && "This account will be permanently removed. This action cannot be undone."}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setShowDeleteConfirm(false)}>Cancel</AlertDialogCancel>
<AlertDialogAction
className={deleteType === 'account' ? 'bg-red-600 hover:bg-red-700' : ''}
onClick={handleDeleteConfirm}
>
{deleteType === 'account' ? 'Delete Account' : 'Move to Trash'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
)
}

406
package-lock.json generated
View File

@ -38,6 +38,9 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@types/imap": "^0.8.42",
"@types/mailparser": "^3.4.5",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.11.12",
"@types/react-datepicker": "^6.2.0",
"autoprefixer": "^10.4.20",
@ -47,11 +50,14 @@
"date-fns": "^3.6.0",
"embla-carousel-react": "8.5.1",
"fullcalendar": "^6.1.15",
"imap": "^0.8.19",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"mailparser": "^3.7.2",
"next": "14.2.24",
"next-auth": "^4.24.11",
"next-themes": "^0.4.4",
"nodemailer": "^6.10.1",
"pg": "^8.14.1",
"react": "^18",
"react-datepicker": "^8.3.0",
@ -2722,6 +2728,19 @@
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
"integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
},
"node_modules/@selderee/plugin-htmlparser2": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
"integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"selderee": "^0.11.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@ -2790,6 +2809,25 @@
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/imap": {
"version": "0.8.42",
"resolved": "https://registry.npmjs.org/@types/imap/-/imap-0.8.42.tgz",
"integrity": "sha512-FusePG9Cp2GYN6OLow9xBCkjznFkAR7WCz0Fm+j1p/ER6C8V8P71DtjpSmwrZsS7zekCeqdTPHEk9N5OgPwcsg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/mailparser": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.5.tgz",
"integrity": "sha512-EPERBp7fLeFZh7tS2X36MF7jawUx3Y6/0rXciZah3CTYgwLi3e0kpGUJ6FOmUabgzis/U1g+3/JzrVWbWIOGjg==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"iconv-lite": "^0.6.3"
}
},
"node_modules/@types/node": {
"version": "22.10.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
@ -2798,6 +2836,15 @@
"undici-types": "~6.20.0"
}
},
"node_modules/@types/nodemailer": {
"version": "6.4.17",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/pg": {
"version": "8.11.12",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.12.tgz",
@ -3144,6 +3191,12 @@
"node": ">= 0.6"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -3316,6 +3369,15 @@
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@ -3340,6 +3402,61 @@
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -3380,6 +3497,27 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/encoding-japanese": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz",
"integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==",
"license": "MIT",
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
@ -3616,6 +3754,80 @@
"node": ">= 0.4"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"license": "MIT",
"bin": {
"he": "bin/he"
}
},
"node_modules/html-to-text": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
"integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==",
"license": "MIT",
"dependencies": {
"@selderee/plugin-htmlparser2": "^0.11.0",
"deepmerge": "^4.3.1",
"dom-serializer": "^2.0.0",
"htmlparser2": "^8.0.2",
"selderee": "^0.11.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/imap": {
"version": "0.8.19",
"resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz",
"integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==",
"dependencies": {
"readable-stream": "1.1.x",
"utf7": ">=1.0.2"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/input-otp": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz",
@ -3693,6 +3905,12 @@
"node": ">=0.12.0"
}
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -3734,6 +3952,39 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/leac": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz",
"integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/libbase64": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz",
"integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==",
"license": "MIT"
},
"node_modules/libmime": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.6.tgz",
"integrity": "sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"iconv-lite": "0.6.3",
"libbase64": "1.3.0",
"libqp": "2.1.1"
}
},
"node_modules/libqp": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz",
"integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==",
"license": "MIT"
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@ -3750,6 +4001,15 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -3779,6 +4039,44 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/mailparser": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.2.tgz",
"integrity": "sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==",
"license": "MIT",
"dependencies": {
"encoding-japanese": "2.2.0",
"he": "1.2.0",
"html-to-text": "9.0.5",
"iconv-lite": "0.6.3",
"libmime": "5.3.6",
"linkify-it": "5.0.0",
"mailsplit": "5.4.2",
"nodemailer": "6.9.16",
"punycode.js": "2.3.1",
"tlds": "1.255.0"
}
},
"node_modules/mailparser/node_modules/nodemailer": {
"version": "6.9.16",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/mailsplit": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.2.tgz",
"integrity": "sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==",
"license": "(MIT OR EUPL-1.1+)",
"dependencies": {
"libbase64": "1.3.0",
"libmime": "5.3.6",
"libqp": "2.1.1"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3978,6 +4276,15 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
},
"node_modules/nodemailer": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz",
"integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -4072,6 +4379,19 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"node_modules/parseley": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz",
"integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==",
"license": "MIT",
"dependencies": {
"leac": "^0.6.0",
"peberminta": "^0.9.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@ -4100,6 +4420,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/peberminta": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
"integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==",
"license": "MIT",
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/pg": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.14.1.tgz",
@ -4541,6 +4870,15 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -4776,6 +5114,18 @@
"pify": "^2.3.0"
}
},
"node_modules/readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -4872,6 +5222,12 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@ -4880,6 +5236,27 @@
"loose-envify": "^1.1.0"
}
},
"node_modules/selderee": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz",
"integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==",
"license": "MIT",
"dependencies": {
"parseley": "^0.12.0"
},
"funding": {
"url": "https://ko-fi.com/killymxi"
}
},
"node_modules/semver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==",
"license": "ISC",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -4944,6 +5321,12 @@
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
"license": "MIT"
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -5169,6 +5552,15 @@
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
},
"node_modules/tlds": {
"version": "1.255.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz",
"integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==",
"license": "MIT",
"bin": {
"tlds": "bin.js"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -5203,6 +5595,12 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
@ -5286,6 +5684,14 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/utf7": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz",
"integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==",
"dependencies": {
"semver": "~5.3.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -39,6 +39,9 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@types/imap": "^0.8.42",
"@types/mailparser": "^3.4.5",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.11.12",
"@types/react-datepicker": "^6.2.0",
"autoprefixer": "^10.4.20",
@ -48,11 +51,14 @@
"date-fns": "^3.6.0",
"embla-carousel-react": "8.5.1",
"fullcalendar": "^6.1.15",
"imap": "^0.8.19",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"mailparser": "^3.7.2",
"next": "14.2.24",
"next-auth": "^4.24.11",
"next-themes": "^0.4.4",
"nodemailer": "^6.10.1",
"pg": "^8.14.1",
"react": "^18",
"react-datepicker": "^8.3.0",

280
yarn.lock
View File

@ -19,11 +19,6 @@
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz"
integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==
"@esbuild/linux-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz"
integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==
"@floating-ui/core@^1.6.0":
version "1.6.9"
resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz"
@ -169,16 +164,6 @@
resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.24.tgz"
integrity sha512-7Tdi13aojnAZGpapVU6meVSpNzgrFwZ8joDcNS8cJVNuP3zqqrLqeory9Xec5TJZR/stsGJdfwo8KeyloT3+rQ==
"@next/swc-linux-arm64-gnu@14.2.24":
version "14.2.24"
resolved "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.24.tgz"
integrity sha512-nxvJgWOpSNmzidYvvGDfXwxkijb6hL9+cjZx1PVG6urr2h2jUqBALkKjT7kpfurRWicK6hFOvarmaWsINT1hnA==
"@next/swc-linux-arm64-musl@14.2.24":
version "14.2.24"
resolved "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.24.tgz"
integrity sha512-PaBgOPhqa4Abxa3y/P92F3kklNPsiFjcjldQGT7kFmiY5nuFn8ClBEoX8GIpqU1ODP2y8P6hio6vTomx2Vy0UQ==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@ -950,6 +935,14 @@
resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz"
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
"@selderee/plugin-htmlparser2@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz"
integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==
dependencies:
domhandler "^5.0.3"
selderee "^0.11.0"
"@swc/counter@^0.1.3":
version "0.1.3"
resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz"
@ -1014,6 +1007,21 @@
resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz"
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
"@types/imap@^0.8.42":
version "0.8.42"
resolved "https://registry.npmjs.org/@types/imap/-/imap-0.8.42.tgz"
integrity sha512-FusePG9Cp2GYN6OLow9xBCkjznFkAR7WCz0Fm+j1p/ER6C8V8P71DtjpSmwrZsS7zekCeqdTPHEk9N5OgPwcsg==
dependencies:
"@types/node" "*"
"@types/mailparser@^3.4.5":
version "3.4.5"
resolved "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.5.tgz"
integrity sha512-EPERBp7fLeFZh7tS2X36MF7jawUx3Y6/0rXciZah3CTYgwLi3e0kpGUJ6FOmUabgzis/U1g+3/JzrVWbWIOGjg==
dependencies:
"@types/node" "*"
iconv-lite "^0.6.3"
"@types/node@*", "@types/node@^22":
version "22.10.5"
resolved "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz"
@ -1021,6 +1029,13 @@
dependencies:
undici-types "~6.20.0"
"@types/nodemailer@^6.4.17":
version "6.4.17"
resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz"
integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==
dependencies:
"@types/node" "*"
"@types/pg@^8.11.12":
version "8.11.12"
resolved "https://registry.npmjs.org/@types/pg/-/pg-8.11.12.tgz"
@ -1231,6 +1246,11 @@ cookie@^0.7.0:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cross-spawn@^7.0.0:
version "7.0.6"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
@ -1343,6 +1363,11 @@ decimal.js-light@^2.4.1:
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==
deepmerge@^4.3.1:
version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz"
@ -1366,6 +1391,36 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^3.0.1:
version "3.2.2"
resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz"
integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
@ -1404,6 +1459,16 @@ emoji-regex@^9.2.2:
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
encoding-japanese@2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz"
integrity sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==
entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild-register@3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz"
@ -1567,6 +1632,52 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
he@1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
html-to-text@9.0.5:
version "9.0.5"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz"
integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==
dependencies:
"@selderee/plugin-htmlparser2" "^0.11.0"
deepmerge "^4.3.1"
dom-serializer "^2.0.0"
htmlparser2 "^8.0.2"
selderee "^0.11.0"
htmlparser2@^8.0.2:
version "8.0.2"
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
entities "^4.4.0"
iconv-lite@^0.6.3, iconv-lite@0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
imap@^0.8.19:
version "0.8.19"
resolved "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz"
integrity sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==
dependencies:
readable-stream "1.1.x"
utf7 ">=1.0.2"
inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
input-otp@1.4.1:
version "1.4.1"
resolved "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz"
@ -1613,6 +1724,11 @@ is-number@^7.0.0:
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
@ -1642,6 +1758,31 @@ jose@^4.15.5, jose@^4.15.9:
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
leac@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz"
integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==
libbase64@1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz"
integrity sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==
libmime@5.3.6:
version "5.3.6"
resolved "https://registry.npmjs.org/libmime/-/libmime-5.3.6.tgz"
integrity sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==
dependencies:
encoding-japanese "2.2.0"
iconv-lite "0.6.3"
libbase64 "1.3.0"
libqp "2.1.1"
libqp@2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz"
integrity sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==
lilconfig@^3.0.0, lilconfig@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz"
@ -1652,6 +1793,13 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
linkify-it@5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz"
integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==
dependencies:
uc.micro "^2.0.0"
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
@ -1681,6 +1829,31 @@ lucide-react@^0.454.0:
resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz"
integrity sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==
mailparser@^3.7.2:
version "3.7.2"
resolved "https://registry.npmjs.org/mailparser/-/mailparser-3.7.2.tgz"
integrity sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==
dependencies:
encoding-japanese "2.2.0"
he "1.2.0"
html-to-text "9.0.5"
iconv-lite "0.6.3"
libmime "5.3.6"
linkify-it "5.0.0"
mailsplit "5.4.2"
nodemailer "6.9.16"
punycode.js "2.3.1"
tlds "1.255.0"
mailsplit@5.4.2:
version "5.4.2"
resolved "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.2.tgz"
integrity sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==
dependencies:
libbase64 "1.3.0"
libmime "5.3.6"
libqp "2.1.1"
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
@ -1773,6 +1946,16 @@ node-releases@^2.0.19:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz"
integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
nodemailer@^6.10.1, nodemailer@^6.6.5:
version "6.10.1"
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz"
integrity sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==
nodemailer@6.9.16:
version "6.9.16"
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz"
integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
@ -1828,6 +2011,14 @@ package-json-from-dist@^1.0.0:
resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
parseley@^0.12.0:
version "0.12.1"
resolved "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz"
integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==
dependencies:
leac "^0.6.0"
peberminta "^0.9.0"
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
@ -1846,6 +2037,11 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
peberminta@^0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz"
integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==
pg-cloudflare@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz"
@ -2093,6 +2289,11 @@ prop-types@^15.6.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
punycode.js@2.3.1:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz"
integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -2205,6 +2406,16 @@ read-cache@^1.0.0:
dependencies:
pify "^2.3.0"
readable-stream@1.1.x:
version "1.1.14"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz"
integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
@ -2259,6 +2470,11 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"
@ -2266,6 +2482,18 @@ scheduler@^0.23.2:
dependencies:
loose-envify "^1.1.0"
selderee@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz"
integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==
dependencies:
parseley "^0.12.0"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz"
integrity sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
@ -2303,6 +2531,11 @@ streamsearch@^1.1.0:
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@ -2438,6 +2671,11 @@ tiny-invariant@^1.3.1:
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz"
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
tlds@1.255.0:
version "1.255.0"
resolved "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz"
integrity sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
@ -2460,6 +2698,11 @@ typescript@^5, typescript@>=5.1.0:
resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
uc.micro@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz"
integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz"
@ -2493,6 +2736,13 @@ use-sync-external-store@^1.2.2:
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
utf7@>=1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz"
integrity sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==
dependencies:
semver "~5.3.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"