diff --git a/packages/console/src/components/ContactUsPhraseLink/index.tsx b/packages/console/src/components/ContactUsPhraseLink/index.tsx
index e87bf38a2..6deaa4fa1 100644
--- a/packages/console/src/components/ContactUsPhraseLink/index.tsx
+++ b/packages/console/src/components/ContactUsPhraseLink/index.tsx
@@ -8,11 +8,7 @@ type Props = {
};
function ContactUsPhraseLink({ children }: Props) {
- return (
-
- {children}
-
- );
+ return {children};
}
export default ContactUsPhraseLink;
diff --git a/packages/console/src/ds-components/Switch/index.module.scss b/packages/console/src/ds-components/Switch/index.module.scss
index 2605536d2..5e559f240 100644
--- a/packages/console/src/ds-components/Switch/index.module.scss
+++ b/packages/console/src/ds-components/Switch/index.module.scss
@@ -44,6 +44,11 @@
input:checked + .slider::before {
transform: translateX(16px);
}
+
+ input:disabled + .slider {
+ background-color: var(--color-disabled);
+ cursor: not-allowed;
+ }
}
.wrapper {
diff --git a/packages/console/src/pages/Mfa/MfaForm/index.module.scss b/packages/console/src/pages/Mfa/MfaForm/index.module.scss
index 0226d6c7a..26b608c60 100644
--- a/packages/console/src/pages/Mfa/MfaForm/index.module.scss
+++ b/packages/console/src/pages/Mfa/MfaForm/index.module.scss
@@ -22,6 +22,10 @@
}
}
+.unlockMfaNotice {
+ margin-top: _.unit(4);
+}
+
.policyRadio {
> div[class$='content'] {
> div[class$='indicator'] {
diff --git a/packages/console/src/pages/Mfa/MfaForm/index.tsx b/packages/console/src/pages/Mfa/MfaForm/index.tsx
index bb5d2e853..bc418dc4b 100644
--- a/packages/console/src/pages/Mfa/MfaForm/index.tsx
+++ b/packages/console/src/pages/Mfa/MfaForm/index.tsx
@@ -1,19 +1,24 @@
import { MfaFactor, MfaPolicy, type SignInExperience } from '@logto/schemas';
import classNames from 'classnames';
-import { useMemo } from 'react';
+import { useContext, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
-import { useTranslation } from 'react-i18next';
+import { Trans, useTranslation } from 'react-i18next';
+import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
import DetailsForm from '@/components/DetailsForm';
import FormCard from '@/components/FormCard';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
+import { isCloud } from '@/consts/env';
+import { TenantsContext } from '@/contexts/TenantsProvider';
import DynamicT from '@/ds-components/DynamicT';
import FormField from '@/ds-components/FormField';
import InlineNotification from '@/ds-components/InlineNotification';
import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
import Switch from '@/ds-components/Switch';
import useApi from '@/hooks/use-api';
+import useSubscriptionPlan from '@/hooks/use-subscription-plan';
+import useTenantPathname from '@/hooks/use-tenant-pathname';
import { trySubmitSafe } from '@/utils/form';
import { type MfaConfigForm, type MfaConfig } from '../types';
@@ -30,6 +35,11 @@ type Props = {
};
function MfaForm({ data, onMfaUpdated }: Props) {
+ const { currentTenantId } = useContext(TenantsContext);
+ const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
+ const { navigate } = useTenantPathname();
+ const isMfaDisabled = isCloud && !currentPlan?.quota.mfaEnabled;
+
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
register,
@@ -80,8 +90,13 @@ function MfaForm({ data, onMfaUpdated }: Props) {
- } {...register('totpEnabled')} />
}
+ {...register('totpEnabled')}
+ />
+ }
{...register('webAuthnEnabled')}
/>
@@ -90,6 +105,7 @@ function MfaForm({ data, onMfaUpdated }: Props) {
}
hasError={!isBackupCodeAllowed}
{...register('backupCodeEnabled')}
@@ -102,6 +118,23 @@ function MfaForm({ data, onMfaUpdated }: Props) {
+ {isMfaDisabled && (
+ {
+ navigate('/tenant-settings/subscription');
+ }}
+ >
+ ,
+ }}
+ >
+ {t('mfa.unlock_reminder')}
+
+
+ )}
@@ -118,6 +151,7 @@ function MfaForm({ data, onMfaUpdated }: Props) {
return (
}
value={policy}