1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-10-14 20:06:41 +02:00

feat: plan specific navigation (#7126)

This commit is contained in:
Mateusz Kwasniewski 2024-05-23 12:44:00 +02:00 committed by GitHub
parent a4c48a16a4
commit b783e89c88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 130 additions and 98 deletions

View File

@ -1,5 +1,5 @@
import { forwardRef, type ReactNode } from 'react';
import { Box, Grid, styled } from '@mui/material';
import { Box, Grid, styled, useMediaQuery, useTheme } from '@mui/material';
import Header from 'component/menu/Header/Header';
import Footer from 'component/menu/Footer/Footer';
import Proclamation from 'component/common/Proclamation/Proclamation';
@ -105,6 +105,8 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
const StyledMainLayoutContent = SpaciousMainLayoutContent;
const sidebarNavigationEnabled = useUiFlag('navigationSidebar');
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
return (
<>
@ -120,9 +122,16 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
)}
show={<DraftBanner project={projectId || ''} />}
/>
<Box sx={{ display: 'flex', mt: '2px' }}>
<Box
sx={(theme) => ({
display: 'flex',
mt: theme.spacing(0.25),
})}
>
<ConditionallyRender
condition={sidebarNavigationEnabled}
condition={
sidebarNavigationEnabled && !isSmallScreen
}
show={<NavigationSidebar />}
/>
<StyledMainLayoutContent

View File

