"use client"; import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "@/i18n/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Inbox, Send, File, Star, Trash2, Archive, PenSquare, Search, Menu, LogOut, ChevronRight, ChevronDown, Folder, FolderOpen, Settings, ChevronUp, Users, User, BookUser, Palmtree, SlidersHorizontal, X, } from "lucide-react"; import { cn, buildMailboxTree, MailboxNode, formatFileSize } from "@/lib/utils"; import { Mailbox } from "@/lib/jmap/types"; import { useDragDropContext } from "@/contexts/drag-drop-context"; import { useMailboxDrop } from "@/hooks/use-mailbox-drop"; import { useEmailStore } from "@/stores/email-store"; import { activeFilterCount } from "@/lib/jmap/search-utils"; import { useVacationStore } from "@/stores/vacation-store"; import { toast } from "@/stores/toast-store"; interface SidebarProps { mailboxes: Mailbox[]; selectedMailbox?: string; onMailboxSelect?: (mailboxId: string) => void; onCompose?: () => void; onLogout?: () => void; onSidebarClose?: () => void; onSearch?: (query: string) => void; onClearSearch?: () => void; activeSearchQuery?: string; quota?: { used: number; total: number } | null; isPushConnected?: boolean; className?: string; } // Map role to icon const getIconForMailbox = (role?: string, name?: string, hasChildren?: boolean, isExpanded?: boolean, isShared?: boolean, id?: string) => { const lowerName = name?.toLowerCase() || ""; // Shared folders root node if (id === 'shared-folders-root') { return isExpanded ? FolderOpen : Users; } // Shared account nodes if (id?.startsWith('shared-account-')) { return isExpanded ? FolderOpen : User; } // Shared mailboxes (but not virtual nodes) if (isShared && hasChildren && !id?.startsWith('shared-')) { return isExpanded ? FolderOpen : Folder; } if (hasChildren) { // For folders with children, return open/closed folder icon return isExpanded ? FolderOpen : Folder; } if (role === "inbox" || lowerName.includes("inbox")) return Inbox; if (role === "sent" || lowerName.includes("sent")) return Send; if (role === "drafts" || lowerName.includes("draft")) return File; if (role === "trash" || lowerName.includes("trash")) return Trash2; if (role === "archive" || lowerName.includes("archive")) return Archive; if (lowerName.includes("star") || lowerName.includes("flag")) return Star; return Inbox; // Default icon }; // Component for rendering a single mailbox node with its children function MailboxTreeItem({ node, selectedMailbox, expandedFolders, onMailboxSelect, onToggleExpand, isCollapsed, }: { node: MailboxNode; selectedMailbox: string; expandedFolders: Set; onMailboxSelect?: (id: string) => void; onToggleExpand: (id: string) => void; isCollapsed: boolean; }) { const t = useTranslations('sidebar'); const tNotifications = useTranslations('notifications'); const hasChildren = node.children.length > 0; const isExpanded = expandedFolders.has(node.id); const Icon = getIconForMailbox(node.role, node.name, hasChildren, isExpanded, node.isShared, node.id); const indentPixels = node.depth * 16; // 16px per depth level const isVirtualNode = node.id.startsWith('shared-'); // Virtual nodes for shared folder organization // Drag and drop functionality const { isDragging: globalDragging } = useDragDropContext(); const { dropHandlers, isValidDropTarget, isInvalidDropTarget } = useMailboxDrop({ mailbox: node, onSuccess: (count, mailboxName) => { if (count === 1) { toast.success( tNotifications('email_moved'), tNotifications('moved_to_mailbox', { mailbox: mailboxName }) ); } else { toast.success( tNotifications('emails_moved', { count }), tNotifications('moved_to_mailbox', { mailbox: mailboxName }) ); } }, onError: () => { toast.error(tNotifications('move_failed'), tNotifications('move_error')); }, }); return ( <>
{/* Expand/Collapse Chevron */} {hasChildren && ( )} {/* Mailbox Button */}
{/* Render children if expanded */} {hasChildren && isExpanded && !isCollapsed && (
{node.children.map((child) => ( ))}
)} ); } function VacationIndicator() { const t = useTranslations('sidebar'); const { isEnabled, isSupported } = useVacationStore(); if (!isSupported || !isEnabled) return null; return ( {t("vacation_active")} ); } function AdvancedSearchToggle() { const tSearch = useTranslations("advanced_search"); const { searchFilters, isAdvancedSearchOpen, toggleAdvancedSearch } = useEmailStore(); const filterCount = activeFilterCount(searchFilters); return ( ); } export function Sidebar({ mailboxes = [], selectedMailbox = "", onMailboxSelect, onCompose, onLogout, onSidebarClose, onSearch, onClearSearch, activeSearchQuery = "", quota, isPushConnected = false, className, }: SidebarProps) { const [isCollapsed, setIsCollapsed] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [expandedFolders, setExpandedFolders] = useState>(new Set()); const [showMenu, setShowMenu] = useState(false); const t = useTranslations('sidebar'); // Sync local search query with store's active search query useEffect(() => { setSearchQuery(activeSearchQuery); }, [activeSearchQuery]); const router = useRouter(); // Load expanded folders from localStorage on mount useEffect(() => { const stored = localStorage.getItem('expandedMailboxes'); if (stored) { try { const parsed = JSON.parse(stored); setExpandedFolders(new Set(parsed)); } catch (e) { console.error('Failed to parse expanded mailboxes:', e); } } else { // By default, expand root folders that have children const tree = buildMailboxTree(mailboxes); const defaultExpanded = tree .filter(node => node.children.length > 0) .map(node => node.id); setExpandedFolders(new Set(defaultExpanded)); } }, [mailboxes]); // Save expanded folders to localStorage when changed const handleToggleExpand = (mailboxId: string) => { setExpandedFolders((prev) => { const next = new Set(prev); if (next.has(mailboxId)) { next.delete(mailboxId); } else { next.add(mailboxId); } localStorage.setItem('expandedMailboxes', JSON.stringify(Array.from(next))); return next; }); }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (searchQuery.trim() && onSearch) { onSearch(searchQuery); } }; // Build hierarchical mailbox tree const mailboxTree = buildMailboxTree(mailboxes); // Keyboard navigation useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!selectedMailbox || isCollapsed) return; // Find the selected node in the tree const findNode = (nodes: MailboxNode[]): MailboxNode | null => { for (const node of nodes) { if (node.id === selectedMailbox) return node; const found = findNode(node.children); if (found) return found; } return null; }; const selectedNode = findNode(mailboxTree); if (!selectedNode) return; // Handle arrow keys for expand/collapse if (e.key === 'ArrowRight' && selectedNode.children.length > 0) { // Expand folder if (!expandedFolders.has(selectedMailbox)) { handleToggleExpand(selectedMailbox); } } else if (e.key === 'ArrowLeft' && selectedNode.children.length > 0) { // Collapse folder if (expandedFolders.has(selectedMailbox)) { handleToggleExpand(selectedMailbox); } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedMailbox, isCollapsed, expandedFolders, mailboxTree]); return (
{/* Header */}
{/* Mobile/Tablet: Close button */} {/* Desktop: Collapse toggle */} {!isCollapsed && ( )}
{/* Search + Advanced Filter Toggle */} {!isCollapsed && (
setSearchQuery(e.target.value)} className={cn("pl-9", searchQuery && "pr-8")} data-search-input /> {searchQuery && ( )}
)} {/* Mailbox List */}
{mailboxes.length === 0 ? (
{!isCollapsed && t("loading_mailboxes")}
) : ( <> {/* Render hierarchical mailbox tree */} {mailboxTree.map((node) => ( ))} )}
{/* Footer */} {!isCollapsed && ( <> {/* Sliding Menu Panel */}
{/* Storage Info */} {quota && quota.total > 0 && (
{t("storage")} {formatFileSize(quota.used)} / {formatFileSize(quota.total)}
)}
{/* Contacts */} {/* Settings */} {/* Sign Out */} {onLogout && ( )}
{/* Menu Toggle Button */}
)}
); }