mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
fix(core,console): disable quota guard and unblock resource creation for pro tenants (#6487)
This commit is contained in:
parent
c3bec6803d
commit
a999c51919
26 changed files with 92 additions and 44 deletions
|
@ -51,7 +51,7 @@ function TabNavItem<Paths extends string>({
|
|||
)}
|
||||
</div>
|
||||
{errorCount > 0 && (
|
||||
<div className={styles.errors}>{t('general.tab_errors', { count: errorCount })}</div>
|
||||
<div className={styles.errors}>{t('general.tab_error', { count: errorCount })}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -62,7 +62,8 @@ function SsoCreationModal({ isOpen, onClose: rawOnClose }: Props) {
|
|||
const isSsoEnabled =
|
||||
!isCloud ||
|
||||
currentSubscriptionQuota.enterpriseSsoLimit === null ||
|
||||
currentSubscriptionQuota.enterpriseSsoLimit > 0;
|
||||
currentSubscriptionQuota.enterpriseSsoLimit > 0 ||
|
||||
planId === ReservedPlanId.Pro;
|
||||
|
||||
const { data, error } = useSWR<SsoConnectorProvidersResponse, RequestError>(
|
||||
'api/sso-connector-providers'
|
||||
|
|
|
@ -37,7 +37,7 @@ function EnterpriseSso() {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { isDevTenant } = useContext(TenantsContext);
|
||||
const {
|
||||
currentSubscription: { isAddOnAvailable },
|
||||
currentSubscription: { planId, isAddOnAvailable },
|
||||
currentSubscriptionQuota,
|
||||
} = useContext(SubscriptionDataContext);
|
||||
|
||||
|
@ -45,7 +45,8 @@ function EnterpriseSso() {
|
|||
page: 1,
|
||||
});
|
||||
|
||||
const isSsoEnabled = !isCloud || currentSubscriptionQuota.enterpriseSsoLimit !== 0;
|
||||
const isSsoEnabled =
|
||||
!isCloud || currentSubscriptionQuota.enterpriseSsoLimit !== 0 || planId === ReservedPlanId.Pro;
|
||||
|
||||
const url = buildUrl('api/sso-connectors', {
|
||||
page: String(page),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { MfaFactor, MfaPolicy, type SignInExperience } from '@logto/schemas';
|
||||
import { MfaFactor, MfaPolicy, ReservedPlanId, type SignInExperience } from '@logto/schemas';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -33,9 +33,13 @@ type Props = {
|
|||
};
|
||||
|
||||
function MfaForm({ data, onMfaUpdated }: Props) {
|
||||
const { currentSubscriptionQuota, mutateSubscriptionQuotaAndUsages } =
|
||||
useContext(SubscriptionDataContext);
|
||||
const isMfaDisabled = isCloud && !currentSubscriptionQuota.mfaEnabled;
|
||||
const {
|
||||
currentSubscription: { planId },
|
||||
currentSubscriptionQuota,
|
||||
mutateSubscriptionQuotaAndUsages,
|
||||
} = useContext(SubscriptionDataContext);
|
||||
const isMfaDisabled =
|
||||
isCloud && !currentSubscriptionQuota.mfaEnabled && planId !== ReservedPlanId.Pro;
|
||||
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
|
|
|
@ -17,10 +17,10 @@ type Props = {
|
|||
function PageWrapper({ children }: Props) {
|
||||
const { isDevTenant } = useContext(TenantsContext);
|
||||
const {
|
||||
currentSubscription: { isAddOnAvailable },
|
||||
currentSubscription: { planId, isAddOnAvailable },
|
||||
currentSubscriptionQuota: { mfaEnabled },
|
||||
} = useContext(SubscriptionDataContext);
|
||||
const isMfaEnabled = !isCloud || mfaEnabled;
|
||||
const isMfaEnabled = !isCloud || mfaEnabled || planId === ReservedPlanId.Pro;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
|
|
@ -32,9 +32,13 @@ const basePathname = '/organization-template';
|
|||
function OrganizationTemplate() {
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
const [isGuideDrawerOpen, setIsGuideDrawerOpen] = useState(false);
|
||||
const { currentSubscriptionQuota } = useContext(SubscriptionDataContext);
|
||||
const {
|
||||
currentSubscription: { planId },
|
||||
currentSubscriptionQuota,
|
||||
} = useContext(SubscriptionDataContext);
|
||||
const { isDevTenant } = useContext(TenantsContext);
|
||||
const isOrganizationsDisabled = isCloud && !currentSubscriptionQuota.organizationsEnabled;
|
||||
const isOrganizationsDisabled =
|
||||
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro;
|
||||
const { navigate } = useTenantPathname();
|
||||
|
||||
const handleUpgradePlan = useCallback(() => {
|
||||
|
|
|
@ -40,7 +40,8 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
|
|||
data: { organizationUpsellNoticeAcknowledged },
|
||||
update,
|
||||
} = useUserPreferences();
|
||||
const isOrganizationsDisabled = isCloud && !currentSubscriptionQuota.organizationsEnabled;
|
||||
const isOrganizationsDisabled =
|
||||
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro;
|
||||
|
||||
const {
|
||||
reset,
|
||||
|
|
|
@ -26,7 +26,7 @@ const organizationsPathname = '/organizations';
|
|||
function Organizations() {
|
||||
const { getDocumentationUrl } = useDocumentationUrl();
|
||||
const {
|
||||
currentSubscription: { isAddOnAvailable },
|
||||
currentSubscription: { planId, isAddOnAvailable },
|
||||
currentSubscriptionQuota,
|
||||
} = useContext(SubscriptionDataContext);
|
||||
const { isDevTenant } = useContext(TenantsContext);
|
||||
|
@ -34,7 +34,8 @@ function Organizations() {
|
|||
const { navigate } = useTenantPathname();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const isOrganizationsDisabled = isCloud && !currentSubscriptionQuota.organizationsEnabled;
|
||||
const isOrganizationsDisabled =
|
||||
isCloud && !currentSubscriptionQuota.organizationsEnabled && planId !== ReservedPlanId.Pro;
|
||||
|
||||
const upgradePlan = useCallback(() => {
|
||||
navigate(subscriptionPage);
|
||||
|
|
|
@ -14,11 +14,14 @@ import { type CloudConnectionLibrary } from './cloud-connection.js';
|
|||
|
||||
export type QuotaLibrary = ReturnType<typeof createQuotaLibrary>;
|
||||
|
||||
const shouldReportSubscriptionUpdates = (
|
||||
planId: string,
|
||||
key: keyof SubscriptionQuota,
|
||||
isAddOnAvailable?: boolean
|
||||
) => planId === ReservedPlanId.Pro && isAddOnAvailable && isReportSubscriptionUpdatesUsageKey(key);
|
||||
/**
|
||||
* @remarks
|
||||
* Should report usage changes to the Cloud only when the following conditions are met:
|
||||
* 1. The tenant is on the Pro plan.
|
||||
* 2. The usage key is add-on related usage key.
|
||||
*/
|
||||
const shouldReportSubscriptionUpdates = (planId: string, key: keyof SubscriptionQuota) =>
|
||||
planId === ReservedPlanId.Pro && isReportSubscriptionUpdatesUsageKey(key);
|
||||
|
||||
export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => {
|
||||
const guardTenantUsageByKey = async (key: keyof SubscriptionQuota) => {
|
||||
|
@ -36,13 +39,12 @@ export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => {
|
|||
|
||||
const {
|
||||
planId,
|
||||
isAddOnAvailable,
|
||||
quota: fullQuota,
|
||||
usage: fullUsage,
|
||||
} = await getTenantSubscriptionData(cloudConnection);
|
||||
|
||||
// Do not block Pro plan from adding add-on resources.
|
||||
if (shouldReportSubscriptionUpdates(planId, key, isAddOnAvailable)) {
|
||||
if (shouldReportSubscriptionUpdates(planId, key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -159,7 +161,7 @@ export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => {
|
|||
|
||||
const { planId, isAddOnAvailable } = await getTenantSubscriptionData(cloudConnection);
|
||||
|
||||
if (shouldReportSubscriptionUpdates(planId, key, isAddOnAvailable)) {
|
||||
if (shouldReportSubscriptionUpdates(planId, key) && isAddOnAvailable) {
|
||||
await reportSubscriptionUpdates(cloudConnection, key);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -57,6 +57,10 @@ export const reportSubscriptionUpdates = async (
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @remarks
|
||||
* Check whether the provided usage key is add-on related usage key.
|
||||
*/
|
||||
export const isReportSubscriptionUpdatesUsageKey = (
|
||||
value: string
|
||||
): value is ReportSubscriptionUpdatesUsageKey => {
|
||||
|
|
|
@ -73,7 +73,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -129,7 +129,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -220,7 +220,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
await expectSignInMethodError(page, 'Phone number');
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
|
||||
// Disable password option for sign-in method
|
||||
|
@ -234,7 +234,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
await expectSignInMethodError(page, 'Phone number');
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -285,7 +285,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
await expectSignInMethodError(page, 'Email address');
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
|
||||
// Disable password option for sign-in method
|
||||
|
@ -299,7 +299,7 @@ describe('sign-in experience(sad path): sign-up and sign-in', () => {
|
|||
await expectSignInMethodError(page, 'Email address');
|
||||
await expectErrorsOnNavTab(page, {
|
||||
tab: 'Sign-up and sign-in',
|
||||
error: '1 errors',
|
||||
error: '1 error',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Fortsetzen',
|
||||
page_info: '{{min, number}}-{{max, number}} von {{total, number}}',
|
||||
learn_more: 'Mehr erfahren',
|
||||
tab_errors: '{{count, number}} Fehler',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} Fehler',
|
||||
skip_for_now: 'Jetzt überspringen',
|
||||
remove: 'Entfernen',
|
||||
visit: 'Besuchen',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: 'Continue',
|
||||
page_info: '{{min, number}}-{{max, number}} of {{total, number}}',
|
||||
learn_more: 'Learn more',
|
||||
tab_errors: '{{count, number}} errors',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} errors',
|
||||
skip_for_now: 'Skip for now',
|
||||
remove: 'Remove',
|
||||
visit: 'Visit',
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Continuar',
|
||||
page_info: '{{min, number}}-{{max, number}} de {{total, number}}',
|
||||
learn_more: 'Saber más',
|
||||
tab_errors: '{{count, number}} errores',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} errores',
|
||||
skip_for_now: 'Omitir por ahora',
|
||||
remove: 'Eliminar',
|
||||
visit: 'Visitar',
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Continuez',
|
||||
page_info: '{{min, number}}-{{max, number}} de {{total, number}}',
|
||||
learn_more: 'En savoir plus',
|
||||
tab_errors: '{{count, number}} erreurs',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} erreurs',
|
||||
skip_for_now: 'Passer pour l`instant',
|
||||
remove: 'Supprimer',
|
||||
visit: 'Visiter',
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Continua',
|
||||
page_info: '{{min, number}}-{{max, number}} di {{total, number}}',
|
||||
learn_more: 'Scopri di più',
|
||||
tab_errors: '{{count, number}} errori',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} errori',
|
||||
skip_for_now: 'Salta per ora',
|
||||
remove: 'Rimuovi',
|
||||
visit: 'Visita',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: '続ける',
|
||||
page_info: '{{total}}件中{{min}}件〜{{max}}件を表示',
|
||||
learn_more: '詳しく見る',
|
||||
tab_errors: '{{count}}件のエラーがあります',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count}}件のエラーがあります',
|
||||
skip_for_now: '今回はスキップする',
|
||||
remove: '削除する',
|
||||
visit: '訪問する',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: '계속하기',
|
||||
page_info: '{{min, number}}-{{max, number}} / {{total, number}}',
|
||||
learn_more: '더 알아보기',
|
||||
tab_errors: '{{count, number}} 오류',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} 오류',
|
||||
skip_for_now: '지금은 건너뛰기',
|
||||
remove: '삭제',
|
||||
visit: '방문하기',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: 'Kontynuuj',
|
||||
page_info: '{{min, number}}-{{max, number}} z {{total, number}}',
|
||||
learn_more: 'Dowiedz się więcej',
|
||||
tab_errors: '{{count, number}} błędów',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} błędów',
|
||||
skip_for_now: 'Pomiń na teraz',
|
||||
remove: 'Usuń',
|
||||
visit: 'Odwiedź',
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Continuar',
|
||||
page_info: '{{min, number}}-{{max, number}} de {{total, number}}',
|
||||
learn_more: 'Saiba mais',
|
||||
tab_errors: '{{count, number}} erros',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} erros',
|
||||
skip_for_now: 'Pular por agora',
|
||||
remove: 'Remover',
|
||||
visit: 'Visitar',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: 'Continuar',
|
||||
page_info: '{{min, number}}-{{max, number}} de {{total, number}}',
|
||||
learn_more: 'Saber mais',
|
||||
tab_errors: '{{count, number}} erros',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} erros',
|
||||
skip_for_now: 'Saltar por agora',
|
||||
remove: 'Remover',
|
||||
visit: 'Visitar',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: 'Продолжить',
|
||||
page_info: '{{min, number}}-{{max, number}} из {{total, number}}',
|
||||
learn_more: 'Узнать больше',
|
||||
tab_errors: '{{count, number}} ошибок',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} ошибок',
|
||||
skip_for_now: 'Пропустить',
|
||||
remove: 'Удалить',
|
||||
visit: 'Посетить',
|
||||
|
|
|
@ -47,7 +47,9 @@ const general = {
|
|||
continue: 'Devam et',
|
||||
page_info: '{{min, number}}-{{max, number}} / {{total, number}}',
|
||||
learn_more: 'Daha fazla bilgi edinin',
|
||||
tab_errors: '{{count, number}} hata',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} hata',
|
||||
skip_for_now: 'Şimdilik atla',
|
||||
remove: 'Kaldır',
|
||||
visit: 'Ziyaret et',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: '继续',
|
||||
page_info: '{{min, number}}-{{max, number}} 共 {{total, number}} 条',
|
||||
learn_more: '了解更多',
|
||||
tab_errors: '{{count, number}} 个错误',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} 个错误',
|
||||
skip_for_now: '先跳过',
|
||||
remove: '移除',
|
||||
visit: '访问',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: '繼續',
|
||||
page_info: '{{min, number}}-{{max, number}} 共 {{total, number}} 條',
|
||||
learn_more: '了解更多',
|
||||
tab_errors: '{{count, number}} 個錯誤',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} 個錯誤',
|
||||
skip_for_now: '先跳過',
|
||||
remove: '移除',
|
||||
visit: '訪問',
|
||||
|
|
|
@ -46,7 +46,9 @@ const general = {
|
|||
continue: '繼續',
|
||||
page_info: '{{min, number}}-{{max, number}} 共 {{total, number}} 條',
|
||||
learn_more: '了解更多',
|
||||
tab_errors: '{{count, number}} 個錯誤',
|
||||
/** UNTRANSLATED */
|
||||
tab_error_one: '{{count, number}} error',
|
||||
tab_error_other: '{{count, number}} 個錯誤',
|
||||
skip_for_now: '先跳過',
|
||||
remove: '移除',
|
||||
visit: '訪問',
|
||||
|
|
Loading…
Add table
Reference in a new issue