"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { createPortal } from "react-dom"; import { Mail, Calendar, BookUser, HardDrive, Settings, LogOut } from "lucide-react"; import { usePathname, Link } from "@/i18n/navigation"; import { useTranslations } from "next-intl"; import { useCalendarStore } from "@/stores/calendar-store"; import { useEmailStore } from "@/stores/email-store"; import { useWebDAVStore } from "@/stores/webdav-store"; import { cn, formatFileSize } from "@/lib/utils"; interface NavItem { id: string; icon: typeof Mail; labelKey: string; href: string; hidden?: boolean; badge?: number; } interface NavigationRailProps { orientation?: "vertical" | "horizontal"; collapsed?: boolean; className?: string; quota?: { used: number; total: number } | null; isPushConnected?: boolean; onLogout?: () => void; } function StorageQuotaCircle({ quota, usagePercent }: { quota: { used: number; total: number }; usagePercent: number }) { const t = useTranslations("sidebar"); const [open, setOpen] = useState(false); const buttonRef = useRef(null); const popoverRef = useRef(null); const [popoverStyle, setPopoverStyle] = useState({}); const updatePosition = useCallback(() => { if (!buttonRef.current) return; const rect = buttonRef.current.getBoundingClientRect(); setPopoverStyle({ position: "fixed", left: rect.right + 8, bottom: window.innerHeight - rect.bottom, }); }, []); useEffect(() => { if (!open) return; updatePosition(); const handleClick = (e: MouseEvent) => { if ( buttonRef.current?.contains(e.target as Node) || popoverRef.current?.contains(e.target as Node) ) return; setOpen(false); }; document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, [open, updatePosition]); const free = quota.total - quota.used; const strokeColor = usagePercent > 90 ? "stroke-red-500 dark:stroke-red-400" : usagePercent > 70 ? "stroke-amber-500 dark:stroke-amber-400" : "stroke-green-500 dark:stroke-green-400"; return (
{open && createPortal(

{t("storage")}

{t("storage_used")} {formatFileSize(quota.used)}
{t("storage_free")} {formatFileSize(free)}
{t("storage_total")} {formatFileSize(quota.total)}
90 ? "bg-red-500 dark:bg-red-400" : usagePercent > 70 ? "bg-amber-500 dark:bg-amber-400" : "bg-green-500 dark:bg-green-400" )} style={{ width: `${usagePercent}%` }} />

{Math.round(usagePercent)}% {t("storage_used").toLowerCase()}

, document.body )}
); } export function NavigationRail({ orientation = "vertical", collapsed = false, className, quota, isPushConnected, onLogout, }: NavigationRailProps) { const t = useTranslations("sidebar"); const pathname = usePathname(); const { supportsCalendar } = useCalendarStore(); const { mailboxes } = useEmailStore(); const { supportsWebDAV } = useWebDAVStore(); const inboxUnread = mailboxes.find(m => m.role === "inbox")?.unreadEmails || 0; const navItems: NavItem[] = [ { id: "mail", icon: Mail, labelKey: "mail", href: "/", badge: inboxUnread }, { id: "calendar", icon: Calendar, labelKey: "calendar", href: "/calendar", hidden: !supportsCalendar }, { id: "contacts", icon: BookUser, labelKey: "contacts", href: "/contacts" }, { id: "files", icon: HardDrive, labelKey: "files", href: "/files", hidden: supportsWebDAV === false }, { id: "settings", icon: Settings, labelKey: "settings", href: "/settings" }, ]; const visibleItems = navItems.filter((item) => !item.hidden); const getIsActive = (href: string) => { if (href === "/") { return pathname === "/" || pathname === ""; } return pathname.startsWith(href); }; if (orientation === "horizontal") { return ( ); } const quotaUsagePercent = quota && quota.total > 0 ? Math.min((quota.used / quota.total) * 100, 100) : 0; return (
{/* Footer: Storage Quota + Sign Out + Push Status */}
{quota && quota.total > 0 && ( )} {isPushConnected != null && ( )} {onLogout && ( )}
); }