Loading portal-gui/src/app/config/navigation.ts +21 −16 Changes for portal-gui/src/app/config/navigation.ts: 21 added lines, 16 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ import { FileText, Server, Rocket, UserIcon, } from 'lucide-react'; import { ROUTES } from './route'; Loading Loading @@ -55,6 +56,12 @@ export const mainNavItems: NavItem[] = [ }, ], }, { title: 'Profile', to: ROUTES.PROFILE, description: 'Your account details and portal roles.', icon: UserIcon, }, ]; export function isActivePath(pathname: string, to: string) { Loading Loading @@ -89,23 +96,21 @@ export function findNavPath( } export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { const navPath = findNavPath(mainNavItems, pathname); if (!navPath) { return [ { title: 'Home', to: ROUTES.HOME, }, ]; // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; for ( let path = pathname; !navPath && path; path = path.slice(0, path.lastIndexOf('/')) ) { navPath = findNavPath(mainNavItems, path); } return [ { title: 'Home', to: ROUTES.HOME, }, ...navPath.map((item) => ({ const trail = (navPath ?? []).map((item) => ({ title: item.title, to: item.to, })), ]; })); return trail[0]?.to === ROUTES.HOME ? trail : [{ title: 'Home', to: ROUTES.HOME }, ...trail]; } portal-gui/src/app/config/route.ts +1 −1 Changes for portal-gui/src/app/config/route.ts: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ export const ROUTES = { HOME: '/', LOGIN: '/login', SETTINGS: '/settings', PROFILE: '/profile', APIS: '/apis', API: '/apis/:id', APPLICATIONS: '/applications', Loading portal-gui/src/app/router.tsx +3 −9 Changes for portal-gui/src/app/router.tsx: 3 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ import { PublicRoute } from './guards/public-route'; // Pages import HomePage from '@/pages/home/index'; import SettingsPage from '@/pages/settings'; import ProfilePage from '@/pages/profile'; import LoginPage from '@/pages/auth/login'; import ApisPage from '@/pages/apis/'; import ApiDetailsPage from '@/pages/apis/api-details'; Loading @@ -23,8 +23,6 @@ import NotFoundPage from '@/pages/errors/not-found'; import { ROUTES } from './config/route'; import { RoleGuard } from './guards/role-guard'; export const router = createBrowserRouter([ // Public routes { Loading Loading @@ -53,12 +51,8 @@ export const router = createBrowserRouter([ element: <HomePage />, }, { path: ROUTES.SETTINGS, element: ( <RoleGuard allowedRoles={['platform-admin']}> <SettingsPage /> </RoleGuard> ), path: ROUTES.PROFILE, element: <ProfilePage />, }, { path: ROUTES.APIS, Loading portal-gui/src/components/common/app-breadcrumbs.tsx +2 −1 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -20,11 +20,12 @@ export function AppBreadcrumbs() { <BreadcrumbList> {items.map((item, index) => { const isLast = index === items.length - 1; const isCurrent = item.to === pathname; return ( <Fragment key={`{item.title}-${index}`}> <BreadcrumbItem> {isLast ? ( {isCurrent ? ( <BreadcrumbPage>{item.title}</BreadcrumbPage> ) : ( <BreadcrumbLink asChild> Loading portal-gui/src/components/common/nav-main.tsx +47 −21 Changes for portal-gui/src/components/common/nav-main.tsx: 47 added lines, 21 removed lines. Original line number Diff line number Diff line import { useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; import { isActivePath } from '@/app/config/navigation'; Loading @@ -6,6 +7,7 @@ import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, Loading Loading @@ -47,26 +49,58 @@ export function NavMain({ items }: { items: NavItem[] }) { </SidebarMenuItem> ); } // Navigation item with children const childIsActive = item.children!.some((child) => isActivePath(pathname, child.to), return ( <NavGroup key={item.to} item={item} pathname={pathname} onNavigate={() => setOpenMobile(false)} /> ); })} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> ); } type NavGroupProps = { item: NavItem; pathname: string; onNavigate: () => void; }; // The label opens the section's page; the chevron only expands the submenu. function NavGroup({ item, pathname, onNavigate }: NavGroupProps) { const isActive = isActivePath(pathname, item.to); const [open, setOpen] = useState(isActive); const Icon = item.icon; return ( <SidebarMenuItem key={item.to}> <Collapsible defaultOpen={childIsActive} className="group/collapsible" > <CollapsibleTrigger asChild> <Collapsible open={open} onOpenChange={setOpen} asChild> <SidebarMenuItem> <SidebarMenuButton asChild isActive={isActive} tooltip={item.title} isActive={childIsActive} className="text-muted-foreground hover:text-foreground data-[active=true]:text-primary" > <Link to={item.to} onClick={() => { setOpen(true); onNavigate(); }} > <Icon /> <span>{item.title}</span> <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" /> </Link> </SidebarMenuButton> <CollapsibleTrigger asChild> <SidebarMenuAction className="data-[state=open]:rotate-90"> <ChevronRight /> <span className="sr-only">Toggle {item.title}</span> </SidebarMenuAction> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> Loading @@ -79,10 +113,7 @@ export function NavMain({ items }: { items: NavItem[] }) { asChild isActive={pathname === child.to} > <Link to={child.to} onClick={() => setOpenMobile(false)} > <Link to={child.to} onClick={onNavigate}> <ChildIcon /> <span>{child.title}</span> </Link> Loading @@ -92,12 +123,7 @@ export function NavMain({ items }: { items: NavItem[] }) { })} </SidebarMenuSub> </CollapsibleContent> </Collapsible> </SidebarMenuItem> ); })} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </Collapsible> ); } Loading
portal-gui/src/app/config/navigation.ts +21 −16 Changes for portal-gui/src/app/config/navigation.ts: 21 added lines, 16 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ import { FileText, Server, Rocket, UserIcon, } from 'lucide-react'; import { ROUTES } from './route'; Loading Loading @@ -55,6 +56,12 @@ export const mainNavItems: NavItem[] = [ }, ], }, { title: 'Profile', to: ROUTES.PROFILE, description: 'Your account details and portal roles.', icon: UserIcon, }, ]; export function isActivePath(pathname: string, to: string) { Loading Loading @@ -89,23 +96,21 @@ export function findNavPath( } export function getBreadcrumbItems(pathname: string): BreadcrumbItem[] { const navPath = findNavPath(mainNavItems, pathname); if (!navPath) { return [ { title: 'Home', to: ROUTES.HOME, }, ]; // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; for ( let path = pathname; !navPath && path; path = path.slice(0, path.lastIndexOf('/')) ) { navPath = findNavPath(mainNavItems, path); } return [ { title: 'Home', to: ROUTES.HOME, }, ...navPath.map((item) => ({ const trail = (navPath ?? []).map((item) => ({ title: item.title, to: item.to, })), ]; })); return trail[0]?.to === ROUTES.HOME ? trail : [{ title: 'Home', to: ROUTES.HOME }, ...trail]; }
portal-gui/src/app/config/route.ts +1 −1 Changes for portal-gui/src/app/config/route.ts: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ export const ROUTES = { HOME: '/', LOGIN: '/login', SETTINGS: '/settings', PROFILE: '/profile', APIS: '/apis', API: '/apis/:id', APPLICATIONS: '/applications', Loading
portal-gui/src/app/router.tsx +3 −9 Changes for portal-gui/src/app/router.tsx: 3 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ import { PublicRoute } from './guards/public-route'; // Pages import HomePage from '@/pages/home/index'; import SettingsPage from '@/pages/settings'; import ProfilePage from '@/pages/profile'; import LoginPage from '@/pages/auth/login'; import ApisPage from '@/pages/apis/'; import ApiDetailsPage from '@/pages/apis/api-details'; Loading @@ -23,8 +23,6 @@ import NotFoundPage from '@/pages/errors/not-found'; import { ROUTES } from './config/route'; import { RoleGuard } from './guards/role-guard'; export const router = createBrowserRouter([ // Public routes { Loading Loading @@ -53,12 +51,8 @@ export const router = createBrowserRouter([ element: <HomePage />, }, { path: ROUTES.SETTINGS, element: ( <RoleGuard allowedRoles={['platform-admin']}> <SettingsPage /> </RoleGuard> ), path: ROUTES.PROFILE, element: <ProfilePage />, }, { path: ROUTES.APIS, Loading
portal-gui/src/components/common/app-breadcrumbs.tsx +2 −1 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -20,11 +20,12 @@ export function AppBreadcrumbs() { <BreadcrumbList> {items.map((item, index) => { const isLast = index === items.length - 1; const isCurrent = item.to === pathname; return ( <Fragment key={`{item.title}-${index}`}> <BreadcrumbItem> {isLast ? ( {isCurrent ? ( <BreadcrumbPage>{item.title}</BreadcrumbPage> ) : ( <BreadcrumbLink asChild> Loading
portal-gui/src/components/common/nav-main.tsx +47 −21 Changes for portal-gui/src/components/common/nav-main.tsx: 47 added lines, 21 removed lines. Original line number Diff line number Diff line import { useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { ChevronRight } from 'lucide-react'; import { isActivePath } from '@/app/config/navigation'; Loading @@ -6,6 +7,7 @@ import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, Loading Loading @@ -47,26 +49,58 @@ export function NavMain({ items }: { items: NavItem[] }) { </SidebarMenuItem> ); } // Navigation item with children const childIsActive = item.children!.some((child) => isActivePath(pathname, child.to), return ( <NavGroup key={item.to} item={item} pathname={pathname} onNavigate={() => setOpenMobile(false)} /> ); })} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> ); } type NavGroupProps = { item: NavItem; pathname: string; onNavigate: () => void; }; // The label opens the section's page; the chevron only expands the submenu. function NavGroup({ item, pathname, onNavigate }: NavGroupProps) { const isActive = isActivePath(pathname, item.to); const [open, setOpen] = useState(isActive); const Icon = item.icon; return ( <SidebarMenuItem key={item.to}> <Collapsible defaultOpen={childIsActive} className="group/collapsible" > <CollapsibleTrigger asChild> <Collapsible open={open} onOpenChange={setOpen} asChild> <SidebarMenuItem> <SidebarMenuButton asChild isActive={isActive} tooltip={item.title} isActive={childIsActive} className="text-muted-foreground hover:text-foreground data-[active=true]:text-primary" > <Link to={item.to} onClick={() => { setOpen(true); onNavigate(); }} > <Icon /> <span>{item.title}</span> <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" /> </Link> </SidebarMenuButton> <CollapsibleTrigger asChild> <SidebarMenuAction className="data-[state=open]:rotate-90"> <ChevronRight /> <span className="sr-only">Toggle {item.title}</span> </SidebarMenuAction> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> Loading @@ -79,10 +113,7 @@ export function NavMain({ items }: { items: NavItem[] }) { asChild isActive={pathname === child.to} > <Link to={child.to} onClick={() => setOpenMobile(false)} > <Link to={child.to} onClick={onNavigate}> <ChildIcon /> <span>{child.title}</span> </Link> Loading @@ -92,12 +123,7 @@ export function NavMain({ items }: { items: NavItem[] }) { })} </SidebarMenuSub> </CollapsibleContent> </Collapsible> </SidebarMenuItem> ); })} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> </Collapsible> ); }