EventBuzz_Console/src/app/components/Sidebar.tsx

780 lines
35 KiB
TypeScript

import React, { useState } from "react";
import {
House, Layers, Zap, Palette, Mic2, Users, FileText, BookOpen,
MessageSquare, CreditCard, Shield, ChevronRight,
ChevronLeft, Settings, LogOut, Plus, X, Sun, Moon,
} from "lucide-react";
import { useBreakpoint } from "../hooks/useBreakpoint";
import { useNavigate } from "react-router-dom";
import { useTheme } from "../contexts/ThemeContext";
import { useAuth } from "../contexts/AuthContext";
import { navItemAllowed, usePermission } from "../contexts/PermissionContext";
/* ─────────────────────────────────────
Types
───────────────────────────────────── */
interface SubItem { id: string; label: string; isAction?: boolean; }
interface NavItem { id: string; label: string; icon: React.ReactNode; children?: SubItem[]; }
/* ─────────────────────────────────────
Sidebar events list (accordion data)
───────────────────────────────────── */
interface SidebarEventItem {
id: string;
name: string;
status: "Live" | "Upcoming" | "Completed";
}
const SIDEBAR_EVENTS: SidebarEventItem[] = [
{ id: "e1", name: "Napcon Jaipur 2026", status: "Live" },
{ id: "e2", name: "Napcon Mumbai 2025", status: "Completed" },
{ id: "e3", name: "CHEST India 2026", status: "Upcoming" },
{ id: "e4", name: "ICS Annual Conf. 2026", status: "Upcoming" },
{ id: "e5", name: "COPD Workshop", status: "Completed" },
{ id: "e6", name: "Interventional Pulmo.", status: "Completed" },
{ id: "e7", name: "Resp. Physiology Lab", status: "Upcoming" },
{ id: "e8", name: "IMA Annual Summit 2025", status: "Completed" },
{ id: "e9", name: "AIIMS Research Conclave", status: "Upcoming" },
];
const EVENT_STATUS_DOT: Record<"Live" | "Upcoming" | "Completed", string> = {
Live: "#16a34a",
Upcoming: "#2563eb",
Completed: "var(--muted)",
};
/* ─────────────────────────────────────
Nav structure — Portfolio / All Events
───────────────────────────────────── */
const PORTFOLIO_NAV: NavItem[] = [
{ id: "home", label: "Home", icon: <House size={16} /> },
{ id: "events", label: "All Events", icon: <Layers size={16} /> },
{ id: "activities", label: "Activities", icon: <Zap size={16} /> },
{
id: "design", label: "Design", icon: <Palette size={16} />,
children: [
{ id: "design-brochure", label: "Brochure" },
{ id: "design-badges", label: "Badges" },
{ id: "design-emailers", label: "Emailers" },
{ id: "design-whatsapp-templates", label: "WhatsApp Templates" },
],
},
{ id: "faculty", label: "Faculty & Speakers", icon: <Mic2 size={16} /> },
{ id: "all-participants", label: "Participants", icon: <Users size={16} /> },
{
id: "forms", label: "Forms & Workflow", icon: <FileText size={16} />,
children: [
{ id: "forms-feedback", label: "Feedback" },
{ id: "forms-registration", label: "Registration" },
{ id: "forms-other", label: "Other Forms" },
{ id: "forms-add", label: "Add New Form", isAction: true },
],
},
{
id: "content", label: "Content", icon: <BookOpen size={16} />,
children: [
{ id: "content-library", label: "Library" },
{ id: "content-collections", label: "Collections" },
],
},
{
id: "messaging", label: "Messaging", icon: <MessageSquare size={16} />,
children: [
{ id: "messaging-email", label: "Email" },
{ id: "messaging-whatsapp", label: "WhatsApp" },
{ id: "messaging-whatsapp-chatbots", label: "WhatsApp Chatbots" },
],
},
{ id: "payment", label: "Payment Settings", icon: <CreditCard size={16} /> },
{ id: "user-management", label: "User Management", icon: <Shield size={16} /> },
{ id: "settings", label: "Settings", icon: <Settings size={16} /> },
];
/* ─────────────────────────────────────
Helpers
───────────────────────────────────── */
function isChildActive(item: NavItem, active: string) {
return !!item.children?.some(c => c.id === active);
}
function isItemActive(item: NavItem, active: string) {
return active === item.id || isChildActive(item, active);
}
/* ─────────────────────────────────────
Tooltip (collapsed mode)
───────────────────────────────────── */
function Tooltip({ label, children }: { label: string; children: React.ReactNode }) {
const [show, setShow] = useState(false);
return (
<div style={{ position: "relative" }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
{children}
{show && (
<div style={{
position: "absolute", left: "calc(100% + 8px)", top: "50%", transform: "translateY(-50%)",
background: "var(--foreground)", color: "#fff", padding: "4px 10px",
borderRadius: "var(--radius-button)", fontSize: "12px", fontFamily: "var(--font-noto-sans)",
whiteSpace: "nowrap", zIndex: 300, pointerEvents: "none", boxShadow: "var(--elevation-sm)",
}}>
{label}
<span style={{ position: "absolute", right: "100%", top: "50%", transform: "translateY(-50%)", borderWidth: "4px", borderStyle: "solid", borderColor: "transparent var(--foreground) transparent transparent" }} />
</div>
)}
</div>
);
}
/* ─────────────────────────────────────
Inner nav list — shared between drawer and desktop
───────────────────────────────────── */
function NavList({
activeItem, onNavigate, showCollapsed, selectedEventId, onEventSelect,
}: {
activeItem: string;
onNavigate: (id: string) => void;
showCollapsed: boolean;
selectedEventId?: string | null;
onEventSelect?: (id: string) => void;
}) {
const { canModule } = usePermission();
const visibleNav = PORTFOLIO_NAV.filter((item) => navItemAllowed(item.id, canModule));
const defaultOpen = visibleNav.filter(n => n.children).reduce<Record<string, boolean>>((acc, item) => {
acc[item.id] = isChildActive(item, activeItem);
return acc;
}, {});
// Events accordion starts open by default
const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({ events: true, ...defaultOpen });
const toggle = (id: string) => setOpenGroups(p => ({ ...p, [id]: !p[id] }));
const btnStyle = (isActive: boolean): React.CSSProperties => ({
width: "100%", display: "flex", alignItems: "center",
gap: showCollapsed ? 0 : "9px",
padding: showCollapsed ? "8px 0" : "7px 10px",
justifyContent: showCollapsed ? "center" : "flex-start",
borderRadius: "var(--radius-button)",
background: isActive ? "var(--sidebar-accent)" : "transparent",
border: "none", cursor: "pointer", textAlign: "left",
fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)",
fontWeight: isActive ? "var(--font-weight-medium)" : "var(--font-weight-normal)",
color: isActive ? "var(--sidebar-accent-foreground)" : "var(--sidebar-foreground)",
transition: "background 0.12s",
marginBottom: "1px", whiteSpace: "nowrap", overflow: "hidden",
});
const iconStyle = (isActive: boolean): React.CSSProperties => ({
color: isActive ? "var(--sidebar-primary)" : "var(--muted)",
flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center",
});
return (
<>
{visibleNav.map(item => {
/* ── Special: All Events accordion ── */
if (item.id === "events") {
const isEvtActive = activeItem === "events";
const isOpen = !!openGroups["events"] && !showCollapsed;
if (showCollapsed) {
return (
<Tooltip key="events" label="All Events">
<button
onClick={() => onNavigate("events")}
style={{ ...btnStyle(isEvtActive || !!selectedEventId), position: "relative" }}
onMouseEnter={e => { if (!isEvtActive) (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { if (!isEvtActive) (e.currentTarget as HTMLElement).style.background = (isEvtActive || !!selectedEventId) ? "var(--sidebar-accent)" : "transparent"; }}
>
<span style={iconStyle(isEvtActive || !!selectedEventId)}>{item.icon}</span>
{/* Dot indicator when an event is selected */}
{selectedEventId && (
<span style={{
position: "absolute", top: "6px", right: "6px",
width: "6px", height: "6px", borderRadius: "50%",
background: "#16a34a",
borderWidth: "1px", borderStyle: "solid", borderColor: "var(--sidebar)",
}} />
)}
</button>
</Tooltip>
);
}
return (
<div key="events" style={{ marginBottom: "1px" }}>
{/* Header row: label navigates, chevron toggles */}
<div style={{ display: "flex", alignItems: "center" }}>
<button
onClick={() => { onNavigate("events"); if (!isOpen) setOpenGroups(p => ({ ...p, events: true })); }}
style={{
...btnStyle(isEvtActive),
flex: 1,
borderRadius: "var(--radius-button) 0 0 var(--radius-button)",
marginBottom: 0,
overflow: "hidden",
}}
onMouseEnter={e => { if (!isEvtActive) (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { if (!isEvtActive) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
<span style={iconStyle(isEvtActive)}>{item.icon}</span>
<span style={{ flex: 1, fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)", overflow: "hidden", textOverflow: "ellipsis" }}>
{item.label}
</span>
{/* Dot when event selected but list is closed */}
{selectedEventId && !isOpen && (
<span style={{ width: "5px", height: "5px", borderRadius: "50%", background: "var(--primary)", flexShrink: 0, marginRight: "2px" }} />
)}
</button>
</div>
</div>
);
}
/* ── Standard accordion items (Design, Forms, Content, Messaging) ── */
const active = isItemActive(item, activeItem);
const isOpen = !!openGroups[item.id] && !showCollapsed;
if (item.children) {
const GroupBtn = (
<button
onClick={() => toggle(item.id)}
style={btnStyle(active)}
onMouseEnter={e => { if (!active) (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { if (!active) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
<span style={iconStyle(active)}>{item.icon}</span>
{!showCollapsed && <>
<span style={{ flex: 1, fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)" }}>{item.label}</span>
<ChevronRight size={13} style={{ color: "var(--muted)", flexShrink: 0, transform: isOpen ? "rotate(90deg)" : "none", transition: "transform 0.18s" }} />
</>}
</button>
);
return (
<div key={item.id}>
{showCollapsed ? <Tooltip label={item.label}>{GroupBtn}</Tooltip> : GroupBtn}
{isOpen && (
<div style={{ paddingLeft: "14px", marginBottom: "2px" }}>
<div style={{ borderLeft: "1px solid var(--sidebar-border)", paddingLeft: "8px" }}>
{item.children!.filter((child) => navItemAllowed(child.id, canModule)).map(child => {
const cActive = activeItem === child.id;
return (
<button key={child.id} onClick={() => onNavigate(child.id)}
style={{ ...btnStyle(cActive), padding: "5px 8px", fontSize: "var(--text-xs)", gap: "6px" }}
onMouseEnter={e => { if (!cActive) (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { if (!cActive) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
{child.isAction && <Plus size={11} style={{ color: "var(--primary)", flexShrink: 0 }} />}
<span style={{ fontFamily: "var(--font-noto-sans)", fontSize: "var(--text-xs)", color: cActive ? "var(--sidebar-accent-foreground)" : child.isAction ? "var(--primary)" : "var(--card-foreground)" }}>
{child.label}
</span>
</button>
);
})}
</div>
</div>
)}
</div>
);
}
/* ── Leaf items (Faculty, Participants, Payment, User Mgmt) ── */
const LeafBtn = (
<button key={item.id} onClick={() => onNavigate(item.id)} style={btnStyle(active)}
onMouseEnter={e => { if (!active) (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { if (!active) (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
<span style={iconStyle(active)}>{item.icon}</span>
{!showCollapsed && <>
<span style={{ flex: 1, fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)" }}>{item.label}</span>
{active && <span style={{ width: "5px", height: "5px", borderRadius: "50%", background: "var(--primary)", flexShrink: 0 }} />}
</>}
</button>
);
return showCollapsed
? <Tooltip key={item.id} label={item.label}>{LeafBtn}</Tooltip>
: LeafBtn;
})}
</>
);
}
/* ─────────────────────────────────────
Bottom user row
───────────────────────────────────── */
function sidebarUserDisplayName(user: { firstName?: string; lastName?: string; email: string } | null): string {
if (!user) return "User";
if (user.firstName || user.lastName) return [user.firstName, user.lastName].filter(Boolean).join(" ").trim();
return user.email?.split("@")[0] ?? "User";
}
function sidebarUserInitials(user: { firstName?: string; lastName?: string; email: string } | null): string {
const name = sidebarUserDisplayName(user);
return name.slice(0, 2).toUpperCase() || "?";
}
function BottomUser({ collapsed, onNavigate }: { collapsed: boolean; onNavigate: (id: string) => void }) {
const { theme, setTheme } = useTheme();
const { user, logout } = useAuth();
const navigate = useNavigate();
const isPlatformAdmin = user?.platform_admin === "yes";
const displayName = sidebarUserDisplayName(user ?? null);
const initials = sidebarUserInitials(user ?? null);
const email = user?.email ?? "";
const handleLogout = () => {
logout();
navigate("/login", { replace: true });
};
const btnStyle: React.CSSProperties = {
width: "100%", display: "flex", alignItems: "center",
gap: collapsed ? 0 : "9px",
padding: collapsed ? "8px 0" : "7px 10px",
justifyContent: collapsed ? "center" : "flex-start",
borderRadius: "var(--radius-button)", background: "transparent",
border: "none", cursor: "pointer", transition: "background 0.12s",
marginBottom: "1px", whiteSpace: "nowrap", overflow: "hidden",
};
return (
<div style={{ borderTopWidth: "1px", borderTopStyle: "solid", borderTopColor: "var(--sidebar-border)", padding: collapsed ? "8px 4px 12px" : "8px 8px 12px", display: "flex", flexDirection: "column", alignItems: collapsed ? "center" : "stretch", gap: "2px", flexShrink: 0 }}>
{/* ── Appearance toggle ── */}
{collapsed ? (
<Tooltip label={theme === "dark" ? "Switch to Light" : "Switch to Dark"}>
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
style={{ ...btnStyle, marginBottom: "4px" }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}
>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}>
{theme === "dark" ? <Sun size={16} /> : <Moon size={16} />}
</span>
</button>
</Tooltip>
) : (
<div style={{ marginBottom: "4px" }}>
<p style={{ fontSize: "10px", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", fontWeight: "700", letterSpacing: "0.07em", textTransform: "uppercase", margin: "0 0 6px 2px" }}>
Appearance
</p>
<div style={{
display: "flex", alignItems: "center",
background: "var(--sidebar-accent)",
borderTopWidth: "1px", borderRightWidth: "1px", borderBottomWidth: "1px", borderLeftWidth: "1px",
borderTopStyle: "solid", borderRightStyle: "solid", borderBottomStyle: "solid", borderLeftStyle: "solid",
borderTopColor: "var(--sidebar-border)", borderRightColor: "var(--sidebar-border)",
borderBottomColor: "var(--sidebar-border)", borderLeftColor: "var(--sidebar-border)",
borderRadius: "var(--radius-button)", padding: "2px", gap: "2px",
}}>
{(["light", "dark"] as const).map(mode => {
const isActive = theme === mode;
return (
<button
key={mode}
onClick={() => setTheme(mode)}
style={{
flex: 1, display: "flex", alignItems: "center", justifyContent: "center",
padding: "5px",
borderRadius: "var(--radius-button)",
border: "none", cursor: "pointer",
background: isActive ? "var(--sidebar)" : "transparent",
boxShadow: isActive ? "var(--elevation-sm)" : "none",
color: isActive ? "var(--sidebar-foreground)" : "var(--muted)",
transition: "background 0.15s, color 0.15s, box-shadow 0.15s",
}}
onMouseEnter={e => { if (!isActive) (e.currentTarget as HTMLElement).style.color = "var(--sidebar-foreground)"; }}
onMouseLeave={e => { if (!isActive) (e.currentTarget as HTMLElement).style.color = "var(--muted)"; }}
title={mode === "light" ? "Light mode" : "Dark mode"}
>
{mode === "light" ? <Sun size={13} /> : <Moon size={13} />}
</button>
);
})}
</div>
</div>
)}
{isPlatformAdmin &&
(collapsed ? (
<Tooltip label="Go to Super Admin Panel">
<button
type="button"
style={btnStyle}
onClick={() => navigate("/super-admin")}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.background = "var(--accent)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.background = "transparent";
}}
>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}>
<Shield size={16} />
</span>
</button>
</Tooltip>
) : (
<button
type="button"
style={btnStyle}
onClick={() => navigate("/super-admin")}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.background = "var(--accent)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.background = "transparent";
}}
>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}>
<Shield size={16} />
</span>
<span style={{ fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)", color: "var(--sidebar-foreground)" }}>
Go to Super Admin Panel
</span>
</button>
))}
{collapsed ? (
<Tooltip label="Settings">
<button style={btnStyle}
onClick={() => onNavigate("settings")}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}><Settings size={16} /></span>
</button>
</Tooltip>
) : (
<button style={btnStyle}
onClick={() => onNavigate("settings")}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}><Settings size={16} /></span>
<span style={{ fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)", color: "var(--sidebar-foreground)" }}>Settings</span>
</button>
)}
{collapsed ? (
<>
<Tooltip label={`${displayName} · Settings`}>
<button
type="button"
onClick={() => onNavigate("settings")}
style={{ ...btnStyle, padding: "6px 0" }}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.background = "var(--accent)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.background = "transparent";
}}
>
<div
style={{
width: "28px",
height: "28px",
borderRadius: "50%",
background: "var(--primary)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<span style={{ color: "#fff", fontSize: "10px", fontFamily: "var(--font-inter)", fontWeight: "600" }}>
{initials}
</span>
</div>
</button>
</Tooltip>
<Tooltip label="Log out">
<button
type="button"
onClick={handleLogout}
style={btnStyle}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.background = "var(--accent)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.background = "transparent";
}}
>
<span style={{ color: "var(--muted)", flexShrink: 0, display: "flex" }}>
<LogOut size={16} />
</span>
</button>
</Tooltip>
</>
) : (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
padding: "6px 10px",
borderRadius: "var(--radius-button)",
}}
>
<button
type="button"
onClick={() => onNavigate("settings")}
title="Settings"
style={{
display: "flex",
alignItems: "center",
gap: "8px",
flex: 1,
minWidth: 0,
background: "none",
border: "none",
cursor: "pointer",
padding: 0,
textAlign: "left",
}}
onMouseEnter={(e) => {
const nameEl = e.currentTarget.querySelector("[data-user-name]") as HTMLElement | null;
if (nameEl) nameEl.style.color = "var(--primary)";
}}
onMouseLeave={(e) => {
const nameEl = e.currentTarget.querySelector("[data-user-name]") as HTMLElement | null;
if (nameEl) nameEl.style.color = "var(--sidebar-foreground)";
}}
>
<div
style={{
width: "28px",
height: "28px",
borderRadius: "50%",
background: "var(--primary)",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
}}
>
<span style={{ color: "#fff", fontSize: "10px", fontFamily: "var(--font-inter)", fontWeight: "600" }}>
{initials}
</span>
</div>
<div style={{ flex: 1, overflow: "hidden" }}>
<p
data-user-name
style={{
fontSize: "var(--text-xs)",
fontFamily: "var(--font-noto-sans)",
fontWeight: "var(--font-weight-medium)",
color: "var(--sidebar-foreground)",
lineHeight: "1.3",
margin: 0,
}}
>
{displayName}
</p>
<p
style={{
fontSize: "10px",
fontFamily: "var(--font-noto-sans)",
color: "var(--muted)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
margin: 0,
}}
>
{email || "—"}
</p>
</div>
</button>
<button
type="button"
onClick={handleLogout}
title="Log out"
style={{
background: "none",
border: "none",
cursor: "pointer",
color: "var(--muted)",
display: "flex",
padding: "2px",
flexShrink: 0,
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLElement).style.color = "var(--foreground)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLElement).style.color = "var(--muted)";
}}
>
<LogOut size={13} />
</button>
</div>
)}
</div>
);
}
/* ─────────────────────────────────────
Section label
───────────────────────────────────── */
function SectionLabel() {
return (
<div style={{ padding: "10px 14px 4px", flexShrink: 0 }}>
<p style={{ fontSize: "10px", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", fontWeight: "700", letterSpacing: "0.07em", textTransform: "uppercase", margin: 0 }}>Portfolio</p>
</div>
);
}
/* ─────────────────────────────────────
Public Sidebar component
───────────────────────────────────── */
interface SidebarProps {
activeItem: string;
onItemClick: (id: string) => void;
collapsed: boolean;
onToggleCollapse: () => void;
drawerOpen?: boolean;
onDrawerClose?: () => void;
selectedEventId?: string | null;
onEventSelect?: (id: string) => void;
}
export function Sidebar({
activeItem, onItemClick, collapsed, onToggleCollapse,
drawerOpen, onDrawerClose, selectedEventId, onEventSelect,
}: SidebarProps) {
const bp = useBreakpoint();
const isSmall = bp === "mobile" || bp === "tablet";
const handleNav = (id: string) => {
onItemClick(id);
if (isSmall && onDrawerClose) onDrawerClose();
};
const handleEventSelect = (id: string) => {
onEventSelect?.(id);
if (isSmall && onDrawerClose) onDrawerClose();
};
/* ── Mobile / Tablet: slide-in drawer ── */
if (isSmall) {
return (
<>
{/* Backdrop */}
<div
onClick={onDrawerClose}
style={{
position: "fixed", inset: 0,
background: "rgba(0,0,0,0.4)",
zIndex: 40,
opacity: drawerOpen ? 1 : 0,
pointerEvents: drawerOpen ? "auto" : "none",
transition: "opacity 0.22s",
}}
/>
{/* Panel */}
<aside style={{
position: "fixed", top: 0, left: 0, bottom: 0, width: "272px",
background: "var(--sidebar)",
borderRight: "1px solid var(--sidebar-border)",
zIndex: 50,
display: "flex", flexDirection: "column",
transform: drawerOpen ? "translateX(0)" : "translateX(-100%)",
transition: "transform 0.22s cubic-bezier(0.4,0,0.2,1)",
boxShadow: drawerOpen ? "var(--elevation-sm)" : "none",
overflow: "hidden",
}}>
{/* Header */}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 14px 14px 16px", borderBottom: "1px solid var(--sidebar-border)", flexShrink: 0, minHeight: "52px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "9px" }}>
<div style={{ width: "28px", height: "28px", borderRadius: "6px", background: "var(--primary)", display: "flex", alignItems: "center", justifyContent: "center" }}>
<span style={{ color: "#fff", fontSize: "11px", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)" }}>EB</span>
</div>
<span style={{ fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", fontSize: "var(--text-base)", color: "var(--sidebar-foreground)" }}>EventBuzz</span>
</div>
<button onClick={onDrawerClose}
style={{ width: "28px", height: "28px", display: "flex", alignItems: "center", justifyContent: "center", background: "none", border: "none", cursor: "pointer", borderRadius: "var(--radius-button)", color: "var(--muted)" }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "none"; }}>
<X size={15} />
</button>
</div>
<SectionLabel />
<nav style={{ flex: 1, overflowY: "auto", overflowX: "hidden", padding: "4px 8px" }}>
<NavList
activeItem={activeItem}
onNavigate={handleNav}
showCollapsed={false}
selectedEventId={selectedEventId}
onEventSelect={handleEventSelect}
/>
</nav>
<BottomUser collapsed={false} onNavigate={handleNav} />
</aside>
</>
);
}
/* ── Desktop: inline collapsible sidebar ── */
const w = collapsed ? 60 : 240;
return (
<aside style={{
width: `${w}px`, flexShrink: 0, height: "100%",
display: "flex", flexDirection: "column",
background: "var(--sidebar)",
borderRight: "1px solid var(--sidebar-border)",
transition: "width 0.2s cubic-bezier(0.4,0,0.2,1)",
overflow: "hidden", zIndex: 30, position: "relative",
}}>
{/* Logo row */}
<div style={{
display: "flex", alignItems: "center",
justifyContent: collapsed ? "center" : "space-between",
padding: collapsed ? "14px 0" : "14px 12px 14px 14px",
borderBottom: "1px solid var(--sidebar-border)",
flexShrink: 0, minHeight: "52px",
}}>
<div style={{ display: "flex", alignItems: "center", gap: "9px", overflow: "hidden" }}>
<div style={{ width: "28px", height: "28px", borderRadius: "6px", background: "var(--primary)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
<span style={{ color: "#fff", fontSize: "11px", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)" }}>EB</span>
</div>
{!collapsed && <span style={{ fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", fontSize: "var(--text-base)", color: "var(--sidebar-foreground)", whiteSpace: "nowrap", overflow: "hidden" }}>EventBuzz</span>}
</div>
{/* H3: Always show collapse/expand button — critical for user control */}
<button onClick={onToggleCollapse} title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
style={{ width: "24px", height: "24px", display: "flex", alignItems: "center", justifyContent: "center", background: "none", border: "none", cursor: "pointer", borderRadius: "var(--radius-button)", color: "var(--muted)", flexShrink: 0 }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; (e.currentTarget as HTMLElement).style.color = "var(--primary)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "none"; (e.currentTarget as HTMLElement).style.color = "var(--muted)"; }}>
{collapsed ? <ChevronRight size={14} /> : <ChevronLeft size={14} />}
</button>
</div>
{!collapsed && <SectionLabel />}
<nav style={{ flex: 1, overflowY: "auto", overflowX: "hidden", padding: collapsed ? "8px 4px" : "4px 8px" }}>
<NavList
activeItem={activeItem}
onNavigate={handleNav}
showCollapsed={collapsed}
selectedEventId={selectedEventId}
onEventSelect={handleEventSelect}
/>
</nav>
{/* Expand toggle when collapsed */}
{collapsed && (
<div style={{ borderTop: "1px solid var(--sidebar-border)", padding: "8px 4px 4px", display: "flex", justifyContent: "center" }}>
<button onClick={onToggleCollapse} title="Expand"
style={{ width: "36px", height: "28px", display: "flex", alignItems: "center", justifyContent: "center", background: "none", border: "none", cursor: "pointer", borderRadius: "var(--radius-button)", color: "var(--muted)" }}
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = "var(--accent)"; (e.currentTarget as HTMLElement).style.color = "var(--primary)"; }}
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "none"; (e.currentTarget as HTMLElement).style.color = "var(--muted)"; }}>
<ChevronRight size={14} />
</button>
</div>
)}
<BottomUser collapsed={collapsed} onNavigate={handleNav} />
</aside>
);
}