0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00

refactor: move has{Reach,Surpass}Limit method to subscription context (#6664)

* refactor: move has{Reach,Surpass}Limit method to subscription context

* chore: update code
This commit is contained in:
Darcy Ye 2024-10-12 12:19:07 +08:00 committed by GitHub
parent c6f59cb0f8
commit 4dc1f82195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 152 additions and 190 deletions

View file

@ -6,7 +6,6 @@ import QuotaGuardFooter from '@/components/QuotaGuardFooter';
import SkuName from '@/components/SkuName'; import SkuName from '@/components/SkuName';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import Button from '@/ds-components/Button'; import Button from '@/ds-components/Button';
import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota';
type Props = { type Props = {
readonly isCreatingSocialConnector: boolean; readonly isCreatingSocialConnector: boolean;
@ -18,15 +17,11 @@ function Footer({ isCreatingSocialConnector, isCreateButtonDisabled, onClickCrea
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.paywall' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.paywall' });
const { const {
currentSubscription: { planId, isEnterprisePlan }, currentSubscription: { planId, isEnterprisePlan },
currentSubscriptionUsage,
currentSubscriptionQuota, currentSubscriptionQuota,
hasReachedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const isSocialConnectorsReachLimit = hasReachedSubscriptionQuotaLimit({ const isSocialConnectorsReachLimit = hasReachedSubscriptionQuotaLimit('socialConnectorsLimit');
quotaKey: 'socialConnectorsLimit',
usage: currentSubscriptionUsage.socialConnectorsLimit,
quota: currentSubscriptionQuota,
});
if (isCreatingSocialConnector && isSocialConnectorsReachLimit) { if (isCreatingSocialConnector && isSocialConnectorsReachLimit) {
return ( return (

View file

@ -2,12 +2,17 @@ import { conditional, joinPath } from '@silverhand/essentials';
import { useContext, useRef } from 'react'; import { useContext, useRef } from 'react';
import { Navigate, Outlet, useParams } from 'react-router-dom'; import { Navigate, Outlet, useParams } from 'react-router-dom';
import { type NewSubscriptionCountBasedUsage } from '@/cloud/types/router';
import AppLoading from '@/components/AppLoading'; import AppLoading from '@/components/AppLoading';
import Topbar from '@/components/Topbar'; import Topbar from '@/components/Topbar';
import { isCloud } from '@/consts/env'; import { isCloud } from '@/consts/env';
import SubscriptionDataProvider from '@/contexts/SubscriptionDataProvider'; import SubscriptionDataProvider from '@/contexts/SubscriptionDataProvider';
import useNewSubscriptionData from '@/contexts/SubscriptionDataProvider/use-new-subscription-data'; import useNewSubscriptionData from '@/contexts/SubscriptionDataProvider/use-new-subscription-data';
import useSubscriptionData from '@/contexts/SubscriptionDataProvider/use-subscription-data'; import useSubscriptionData from '@/contexts/SubscriptionDataProvider/use-subscription-data';
import {
hasSurpassedSubscriptionQuotaLimit,
hasReachedSubscriptionQuotaLimit,
} from '@/contexts/SubscriptionDataProvider/utils';
import { TenantsContext } from '@/contexts/TenantsProvider'; import { TenantsContext } from '@/contexts/TenantsProvider';
import useScroll from '@/hooks/use-scroll'; import useScroll from '@/hooks/use-scroll';
import useUserPreferences from '@/hooks/use-user-preferences'; import useUserPreferences from '@/hooks/use-user-preferences';
@ -42,9 +47,29 @@ export default function AppContent() {
return ( return (
<SubscriptionDataProvider <SubscriptionDataProvider
subscriptionData={{ subscriptionDataAndUtils={{
...subscriptionDta, ...subscriptionDta,
...newSubscriptionData, ...newSubscriptionData,
hasSurpassedSubscriptionQuotaLimit: <T extends keyof NewSubscriptionCountBasedUsage>(
quotaKey: T,
usage?: NewSubscriptionCountBasedUsage[T]
) =>
hasSurpassedSubscriptionQuotaLimit({
quotaKey,
usage,
subscriptionUsage: newSubscriptionData.currentSubscriptionUsage,
subscriptionQuota: newSubscriptionData.currentSubscriptionQuota,
}),
hasReachedSubscriptionQuotaLimit: <T extends keyof NewSubscriptionCountBasedUsage>(
quotaKey: T,
usage?: NewSubscriptionCountBasedUsage[T]
) =>
hasReachedSubscriptionQuotaLimit({
quotaKey,
usage,
subscriptionUsage: newSubscriptionData.currentSubscriptionUsage,
subscriptionQuota: newSubscriptionData.currentSubscriptionQuota,
}),
}} }}
> >
<div className={styles.app}> <div className={styles.app}>

View file

@ -32,16 +32,18 @@ export const SubscriptionDataContext = createContext<FullContext>({
currentSubscriptionRoleScopeUsage: {}, currentSubscriptionRoleScopeUsage: {},
mutateSubscriptionQuotaAndUsages: noop, mutateSubscriptionQuotaAndUsages: noop,
/* ==== For new pricing model ==== */ /* ==== For new pricing model ==== */
hasSurpassedSubscriptionQuotaLimit: () => false,
hasReachedSubscriptionQuotaLimit: () => false,
}); });
type Props = { type Props = {
readonly subscriptionData: FullContext; readonly subscriptionDataAndUtils: FullContext;
readonly children: ReactNode; readonly children: ReactNode;
}; };
function SubscriptionDataProvider({ children, subscriptionData }: Props) { function SubscriptionDataProvider({ children, subscriptionDataAndUtils }: Props) {
return ( return (
<SubscriptionDataContext.Provider value={subscriptionData}> <SubscriptionDataContext.Provider value={subscriptionDataAndUtils}>
{children} {children}
</SubscriptionDataContext.Provider> </SubscriptionDataContext.Provider>
); );

View file

@ -12,6 +12,13 @@ export type Context = {
onCurrentSubscriptionUpdated: (subscription?: Subscription) => void; onCurrentSubscriptionUpdated: (subscription?: Subscription) => void;
}; };
export type SubscriptionUsageOptions<T extends keyof NewSubscriptionCountBasedUsage> = {
quotaKey: T;
subscriptionUsage: NewSubscriptionCountBasedUsage;
subscriptionQuota: NewSubscriptionQuota;
usage?: NewSubscriptionCountBasedUsage[T];
};
type NewSubscriptionSupplementContext = { type NewSubscriptionSupplementContext = {
logtoSkus: LogtoSkuResponse[]; logtoSkus: LogtoSkuResponse[];
currentSku: LogtoSkuResponse; currentSku: LogtoSkuResponse;
@ -23,6 +30,19 @@ type NewSubscriptionSupplementContext = {
mutateSubscriptionQuotaAndUsages: () => void; mutateSubscriptionQuotaAndUsages: () => void;
}; };
type NewSubscriptionResourceStatus = {
hasSurpassedSubscriptionQuotaLimit: <T extends keyof NewSubscriptionCountBasedUsage>(
quotaKey: T,
usage?: NewSubscriptionCountBasedUsage[T]
) => boolean;
hasReachedSubscriptionQuotaLimit: <T extends keyof NewSubscriptionCountBasedUsage>(
quotaKey: T,
usage?: NewSubscriptionCountBasedUsage[T]
) => boolean;
};
export type NewSubscriptionContext = Context & NewSubscriptionSupplementContext; export type NewSubscriptionContext = Context & NewSubscriptionSupplementContext;
export type FullContext = Context & NewSubscriptionSupplementContext; export type FullContext = Context &
NewSubscriptionSupplementContext &
NewSubscriptionResourceStatus;

View file

@ -0,0 +1,43 @@
import { type NewSubscriptionCountBasedUsage } from '@/cloud/types/router';
import { isCloud } from '@/consts/env';
import { type SubscriptionUsageOptions } from './types';
/* === For new pricing model === */
const isSubscriptionUsageWithInLimit = <T extends keyof NewSubscriptionCountBasedUsage>(
{ quotaKey, subscriptionUsage, subscriptionQuota, usage }: SubscriptionUsageOptions<T>,
inclusive = true
) => {
// No limitations for OSS version
if (!isCloud) {
return true;
}
/**
* Sometimes we need to manually retrieve usage to overwrite the usage in subscriptionUsage.
* For example, for the usage of `scopesPerResourceLimit`, `subscriptionUsage.scopesPerResourceLimit` records the largest value among all resource scopes.
* However, when operating on resources in practice, we need to know the specific usage of scopes for the current resource. In this case, we need to manually calculate the value of scopes for the current resource before calling the function.
*/
const usageValue = usage ?? subscriptionUsage[quotaKey];
const quotaValue = subscriptionQuota[quotaKey];
// Unlimited
if (quotaValue === null) {
return true;
}
if (typeof quotaValue === 'boolean') {
return quotaValue;
}
return inclusive ? usageValue <= quotaValue : usageValue < quotaValue;
};
export const hasSurpassedSubscriptionQuotaLimit = <T extends keyof NewSubscriptionCountBasedUsage>(
options: SubscriptionUsageOptions<T>
) => !isSubscriptionUsageWithInLimit(options);
export const hasReachedSubscriptionQuotaLimit = <T extends keyof NewSubscriptionCountBasedUsage>(
options: SubscriptionUsageOptions<T>
) => !isSubscriptionUsageWithInLimit(options, false);
/* === For new pricing model === */

View file

@ -1,34 +1,14 @@
import { useContext, useMemo } from 'react'; import { useContext } from 'react';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import {
hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} from '@/utils/quota';
const useApiResourcesUsage = () => { const useApiResourcesUsage = () => {
const { currentSubscriptionQuota, currentSubscriptionUsage } = const { hasReachedSubscriptionQuotaLimit, hasSurpassedSubscriptionQuotaLimit } =
useContext(SubscriptionDataContext); useContext(SubscriptionDataContext);
const hasReachedLimit = useMemo( const hasReachedLimit = hasReachedSubscriptionQuotaLimit('resourcesLimit');
() =>
hasReachedSubscriptionQuotaLimit({
quotaKey: 'resourcesLimit',
usage: currentSubscriptionUsage.resourcesLimit,
quota: currentSubscriptionQuota,
}),
[currentSubscriptionQuota, currentSubscriptionUsage.resourcesLimit]
);
const hasSurpassedLimit = useMemo( const hasSurpassedLimit = hasSurpassedSubscriptionQuotaLimit('resourcesLimit');
() =>
hasSurpassedSubscriptionQuotaLimit({
quotaKey: 'resourcesLimit',
usage: currentSubscriptionUsage.resourcesLimit,
quota: currentSubscriptionQuota,
}),
[currentSubscriptionQuota, currentSubscriptionUsage.resourcesLimit]
);
return { return {
hasReachedLimit, hasReachedLimit,

View file

@ -1,54 +1,22 @@
import { useContext, useMemo } from 'react'; import { useContext } from 'react';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import {
hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} from '@/utils/quota';
const useApplicationsUsage = () => { const useApplicationsUsage = () => {
const { currentSubscriptionQuota, currentSubscriptionUsage } = const { hasReachedSubscriptionQuotaLimit, hasSurpassedSubscriptionQuotaLimit } =
useContext(SubscriptionDataContext); useContext(SubscriptionDataContext);
const hasMachineToMachineAppsReachedLimit = useMemo( const hasMachineToMachineAppsReachedLimit =
() => hasReachedSubscriptionQuotaLimit('machineToMachineLimit');
hasReachedSubscriptionQuotaLimit({
quotaKey: 'machineToMachineLimit', const hasMachineToMachineAppsSurpassedLimit =
usage: currentSubscriptionUsage.machineToMachineLimit, hasSurpassedSubscriptionQuotaLimit('machineToMachineLimit');
quota: currentSubscriptionQuota,
}), const hasThirdPartyAppsReachedLimit = hasReachedSubscriptionQuotaLimit(
[currentSubscriptionUsage.machineToMachineLimit, currentSubscriptionQuota] 'thirdPartyApplicationsLimit'
); );
const hasMachineToMachineAppsSurpassedLimit = useMemo( const hasAppsReachedLimit = hasReachedSubscriptionQuotaLimit('applicationsLimit');
() =>
hasSurpassedSubscriptionQuotaLimit({
quotaKey: 'machineToMachineLimit',
usage: currentSubscriptionUsage.machineToMachineLimit,
quota: currentSubscriptionQuota,
}),
[currentSubscriptionUsage.machineToMachineLimit, currentSubscriptionQuota]
);
const hasThirdPartyAppsReachedLimit = useMemo(
() =>
hasReachedSubscriptionQuotaLimit({
quotaKey: 'thirdPartyApplicationsLimit',
usage: currentSubscriptionUsage.thirdPartyApplicationsLimit,
quota: currentSubscriptionQuota,
}),
[currentSubscriptionUsage.thirdPartyApplicationsLimit, currentSubscriptionQuota]
);
const hasAppsReachedLimit = useMemo(
() =>
hasReachedSubscriptionQuotaLimit({
quotaKey: 'applicationsLimit',
usage: currentSubscriptionUsage.applicationsLimit,
quota: currentSubscriptionQuota,
}),
[currentSubscriptionUsage.applicationsLimit, currentSubscriptionQuota]
);
return { return {
hasMachineToMachineAppsReachedLimit, hasMachineToMachineAppsReachedLimit,

View file

@ -16,7 +16,6 @@ import TextInput from '@/ds-components/TextInput';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import modalStyles from '@/scss/modal.module.scss'; import modalStyles from '@/scss/modal.module.scss';
import { trySubmitSafe } from '@/utils/form'; import { trySubmitSafe } from '@/utils/form';
import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota';
type Props = { type Props = {
readonly resourceId: string; readonly resourceId: string;
@ -32,6 +31,7 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop
currentSubscriptionQuota, currentSubscriptionQuota,
currentSubscriptionResourceScopeUsage, currentSubscriptionResourceScopeUsage,
currentSubscription: { planId, isEnterprisePlan }, currentSubscription: { planId, isEnterprisePlan },
hasReachedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -57,11 +57,10 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop
}) })
); );
const isScopesPerResourceReachLimit = hasReachedSubscriptionQuotaLimit({ const isScopesPerResourceReachLimit = hasReachedSubscriptionQuotaLimit(
quotaKey: 'scopesPerResourceLimit', 'scopesPerResourceLimit',
usage: currentSubscriptionResourceScopeUsage[resourceId] ?? 0, currentSubscriptionResourceScopeUsage[resourceId] ?? 0
quota: currentSubscriptionQuota, );
});
return ( return (
<ReactModal <ReactModal

View file

@ -14,7 +14,6 @@ import FormField from '@/ds-components/FormField';
import ModalLayout from '@/ds-components/ModalLayout'; import ModalLayout from '@/ds-components/ModalLayout';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import modalStyles from '@/scss/modal.module.scss'; import modalStyles from '@/scss/modal.module.scss';
import { hasSurpassedSubscriptionQuotaLimit } from '@/utils/quota';
type Props = { type Props = {
readonly roleId: string; readonly roleId: string;
@ -28,6 +27,7 @@ function AssignPermissionsModal({ roleId, roleType, onClose }: Props) {
currentSubscription: { planId, isEnterprisePlan }, currentSubscription: { planId, isEnterprisePlan },
currentSubscriptionRoleScopeUsage, currentSubscriptionRoleScopeUsage,
currentSubscriptionQuota, currentSubscriptionQuota,
hasSurpassedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [scopes, setScopes] = useState<ScopeResponse[]>([]); const [scopes, setScopes] = useState<ScopeResponse[]>([]);
@ -52,11 +52,10 @@ function AssignPermissionsModal({ roleId, roleType, onClose }: Props) {
} }
}; };
const shouldBlockScopeAssignment = hasSurpassedSubscriptionQuotaLimit({ const shouldBlockScopeAssignment = hasSurpassedSubscriptionQuotaLimit(
quotaKey: 'scopesPerRoleLimit', 'scopesPerRoleLimit',
usage: (currentSubscriptionRoleScopeUsage[roleId] ?? 0) + scopes.length, (currentSubscriptionRoleScopeUsage[roleId] ?? 0) + scopes.length
quota: currentSubscriptionQuota, );
});
return ( return (
<ReactModal <ReactModal

View file

@ -1,4 +1,4 @@
import { RoleType } from '@logto/schemas'; import { RoleType, type ScopeResponse } from '@logto/schemas';
import { useContext } from 'react'; import { useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
@ -7,39 +7,31 @@ import QuotaGuardFooter from '@/components/QuotaGuardFooter';
import SkuName from '@/components/SkuName'; import SkuName from '@/components/SkuName';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import Button from '@/ds-components/Button'; import Button from '@/ds-components/Button';
import {
hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} from '@/utils/quota';
type Props = { type Props = {
readonly roleType: RoleType; readonly roleType: RoleType;
readonly scopes?: ScopeResponse[];
readonly isCreating: boolean; readonly isCreating: boolean;
readonly onClickCreate: () => void; readonly onClickCreate: () => void;
}; };
function Footer({ roleType, isCreating, onClickCreate }: Props) { function Footer({ roleType, scopes, isCreating, onClickCreate }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { const {
currentSubscription: { planId, isEnterprisePlan }, currentSubscription: { planId, isEnterprisePlan },
currentSubscriptionQuota, currentSubscriptionQuota,
currentSubscriptionUsage, hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const hasRoleReachedLimit = hasReachedSubscriptionQuotaLimit({ const hasRoleReachedLimit = hasReachedSubscriptionQuotaLimit(
quotaKey: roleType === RoleType.User ? 'userRolesLimit' : 'machineToMachineRolesLimit', roleType === RoleType.User ? 'userRolesLimit' : 'machineToMachineRolesLimit'
usage: );
roleType === RoleType.User
? currentSubscriptionUsage.userRolesLimit
: currentSubscriptionUsage.machineToMachineRolesLimit,
quota: currentSubscriptionQuota,
});
const hasScopesPerRoleSurpassedLimit = hasSurpassedSubscriptionQuotaLimit({ const hasScopesPerRoleSurpassedLimit = hasSurpassedSubscriptionQuotaLimit(
quotaKey: 'scopesPerRoleLimit', 'scopesPerRoleLimit',
usage: currentSubscriptionUsage.scopesPerRoleLimit, scopes?.length ?? 0
quota: currentSubscriptionQuota, );
});
if (hasRoleReachedLimit || hasScopesPerRoleSurpassedLimit) { if (hasRoleReachedLimit || hasScopesPerRoleSurpassedLimit) {
return ( return (

View file

@ -77,7 +77,12 @@ function CreateRoleForm({ onClose }: Props) {
}} }}
size="large" size="large"
footer={ footer={
<Footer roleType={watch('type')} isCreating={isSubmitting} onClickCreate={onSubmit} /> <Footer
roleType={watch('type')}
scopes={watch('scopes')}
isCreating={isSubmitting}
onClickCreate={onSubmit}
/>
} }
onClose={onClose} onClose={onClose}
> >

View file

@ -19,7 +19,6 @@ import TextLink from '@/ds-components/TextLink';
import { useConfirmModal } from '@/hooks/use-confirm-modal'; import { useConfirmModal } from '@/hooks/use-confirm-modal';
import useUserPreferences from '@/hooks/use-user-preferences'; import useUserPreferences from '@/hooks/use-user-preferences';
import modalStyles from '@/scss/modal.module.scss'; import modalStyles from '@/scss/modal.module.scss';
import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota';
import { isPaidPlan } from '@/utils/subscription'; import { isPaidPlan } from '@/utils/subscription';
import InviteEmailsInput from '../InviteEmailsInput'; import InviteEmailsInput from '../InviteEmailsInput';
@ -44,9 +43,8 @@ function InviteMemberModal({ isOpen, onClose }: Props) {
const { show } = useConfirmModal(); const { show } = useConfirmModal();
const { const {
currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan },
currentSubscriptionQuota,
currentSubscriptionUsage: { tenantMembersLimit },
mutateSubscriptionQuotaAndUsages, mutateSubscriptionQuotaAndUsages,
hasReachedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const { const {
data: { tenantMembersUpsellNoticeAcknowledged }, data: { tenantMembersUpsellNoticeAcknowledged },
@ -82,11 +80,7 @@ function InviteMemberModal({ isOpen, onClose }: Props) {
[t] [t]
); );
const hasTenantMembersReachedLimit = hasReachedSubscriptionQuotaLimit({ const hasTenantMembersReachedLimit = hasReachedSubscriptionQuotaLimit('tenantMembersLimit');
quotaKey: 'tenantMembersLimit',
usage: tenantMembersLimit,
quota: currentSubscriptionQuota,
});
const onSubmit = handleSubmit(async ({ emails, role }) => { const onSubmit = handleSubmit(async ({ emails, role }) => {
if (role === TenantRole.Admin) { if (role === TenantRole.Admin) {

View file

@ -1,38 +1,22 @@
import { useContext, useMemo } from 'react'; import { useContext, useMemo } from 'react';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import {
hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} from '@/utils/quota';
const useTenantMembersUsage = () => { const useTenantMembersUsage = () => {
const { currentSubscriptionUsage, currentSubscriptionQuota } = const {
useContext(SubscriptionDataContext); currentSubscriptionUsage,
currentSubscriptionQuota,
hasReachedSubscriptionQuotaLimit,
hasSurpassedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext);
const usage = useMemo(() => { const usage = useMemo(() => {
return currentSubscriptionUsage.tenantMembersLimit; return currentSubscriptionUsage.tenantMembersLimit;
}, [currentSubscriptionUsage.tenantMembersLimit]); }, [currentSubscriptionUsage.tenantMembersLimit]);
const hasTenantMembersReachedLimit = useMemo( const hasTenantMembersReachedLimit = hasReachedSubscriptionQuotaLimit('tenantMembersLimit');
() =>
hasReachedSubscriptionQuotaLimit({
quotaKey: 'tenantMembersLimit',
quota: currentSubscriptionQuota,
usage: currentSubscriptionUsage.tenantMembersLimit,
}),
[currentSubscriptionQuota, currentSubscriptionUsage.tenantMembersLimit]
);
const hasTenantMembersSurpassedLimit = useMemo( const hasTenantMembersSurpassedLimit = hasSurpassedSubscriptionQuotaLimit('tenantMembersLimit');
() =>
hasSurpassedSubscriptionQuotaLimit({
quotaKey: 'tenantMembersLimit',
quota: currentSubscriptionQuota,
usage: currentSubscriptionUsage.tenantMembersLimit,
}),
[currentSubscriptionQuota, currentSubscriptionUsage.tenantMembersLimit]
);
return { return {
hasTenantMembersReachedLimit, hasTenantMembersReachedLimit,

View file

@ -12,7 +12,6 @@ import Button from '@/ds-components/Button';
import ModalLayout from '@/ds-components/ModalLayout'; import ModalLayout from '@/ds-components/ModalLayout';
import useApi from '@/hooks/use-api'; import useApi from '@/hooks/use-api';
import { trySubmitSafe } from '@/utils/form'; import { trySubmitSafe } from '@/utils/form';
import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota';
type Props = { type Props = {
readonly onClose: (createdHook?: Hook) => void; readonly onClose: (createdHook?: Hook) => void;
@ -28,16 +27,12 @@ type CreateHookPayload = Pick<CreateHook, 'name'> & {
function CreateForm({ onClose }: Props) { function CreateForm({ onClose }: Props) {
const { const {
currentSubscription: { planId, isEnterprisePlan }, currentSubscription: { planId, isEnterprisePlan },
currentSubscriptionQuota,
currentSubscriptionUsage, currentSubscriptionUsage,
hasReachedSubscriptionQuotaLimit,
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const shouldBlockCreation = hasReachedSubscriptionQuotaLimit({ const shouldBlockCreation = hasReachedSubscriptionQuotaLimit('hooksLimit');
quotaKey: 'hooksLimit',
usage: currentSubscriptionUsage.hooksLimit,
quota: currentSubscriptionQuota,
});
const formMethods = useForm<BasicWebhookFormType>(); const formMethods = useForm<BasicWebhookFormType>();
const { const {

View file

@ -1,39 +0,0 @@
import { type NewSubscriptionQuota } from '@/cloud/types/router';
import { isCloud } from '@/consts/env';
/* === For new pricing model === */
type SubscriptionUsageOptions = {
quotaKey: keyof NewSubscriptionQuota;
usage: number;
quota: NewSubscriptionQuota;
};
const isSubscriptionUsageWithInLimit = (
{ quotaKey, usage, quota }: SubscriptionUsageOptions,
inclusive = true
) => {
// No limitations for OSS version
if (!isCloud) {
return true;
}
const quotaValue = quota[quotaKey];
// Unlimited
if (quotaValue === null) {
return true;
}
if (typeof quotaValue === 'boolean') {
return quotaValue;
}
return inclusive ? usage <= quotaValue : usage < quotaValue;
};
export const hasSurpassedSubscriptionQuotaLimit = (options: SubscriptionUsageOptions) =>
!isSubscriptionUsageWithInLimit(options);
export const hasReachedSubscriptionQuotaLimit = (options: SubscriptionUsageOptions) =>
!isSubscriptionUsageWithInLimit(options, false);
/* === For new pricing model === */