mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 15:45:57 +08:00
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:
parent
5c546e8efd
commit
e92d055c30
@ -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 => {
|
||||||
|
@ -15,6 +15,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
.nav-item-data {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -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' : '')}>
|
||||||
|
@ -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,
|
||||||
|
@ -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} />;
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user