"use client"; import React, { useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { Inbox, Send, FileText, Archive, AlertTriangle, Trash2, Folder as FolderIcon, Plus, PenSquare, LogOut, Mail, ShieldCheck, ShieldAlert, SlidersHorizontal, Menu, Search, X, } from "lucide-react"; import { useTranslations } from "../../../i18n/provider"; import { LanguageSwitcher } from "../../../i18n/LanguageSwitcher"; import { useAccount, isAdminRole } from "../../../lib/useAccount"; import { useFolders } from "../../../lib/useFolders"; import { folderBadge, customFolders } from "../../../lib/mailCounts"; import { AccountSwitcher } from "../../../components/AccountSwitcher"; import { Logo } from "../../../components/Logo"; import { CommandPalette } from "../../../components/CommandPalette"; export default function UserWebmailLayout({ children }: { children: React.ReactNode }) { const t = useTranslations(); const pathname = usePathname(); const [mobileOpen, setMobileOpen] = useState(false); const account = useAccount("user"); // Live, not fetched once: these badges used to freeze at the value they had // when the tab was opened. const { folders, reload: reloadFolders } = useFolders(); const [newFolder, setNewFolder] = useState(""); const [folderError, setFolderError] = useState(null); /** Creates, renames or deletes one of the user's own folders. */ const manageFolder = async (body: Record) => { setFolderError(null); try { const res = await fetch("/api/v1/mail/folders/manage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json().catch(() => ({})); setFolderError(data.message ?? t("errors.serverError")); return false; } reloadFolders(); return true; } catch { setFolderError(t("errors.serverError")); return false; } }; const navItems = [ { href: "/user/mail/inbox", label: t("mail.inbox"), icon: Inbox, role: "inbox" }, { href: "/user/mail/sent", label: t("mail.sent"), icon: Send, role: "sent" }, { href: "/user/mail/drafts", label: t("mail.drafts"), icon: FileText, role: "drafts" }, { href: "/user/mail/archive", label: t("mail.archive"), icon: Archive, role: "archive" }, { href: "/user/mail/junk", label: t("mail.junk"), icon: AlertTriangle, role: "junk" }, { href: "/user/mail/trash", label: t("mail.trash"), icon: Trash2, role: "trash" }, // Where delivered DMARC and TLS-RPT reports are filed. Matched by name // rather than a special-use role, because IMAP defines none for reports. { href: "/user/mail/reports", label: t("mail.reports"), icon: ShieldCheck, role: "reports" }, ]; const handleLogout = () => { void fetch("/api/v1/auth/logout", { method: "POST" }).finally(() => { window.location.href = "/user/login"; }); }; const settingsActive = pathname === "/user/settings"; const SidebarContent = (
{/* The brand mark. One mark for both surfaces - the line underneath is the only thing that says which one you are in. */}
LambdaMail
{t("common.userPortal")}
setMobileOpen(false)} className="flex w-full items-center justify-center gap-2 rounded-xl bg-fill px-3 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-fill-hover active:brightness-95" > {t("mail.compose")} {/* The way into everything that is not on screen. Quiet on purpose: the sidebar keeps to folders, and this is where the rest lives. */} {/* Folder navigation. Every row is a mark, an icon, a label that may wrap, and its numbers - nothing is sized by the length of the label, which is what clipped the Portuguese and Spanish names before. */}
{/* The two surfaces as one control, so where you are and where else you could be are the same question. The console goes to the step-up rather than to the admin sign-in: the admin audience is a separate token, so crossing over costs a second factor - but not the password, which the session in hand proved already. */} {isAdminRole(account?.role) && (
{t("common.userPortal")} {t("admin.openAdminShort")}
)} setMobileOpen(false)} />
); return (
{/* Mobile Top Header */}
LambdaMail
{/* Desktop rail */} {/* Mobile Overlay Menu */} {mobileOpen && ( {SidebarContent} )} {/* Main Content Area */}
{children}
); }