courrier preview
This commit is contained in:
parent
d6e3acd17a
commit
7fcf4d5ea8
@ -894,6 +894,7 @@ export default function CourrierPage() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClose={() => setShowComposeModal(false)}
|
onClose={() => setShowComposeModal(false)}
|
||||||
|
accounts={accounts}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -2,11 +2,19 @@
|
|||||||
|
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
X, Paperclip, SendHorizontal, Loader2, Plus
|
X, Paperclip, SendHorizontal, Loader2, Plus, ChevronDown
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import DOMPurify from 'isomorphic-dompurify';
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
// Import from the centralized utils
|
// Import from the centralized utils
|
||||||
import {
|
import {
|
||||||
@ -29,16 +37,22 @@ interface ComposeEmailProps {
|
|||||||
bcc?: string;
|
bcc?: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
fromAccount?: string;
|
||||||
attachments?: Array<{
|
attachments?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
type: string;
|
type: string;
|
||||||
}>;
|
}>;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
|
accounts?: Array<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
display_name?: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ComposeEmail(props: ComposeEmailProps) {
|
export default function ComposeEmail(props: ComposeEmailProps) {
|
||||||
const { initialEmail, type = 'new', onClose, onSend } = props;
|
const { initialEmail, type = 'new', onClose, onSend, accounts = [] } = props;
|
||||||
|
|
||||||
// Email form state
|
// Email form state
|
||||||
const [to, setTo] = useState<string>('');
|
const [to, setTo] = useState<string>('');
|
||||||
@ -49,6 +63,11 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
const [showCc, setShowCc] = useState<boolean>(false);
|
const [showCc, setShowCc] = useState<boolean>(false);
|
||||||
const [showBcc, setShowBcc] = useState<boolean>(false);
|
const [showBcc, setShowBcc] = useState<boolean>(false);
|
||||||
const [sending, setSending] = 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<{
|
const [attachments, setAttachments] = useState<Array<{
|
||||||
name: string;
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
@ -171,6 +190,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
bcc: bcc || undefined,
|
bcc: bcc || undefined,
|
||||||
subject,
|
subject,
|
||||||
body: emailContent,
|
body: emailContent,
|
||||||
|
fromAccount: selectedAccount?.id,
|
||||||
attachments
|
attachments
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -197,7 +217,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
|
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
|
||||||
{/* Header */}
|
{/* 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>
|
<h2 className="text-lg font-medium text-gray-800">{getComposeTitle()}</h2>
|
||||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
@ -206,48 +226,91 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
|
|
||||||
{/* Email Form */}
|
{/* Email Form */}
|
||||||
<div className="flex-1 overflow-y-auto bg-white">
|
<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 */}
|
{/* Recipients */}
|
||||||
<div className="border-b pb-2">
|
<div className="border-b pb-1">
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center">
|
||||||
<span className="w-16 text-gray-700 font-medium">To:</span>
|
<span className="w-16 text-gray-700 font-medium">To:</span>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={to}
|
value={to}
|
||||||
onChange={(e) => setTo(e.target.value)}
|
onChange={(e) => setTo(e.target.value)}
|
||||||
placeholder="recipient@example.com"
|
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>
|
</div>
|
||||||
|
|
||||||
{showCc && (
|
{showCc && (
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center">
|
||||||
<span className="w-16 text-gray-700 font-medium">Cc:</span>
|
<span className="w-16 text-gray-700 font-medium">Cc:</span>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={cc}
|
value={cc}
|
||||||
onChange={(e) => setCc(e.target.value)}
|
onChange={(e) => setCc(e.target.value)}
|
||||||
placeholder="cc@example.com"
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showBcc && (
|
{showBcc && (
|
||||||
<div className="flex items-center mb-2">
|
<div className="flex items-center">
|
||||||
<span className="w-16 text-gray-700 font-medium">Bcc:</span>
|
<span className="w-16 text-gray-700 font-medium">Bcc:</span>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={bcc}
|
value={bcc}
|
||||||
onChange={(e) => setBcc(e.target.value)}
|
onChange={(e) => setBcc(e.target.value)}
|
||||||
placeholder="bcc@example.com"
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* CC/BCC Toggle Links */}
|
{/* CC/BCC Toggle Links */}
|
||||||
<div className="flex gap-3 ml-16 mb-1">
|
<div className="flex gap-3 ml-16">
|
||||||
{!showCc && (
|
{!showCc && (
|
||||||
<button
|
<button
|
||||||
className="text-blue-600 text-sm hover:underline"
|
className="text-blue-600 text-sm hover:underline"
|
||||||
@ -269,7 +332,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subject */}
|
{/* Subject */}
|
||||||
<div className="border-b pb-2">
|
<div className="border-b pb-1">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span className="w-16 text-gray-700 font-medium">Subject:</span>
|
<span className="w-16 text-gray-700 font-medium">Subject:</span>
|
||||||
<Input
|
<Input
|
||||||
@ -277,7 +340,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
value={subject}
|
value={subject}
|
||||||
onChange={(e) => setSubject(e.target.value)}
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
placeholder="Subject"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@ -285,7 +348,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
{/* Message Body */}
|
{/* Message Body */}
|
||||||
<div
|
<div
|
||||||
ref={editorRef}
|
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}
|
contentEditable={true}
|
||||||
dangerouslySetInnerHTML={{ __html: emailContent }}
|
dangerouslySetInnerHTML={{ __html: emailContent }}
|
||||||
onInput={(e) => setEmailContent(e.currentTarget.innerHTML)}
|
onInput={(e) => setEmailContent(e.currentTarget.innerHTML)}
|
||||||
|
|||||||
@ -18,12 +18,18 @@ interface ComposeEmailAdapterProps {
|
|||||||
bcc?: string;
|
bcc?: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
fromAccount?: string;
|
||||||
attachments?: Array<{
|
attachments?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
type: string;
|
type: string;
|
||||||
}>;
|
}>;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
|
accounts?: Array<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
display_name?: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,7 +40,8 @@ export default function ComposeEmailAdapter({
|
|||||||
initialEmail,
|
initialEmail,
|
||||||
type = 'new',
|
type = 'new',
|
||||||
onClose,
|
onClose,
|
||||||
onSend
|
onSend,
|
||||||
|
accounts = []
|
||||||
}: ComposeEmailAdapterProps) {
|
}: ComposeEmailAdapterProps) {
|
||||||
// Convert the new EmailMessage format to the old format
|
// Convert the new EmailMessage format to the old format
|
||||||
const [adaptedEmail, setAdaptedEmail] = useState<any | null>(null);
|
const [adaptedEmail, setAdaptedEmail] = useState<any | null>(null);
|
||||||
@ -130,6 +137,7 @@ export default function ComposeEmailAdapter({
|
|||||||
type={type}
|
type={type}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSend={onSend}
|
onSend={onSend}
|
||||||
|
accounts={accounts}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -11,7 +11,6 @@ import { Separator } from '@/components/ui/separator';
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import EmailPanel from './EmailPanel';
|
import EmailPanel from './EmailPanel';
|
||||||
import { EmailMessage } from '@/lib/services/email-service';
|
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { sendEmail } from '@/lib/services/email-service';
|
import { sendEmail } from '@/lib/services/email-service';
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
@ -22,7 +21,7 @@ interface EmailLayoutProps {
|
|||||||
|
|
||||||
export default function EmailLayout({ className = '' }: EmailLayoutProps) {
|
export default function EmailLayout({ className = '' }: EmailLayoutProps) {
|
||||||
// Email state
|
// Email state
|
||||||
const [emails, setEmails] = useState<EmailMessage[]>([]);
|
const [emails, setEmails] = useState<any[]>([]);
|
||||||
const [selectedEmail, setSelectedEmail] = useState<{
|
const [selectedEmail, setSelectedEmail] = useState<{
|
||||||
emailId: string;
|
emailId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
@ -31,6 +30,11 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
|
|||||||
const [currentFolder, setCurrentFolder] = useState<string>('INBOX');
|
const [currentFolder, setCurrentFolder] = useState<string>('INBOX');
|
||||||
const [folders, setFolders] = useState<string[]>([]);
|
const [folders, setFolders] = useState<string[]>([]);
|
||||||
const [mailboxes, setMailboxes] = useState<string[]>([]);
|
const [mailboxes, setMailboxes] = useState<string[]>([]);
|
||||||
|
const [accounts, setAccounts] = useState<Array<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
display_name?: string;
|
||||||
|
}>>([]);
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
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
|
// Load emails on component mount and when folder changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadEmails();
|
loadEmails();
|
||||||
|
loadAccounts(); // Load accounts when component mounts
|
||||||
}, [currentFolder, page]);
|
}, [currentFolder, page]);
|
||||||
|
|
||||||
// Function to load emails
|
// 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
|
// Handle folder change
|
||||||
const handleFolderChange = (folder: string) => {
|
const handleFolderChange = (folder: string) => {
|
||||||
setCurrentFolder(folder);
|
setCurrentFolder(folder);
|
||||||
@ -367,8 +391,8 @@ export default function EmailLayout({ className = '' }: EmailLayoutProps) {
|
|||||||
<div className="flex-1 h-full overflow-hidden">
|
<div className="flex-1 h-full overflow-hidden">
|
||||||
<EmailPanel
|
<EmailPanel
|
||||||
selectedEmail={selectedEmail}
|
selectedEmail={selectedEmail}
|
||||||
folder={currentFolder}
|
|
||||||
onSendEmail={handleSendEmail}
|
onSendEmail={handleSendEmail}
|
||||||
|
accounts={accounts}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,6 +16,11 @@ interface EmailPanelProps {
|
|||||||
folder: string;
|
folder: string;
|
||||||
} | null;
|
} | null;
|
||||||
onSendEmail: (email: any) => Promise<void>;
|
onSendEmail: (email: any) => Promise<void>;
|
||||||
|
accounts?: Array<{
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
display_name?: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type for the legacy ComposeEmail component props
|
// Type for the legacy ComposeEmail component props
|
||||||
@ -29,6 +34,7 @@ interface ComposeEmailProps {
|
|||||||
bcc?: string;
|
bcc?: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
fromAccount?: string;
|
||||||
attachments?: Array<{
|
attachments?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
content: string;
|
content: string;
|
||||||
@ -39,7 +45,8 @@ interface ComposeEmailProps {
|
|||||||
|
|
||||||
export default function EmailPanel({
|
export default function EmailPanel({
|
||||||
selectedEmail,
|
selectedEmail,
|
||||||
onSendEmail
|
onSendEmail,
|
||||||
|
accounts = []
|
||||||
}: EmailPanelProps) {
|
}: EmailPanelProps) {
|
||||||
// Use the new email fetch hook
|
// Use the new email fetch hook
|
||||||
const { email, loading, error, fetchEmail } = useEmailFetch({
|
const { email, loading, error, fetchEmail } = useEmailFetch({
|
||||||
@ -172,6 +179,7 @@ export default function EmailPanel({
|
|||||||
type={composeType}
|
type={composeType}
|
||||||
onClose={handleComposeClose}
|
onClose={handleComposeClose}
|
||||||
onSend={handleSendEmail}
|
onSend={handleSendEmail}
|
||||||
|
accounts={accounts}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="max-w-4xl mx-auto h-full">
|
<div className="max-w-4xl mx-auto h-full">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user