courrier preview

This commit is contained in:
alma 2025-04-30 23:41:10 +02:00
parent d6e3acd17a
commit 7fcf4d5ea8
5 changed files with 124 additions and 20 deletions

View File

@ -894,6 +894,7 @@ export default function CourrierPage() {
}
}}
onClose={() => setShowComposeModal(false)}
accounts={accounts}
/>
</DialogContent>
</Dialog>

View File

@ -2,11 +2,19 @@
import { useState, useRef, useEffect } from 'react';
import {
X, Paperclip, SendHorizontal, Loader2, Plus
X, Paperclip, SendHorizontal, Loader2, Plus, ChevronDown
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import DOMPurify from 'isomorphic-dompurify';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// Import from the centralized utils
import {
@ -29,16 +37,22 @@ interface ComposeEmailProps {
bcc?: string;
subject: string;
body: string;
fromAccount?: string;
attachments?: Array<{
name: string;
content: string;
type: string;
}>;
}) => Promise<void>;
accounts?: Array<{
id: string;
email: string;
display_name?: string;
}>;
}
export default function ComposeEmail(props: ComposeEmailProps) {
const { initialEmail, type = 'new', onClose, onSend } = props;
const { initialEmail, type = 'new', onClose, onSend, accounts = [] } = props;
// Email form state
const [to, setTo] = useState<string>('');
@ -49,6 +63,11 @@ export default function ComposeEmail(props: ComposeEmailProps) {
const [showCc, setShowCc] = useState<boolean>(false);
const [showBcc, setShowBcc] = useState<boolean>(false);
const [sending, setSending] = useState<boolean>(false);
const [selectedAccount, setSelectedAccount] = useState<{
id: string;
email: string;
display_name?: string;
} | null>(accounts.length > 0 ? accounts[0] : null);
const [attachments, setAttachments] = useState<Array<{
name: string;
content: string;
@ -171,6 +190,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
bcc: bcc || undefined,
subject,
body: emailContent,
fromAccount: selectedAccount?.id,
attachments
});
@ -197,7 +217,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
return (
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b bg-gray-50">
<div className="flex items-center justify-between p-3 border-b bg-gray-50">
<h2 className="text-lg font-medium text-gray-800">{getComposeTitle()}</h2>
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="h-5 w-5" />
@ -206,48 +226,91 @@ export default function ComposeEmail(props: ComposeEmailProps) {
{/* Email Form */}
<div className="flex-1 overflow-y-auto bg-white">
<div className="p-4 space-y-4">
<div className="p-2 space-y-2">
{/* From */}
<div className="border-b pb-1">
<div className="flex items-center">
<span className="w-16 text-gray-700 font-medium">From:</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="w-full flex justify-between items-center h-8 px-2 py-1 text-left font-normal"
>
<span className="truncate">
{selectedAccount ?
(selectedAccount.display_name ?
`${selectedAccount.display_name} <${selectedAccount.email}>` :
selectedAccount.email) :
'Select account'}
</span>
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-[240px]">
<DropdownMenuLabel>Select account</DropdownMenuLabel>
<DropdownMenuSeparator />
{accounts.length > 0 ? (
accounts.map(account => (
<DropdownMenuItem
key={account.id}
onClick={() => setSelectedAccount(account)}
className="cursor-pointer"
>
{account.display_name ?
`${account.display_name} <${account.email}>` :
account.email}
</DropdownMenuItem>
))
) : (
<DropdownMenuItem disabled>No accounts available</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* Recipients */}
<div className="border-b pb-2">
<div className="flex items-center mb-2">
<div className="border-b pb-1">
<div className="flex items-center">
<span className="w-16 text-gray-700 font-medium">To:</span>
<Input
type="text"
value={to}
onChange={(e) => setTo(e.target.value)}
placeholder="recipient@example.com"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 bg-white text-gray-800"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800"
/>
</div>
{showCc && (
<div className="flex items-center mb-2">
<div className="flex items-center">
<span className="w-16 text-gray-700 font-medium">Cc:</span>
<Input
type="text"
value={cc}
onChange={(e) => setCc(e.target.value)}
placeholder="cc@example.com"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 bg-white text-gray-800"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800"
/>
</div>
)}
{showBcc && (
<div className="flex items-center mb-2">
<div className="flex items-center">
<span className="w-16 text-gray-700 font-medium">Bcc:</span>
<Input
type="text"
value={bcc}
onChange={(e) => setBcc(e.target.value)}
placeholder="bcc@example.com"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 bg-white text-gray-800"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800"
/>
</div>
)}
{/* CC/BCC Toggle Links */}
<div className="flex gap-3 ml-16 mb-1">
<div className="flex gap-3 ml-16">
{!showCc && (
<button
className="text-blue-600 text-sm hover:underline"
@ -269,7 +332,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
</div>
{/* Subject */}
<div className="border-b pb-2">
<div className="border-b pb-1">
<div className="flex items-center">
<span className="w-16 text-gray-700 font-medium">Subject:</span>
<Input
@ -277,7 +340,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="Subject"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 bg-white text-gray-800"
className="flex-1 border-0 shadow-none focus-visible:ring-0 px-0 h-8 bg-white text-gray-800"
/>
</div>
</div>
@ -285,7 +348,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
{/* Message Body */}
<div
ref={editorRef}
className="min-h-[300px] outline-none p-2 border rounded-md bg-white text-gray-800"
className="min-h-[320px] outline-none p-2 border rounded-md bg-white text-gray-800 flex-1"
contentEditable={true}
dangerouslySetInnerHTML={{ __html: emailContent }}
onInput={(e) => setEmailContent(e.currentTarget.innerHTML)}

View File

@ -18,12 +18,18 @@ interface ComposeEmailAdapterProps {
bcc?: string;
subject: string;
body: string;
fromAccount?: string;
attachments?: Array<{
name: string;
content: string;
type: string;
}>;
}) => Promise<void>;
accounts?: Array<{
id: string;
email: string;
display_name?: string;
}>;
}
/**
@ -34,7 +40,8 @@ export default function ComposeEmailAdapter({
initialEmail,
type = 'new',
onClose,
onSend
onSend,
accounts = []
}: ComposeEmailAdapterProps) {
// Convert the new EmailMessage format to the old format
const [adaptedEmail, setAdaptedEmail] = useState<any | null>(null);
@ -130,6 +137,7 @@ export default function ComposeEmailAdapter({
type={type}
onClose={onClose}
onSend={onSend}
accounts={accounts}
/>
);
}

View File

@ -11,7 +11,6 @@ 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";
@ -22,7 +21,7 @@ interface EmailLayoutProps {
export default function EmailLayout({ className = '' }: EmailLayoutProps) {
// Email state
const [emails, setEmails] = useState<EmailMessage[]>([]);
const [emails, setEmails] = useState<any[]>([]);
const [selectedEmail, setSelectedEmail] = useState<{
emailId: string;
accountId: string;
@ -31,6 +30,11 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
const [currentFolder, setCurrentFolder] = useState<string>('INBOX');
const [folders, setFolders] = useState<string[]>([]);
const [mailboxes, setMailboxes] = useState<string[]>([]);
const [accounts, setAccounts] = useState<Array<{
id: string;
email: string;
display_name?: string;
}>>([]);
// UI state
const [loading, setLoading] = useState<boolean>(true);
@ -49,6 +53,7 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
// Load emails on component mount and when folder changes
useEffect(() => {
loadEmails();
loadAccounts(); // Load accounts when component mounts
}, [currentFolder, page]);
// Function to load emails
@ -117,6 +122,25 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
}
};
// Function to load accounts
const loadAccounts = async () => {
try {
const response = await fetch('/api/courrier/accounts');
if (response.ok) {
const data = await response.json();
if (data.accounts) {
setAccounts(data.accounts.map((acc: any) => ({
id: acc.id || acc.email,
email: acc.email,
display_name: acc.display_name
})));
}
}
} catch (err) {
console.error('Error loading accounts:', err);
}
};
// Handle folder change
const handleFolderChange = (folder: string) => {
setCurrentFolder(folder);
@ -367,8 +391,8 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
<div className="flex-1 h-full overflow-hidden">
<EmailPanel
selectedEmail={selectedEmail}
folder={currentFolder}
onSendEmail={handleSendEmail}
accounts={accounts}
/>
</div>
</div>

View File

@ -16,6 +16,11 @@ interface EmailPanelProps {
folder: string;
} | null;
onSendEmail: (email: any) => Promise<void>;
accounts?: Array<{
id: string;
email: string;
display_name?: string;
}>;
}
// Type for the legacy ComposeEmail component props
@ -29,6 +34,7 @@ interface ComposeEmailProps {
bcc?: string;
subject: string;
body: string;
fromAccount?: string;
attachments?: Array<{
name: string;
content: string;
@ -39,7 +45,8 @@ interface ComposeEmailProps {
export default function EmailPanel({
selectedEmail,
onSendEmail
onSendEmail,
accounts = []
}: EmailPanelProps) {
// Use the new email fetch hook
const { email, loading, error, fetchEmail } = useEmailFetch({
@ -172,6 +179,7 @@ export default function EmailPanel({
type={composeType}
onClose={handleComposeClose}
onSend={handleSendEmail}
accounts={accounts}
/>
) : (
<div className="max-w-4xl mx-auto h-full">