From 73ed0c54e58ebbcc7aa816c491635e3495484b13 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 7 Mar 2026 21:57:14 +0000 Subject: [PATCH] feat: feature flag upgrade --- .../public/locales/en-US/translation.json | 9 +- .../components/settings/settings-sidebar.tsx | 102 ++++++------------ .../src/ee/ai/components/enable-ai-search.tsx | 32 +++--- .../ee/ai/components/enable-generative-ai.tsx | 21 ++-- .../src/ee/ai/components/mcp-settings.tsx | 69 +++++++----- apps/client/src/ee/ai/pages/ai-settings.tsx | 11 +- .../components/restrict-api-to-admins.tsx | 9 +- apps/client/src/ee/components/sso-login.tsx | 3 +- apps/client/src/ee/features.ts | 17 +++ .../src/ee/hooks/use-enterprise-access.tsx | 12 --- apps/client/src/ee/hooks/use-feature.ts | 12 +++ apps/client/src/ee/hooks/use-license.tsx | 9 -- apps/client/src/ee/hooks/use-upgrade-label.ts | 16 +++ .../ee/licence/components/license-details.tsx | 3 +- .../src/ee/licence/types/license.types.ts | 3 + .../src/ee/mfa/components/mfa-settings.tsx | 12 +-- .../components/page-share-modal.tsx | 11 +- .../components/disable-public-sharing.tsx | 27 +++-- .../ee/security/components/enforce-mfa.tsx | 33 ++++-- .../ee/security/components/enforce-sso.tsx | 26 +++-- .../security/components/trash-retention.tsx | 41 ++++--- .../client/src/ee/security/pages/security.tsx | 15 ++- .../comment/components/comment-list-item.tsx | 9 +- .../comment/components/comment-menu.tsx | 52 +++++---- .../page/components/page-import-modal.tsx | 16 +-- .../components/search-spotlight-filters.tsx | 8 +- .../search/components/search-spotlight.tsx | 11 +- .../search/hooks/use-unified-search.ts | 8 +- .../features/share/components/share-shell.tsx | 2 +- .../src/features/share/types/share.types.ts | 4 +- .../space/components/space-details.tsx | 5 +- .../workspace/types/workspace.types.ts | 3 +- apps/client/src/hooks/use-is-cloud-ee.tsx | 7 -- apps/client/src/pages/share/shared-page.tsx | 2 +- apps/server/src/common/helpers/utils.ts | 9 -- .../server/src/core/share/share.controller.ts | 23 ++-- .../src/core/space/services/space.service.ts | 4 +- apps/server/src/core/user/user.controller.ts | 8 ++ .../workspace/services/workspace.service.ts | 8 +- apps/server/src/ee | 2 +- .../environment/license-check.service.ts | 44 ++++++++ 41 files changed, 415 insertions(+), 303 deletions(-) create mode 100644 apps/client/src/ee/features.ts delete mode 100644 apps/client/src/ee/hooks/use-enterprise-access.tsx create mode 100644 apps/client/src/ee/hooks/use-feature.ts delete mode 100644 apps/client/src/ee/hooks/use-license.tsx create mode 100644 apps/client/src/ee/hooks/use-upgrade-label.ts delete mode 100644 apps/client/src/hooks/use-is-cloud-ee.tsx diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index cd2b7559..5370cc2a 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -439,7 +439,6 @@ "Toggle space public sharing": "Toggle space public sharing", "Public sharing is disabled at the workspace level": "Public sharing is disabled at the workspace level", "Prevent pages in this space from being shared publicly.": "Prevent pages in this space from being shared publicly.", - "Requires an enterprise license": "Requires an enterprise license", "Page permissions": "Page permissions", "Control who can view and edit individual pages. Available with an enterprise license.": "Control who can view and edit individual pages. Available with an enterprise license.", "Enable public sharing": "Enable public sharing", @@ -621,14 +620,16 @@ "Generative AI (Ask AI)": "Generative AI (Ask AI)", "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.": "Enable AI-powered content generation in the editor. Allows users to generate, improve, translate and transform text.", "Toggle generative AI": "Toggle generative AI", - "Enterprise feature": "Enterprise feature", - "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "Upgrade your plan": "Upgrade your plan", + "Available with a paid license": "Available with a paid license", + "Upgrade your license tier.": "Upgrade your license tier.", + "AI features require a paid plan. Visit docmost.com for more information.": "AI features require a paid plan. Visit docmost.com for more information.", "AI & MCP": "AI & MCP", "AI": "AI", "MCP": "MCP", "Model Context Protocol (MCP)": "Model Context Protocol (MCP)", "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.": "Enable the MCP server to allow AI assistants and tools to interact with your workspace content.", - "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.": "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "MCP requires a paid plan. Visit docmost.com for more information.": "MCP requires a paid plan. Visit docmost.com for more information.", "MCP documentation": "MCP documentation", "MCP Server URL": "MCP Server URL", "Use your API key for authentication. You can manage API keys in your account settings.": "Use your API key for authentication. You can manage API keys in your account settings.", diff --git a/apps/client/src/components/settings/settings-sidebar.tsx b/apps/client/src/components/settings/settings-sidebar.tsx index 6ac3587f..3000d79f 100644 --- a/apps/client/src/components/settings/settings-sidebar.tsx +++ b/apps/client/src/components/settings/settings-sidebar.tsx @@ -21,7 +21,9 @@ import { useTranslation } from "react-i18next"; import { isCloud } from "@/lib/config.ts"; import useUserRole from "@/hooks/use-user-role.tsx"; import { useAtom } from "jotai"; -import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; import { prefetchApiKeyManagement, prefetchApiKeys, @@ -39,22 +41,19 @@ import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sideb import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; import { useSettingsNavigation } from "@/hooks/use-settings-navigation"; -interface DataItem { +type DataItem = { label: string; icon: React.ElementType; path: string; - isCloud?: boolean; - isEnterprise?: boolean; - isAdmin?: boolean; - isOwner?: boolean; - isSelfhosted?: boolean; - showDisabledInNonEE?: boolean; -} + feature?: string; + role?: "admin" | "owner"; + env?: "cloud" | "selfhosted"; +}; -interface DataGroup { +type DataGroup = { heading: string; items: DataItem[]; -} +}; const groupedData: DataGroup[] = [ { @@ -70,9 +69,7 @@ const groupedData: DataGroup[] = [ label: "API keys", icon: IconKey, path: "/settings/account/api-keys", - isCloud: true, - isEnterprise: true, - showDisabledInNonEE: true, + feature: Feature.API_KEYS, }, ], }, @@ -80,26 +77,20 @@ const groupedData: DataGroup[] = [ heading: "Workspace", items: [ { label: "General", icon: IconSettings, path: "/settings/workspace" }, - { - label: "Members", - icon: IconUsers, - path: "/settings/members", - }, + { label: "Members", icon: IconUsers, path: "/settings/members" }, { label: "Billing", icon: IconCoin, path: "/settings/billing", - isCloud: true, - isAdmin: true, + role: "admin", + env: "cloud", }, { label: "Security & SSO", icon: IconLock, path: "/settings/security", - isCloud: true, - isEnterprise: true, - isAdmin: true, - showDisabledInNonEE: true, + feature: Feature.SECURITY_SETTINGS, + role: "admin", }, { label: "Groups", icon: IconUsersGroup, path: "/settings/groups" }, { label: "Spaces", icon: IconSpaces, path: "/settings/spaces" }, @@ -108,25 +99,22 @@ const groupedData: DataGroup[] = [ label: "API management", icon: IconKey, path: "/settings/api-keys", - isCloud: true, - isEnterprise: true, - isAdmin: true, - showDisabledInNonEE: true, + feature: Feature.API_KEYS, + role: "admin", }, { label: "AI settings", icon: IconSparkles, path: "/settings/ai", - isAdmin: true, + role: "admin", }, { label: "Audit log", icon: IconHistory, path: "/settings/audit", - isEnterprise: true, - isOwner: true, - isSelfhosted: true, - showDisabledInNonEE: true, + feature: Feature.AUDIT_LOGS, + role: "owner", + env: "selfhosted", }, ], }, @@ -149,6 +137,7 @@ export default function SettingsSidebar() { const { goBack } = useSettingsNavigation(); const { isAdmin, isOwner } = useUserRole(); const [workspace] = useAtom(workspaceAtom); + const upgradeLabel = useUpgradeLabel(); const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); @@ -156,43 +145,20 @@ export default function SettingsSidebar() { setActive(location.pathname); }, [location.pathname]); - const hasRoleAccess = (item: DataItem) => { - if (item.isOwner) return isOwner; - if (item.isAdmin) return isAdmin; + const hasFeature = (f: string) => + workspace?.features?.includes(f) ?? false; + + const canShowItem = (item: DataItem) => { + if (item.env === "cloud" && !isCloud()) return false; + if (item.env === "selfhosted" && isCloud()) return false; + if (item.role === "admin" && !isAdmin) return false; + if (item.role === "owner" && !isOwner) return false; return true; }; - const canShowItem = (item: DataItem) => { - if (item.showDisabledInNonEE && item.isEnterprise) { - if (item.isSelfhosted && isCloud()) return false; - return hasRoleAccess(item); - } - - if (item.isCloud && item.isEnterprise) { - if (!(isCloud() || workspace?.hasLicenseKey)) return false; - return hasRoleAccess(item); - } - - if (item.isCloud) { - return isCloud() ? hasRoleAccess(item) : false; - } - - if (item.isSelfhosted) { - return !isCloud() ? hasRoleAccess(item) : false; - } - - if (item.isEnterprise) { - return workspace?.hasLicenseKey ? hasRoleAccess(item) : false; - } - - return hasRoleAccess(item); - }; - const isItemDisabled = (item: DataItem) => { - if (item.showDisabledInNonEE && item.isEnterprise) { - return !(isCloud() || workspace?.hasLicenseKey); - } - return false; + if (!item.feature) return false; + return !hasFeature(item.feature); }; const menuItems = groupedData.map((group) => { @@ -280,7 +246,7 @@ export default function SettingsSidebar() { return ( diff --git a/apps/client/src/ee/ai/components/enable-ai-search.tsx b/apps/client/src/ee/ai/components/enable-ai-search.tsx index 91242804..3a2abd26 100644 --- a/apps/client/src/ee/ai/components/enable-ai-search.tsx +++ b/apps/client/src/ee/ai/components/enable-ai-search.tsx @@ -1,12 +1,13 @@ -import { Group, Text, Switch, MantineSize, Title } from "@mantine/core"; +import { Group, Text, Switch, MantineSize, Tooltip } from "@mantine/core"; import { useAtom } from "jotai"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; import { notifications } from "@mantine/notifications"; -import { isCloud } from "@/lib/config.ts"; -import useLicense from "@/ee/hooks/use-license.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; export default function EnableAiSearch() { const { t } = useTranslation(); @@ -37,9 +38,8 @@ export function AiSearchToggle({ size, label }: AiSearchToggleProps) { const { t } = useTranslation(); const [workspace, setWorkspace] = useAtom(workspaceAtom); const [checked, setChecked] = useState(workspace?.settings?.ai?.search); - const { hasLicenseKey } = useLicense(); - - const hasAccess = isCloud() || (!isCloud() && hasLicenseKey); + const hasAccess = useHasFeature(Feature.AI); + const upgradeLabel = useUpgradeLabel(); const handleChange = async (event: React.ChangeEvent) => { const value = event.currentTarget.checked; @@ -56,14 +56,16 @@ export function AiSearchToggle({ size, label }: AiSearchToggleProps) { }; return ( - + + + ); } diff --git a/apps/client/src/ee/ai/components/enable-generative-ai.tsx b/apps/client/src/ee/ai/components/enable-generative-ai.tsx index 9e09f4f0..1db611ce 100644 --- a/apps/client/src/ee/ai/components/enable-generative-ai.tsx +++ b/apps/client/src/ee/ai/components/enable-generative-ai.tsx @@ -1,17 +1,20 @@ -import { Group, Text, Switch } from "@mantine/core"; +import { Group, Text, Switch, Tooltip } from "@mantine/core"; import { useAtom } from "jotai"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; import { notifications } from "@mantine/notifications"; -import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; export default function EnableGenerativeAi() { const { t } = useTranslation(); const [workspace, setWorkspace] = useAtom(workspaceAtom); const [checked, setChecked] = useState(workspace?.settings?.ai?.generative); - const hasAccess = useIsCloudEE(); + const hasAccess = useHasFeature(Feature.AI); + const upgradeLabel = useUpgradeLabel(); const handleChange = async (event: React.ChangeEvent) => { const value = event.currentTarget.checked; @@ -38,11 +41,13 @@ export default function EnableGenerativeAi() { - + + + ); } diff --git a/apps/client/src/ee/ai/components/mcp-settings.tsx b/apps/client/src/ee/ai/components/mcp-settings.tsx index d39d285b..b09ee1b2 100644 --- a/apps/client/src/ee/ai/components/mcp-settings.tsx +++ b/apps/client/src/ee/ai/components/mcp-settings.tsx @@ -16,7 +16,9 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; import { notifications } from "@mantine/notifications"; -import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; import { getAppUrl } from "@/lib/config.ts"; import { IconCheck, IconCopy, IconInfoCircle } from "@tabler/icons-react"; import { CopyButton } from "@/components/common/copy-button.tsx"; @@ -25,7 +27,8 @@ export default function McpSettings() { const { t } = useTranslation(); const [workspace, setWorkspace] = useAtom(workspaceAtom); const [checked, setChecked] = useState(workspace?.settings?.ai?.mcp); - const hasAccess = useIsCloudEE(); + const hasAccess = useHasFeature(Feature.MCP); + const upgradeLabel = useUpgradeLabel(); const mcpUrl = `${getAppUrl()}/mcp`; @@ -46,13 +49,9 @@ export default function McpSettings() { return ( {!hasAccess && ( - } - title={t("Enterprise feature")} - color="blue" - > + } title={upgradeLabel} color="blue"> {t( - "MCP is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "MCP requires a paid plan. Visit docmost.com for more information.", )} )} @@ -76,11 +75,13 @@ export default function McpSettings() { - + + + {checked && ( @@ -89,11 +90,7 @@ export default function McpSettings() { {t("MCP Server URL")} - + {({ copied, copy }) => ( - search_pages, get_page, create_page, update_page - list_pages, list_child_pages, duplicate_page - copy_page_to_space, move_page, move_page_to_space - get_space, list_spaces, create_space, update_space - get_comments, create_comment, update_comment - search_attachments, list_workspace_members, get_current_user + + + search_pages, get_page, create_page, update_page + + + + + list_pages, list_child_pages, duplicate_page + + + + + copy_page_to_space, move_page, move_page_to_space + + + + + get_space, list_spaces, create_space, update_space + + + + + get_comments, create_comment, update_comment + + + + + search_attachments, list_workspace_members, get_current_user + + diff --git a/apps/client/src/ee/ai/pages/ai-settings.tsx b/apps/client/src/ee/ai/pages/ai-settings.tsx index 53fa9a87..cf09338c 100644 --- a/apps/client/src/ee/ai/pages/ai-settings.tsx +++ b/apps/client/src/ee/ai/pages/ai-settings.tsx @@ -9,14 +9,17 @@ import EnableGenerativeAi from "@/ee/ai/components/enable-generative-ai.tsx"; import McpSettings from "@/ee/ai/components/mcp-settings.tsx"; import { Alert, Stack, Tabs } from "@mantine/core"; import { IconInfoCircle } from "@tabler/icons-react"; -import { useIsCloudEE } from "@/hooks/use-is-cloud-ee.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; import { isCloud } from "@/lib/config.ts"; import { useLocation, useNavigate } from "react-router-dom"; export default function AiSettings() { const { t } = useTranslation(); const { isAdmin } = useUserRole(); - const hasAccess = useIsCloudEE(); + const hasAccess = useHasFeature(Feature.AI); + const upgradeLabel = useUpgradeLabel(); const location = useLocation(); const navigate = useNavigate(); @@ -55,12 +58,12 @@ export default function AiSettings() { {!hasAccess && ( } - title={t("Enterprise feature")} + title={upgradeLabel} color="blue" mb="lg" > {t( - "AI is only available in the Docmost enterprise edition. Contact sales@docmost.com.", + "AI features require a paid plan. Visit docmost.com for more information.", )} )} diff --git a/apps/client/src/ee/api-key/components/restrict-api-to-admins.tsx b/apps/client/src/ee/api-key/components/restrict-api-to-admins.tsx index accbc545..356d3dcb 100644 --- a/apps/client/src/ee/api-key/components/restrict-api-to-admins.tsx +++ b/apps/client/src/ee/api-key/components/restrict-api-to-admins.tsx @@ -5,12 +5,14 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; import { notifications } from "@mantine/notifications"; -import useEnterpriseAccess from "@/ee/hooks/use-enterprise-access.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl, } from "@/components/ui/responsive-settings-row"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label.ts"; export default function RestrictApiToAdmins() { const { t } = useTranslation(); @@ -18,7 +20,8 @@ export default function RestrictApiToAdmins() { const [checked, setChecked] = useState( workspace?.settings?.api?.restrictToAdmins === true, ); - const hasAccess = useEnterpriseAccess(); + const hasAccess = useHasFeature(Feature.API_KEYS); + const upgradeLabel = useUpgradeLabel(); const handleChange = async (event: React.ChangeEvent) => { const value = event.currentTarget.checked; @@ -51,7 +54,7 @@ export default function RestrictApiToAdmins() { diff --git a/apps/client/src/ee/components/sso-login.tsx b/apps/client/src/ee/components/sso-login.tsx index 8c96d9c5..dd516060 100644 --- a/apps/client/src/ee/components/sso-login.tsx +++ b/apps/client/src/ee/components/sso-login.tsx @@ -6,7 +6,6 @@ import { IAuthProvider } from "@/ee/security/types/security.types.ts"; import { buildSsoLoginUrl } from "@/ee/security/sso.utils.ts"; import { SSO_PROVIDER } from "@/ee/security/contants.ts"; import { GoogleIcon } from "@/components/icons/google-icon.tsx"; -import { isCloud } from "@/lib/config.ts"; import { LdapLoginModal } from "@/ee/components/ldap-login-modal.tsx"; export default function SsoLogin() { @@ -57,7 +56,7 @@ export default function SsoLogin() { /> )} - {(isCloud() || data.hasLicenseKey) && ( + {(data.features?.length > 0) && ( <> {data.authProviders.map((provider) => ( diff --git a/apps/client/src/ee/features.ts b/apps/client/src/ee/features.ts new file mode 100644 index 00000000..6ef349d3 --- /dev/null +++ b/apps/client/src/ee/features.ts @@ -0,0 +1,17 @@ +export const Feature = { + SSO_CUSTOM: 'sso:custom', + SSO_GOOGLE: 'sso:google', + MFA: 'mfa', + API_KEYS: 'api:keys', + COMMENT_RESOLUTION: 'comment:resolution', + PAGE_PERMISSIONS: 'page:permissions', + AI: 'ai', + CONFLUENCE_IMPORT: 'import:confluence', + DOCX_IMPORT: 'import:docx', + ATTACHMENT_INDEXING: 'attachment:indexing', + SECURITY_SETTINGS: 'security:settings', + MCP: 'mcp', + SCIM: 'scim', + PAGE_VERIFICATION: 'page:verification', + AUDIT_LOGS: 'audit:logs', +} as const; diff --git a/apps/client/src/ee/hooks/use-enterprise-access.tsx b/apps/client/src/ee/hooks/use-enterprise-access.tsx deleted file mode 100644 index b7746d6f..00000000 --- a/apps/client/src/ee/hooks/use-enterprise-access.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { isCloud } from "@/lib/config"; -import useLicense from "@/ee/hooks/use-license"; -import usePlan from "@/ee/hooks/use-plan"; - -const useEnterpriseAccess = () => { - const { hasLicenseKey } = useLicense(); - const { isBusiness } = usePlan(); - - return (isCloud() && isBusiness) || (!isCloud() && hasLicenseKey); -}; - -export default useEnterpriseAccess; diff --git a/apps/client/src/ee/hooks/use-feature.ts b/apps/client/src/ee/hooks/use-feature.ts new file mode 100644 index 00000000..0801dad2 --- /dev/null +++ b/apps/client/src/ee/hooks/use-feature.ts @@ -0,0 +1,12 @@ +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom"; + +export const useHasFeature = (feature: string): boolean => { + const [workspace] = useAtom(workspaceAtom); + return workspace?.features?.includes(feature) ?? false; +}; + +export const useHasAnyFeature = (): boolean => { + const [workspace] = useAtom(workspaceAtom); + return (workspace?.features?.length ?? 0) > 0; +}; diff --git a/apps/client/src/ee/hooks/use-license.tsx b/apps/client/src/ee/hooks/use-license.tsx deleted file mode 100644 index e3f72d82..00000000 --- a/apps/client/src/ee/hooks/use-license.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { useAtom } from "jotai"; -import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts"; - -export const useLicense = () => { - const [currentUser] = useAtom(currentUserAtom); - return { hasLicenseKey: currentUser?.workspace?.hasLicenseKey }; -}; - -export default useLicense; diff --git a/apps/client/src/ee/hooks/use-upgrade-label.ts b/apps/client/src/ee/hooks/use-upgrade-label.ts new file mode 100644 index 00000000..379c1af8 --- /dev/null +++ b/apps/client/src/ee/hooks/use-upgrade-label.ts @@ -0,0 +1,16 @@ +import { useAtom } from "jotai"; +import { useTranslation } from "react-i18next"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom"; +import { isCloud } from "@/lib/config"; + +export function useUpgradeLabel(): string { + const { t } = useTranslation(); + const [workspace] = useAtom(workspaceAtom); + + if (!isCloud()) { + return workspace?.hasLicenseKey + ? t("Upgrade your license tier.") + : t("Available with a paid license"); + } + return t("Upgrade your plan"); +} diff --git a/apps/client/src/ee/licence/components/license-details.tsx b/apps/client/src/ee/licence/components/license-details.tsx index 323b72d3..d3a93632 100644 --- a/apps/client/src/ee/licence/components/license-details.tsx +++ b/apps/client/src/ee/licence/components/license-details.tsx @@ -31,7 +31,8 @@ export default function LicenseDetails() { Edition - Enterprise {license.trial && Trial} + {license.licenseType === "business" ? "Business" : "Enterprise"}{" "} + {license.trial && Trial} diff --git a/apps/client/src/ee/licence/types/license.types.ts b/apps/client/src/ee/licence/types/license.types.ts index e0493a64..ec3c9a18 100644 --- a/apps/client/src/ee/licence/types/license.types.ts +++ b/apps/client/src/ee/licence/types/license.types.ts @@ -1,7 +1,10 @@ +export type LicenseType = 'business' | 'enterprise'; + export interface ILicenseInfo { id: string; customerName: string; seatCount: number; + licenseType: LicenseType; issuedAt: Date; expiresAt: Date; trial: boolean; diff --git a/apps/client/src/ee/mfa/components/mfa-settings.tsx b/apps/client/src/ee/mfa/components/mfa-settings.tsx index 73d9247d..620d67ac 100644 --- a/apps/client/src/ee/mfa/components/mfa-settings.tsx +++ b/apps/client/src/ee/mfa/components/mfa-settings.tsx @@ -7,8 +7,9 @@ import { getMfaStatus } from "@/ee/mfa"; import { MfaSetupModal } from "@/ee/mfa"; import { MfaDisableModal } from "@/ee/mfa"; import { MfaBackupCodesModal } from "@/ee/mfa"; -import { isCloud } from "@/lib/config.ts"; -import useLicense from "@/ee/hooks/use-license.tsx"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label"; import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row"; export function MfaSettings() { @@ -17,7 +18,8 @@ export function MfaSettings() { const [setupModalOpen, setSetupModalOpen] = useState(false); const [disableModalOpen, setDisableModalOpen] = useState(false); const [backupCodesModalOpen, setBackupCodesModalOpen] = useState(false); - const { hasLicenseKey } = useLicense(); + const canUseMfa = useHasFeature(Feature.MFA); + const upgradeLabel = useUpgradeLabel(); const { data: mfaStatus, isLoading } = useQuery({ queryKey: ["mfa-status"], @@ -28,8 +30,6 @@ export function MfaSettings() { return null; } - const canUseMfa = isCloud() || hasLicenseKey; - // Check if MFA is truly enabled const isMfaEnabled = mfaStatus?.isEnabled === true; @@ -69,7 +69,7 @@ export function MfaSettings() { {!isMfaEnabled ? (