1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-08 01:15:49 +02:00

Update PRO plan menu (#4409)

<!-- Thanks for creating a PR! To make it easier for reviewers and
everyone else to understand what your changes relate to, please add some
relevant content to the headings below. Feel free to ignore or delete
sections that you don't think are relevant. Thank you! ❤️ -->

## About the changes
Update menu for Pro customers - show enterprise options with plan
upgrade suggestion page.


![image](https://github.com/Unleash/unleash/assets/2625371/0b670b48-a2fc-4973-89ce-5d0b0c36b81a)
This commit is contained in:
Tymoteusz Czech 2023-08-04 11:57:36 +02:00 committed by GitHub
parent ad1f5ffa1e
commit 87e75d10b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 376 additions and 204 deletions

View File

@ -12,7 +12,7 @@ import { GroupsAdmin } from './groups/GroupsAdmin';
import { InstanceAdmin } from './instance-admin/InstanceAdmin';
import { InstancePrivacy } from './instance-privacy/InstancePrivacy';
import { MaintenanceAdmin } from './maintenance';
import AdminMenu from './menu/AdminMenu';
import { AdminTabsMenu } from './menu/AdminTabsMenu';
import { Network } from './network/Network';
import { Roles } from './roles/Roles';
import { ServiceAccounts } from './serviceAccounts/ServiceAccounts';
@ -20,34 +20,67 @@ 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';
export const Admin = () => (
<>
<AdminMenu />
<Routes>
<Route path="users" element={<UsersAdmin />} />
<Route path="api" element={<ApiTokenPage />} />
<Route path="api/create-token" element={<CreateApiToken />} />
<Route path="users/:id/edit" element={<EditUser />} />
<Route path="service-accounts" element={<ServiceAccounts />} />
<Route path="create-user" element={<CreateUser />} />
<Route path="invite-link" element={<InviteLink />} />
<Route path="groups" element={<GroupsAdmin />} />
<Route path="groups/create-group" element={<CreateGroup />} />
<Route
path="groups/:groupId/edit"
element={<EditGroupContainer />}
/>
<Route path="groups/:groupId" element={<Group />} />
<Route path="roles/*" element={<Roles />} />
<Route path="instance" element={<InstanceAdmin />} />
<Route path="network/*" element={<Network />} />
<Route path="maintenance" element={<MaintenanceAdmin />} />
<Route path="cors" element={<CorsAdmin />} />
<Route path="auth" element={<AuthSettings />} />
<Route path="admin-invoices" element={<FlaggedBillingRedirect />} />
<Route path="billing" element={<Billing />} />
<Route path="instance-privacy" element={<InstancePrivacy />} />
</Routes>
</>
);
export const Admin = () => {
const { isEnterprise } = useUiConfig();
return (
<>
<AdminTabsMenu />
<Routes>
<Route path="users" element={<UsersAdmin />} />
<Route path="api" element={<ApiTokenPage />} />
<Route path="api/create-token" element={<CreateApiToken />} />
<Route path="users/:id/edit" element={<EditUser />} />
<Route
path="service-accounts"
element={
isEnterprise() ? (
<ServiceAccounts />
) : (
<EnterpriseFeatureUpgradePage
title="Service accounts"
link="https://docs.getunleash.io/reference/service-accounts"
/>
)
}
/>
<Route path="create-user" element={<CreateUser />} />
<Route path="invite-link" element={<InviteLink />} />
<Route path="groups" element={<GroupsAdmin />} />
<Route path="groups/create-group" element={<CreateGroup />} />
<Route
path="groups/:groupId/edit"
element={<EditGroupContainer />}
/>
<Route path="groups/:groupId" element={<Group />} />
<Route
path="roles/*"
element={
isEnterprise() ? (
<Roles />
) : (
<EnterpriseFeatureUpgradePage
title="Project roles"
link="https://docs.getunleash.io/reference/rbac#custom-project-roles"
/>
)
}
/>
<Route path="instance" element={<InstanceAdmin />} />
<Route path="network/*" element={<Network />} />
<Route path="maintenance" element={<MaintenanceAdmin />} />
<Route path="cors" element={<CorsAdmin />} />
<Route path="auth" element={<AuthSettings />} />
<Route
path="admin-invoices"
element={<FlaggedBillingRedirect />}
/>
<Route path="billing" element={<Billing />} />
<Route path="instance-privacy" element={<InstancePrivacy />} />
</Routes>
</>
);
};

View File

@ -1,146 +0,0 @@
import { useLocation } from 'react-router-dom';
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';
const StyledPaper = styled(Paper)(({ theme }) => ({
marginBottom: '1rem',
borderRadius: '12.5px',
boxShadow: 'none',
padding: '0 2rem',
}));
function AdminMenu() {
const { uiConfig, isEnterprise } = useUiConfig();
const { pathname } = useLocation();
const { isBilling } = useInstanceStatus();
const { flags, networkViewEnabled } = uiConfig;
const activeTab = pathname.split('/')[2];
return (
<StyledPaper>
<Tabs
value={activeTab}
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
>
<Tab
value="users"
label={
<CenteredNavLink to="/admin/users">
<span>Users</span>
</CenteredNavLink>
}
/>
{isEnterprise() && (
<Tab
value="service-accounts"
label={
<CenteredNavLink to="/admin/service-accounts">
<span>Service accounts</span>
</CenteredNavLink>
}
/>
)}
{flags.UG && (
<Tab
value="groups"
label={
<CenteredNavLink to="/admin/groups">
<span>Groups</span>
</CenteredNavLink>
}
/>
)}
{isEnterprise() && (
<Tab
value="roles"
label={
<CenteredNavLink to="/admin/roles">
<span>Roles</span>
</CenteredNavLink>
}
/>
)}
<Tab
value="api"
label={
<CenteredNavLink to="/admin/api">
API access
</CenteredNavLink>
}
/>
{uiConfig.flags.embedProxyFrontend && (
<Tab
value="cors"
label={
<CenteredNavLink to="/admin/cors">
CORS origins
</CenteredNavLink>
}
/>
)}
<Tab
value="auth"
label={
<CenteredNavLink to="/admin/auth">
Single sign-on
</CenteredNavLink>
}
/>
<Tab
value="instance"
label={
<CenteredNavLink to="/admin/instance">
Instance stats
</CenteredNavLink>
}
/>
{networkViewEnabled && (
<Tab
value="network"
label={
<CenteredNavLink to="/admin/network">
Network
</CenteredNavLink>
}
/>
)}
<Tab
value="maintenance"
label={
<CenteredNavLink to="/admin/maintenance">
Maintenance
</CenteredNavLink>
}
/>
<Tab
value="instance-privacy"
label={
<CenteredNavLink to="/admin/instance-privacy">
Instance privacy
</CenteredNavLink>
}
/>
{isBilling && (
<Tab
value="billing"
label={
<CenteredNavLink to="/admin/billing">
Billing
</CenteredNavLink>
}
/>
)}
</Tabs>
</StyledPaper>
);
}
export default AdminMenu;

View File

@ -0,0 +1,141 @@
import { useLocation } from 'react-router-dom';
import { Box, 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';
import { VFC } from 'react';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { EnterpriseBadge } from 'component/common/EnterpriseBadge/EnterpriseBadge';
const StyledPaper = styled(Paper)(({ theme }) => ({
marginBottom: '1rem',
borderRadius: `${theme.shape.borderRadiusLarge}px`,
boxShadow: 'none',
padding: theme.spacing(0, 2),
}));
const StyledBadgeContainer = styled('div')(({ theme }) => ({
marginLeft: theme.spacing(1),
display: 'flex',
alignItems: 'center',
}));
export const AdminTabsMenu: VFC = () => {
const { uiConfig, isEnterprise, isPro } = useUiConfig();
const { pathname } = useLocation();
const { isBilling } = useInstanceStatus();
const { flags, networkViewEnabled } = uiConfig;
const activeTab = pathname.split('/')[2];
const showEnterpriseFeaturesInPro =
uiConfig?.flags?.frontendNavigationUpdate;
const tabs = [
{
value: 'users',
label: 'Users',
link: '/admin/users',
},
{
value: 'service-accounts',
label: 'Service accounts',
link: '/admin/service-accounts',
condition:
isEnterprise() || (isPro() && showEnterpriseFeaturesInPro),
showEnterpriseBadge: true,
},
{
value: 'groups',
label: 'Groups',
link: '/admin/groups',
condition: flags.UG,
},
{
value: 'roles',
label: 'Roles',
link: '/admin/roles',
condition:
isEnterprise() || (isPro() && showEnterpriseFeaturesInPro),
showEnterpriseBadge: true,
},
{
value: 'api',
label: 'API access',
link: '/admin/api',
},
{
value: 'cors',
label: 'CORS origins',
link: '/admin/cors',
condition: uiConfig.flags.embedProxyFrontend,
},
{
value: 'auth',
label: 'Single sign-on',
link: '/admin/auth',
},
{
value: 'instance',
label: 'Instance stats',
link: '/admin/instance',
},
{
value: 'network',
label: 'Network',
link: '/admin/network',
condition: networkViewEnabled,
},
{
value: 'maintenance',
label: 'Maintenance',
link: '/admin/maintenance',
},
{
value: 'instance-privacy',
label: 'Instance privacy',
link: '/admin/instance-privacy',
},
{
value: 'billing',
label: 'Billing',
link: '/admin/billing',
condition: isBilling,
},
];
return (
<StyledPaper>
<Tabs
value={activeTab}
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
>
{tabs
.filter(tab => tab.condition || tab.condition === undefined)
.map(tab => (
<Tab
key={tab.value}
value={tab.value}
label={
<CenteredNavLink to={tab.link}>
{tab.label}
<ConditionallyRender
condition={Boolean(
tab.showEnterpriseBadge
)}
show={
<StyledBadgeContainer>
<EnterpriseBadge size={16} />
</StyledBadgeContainer>
}
/>
</CenteredNavLink>
}
/>
))}
</Tabs>
</StyledPaper>
);
};

View File

@ -0,0 +1,15 @@
import { VFC } from 'react';
import { ReactComponent as ProPlanIcon } from 'assets/icons/pro-enterprise-feature-badge.svg';
import { ReactComponent as ProPlanIconLight } from 'assets/icons/pro-enterprise-feature-badge-light.svg';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
type EnterpriseBadgeProps = {
size?: number;
};
export const EnterpriseBadge: VFC<EnterpriseBadgeProps> = ({ size = 16 }) => (
<ThemeMode
darkmode={<ProPlanIconLight width={size} height={size} />}
lightmode={<ProPlanIcon width={size} height={size} />}
/>
);

View File

@ -0,0 +1,64 @@
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>
);

View File

@ -1,11 +1,26 @@
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';
export const LoginHistory = () => (
<div>
<PermissionGuard permissions={ADMIN}>
<LoginHistoryTable />
</PermissionGuard>
</div>
);
export const LoginHistory = () => {
const { isEnterprise } = useUiConfig();
if (!isEnterprise()) {
return (
<EnterpriseFeatureUpgradePage
title="Login history"
link="https://docs.getunleash.io/reference/login-history"
/>
);
}
return (
<div>
<PermissionGuard permissions={ADMIN}>
<LoginHistoryTable />
</PermissionGuard>
</div>
);
};

View File

@ -132,12 +132,23 @@ const Header: VFC = () => {
const routes = getRoutes();
const filterByMode = (route: INavigationMenuItem): boolean => {
const { mode } = route.menu;
return (
!mode ||
(mode.includes('pro') && isPro()) ||
(mode.includes('enterprise') && isEnterprise())
);
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 = {

View File

@ -2,11 +2,12 @@ import { Divider } from '@mui/material';
import { Menu, MenuItem, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { Fragment } from 'react';
import { INavigationMenuItem } from 'interfaces/route';
import { Link } from 'react-router-dom';
import { EnterpriseBadge } from '../../../common/EnterpriseBadge/EnterpriseBadge';
interface INavigationMenuProps {
options: any[];
options: INavigationMenuItem[];
id: string;
anchorEl: any;
handleClose: () => void;
@ -35,6 +36,12 @@ const StyledSpan = styled('span')(({ theme }) => ({
borderRadius: '2px',
}));
const StyledBadgeContainer = styled('div')(({ theme }) => ({
marginLeft: 'auto',
paddingLeft: theme.spacing(2),
display: 'flex',
}));
export const NavigationMenu = ({
options,
id,
@ -43,7 +50,7 @@ export const NavigationMenu = ({
style,
}: INavigationMenuProps) => {
const { uiConfig } = useUiConfig();
const showDividers = uiConfig?.flags?.frontendNavigationUpdate;
const showUpdatedMenu = uiConfig?.flags?.frontendNavigationUpdate;
return (
<Menu
@ -53,26 +60,41 @@ export const NavigationMenu = ({
open={Boolean(anchorEl)}
style={style}
>
{options.map((option, i) => (
<Fragment key={option.path}>
{options
.map((option, i) => [
<ConditionallyRender
key={`${option.path}-divider`}
condition={Boolean(
showDividers &&
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}
</MenuItem>
</Fragment>
))}
<ConditionallyRender
condition={Boolean(
option.menu.showEnterpriseBadge &&
showUpdatedMenu
)}
show={
<StyledBadgeContainer>
<EnterpriseBadge />
</StyledBadgeContainer>
}
/>
</MenuItem>,
])
.flat()
.filter(Boolean)}
</Menu>
);
};

View File

@ -462,20 +462,32 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
{
path: '/admin/service-accounts',
title: 'Service accounts',
menu: { adminSettings: true, mode: ['enterprise'] },
menu: {
adminSettings: true,
mode: ['enterprise'],
showEnterpriseBadge: true,
},
group: 'users',
},
{
path: '/admin/groups',
title: 'Groups',
menu: { adminSettings: true, mode: ['enterprise'] },
menu: {
adminSettings: true,
mode: ['enterprise'],
showEnterpriseBadge: true,
},
flag: UG,
group: 'users',
},
{
path: '/admin/roles/*',
title: 'Roles',
menu: { adminSettings: true, mode: ['enterprise'] },
menu: {
adminSettings: true,
mode: ['enterprise'],
showEnterpriseBadge: true,
},
group: 'users',
},
{
@ -532,7 +544,11 @@ export const adminMenuRoutes: INavigationMenuItem[] = [
{
path: '/admin/logins',
title: 'Login history',
menu: { adminSettings: true, mode: ['enterprise'] },
menu: {
adminSettings: true,
mode: ['enterprise'],
showEnterpriseBadge: true,
},
group: 'log',
},
{

View File

@ -30,4 +30,5 @@ interface IRouteMenu {
advanced?: boolean;
adminSettings?: boolean;
mode?: Array<'pro' | 'enterprise'>;
showEnterpriseBadge?: boolean;
}