diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 70a51882..a8cced06 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -29,9 +29,10 @@ 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 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'; @@ -89,35 +90,13 @@ export default function CourrierPage() { setPage, } = useCourrier(); - // Local state + // UI state const [showComposeModal, setShowComposeModal] = useState(false); - const [composeData, setComposeData] = useState(null); const [composeType, setComposeType] = useState<'new' | 'reply' | 'reply-all' | 'forward'>('new'); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showLoginNeeded, setShowLoginNeeded] = useState(false); - - // States to match the provided implementation const [sidebarOpen, setSidebarOpen] = useState(true); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); - const [isReplying, setIsReplying] = useState(false); - const [isForwarding, setIsForwarding] = useState(false); - const [accountsDropdownOpen, setAccountsDropdownOpen] = useState(false); - const [showCompose, setShowCompose] = useState(false); - const [composeTo, setComposeTo] = useState(''); - const [composeCc, setComposeCc] = useState(''); - const [composeBcc, setComposeBcc] = useState(''); - const [composeSubject, setComposeSubject] = useState(''); - const [composeBody, setComposeBody] = useState(''); - const [showCc, setShowCc] = useState(false); - const [showBcc, setShowBcc] = useState(false); - const [attachments, setAttachments] = useState([]); - - // Mock accounts - const [accounts, setAccounts] = useState([ - { id: 0, name: 'All', email: '', color: 'bg-gray-500' }, - { id: 1, name: 'Mail', email: 'user@example.com', color: 'bg-blue-500' } - ]); - const [selectedAccount, setSelectedAccount] = useState(null); // Check for more emails const hasMoreEmails = page < totalPages; @@ -158,77 +137,33 @@ export default function CourrierPage() { } }; - // Handle email reply or forward - const handleReplyOrForward = (type: 'reply' | 'reply-all' | 'forward') => { + // Handle email actions + const handleReply = () => { if (!selectedEmail) return; - - const formattedEmail = formatEmailForAction(selectedEmail, type); - if (!formattedEmail) return; - - setComposeTo(formattedEmail.to || ''); - setComposeCc(formattedEmail.cc || ''); - setComposeSubject(formattedEmail.subject || ''); - setComposeBody(formattedEmail.body || ''); - - if (type === 'forward') { - setIsForwarding(true); - } else { - setIsReplying(true); - } - - setShowCompose(true); - setShowCc(type === 'reply-all'); + setComposeType('reply'); + setShowComposeModal(true); }; - - // Handle compose new email + + const handleReplyAll = () => { + if (!selectedEmail) return; + setComposeType('reply-all'); + setShowComposeModal(true); + }; + + const handleForward = () => { + if (!selectedEmail) return; + setComposeType('forward'); + setShowComposeModal(true); + }; + const handleComposeNew = () => { - setComposeTo(''); - setComposeCc(''); - setComposeBcc(''); - setComposeSubject(''); - setComposeBody(''); - setShowCc(false); - setShowBcc(false); - setIsReplying(false); - setIsForwarding(false); - setShowCompose(true); + setComposeType('new'); + setShowComposeModal(true); }; // Handle sending email - const handleSend = async () => { - if (!composeTo) { - alert('Please specify at least one recipient'); - return; - } - - try { - await sendEmail({ - to: composeTo, - cc: composeCc, - bcc: composeBcc, - subject: composeSubject, - body: composeBody, - }); - - // Clear compose form and close modal - setComposeTo(''); - setComposeCc(''); - setComposeBcc(''); - setComposeSubject(''); - setComposeBody(''); - setAttachments([]); - setShowCompose(false); - setIsReplying(false); - setIsForwarding(false); - - // Refresh the Sent folder if we're currently viewing it - if (currentFolder.toLowerCase() === 'sent') { - loadEmails(); - } - } catch (error) { - console.error('Error sending email:', error); - alert('Failed to send email. Please try again.'); - } + const handleSendEmail = async (emailData: EmailData) => { + return await sendEmail(emailData); }; // Handle delete confirmation @@ -254,406 +189,115 @@ export default function CourrierPage() { router.push('/courrier/login'); }; - // Handle mailbox change - const handleMailboxChange = (newMailbox: string) => { - changeFolder(newMailbox); - }; - - // 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' }); - } - }; - - // Render sidebar navigation - const renderSidebarNav = () => ( - - ); - - // Helper to format folder names - const formatFolderName = (folder: string) => { - return folder.charAt(0).toUpperCase() + folder.slice(1).toLowerCase(); - }; - - // Get account color - const getAccountColor = (accountId: number) => { - const account = accounts.find(acc => acc.id === accountId); - return account ? account.color : 'bg-gray-500'; - }; - - // Helper to get folder icons - const getFolderIcon = (folder: string) => { - const folderLower = folder.toLowerCase(); - - if (folderLower.includes('inbox')) { - return ; - } else if (folderLower.includes('sent')) { - return ; - } else if (folderLower.includes('trash')) { - return ; - } else if (folderLower.includes('archive')) { - return ; - } else if (folderLower.includes('draft')) { - return ; - } else if (folderLower.includes('spam') || folderLower.includes('junk')) { - return ; - } else { - return ; - } - }; - - // Render email list component - const renderEmailList = () => ( - - ); - - // Render email content based on the email body - const renderEmailContent = (email: any) => { - try { - // For simple rendering in this example, we'll just display the content directly - return
; - } catch (e) { - console.error('Error rendering email:', e); - return
Failed to render email content
; - } - }; - - // Email list wrapper with preview panel - const renderEmailListWrapper = () => ( -
- {/* Email list panel */} - {renderEmailList()} - - {/* Preview panel - will automatically take remaining space */} -
- {selectedEmail ? ( - <> - {/* Email actions header */} -
-
-
- -
-

- {selectedEmail.subject} -

-
-
-
-
- - - - -
-
-
-
- - {/* Scrollable content area */} - -
- - - {(selectedEmail.from?.[0]?.name || '').charAt(0) || (selectedEmail.from?.[0]?.address || '').charAt(0) || '?'} - - -
-

- {selectedEmail.from?.[0]?.name || ''} <{selectedEmail.from?.[0]?.address || ''}> -

-

- to {selectedEmail.to?.[0]?.address || ''} -

- {selectedEmail.cc && selectedEmail.cc.length > 0 && ( -

- cc {selectedEmail.cc.map(c => c.address).join(', ')} -

- )} -
-
- {formatDate(selectedEmail.date)} -
-
- -
- {renderEmailContent(selectedEmail)} -
-
- - ) : ( -
- -

Select an email to view its contents

-
- )} -
-
- ); - - // Delete confirmation dialog - const renderDeleteConfirmDialog = () => ( - - - - Delete Emails - - Are you sure you want to delete {selectedEmailIds.length} selected email{selectedEmailIds.length > 1 ? 's' : ''}? This action cannot be undone. - - - - Cancel - Delete - - - - ); - - // If there's a critical error, show error dialog - if (error && !isLoading && emails.length === 0 && !showLoginNeeded) { - return ( -
- - - Error loading emails - {error} - -
- ); - } - return ( - <> +
+ {/* Loading Fix for development */} - {/* Login required dialog */} - - - - Login Required - - You need to configure your email account credentials before you can access your emails. - - - - Cancel - Setup Email - - - + {/* Login needed alert */} + setShowLoginNeeded(false)} + /> - {/* Main layout */} -
-
-
- {/* Sidebar */} -
- {/* Courrier Title */} -
-
- - COURRIER -
-
- - {/* Compose button and refresh button */} -
- + {/* Main Content */} +
+ {/* Sidebar (Desktop) */} + {sidebarOpen && ( + + )} + + {/* Email List and Content View */} +
+ {/* Email List */} + + + {/* Email Content View */} +
+ {selectedEmail ? ( + handleEmailSelect('')} + onReply={handleReply} + onReplyAll={handleReplyAll} + onForward={handleForward} + onToggleStar={() => toggleStarred(selectedEmail.id)} + /> + ) : ( +
+ +

Select an email to read

+

+ Choose an email from the list or compose a new message to get started. +

- - {/* Accounts Section */} -
- - - {accountsDropdownOpen && ( -
- {accounts.map(account => ( -
- -
- ))} -
- )} -
- - {/* Navigation */} - {renderSidebarNav()} -
- - {/* Main content area */} -
- {/* Email list panel */} - {renderEmailListWrapper()} -
+ )}
-
- - {/* Compose Email Modal */} - { - console.log('Email sent:', email); - setShowCompose(false); - setIsReplying(false); - setIsForwarding(false); - return Promise.resolve(); - }} - onCancel={() => { - setShowCompose(false); - setComposeTo(''); - setComposeCc(''); - setComposeBcc(''); - setComposeSubject(''); - setComposeBody(''); - setShowCc(false); - setShowBcc(false); - setAttachments([]); - setIsReplying(false); - setIsForwarding(false); - }} +
+ + {/* Compose Modal */} + + + setShowComposeModal(false)} + onSend={async (emailData: EmailData) => { + await sendEmail(emailData); + }} + /> + + + + {/* Delete Confirmation Dialog */} + setShowDeleteConfirm(false)} /> - {renderDeleteConfirmDialog()} - +
); } \ No newline at end of file diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 6e727edb..8e539395 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -11,6 +11,11 @@ import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/componen import DOMPurify from 'isomorphic-dompurify'; import { Label } from '@/components/ui/label'; +// Import sub-components +import ComposeEmailHeader from './ComposeEmailHeader'; +import ComposeEmailForm from './ComposeEmailForm'; +import ComposeEmailFooter from './ComposeEmailFooter'; + // Import ONLY from the centralized formatter import { formatForwardedEmail, @@ -148,10 +153,6 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { type: string; }>>([]); - // Refs - const editorRef = useRef(null); - const attachmentInputRef = useRef(null); - // Initialize the form when replying to or forwarding an email useEffect(() => { if (initialEmail && type !== 'new') { @@ -187,148 +188,47 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { setSubject(subject); setEmailContent(content); } - - // For type safety - const isReplyType = (t: 'new' | 'reply' | 'reply-all' | 'forward'): t is 'reply' | 'reply-all' | 'forward' => - t === 'reply' || t === 'reply-all' || t === 'forward'; - - // Focus editor after initializing content - setTimeout(() => { - if (isReplyType(type) && editorRef.current) { - // For replies/forwards, focus contentEditable - editorRef.current.focus(); - - try { - // Place cursor at the beginning - const selection = window.getSelection(); - if (selection) { - const range = document.createRange(); - - if (editorRef.current.firstChild) { - range.setStart(editorRef.current.firstChild, 0); - range.collapse(true); - - selection.removeAllRanges(); - selection.addRange(range); - } - } - } catch (e) { - console.error('Error positioning cursor:', e); - } - } else { - // For new emails, focus the textarea - const textarea = document.querySelector('textarea'); - textarea?.focus(); - } - }, 100); - } catch (error) { - console.error('Error formatting email:', error); + } catch (err) { + console.error('Error formatting email for reply/forward:', err); } } }, [initialEmail, type]); - - // Handle attachment selection - const handleAttachmentClick = () => { - attachmentInputRef.current?.click(); - }; - - // Process selected files - const handleFileSelection = async (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files || files.length === 0) return; - - // Read files as data URLs - for (const file of files) { - const reader = new FileReader(); - - reader.onload = (event) => { - const content = event.target?.result as string; - - setAttachments(current => [ - ...current, - { - name: file.name, - content: content.split(',')[1], // Remove data:mime/type;base64, prefix - type: file.type - } - ]); - }; - - reader.readAsDataURL(file); - } + + // Handle file attachments + const handleAttachmentAdd = async (files: FileList) => { + const newAttachments = Array.from(files).map(file => ({ + name: file.name, + type: file.type, + content: URL.createObjectURL(file) + })); - // Reset file input - if (e.target) { - e.target.value = ''; - } + setAttachments(prev => [...prev, ...newAttachments]); }; - - // Remove attachment - const removeAttachment = (index: number) => { - setAttachments(current => current.filter((_, i) => i !== index)); + + const handleAttachmentRemove = (index: number) => { + setAttachments(prev => prev.filter((_, i) => i !== index)); }; - - // Handle editor input - const handleEditorInput = () => { - if (!editorRef.current) return; - - // Store the current selection/cursor position - const selection = window.getSelection(); - const range = selection?.getRangeAt(0); - const offset = range?.startOffset || 0; - const container = range?.startContainer; - - // Capture the content - setEmailContent(editorRef.current.innerHTML); - - // Try to restore the cursor position after React updates - setTimeout(() => { - if (!selection || !range || !container || !editorRef.current) return; - - try { - if (editorRef.current.contains(container)) { - const newRange = document.createRange(); - newRange.setStart(container, offset); - newRange.collapse(true); - selection.removeAllRanges(); - selection.addRange(newRange); - } - } catch (e) { - console.error('Error restoring cursor position:', e); - } - }, 0); - }; - - // Add a handler for textarea changes - const handleTextareaChange = (e: React.ChangeEvent) => { - setEmailContent(e.target.value); - }; - - // Send email + + // Handle sending email const handleSend = async () => { if (!to) { alert('Please specify at least one recipient'); return; } - + + setSending(true); + try { - setSending(true); - - // For new emails, emailContent is already set via onChange - // For replies/forwards, we need to get content from editorRef - const finalContent = type === 'new' - ? emailContent - : editorRef.current?.innerHTML || emailContent; - await onSend({ to, cc: cc || undefined, bcc: bcc || undefined, subject, - body: finalContent, + body: emailContent, attachments }); + // Reset form and close onClose(); } catch (error) { console.error('Error sending email:', error); @@ -337,201 +237,46 @@ export default function ComposeEmail(props: ComposeEmailAllProps) { setSending(false); } }; - + return ( - - - - {type === 'new' ? 'New Message' : type === 'forward' ? 'Forward Email' : 'Reply to Email'} - - - - - {/* Recipients, Subject fields */} -
-
- To: - setTo(e.target.value)} - className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" - placeholder="recipient@example.com" - /> -
- - {showCc && ( -
- Cc: - setCc(e.target.value)} - className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" - placeholder="cc@example.com" - /> -
- )} - - {showBcc && ( -
- Bcc: - setBcc(e.target.value)} - className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" - placeholder="bcc@example.com" - /> -
- )} - - {/* CC/BCC controls */} -
- - - -
- -
- Subject: - setSubject(e.target.value)} - className="flex-1 border-0 shadow-none h-8 focus-visible:ring-0" - placeholder="Subject" - /> -
-
- - {/* Email Body Editor - different approach based on type */} -
-
- -
- - {/* For new emails, use textarea for better text direction */} - {type === 'new' ? ( -
-