courrier formatting

This commit is contained in:
alma 2025-04-30 15:44:06 +02:00
parent f011bfc43e
commit e02ee396dc
3 changed files with 143 additions and 117 deletions

View File

@ -15,19 +15,12 @@ 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) {
export default function EmailLayout({ className = '' }: EmailLayoutProps) {
// Email state
const [emails, setEmails] = useState<EmailMessage[]>([]);
const [selectedEmail, setSelectedEmail] = useState<{
@ -250,70 +243,131 @@ export default function EmailLayout({
};
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}
<div className={`flex h-full bg-background ${className}`}>
{/* Sidebar */}
<div className="w-64 border-r h-full flex flex-col">
{/* 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>
{/* Main content */}
<div className="flex-1 flex flex-col lg:flex-row h-full">
{/* Email list */}
<div className="w-full lg:w-96 border-r h-full flex flex-col overflow-hidden">
{/* Search and refresh */}
<div className="p-2 border-b flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search emails..."
className="pl-8"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
</div>
<Button
variant="ghost"
size="icon"
onClick={handleRefresh}
disabled={loading}
>
<Plus className="h-4 w-4 mr-2" />
New Email
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Folder navigation */}
{/* Email list */}
<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>
{loading && emails.length === 0 ? (
<div className="flex items-center justify-center h-32">
<Loader2 className="h-6 w-6 animate-spin text-primary" />
</div>
) : emails.length === 0 ? (
<div className="flex items-center justify-center h-32 text-muted-foreground">
No emails found
</div>
) : (
<div className="divide-y">
{emails.map((email) => (
<div
key={email.id}
className={`p-3 hover:bg-secondary/20 cursor-pointer transition-colors ${
selectedEmail?.emailId === email.id ? 'bg-secondary/30' : ''
} ${!email.flags.seen ? 'bg-primary/5' : ''}`}
onClick={() => handleEmailSelect(email.id, email.accountId || '', email.folder || '')}
>
<div className="flex items-start gap-2">
<div className="pt-0.5">
{email.flags.seen ? (
<MailOpen className="h-4 w-4 text-muted-foreground" />
) : (
<Mail className="h-4 w-4 text-primary" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-baseline">
<p className={`text-sm truncate font-medium ${!email.flags.seen ? 'text-primary' : ''}`}>
{email.from[0]?.name || email.from[0]?.address || 'Unknown'}
</p>
<span className="text-xs text-muted-foreground whitespace-nowrap ml-2">
{formatDate(email.date.toString())}
</span>
</div>
<p className="text-sm font-medium truncate">{email.subject}</p>
<p className="text-xs text-muted-foreground truncate">{email.preview}</p>
</div>
</div>
{/* Email indicators */}
<div className="flex items-center mt-1 gap-1">
{email.hasAttachments && (
<Badge variant="outline" className="text-xs py-0 h-5">
<span className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-3 h-3 mr-1">
<path fillRule="evenodd" d="M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z" clipRule="evenodd" />
</svg>
Attachment
</span>
</Badge>
)}
</div>
</div>
))}
</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}
folder={currentFolder}
onSendEmail={handleSendEmail}
/>
</div>

View File

@ -23,7 +23,6 @@ interface EmailListProps {
onToggleStarred: (emailId: string) => void;
onLoadMore: () => void;
onSearch?: (query: string) => void;
previewMode?: boolean;
}
export default function EmailList({
@ -40,44 +39,33 @@ export default function EmailList({
onBulkAction,
onToggleStarred,
onLoadMore,
onSearch,
previewMode = false
onSearch
}: EmailListProps) {
const [scrollPosition, setScrollPosition] = useState(0);
const [searchQuery, setSearchQuery] = useState('');
// Handle scroll to detect when user is near the bottom
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const target = e.target as HTMLDivElement;
const scrollTop = target.scrollTop;
// Handle scroll to detect when user reaches the bottom
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
const target = event.target as HTMLDivElement;
const { scrollTop, scrollHeight, clientHeight } = target;
setScrollPosition(scrollTop);
// Check if we're near the bottom
const scrollHeight = target.scrollHeight;
const clientHeight = target.clientHeight;
const scrollPosition = scrollTop + clientHeight;
// If we're within 100px of the bottom, load more
if (scrollHeight - scrollPosition < 100 && !isLoading && hasMoreEmails) {
// If user scrolls near the bottom and we have more emails, load more
if (scrollHeight - scrollTop - clientHeight < 200 && hasMoreEmails && !isLoading) {
onLoadMore();
}
};
// Handle search form submission
// Handle search
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
if (onSearch && searchQuery.trim()) {
onSearch(searchQuery.trim());
}
onSearch?.(searchQuery);
};
// Clear search
const clearSearch = () => {
setSearchQuery('');
if (onSearch) {
onSearch('');
}
onSearch?.('');
};
// Render loading state
@ -138,27 +126,16 @@ export default function EmailList({
</form>
</div>
</div>
{/* Only show selection UI if not in preview mode */}
{!previewMode ? (
<EmailListHeader
allSelected={allSelected}
someSelected={someSelected}
onToggleSelectAll={onToggleSelectAll}
currentFolder={currentFolder}
totalEmails={totalEmails}
/>
) : (
<div className="flex items-center justify-between px-4 h-14">
<h2 className="text-base font-semibold text-gray-900 capitalize">Messages</h2>
<span className="text-sm text-gray-600">
{totalEmails} {totalEmails === 1 ? 'email' : 'emails'}
</span>
</div>
)}
<EmailListHeader
allSelected={allSelected}
someSelected={someSelected}
onToggleSelectAll={onToggleSelectAll}
currentFolder={currentFolder}
totalEmails={totalEmails}
/>
</div>
{/* Only show bulk actions if not in preview mode and there are selected emails */}
{!previewMode && selectedEmailIds.length > 0 && (
{selectedEmailIds.length > 0 && (
<BulkActionsToolbar
selectedCount={selectedEmailIds.length}
onBulkAction={onBulkAction}
@ -185,7 +162,6 @@ export default function EmailList({
e.stopPropagation();
onToggleStarred(email.id);
}}
previewMode={previewMode}
/>
))}

View File

@ -15,7 +15,6 @@ interface EmailListItemProps {
onSelect: () => void;
onToggleSelect: (e: React.MouseEvent) => void;
onToggleStarred: (e: React.MouseEvent) => void;
previewMode?: boolean;
}
const PREVIEW_LENGTH = 70;
@ -26,8 +25,7 @@ export default function EmailListItem({
isActive,
onSelect,
onToggleSelect,
onToggleStarred,
previewMode = false
onToggleStarred
}: EmailListItemProps) {
// Format the date in a readable way
const formatDate = (dateString: string) => {
@ -127,34 +125,32 @@ export default function EmailListItem({
className={cn(
'flex items-center gap-3 px-4 py-2 hover:bg-gray-50/80 cursor-pointer',
isActive ? 'bg-blue-50/50' : '',
!email.flags?.seen ? 'bg-blue-50/20' : ''
!email.read ? 'bg-blue-50/20' : ''
)}
onClick={onSelect}
>
{!previewMode && (
<Checkbox
checked={isSelected}
onClick={onToggleSelect}
className="mt-0.5"
/>
)}
<Checkbox
checked={isSelected}
onClick={onToggleSelect}
className="mt-0.5"
/>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 min-w-0">
<span className={`text-sm truncate ${!email.flags?.seen ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
<span className={`text-sm truncate ${!email.read ? 'font-semibold text-gray-900' : 'text-gray-600'}`}>
{getSenderName()}
</span>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<span className="text-xs text-gray-500 whitespace-nowrap">
{formatDate(typeof email.date === 'string' ? email.date : email.date.toString())}
{formatDate(email.date)}
</span>
<button
className="h-6 w-6 text-gray-400 hover:text-yellow-400"
onClick={onToggleStarred}
>
<Star className={`h-4 w-4 ${email.flags?.flagged ? 'fill-yellow-400 text-yellow-400' : ''}`} />
<Star className={`h-4 w-4 ${email.starred ? 'fill-yellow-400 text-yellow-400' : ''}`} />
</button>
</div>
</div>