import { Tooltip, TooltipContent, TooltipTrigger } from '@/coop-ui/Tooltip'; import { GQLUserPermission } from '@/graphql/generated'; import { CogFilled, ExitFilled, UserAlt3Filled } from '@/icons'; import AngleDoubleLeft from '@/icons/lni/Direction/angle-double-left.svg?react'; import AngleDoubleRight from '@/icons/lni/Direction/angle-double-right.svg?react'; import { cn } from '@/lib/utils'; import { makeEnumLike } from '@roostorg/types'; import React, { ReactElement, useEffect, useMemo, useState, type SVGProps, } from 'react'; import { Link, useLocation } from 'react-router-dom'; import DashboardMenuButton from '@/webpages/dashboard/components/DashboardMenuButton'; import LogoAndWordmarkPurple from '../images/LogoAndWordmarkPurple.png'; // eslint-disable-next-line @typescript-eslint/no-unused-vars -- value consumed only via `typeof` for MenuItemName const MenuItemNames = makeEnumLike([ 'Overview', 'Automated Enforcement', 'Proactive Rules', 'Report Rules', 'Review Console', 'Queues', 'Routing', 'Analytics', 'Investigation', 'Bulk Actioning', 'Recent Decisions', 'NCMEC Reports', 'Policies', 'Matching Banks', 'Log Out', 'Account', 'Settings', 'Item Types', 'Actions', 'API Keys', 'Integrations', 'Appeal Settings', 'Users', 'Wellness', 'NCMEC Settings', 'SSO', 'Organization', ]); type MenuItemName = keyof typeof MenuItemNames; export type MenuItem = { title: MenuItemName; urlPath: string; icon?: React.JSXElementConstructor>; requiredPermissions: GQLUserPermission[]; subItems?: Omit[]; }; interface SidebarProps { menuItems: MenuItem[]; settingsMenuItems: MenuItem[]; selectedMenuItem: string | null; setSelectedMenuItem: React.Dispatch>; permissions: readonly GQLUserPermission[] | undefined; logout: () => void; isDemoOrg?: boolean; } export default function Sidebar(props: SidebarProps) { const { menuItems, settingsMenuItems, selectedMenuItem, setSelectedMenuItem, permissions, logout, isDemoOrg, } = props; const [collapsed, setCollapsed] = useState(false); const { pathname } = useLocation(); useEffect(() => { const pathParts = pathname.split('/'); let items: MenuItem[] = [...menuItems, ...settingsMenuItems]; if (pathParts.length < 2) { return; } for (let i = 2; i < pathParts.length; i++) { const part = pathParts[i]; const item = items.find((item) => item.urlPath === part); if (item == null) { return; } if (item.subItems) { items = item.subItems; } else { setSelectedMenuItem(item.title); } } }, [menuItems, pathname, settingsMenuItems, setSelectedMenuItem]); const isSettingsSelected = useMemo( () => settingsMenuItems[0]?.subItems?.some( (item) => item.title === selectedMenuItem, ) ?? false, [selectedMenuItem, settingsMenuItems], ); const isDescendant = ( parent: MenuItem, descendantTitle: string | null, ): boolean => { if (descendantTitle == null) { return false; } return ( parent.subItems != null && parent.subItems.some( (subItem) => subItem.title === descendantTitle || isDescendant(subItem, descendantTitle), ) ); }; const recursiveMenuItems = ( item: MenuItem, level: number, prevUrlPath: string, ): ReactElement | null => { if ( item.requiredPermissions.filter( (perm) => permissions?.includes(perm) ?? false, ).length < item.requiredPermissions.length ) { return null; } const subItems = item.subItems?.map((subItem, i) => ( {recursiveMenuItems(subItem, level + 1, item.urlPath)} )); const isInSelectedPath = selectedMenuItem === item.title || isDescendant(item, selectedMenuItem); return (
0 ? `${prevUrlPath}/${item.urlPath}` : `${item.urlPath}` } selected={selectedMenuItem === item.title} onClick={() => { if (collapsed) { setCollapsed(false); } setSelectedMenuItem(item.title); }} level={level} icon={item.icon} collapsed={collapsed} highlighted={isInSelectedPath && level === 0} /> {!collapsed && isInSelectedPath ? subItems : null}
); }; const footerButton = ( props: { icon: React.JSXElementConstructor>; menuItemName: MenuItemName; } & ( | { onClick: () => void; url?: undefined } | { onClick?: undefined; url: string } ), ) => { const { icon: Icon, menuItemName, onClick, url } = props; const isFooterButtonSelected = selectedMenuItem === menuItemName || (menuItemName === 'Settings' && isSettingsSelected); return ( {onClick ? (
{ onClick(); setSelectedMenuItem(menuItemName); }} >
) : ( setSelectedMenuItem(menuItemName)} > )}
{menuItemName}
); }; const isSettingsMenuVisible = isSettingsSelected && !collapsed; const settingsMenu = (
{settingsMenuItems[0]?.subItems?.map((item) => ( setSelectedMenuItem(item.title)} >
{item.title}
))}
); return (
{!collapsed && ( Logo )}
setCollapsed((prev) => !prev)} > {collapsed ? ( ) : ( )}
{menuItems.map((item, i) => ( {recursiveMenuItems(item, 0, '')} ))}
{isDemoOrg ? (
Demo Account
) : null} {settingsMenu}
{!collapsed && footerButton({ icon: ExitFilled, menuItemName: 'Log Out' as const, onClick: async () => logout(), })} {!(collapsed && selectedMenuItem === 'Account') && footerButton({ icon: CogFilled, menuItemName: 'Settings' as const, url: '/dashboard/settings', })} {!(collapsed && selectedMenuItem !== 'Account') && footerButton({ icon: UserAlt3Filled, menuItemName: 'Account' as const, url: '/dashboard/account', })}
); }