Neah/app/courrier/page.tsx
2025-04-26 23:18:51 +02:00

389 lines
13 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import {
Mail, Loader2, AlertCircle,
ChevronLeft, ChevronRight, Reply, ReplyAll, Forward,
Star, FolderOpen
} 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 EmailContent from '@/components/email/EmailContent';
import EmailHeader from '@/components/email/EmailHeader';
import ComposeEmail from '@/components/email/ComposeEmail';
// 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>
);
}
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();
// Local state
const [showComposeModal, setShowComposeModal] = useState(false);
const [composeData, setComposeData] = useState<EmailData | null>(null);
const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new');
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [showLoginNeeded, setShowLoginNeeded] = useState(false);
// 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 reply or forward
const handleReplyOrForward = (type: 'reply' | 'reply-all' | 'forward') => {
if (!selectedEmail) return;
const formattedEmail = formatEmailForAction(selectedEmail, type);
if (!formattedEmail) return;
setComposeData({
to: formattedEmail.to,
cc: formattedEmail.cc,
subject: formattedEmail.subject,
body: formattedEmail.body,
});
setComposeType(type);
setShowComposeModal(true);
};
// Handle compose new email
const handleComposeNew = () => {
setComposeData({
to: '',
subject: '',
body: '',
});
setComposeType('new');
setShowComposeModal(true);
};
// Handle sending email
const handleSend = async (emailData: EmailData) => {
await sendEmail(emailData);
setShowComposeModal(false);
setComposeData(null);
// Refresh the Sent folder if we're currently viewing it
if (currentFolder.toLowerCase() === 'sent') {
loadEmails();
}
};
// Handle delete confirmation
const handleDeleteConfirm = async () => {
await deleteEmails(selectedEmailIds);
setShowDeleteConfirm(false);
};
// 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');
};
// If there's a critical error, show error dialog
if (error && !isLoading && emails.length === 0 && !showLoginNeeded) {
return (
<div className="flex h-screen items-center justify-center">
<Alert variant="destructive" className="max-w-md">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error loading emails</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
</div>
);
}
// Create a properly formatted email message from composeData for the ComposeEmail component
const createEmailMessage = () => {
if (!composeData) return null;
return {
id: 'temp-id',
messageId: '',
subject: composeData.subject || '',
from: [{ name: '', address: '' }],
to: [{ name: '', address: composeData.to || '' }],
cc: composeData.cc ? [{ name: '', address: composeData.cc }] : [],
bcc: composeData.bcc ? [{ name: '', address: composeData.bcc }] : [],
date: new Date(),
content: composeData.body || '',
html: composeData.body || '',
hasAttachments: false
};
};
// 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' });
}
};
return (
<div className="h-screen">
<SimplifiedLoadingFix />
{/* Login required dialog */}
<AlertDialog open={showLoginNeeded} onOpenChange={setShowLoginNeeded}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Login Required</AlertDialogTitle>
<AlertDialogDescription>
You need to configure your email account credentials before you can access your emails.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleGoToLogin}>Setup Email</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Delete confirmation dialog */}
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirm Deletion</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete {selectedEmailIds.length} {selectedEmailIds.length === 1 ? 'email' : 'emails'}?
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteConfirm}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Compose email dialog */}
<Dialog open={showComposeModal} onOpenChange={setShowComposeModal}>
<DialogContent className="sm:max-w-[800px] h-[80vh] p-0">
<ComposeEmail
initialEmail={createEmailMessage()}
type={composeType}
onClose={() => setShowComposeModal(false)}
onSend={handleSend}
/>
</DialogContent>
</Dialog>
{/* Main email interface */}
<div className="h-full flex flex-col">
{/* Email header */}
<EmailHeader
onSearch={searchEmails}
/>
{/* Main content area - fixed-width sidebar and list panel layout */}
<div className="flex-1 flex overflow-hidden">
{/* Sidebar - fixed width 64px */}
<div className="w-64 flex-shrink-0">
<EmailSidebar
currentFolder={currentFolder}
folders={mailboxes}
onFolderChange={changeFolder}
onRefresh={() => loadEmails(false)}
onCompose={handleComposeNew}
isLoading={isLoading}
/>
</div>
{/* Email list - fixed width 320px */}
<div className="w-[320px] flex-shrink-0">
<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}
/>
</div>
{/* Email content - takes remaining space */}
<div className="flex-1 h-full overflow-hidden flex flex-col">
{selectedEmail ? (
<>
{/* Email actions header */}
<div className="flex-none px-4 py-3 border-b border-gray-100">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 min-w-0 flex-1">
<div className="min-w-0 max-w-[500px]">
<h2 className="text-lg font-semibold text-gray-900 truncate">
{selectedEmail.subject || '(No subject)'}
</h2>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<div className="flex items-center border-l border-gray-200 pl-4">
<Button
variant="ghost"
className="text-gray-600 hover:text-gray-900 px-2 py-1"
onClick={() => handleReplyOrForward('reply')}
>
Reply
</Button>
<Button
variant="ghost"
className="text-gray-600 hover:text-gray-900 px-2 py-1"
onClick={() => handleReplyOrForward('reply-all')}
>
Reply All
</Button>
<Button
variant="ghost"
className="text-gray-600 hover:text-gray-900 px-2 py-1"
onClick={() => handleReplyOrForward('forward')}
>
Forward
</Button>
</div>
</div>
</div>
</div>
{/* Email content with avatar and proper scroll area */}
<ScrollArea className="flex-1">
<EmailContent email={selectedEmail} />
</ScrollArea>
</>
) : (
<div className="flex flex-col items-center justify-center h-full">
<Mail className="h-12 w-12 text-gray-400 mb-4" />
<p className="text-gray-500">Select an email to view its contents</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}