Neah/app/courrier/page.tsx
2025-04-27 10:02:33 +02:00

481 lines
17 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import {
Mail, Loader2, AlertCircle,
MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit,
Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
AlertOctagon, Archive, RefreshCw
} from 'lucide-react';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { ScrollArea } from '@/components/ui/scroll-area';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
// Import components
import EmailSidebar from '@/components/email/EmailSidebar';
import EmailList from '@/components/email/EmailList';
import EmailSidebarContent from '@/components/email/EmailSidebarContent';
import EmailDetailView from '@/components/email/EmailDetailView';
import ComposeEmail from '@/components/email/ComposeEmail';
import { DeleteConfirmDialog, LoginNeededAlert } from '@/components/email/EmailDialogs';
// Import the custom hook
import { useCourrier, EmailData } from '@/hooks/use-courrier';
// Simplified version for this component
function SimplifiedLoadingFix() {
// In production, don't render anything
if (process.env.NODE_ENV === 'production') {
return null;
}
// Simple debugging component
return (
<div className="fixed bottom-4 right-4 z-50 p-2 bg-white/80 shadow rounded-lg text-xs">
Debug: Email app loaded
</div>
);
}
interface Account {
id: number;
name: string;
email: string;
color: string;
folders?: string[];
}
export default function CourrierPage() {
const router = useRouter();
// Get all the email functionality from the hook
const {
emails,
selectedEmail,
selectedEmailIds,
currentFolder,
mailboxes,
isLoading,
isSending,
error,
searchQuery,
page,
totalPages,
loadEmails,
handleEmailSelect,
markEmailAsRead,
toggleStarred,
sendEmail,
deleteEmails,
toggleEmailSelection,
toggleSelectAll,
changeFolder,
searchEmails,
formatEmailForAction,
setPage,
} = useCourrier();
// UI state
const [showComposeModal, setShowComposeModal] = useState(false);
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [showLoginNeeded, setShowLoginNeeded] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [accountsDropdownOpen, setAccountsDropdownOpen] = useState(false);
const [currentView, setCurrentView] = useState('INBOX');
const [unreadCount, setUnreadCount] = useState(0);
const [loading, setLoading] = useState(false);
// Mock accounts for the sidebar
const [accounts, setAccounts] = useState<Account[]>([
{ id: 0, name: 'All', email: '', color: 'bg-gray-500' },
{ id: 1, name: 'Mail', email: 'user@example.com', color: 'bg-blue-500', folders: mailboxes }
]);
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
// Update account folders when mailboxes change
useEffect(() => {
setAccounts(prev => {
const updated = [...prev];
if (updated[1]) {
updated[1].folders = mailboxes;
}
return updated;
});
}, [mailboxes]);
// Calculate unread count (this would be replaced with actual data in production)
useEffect(() => {
// Example: counting unread emails in the inbox
const unreadInInbox = emails.filter(email => !email.read && currentFolder === 'INBOX').length;
setUnreadCount(unreadInInbox);
}, [emails, currentFolder]);
// Helper to get folder icons
const getFolderIcon = (folder: string) => {
const folderLower = folder.toLowerCase();
if (folderLower.includes('inbox')) {
return Inbox;
} else if (folderLower.includes('sent')) {
return Send;
} else if (folderLower.includes('trash')) {
return Trash;
} else if (folderLower.includes('archive')) {
return Archive;
} else if (folderLower.includes('draft')) {
return Edit;
} else if (folderLower.includes('spam') || folderLower.includes('junk')) {
return AlertOctagon;
} else {
return Folder;
}
};
// Helper to format folder names
const formatFolderName = (folder: string) => {
return folder.charAt(0).toUpperCase() + folder.slice(1).toLowerCase();
};
// Check for more emails
const hasMoreEmails = page < totalPages;
// Handle loading more emails on scroll
const handleLoadMore = () => {
if (hasMoreEmails && !isLoading) {
setPage(page + 1);
}
};
// Handle bulk actions
const handleBulkAction = async (action: 'delete' | 'mark-read' | 'mark-unread' | 'archive') => {
if (selectedEmailIds.length === 0) return;
switch (action) {
case 'delete':
setShowDeleteConfirm(true);
break;
case 'mark-read':
// Mark all selected emails as read
for (const emailId of selectedEmailIds) {
await markEmailAsRead(emailId, true);
}
break;
case 'mark-unread':
// Mark all selected emails as unread
for (const emailId of selectedEmailIds) {
await markEmailAsRead(emailId, false);
}
break;
case 'archive':
// Archive functionality would be implemented here
break;
}
};
// Handle email actions
const handleReply = () => {
if (!selectedEmail) return;
setComposeType('reply');
setShowComposeModal(true);
};
const handleReplyAll = () => {
if (!selectedEmail) return;
setComposeType('reply-all');
setShowComposeModal(true);
};
const handleForward = () => {
if (!selectedEmail) return;
setComposeType('forward');
setShowComposeModal(true);
};
const handleComposeNew = () => {
setComposeType('new');
setShowComposeModal(true);
};
// Handle mailbox change
const handleMailboxChange = (folder: string) => {
changeFolder(folder);
setCurrentView(folder);
};
// Handle sending email
const handleSendEmail = async (emailData: EmailData) => {
return await sendEmail(emailData);
};
// Handle delete confirmation
const handleDeleteConfirm = async () => {
await deleteEmails(selectedEmailIds);
setShowDeleteConfirm(false);
};
// Render sidebar navigation
const renderSidebarNav = () => (
<nav className="p-3">
<ul className="space-y-0.5 px-2">
{mailboxes.map((folder) => (
<li key={folder}>
<Button
variant={currentFolder === folder ? 'secondary' : 'ghost'}
className={`w-full justify-start py-2 ${
currentFolder === folder ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'
}`}
onClick={() => handleMailboxChange(folder)}
>
<div className="flex items-center">
{React.createElement(getFolderIcon(folder), { className: "h-4 w-4" })}
<span className="ml-2">{formatFolderName(folder)}</span>
</div>
</Button>
</li>
))}
</ul>
</nav>
);
// Check login on mount
useEffect(() => {
// Check if the user is logged in after a short delay
const timer = setTimeout(() => {
if (error?.includes('Not authenticated') || error?.includes('No email credentials found')) {
setShowLoginNeeded(true);
}
}, 2000);
return () => clearTimeout(timer);
}, [error]);
// Go to login page
const handleGoToLogin = () => {
router.push('/courrier/login');
};
return (
<>
<SimplifiedLoadingFix />
{/* Main layout */}
<main className="w-full h-screen bg-black">
<div className="w-full h-full px-4 pt-12 pb-4">
<div className="flex h-full bg-carnet-bg">
{/* Sidebar */}
<div className={`${sidebarOpen ? 'w-60' : 'w-16'} bg-white/95 backdrop-blur-sm border-r border-gray-100 flex flex-col transition-all duration-300 ease-in-out
${mobileSidebarOpen ? 'fixed inset-y-0 left-0 z-40' : 'hidden'} md:block`}>
{/* Courrier Title */}
<div className="p-3 border-b border-gray-100">
<div className="flex items-center gap-2">
<Mail className="h-6 w-6 text-gray-600" />
<span className="text-xl font-semibold text-gray-900">COURRIER</span>
</div>
</div>
{/* Compose button and refresh button */}
<div className="p-2 border-b border-gray-100 flex items-center gap-2">
<Button
className="flex-1 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center justify-center transition-all py-1.5 text-sm"
onClick={handleComposeNew}
>
<div className="flex items-center gap-2">
<PlusIcon className="h-3.5 w-3.5" />
<span>Compose</span>
</div>
</Button>
<Button
variant="ghost"
size="icon"
className="h-9 w-9 text-gray-400 hover:text-gray-600"
onClick={() => {
setLoading(true);
loadEmails().finally(() => setLoading(false));
}}
>
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Accounts Section */}
<div className="p-3 border-b border-gray-100">
<Button
variant="ghost"
className="w-full justify-between mb-2 text-sm font-medium text-gray-500"
onClick={() => setAccountsDropdownOpen(!accountsDropdownOpen)}
>
<span>Accounts</span>
{accountsDropdownOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
{accountsDropdownOpen && (
<div className="space-y-1 pl-2">
{accounts.map(account => (
<div key={account.id} className="relative group">
<Button
variant="ghost"
className="w-full justify-between px-2 py-1.5 text-sm group"
onClick={() => setSelectedAccount(account)}
>
<div className="flex items-center gap-2">
<div className={`w-2.5 h-2.5 rounded-full ${account.color}`}></div>
<span className="font-medium text-gray-700">{account.name}</span>
</div>
</Button>
{/* Show folders for email accounts (not for "All" account) without the "Folders" header */}
{account.id !== 0 && (
<div className="pl-4 mt-1 mb-2 space-y-0.5 border-l border-gray-200">
{account.folders && account.folders.length > 0 ? (
account.folders.map((folder) => (
<Button
key={folder}
variant="ghost"
className={`w-full justify-start py-1 px-2 text-xs ${
currentView === folder ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900'
}`}
onClick={(e) => {
e.stopPropagation();
handleMailboxChange(folder);
}}
>
<div className="flex items-center justify-between w-full gap-1.5">
<div className="flex items-center gap-1.5">
{React.createElement(getFolderIcon(folder), { className: "h-3.5 w-3.5" })}
<span className="truncate">{folder}</span>
</div>
{folder === 'INBOX' && unreadCount > 0 && (
<span className="ml-auto bg-blue-600 text-white text-xs px-1.5 py-0.5 rounded-full text-[10px]">
{unreadCount}
</span>
)}
</div>
</Button>
))
) : (
<div className="px-2 py-2">
<div className="flex flex-col space-y-2">
{/* Create placeholder folder items with shimmer effect */}
{Array.from({ length: 5 }).map((_, index) => (
<div key={index} className="flex items-center gap-1.5 animate-pulse">
<div className="h-3.5 w-3.5 bg-gray-200 rounded-sm"></div>
<div className="h-3 w-24 bg-gray-200 rounded"></div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
))}
</div>
)}
</div>
{/* Navigation */}
{renderSidebarNav()}
</div>
{/* Email List and Content View */}
<div className="flex-1 flex overflow-hidden">
{/* Email List */}
<EmailList
emails={emails}
selectedEmailIds={selectedEmailIds}
selectedEmail={selectedEmail}
currentFolder={currentFolder}
isLoading={isLoading}
totalEmails={emails.length}
hasMoreEmails={hasMoreEmails}
onSelectEmail={handleEmailSelect}
onToggleSelect={toggleEmailSelection}
onToggleSelectAll={toggleSelectAll}
onBulkAction={handleBulkAction}
onToggleStarred={toggleStarred}
onLoadMore={handleLoadMore}
onSearch={searchEmails}
/>
{/* Email Content View */}
<div className="flex-1 bg-white/95 backdrop-blur-sm flex flex-col">
{selectedEmail ? (
<EmailDetailView
email={selectedEmail}
onBack={() => handleEmailSelect('')}
onReply={handleReply}
onReplyAll={handleReplyAll}
onForward={handleForward}
onToggleStar={() => toggleStarred(selectedEmail.id)}
/>
) : (
<div className="flex-1 flex flex-col items-center justify-center text-center p-8">
<Mail className="h-12 w-12 text-gray-300 mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-1">Select an email to read</h3>
<p className="text-sm text-gray-500 max-w-sm">
Choose an email from the list or compose a new message to get started.
</p>
<Button
className="mt-6 bg-blue-600 hover:bg-blue-700"
onClick={handleComposeNew}
>
<PlusIcon className="mr-2 h-4 w-4" />
Compose New
</Button>
</div>
)}
</div>
</div>
</div>
</div>
</main>
{/* Login needed alert */}
<LoginNeededAlert
show={showLoginNeeded}
onLogin={handleGoToLogin}
onClose={() => setShowLoginNeeded(false)}
/>
{/* Compose Modal */}
{showComposeModal && (
<ComposeEmail
initialEmail={selectedEmail}
type={composeType}
onClose={() => setShowComposeModal(false)}
onSend={async (emailData: EmailData) => {
await sendEmail(emailData);
}}
/>
)}
{/* Delete Confirmation Dialog */}
<DeleteConfirmDialog
show={showDeleteConfirm}
selectedCount={selectedEmailIds.length}
onConfirm={handleDeleteConfirm}
onCancel={() => setShowDeleteConfirm(false)}
/>
</>
);
}