"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, Users, User, Palmtree, SlidersHorizontal, Settings, 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"; import { debug } from "@/lib/debug"; 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; } const getIconForMailbox = (role?: string, name?: string, hasChildren?: boolean, isExpanded?: boolean, isShared?: boolean, id?: string) => { const lowerName = name?.toLowerCase() || ""; if (id === 'shared-folders-root') { return isExpanded ? FolderOpen : Users; } if (id?.startsWith('shared-account-')) { return isExpanded ? FolderOpen : User; } if (isShared && hasChildren && !id?.startsWith('shared-')) { return isExpanded ? FolderOpen : Folder; } if (hasChildren) { 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; }; 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; const isVirtualNode = node.id.startsWith('shared-'); 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 ( <>
{hasChildren && ( )}
{hasChildren && isExpanded && !isCollapsed && (
{node.children.map((child) => ( ))}
)} ); } function VacationBanner() { const t = useTranslations('sidebar'); const router = useRouter(); const { isEnabled, isSupported } = useVacationStore(); if (!isSupported || !isEnabled) return null; return ( ); } function AdvancedSearchToggle() { const tSearch = useTranslations("advanced_search"); const { searchFilters, isAdvancedSearchOpen, toggleAdvancedSearch } = useEmailStore(); const filterCount = activeFilterCount(searchFilters); return ( ); } function StorageQuota({ quota, isCollapsed }: { quota: { used: number; total: number } | null; isCollapsed: boolean }) { const t = useTranslations('sidebar'); if (!quota || quota.total <= 0) return null; const usagePercent = Math.min((quota.used / quota.total) * 100, 100); const barColor = usagePercent > 90 ? "bg-red-500 dark:bg-red-400" : usagePercent > 70 ? "bg-amber-500 dark:bg-amber-400" : "bg-green-500 dark:bg-green-400"; if (isCollapsed) { return (
); } return (
{t("storage")} {formatFileSize(quota.used)} / {formatFileSize(quota.total)}
); } 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 t = useTranslations('sidebar'); useEffect(() => { setSearchQuery(activeSearchQuery); }, [activeSearchQuery]); useEffect(() => { const stored = localStorage.getItem('expandedMailboxes'); if (stored) { try { const parsed = JSON.parse(stored); setExpandedFolders(new Set(parsed)); } catch (e) { debug.error('Failed to parse expanded mailboxes:', e); } } else { const tree = buildMailboxTree(mailboxes); const defaultExpanded = tree .filter(node => node.children.length > 0) .map(node => node.id); setExpandedFolders(new Set(defaultExpanded)); } }, [mailboxes]); const handleToggleExpand = (mailboxId: string) => { setExpandedFolders((prev) => { const next = new Set(prev); if (next.has(mailboxId)) { next.delete(mailboxId); } else { next.add(mailboxId); } try { localStorage.setItem('expandedMailboxes', JSON.stringify(Array.from(next))); } catch { /* storage full or unavailable */ } return next; }); }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); if (searchQuery.trim() && onSearch) { onSearch(searchQuery); } }; const mailboxTree = buildMailboxTree(mailboxes); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!selectedMailbox || isCollapsed) return; 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; if (e.key === 'ArrowRight' && selectedNode.children.length > 0) { if (!expandedFolders.has(selectedMailbox)) { handleToggleExpand(selectedMailbox); } } else if (e.key === 'ArrowLeft' && selectedNode.children.length > 0) { if (expandedFolders.has(selectedMailbox)) { handleToggleExpand(selectedMailbox); } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedMailbox, isCollapsed, expandedFolders, mailboxTree]); return (
{/* Header */}
{!isCollapsed && ( )}
{/* Vacation Banner */} {!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")}
) : ( <> {mailboxTree.map((node) => ( ))} )}
{/* Footer: Storage Quota + Sign Out + Push Status */}
{onLogout && ( )} {!isCollapsed && ( {isPushConnected ? t("push_connected") : t("push_disconnected")} )}
); }