@ -6,6 +6,7 @@ import {
ListItemIcon,
ListItemText,
styled,
Tooltip,
Typography,
} from '@mui/material';
import { Link } from 'react-router-dom';
@ -37,8 +38,19 @@ import LicenseIcon from '@mui/icons-material/ReceiptLongOutlined';
import InstancePrivacyIcon from '@mui/icons-material/ShieldOutlined';
import LoginHistoryIcon from '@mui/icons-material/HistoryOutlined';
import EventLogIcon from '@mui/icons-material/EventNoteOutlined';
import FlagTypesIcon from '@mui/icons-material/OutlinedFlag';
import EmptyIcon from '@mui/icons-material/CheckBoxOutlineBlankOutlined';
import CorsIcon from '@mui/icons-material/StorageOutlined';
import BillingIcon from '@mui/icons-material/CreditCardOutlined';
import { ReactComponent as ProjectIcon } from 'assets/icons/projectIconSmall.svg';
import type { FC } from 'react';
import { type FC, type ReactNode, useCallback } from 'react';
import { getCondensedRoutes, getRoutes } from '../../../menu/routes';
import { useAdminRoutes } from '../../../admin/useAdminRoutes';
import { filterByConfig, mapRouteLink } from 'component/common/util';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import type SvgIcon from '@mui/material/SvgIcon/SvgIcon';
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
import type { INavigationMenuItem } from 'interfaces/route';
export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
fill: theme.palette.neutral.main,
@ -49,9 +61,23 @@ export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
fontSize: theme.spacing(3),
}));
const StyledListItem: FC<{ href: string; text: string }> = ({
const StyledBadgeContainer = styled('div')(({ theme }) => ({
paddingLeft: theme.spacing(2),
display: 'flex',
}));
const EnterprisePlanBadge = () => (
<Tooltip title='This is an Enterprise feature'>
<StyledBadgeContainer>
<EnterpriseBadge />
</StyledBadgeContainer>
</Tooltip>
);
const StyledListItem: FC<{ href: string; text: string; badge?: ReactNode }> = ({
href,
text,
badge,
children,
}) => {
return (
@ -61,6 +87,7 @@ const StyledListItem: FC<{ href: string; text: string }> = ({
{children}
</ListItemIcon>
<ListItemText primary={text} />
{badge}
</ListItemButton>
</ListItem>
);
@ -73,7 +100,75 @@ export const StyledBox = styled(Box)(({ theme }) => ({
minHeight: '95vh',
}));
const icons: Record<string, typeof SvgIcon> = {
'/applications': ApplicationsIcon,
'/context': ContextFieldsIcon,
'/feature-toggle-type': FlagTypesIcon,
'/integrations': IntegrationsIcon,
'/segments': SegmentsIcon,
'/strategies': CustomStrategiesIcon,
'/tag-types': TagTypesIcon,
'/environments': EnvironmentsIcon,
'/admin/users': UsersIcon,
'/admin/service-accounts': ServiceAccountIcon,
'/admin/groups': GroupsIcon,
'/admin/roles': RoleIcon,
'/admin/api': ApiAccessIcon,
'/admin/auth': SingleSignOnIcon,
'/admin/network': NetworkIcon,
'/admin/maintenance': MaintenanceIcon,
'/admin/banners': BannersIcon,
'/admin/instance': InstanceStatsIcon,
'/admin/license': LicenseIcon,
'/admin/instance-privacy': InstancePrivacyIcon,
'/admin/logins': LoginHistoryIcon,
'/admin/cors': CorsIcon,
'/admin/billing': BillingIcon,
'/history': EventLogIcon,
};
const findIconByPath = (path: string) => {
return icons[path] || EmptyIcon;
};
const IconRenderer: FC<{ path: string }> = ({ path }) => {
const IconComponent = findIconByPath(path); // Fallback to 'default' if the type is not found
return <IconComponent />;
};
const useRoutes = () => {
const { uiConfig, isPro } = useUiConfig();
const routes = getRoutes();
const adminRoutes = useAdminRoutes();
const filteredMainRoutes = {
mainNavRoutes: getCondensedRoutes(routes.mainNavRoutes)
.filter(filterByConfig(uiConfig))
.map(mapRouteLink),
mobileRoutes: getCondensedRoutes(routes.mobileRoutes)
.filter(filterByConfig(uiConfig))
.map(mapRouteLink),
adminRoutes,
};
const showBadge = useCallback(
(mode?: INavigationMenuItem['menu']['mode']) => {
return !!(
isPro() &&
!mode?.includes('pro') &&
mode?.includes('enterprise')
);
},
[isPro],
);
return { routes: filteredMainRoutes, showBadge };
};
export const NavigationSidebar = () => {
const { routes, showBadge } = useRoutes();
return (
<StyledBox>
<List>
@ -102,39 +197,14 @@ export const NavigationSidebar = () => {
</AccordionSummary>
<AccordionDetails sx={{ p: 0 }}>
<List>
<StyledListItem
href='/integrations'
text='Integrations'
>
<IntegrationsIcon />
</StyledListItem>
<StyledListItem
href='/environments'
text='Environments'
>
<EnvironmentsIcon />
</StyledListItem>
<StyledListItem href='/context' text='Context fields'>
<ContextFieldsIcon />
</StyledListItem>
<StyledListItem href='/segments' text='Segments'>
<SegmentsIcon />
</StyledListItem>
<StyledListItem href='/tag-types' text='Tag types'>
<TagTypesIcon />
</StyledListItem>
<StyledListItem
href='/applications'
text='Applications'
>
<ApplicationsIcon />
</StyledListItem>
<StyledListItem
href='/strategies'
text='Custom strategies'
>
<CustomStrategiesIcon />
</StyledListItem>
{routes.mainNavRoutes.map((route) => (
<StyledListItem
href={route.path}
text={route.title}
>
<IconRenderer path={route.path} />
</StyledListItem>
))}
</List>
</AccordionDetails>
</Accordion>
@ -150,66 +220,19 @@ export const NavigationSidebar = () => {
</AccordionSummary>
<AccordionDetails sx={{ p: 0 }}>
<List>
<StyledListItem href='/admin/users' text='Users'>
<UsersIcon />
</StyledListItem>
<StyledListItem
href='/admin/service-accounts'
text='Service accounts'
>
<ServiceAccountIcon />
</StyledListItem>
<StyledListItem href='/admin/groups' text='Groups'>
<GroupsIcon />
</StyledListItem>
<StyledListItem href='/admin/roles' text='Roles'>
<RoleIcon />
</StyledListItem>
<StyledListItem href='/admin/api' text='API Access'>
<ApiAccessIcon />
</StyledListItem>
<StyledListItem
href='/admin/auth'
text='Single sign-on'
>
<SingleSignOnIcon />
</StyledListItem>
<StyledListItem href='/admin/network' text='Network'>
<NetworkIcon />
</StyledListItem>
<StyledListItem
href='/admin/maintenance'
text='Maintenance'
>
<MaintenanceIcon />
</StyledListItem>
<StyledListItem href='/admin/banners' text='Banners'>
<BannersIcon />
</StyledListItem>
<StyledListItem
href='/admin/instance'
text='Instance stats'
>
<InstanceStatsIcon />
</StyledListItem>
<StyledListItem href='/admin/license' text='License'>
<LicenseIcon />
</StyledListItem>
<StyledListItem
href='/admin/instance-privacy'
text='Instance privacy'
>
<InstancePrivacyIcon />
</StyledListItem>
<StyledListItem
href='/admin/logins'
text='Login history'
>
<LoginHistoryIcon />
</StyledListItem>
<StyledListItem href='/history' text='Event log'>
<EventLogIcon />
</StyledListItem>
{routes.adminRoutes.map((route) => (
<StyledListItem
href={route.path}
text={route.title}
badge={
showBadge(route?.menu?.mode) ? (
<EnterprisePlanBadge />
) : null
}
>
<IconRenderer path={route.path} />
</StyledListItem>
))}
</List>
</AccordionDetails>
</Accordion>