"use client"; import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { useRouter } from "@/i18n/navigation"; import { Button } from "@/components/ui/button"; import { Inbox, Send, File, Star, Trash2, Archive, PenSquare, ChevronsLeft, ChevronsRight, ChevronRight, ChevronDown, Folder, FolderOpen, Users, User, Palmtree, Settings, X, Tag, RotateCcw, FlaskConical, PlayCircle, Loader2, } from "lucide-react"; import { cn, buildMailboxTree, MailboxNode } from "@/lib/utils"; import { Mailbox } from "@/lib/jmap/types"; import { useDragDropContext } from "@/contexts/drag-drop-context"; import { useMailboxDrop } from "@/hooks/use-mailbox-drop"; import { useTagDrop } from "@/hooks/use-tag-drop"; import { useUIStore } from "@/stores/ui-store"; import { useAuthStore } from "@/stores/auth-store"; import { useVacationStore } from "@/stores/vacation-store"; import { useSettingsStore, KEYWORD_PALETTE, KeywordDefinition } from "@/stores/settings-store"; import { useEmailStore } from "@/stores/email-store"; import { toast } from "@/stores/toast-store"; import { debug } from "@/lib/debug"; import { useConfig } from "@/hooks/use-config"; import { useThemeStore } from "@/stores/theme-store"; import { AccountSwitcher } from "./account-switcher"; import { useTour } from "@/components/tour/tour-provider"; interface SidebarProps { mailboxes: Mailbox[]; selectedMailbox?: string; selectedKeyword?: string | null; onMailboxSelect?: (mailboxId: string) => void; onTagSelect?: (keywordId: string | null) => void; onCompose?: () => void; onSidebarClose?: () => void; onUnreadFilterClick?: (mailboxId: string) => void; 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, onUnreadFilterClick, }: { node: MailboxNode; selectedMailbox: string; expandedFolders: Set; onMailboxSelect?: (id: string) => void; onToggleExpand: (id: string) => void; isCollapsed: boolean; onUnreadFilterClick?: (mailboxId: string) => void; }) { 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 && !isCollapsed && ( )}
{hasChildren && isExpanded && !isCollapsed && (
{node.children.map((child) => ( ))}
)} ); } function TagItem({ kw, isSelected, isCollapsed, onTagSelect, totalCount, unreadCount, }: { kw: KeywordDefinition; isSelected: boolean; isCollapsed: boolean; onTagSelect?: (keywordId: string | null) => void; totalCount: number; unreadCount: number; }) { const t = useTranslations('notifications'); const palette = KEYWORD_PALETTE[kw.color]; const { isDragging: globalDragging } = useDragDropContext(); const { dropHandlers, isValidDropTarget } = useTagDrop({ tagId: kw.id, onSuccess: (count, _tagLabel) => { if (count === 1) { toast.success(t('email_tagged'), kw.label); } else { toast.success(t('emails_tagged', { count }), kw.label); } }, onError: () => { toast.error(t('tag_failed'), kw.label); }, }); return (
); } function DemoBanner() { const t = useTranslations('sidebar'); const { isDemoMode, loginDemo } = useAuthStore(); const { startTour, resetTourCompletion } = useTour(); const router = useRouter(); const [isResetting, setIsResetting] = useState(false); if (!isDemoMode) return null; const handleReset = async () => { setIsResetting(true); // Navigate to home first so the mail page re-fetches data router.push('/'); await loginDemo(); setIsResetting(false); }; const handleStartTour = () => { resetTourCompletion(); router.push('/'); setTimeout(() => startTour(), 100); }; return (
{t("demo_banner")}
); } function VacationBanner() { const t = useTranslations('sidebar'); const router = useRouter(); const { isEnabled, isSupported } = useVacationStore(); if (!isSupported || !isEnabled) return null; return ( ); } export function Sidebar({ mailboxes = [], selectedMailbox = "", selectedKeyword = null, onMailboxSelect, onTagSelect, onCompose, onSidebarClose, onUnreadFilterClick, className, }: SidebarProps) { const { sidebarCollapsed: isCollapsed, toggleSidebarCollapsed } = useUIStore(); const { primaryIdentity } = useAuthStore(); const { appLogoLightUrl, appLogoDarkUrl } = useConfig(); const resolvedTheme = useThemeStore((s) => s.resolvedTheme); const [expandedFolders, setExpandedFolders] = useState>(new Set()); const [tagsExpanded, setTagsExpanded] = useState(() => { try { const stored = localStorage.getItem('sidebarTagsExpanded'); return stored !== null ? JSON.parse(stored) : true; } catch { return true; } }); const emailKeywords = useSettingsStore(s => s.emailKeywords); const tagCounts = useEmailStore(s => s.tagCounts); const t = useTranslations('sidebar'); 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 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 */}
{(() => { const logoUrl = resolvedTheme === 'dark' ? (appLogoDarkUrl || appLogoLightUrl) : (appLogoLightUrl || appLogoDarkUrl); return logoUrl ? ( ) : null; })()} {!isCollapsed && ( )}
{/* Demo Banner */} {!isCollapsed && } {/* Vacation Banner */} {!isCollapsed && } {/* Mailbox List */}
{mailboxes.length === 0 ? (
{!isCollapsed && t("loading_mailboxes")}
) : ( <> {mailboxTree.map((node) => ( ))} )}
{/* Tags Section */} {emailKeywords.length > 0 && ( <>
{!isCollapsed && ( )}
{((tagsExpanded && !isCollapsed) || isCollapsed) && (
{emailKeywords.map((kw) => { const isSelected = selectedKeyword === kw.id; return ( ); })}
)} )}
{/* Compose Button */}
{isCollapsed ? ( ) : ( )}
); }