mirror of
https://github.com/docmost/docmost.git
synced 2025-11-13 15:22:40 +10:00
upsell
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Group, Text, ScrollArea, ActionIcon } from "@mantine/core";
|
import { Group, Text, ScrollArea, ActionIcon, Tooltip } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconUser,
|
IconUser,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
@ -42,6 +42,7 @@ interface DataItem {
|
|||||||
isEnterprise?: boolean;
|
isEnterprise?: boolean;
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
isSelfhosted?: boolean;
|
isSelfhosted?: boolean;
|
||||||
|
showDisabledInNonEE?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataGroup {
|
interface DataGroup {
|
||||||
@ -84,6 +85,7 @@ const groupedData: DataGroup[] = [
|
|||||||
isCloud: true,
|
isCloud: true,
|
||||||
isEnterprise: true,
|
isEnterprise: true,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
|
showDisabledInNonEE: true,
|
||||||
},
|
},
|
||||||
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
||||||
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
||||||
@ -117,6 +119,12 @@ export default function SettingsSidebar() {
|
|||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
const canShowItem = (item: DataItem) => {
|
const canShowItem = (item: DataItem) => {
|
||||||
|
// For items with showDisabledInCommunity, always show them but in disabled state
|
||||||
|
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||||
|
// Check admin permission regardless of license
|
||||||
|
return item.isAdmin ? isAdmin : true;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.isCloud && item.isEnterprise) {
|
if (item.isCloud && item.isEnterprise) {
|
||||||
if (!(isCloud() || workspace?.hasLicenseKey)) return false;
|
if (!(isCloud() || workspace?.hasLicenseKey)) return false;
|
||||||
return item.isAdmin ? isAdmin : true;
|
return item.isAdmin ? isAdmin : true;
|
||||||
@ -141,6 +149,13 @@ export default function SettingsSidebar() {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isItemDisabled = (item: DataItem) => {
|
||||||
|
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||||
|
return !(isCloud() || workspace?.hasLicenseKey);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const menuItems = groupedData.map((group) => {
|
const menuItems = groupedData.map((group) => {
|
||||||
if (group.heading === "System" && (!isAdmin || isCloud())) {
|
if (group.heading === "System" && (!isAdmin || isCloud())) {
|
||||||
return null;
|
return null;
|
||||||
@ -185,23 +200,48 @@ export default function SettingsSidebar() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const isDisabled = isItemDisabled(item);
|
||||||
|
const linkElement = (
|
||||||
<Link
|
<Link
|
||||||
onMouseEnter={prefetchHandler}
|
onMouseEnter={!isDisabled ? prefetchHandler : undefined}
|
||||||
className={classes.link}
|
className={classes.link}
|
||||||
data-active={active.startsWith(item.path) || undefined}
|
data-active={active.startsWith(item.path) || undefined}
|
||||||
|
data-disabled={isDisabled || undefined}
|
||||||
key={item.label}
|
key={item.label}
|
||||||
to={item.path}
|
to={isDisabled ? "#" : item.path}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
if (isDisabled) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (mobileSidebarOpened) {
|
if (mobileSidebarOpened) {
|
||||||
toggleMobileSidebar();
|
toggleMobileSidebar();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{
|
||||||
|
opacity: isDisabled ? 0.5 : 1,
|
||||||
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<item.icon className={classes.linkIcon} stroke={2} />
|
<item.icon className={classes.linkIcon} stroke={2} />
|
||||||
<span>{t(item.label)}</span>
|
<span>{t(item.label)}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isDisabled) {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
key={item.label}
|
||||||
|
label={t("Available in enterprise edition")}
|
||||||
|
position="right"
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
|
{linkElement}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkElement;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default function OssDetails() {
|
|||||||
withTableBorder
|
withTableBorder
|
||||||
>
|
>
|
||||||
<Table.Caption>
|
<Table.Caption>
|
||||||
To unlock enterprise features like SSO, contact sales@docmost.com.
|
To unlock enterprise features like SSO, MFA, Resolve comments, contact sales@docmost.com.
|
||||||
</Table.Caption>
|
</Table.Caption>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Group, Text, Button } from "@mantine/core";
|
import { Group, Text, Button, Tooltip } from "@mantine/core";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -7,6 +7,8 @@ import { getMfaStatus } from "@/ee/mfa";
|
|||||||
import { MfaSetupModal } from "@/ee/mfa";
|
import { MfaSetupModal } from "@/ee/mfa";
|
||||||
import { MfaDisableModal } from "@/ee/mfa";
|
import { MfaDisableModal } from "@/ee/mfa";
|
||||||
import { MfaBackupCodesModal } from "@/ee/mfa";
|
import { MfaBackupCodesModal } from "@/ee/mfa";
|
||||||
|
import { isCloud } from "@/lib/config.ts";
|
||||||
|
import useLicense from "@/ee/hooks/use-license.tsx";
|
||||||
|
|
||||||
export function MfaSettings() {
|
export function MfaSettings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -14,16 +16,19 @@ export function MfaSettings() {
|
|||||||
const [setupModalOpen, setSetupModalOpen] = useState(false);
|
const [setupModalOpen, setSetupModalOpen] = useState(false);
|
||||||
const [disableModalOpen, setDisableModalOpen] = useState(false);
|
const [disableModalOpen, setDisableModalOpen] = useState(false);
|
||||||
const [backupCodesModalOpen, setBackupCodesModalOpen] = useState(false);
|
const [backupCodesModalOpen, setBackupCodesModalOpen] = useState(false);
|
||||||
|
const { hasLicenseKey } = useLicense();
|
||||||
|
|
||||||
const { data: mfaStatus, isLoading } = useQuery({
|
const { data: mfaStatus, isLoading } = useQuery({
|
||||||
queryKey: ["mfa-status"],
|
queryKey: ["mfa-status"],
|
||||||
queryFn: getMfaStatus,
|
queryFn: getMfaStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || !mfaStatus) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canUseMfa = isCloud() || hasLicenseKey;
|
||||||
|
|
||||||
// Check if MFA is truly enabled
|
// Check if MFA is truly enabled
|
||||||
const isMfaEnabled = mfaStatus?.isEnabled === true;
|
const isMfaEnabled = mfaStatus?.isEnabled === true;
|
||||||
|
|
||||||
@ -61,13 +66,19 @@ export function MfaSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isMfaEnabled ? (
|
{!isMfaEnabled ? (
|
||||||
<Button
|
<Tooltip
|
||||||
variant="default"
|
label={t("Available in enterprise edition")}
|
||||||
onClick={() => setSetupModalOpen(true)}
|
disabled={canUseMfa}
|
||||||
style={{ whiteSpace: "nowrap" }}
|
|
||||||
>
|
>
|
||||||
{t("Add 2FA method")}
|
<Button
|
||||||
</Button>
|
disabled={!canUseMfa}
|
||||||
|
variant="default"
|
||||||
|
onClick={() => setSetupModalOpen(true)}
|
||||||
|
style={{ whiteSpace: "nowrap" }}
|
||||||
|
>
|
||||||
|
{t("Add 2FA method")}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Group gap="sm" wrap="nowrap">
|
<Group gap="sm" wrap="nowrap">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -319,7 +319,7 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
|
|||||||
>
|
>
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="Available in enterprise edition"
|
label={t("Available in enterprise edition")}
|
||||||
disabled={canUseConfluence}
|
disabled={canUseConfluence}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -1,15 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { isCloud } from "@/lib/config";
|
|
||||||
import { useLicense } from "@/ee/hooks/use-license";
|
|
||||||
import { MfaSettings } from "@/ee/mfa";
|
import { MfaSettings } from "@/ee/mfa";
|
||||||
|
|
||||||
export function AccountMfaSection() {
|
export function AccountMfaSection() {
|
||||||
const { hasLicenseKey } = useLicense();
|
|
||||||
const showMfa = isCloud() || hasLicenseKey;
|
|
||||||
|
|
||||||
if (!showMfa) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <MfaSettings />;
|
return <MfaSettings />;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user