mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: move admin menu into nav sidebar (#9774)
This commit is contained in:
		
							parent
							
								
									86cfb2f651
								
							
						
					
					
						commit
						ab594f5c29
					
				| @ -1,11 +1,11 @@ | ||||
| import type { INavigationMenuItem } from 'interfaces/route'; | ||||
| 
 | ||||
| export const adminGroups: Record<string, string> = { | ||||
|     users: 'User administration', | ||||
|     users: 'User config', | ||||
|     access: 'Access control', | ||||
|     sso: 'Single sign-on', | ||||
|     network: 'Network', | ||||
|     instance: 'Instance configuration', | ||||
|     instance: 'Instance config', | ||||
| }; | ||||
| 
 | ||||
| export const adminRoutes: INavigationMenuItem[] = [ | ||||
| @ -87,7 +87,7 @@ export const adminRoutes: INavigationMenuItem[] = [ | ||||
| 
 | ||||
|     // Single sign-on/login
 | ||||
|     { | ||||
|         path: '/admin/auth', | ||||
|         path: '/admin/auth/oidc', | ||||
|         title: 'Open ID Connect', | ||||
|         menu: { adminSettings: true, mode: ['enterprise'] }, | ||||
|         group: 'sso', | ||||
|  | ||||
| @ -9,7 +9,7 @@ import { GoogleAuth } from './GoogleAuth/GoogleAuth'; | ||||
| import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard'; | ||||
| import { ADMIN, UPDATE_AUTH_CONFIGURATION } from '@server/types/permissions'; | ||||
| import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature'; | ||||
| import { Route, Routes, useLocation } from 'react-router-dom'; | ||||
| import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| @ -20,7 +20,7 @@ export const AuthSettings = () => { | ||||
|     const tabs = [ | ||||
|         { | ||||
|             label: 'Single sign-on: OpenID Connect', | ||||
|             path: '/admin/auth', | ||||
|             path: '/admin/auth/oidc', | ||||
|         }, | ||||
|         { | ||||
|             label: 'Single sign-on: SAML 2.0', | ||||
| @ -55,7 +55,12 @@ export const AuthSettings = () => { | ||||
|             <PermissionGuard permissions={[ADMIN, UPDATE_AUTH_CONFIGURATION]}> | ||||
|                 <PageContent header={activeTab}> | ||||
|                     <Routes> | ||||
|                         <Route path='/' element={<OidcAuth />} /> | ||||
|                         <Route | ||||
|                             path='/' | ||||
|                             index | ||||
|                             element={<Navigate to='/admin/auth/oidc' />} | ||||
|                         /> | ||||
|                         <Route path='/oidc' index element={<OidcAuth />} /> | ||||
|                         <Route path='/saml' element={<SamlAuth />} /> | ||||
|                         <Route path='/password' element={<PasswordAuth />} /> | ||||
|                         {googleAuthEnabled && ( | ||||
|  | ||||
| @ -10,7 +10,7 @@ import { | ||||
|     AccordionDetails, | ||||
| } from '@mui/material'; | ||||
| import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | ||||
| import type { FC, ReactNode } from 'react'; | ||||
| import { useState, type FC, type ReactNode } from 'react'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import type { Theme } from '@mui/material/styles/createTheme'; | ||||
| 
 | ||||
| @ -18,15 +18,29 @@ const listItemButtonStyle = (theme: Theme) => ({ | ||||
|     borderRadius: theme.spacing(0.5), | ||||
|     borderLeft: `${theme.spacing(0.5)} solid transparent`, | ||||
|     m: 0, | ||||
|     paddingTop: theme.spacing(1), | ||||
|     paddingBottom: theme.spacing(1), | ||||
|     '&.Mui-selected': { | ||||
|         borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`, | ||||
|         backgroundColor: '#607B81', | ||||
|         color: theme.palette.common.white, | ||||
|         '& p': { | ||||
|             fontWeight: theme.typography.fontWeightBold, | ||||
|         }, | ||||
|         '&:hover': { | ||||
|             backgroundColor: theme.palette.action.hover, | ||||
|             color: 'inherit', | ||||
|             fontWeight: theme.typography.fontWeightLight, | ||||
|             '& p': { | ||||
|                 fontWeight: theme.typography.fontWeightLight, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
|     minHeight: '0px', | ||||
|     '.MuiAccordionSummary-content': { margin: 0 }, | ||||
|     '&>.MuiAccordionSummary-content.MuiAccordionSummary-content': { | ||||
|         margin: '0', | ||||
|         alignItems: 'center', | ||||
|         padding: theme.spacing(0.5, 0), | ||||
|         padding: theme.spacing(0.1, 0), | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| @ -35,17 +49,32 @@ const subListItemButtonStyle = (theme: Theme) => ({ | ||||
|     borderRadius: theme.spacing(0.5), | ||||
|     borderLeft: `${theme.spacing(0.5)} solid transparent`, | ||||
|     m: 0, | ||||
|     paddingTop: theme.spacing(0.75), | ||||
|     paddingBottom: theme.spacing(0.75), | ||||
|     '&.Mui-selected': { | ||||
|         borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`, | ||||
|         backgroundColor: '#607B81', | ||||
|         color: theme.palette.common.white, | ||||
|         '& p': { | ||||
|             fontWeight: theme.typography.fontWeightBold, | ||||
|         }, | ||||
|         '&:hover': { | ||||
|             backgroundColor: theme.palette.action.hover, | ||||
|             color: 'inherit', | ||||
|             fontWeight: theme.typography.fontWeightLight, | ||||
|             '& p': { | ||||
|                 fontWeight: theme.typography.fontWeightLight, | ||||
|             }, | ||||
|         }, | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| const CappedText = styled(Typography)({ | ||||
| const CappedText = styled(Typography)(({ theme }) => ({ | ||||
|     whiteSpace: 'nowrap', | ||||
|     overflow: 'hidden', | ||||
|     textOverflow: 'ellipsis', | ||||
|     width: '100%', | ||||
| }); | ||||
|     paddingTop: theme.spacing(0.25), | ||||
| })); | ||||
| 
 | ||||
| const StyledListItemIcon = styled(ListItemIcon)(({ theme }) => ({ | ||||
|     minWidth: theme.spacing(4), | ||||
| @ -58,6 +87,7 @@ const StyledListItemText = styled(ListItemText)(({ theme }) => ({ | ||||
| 
 | ||||
| const StyledAccordion = styled(Accordion)(({ theme }) => ({ | ||||
|     paddingTop: theme.spacing(0), | ||||
|     backgroundColor: 'inherit', | ||||
| })); | ||||
| 
 | ||||
| const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ | ||||
| @ -84,10 +114,16 @@ export const MenuGroup = ({ | ||||
|     isActiveMenu, | ||||
|     staticExpanded, | ||||
| }: IMenuGroupProps) => { | ||||
|     const [isExpanded, setIsExpanded] = staticExpanded | ||||
|         ? [true, () => undefined] | ||||
|         : useState<boolean>(isActiveMenu); | ||||
|     return ( | ||||
|         <StyledAccordion | ||||
|             disableGutters={true} | ||||
|             expanded={staticExpanded} | ||||
|             expanded={isExpanded} | ||||
|             onChange={(element, expanded) => { | ||||
|                 setIsExpanded(expanded); | ||||
|             }} | ||||
|             sx={{ | ||||
|                 boxShadow: 'none', | ||||
|                 '&:before': { | ||||
|  | ||||
| @ -35,9 +35,9 @@ export const IconRenderer: FC<{ path: string; active: boolean }> = ({ | ||||
| 
 | ||||
|     return ( | ||||
|         <IconComponent | ||||
|             sx={{ | ||||
|                 color: active ? 'primary.main' : 'inherit', | ||||
|             }} | ||||
|             sx={(theme) => ({ | ||||
|                 color: active ? theme.palette.common.white : 'inherit', | ||||
|             })} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,4 +1,12 @@ | ||||
| import { Button, styled, Typography, List } from '@mui/material'; | ||||
| import { | ||||
|     styled, | ||||
|     Typography, | ||||
|     List, | ||||
|     ListItem, | ||||
|     ListItemButton, | ||||
|     ListItemText, | ||||
|     type Theme, | ||||
| } from '@mui/material'; | ||||
| import { OtherLinksList } from '../NavigationSidebar/NavigationList'; | ||||
| import ArrowBackIcon from '@mui/icons-material/ArrowBack'; | ||||
| import StopRoundedIcon from '@mui/icons-material/StopRounded'; | ||||
| @ -6,11 +14,11 @@ import { AdminListItem, AdminSubListItem, MenuGroup } from './AdminListItem'; | ||||
| import { IconRenderer } from './AdminMenuIcons'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; | ||||
| import { useLocation, useNavigate } from 'react-router-dom'; | ||||
| import { Link, useLocation } from 'react-router-dom'; | ||||
| import { filterByConfig } from 'component/common/util'; | ||||
| import { filterAdminRoutes } from 'component/admin/filterAdminRoutes'; | ||||
| import { adminGroups, adminRoutes } from 'component/admin/adminRoutes'; | ||||
| import type { ReactNode } from 'react'; | ||||
| import { useEffect, useState, type ReactNode } from 'react'; | ||||
| import type { INavigationMenuItem } from 'interfaces/route'; | ||||
| import { useShowBadge } from 'component/layout/components/EnterprisePlanBadge/useShowBadge'; | ||||
| import { EnterprisePlanBadge } from 'component/layout/components/EnterprisePlanBadge/EnterprisePlanBadge'; | ||||
| @ -34,31 +42,72 @@ const SettingsHeader = styled(Typography)(({ theme }) => ({ | ||||
|     fontWeight: theme.fontWeight.bold, | ||||
| })); | ||||
| 
 | ||||
| const StyledButton = styled(Button)(({ theme }) => ({ | ||||
|     paddingLeft: theme.spacing(0), | ||||
|     marginBottom: theme.spacing(3), | ||||
| const CappedText = styled(Typography)(({ theme }) => ({ | ||||
|     whiteSpace: 'nowrap', | ||||
|     overflow: 'hidden', | ||||
|     textOverflow: 'ellipsis', | ||||
|     width: '100%', | ||||
|     paddingTop: theme.spacing(0.25), | ||||
|     marginLeft: theme.spacing(0.75), | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
| })); | ||||
| 
 | ||||
| const StyledListItemText = styled(ListItemText)(({ theme }) => ({ | ||||
|     margin: 0, | ||||
| })); | ||||
| 
 | ||||
| const StyledDiv = styled('div')(({ theme }) => ({ | ||||
|     padding: theme.spacing(2, 2.5, 0, 2.5), | ||||
| 
 | ||||
|     '&.MuiButton-root': { | ||||
|         padding: theme.spacing(0), | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({ | ||||
|     color: theme.palette.primary.main, | ||||
|     color: '#607B81', | ||||
| })); | ||||
| 
 | ||||
| export const DashboardLink = () => { | ||||
|     const navigate = useNavigate(); | ||||
| const ActiveStyledStopRoundedIcon = styled(StopRoundedIcon)(({ theme }) => ({ | ||||
|     color: theme.palette.common.white, | ||||
| })); | ||||
| 
 | ||||
| const listItemButtonStyle = (theme: Theme) => ({ | ||||
|     borderRadius: theme.spacing(0.5), | ||||
|     borderLeft: `${theme.spacing(0.5)} solid transparent`, | ||||
|     color: theme.palette.primary.main, | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
|     m: 0, | ||||
|     paddingTop: theme.spacing(1), | ||||
|     paddingBottom: theme.spacing(1), | ||||
|     minHeight: '0px', | ||||
|     '.MuiAccordionSummary-content': { margin: 0 }, | ||||
|     '&>.MuiAccordionSummary-content.MuiAccordionSummary-content': { | ||||
|         margin: '0', | ||||
|         alignItems: 'center', | ||||
|         padding: theme.spacing(0.1, 0), | ||||
|     }, | ||||
| }); | ||||
| 
 | ||||
| export const DashboardLink = ({ onClick }: { onClick: () => void }) => { | ||||
|     return ( | ||||
|         <StyledButton | ||||
|             onClick={() => { | ||||
|                 navigate(`/personal`); | ||||
|             }} | ||||
|             rel='noreferrer' | ||||
|             startIcon={<ArrowBackIcon />} | ||||
|         > | ||||
|             Back to Unleash | ||||
|         </StyledButton> | ||||
|         <List> | ||||
|             <ListItem disablePadding> | ||||
|                 <ListItemButton | ||||
|                     dense={true} | ||||
|                     component={Link} | ||||
|                     to='/personal' | ||||
|                     sx={listItemButtonStyle} | ||||
|                     selected={false} | ||||
|                     onClick={onClick} | ||||
|                 > | ||||
|                     <ArrowBackIcon /> | ||||
|                     <StyledListItemText> | ||||
|                         <CappedText>Back to Unleash</CappedText> | ||||
|                     </StyledListItemText> | ||||
|                 </ListItemButton> | ||||
|             </ListItem> | ||||
|         </List> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| @ -66,8 +115,9 @@ export const AdminMobileNavigation = ({ onClick }: { onClick: () => void }) => { | ||||
|     return ( | ||||
|         <> | ||||
|             <StyledDiv> | ||||
|                 <AdminNavigationHeader /> | ||||
|                 <SettingsHeader>Admin settings</SettingsHeader> | ||||
|             </StyledDiv> | ||||
|             <DashboardLink onClick={onClick} /> | ||||
| 
 | ||||
|             <AdminNavigationItems staticExpanded={true} onClick={onClick} /> | ||||
| 
 | ||||
| @ -79,29 +129,18 @@ export const AdminMobileNavigation = ({ onClick }: { onClick: () => void }) => { | ||||
| export const AdminMenuNavigation = ({ onClick }: { onClick: () => void }) => { | ||||
|     return ( | ||||
|         <> | ||||
|             <AdminNavigationHeader /> | ||||
|             <DashboardLink onClick={onClick} /> | ||||
|             <AdminNavigationItems onClick={onClick} /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const AdminNavigationHeader = () => { | ||||
|     return ( | ||||
|         <> | ||||
|             <SettingsHeader>Admin settings</SettingsHeader> | ||||
|             <DashboardLink /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const AdminNavigationItems = ({ | ||||
|     onClick, | ||||
|     staticExpanded, | ||||
| }: { onClick: () => void; staticExpanded?: true | undefined }) => { | ||||
|     const { uiConfig, isPro, isEnterprise } = useUiConfig(); | ||||
|     const { isBilling } = useInstanceStatus(); | ||||
|     const isActiveItem = (item?: string) => | ||||
|         item !== undefined && location.pathname === item; | ||||
|     const location = useLocation(); | ||||
|     const showBadge = useShowBadge(); | ||||
| 
 | ||||
| @ -115,6 +154,28 @@ export const AdminNavigationItems = ({ | ||||
|             }), | ||||
|         ); | ||||
| 
 | ||||
|     const findActiveItem = () => { | ||||
|         const activeItem = routes.find( | ||||
|             (route) => route.path === location.pathname, | ||||
|         ); | ||||
|         if (!activeItem) { | ||||
|             return routes.find( | ||||
|                 (route) => | ||||
|                     route.path !== '/admin' && | ||||
|                     location.pathname.startsWith(route.path), | ||||
|             )?.path; | ||||
|         } | ||||
|         return activeItem.path; | ||||
|     }; | ||||
|     const [activeItem, setActiveItem] = useState<string | undefined>( | ||||
|         findActiveItem(), | ||||
|     ); | ||||
|     const isActiveItem = (item?: string) => | ||||
|         item !== undefined && activeItem !== undefined && item === activeItem; | ||||
|     useEffect(() => { | ||||
|         setActiveItem(findActiveItem()); | ||||
|     }, [location, location.pathname]); | ||||
| 
 | ||||
|     const menuStructure = routes.reduce( | ||||
|         (acc: Record<string, IMenuItem>, route) => { | ||||
|             if (route.group && adminGroups[route.group]) { | ||||
| @ -159,7 +220,7 @@ export const AdminNavigationItems = ({ | ||||
|                                 <IconRenderer path={item.href} active={false} /> | ||||
|                             } | ||||
|                             activeIcon={ | ||||
|                                 <IconRenderer path={item.href} active={true} /> | ||||
|                                 <IconRenderer path={item.href} active={false} /> | ||||
|                             } | ||||
|                             isActiveMenu={Boolean(isActiveMenu)} | ||||
|                             key={item.text} | ||||
| @ -178,7 +239,11 @@ export const AdminNavigationItems = ({ | ||||
|                                         ) : null | ||||
|                                     } | ||||
|                                 > | ||||
|                                     <StyledStopRoundedIcon /> | ||||
|                                     {isActiveItem(subItem.href) ? ( | ||||
|                                         <ActiveStyledStopRoundedIcon /> | ||||
|                                     ) : ( | ||||
|                                         <StyledStopRoundedIcon /> | ||||
|                                     )} | ||||
|                                 </AdminSubListItem> | ||||
|                             ))} | ||||
|                         </MenuGroup> | ||||
| @ -197,7 +262,10 @@ export const AdminNavigationItems = ({ | ||||
|                             ) : null | ||||
|                         } | ||||
|                     > | ||||
|                         <IconRenderer path={item.href} active={false} /> | ||||
|                         <IconRenderer | ||||
|                             path={item.href} | ||||
|                             active={isActiveItem(item.href)} | ||||
|                         /> | ||||
|                     </AdminListItem> | ||||
|                 ); | ||||
|             })} | ||||
|  | ||||
| @ -18,9 +18,6 @@ import { NavigationSidebar } from './NavigationSidebar/NavigationSidebar'; | ||||
| import { EventTimelineProvider } from 'component/events/EventTimeline/EventTimelineProvider'; | ||||
| import { NewInUnleash } from './NavigationSidebar/NewInUnleash/NewInUnleash'; | ||||
| 
 | ||||
| import { WrapIfAdminSubpage } from './AdminMenu/AdminMenu'; | ||||
| import { useNewAdminMenu } from '../../../hooks/useNewAdminMenu'; | ||||
| 
 | ||||
| interface IMainLayoutProps { | ||||
|     children: ReactNode; | ||||
| } | ||||
| @ -34,6 +31,34 @@ const MainLayoutContainer = styled(Grid)(() => ({ | ||||
|     position: 'relative', | ||||
| })); | ||||
| 
 | ||||
| const MainLayoutContent = styled(Grid)(({ theme }) => ({ | ||||
|     minWidth: 0, // this is a fix for overflowing flex
 | ||||
|     maxWidth: `1512px`, | ||||
|     margin: '0 auto', | ||||
|     paddingLeft: theme.spacing(2), | ||||
|     paddingRight: theme.spacing(2), | ||||
|     [theme.breakpoints.up(1856)]: { | ||||
|         width: '100%', | ||||
|     }, | ||||
|     [theme.breakpoints.down(1856)]: { | ||||
|         marginLeft: theme.spacing(7), | ||||
|         marginRight: theme.spacing(7), | ||||
|     }, | ||||
|     [theme.breakpoints.down('lg')]: { | ||||
|         maxWidth: `1250px`, | ||||
|         paddingLeft: theme.spacing(1), | ||||
|         paddingRight: theme.spacing(1), | ||||
|     }, | ||||
|     [theme.breakpoints.down(1024)]: { | ||||
|         marginLeft: 0, | ||||
|         marginRight: 0, | ||||
|     }, | ||||
|     [theme.breakpoints.down('sm')]: { | ||||
|         minWidth: '100%', | ||||
|     }, | ||||
|     minHeight: '94vh', | ||||
| })); | ||||
| 
 | ||||
| const MainLayoutContentWrapper = styled('div')(({ theme }) => ({ | ||||
|     margin: theme.spacing(0, 'auto'), | ||||
|     flexGrow: 1, | ||||
| @ -65,7 +90,6 @@ const MainLayoutContentContainer = styled('main')(({ theme }) => ({ | ||||
| 
 | ||||
| export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>( | ||||
|     ({ children }, ref) => { | ||||
|         const showOnlyAdminMenu = useNewAdminMenu(); | ||||
|         const { uiConfig } = useUiConfig(); | ||||
|         const projectId = useOptionalPathParam('projectId'); | ||||
|         const { isChangeRequestConfiguredInAnyEnv } = useChangeRequestsEnabled( | ||||
| @ -74,9 +98,6 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>( | ||||
|         const theme = useTheme(); | ||||
|         const isSmallScreen = useMediaQuery(theme.breakpoints.down('lg')); | ||||
| 
 | ||||
|         const showRegularNavigationSideBar = | ||||
|             !isSmallScreen && !showOnlyAdminMenu; | ||||
| 
 | ||||
|         return ( | ||||
|             <EventTimelineProvider> | ||||
|                 <SkipNavLink /> | ||||
| @ -97,7 +118,7 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>( | ||||
|                             })} | ||||
|                         > | ||||
|                             <ConditionallyRender | ||||
|                                 condition={showRegularNavigationSideBar} | ||||
|                                 condition={!isSmallScreen} | ||||
|                                 show={ | ||||
|                                     <NavigationSidebar | ||||
|                                         NewInUnleash={NewInUnleash} | ||||
| @ -115,14 +136,14 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>( | ||||
|                             > | ||||
|                                 <Header /> | ||||
| 
 | ||||
|                                 <WrapIfAdminSubpage> | ||||
|                                 <MainLayoutContent> | ||||
|                                     <SkipNavTarget /> | ||||
|                                     <MainLayoutContentContainer ref={ref}> | ||||
|                                         <BreadcrumbNav /> | ||||
|                                         <Proclamation toast={uiConfig.toast} /> | ||||
|                                         {children} | ||||
|                                     </MainLayoutContentContainer> | ||||
|                                 </WrapIfAdminSubpage> | ||||
|                                 </MainLayoutContent> | ||||
|                             </Box> | ||||
|                         </Box> | ||||
| 
 | ||||
|  | ||||
| @ -60,6 +60,7 @@ const icons: Record< | ||||
|     '/admin/roles/project-roles': RoleIcon, | ||||
|     '/admin/api': ApiAccessIcon, | ||||
|     '/admin/auth': SingleSignOnIcon, | ||||
|     '/admin/auth/oidc': SingleSignOnIcon, | ||||
|     '/admin/auth/saml': SingleSignOnIcon, | ||||
|     '/admin/auth/scim': SingleSignOnIcon, | ||||
|     '/admin/auth/password': SingleSignOnIcon, | ||||
|  | ||||
| @ -22,6 +22,8 @@ import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon'; | ||||
| import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; | ||||
| import { useShowBadge } from 'component/layout/components/EnterprisePlanBadge/useShowBadge'; | ||||
| import { EnterprisePlanBadge } from 'component/layout/components/EnterprisePlanBadge/EnterprisePlanBadge'; | ||||
| import { useNewAdminMenu } from 'hooks/useNewAdminMenu'; | ||||
| import { AdminMenuNavigation } from '../AdminMenu/AdminNavigationItems'; | ||||
| 
 | ||||
| export const SecondaryNavigationList: FC<{ | ||||
|     routes: INavigationMenuItem[]; | ||||
| @ -235,10 +237,18 @@ export const AdminSettingsNavigation: FC<{ | ||||
|     activeItem, | ||||
|     mode, | ||||
| }) => { | ||||
|     const newAdminUIEnabled = useUiFlag('adminNavUI'); | ||||
|     const { showOnlyAdminMenu, newAdminUIEnabled } = useNewAdminMenu(); | ||||
|     if (showOnlyAdminMenu) { | ||||
|         return <AdminMenuNavigation onClick={() => onClick('/admin')} />; | ||||
|     } | ||||
| 
 | ||||
|     const setFullModeOnClick = (activeItem: string) => { | ||||
|         onSetFullMode(); | ||||
|         onClick(activeItem); | ||||
|     }; | ||||
| 
 | ||||
|     if (newAdminUIEnabled) { | ||||
|         return <AdminSettingsLink mode={mode} onClick={onClick} />; | ||||
|         return <AdminSettingsLink mode={mode} onClick={setFullModeOnClick} />; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|  | ||||
| @ -31,6 +31,7 @@ import { ReactComponent as LogoOnly } from 'assets/img/logoDark.svg'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { useFlag } from '@unleash/proxy-client-react'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { useNewAdminMenu } from 'hooks/useNewAdminMenu'; | ||||
| 
 | ||||
| export const MobileNavigationSidebar: FC<{ | ||||
|     onClick: () => void; | ||||
| @ -62,9 +63,12 @@ export const MobileNavigationSidebar: FC<{ | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const StretchContainer = styled(Box)<{ mode: string }>( | ||||
|     ({ theme, mode }) => ({ | ||||
|         backgroundColor: theme.palette.background.paper, | ||||
| export const StretchContainer = styled(Box)<{ mode: string; admin: boolean }>( | ||||
|     ({ theme, mode, admin }) => ({ | ||||
|         backgroundColor: admin | ||||
|             ? theme.palette.background.application | ||||
|             : theme.palette.background.paper, | ||||
|         borderRight: admin ? `2px solid ${theme.palette.divider}` : 'none', | ||||
|         padding: theme.spacing(2), | ||||
|         alignSelf: 'stretch', | ||||
|         display: 'flex', | ||||
| @ -98,12 +102,14 @@ const StyledUnleashLogoOnlyWhite = styled(LogoOnlyWhite)(({ theme }) => ({ | ||||
| })); | ||||
| 
 | ||||
| // This component is needed when the sticky item could overlap with nav items. You can replicate it on a short screen.
 | ||||
| const StickyContainer = styled(Box)(({ theme }) => ({ | ||||
| const StickyContainer = styled(Box)<{ admin: boolean }>(({ theme, admin }) => ({ | ||||
|     position: 'sticky', | ||||
|     paddingBottom: theme.spacing(1.5), | ||||
|     paddingTop: theme.spacing(1), | ||||
|     bottom: theme.spacing(0), | ||||
|     backgroundColor: theme.palette.background.paper, | ||||
|     backgroundColor: admin | ||||
|         ? theme.palette.background.application | ||||
|         : theme.palette.background.paper, | ||||
|     borderTop: `1px solid ${theme.palette.divider}`, | ||||
| })); | ||||
| 
 | ||||
| @ -112,6 +118,7 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({ | ||||
| }) => { | ||||
|     const { routes } = useRoutes(); | ||||
|     const celebrateUnleashFrontend = useFlag('celebrateUnleashFrontend'); | ||||
|     const { showOnlyAdminMenu } = useNewAdminMenu(); | ||||
| 
 | ||||
|     const [mode, setMode] = useNavigationMode(); | ||||
|     const [expanded, changeExpanded] = useExpanded<'configure' | 'admin'>(); | ||||
| @ -131,7 +138,7 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({ | ||||
|     }, [initialPathname]); | ||||
| 
 | ||||
|     return ( | ||||
|         <StretchContainer mode={mode}> | ||||
|         <StretchContainer mode={mode} admin={showOnlyAdminMenu}> | ||||
|             <ConditionallyRender | ||||
|                 condition={mode === 'full'} | ||||
|                 show={ | ||||
| @ -168,72 +175,94 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({ | ||||
|                 } | ||||
|             /> | ||||
| 
 | ||||
|             <PrimaryNavigationList | ||||
|                 mode={mode} | ||||
|                 onClick={setActiveItem} | ||||
|                 activeItem={activeItem} | ||||
|             <ConditionallyRender | ||||
|                 condition={!showOnlyAdminMenu} | ||||
|                 show={ | ||||
|                     <> | ||||
|                         <PrimaryNavigationList | ||||
|                             mode={mode} | ||||
|                             onClick={setActiveItem} | ||||
|                             activeItem={activeItem} | ||||
|                         /> | ||||
|                         <SecondaryNavigation | ||||
|                             expanded={expanded.includes('configure')} | ||||
|                             onExpandChange={(expand) => { | ||||
|                                 changeExpanded('configure', expand); | ||||
|                             }} | ||||
|                             mode={mode} | ||||
|                             title='Configure' | ||||
|                         > | ||||
|                             <SecondaryNavigationList | ||||
|                                 routes={routes.mainNavRoutes} | ||||
|                                 mode={mode} | ||||
|                                 onClick={setActiveItem} | ||||
|                                 activeItem={activeItem} | ||||
|                             /> | ||||
|                         </SecondaryNavigation> | ||||
| 
 | ||||
|                         <AdminSettingsNavigation | ||||
|                             onClick={setActiveItem} | ||||
|                             mode={mode} | ||||
|                             onSetFullMode={() => setMode('full')} | ||||
|                             activeItem={activeItem} | ||||
|                             onExpandChange={(expand) => { | ||||
|                                 changeExpanded('admin', expand); | ||||
|                             }} | ||||
|                             expanded={expanded.includes('admin')} | ||||
|                             routes={routes.adminRoutes} | ||||
|                         /> | ||||
| 
 | ||||
|                         {showRecentProject && ( | ||||
|                             <RecentProjectsNavigation | ||||
|                                 mode={mode} | ||||
|                                 projectId={lastViewedProject} | ||||
|                                 onClick={() => setActiveItem('/projects')} | ||||
|                             /> | ||||
|                         )} | ||||
| 
 | ||||
|                         {showRecentFlags && ( | ||||
|                             <RecentFlagsNavigation | ||||
|                                 mode={mode} | ||||
|                                 flags={lastViewedFlags} | ||||
|                                 onClick={() => setActiveItem('/projects')} | ||||
|                             /> | ||||
|                         )} | ||||
| 
 | ||||
|                         {/* this will push the show/hide to the bottom on short nav list */} | ||||
|                         <Box sx={{ flex: 1 }} /> | ||||
| 
 | ||||
|                         <StickyContainer admin={showOnlyAdminMenu}> | ||||
|                             {NewInUnleash ? ( | ||||
|                                 <NewInUnleash | ||||
|                                     mode={mode} | ||||
|                                     onMiniModeClick={() => setMode('full')} | ||||
|                                 /> | ||||
|                             ) : null} | ||||
|                             <ShowHide | ||||
|                                 mode={mode} | ||||
|                                 onChange={() => { | ||||
|                                     setMode(mode === 'full' ? 'mini' : 'full'); | ||||
|                                 }} | ||||
|                             /> | ||||
|                         </StickyContainer> | ||||
|                     </> | ||||
|                 } | ||||
|                 elseShow={ | ||||
|                     <> | ||||
|                         <AdminSettingsNavigation | ||||
|                             onClick={setActiveItem} | ||||
|                             mode={mode} | ||||
|                             onSetFullMode={() => setMode('full')} | ||||
|                             activeItem={activeItem} | ||||
|                             onExpandChange={(expand) => { | ||||
|                                 changeExpanded('admin', expand); | ||||
|                             }} | ||||
|                             expanded={expanded.includes('admin')} | ||||
|                             routes={routes.adminRoutes} | ||||
|                         /> | ||||
|                     </> | ||||
|                 } | ||||
|             /> | ||||
|             <SecondaryNavigation | ||||
|                 expanded={expanded.includes('configure')} | ||||
|                 onExpandChange={(expand) => { | ||||
|                     changeExpanded('configure', expand); | ||||
|                 }} | ||||
|                 mode={mode} | ||||
|                 title='Configure' | ||||
|             > | ||||
|                 <SecondaryNavigationList | ||||
|                     routes={routes.mainNavRoutes} | ||||
|                     mode={mode} | ||||
|                     onClick={setActiveItem} | ||||
|                     activeItem={activeItem} | ||||
|                 /> | ||||
|             </SecondaryNavigation> | ||||
| 
 | ||||
|             <AdminSettingsNavigation | ||||
|                 onClick={setActiveItem} | ||||
|                 mode={mode} | ||||
|                 onSetFullMode={() => setMode('full')} | ||||
|                 activeItem={activeItem} | ||||
|                 onExpandChange={(expand) => { | ||||
|                     changeExpanded('admin', expand); | ||||
|                 }} | ||||
|                 expanded={expanded.includes('admin')} | ||||
|                 routes={routes.adminRoutes} | ||||
|             /> | ||||
| 
 | ||||
|             {showRecentProject && ( | ||||
|                 <RecentProjectsNavigation | ||||
|                     mode={mode} | ||||
|                     projectId={lastViewedProject} | ||||
|                     onClick={() => setActiveItem('/projects')} | ||||
|                 /> | ||||
|             )} | ||||
| 
 | ||||
|             {showRecentFlags && ( | ||||
|                 <RecentFlagsNavigation | ||||
|                     mode={mode} | ||||
|                     flags={lastViewedFlags} | ||||
|                     onClick={() => setActiveItem('/projects')} | ||||
|                 /> | ||||
|             )} | ||||
| 
 | ||||
|             {/* this will push the show/hide to the bottom on short nav list */} | ||||
|             <Box sx={{ flex: 1 }} /> | ||||
| 
 | ||||
|             <StickyContainer> | ||||
|                 {NewInUnleash ? ( | ||||
|                     <NewInUnleash | ||||
|                         mode={mode} | ||||
|                         onMiniModeClick={() => setMode('full')} | ||||
|                     /> | ||||
|                 ) : null} | ||||
|                 <ShowHide | ||||
|                     mode={mode} | ||||
|                     onChange={() => { | ||||
|                         setMode(mode === 'full' ? 'mini' : 'full'); | ||||
|                     }} | ||||
|                 /> | ||||
|             </StickyContainer> | ||||
|         </StretchContainer> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -33,7 +33,7 @@ export const DrawerMenu: VFC<IDrawerMenuProps> = ({ | ||||
|     open = false, | ||||
|     toggleDrawer, | ||||
| }) => { | ||||
|     const showOnlyAdminMenu = useNewAdminMenu(); | ||||
|     const { showOnlyAdminMenu } = useNewAdminMenu(); | ||||
|     const onClick = () => { | ||||
|         toggleDrawer(); | ||||
|     }; | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| import { useState } from 'react'; | ||||
| import useMediaQuery from '@mui/material/useMediaQuery'; | ||||
| import { type Theme, useTheme } from '@mui/material/styles'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { useTheme } from '@mui/material/styles'; | ||||
| import { | ||||
|     AppBar, | ||||
|     Box, | ||||
| @ -14,12 +13,9 @@ import MenuIcon from '@mui/icons-material/Menu'; | ||||
| import UserProfile from 'component/user/UserProfile'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import MenuBookIcon from '@mui/icons-material/MenuBook'; | ||||
| import { ReactComponent as UnleashLogo } from 'assets/img/logoDarkWithText.svg'; | ||||
| import { ReactComponent as UnleashLogoWhite } from 'assets/img/logoWithWhiteText.svg'; | ||||
| 
 | ||||
| import { DrawerMenu } from './DrawerMenu/DrawerMenu'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { focusable } from 'themes/themeStyles'; | ||||
| import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'; | ||||
| import LightModeOutlined from '@mui/icons-material/LightModeOutlined'; | ||||
| import { useThemeMode } from 'hooks/useThemeMode'; | ||||
| @ -27,10 +23,6 @@ import { Notifications } from 'component/common/Notifications/Notifications'; | ||||
| import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { CommandBar } from 'component/commandBar/CommandBar'; | ||||
| import { ThemeMode } from 'component/common/ThemeMode/ThemeMode'; | ||||
| import { ReactComponent as CelebratoryUnleashLogo } from 'assets/img/unleashHoliday.svg'; | ||||
| import { ReactComponent as CelebratoryUnleashLogoWhite } from 'assets/img/unleashHolidayDark.svg'; | ||||
| import { useNewAdminMenu } from 'hooks/useNewAdminMenu'; | ||||
| 
 | ||||
| const HeaderComponent = styled(AppBar)(({ theme }) => ({ | ||||
|     backgroundColor: theme.palette.background.application, | ||||
| @ -71,16 +63,6 @@ const StyledNav = styled('nav')({ | ||||
|     flexGrow: 1, | ||||
| }); | ||||
| 
 | ||||
| const StyledCelebratoryLogo = styled(CelebratoryUnleashLogo)({ | ||||
|     height: '50px', | ||||
| }); | ||||
| 
 | ||||
| const StyledUnleashLogoWhite = styled(UnleashLogoWhite)({ height: '50px' }); | ||||
| 
 | ||||
| const StyledUnleashLogo = styled(UnleashLogo)({ height: '50px' }); | ||||
| 
 | ||||
| const StyledLink = styled(Link)(({ theme }) => focusable(theme)); | ||||
| 
 | ||||
| const StyledIconButton = styled(IconButton)<{ | ||||
|     component?: 'a' | 'button'; | ||||
|     href?: string; | ||||
| @ -100,16 +82,10 @@ const Header = () => { | ||||
|     const theme = useTheme(); | ||||
| 
 | ||||
|     const disableNotifications = useUiFlag('disableNotifications'); | ||||
|     const { uiConfig, isOss } = useUiConfig(); | ||||
|     const { isOss } = useUiConfig(); | ||||
|     const smallScreen = useMediaQuery(theme.breakpoints.down('lg')); | ||||
|     const [openDrawer, setOpenDrawer] = useState(false); | ||||
|     const toggleDrawer = () => setOpenDrawer((prev) => !prev); | ||||
|     const celebratoryUnleash = useUiFlag('celebrateUnleash'); | ||||
|     const headerLogo = (theme: Theme) => ({ | ||||
|         height: '50px', | ||||
|         marginLeft: theme.spacing(1.5), | ||||
|     }); | ||||
|     const adminMenu = useNewAdminMenu(); | ||||
| 
 | ||||
|     if (smallScreen) { | ||||
|         return ( | ||||
| @ -141,40 +117,6 @@ const Header = () => { | ||||
|         <HeaderComponent position='static'> | ||||
|             <ContainerComponent> | ||||
|                 <StyledNav> | ||||
|                     <ConditionallyRender | ||||
|                         condition={adminMenu} | ||||
|                         show={ | ||||
|                             <StyledLink | ||||
|                                 to='/personal' | ||||
|                                 sx={headerLogo} | ||||
|                                 aria-label='Home' | ||||
|                             > | ||||
|                                 <ThemeMode | ||||
|                                     darkmode={ | ||||
|                                         <ConditionallyRender | ||||
|                                             condition={celebratoryUnleash} | ||||
|                                             show={ | ||||
|                                                 <CelebratoryUnleashLogoWhite /> | ||||
|                                             } | ||||
|                                             elseShow={ | ||||
|                                                 <StyledUnleashLogoWhite aria-label='Unleash logo' /> | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     } | ||||
|                                     lightmode={ | ||||
|                                         <ConditionallyRender | ||||
|                                             condition={celebratoryUnleash} | ||||
|                                             show={<StyledCelebratoryLogo />} | ||||
|                                             elseShow={ | ||||
|                                                 <StyledUnleashLogo aria-label='Unleash logo' /> | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     } | ||||
|                                 /> | ||||
|                             </StyledLink> | ||||
|                         } | ||||
|                     /> | ||||
| 
 | ||||
|                     <StyledUserContainer> | ||||
|                         <CommandBar /> | ||||
|                         <Divider | ||||
|  | ||||
| @ -4,9 +4,11 @@ import { useLocation } from 'react-router'; | ||||
| export const useNewAdminMenu = () => { | ||||
|     const newAdminUIEnabled = useUiFlag('adminNavUI'); | ||||
|     const location = useLocation(); | ||||
|     return ( | ||||
|         newAdminUIEnabled && | ||||
|         (location.pathname.indexOf('/admin') === 0 || | ||||
|             location.pathname.indexOf('/history') === 0) | ||||
|     ); | ||||
|     return { | ||||
|         showOnlyAdminMenu: | ||||
|             newAdminUIEnabled && | ||||
|             (location.pathname.indexOf('/admin') === 0 || | ||||
|                 location.pathname.indexOf('/history') === 0), | ||||
|         newAdminUIEnabled, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user