import { useState, useCallback, useEffect } from 'react'; import { useSession } from 'next-auth/react'; import { useToast } from './use-toast'; import { formatEmailForReplyOrForward } from '@/lib/utils/email-formatter'; export interface EmailAddress { name: string; address: string; } export interface Email { id: string; from: EmailAddress[]; to: EmailAddress[]; cc?: EmailAddress[]; bcc?: EmailAddress[]; subject: string; content: string; preview?: string; date: string; read: boolean; starred: boolean; attachments?: { filename: string; contentType: string; size: number; content?: string }[]; folder: string; hasAttachments: boolean; contentFetched?: boolean; } export interface EmailListResult { emails: Email[]; totalEmails: number; page: number; perPage: number; totalPages: number; folder: string; mailboxes: string[]; } export interface EmailData { to: string; cc?: string; bcc?: string; subject: string; body: string; attachments?: Array<{ name: string; content: string; type: string; }>; } export type MailFolder = string; // Hook for managing email operations export const useCourrier = () => { // State for email data const [emails, setEmails] = useState([]); const [selectedEmail, setSelectedEmail] = useState(null); const [selectedEmailIds, setSelectedEmailIds] = useState([]); const [currentFolder, setCurrentFolder] = useState('INBOX'); const [mailboxes, setMailboxes] = useState([]); // State for UI const [isLoading, setIsLoading] = useState(false); const [isSending, setIsSending] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(20); const [totalEmails, setTotalEmails] = useState(0); const [totalPages, setTotalPages] = useState(0); // Auth and notifications const { data: session } = useSession(); const { toast } = useToast(); // Load emails when folder or page changes useEffect(() => { if (session?.user?.id) { loadEmails(); } }, [currentFolder, page, perPage, session?.user?.id]); // Load emails from the server const loadEmails = useCallback(async (isLoadMore = false) => { if (!session?.user?.id) return; setIsLoading(true); setError(null); try { // Build query params const queryParams = new URLSearchParams({ folder: currentFolder, page: page.toString(), perPage: perPage.toString() }); if (searchQuery) { queryParams.set('search', searchQuery); } // Fetch emails from API 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: EmailListResult = await response.json(); // Update state with the fetched data if (isLoadMore) { setEmails(prev => [...prev, ...data.emails]); } else { setEmails(data.emails); } setTotalEmails(data.totalEmails); setTotalPages(data.totalPages); // Update available mailboxes if provided if (data.mailboxes && data.mailboxes.length > 0) { setMailboxes(data.mailboxes); } // Clear selection if not loading more if (!isLoadMore) { setSelectedEmail(null); setSelectedEmailIds([]); } } catch (err) { console.error('Error loading emails:', err); setError(err instanceof Error ? err.message : 'Failed to load emails'); toast({ variant: "destructive", title: "Error", description: err instanceof Error ? err.message : 'Failed to load emails' }); } finally { setIsLoading(false); } }, [currentFolder, page, perPage, searchQuery, session?.user?.id, toast]); // Fetch a single email's content const fetchEmailContent = useCallback(async (emailId: string) => { try { const response = await fetch(`/api/courrier/${emailId}?folder=${encodeURIComponent(currentFolder)}`); if (!response.ok) { throw new Error(`Failed to fetch email content: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching email content:', error); throw error; } }, [currentFolder]); // Select an email to view const handleEmailSelect = useCallback(async (emailId: string) => { setIsLoading(true); try { // Find the email in the current list const email = emails.find(e => e.id === emailId); if (!email) { throw new Error('Email not found'); } // If content is not fetched, get the full content if (!email.contentFetched) { const fullEmail = await fetchEmailContent(emailId); // Merge the full content with the email const updatedEmail = { ...email, content: fullEmail.content, attachments: fullEmail.attachments, contentFetched: true }; // Update the email in the list setEmails(emails.map(e => e.id === emailId ? updatedEmail : e)); setSelectedEmail(updatedEmail); } else { setSelectedEmail(email); } // Mark the email as read if it's not already if (!email.read) { markEmailAsRead(emailId, true); } } catch (err) { console.error('Error selecting email:', err); toast({ variant: "destructive", title: "Error", description: "Could not load email content" }); } finally { setIsLoading(false); } }, [emails, fetchEmailContent, toast]); // Mark an email as read/unread const markEmailAsRead = useCallback(async (emailId: string, isRead: boolean) => { try { const response = await fetch(`/api/courrier/${emailId}/mark-read`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ isRead, folder: currentFolder }) }); if (!response.ok) { throw new Error('Failed to mark email as read'); } // Update the email in the list setEmails(emails.map(email => email.id === emailId ? { ...email, read: isRead } : email )); // If the selected email is the one being marked, update it too if (selectedEmail && selectedEmail.id === emailId) { setSelectedEmail({ ...selectedEmail, read: isRead }); } return true; } catch (error) { console.error('Error marking email as read:', error); return false; } }, [emails, selectedEmail, currentFolder]); // Toggle starred status for an email const toggleStarred = useCallback(async (emailId: string) => { const email = emails.find(e => e.id === emailId); if (!email) return; const newStarredStatus = !email.starred; try { const response = await fetch(`/api/courrier/${emailId}/star`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ starred: newStarredStatus, folder: currentFolder }) }); if (!response.ok) { throw new Error('Failed to toggle star status'); } // Update the email in the list setEmails(emails.map(email => email.id === emailId ? { ...email, starred: newStarredStatus } : email )); // If the selected email is the one being starred, update it too if (selectedEmail && selectedEmail.id === emailId) { setSelectedEmail({ ...selectedEmail, starred: newStarredStatus }); } } catch (error) { console.error('Error toggling star status:', error); toast({ variant: "destructive", title: "Error", description: "Could not update star status" }); } }, [emails, selectedEmail, currentFolder, toast]); // Send an email const sendEmail = useCallback(async (emailData: EmailData) => { if (!session?.user?.id) { toast({ variant: "destructive", title: "Error", description: "You must be logged in to send emails" }); return { success: false, error: "Not authenticated" }; } setIsSending(true); try { const response = await fetch('/api/courrier/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(emailData) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to send email'); } toast({ title: "Success", description: "Email sent successfully" }); return { success: true, messageId: result.messageId }; } catch (error) { console.error('Error sending email:', error); toast({ variant: "destructive", title: "Error", description: error instanceof Error ? error.message : 'Failed to send email' }); return { success: false, error: error instanceof Error ? error.message : 'Failed to send email' }; } finally { setIsSending(false); } }, [session?.user?.id, toast]); // Delete selected emails const deleteEmails = useCallback(async (emailIds: string[]) => { if (emailIds.length === 0) return; setIsDeleting(true); try { const response = await fetch('/api/courrier/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailIds, folder: currentFolder }) }); if (!response.ok) { throw new Error('Failed to delete emails'); } // Remove the deleted emails from the list setEmails(emails.filter(email => !emailIds.includes(email.id))); // Clear selection if the selected email was deleted if (selectedEmail && emailIds.includes(selectedEmail.id)) { setSelectedEmail(null); } // Clear selected IDs setSelectedEmailIds([]); toast({ title: "Success", description: `${emailIds.length} email(s) deleted` }); } catch (error) { console.error('Error deleting emails:', error); toast({ variant: "destructive", title: "Error", description: "Failed to delete emails" }); } finally { setIsDeleting(false); } }, [emails, selectedEmail, currentFolder, toast]); // Toggle selection of an email const toggleEmailSelection = useCallback((emailId: string) => { setSelectedEmailIds(prev => { if (prev.includes(emailId)) { return prev.filter(id => id !== emailId); } else { return [...prev, emailId]; } }); }, []); // Select all emails const toggleSelectAll = useCallback(() => { if (selectedEmailIds.length === emails.length) { setSelectedEmailIds([]); } else { setSelectedEmailIds(emails.map(email => email.id)); } }, [emails, selectedEmailIds]); // Change the current folder const changeFolder = useCallback((folder: MailFolder) => { setCurrentFolder(folder); setPage(1); setSelectedEmail(null); setSelectedEmailIds([]); }, []); // Search emails const searchEmails = useCallback((query: string) => { setSearchQuery(query); setPage(1); loadEmails(); }, [loadEmails]); // Format an email for reply or forward const formatEmailForAction = useCallback((email: Email, type: 'reply' | 'reply-all' | 'forward') => { if (!email) return null; return formatEmailForReplyOrForward(email, type); }, []); // Return all the functionality and state values return { // Data emails, selectedEmail, selectedEmailIds, currentFolder, mailboxes, isLoading, isSending, isDeleting, error, searchQuery, page, perPage, totalEmails, totalPages, // Functions loadEmails, handleEmailSelect, markEmailAsRead, toggleStarred, sendEmail, deleteEmails, toggleEmailSelection, toggleSelectAll, changeFolder, searchEmails, formatEmailForAction, setPage, setPerPage, setSearchQuery, }; };