mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: plan specific navigation (#7126)
This commit is contained in:
parent
a4c48a16a4
commit
b783e89c88
@ -1,5 +1,5 @@
|
|||||||
import { forwardRef, type ReactNode } from 'react';
|
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 Header from 'component/menu/Header/Header';
|
||||||
import Footer from 'component/menu/Footer/Footer';
|
import Footer from 'component/menu/Footer/Footer';
|
||||||
import Proclamation from 'component/common/Proclamation/Proclamation';
|
import Proclamation from 'component/common/Proclamation/Proclamation';
|
||||||
@ -105,6 +105,8 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
|||||||
|
|
||||||
const StyledMainLayoutContent = SpaciousMainLayoutContent;
|
const StyledMainLayoutContent = SpaciousMainLayoutContent;
|
||||||
const sidebarNavigationEnabled = useUiFlag('navigationSidebar');
|
const sidebarNavigationEnabled = useUiFlag('navigationSidebar');
|
||||||
|
const theme = useTheme();
|
||||||
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -120,9 +122,16 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
|||||||
)}
|
)}
|
||||||
show={<DraftBanner project={projectId || ''} />}
|
show={<DraftBanner project={projectId || ''} />}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ display: 'flex', mt: '2px' }}>
|
<Box
|
||||||
|
sx={(theme) => ({
|
||||||
|
display: 'flex',
|
||||||
|
mt: theme.spacing(0.25),
|
||||||
|
})}
|
||||||
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={sidebarNavigationEnabled}
|
condition={
|
||||||
|
sidebarNavigationEnabled && !isSmallScreen
|
||||||
|
}
|
||||||
show={<NavigationSidebar />}
|
show={<NavigationSidebar />}
|
||||||
/>
|
/>
|
||||||
<StyledMainLayoutContent
|
<StyledMainLayoutContent
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
ListItemIcon,
|
ListItemIcon,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
styled,
|
styled,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link } from 'react-router-dom';
|
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 InstancePrivacyIcon from '@mui/icons-material/ShieldOutlined';
|
||||||
import LoginHistoryIcon from '@mui/icons-material/HistoryOutlined';
|
import LoginHistoryIcon from '@mui/icons-material/HistoryOutlined';
|
||||||
import EventLogIcon from '@mui/icons-material/EventNoteOutlined';
|
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 { 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 }) => ({
|
export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
|
||||||
fill: theme.palette.neutral.main,
|
fill: theme.palette.neutral.main,
|
||||||
@ -49,9 +61,23 @@ export const StyledProjectIcon = styled(ProjectIcon)(({ theme }) => ({
|
|||||||
fontSize: theme.spacing(3),
|
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,
|
href,
|
||||||
text,
|
text,
|
||||||
|
badge,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@ -61,6 +87,7 @@ const StyledListItem: FC<{ href: string; text: string }> = ({
|
|||||||
{children}
|
{children}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={text} />
|
<ListItemText primary={text} />
|
||||||
|
{badge}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
@ -73,7 +100,75 @@ export const StyledBox = styled(Box)(({ theme }) => ({
|
|||||||
minHeight: '95vh',
|
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 = () => {
|
export const NavigationSidebar = () => {
|
||||||
|
const { routes, showBadge } = useRoutes();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBox>
|
<StyledBox>
|
||||||
<List>
|
<List>
|
||||||
@ -102,39 +197,14 @@ export const NavigationSidebar = () => {
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ p: 0 }}>
|
<AccordionDetails sx={{ p: 0 }}>
|
||||||
<List>
|
<List>
|
||||||
<StyledListItem
|
{routes.mainNavRoutes.map((route) => (
|
||||||
href='/integrations'
|
<StyledListItem
|
||||||
text='Integrations'
|
href={route.path}
|
||||||
>
|
text={route.title}
|
||||||
<IntegrationsIcon />
|
>
|
||||||
</StyledListItem>
|
<IconRenderer path={route.path} />
|
||||||
<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>
|
|
||||||
</List>
|
</List>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@ -150,66 +220,19 @@ export const NavigationSidebar = () => {
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ p: 0 }}>
|
<AccordionDetails sx={{ p: 0 }}>
|
||||||
<List>
|
<List>
|
||||||
<StyledListItem href='/admin/users' text='Users'>
|
{routes.adminRoutes.map((route) => (
|
||||||
<UsersIcon />
|
<StyledListItem
|
||||||
</StyledListItem>
|
href={route.path}
|
||||||
<StyledListItem
|
text={route.title}
|
||||||
href='/admin/service-accounts'
|
badge={
|
||||||
text='Service accounts'
|
showBadge(route?.menu?.mode) ? (
|
||||||
>
|
<EnterprisePlanBadge />
|
||||||
<ServiceAccountIcon />
|
) : null
|
||||||
</StyledListItem>
|
}
|
||||||
<StyledListItem href='/admin/groups' text='Groups'>
|
>
|
||||||
<GroupsIcon />
|
<IconRenderer path={route.path} />
|
||||||
</StyledListItem>
|
</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>
|
|
||||||
</List>
|
</List>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
Loading…
Reference in New Issue
Block a user