'use client' import { useState, useEffect, useRef } from 'react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { listSites, type Site } from '@/lib/api/sites' import { useAuth } from '@/lib/auth/context' import { LayoutDashboardIcon, PathIcon, FunnelIcon, CursorClickIcon, SearchIcon, CloudUploadIcon, HeartbeatIcon, SettingsIcon, CollapseLeftIcon, CollapseRightIcon, ChevronUpDownIcon, PlusIcon, XIcon, MenuIcon, } from '@ciphera-net/ui' const SIDEBAR_COLLAPSED_KEY = 'pulse_sidebar_collapsed' interface NavItem { label: string href: (siteId: string) => string icon: React.ComponentType<{ className?: string; weight?: 'thin' | 'light' | 'regular' | 'bold' | 'fill' | 'duotone' }> matchPrefix?: boolean } interface NavGroup { label: string items: NavItem[] } const NAV_GROUPS: NavGroup[] = [ { label: 'Analytics', items: [ { label: 'Dashboard', href: (id) => `/sites/${id}`, icon: LayoutDashboardIcon }, { label: 'Journeys', href: (id) => `/sites/${id}/journeys`, icon: PathIcon, matchPrefix: true }, { label: 'Funnels', href: (id) => `/sites/${id}/funnels`, icon: FunnelIcon, matchPrefix: true }, { label: 'Behavior', href: (id) => `/sites/${id}/behavior`, icon: CursorClickIcon, matchPrefix: true }, { label: 'Search', href: (id) => `/sites/${id}/search`, icon: SearchIcon, matchPrefix: true }, ], }, { label: 'Infrastructure', items: [ { label: 'CDN', href: (id) => `/sites/${id}/cdn`, icon: CloudUploadIcon, matchPrefix: true }, { label: 'Uptime', href: (id) => `/sites/${id}/uptime`, icon: HeartbeatIcon, matchPrefix: true }, ], }, ] const SETTINGS_ITEM: NavItem = { label: 'Settings', href: (id) => `/sites/${id}/settings`, icon: SettingsIcon, matchPrefix: true, } function SitePicker({ sites, currentSiteId, collapsed, }: { sites: Site[] currentSiteId: string collapsed: boolean }) { const [open, setOpen] = useState(false) const [search, setSearch] = useState('') const ref = useRef(null) const pathname = usePathname() const router = useRouter() const currentSite = sites.find((s) => s.id === currentSiteId) useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false) setSearch('') } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const filtered = sites.filter( (s) => s.name.toLowerCase().includes(search.toLowerCase()) || s.domain.toLowerCase().includes(search.toLowerCase()) ) const switchSite = (siteId: string) => { // Preserve current page type const currentPageType = pathname.replace(/^\/sites\/[^/]+/, '') router.push(`/sites/${siteId}${currentPageType}`) setOpen(false) setSearch('') } const initial = currentSite?.name?.charAt(0)?.toUpperCase() || '?' return (
{open && (
setSearch(e.target.value)} className="w-full px-3 py-1.5 text-sm bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg outline-none focus:ring-2 focus:ring-brand-orange/40 text-neutral-900 dark:text-white placeholder:text-neutral-400" autoFocus />
{filtered.map((site) => ( ))} {filtered.length === 0 && (

No sites found

)}
setOpen(false)} className="flex items-center gap-2 px-3 py-1.5 text-sm text-brand-orange hover:bg-neutral-50 dark:hover:bg-neutral-800 rounded-lg transition-colors" > Add new site
)}
) } function NavItemLink({ item, siteId, collapsed, onClick, }: { item: NavItem siteId: string collapsed: boolean onClick?: () => void }) { const pathname = usePathname() const href = item.href(siteId) const isActive = item.matchPrefix ? pathname.startsWith(href) : pathname === href return ( {!collapsed && {item.label}} ) } export default function Sidebar({ siteId, mobileOpen, onMobileClose, }: { siteId: string mobileOpen: boolean onMobileClose: () => void }) { const { user } = useAuth() const canEdit = user?.role === 'owner' || user?.role === 'admin' const [collapsed, setCollapsed] = useState(() => { if (typeof window === 'undefined') return false return localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === 'true' }) const [sites, setSites] = useState([]) const pathname = usePathname() // Close mobile drawer on navigation useEffect(() => { onMobileClose() }, [pathname, onMobileClose]) useEffect(() => { listSites() .then(setSites) .catch(() => {}) }, []) const toggleCollapsed = () => { const next = !collapsed setCollapsed(next) localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(next)) } const sidebarContent = (isMobile: boolean) => { const isCollapsed = isMobile ? false : collapsed return (
{/* Logo */} Pulse {!isCollapsed && ( Pulse )} {/* Site Picker */}
{/* Nav Groups */} {/* Bottom: Settings + Collapse toggle */}
{canEdit && ( onMobileClose() : undefined} /> )} {!isMobile && ( )}
) } return ( <> {/* Mobile hamburger trigger — rendered in the header via leftActions */} {/* Desktop sidebar */} {/* Mobile overlay drawer */} {mobileOpen && ( <>
onMobileClose()} /> )} ) } export function SidebarMobileToggle({ onClick }: { onClick: () => void }) { return ( ) }