Neah/components/email/EmailLayout.tsx
2025-04-30 15:43:16 +02:00

323 lines
9.7 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import {
Inbox, Star, Send, File, Trash, RefreshCw, Plus,
Search, Loader2, MailOpen, Mail, ArchiveIcon
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Badge } from '@/components/ui/badge';
import EmailPanel from './EmailPanel';
import { EmailMessage } from '@/lib/services/email-service';
import { useToast } from "@/hooks/use-toast";
import { sendEmail } from '@/lib/services/email-service';
import { useSession } from "next-auth/react";
import { cn } from '@/lib/utils';
import EmailList from './EmailList';
import { Email } from '@/hooks/use-courrier';
interface EmailLayoutProps {
className?: string;
previewModeEnabled?: boolean;
}
export default function EmailLayout({
className = '',
previewModeEnabled = false
}: EmailLayoutProps) {
// Email state
const [emails, setEmails] = useState<EmailMessage[]>([]);
const [selectedEmail, setSelectedEmail] = useState<{
emailId: string;
accountId: string;
folder: string;
} | null>(null);
const [currentFolder, setCurrentFolder] = useState<string>('INBOX');
const [folders, setFolders] = useState<string[]>([]);
const [mailboxes, setMailboxes] = useState<string[]>([]);
// UI state
const [loading, setLoading] = useState<boolean>(true);
const [searching, setSearching] = useState<boolean>(false);
const [searchQuery, setSearchQuery] = useState<string>('');
const [page, setPage] = useState<number>(1);
const [hasMore, setHasMore] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [isSending, setIsSending] = useState<boolean>(false);
const [showComposeModal, setShowComposeModal] = useState<boolean>(false);
// Get toast and session
const { toast } = useToast();
const { data: session } = useSession();
// Load emails on component mount and when folder changes
useEffect(() => {
loadEmails();
}, [currentFolder, page]);
// Function to load emails
const loadEmails = async (refresh = false) => {
if (refresh) {
setPage(1);
}
setLoading(true);
setError(null);
try {
// Construct the API endpoint URL with parameters
const queryParams = new URLSearchParams({
folder: currentFolder,
page: page.toString(),
perPage: '20'
});
if (searchQuery) {
queryParams.set('search', searchQuery);
}
const response = await fetch(`/api/courrier?${queryParams.toString()}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch emails');
}
const data = await response.json();
if (refresh || page === 1) {
setEmails(data.emails || []);
} else {
// Append emails for pagination
setEmails(prev => [...prev, ...(data.emails || [])]);
}
// Update available folders if returned from API
if (data.mailboxes && data.mailboxes.length > 0) {
setMailboxes(data.mailboxes);
// Create a nicer list of standard folders
const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk'];
const customFolders = data.mailboxes.filter(
(folder: string) => !standardFolders.includes(folder)
);
// Combine standard folders that exist with custom folders
const availableFolders = [
...standardFolders.filter(f => data.mailboxes.includes(f)),
...customFolders
];
setFolders(availableFolders);
}
// Check if there are more emails to load
setHasMore(data.emails && data.emails.length >= 20);
} catch (err) {
console.error('Error loading emails:', err);
setError(err instanceof Error ? err.message : 'Failed to load emails');
} finally {
setLoading(false);
}
};
// Handle folder change
const handleFolderChange = (folder: string) => {
setCurrentFolder(folder);
setSelectedEmail(null);
setPage(1);
setSearchQuery('');
};
// Handle email selection
const handleEmailSelect = (emailId: string, accountId: string, folder: string) => {
setSelectedEmail({ emailId, accountId, folder });
};
// Handle search
const handleSearch = () => {
if (searchQuery.trim()) {
setSearching(true);
setPage(1);
loadEmails(true);
}
};
// Handle refreshing emails
const handleRefresh = () => {
loadEmails(true);
};
// Handle composing a new email
const handleComposeNew = () => {
setSelectedEmail(null);
// The compose functionality will be handled by the EmailPanel component
};
// Handle email sending
const handleSendEmail = async (emailData: {
to: string;
cc?: string;
bcc?: string;
subject: string;
body: string;
attachments?: Array<{
name: string;
content: string;
type: string;
}>;
}): Promise<void> => {
setIsSending(true);
try {
// The sendEmail function requires userId as the first parameter
const result = await sendEmail(session?.user?.id as string, emailData);
if (result.success) {
toast({
title: "Success",
description: "Email sent successfully"
});
setShowComposeModal(false);
} else {
toast({
variant: "destructive",
title: "Error",
description: `Failed to send email: ${result.error || 'Unknown error'}`
});
}
} catch (error) {
console.error('Error sending email:', error);
toast({
variant: "destructive",
title: "Error",
description: "Failed to send email"
});
} finally {
setIsSending(false);
}
};
// Format the date in a readable format
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
// Check if date is today
if (date >= today) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// Check if date is yesterday
if (date >= yesterday) {
return 'Yesterday';
}
// Check if date is this year
if (date.getFullYear() === now.getFullYear()) {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
// Date is from a previous year
return date.toLocaleDateString([], { year: 'numeric', month: 'short', day: 'numeric' });
};
// Get folder icon
const getFolderIcon = (folder: string) => {
switch (folder.toLowerCase()) {
case 'inbox':
return <Inbox className="h-4 w-4" />;
case 'sent':
case 'sent items':
return <Send className="h-4 w-4" />;
case 'drafts':
return <File className="h-4 w-4" />;
case 'trash':
case 'deleted':
case 'bin':
return <Trash className="h-4 w-4" />;
case 'junk':
case 'spam':
return <ArchiveIcon className="h-4 w-4" />;
default:
return <MailOpen className="h-4 w-4" />;
}
};
return (
<div className={cn("h-full flex flex-col bg-white text-slate-900", className)}>
{/* Main email interface */}
<div className="flex-1 flex h-full overflow-hidden">
{/* Sidebar */}
<div className="hidden md:flex md:w-56 border-r border-gray-100 flex-col">
{/* Sidebar content */}
{/* New email button */}
<div className="p-4">
<Button
className="w-full"
onClick={handleComposeNew}
>
<Plus className="h-4 w-4 mr-2" />
New Email
</Button>
</div>
{/* Folder navigation */}
<ScrollArea className="flex-1">
<div className="p-2 space-y-1">
{folders.map((folder) => (
<Button
key={folder}
variant={currentFolder === folder ? "secondary" : "ghost"}
className="w-full justify-start"
onClick={() => handleFolderChange(folder)}
>
{getFolderIcon(folder)}
<span className="ml-2">{folder}</span>
</Button>
))}
</div>
</ScrollArea>
</div>
{/* Email list */}
<div className="w-1/3 border-r border-gray-100 overflow-hidden">
<EmailList
emails={emails}
selectedEmailIds={selectedEmail ? [selectedEmail.emailId] : []}
selectedEmail={selectedEmail ? {
id: selectedEmail.emailId,
accountId: selectedEmail.accountId,
folder: selectedEmail.folder
} as Email : null}
currentFolder={currentFolder}
isLoading={loading}
totalEmails={emails.length}
hasMoreEmails={hasMore}
onSelectEmail={handleEmailSelect}
onToggleSelect={() => {}}
onToggleSelectAll={() => {}}
onBulkAction={() => {}}
onToggleStarred={() => {}}
onLoadMore={loadEmails}
onSearch={handleSearch}
previewMode={previewModeEnabled}
/>
</div>
{/* Email preview */}
<div className="flex-1 h-full overflow-hidden">
<EmailPanel
selectedEmail={selectedEmail}
onSendEmail={handleSendEmail}
/>
</div>
</div>
</div>
);
}