feat: enable billing and org-settings in workspace blocked state (#6761)

* feat: enable billing and org-settings in workspace blocked state

* feat: disable sidebar items in workspace blocked state
This commit is contained in:
Yunus M 2025-01-08 11:16:41 +05:30 committed by GitHub
parent 5c546e8efd
commit e92d055c30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 96 additions and 6 deletions

View File

@ -11,6 +11,7 @@ import { useQuery } from 'react-query';
import { matchPath, useLocation } from 'react-router-dom'; import { matchPath, useLocation } from 'react-router-dom';
import { LicenseState, LicenseStatus } from 'types/api/licensesV3/getActive'; import { LicenseState, LicenseStatus } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization'; import { Organization } from 'types/api/user/getOrganization';
import { USER_ROLES } from 'types/roles';
import { isCloudUser } from 'utils/app'; import { isCloudUser } from 'utils/app';
import { routePermission } from 'utils/permission'; import { routePermission } from 'utils/permission';
@ -36,6 +37,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
activeLicenseV3, activeLicenseV3,
isFetchingActiveLicenseV3, isFetchingActiveLicenseV3,
} = useAppContext(); } = useAppContext();
const isAdmin = user.role === USER_ROLES.ADMIN;
const mapRoutes = useMemo( const mapRoutes = useMemo(
() => () =>
new Map( new Map(
@ -113,7 +116,17 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const navigateToWorkSpaceBlocked = (route: any): void => { const navigateToWorkSpaceBlocked = (route: any): void => {
const { path } = route; const { path } = route;
if (path && path !== ROUTES.WORKSPACE_LOCKED) { const isRouteEnabledForWorkspaceBlockedState =
isAdmin &&
(path === ROUTES.ORG_SETTINGS ||
path === ROUTES.BILLING ||
path === ROUTES.MY_SETTINGS);
if (
path &&
path !== ROUTES.WORKSPACE_LOCKED &&
!isRouteEnabledForWorkspaceBlockedState
) {
history.push(ROUTES.WORKSPACE_LOCKED); history.push(ROUTES.WORKSPACE_LOCKED);
} }
}; };
@ -127,6 +140,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
navigateToWorkSpaceBlocked(currentRoute); navigateToWorkSpaceBlocked(currentRoute);
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFetchingLicenses, licenses?.workSpaceBlock, mapRoutes, pathname]); }, [isFetchingLicenses, licenses?.workSpaceBlock, mapRoutes, pathname]);
const navigateToWorkSpaceSuspended = (route: any): void => { const navigateToWorkSpaceSuspended = (route: any): void => {

View File

@ -15,6 +15,13 @@
} }
} }
&.disabled {
.nav-item-data {
opacity: 0.5;
cursor: not-allowed;
}
}
&:hover { &:hover {
cursor: pointer; cursor: pointer;

View File

@ -11,17 +11,28 @@ export default function NavItem({
item, item,
isActive, isActive,
onClick, onClick,
isDisabled,
}: { }: {
item: SidebarItem; item: SidebarItem;
isActive: boolean; isActive: boolean;
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void; onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
isDisabled: boolean;
}): JSX.Element { }): JSX.Element {
const { label, icon, isBeta, isNew } = item; const { label, icon, isBeta, isNew } = item;
return ( return (
<div <div
className={cx('nav-item', isActive ? 'active' : '')} className={cx(
onClick={(event): void => onClick(event)} 'nav-item',
isActive ? 'active' : '',
isDisabled ? 'disabled' : '',
)}
onClick={(event): void => {
if (isDisabled) {
return;
}
onClick(event);
}}
> >
<div className="nav-item-active-marker" /> <div className="nav-item-active-marker" />
<div className={cx('nav-item-data', isBeta ? 'beta-tag' : '')}> <div className={cx('nav-item-data', isBeta ? 'beta-tag' : '')}>

View File

@ -89,6 +89,8 @@ function SideNav(): JSX.Element {
const licenseStatus: string = const licenseStatus: string =
licenses?.licenses?.find((e: License) => e.isCurrent)?.status || ''; licenses?.licenses?.find((e: License) => e.isCurrent)?.status || '';
const isWorkspaceBlocked = licenses?.workSpaceBlock || false;
const isLicenseActive = const isLicenseActive =
licenseStatus?.toLocaleLowerCase() === licenseStatus?.toLocaleLowerCase() ===
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase(); LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase();
@ -351,7 +353,11 @@ function SideNav(): JSX.Element {
<div className="get-started-nav-items"> <div className="get-started-nav-items">
<Button <Button
className="get-started-btn" className="get-started-btn"
disabled={isWorkspaceBlocked}
onClick={(event: MouseEvent): void => { onClick={(event: MouseEvent): void => {
if (isWorkspaceBlocked) {
return;
}
onClickGetStarted(event); onClickGetStarted(event);
}} }}
> >
@ -369,6 +375,11 @@ function SideNav(): JSX.Element {
key={item.key || index} key={item.key || index}
item={item} item={item}
isActive={activeMenuKey === item.key} isActive={activeMenuKey === item.key}
isDisabled={
isWorkspaceBlocked &&
item.key !== ROUTES.BILLING &&
item.key !== ROUTES.SETTINGS
}
onClick={(event): void => { onClick={(event): void => {
handleMenuItemClick(event, item); handleMenuItemClick(event, item);
}} }}
@ -380,6 +391,7 @@ function SideNav(): JSX.Element {
<NavItem <NavItem
key="keyboardShortcuts" key="keyboardShortcuts"
item={shortcutMenuItem} item={shortcutMenuItem}
isDisabled={isWorkspaceBlocked}
isActive={false} isActive={false}
onClick={onClickShortcuts} onClick={onClickShortcuts}
/> />
@ -389,6 +401,7 @@ function SideNav(): JSX.Element {
key="trySignozCloud" key="trySignozCloud"
item={trySignozCloudMenuItem} item={trySignozCloudMenuItem}
isActive={false} isActive={false}
isDisabled={isWorkspaceBlocked}
onClick={onClickSignozCloud} onClick={onClickSignozCloud}
/> />
)} )}
@ -399,6 +412,7 @@ function SideNav(): JSX.Element {
key={item?.key || index} key={item?.key || index}
item={item} item={item}
isActive={activeMenuKey === item?.key} isActive={activeMenuKey === item?.key}
isDisabled={isWorkspaceBlocked}
onClick={(event: MouseEvent): void => { onClick={(event: MouseEvent): void => {
handleUserManagentMenuItemClick(item?.key as string, event); handleUserManagentMenuItemClick(item?.key as string, event);
logEvent('Sidebar: Menu clicked', { logEvent('Sidebar: Menu clicked', {
@ -415,6 +429,7 @@ function SideNav(): JSX.Element {
key={inviteMemberMenuItem.key} key={inviteMemberMenuItem.key}
item={inviteMemberMenuItem} item={inviteMemberMenuItem}
isActive={activeMenuKey === inviteMemberMenuItem?.key} isActive={activeMenuKey === inviteMemberMenuItem?.key}
isDisabled={false}
onClick={(event: React.MouseEvent): void => { onClick={(event: React.MouseEvent): void => {
if (isCtrlMetaKey(event)) { if (isCtrlMetaKey(event)) {
openInNewTab(`${inviteMemberMenuItem.key}`); openInNewTab(`${inviteMemberMenuItem.key}`);
@ -434,6 +449,7 @@ function SideNav(): JSX.Element {
key={ROUTES.MY_SETTINGS} key={ROUTES.MY_SETTINGS}
item={userSettingsMenuItem} item={userSettingsMenuItem}
isActive={activeMenuKey === userSettingsMenuItem?.key} isActive={activeMenuKey === userSettingsMenuItem?.key}
isDisabled={false}
onClick={(event: MouseEvent): void => { onClick={(event: MouseEvent): void => {
handleUserManagentMenuItemClick( handleUserManagentMenuItemClick(
userSettingsMenuItem?.key as string, userSettingsMenuItem?.key as string,

View File

@ -11,7 +11,9 @@ import { getRoutes } from './utils';
function SettingsPage(): JSX.Element { function SettingsPage(): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { user, featureFlags } = useAppContext(); const { user, featureFlags, licenses } = useAppContext();
const isWorkspaceBlocked = licenses?.workSpaceBlock || false;
const [isCurrentOrgSettings] = useComponentPermission( const [isCurrentOrgSettings] = useComponentPermission(
['current_org_settings'], ['current_org_settings'],
@ -24,8 +26,15 @@ function SettingsPage(): JSX.Element {
?.active || false; ?.active || false;
const routes = useMemo( const routes = useMemo(
() => getRoutes(user.role, isCurrentOrgSettings, isGatewayEnabled, t), () =>
[user.role, isCurrentOrgSettings, isGatewayEnabled, t], getRoutes(
user.role,
isCurrentOrgSettings,
isGatewayEnabled,
isWorkspaceBlocked,
t,
),
[user.role, isCurrentOrgSettings, isGatewayEnabled, isWorkspaceBlocked, t],
); );
return <RouteTab routes={routes} activeKey={pathname} history={history} />; return <RouteTab routes={routes} activeKey={pathname} history={history} />;

View File

@ -17,6 +17,7 @@ export const getRoutes = (
userRole: ROLES | null, userRole: ROLES | null,
isCurrentOrgSettings: boolean, isCurrentOrgSettings: boolean,
isGatewayEnabled: boolean, isGatewayEnabled: boolean,
isWorkspaceBlocked: boolean,
t: TFunction, t: TFunction,
): RouteTabProps['routes'] => { ): RouteTabProps['routes'] => {
const settings = []; const settings = [];
@ -27,6 +28,12 @@ export const getRoutes = (
const isAdmin = userRole === USER_ROLES.ADMIN; const isAdmin = userRole === USER_ROLES.ADMIN;
const isEditor = userRole === USER_ROLES.EDITOR; const isEditor = userRole === USER_ROLES.EDITOR;
if (isWorkspaceBlocked && isAdmin) {
settings.push(...organizationSettings(t));
return settings;
}
settings.push(...generalSettings(t)); settings.push(...generalSettings(t));
if (isCurrentOrgSettings) { if (isCurrentOrgSettings) {

View File

@ -49,6 +49,14 @@ $dark-theme: 'darkMode';
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16px; gap: 16px;
.ant-btn-link {
color: var(--text-vanilla-400);
.#{$light-theme} & {
color: var(--text-ink-200);
}
}
} }
} }
.ant-modal-content { .ant-modal-content {

View File

@ -126,6 +126,12 @@ export default function WorkspaceBlocked(): JSX.Element {
}); });
}; };
const handleViewBilling = (): void => {
logEvent('Workspace Blocked: User Clicked View Billing', {});
history.push(ROUTES.BILLING);
};
const renderCustomerStories = ( const renderCustomerStories = (
filterCondition: (index: number) => boolean, filterCondition: (index: number) => boolean,
): JSX.Element[] => ): JSX.Element[] =>
@ -276,6 +282,18 @@ export default function WorkspaceBlocked(): JSX.Element {
{t('trialPlanExpired')} {t('trialPlanExpired')}
</span> </span>
<span className="workspace-locked__modal__header__actions"> <span className="workspace-locked__modal__header__actions">
{isAdmin && (
<Button
className="workspace-locked__modal__header__actions__billing"
type="link"
size="small"
role="button"
onClick={handleViewBilling}
>
View Billing
</Button>
)}
<Typography.Text className="workspace-locked__modal__title"> <Typography.Text className="workspace-locked__modal__title">
Got Questions? Got Questions?
</Typography.Text> </Typography.Text>