0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console): add quota paywall for mfa feature (#4538)

This commit is contained in:
Xiao Yijun 2023-09-19 12:00:47 +08:00 committed by GitHub
parent 9c8b9e4853
commit bfcc5a2cc9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 8 deletions

View file

@ -8,11 +8,7 @@ type Props = {
};
function ContactUsPhraseLink({ children }: Props) {
return (
<TextLink href={contactEmailLink} target="_blank">
{children}
</TextLink>
);
return <TextLink href={contactEmailLink}>{children}</TextLink>;
}
export default ContactUsPhraseLink;

View file

@ -44,6 +44,11 @@
input:checked + .slider::before {
transform: translateX(16px);
}
input:disabled + .slider {
background-color: var(--color-disabled);
cursor: not-allowed;
}
}
.wrapper {

View file

@ -22,6 +22,10 @@
}
}
.unlockMfaNotice {
margin-top: _.unit(4);
}
.policyRadio {
> div[class$='content'] {
> div[class$='indicator'] {

View file

@ -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) {
<DynamicT forKey="mfa.multi_factors_description" />
</div>
<div className={styles.factorField}>
<Switch label={<FactorLabel type={MfaFactor.TOTP} />} {...register('totpEnabled')} />
<Switch
disabled={isMfaDisabled}
label={<FactorLabel type={MfaFactor.TOTP} />}
{...register('totpEnabled')}
/>
<Switch
disabled={isMfaDisabled}
label={<FactorLabel type={MfaFactor.WebAuthn} />}
{...register('webAuthnEnabled')}
/>
@ -90,6 +105,7 @@ function MfaForm({ data, onMfaUpdated }: Props) {
<DynamicT forKey="mfa.backup_code_setup_hint" />
</div>
<Switch
disabled={isMfaDisabled}
label={<FactorLabel type={MfaFactor.BackupCode} />}
hasError={!isBackupCodeAllowed}
{...register('backupCodeEnabled')}
@ -102,6 +118,23 @@ function MfaForm({ data, onMfaUpdated }: Props) {
</div>
</div>
</FormField>
{isMfaDisabled && (
<InlineNotification
className={styles.unlockMfaNotice}
action="mfa.view_plans"
onClick={() => {
navigate('/tenant-settings/subscription');
}}
>
<Trans
components={{
a: <ContactUsPhraseLink />,
}}
>
{t('mfa.unlock_reminder')}
</Trans>
</InlineNotification>
)}
</FormCard>
<FormCard title="mfa.policy">
<FormField title="mfa.two_step_sign_in_policy">
@ -118,6 +151,7 @@ function MfaForm({ data, onMfaUpdated }: Props) {
return (
<Radio
key={policy}
isDisabled={isMfaDisabled}
className={styles.policyRadio}
title={<PolicyOptionTitle {...titleProps} />}
value={policy}