mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
feat(console): add downgrade plan confirm modal (#4161)
This commit is contained in:
parent
c00cfedcbb
commit
513d56afec
14 changed files with 478 additions and 2 deletions
3
packages/console/src/assets/icons/descend-arrow.svg
Normal file
3
packages/console/src/assets/icons/descend-arrow.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 7.33335C13.8231 7.33335 13.6536 7.40358 13.5286 7.52861C13.4035 7.65363 13.3333 7.8232 13.3333 8.00001V9.72668L9.13996 5.52668C9.07798 5.46419 9.00425 5.4146 8.92301 5.38075C8.84177 5.34691 8.75463 5.32948 8.66663 5.32948C8.57862 5.32948 8.49148 5.34691 8.41024 5.38075C8.329 5.4146 8.25527 5.46419 8.19329 5.52668L5.99996 7.72668L2.47329 4.19335C2.34776 4.06781 2.1775 3.99728 1.99996 3.99728C1.82243 3.99728 1.65216 4.06781 1.52663 4.19335C1.40109 4.31888 1.33057 4.48914 1.33057 4.66668C1.33057 4.84421 1.40109 5.01448 1.52663 5.14001L5.52663 9.14001C5.5886 9.2025 5.66234 9.25209 5.74358 9.28594C5.82482 9.31979 5.91195 9.33721 5.99996 9.33721C6.08797 9.33721 6.17511 9.31979 6.25635 9.28594C6.33758 9.25209 6.41132 9.2025 6.47329 9.14001L8.66663 6.94001L12.3933 10.6667H10.6666C10.4898 10.6667 10.3202 10.7369 10.1952 10.8619C10.0702 10.987 9.99996 11.1565 9.99996 11.3333C9.99996 11.5102 10.0702 11.6797 10.1952 11.8048C10.3202 11.9298 10.4898 12 10.6666 12H14C14.0871 11.999 14.1731 11.9808 14.2533 11.9467C14.4162 11.879 14.5456 11.7496 14.6133 11.5867C14.6475 11.5065 14.6656 11.4205 14.6666 11.3333V8.00001C14.6666 7.8232 14.5964 7.65363 14.4714 7.52861C14.3463 7.40358 14.1768 7.33335 14 7.33335Z" fill="#EB9918"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -8,6 +8,7 @@ import Button from '@/ds-components/Button';
|
|||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import ModalLayout from '../ModalLayout';
|
||||
import type { Props as ModalLayoutProps } from '../ModalLayout';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -21,6 +22,7 @@ export type ConfirmModalProps = {
|
|||
isOpen: boolean;
|
||||
isConfirmButtonDisabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
size?: ModalLayoutProps['size'];
|
||||
onCancel?: () => void;
|
||||
onConfirm?: () => void;
|
||||
};
|
||||
|
@ -35,6 +37,7 @@ function ConfirmModal({
|
|||
isOpen,
|
||||
isConfirmButtonDisabled = false,
|
||||
isLoading = false,
|
||||
size,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: ConfirmModalProps) {
|
||||
|
@ -63,6 +66,7 @@ function ConfirmModal({
|
|||
</>
|
||||
}
|
||||
className={classNames(styles.content, className)}
|
||||
size={size}
|
||||
onClose={onCancel}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -10,7 +10,7 @@ import IconButton from '../IconButton';
|
|||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
onClose?: () => void;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.item {
|
||||
margin-left: _.unit(4);
|
||||
|
||||
&.withChangeState {
|
||||
list-style-type: none;
|
||||
margin-left: unset;
|
||||
}
|
||||
|
||||
.itemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: _.unit(2);
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.notCapable {
|
||||
text-decoration: line-through;
|
||||
|
||||
.icon {
|
||||
color: var(--color-error-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import DescendArrow from '@/assets/icons/descend-arrow.svg';
|
||||
import Failed from '@/assets/icons/failed.svg';
|
||||
import {
|
||||
quotaItemUnlimitedPhrasesMap,
|
||||
quotaItemPhrasesMap,
|
||||
quotaItemLimitedPhrasesMap,
|
||||
} from '@/pages/TenantSettings/Subscription/quota-item-phrases';
|
||||
import { type SubscriptionPlanQuota } from '@/types/subscriptions';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
hasIcon?: boolean;
|
||||
quotaKey: keyof SubscriptionPlanQuota;
|
||||
quotaValue: SubscriptionPlanQuota[keyof SubscriptionPlanQuota];
|
||||
};
|
||||
|
||||
function QuotaDiffItem({ hasIcon = false, quotaKey, quotaValue }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.subscription.quota_item' });
|
||||
const isUnlimited = quotaValue === null;
|
||||
const isNotCapable = quotaValue === 0 || quotaValue === false;
|
||||
const isLimited = Boolean(quotaValue);
|
||||
|
||||
const Icon = isNotCapable ? Failed : DescendArrow;
|
||||
|
||||
return (
|
||||
<li className={classNames(styles.item, hasIcon && styles.withChangeState)}>
|
||||
<span
|
||||
className={classNames(styles.itemContent, hasIcon && isNotCapable && styles.notCapable)}
|
||||
>
|
||||
{hasIcon && <Icon className={styles.icon} />}
|
||||
<span>
|
||||
{isUnlimited && <>{t(quotaItemUnlimitedPhrasesMap[quotaKey])}</>}
|
||||
{isNotCapable && <>{t(quotaItemPhrasesMap[quotaKey])}</>}
|
||||
{isLimited && (
|
||||
<>
|
||||
{t(
|
||||
quotaItemLimitedPhrasesMap[quotaKey],
|
||||
conditional(typeof quotaValue === 'number' && { count: quotaValue }) ?? {}
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuotaDiffItem;
|
|
@ -0,0 +1,24 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
background-color: var(--color-layer-2);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(5);
|
||||
|
||||
.title {
|
||||
font: var(--font-title-2);
|
||||
margin-bottom: _.unit(3);
|
||||
}
|
||||
|
||||
.list {
|
||||
font: var(--font-body-2);
|
||||
padding-inline-start: 0;
|
||||
|
||||
> li {
|
||||
&:not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { useMemo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import PlanName from '@/components/PlanName';
|
||||
import { type SubscriptionPlanQuota } from '@/types/subscriptions';
|
||||
|
||||
import QuotaDiffItem from './QuotaDiffItem';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
planName: string;
|
||||
quotaDiff: Partial<SubscriptionPlanQuota>;
|
||||
isTarget?: boolean;
|
||||
};
|
||||
|
||||
function PlanQuotaDiffList({ planName, quotaDiff, isTarget = false }: Props) {
|
||||
const { t } = useTranslation(undefined, {
|
||||
keyPrefix: 'admin_console.subscription.downgrade_modal',
|
||||
});
|
||||
|
||||
// Todo: @xiaoyijun LOG-6540 order keys
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const entries = useMemo(() => Object.entries(quotaDiff), [quotaDiff]) as Array<
|
||||
[keyof SubscriptionPlanQuota, SubscriptionPlanQuota[keyof SubscriptionPlanQuota]]
|
||||
>;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>
|
||||
<Trans
|
||||
components={{
|
||||
name: <PlanName name={planName} />,
|
||||
}}
|
||||
>
|
||||
{t(isTarget ? 'after' : 'before')}
|
||||
</Trans>
|
||||
</div>
|
||||
<ul className={styles.list}>
|
||||
{entries.map(([quotaKey, quotaValue]) => (
|
||||
<QuotaDiffItem
|
||||
key={quotaKey}
|
||||
quotaKey={quotaKey}
|
||||
quotaValue={quotaValue}
|
||||
hasIcon={isTarget}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlanQuotaDiffList;
|
|
@ -0,0 +1,17 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
> :not(:first-child) {
|
||||
margin: _.unit(6) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: _.unit(3);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { diff } from 'deep-object-diff';
|
||||
import { useMemo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import PlanName from '@/components/PlanName';
|
||||
import { type SubscriptionPlan } from '@/types/subscriptions';
|
||||
|
||||
import PlanQuotaDiffList from './PlanQuotaDiffList';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
currentPlan: SubscriptionPlan;
|
||||
targetPlan: SubscriptionPlan;
|
||||
};
|
||||
|
||||
function DowngradeConfirmModalContent({ currentPlan, targetPlan }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { quota: currentQuota, name: currentPlanName } = currentPlan;
|
||||
const { quota: targetQuota, name: targetPlanName } = targetPlan;
|
||||
|
||||
const currentQuotaDiff = useMemo(
|
||||
() => diff(targetQuota, currentQuota),
|
||||
[currentQuota, targetQuota]
|
||||
);
|
||||
|
||||
const targetQuotaDiff = useMemo(
|
||||
() => diff(currentQuota, targetQuota),
|
||||
[currentQuota, targetQuota]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.description}>
|
||||
<Trans
|
||||
components={{
|
||||
targetName: <PlanName name={targetPlanName} />,
|
||||
currentName: <PlanName name={currentPlanName} />,
|
||||
}}
|
||||
>
|
||||
{t('subscription.downgrade_modal.description')}
|
||||
</Trans>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<PlanQuotaDiffList planName={currentPlanName} quotaDiff={currentQuotaDiff} />
|
||||
<PlanQuotaDiffList isTarget planName={targetPlanName} quotaDiff={targetQuotaDiff} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DowngradeConfirmModalContent;
|
|
@ -0,0 +1,26 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.container {
|
||||
> :not(:first-child) {
|
||||
margin: _.unit(6) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
.list {
|
||||
background-color: var(--color-layer-2);
|
||||
border-radius: 12px;
|
||||
padding: _.unit(4);
|
||||
list-style-position: inside;
|
||||
|
||||
> li:not(:first-child) {
|
||||
margin-top: _.unit(3);
|
||||
}
|
||||
}
|
||||
|
||||
.buttonLink {
|
||||
text-decoration: none;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import PlanName from '@/components/PlanName';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import { type SubscriptionPlan, type SubscriptionPlanQuota } from '@/types/subscriptions';
|
||||
|
||||
import { quotaItemLimitedPhrasesMap, quotaItemNotEligiblePhrasesMap } from '../quota-item-phrases';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const excludedQuotaKeys = new Set<keyof SubscriptionPlanQuota>([
|
||||
'auditLogsRetentionDays',
|
||||
'communitySupportEnabled',
|
||||
'ticketSupportResponseTime',
|
||||
]);
|
||||
|
||||
type Props = {
|
||||
targetPlan: SubscriptionPlan;
|
||||
};
|
||||
|
||||
function NotEligibleDowngradeModalContent({ targetPlan }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { name, quota } = targetPlan;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const entries = Object.entries(quota) as Array<
|
||||
[keyof SubscriptionPlanQuota, SubscriptionPlanQuota[keyof SubscriptionPlanQuota]]
|
||||
>;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.description}>
|
||||
<Trans
|
||||
components={{
|
||||
name: <PlanName name={name} />,
|
||||
}}
|
||||
>
|
||||
{t('subscription.downgrade_modal.not_eligible_description')}
|
||||
</Trans>
|
||||
</div>
|
||||
<ul className={styles.list}>
|
||||
{entries.map(([quotaKey, quotaValue]) => {
|
||||
if (
|
||||
excludedQuotaKeys.has(quotaKey) ||
|
||||
quotaValue === null || // Unlimited items
|
||||
quotaValue === true // Eligible items
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={quotaKey}>
|
||||
{quotaValue ? (
|
||||
<Trans
|
||||
components={{
|
||||
item: (
|
||||
<DynamicT
|
||||
forKey={`subscription.quota_item.${quotaItemLimitedPhrasesMap[quotaKey]}`}
|
||||
interpolation={conditional(
|
||||
typeof quotaValue === 'number' && { count: quotaValue }
|
||||
)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{t('subscription.downgrade_modal.a_maximum_of')}
|
||||
</Trans>
|
||||
) : (
|
||||
<DynamicT
|
||||
forKey={`subscription.quota_item.${quotaItemNotEligiblePhrasesMap[quotaKey]}`}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotEligibleDowngradeModalContent;
|
|
@ -1,9 +1,13 @@
|
|||
import { contactEmailLink } from '@/consts';
|
||||
import Button from '@/ds-components/Button';
|
||||
import Spacer from '@/ds-components/Spacer';
|
||||
import { useConfirmModal } from '@/hooks/use-confirm-modal';
|
||||
import { type SubscriptionPlan } from '@/types/subscriptions';
|
||||
import { isDowngradePlan } from '@/utils/subscription';
|
||||
|
||||
import DowngradeConfirmModalContent from '../DowngradeConfirmModalContent';
|
||||
import NotEligibleDowngradeModalContent from '../NotEligibleDowngradeModalContent';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
|
@ -12,6 +16,39 @@ type Props = {
|
|||
};
|
||||
|
||||
function SwitchPlanActionBar({ currentSubscriptionPlanId, subscriptionPlans }: Props) {
|
||||
const { show } = useConfirmModal();
|
||||
|
||||
const handleDownGrade = async (targetPlan: SubscriptionPlan) => {
|
||||
// Todo @xiaoyijun handle downgrade
|
||||
await show({
|
||||
ModalContent: () => <NotEligibleDowngradeModalContent targetPlan={targetPlan} />,
|
||||
title: 'subscription.downgrade_modal.not_eligible',
|
||||
confirmButtonText: 'general.got_it',
|
||||
confirmButtonType: 'primary',
|
||||
});
|
||||
};
|
||||
|
||||
const onDowngradeClick = async (targetPlanId: string) => {
|
||||
const currentPlan = subscriptionPlans.find(({ id }) => id === currentSubscriptionPlanId);
|
||||
const targetPlan = subscriptionPlans.find(({ id }) => id === targetPlanId);
|
||||
if (!currentPlan || !targetPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [result] = await show({
|
||||
ModalContent: () => (
|
||||
<DowngradeConfirmModalContent currentPlan={currentPlan} targetPlan={targetPlan} />
|
||||
),
|
||||
title: 'subscription.downgrade_modal.title',
|
||||
confirmButtonText: 'subscription.downgrade_modal.downgrade',
|
||||
size: 'large',
|
||||
});
|
||||
|
||||
if (result) {
|
||||
await handleDownGrade(targetPlan);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Spacer />
|
||||
|
@ -32,6 +69,11 @@ function SwitchPlanActionBar({ currentSubscriptionPlanId, subscriptionPlans }: P
|
|||
type={isDowngrade ? 'default' : 'primary'}
|
||||
disabled={isCurrentPlan}
|
||||
onClick={async () => {
|
||||
if (isDowngrade) {
|
||||
await onDowngradeClick(planId);
|
||||
// eslint-disable-next-line no-useless-return
|
||||
return;
|
||||
}
|
||||
// Todo @xiaoyijun handle buy plan
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import { type TFuncKey } from 'i18next';
|
||||
|
||||
import { type SubscriptionPlanQuota } from '@/types/subscriptions';
|
||||
|
||||
export const quotaItemPhrasesMap: Record<
|
||||
keyof SubscriptionPlanQuota,
|
||||
TFuncKey<'translation', 'admin_console.subscription.quota_item'>
|
||||
> = {
|
||||
mauLimit: 'mau_limit.name',
|
||||
applicationsLimit: 'applications_limit.name',
|
||||
machineToMachineLimit: 'machine_to_machine_limit.name',
|
||||
resourcesLimit: 'resources_limit.name',
|
||||
scopesPerResourceLimit: 'scopes_per_resource_limit.name',
|
||||
customDomainEnabled: 'custom_domain_enabled.name',
|
||||
omniSignInEnabled: 'omni_sign_in_enabled.name',
|
||||
builtInEmailConnectorEnabled: 'built_in_email_connector_enabled.name',
|
||||
socialConnectorsLimit: 'social_connectors_limit.name',
|
||||
standardConnectorsLimit: 'standard_connectors_limit.name',
|
||||
rolesLimit: 'roles_limit.name',
|
||||
scopesPerRoleLimit: 'scopes_per_role_limit.name',
|
||||
hooksLimit: 'hooks_limit.name',
|
||||
auditLogsRetentionDays: 'audit_logs_retention_days.name',
|
||||
communitySupportEnabled: 'community_support_enabled.name',
|
||||
ticketSupportResponseTime: 'customer_ticket_support.name',
|
||||
};
|
||||
|
||||
export const quotaItemUnlimitedPhrasesMap: Record<
|
||||
keyof SubscriptionPlanQuota,
|
||||
TFuncKey<'translation', 'admin_console.subscription.quota_item'>
|
||||
> = {
|
||||
mauLimit: 'mau_limit.unlimited',
|
||||
applicationsLimit: 'applications_limit.unlimited',
|
||||
machineToMachineLimit: 'machine_to_machine_limit.unlimited',
|
||||
resourcesLimit: 'resources_limit.unlimited',
|
||||
scopesPerResourceLimit: 'scopes_per_resource_limit.unlimited',
|
||||
customDomainEnabled: 'custom_domain_enabled.unlimited',
|
||||
omniSignInEnabled: 'omni_sign_in_enabled.unlimited',
|
||||
builtInEmailConnectorEnabled: 'built_in_email_connector_enabled.unlimited',
|
||||
socialConnectorsLimit: 'social_connectors_limit.unlimited',
|
||||
standardConnectorsLimit: 'standard_connectors_limit.unlimited',
|
||||
rolesLimit: 'roles_limit.unlimited',
|
||||
scopesPerRoleLimit: 'scopes_per_role_limit.unlimited',
|
||||
hooksLimit: 'hooks_limit.unlimited',
|
||||
auditLogsRetentionDays: 'audit_logs_retention_days.unlimited',
|
||||
communitySupportEnabled: 'community_support_enabled.unlimited',
|
||||
ticketSupportResponseTime: 'customer_ticket_support.unlimited',
|
||||
};
|
||||
|
||||
export const quotaItemLimitedPhrasesMap: Record<
|
||||
keyof SubscriptionPlanQuota,
|
||||
TFuncKey<'translation', 'admin_console.subscription.quota_item'>
|
||||
> = {
|
||||
mauLimit: 'mau_limit.limited',
|
||||
applicationsLimit: 'applications_limit.limited',
|
||||
machineToMachineLimit: 'machine_to_machine_limit.limited',
|
||||
resourcesLimit: 'resources_limit.limited',
|
||||
scopesPerResourceLimit: 'scopes_per_resource_limit.limited',
|
||||
customDomainEnabled: 'custom_domain_enabled.limited',
|
||||
omniSignInEnabled: 'omni_sign_in_enabled.limited',
|
||||
builtInEmailConnectorEnabled: 'built_in_email_connector_enabled.limited',
|
||||
socialConnectorsLimit: 'social_connectors_limit.limited',
|
||||
standardConnectorsLimit: 'standard_connectors_limit.limited',
|
||||
rolesLimit: 'roles_limit.limited',
|
||||
scopesPerRoleLimit: 'scopes_per_role_limit.limited',
|
||||
hooksLimit: 'hooks_limit.limited',
|
||||
auditLogsRetentionDays: 'audit_logs_retention_days.limited',
|
||||
communitySupportEnabled: 'community_support_enabled.limited',
|
||||
ticketSupportResponseTime: 'customer_ticket_support.limited',
|
||||
};
|
||||
|
||||
export const quotaItemNotEligiblePhrasesMap: Record<
|
||||
keyof SubscriptionPlanQuota,
|
||||
TFuncKey<'translation', 'admin_console.subscription.quota_item'>
|
||||
> = {
|
||||
mauLimit: 'mau_limit.not_eligible',
|
||||
applicationsLimit: 'applications_limit.not_eligible',
|
||||
machineToMachineLimit: 'machine_to_machine_limit.not_eligible',
|
||||
resourcesLimit: 'resources_limit.not_eligible',
|
||||
scopesPerResourceLimit: 'scopes_per_resource_limit.not_eligible',
|
||||
customDomainEnabled: 'custom_domain_enabled.not_eligible',
|
||||
omniSignInEnabled: 'omni_sign_in_enabled.not_eligible',
|
||||
builtInEmailConnectorEnabled: 'built_in_email_connector_enabled.not_eligible',
|
||||
socialConnectorsLimit: 'social_connectors_limit.not_eligible',
|
||||
standardConnectorsLimit: 'standard_connectors_limit.not_eligible',
|
||||
rolesLimit: 'roles_limit.not_eligible',
|
||||
scopesPerRoleLimit: 'scopes_per_role_limit.not_eligible',
|
||||
hooksLimit: 'hooks_limit.not_eligible',
|
||||
auditLogsRetentionDays: 'audit_logs_retention_days.not_eligible',
|
||||
communitySupportEnabled: 'community_support_enabled.not_eligible',
|
||||
ticketSupportResponseTime: 'customer_ticket_support.not_eligible',
|
||||
};
|
|
@ -7,7 +7,7 @@ export enum ReservedPlanName {
|
|||
Enterprise = 'Enterprise',
|
||||
}
|
||||
|
||||
type SubscriptionPlanQuota = SubscriptionPlanResponse['quota'] & {
|
||||
export type SubscriptionPlanQuota = SubscriptionPlanResponse['quota'] & {
|
||||
communitySupportEnabled: boolean;
|
||||
ticketSupportResponseTime: number;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue