mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
Menu update - improvements and mobile version (#4423)
## About the changes 
This commit is contained in:
parent
555b27a653
commit
6ae1887aa5
@ -1 +1,6 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.739.811a1 1 0 0 0-1.479 0L9.947 2.253a1 1 0 0 1-1.043.28L7.045 1.94a1 1 0 0 0-1.28.739l-.417 1.906a1 1 0 0 1-.763.763l-1.906.417a1 1 0 0 0-.739 1.28l.592 1.859a1 1 0 0 1-.28 1.042L.812 11.261a1 1 0 0 0 0 1.478l1.442 1.314a1 1 0 0 1 .28 1.043l-.593 1.858a1 1 0 0 0 .74 1.28l1.905.417a1 1 0 0 1 .763.764l.417 1.905a1 1 0 0 0 1.28.74l1.859-.592a1 1 0 0 1 1.043.279l1.313 1.442a1 1 0 0 0 1.479 0l1.313-1.442a1 1 0 0 1 1.043-.28l1.859.592a1 1 0 0 0 1.28-.739l.417-1.905a1 1 0 0 1 .763-.764l1.906-.416a1 1 0 0 0 .739-1.28l-.592-1.86a1 1 0 0 1 .28-1.042l1.441-1.314a1 1 0 0 0 0-1.478l-1.442-1.314a1 1 0 0 1-.279-1.042l.592-1.86a1 1 0 0 0-.74-1.28l-1.905-.416a1 1 0 0 1-.763-.763l-.417-1.906a1 1 0 0 0-1.28-.74l-1.859.593a1 1 0 0 1-1.043-.28L12.74.811ZM7 7h3.434v6.565h3.13V7h3.436v10H7V7Z" fill="#E2E1F5"/><path d="M13.565 13.565H17V17h-3.435v-3.435Z" fill="#817AFE"/></svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M12.739.811a1 1 0 0 0-1.479 0L9.947 2.253a1 1 0 0 1-1.043.28L7.045 1.94a1 1 0 0 0-1.28.739l-.417 1.906a1 1 0 0 1-.763.763l-1.906.417a1 1 0 0 0-.739 1.28l.592 1.859a1 1 0 0 1-.28 1.042L.812 11.261a1 1 0 0 0 0 1.478l1.442 1.314a1 1 0 0 1 .28 1.043l-.593 1.858a1 1 0 0 0 .74 1.28l1.905.417a1 1 0 0 1 .763.764l.417 1.905a1 1 0 0 0 1.28.74l1.859-.592a1 1 0 0 1 1.043.279l1.313 1.442a1 1 0 0 0 1.479 0l1.313-1.442a1 1 0 0 1 1.043-.28l1.859.592a1 1 0 0 0 1.28-.739l.417-1.905a1 1 0 0 1 .763-.764l1.906-.416a1 1 0 0 0 .739-1.28l-.592-1.86a1 1 0 0 1 .28-1.042l1.441-1.314a1 1 0 0 0 0-1.478l-1.442-1.314a1 1 0 0 1-.279-1.042l.592-1.86a1 1 0 0 0-.74-1.28l-1.905-.416a1 1 0 0 1-.763-.763l-.417-1.906a1 1 0 0 0-1.28-.74l-1.859.593a1 1 0 0 1-1.043-.28L12.74.811ZM7 7h3.434v6.565h3.13V7h3.436v10H7V7Z"
|
||||
fill="#E2E1F5" />
|
||||
<path d="M13.565 13.565H17V17h-3.435v-3.435Z" fill="#817AFE" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 1.0 KiB |
@ -1,5 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.2608 0.811331C11.6574 0.375976 12.3426 0.375977 12.7392 0.811332L14.0529 2.25322C14.316 2.54202 14.7233 2.65114 15.0955 2.53259L16.9542 1.94073C17.5154 1.76203 18.1087 2.10458 18.2345 2.67993L18.6513 4.58549C18.7347 4.96716 19.0328 5.26527 19.4145 5.34875L21.3201 5.7655C21.8954 5.89133 22.238 6.48465 22.0593 7.04583L21.4674 8.90447C21.3489 9.27675 21.458 9.68397 21.7468 9.9471L23.1887 11.2608C23.624 11.6574 23.624 12.3426 23.1887 12.7392L21.7468 14.0529C21.458 14.316 21.3489 14.7233 21.4674 15.0955L22.0593 16.9542C22.238 17.5154 21.8954 18.1087 21.3201 18.2345L19.4145 18.6513C19.0328 18.7347 18.7347 19.0328 18.6513 19.4145L18.2345 21.3201C18.1087 21.8954 17.5154 22.238 16.9542 22.0593L15.0955 21.4674C14.7233 21.3489 14.316 21.458 14.0529 21.7468L12.7392 23.1887C12.3426 23.624 11.6574 23.624 11.2608 23.1887L9.9471 21.7468C9.68397 21.458 9.27675 21.3489 8.90447 21.4674L7.04583 22.0593C6.48465 22.238 5.89133 21.8954 5.7655 21.3201L5.34875 19.4145C5.26527 19.0328 4.96716 18.7347 4.58549 18.6513L2.67993 18.2345C2.10458 18.1087 1.76203 17.5154 1.94073 16.9542L2.53259 15.0955C2.65114 14.7233 2.54202 14.316 2.25322 14.0529L0.811331 12.7392C0.375976 12.3426 0.375977 11.6574 0.811332 11.2608L2.25322 9.9471C2.54202 9.68397 2.65114 9.27675 2.53259 8.90447L1.94073 7.04583C1.76203 6.48465 2.10458 5.89133 2.67993 5.7655L4.58549 5.34875C4.96716 5.26527 5.26527 4.96716 5.34875 4.58549L5.7655 2.67993C5.89133 2.10458 6.48465 1.76203 7.04583 1.94073L8.90447 2.53259C9.27675 2.65114 9.68397 2.54202 9.9471 2.25322L11.2608 0.811331Z" fill="#1A4049"/>
|
||||
<path d="M10.4351 7.3042H7.30469V16.6955H16.696V7.3042H13.5656V13.5651H10.4351V7.3042Z" fill="white"/>
|
||||
<path d="M13.5664 13.5649H16.6968V16.6954H13.5664V13.5649Z" fill="#817AFE"/>
|
||||
<path
|
||||
d="M11.2608 0.811331C11.6574 0.375976 12.3426 0.375977 12.7392 0.811332L14.0529 2.25322C14.316 2.54202 14.7233 2.65114 15.0955 2.53259L16.9542 1.94073C17.5154 1.76203 18.1087 2.10458 18.2345 2.67993L18.6513 4.58549C18.7347 4.96716 19.0328 5.26527 19.4145 5.34875L21.3201 5.7655C21.8954 5.89133 22.238 6.48465 22.0593 7.04583L21.4674 8.90447C21.3489 9.27675 21.458 9.68397 21.7468 9.9471L23.1887 11.2608C23.624 11.6574 23.624 12.3426 23.1887 12.7392L21.7468 14.0529C21.458 14.316 21.3489 14.7233 21.4674 15.0955L22.0593 16.9542C22.238 17.5154 21.8954 18.1087 21.3201 18.2345L19.4145 18.6513C19.0328 18.7347 18.7347 19.0328 18.6513 19.4145L18.2345 21.3201C18.1087 21.8954 17.5154 22.238 16.9542 22.0593L15.0955 21.4674C14.7233 21.3489 14.316 21.458 14.0529 21.7468L12.7392 23.1887C12.3426 23.624 11.6574 23.624 11.2608 23.1887L9.9471 21.7468C9.68397 21.458 9.27675 21.3489 8.90447 21.4674L7.04583 22.0593C6.48465 22.238 5.89133 21.8954 5.7655 21.3201L5.34875 19.4145C5.26527 19.0328 4.96716 18.7347 4.58549 18.6513L2.67993 18.2345C2.10458 18.1087 1.76203 17.5154 1.94073 16.9542L2.53259 15.0955C2.65114 14.7233 2.54202 14.316 2.25322 14.0529L0.811331 12.7392C0.375976 12.3426 0.375977 11.6574 0.811332 11.2608L2.25322 9.9471C2.54202 9.68397 2.65114 9.27675 2.53259 8.90447L1.94073 7.04583C1.76203 6.48465 2.10458 5.89133 2.67993 5.7655L4.58549 5.34875C4.96716 5.26527 5.26527 4.96716 5.34875 4.58549L5.7655 2.67993C5.89133 2.10458 6.48465 1.76203 7.04583 1.94073L8.90447 2.53259C9.27675 2.65114 9.68397 2.54202 9.9471 2.25322L11.2608 0.811331Z"
|
||||
fill="#1A4049" />
|
||||
<path d="M10.4351 7.3042H7.30469V16.6955H16.696V7.3042H13.5656V13.5651H10.4351V7.3042Z" fill="white" />
|
||||
<path d="M13.5664 13.5649H16.6968V16.6954H13.5664V13.5649Z" fill="#817AFE" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -20,8 +20,8 @@ import CreateUser from './users/CreateUser/CreateUser';
|
||||
import EditUser from './users/EditUser/EditUser';
|
||||
import { InviteLink } from './users/InviteLink/InviteLink';
|
||||
import UsersAdmin from './users/UsersAdmin';
|
||||
import { EnterpriseFeatureUpgradePage } from 'component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||
|
||||
export const Admin = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
@ -40,10 +40,7 @@ export const Admin = () => {
|
||||
isEnterprise() ? (
|
||||
<ServiceAccounts />
|
||||
) : (
|
||||
<EnterpriseFeatureUpgradePage
|
||||
title="Service accounts"
|
||||
link="https://docs.getunleash.io/reference/service-accounts"
|
||||
/>
|
||||
<PremiumFeature feature="service-accounts" page />
|
||||
)
|
||||
}
|
||||
/>
|
||||
@ -62,10 +59,7 @@ export const Admin = () => {
|
||||
isEnterprise() ? (
|
||||
<Roles />
|
||||
) : (
|
||||
<EnterpriseFeatureUpgradePage
|
||||
title="Project roles"
|
||||
link="https://docs.getunleash.io/reference/rbac#custom-project-roles"
|
||||
/>
|
||||
<PremiumFeature feature="project-roles" page />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Box, Paper, styled, Tab, Tabs } from '@mui/material';
|
||||
import { Paper, styled, Tab, Tabs } from '@mui/material';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
||||
import { CenteredNavLink } from './CenteredNavLink';
|
||||
@ -43,7 +43,7 @@ export const AdminTabsMenu: VFC = () => {
|
||||
link: '/admin/service-accounts',
|
||||
condition:
|
||||
isEnterprise() || (isPro() && showEnterpriseFeaturesInPro),
|
||||
showEnterpriseBadge: true,
|
||||
showEnterpriseBadge: isPro(),
|
||||
},
|
||||
{
|
||||
value: 'groups',
|
||||
@ -57,7 +57,7 @@ export const AdminTabsMenu: VFC = () => {
|
||||
link: '/admin/roles',
|
||||
condition:
|
||||
isEnterprise() || (isPro() && showEnterpriseFeaturesInPro),
|
||||
showEnterpriseBadge: true,
|
||||
showEnterpriseBadge: isPro(),
|
||||
},
|
||||
{
|
||||
value: 'api',
|
||||
|
@ -83,6 +83,14 @@ const BreadcrumbNav = () => {
|
||||
}
|
||||
});
|
||||
|
||||
if (index === 0 && path === 'admin') {
|
||||
return (
|
||||
<StyledParagraph key={path}>
|
||||
{path}
|
||||
</StyledParagraph>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLink key={path} to={link}>
|
||||
<StyledParagraph>
|
||||
|
@ -1,64 +0,0 @@
|
||||
import { VFC } from 'react';
|
||||
import { Box, Button, Typography, styled } from '@mui/material';
|
||||
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
|
||||
import { PageContent } from '../PageContent/PageContent';
|
||||
import { PageHeader } from '../PageHeader/PageHeader';
|
||||
|
||||
type EnterpriseFeatureUpgradePageProps = {
|
||||
title: string;
|
||||
link: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled(Box)(({ theme }) => ({
|
||||
background: theme.palette.background.elevation2,
|
||||
padding: theme.spacing(8, 2),
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const StyledHeader = styled(Typography)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(3),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledBadgeContainer = styled('div')(({ theme }) => ({
|
||||
paddingRight: theme.spacing(1),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const EnterpriseFeatureUpgradePage: VFC<
|
||||
EnterpriseFeatureUpgradePageProps
|
||||
> = ({ title, link }) => (
|
||||
<PageContent header={<PageHeader title={title} />}>
|
||||
<StyledContainer>
|
||||
<StyledHeader variant="h2">
|
||||
<StyledBadgeContainer>
|
||||
<EnterpriseBadge size={24} />
|
||||
</StyledBadgeContainer>
|
||||
Enterprise feature
|
||||
</StyledHeader>
|
||||
<Typography variant="body1">
|
||||
<a href={link}>{title}</a> is a feature available for the
|
||||
Enterprise plan.
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
You need to upgrade your plan if you want to use it.
|
||||
</Typography>
|
||||
<Button
|
||||
component="a"
|
||||
href="https://www.getunleash.io/plans/enterprise"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="contained"
|
||||
sx={theme => ({ marginTop: theme.spacing(5) })}
|
||||
>
|
||||
Upgrade now
|
||||
</Button>
|
||||
</StyledContainer>
|
||||
</PageContent>
|
||||
);
|
@ -4,6 +4,8 @@ import { Box, Button, Link, styled, Typography } from '@mui/material';
|
||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||
import { ThemeMode } from '../ThemeMode/ThemeMode';
|
||||
import { PageContent } from '../PageContent/PageContent';
|
||||
import { PageHeader } from '../PageHeader/PageHeader';
|
||||
|
||||
const PremiumFeatureWrapper = styled(Box, {
|
||||
shouldForwardProp: prop => prop !== 'tooltip',
|
||||
@ -70,6 +72,21 @@ const PremiumFeatures = {
|
||||
url: 'https://docs.getunleash.io/reference/segments',
|
||||
label: 'Segments',
|
||||
},
|
||||
'service-accounts': {
|
||||
plan: FeaturePlan.ENTERPRISE,
|
||||
url: 'https://docs.getunleash.io/reference/service-accounts',
|
||||
label: 'Service Accounts',
|
||||
},
|
||||
'project-roles': {
|
||||
plan: FeaturePlan.ENTERPRISE,
|
||||
url: 'https://docs.getunleash.io/reference/rbac#custom-project-roles',
|
||||
label: 'Project Roles',
|
||||
},
|
||||
'login-history': {
|
||||
plan: FeaturePlan.ENTERPRISE,
|
||||
url: 'https://docs.getunleash.io/reference/login-history',
|
||||
label: 'Login history',
|
||||
},
|
||||
};
|
||||
|
||||
type PremiumFeatureType = keyof typeof PremiumFeatures;
|
||||
@ -79,9 +96,14 @@ const UPGRADE_URL = 'https://www.getunleash.io/plans';
|
||||
export interface PremiumFeatureProps {
|
||||
feature: PremiumFeatureType;
|
||||
tooltip?: boolean;
|
||||
page?: boolean;
|
||||
}
|
||||
|
||||
export const PremiumFeature = ({ feature, tooltip }: PremiumFeatureProps) => {
|
||||
export const PremiumFeature = ({
|
||||
feature,
|
||||
tooltip,
|
||||
page,
|
||||
}: PremiumFeatureProps) => {
|
||||
const { url, plan, label } = PremiumFeatures[feature];
|
||||
|
||||
const tracker = usePlausibleTracker();
|
||||
@ -110,7 +132,7 @@ export const PremiumFeature = ({ feature, tooltip }: PremiumFeatureProps) => {
|
||||
|
||||
const upgradeUrl = `${UPGRADE_URL}?feature=${feature}`;
|
||||
|
||||
return (
|
||||
const content = (
|
||||
<PremiumFeatureWrapper tooltip={tooltip}>
|
||||
<StyledTitle>
|
||||
<ThemeMode
|
||||
@ -168,4 +190,14 @@ export const PremiumFeature = ({ feature, tooltip }: PremiumFeatureProps) => {
|
||||
/>
|
||||
</PremiumFeatureWrapper>
|
||||
);
|
||||
|
||||
if (page) {
|
||||
return (
|
||||
<PageContent header={<PageHeader title={label} />}>
|
||||
{content}
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
@ -45,12 +45,14 @@ interface IVerticalTabProps {
|
||||
label: string;
|
||||
selected?: boolean;
|
||||
onClick: () => void;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const VerticalTab = ({
|
||||
label,
|
||||
selected,
|
||||
onClick,
|
||||
icon,
|
||||
}: IVerticalTabProps) => (
|
||||
<StyledTab
|
||||
selected={Boolean(selected)}
|
||||
@ -60,5 +62,6 @@ export const VerticalTab = ({
|
||||
fullWidth
|
||||
>
|
||||
{label}
|
||||
{icon}
|
||||
</StyledTab>
|
||||
);
|
||||
|
@ -31,6 +31,7 @@ export interface ITab {
|
||||
label: string;
|
||||
path?: string;
|
||||
hidden?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface IVerticalTabsProps {
|
||||
@ -56,6 +57,7 @@ export const VerticalTabs = ({
|
||||
label={tab.label}
|
||||
selected={tab.id === value}
|
||||
onClick={() => onChange(tab)}
|
||||
icon={tab.icon}
|
||||
/>
|
||||
))}
|
||||
</StyledTabs>
|
||||
|
@ -2,18 +2,13 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
|
||||
import { LoginHistoryTable } from './LoginHistoryTable/LoginHistoryTable';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { EnterpriseFeatureUpgradePage } from 'component/common/EnterpriseFeatureUpgradePage/EnterpriseFeatureUpgradePage';
|
||||
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
|
||||
|
||||
export const LoginHistory = () => {
|
||||
const { isEnterprise } = useUiConfig();
|
||||
|
||||
if (!isEnterprise()) {
|
||||
return (
|
||||
<EnterpriseFeatureUpgradePage
|
||||
title="Login history"
|
||||
link="https://docs.getunleash.io/reference/login-history"
|
||||
/>
|
||||
);
|
||||
return <PremiumFeature feature="login-history" page />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -121,6 +121,7 @@ export const DrawerMenu: VFC<IDrawerMenuProps> = ({
|
||||
path={item.path}
|
||||
text={item.title}
|
||||
key={item.path}
|
||||
mode={item.menu?.mode}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
@ -38,6 +38,8 @@ import { INavigationMenuItem } from 'interfaces/route';
|
||||
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
||||
import { useThemeMode } from 'hooks/useThemeMode';
|
||||
import { Notifications } from 'component/common/Notifications/Notifications';
|
||||
import { filterAdminRoutes } from './filterAdminRoutes';
|
||||
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
|
||||
|
||||
const StyledHeader = styled(AppBar)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
@ -121,9 +123,13 @@ const Header: VFC = () => {
|
||||
const [configRef, setConfigRef] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const { uiConfig, isOss, isPro, isEnterprise } = useUiConfig();
|
||||
const { isBilling } = useInstanceStatus();
|
||||
const smallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [openDrawer, setOpenDrawer] = useState(false);
|
||||
const showApiAccessInConfigure = !uiConfig?.flags?.frontendNavigationUpdate;
|
||||
const showEnterpriseOptionsInPro = Boolean(
|
||||
uiConfig?.flags?.frontendNavigationUpdate
|
||||
);
|
||||
|
||||
const toggleDrawer = () => setOpenDrawer(prev => !prev);
|
||||
const onAdminClose = () => setAdminRef(null);
|
||||
@ -131,26 +137,6 @@ const Header: VFC = () => {
|
||||
|
||||
const routes = getRoutes();
|
||||
|
||||
const filterByMode = (route: INavigationMenuItem): boolean => {
|
||||
const { mode, showEnterpriseBadge } = route.menu;
|
||||
|
||||
if (!mode) return true;
|
||||
|
||||
if (isPro()) {
|
||||
return (
|
||||
mode.includes('pro') ||
|
||||
(mode.includes('enterprise') && showEnterpriseBadge) ||
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (isEnterprise()) {
|
||||
return mode.includes('enterprise');
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const filteredMainRoutes = {
|
||||
mainNavRoutes: getCondensedRoutes(routes.mainNavRoutes)
|
||||
.concat(
|
||||
@ -182,7 +168,17 @@ const Header: VFC = () => {
|
||||
.map(mapRouteLink),
|
||||
adminRoutes: adminMenuRoutes
|
||||
.filter(filterByConfig(uiConfig))
|
||||
.filter(filterByMode)
|
||||
.filter(route =>
|
||||
filterAdminRoutes(
|
||||
route?.menu,
|
||||
{
|
||||
enterprise: isEnterprise(),
|
||||
pro: isPro(),
|
||||
billing: isBilling,
|
||||
},
|
||||
showEnterpriseOptionsInPro
|
||||
)
|
||||
)
|
||||
.map(mapRouteLink),
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { ListItem, Link, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { INavigationMenuItem } from 'interfaces/route';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
interface INavigationLinkProps {
|
||||
path: string;
|
||||
text: string;
|
||||
handleClose: () => void;
|
||||
mode?: INavigationMenuItem['menu']['mode'];
|
||||
}
|
||||
|
||||
const StyledListItem = styled(ListItem)({
|
||||
@ -37,7 +42,25 @@ const StyledSpan = styled('span')(({ theme }) => ({
|
||||
borderRadius: '2px',
|
||||
}));
|
||||
|
||||
const NavigationLink = ({ path, text, handleClose }: INavigationLinkProps) => {
|
||||
const StyledBadgeContainer = styled('div')(({ theme }) => ({
|
||||
marginLeft: 'auto',
|
||||
paddingLeft: theme.spacing(2),
|
||||
display: 'flex',
|
||||
}));
|
||||
|
||||
const NavigationLink = ({
|
||||
path,
|
||||
text,
|
||||
handleClose,
|
||||
...props
|
||||
}: INavigationLinkProps) => {
|
||||
const { uiConfig, isPro } = useUiConfig();
|
||||
const showEnterpriseBadgeToPro = Boolean(
|
||||
uiConfig?.flags?.frontendNavigationUpdate &&
|
||||
isPro() &&
|
||||
props.mode?.includes('enterprise')
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledListItem
|
||||
onClick={() => {
|
||||
@ -52,6 +75,15 @@ const NavigationLink = ({ path, text, handleClose }: INavigationLinkProps) => {
|
||||
>
|
||||
<StyledSpan />
|
||||
{text}
|
||||
|
||||
<ConditionallyRender
|
||||
condition={showEnterpriseBadgeToPro}
|
||||
show={
|
||||
<StyledBadgeContainer>
|
||||
<EnterpriseBadge />
|
||||
</StyledBadgeContainer>
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
</StyledListItem>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { INavigationMenuItem } from 'interfaces/route';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { EnterpriseBadge } from '../../../common/EnterpriseBadge/EnterpriseBadge';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
interface INavigationMenuProps {
|
||||
options: INavigationMenuItem[];
|
||||
@ -49,9 +50,25 @@ export const NavigationMenu = ({
|
||||
anchorEl,
|
||||
style,
|
||||
}: INavigationMenuProps) => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { uiConfig, isPro } = useUiConfig();
|
||||
const showUpdatedMenu = uiConfig?.flags?.frontendNavigationUpdate;
|
||||
|
||||
const showBadge = useCallback(
|
||||
(mode?: INavigationMenuItem['menu']['mode']) => {
|
||||
if (
|
||||
isPro() &&
|
||||
!mode?.includes('pro') &&
|
||||
mode?.includes('enterprise') &&
|
||||
showUpdatedMenu
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
[isPro, showUpdatedMenu]
|
||||
);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
id={id}
|
||||
@ -61,38 +78,36 @@ export const NavigationMenu = ({
|
||||
style={style}
|
||||
>
|
||||
{options
|
||||
.map((option, i) => [
|
||||
<ConditionallyRender
|
||||
key={`${option.path}-divider`}
|
||||
condition={Boolean(
|
||||
showUpdatedMenu &&
|
||||
options[i - 1]?.group &&
|
||||
options[i - 1]?.group !== option.group
|
||||
)}
|
||||
show={<Divider variant="middle" />}
|
||||
elseShow={null}
|
||||
/>,
|
||||
<MenuItem
|
||||
key={option.path}
|
||||
component={StyledLink}
|
||||
to={option.path}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<StyledSpan />
|
||||
{option.title}
|
||||
<ConditionallyRender
|
||||
condition={Boolean(
|
||||
option.menu.showEnterpriseBadge &&
|
||||
showUpdatedMenu
|
||||
)}
|
||||
show={
|
||||
<StyledBadgeContainer>
|
||||
<EnterpriseBadge />
|
||||
</StyledBadgeContainer>
|
||||
}
|
||||
/>
|
||||
</MenuItem>,
|
||||
])
|
||||
.map((option, i) => {
|
||||
const previousGroup = options[i - 1]?.group;
|
||||
const addDivider =
|
||||
showUpdatedMenu &&
|
||||
previousGroup &&
|
||||
previousGroup !== option.group;
|
||||
|
||||
return [
|
||||
addDivider ? (
|
||||
<Divider variant="middle" key={option.group} />
|
||||
) : null,
|
||||
<MenuItem
|
||||
key={option.path}
|
||||
component={StyledLink}
|
||||
to={option.path}
|
||||
onClick={handleClose}
|
||||
>
|
||||
<StyledSpan />
|
||||
{option.title}
|
||||
<ConditionallyRender
|
||||
condition={showBadge(option?.menu?.mode)}
|
||||
show={
|
||||
<StyledBadgeContainer>
|
||||
<EnterpriseBadge />
|
||||
</StyledBadgeContainer>
|
||||
}
|
||||
/>
|
||||
</MenuItem>,
|
||||
];
|
||||
})
|
||||
.flat()
|
||||
.filter(Boolean)}
|
||||
</Menu>
|
||||
|
103
frontend/src/component/menu/Header/filterAdminRoutes.test.ts
Normal file
103
frontend/src/component/menu/Header/filterAdminRoutes.test.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { filterAdminRoutes } from './filterAdminRoutes';
|
||||
|
||||
describe('filterAdminRoutes - open souce routes', () => {
|
||||
test('open source - should show menu item if mode paid plan mode is not defined', () => {
|
||||
expect(
|
||||
filterAdminRoutes(
|
||||
{},
|
||||
{
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
billing: false,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('open source - should not show menu item from paid plans', () => {
|
||||
const state = {
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
billing: false,
|
||||
};
|
||||
|
||||
expect(filterAdminRoutes({ mode: ['pro'] }, state)).toBe(false);
|
||||
expect(filterAdminRoutes({ mode: ['enterprise'] }, state)).toBe(false);
|
||||
expect(filterAdminRoutes({ mode: ['pro', 'enterprise'] }, state)).toBe(
|
||||
false
|
||||
);
|
||||
expect(filterAdminRoutes({ billing: true }, state)).toBe(false);
|
||||
});
|
||||
|
||||
test('pro - should show menu item for pro customers', () => {
|
||||
const state = {
|
||||
pro: true,
|
||||
enterprise: false,
|
||||
billing: false,
|
||||
};
|
||||
|
||||
expect(filterAdminRoutes({ mode: ['pro'] }, state)).toBe(true);
|
||||
expect(filterAdminRoutes({ mode: ['pro', 'enterprise'] }, state)).toBe(
|
||||
true
|
||||
);
|
||||
// This is to show enterprise badge in pro mode
|
||||
expect(filterAdminRoutes({ mode: ['enterprise'] }, state)).toBe(true);
|
||||
});
|
||||
|
||||
test('enterprise - should show menu item if mode enterprise is defined or mode is undefined', () => {
|
||||
const state = {
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
billing: false,
|
||||
};
|
||||
|
||||
expect(filterAdminRoutes({ mode: ['enterprise'] }, state)).toBe(true);
|
||||
expect(filterAdminRoutes({ mode: ['pro', 'enterprise'] }, state)).toBe(
|
||||
true
|
||||
);
|
||||
expect(filterAdminRoutes({ mode: ['pro'] }, state)).toBe(false);
|
||||
});
|
||||
|
||||
test('billing - should show menu item if billing is defined', () => {
|
||||
expect(
|
||||
filterAdminRoutes(
|
||||
{ mode: ['pro'], billing: true },
|
||||
{
|
||||
pro: true,
|
||||
enterprise: false,
|
||||
billing: true,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
filterAdminRoutes(
|
||||
{ mode: ['enterprise'], billing: true },
|
||||
{
|
||||
pro: false,
|
||||
enterprise: true,
|
||||
billing: true,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
filterAdminRoutes(
|
||||
{ mode: ['pro', 'enterprise'], billing: true },
|
||||
{
|
||||
pro: true,
|
||||
enterprise: false,
|
||||
billing: true,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
filterAdminRoutes(
|
||||
{ mode: ['pro'], billing: true },
|
||||
{
|
||||
pro: false,
|
||||
enterprise: false,
|
||||
billing: true,
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
34
frontend/src/component/menu/Header/filterAdminRoutes.ts
Normal file
34
frontend/src/component/menu/Header/filterAdminRoutes.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { INavigationMenuItem } from 'interfaces/route';
|
||||
|
||||
export const filterAdminRoutes = (
|
||||
menu: INavigationMenuItem['menu'],
|
||||
{
|
||||
pro,
|
||||
enterprise,
|
||||
billing,
|
||||
}: { pro?: boolean; enterprise?: boolean; billing?: boolean },
|
||||
showEnterpriseOptionsInPro = true
|
||||
): boolean => {
|
||||
const mode = menu?.mode;
|
||||
if (menu?.billing && !billing) return false;
|
||||
|
||||
if (!mode || mode.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pro) {
|
||||
if (mode.includes('pro')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (showEnterpriseOptionsInPro && mode.includes('enterprise')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (enterprise && mode.includes('enterprise')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
@ -465,7 +465,6 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
|
||||
menu: {
|
||||
adminSettings: true,
|
||||
mode: ['enterprise'],
|
||||
showEnterpriseBadge: true,
|
||||
},
|
||||
group: 'users',
|
||||
},
|
||||
@ -475,7 +474,6 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
|
||||
menu: {
|
||||
adminSettings: true,
|
||||
mode: ['enterprise'],
|
||||
showEnterpriseBadge: true,
|
||||
},
|
||||
flag: UG,
|
||||
group: 'users',
|
||||
@ -486,7 +484,6 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
|
||||
menu: {
|
||||
adminSettings: true,
|
||||
mode: ['enterprise'],
|
||||
showEnterpriseBadge: true,
|
||||
},
|
||||
group: 'users',
|
||||
},
|
||||
@ -538,7 +535,7 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
|
||||
{
|
||||
path: '/admin/admin-invoices',
|
||||
title: 'Billing & invoices',
|
||||
menu: { adminSettings: true, mode: ['pro'] },
|
||||
menu: { adminSettings: true, mode: ['pro'], billing: true },
|
||||
group: 'instance',
|
||||
},
|
||||
{
|
||||
@ -547,7 +544,6 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
|
||||
menu: {
|
||||
adminSettings: true,
|
||||
mode: ['enterprise'],
|
||||
showEnterpriseBadge: true,
|
||||
},
|
||||
group: 'log',
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
StyledTabContainer,
|
||||
StyledTopRow,
|
||||
} from './Project.styles';
|
||||
import { Paper, Tabs, Typography } from '@mui/material';
|
||||
import { Box, Paper, Tabs, Typography } from '@mui/material';
|
||||
import { Delete, Edit, FileUpload } from '@mui/icons-material';
|
||||
import useToast from 'hooks/useToast';
|
||||
import useQueryParams from 'hooks/useQueryParams';
|
||||
@ -40,6 +40,7 @@ import { ProjectSettings } from './ProjectSettings/ProjectSettings';
|
||||
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
|
||||
import { ImportModal } from './Import/ImportModal';
|
||||
import { IMPORT_BUTTON } from 'utils/testIds';
|
||||
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
|
||||
|
||||
const NAVIGATE_TO_EDIT_PROJECT = 'NAVIGATE_TO_EDIT_PROJECT';
|
||||
|
||||
@ -52,13 +53,15 @@ export const Project = () => {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const { isOss, uiConfig } = useUiConfig();
|
||||
const { isOss, uiConfig, isPro } = useUiConfig();
|
||||
const basePath = `/projects/${projectId}`;
|
||||
const projectName = project?.name || projectId;
|
||||
const { favorite, unfavorite } = useFavoriteProjectsApi();
|
||||
|
||||
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||
|
||||
const updatedNavigation = uiConfig?.flags?.frontendNavigationUpdate;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Overview',
|
||||
@ -79,18 +82,34 @@ export const Project = () => {
|
||||
title: 'Change requests',
|
||||
path: `${basePath}/change-requests`,
|
||||
name: 'change-request',
|
||||
isEnterprise: true,
|
||||
},
|
||||
{
|
||||
title: 'Project settings',
|
||||
path: `${basePath}/settings`,
|
||||
name: 'settings',
|
||||
},
|
||||
{
|
||||
title: 'Event log',
|
||||
path: `${basePath}/logs`,
|
||||
name: 'logs',
|
||||
},
|
||||
];
|
||||
...(updatedNavigation
|
||||
? [
|
||||
{
|
||||
title: 'Event log',
|
||||
path: `${basePath}/logs`,
|
||||
name: 'logs',
|
||||
},
|
||||
{
|
||||
title: 'Project settings',
|
||||
path: `${basePath}/settings`,
|
||||
name: 'settings',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Project settings',
|
||||
path: `${basePath}/settings`,
|
||||
name: 'settings',
|
||||
},
|
||||
{
|
||||
title: 'Event log',
|
||||
path: `${basePath}/logs`,
|
||||
name: 'logs',
|
||||
},
|
||||
]),
|
||||
].filter(tab => !updatedNavigation || !(isOss() && tab.isEnterprise));
|
||||
|
||||
const activeTab = [...tabs]
|
||||
.reverse()
|
||||
@ -130,6 +149,17 @@ export const Project = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const enterpriseIcon = (
|
||||
<Box
|
||||
sx={theme => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
display: 'flex',
|
||||
})}
|
||||
>
|
||||
<EnterpriseBadge />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<StyledHeader>
|
||||
@ -227,6 +257,16 @@ export const Project = () => {
|
||||
value={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
data-testid={`TAB_${tab.title}`}
|
||||
iconPosition={
|
||||
tab.isEnterprise ? 'end' : undefined
|
||||
}
|
||||
icon={
|
||||
tab.isEnterprise &&
|
||||
isPro() &&
|
||||
updatedNavigation
|
||||
? enterpriseIcon
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
|
@ -14,12 +14,16 @@ import { ProjectSegments } from './ProjectSegments/ProjectSegments';
|
||||
import { ProjectDefaultStrategySettings } from './ProjectDefaultStrategySettings/ProjectDefaultStrategySettings';
|
||||
import { Settings } from './Settings/Settings';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const ProjectSettings = () => {
|
||||
const location = useLocation();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const { uiConfig, isPro, isEnterprise } = useUiConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const updatedNavigation = uiConfig.flags?.frontendNavigationUpdate;
|
||||
|
||||
const tabs: ITab[] = [
|
||||
...(uiConfig.flags.newProjectLayout
|
||||
? [
|
||||
@ -33,18 +37,28 @@ export const ProjectSettings = () => {
|
||||
id: 'environments',
|
||||
label: 'Environments',
|
||||
},
|
||||
{
|
||||
id: 'access',
|
||||
label: 'Access',
|
||||
},
|
||||
{
|
||||
id: 'segments',
|
||||
label: 'Segments',
|
||||
},
|
||||
{
|
||||
id: 'change-requests',
|
||||
label: 'Change request configuration',
|
||||
},
|
||||
...(!updatedNavigation || isPro() || isEnterprise()
|
||||
? [
|
||||
{
|
||||
id: 'access',
|
||||
label: 'Access',
|
||||
},
|
||||
{
|
||||
id: 'segments',
|
||||
label: 'Segments',
|
||||
},
|
||||
{
|
||||
id: 'change-requests',
|
||||
label: 'Change request configuration',
|
||||
icon:
|
||||
isPro() && updatedNavigation ? (
|
||||
<Box sx={{ marginLeft: 'auto' }}>
|
||||
<EnterpriseBadge />
|
||||
</Box>
|
||||
) : undefined,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: 'api-access',
|
||||
label: 'API access',
|
||||
|
@ -30,5 +30,5 @@ interface IRouteMenu {
|
||||
advanced?: boolean;
|
||||
adminSettings?: boolean;
|
||||
mode?: Array<'pro' | 'enterprise'>;
|
||||
showEnterpriseBadge?: boolean;
|
||||
billing?: boolean;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user