From c368c2799aab39edbb8d406bf618b660d294df61 Mon Sep 17 00:00:00 2001 From: Darcy Ye Date: Wed, 11 Sep 2024 20:26:39 +0800 Subject: [PATCH] refactor: update display, quota guard and usage report logic for enterprise users (#6565) * refactor: update display, quota guard and usage report logic for enterprise users * chore: undo logto email connector dependency update * chore: use contact us button for pro plan when currently on enterprise plan --- packages/console/package.json | 2 +- .../CreateForm/Footer/index.tsx | 6 ++++-- .../src/components/FeatureTag/index.tsx | 7 ++++++- .../src/components/PlanDescription/index.tsx | 1 + .../PlanUsage/PlanUsageCard/index.module.scss | 4 ++++ .../PlanUsage/PlanUsageCard/index.tsx | 6 +++++- .../src/components/PlanUsage/index.tsx | 15 ++++++++----- .../TenantDropdownItem/index.tsx | 7 ++++--- packages/console/src/consts/tenants.ts | 1 + .../components/CreateForm/Footer.tsx | 6 ++++-- .../EnterpriseSso/SsoCreationModal/index.tsx | 10 ++++++--- .../CreateOrganizationModal/index.tsx | 10 ++++++--- .../TenantSettings/BillingHistory/index.tsx | 15 ++++++++++++- .../Subscription/CurrentPlan/index.tsx | 18 ++++++++++------ .../SwitchPlanActionBar/index.tsx | 19 +++++++++++++++-- .../TenantMembers/InviteMemberModal/index.tsx | 6 ++++-- packages/console/src/utils/subscription.ts | 4 ++++ packages/core/package.json | 2 +- packages/core/src/libraries/quota.ts | 21 ++++++++++++------- packages/core/src/utils/subscription/index.ts | 12 ++++++----- .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 2 ++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ .../admin-console/subscription/index.ts | 3 +++ packages/schemas/src/consts/subscriptions.ts | 1 + pnpm-lock.yaml | 19 +++++++++++++---- 38 files changed, 190 insertions(+), 49 deletions(-) diff --git a/packages/console/package.json b/packages/console/package.json index 6a92be7bd..01a0ce381 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -27,7 +27,7 @@ "devDependencies": { "@fontsource/roboto-mono": "^5.0.0", "@jest/types": "^29.5.0", - "@logto/cloud": "0.2.5-582d792", + "@logto/cloud": "0.2.5-20fd0a2", "@logto/connector-kit": "workspace:^4.0.0", "@logto/core-kit": "workspace:^2.5.0", "@logto/elements": "workspace:^0.0.0", diff --git a/packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx b/packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx index 580729698..342dbb6d6 100644 --- a/packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx +++ b/packages/console/src/components/ApplicationCreation/CreateForm/Footer/index.tsx @@ -13,6 +13,7 @@ import Button from '@/ds-components/Button'; import TextLink from '@/ds-components/TextLink'; import useApplicationsUsage from '@/hooks/use-applications-usage'; import useUserPreferences from '@/hooks/use-user-preferences'; +import { isPaidPlan } from '@/utils/subscription'; import styles from './index.module.scss'; @@ -26,7 +27,7 @@ type Props = { function Footer({ selectedType, isLoading, onClickCreate, isThirdParty }: Props) { const { currentSku, - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscriptionQuota, } = useContext(SubscriptionDataContext); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell' }); @@ -45,7 +46,8 @@ function Footer({ selectedType, isLoading, onClickCreate, isThirdParty }: Props) selectedType === ApplicationType.MachineToMachine && isAddOnAvailable && hasMachineToMachineAppsReachedLimit && - planId === ReservedPlanId.Pro && + // Just in case the enterprise plan has reached the resource limit, we still need to show charge notice. + isPaidPlan(planId, isEnterprisePlan) && !m2mUpsellNoticeAcknowledged ) { return ( diff --git a/packages/console/src/components/FeatureTag/index.tsx b/packages/console/src/components/FeatureTag/index.tsx index 764eea2d1..04814275f 100644 --- a/packages/console/src/components/FeatureTag/index.tsx +++ b/packages/console/src/components/FeatureTag/index.tsx @@ -80,9 +80,14 @@ type CombinedAddOnAndFeatureTagProps = { export function CombinedAddOnAndFeatureTag(props: CombinedAddOnAndFeatureTagProps) { const { hasAddOnTag, className, paywall } = props; const { - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, } = useContext(SubscriptionDataContext); + // We believe that the enterprise plan has already allocated sufficient resource quotas in the deal negotiation, so there is no need for upselling, nor will it trigger the add-on tag prompt. + if (isEnterprisePlan) { + return null; + } + // Show the "Add-on" tag for Pro plan when dev features enabled. if (hasAddOnTag && isAddOnAvailable && isCloud && planId === ReservedPlanId.Pro) { return ( diff --git a/packages/console/src/components/PlanDescription/index.tsx b/packages/console/src/components/PlanDescription/index.tsx index 1db3e0785..96d35edac 100644 --- a/packages/console/src/components/PlanDescription/index.tsx +++ b/packages/console/src/components/PlanDescription/index.tsx @@ -10,6 +10,7 @@ const registeredPlanDescriptionPhrasesMap: Record< > = { [ReservedPlanId.Free]: 'free_plan_description', [ReservedPlanId.Pro]: 'pro_plan_description', + [ReservedPlanId.Enterprise]: 'enterprise_description', }; type Props = { diff --git a/packages/console/src/components/PlanUsage/PlanUsageCard/index.module.scss b/packages/console/src/components/PlanUsage/PlanUsageCard/index.module.scss index 00bfcdd64..d3e078369 100644 --- a/packages/console/src/components/PlanUsage/PlanUsageCard/index.module.scss +++ b/packages/console/src/components/PlanUsage/PlanUsageCard/index.module.scss @@ -31,6 +31,10 @@ .usageTip { font: var(--font-body-2); color: var(--color-text-secondary); + + &.hidden { + display: none; + } } .tag { diff --git a/packages/console/src/components/PlanUsage/PlanUsageCard/index.tsx b/packages/console/src/components/PlanUsage/PlanUsageCard/index.tsx index d44bdeaba..1af08a91b 100644 --- a/packages/console/src/components/PlanUsage/PlanUsageCard/index.tsx +++ b/packages/console/src/components/PlanUsage/PlanUsageCard/index.tsx @@ -22,6 +22,7 @@ export type Props = { readonly titleKey: AdminConsoleKey; readonly tooltipKey?: AdminConsoleKey; readonly unitPrice: number; + readonly isUsageTipHidden: boolean; readonly className?: string; }; @@ -32,6 +33,7 @@ function PlanUsageCard({ usageKey, titleKey, tooltipKey, + isUsageTipHidden, className, }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); @@ -75,7 +77,9 @@ function PlanUsageCard({ > , + span: ( + + ), }} > {t(usageKey, { diff --git a/packages/console/src/components/PlanUsage/index.tsx b/packages/console/src/components/PlanUsage/index.tsx index 946f1b6dc..6dc5e5b34 100644 --- a/packages/console/src/components/PlanUsage/index.tsx +++ b/packages/console/src/components/PlanUsage/index.tsx @@ -22,13 +22,16 @@ function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) { const { currentSubscriptionQuota, currentSubscriptionUsage, - currentSubscription: currentSubscriptionFromNewPricingModel, + currentSubscription: { + currentPeriodStart, + currentPeriodEnd, + planId, + isAddOnAvailable, + isEnterprisePlan, + }, } = useContext(SubscriptionDataContext); const { currentTenant } = useContext(TenantsContext); - const { currentPeriodStart, currentPeriodEnd, planId, isAddOnAvailable } = - currentSubscriptionFromNewPricingModel; - const periodicUsage = useMemo( () => rawPeriodicUsage ?? @@ -71,6 +74,8 @@ function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) { ...cond( (key === 'tokenLimit' || key === 'mauLimit') && { quota: currentSubscriptionQuota[key] } ), + // Hide usage tip for Enterprise plan. + isUsageTipHidden: isEnterprisePlan, })); return ( @@ -83,7 +88,7 @@ function PlanUsage({ periodicUsage: rawPeriodicUsage }: Props) { periodStart: currentPeriodStart, periodEnd: currentPeriodEnd, }), - renewDate: dayjs(currentPeriodEnd).add(1, 'day').format('MMM D, YYYY'), + renewDate: dayjs(currentPeriodEnd).format('MMM D, YYYY'), }} /> diff --git a/packages/console/src/components/Topbar/TenantSelector/TenantDropdownItem/index.tsx b/packages/console/src/components/Topbar/TenantSelector/TenantDropdownItem/index.tsx index c697bec64..947de5b6c 100644 --- a/packages/console/src/components/Topbar/TenantSelector/TenantDropdownItem/index.tsx +++ b/packages/console/src/components/Topbar/TenantSelector/TenantDropdownItem/index.tsx @@ -1,4 +1,4 @@ -import { TenantTag } from '@logto/schemas'; +import { TenantTag, ReservedPlanId } from '@logto/schemas'; import classNames from 'classnames'; import { useContext, useMemo } from 'react'; @@ -23,7 +23,7 @@ function TenantDropdownItem({ tenantData, isSelected, onClick }: Props) { const { name, tag, - subscription: { planId }, + subscription: { planId, isEnterprisePlan }, } = tenantData; const { logtoSkus } = useContext(SubscriptionDataContext); @@ -31,6 +31,7 @@ function TenantDropdownItem({ tenantData, isSelected, onClick }: Props) { () => logtoSkus.find(({ id }) => id === planId), [logtoSkus, planId] ); + const skuId = isEnterprisePlan ? ReservedPlanId.Enterprise : planId; if (!tenantSubscriptionSku) { return null; @@ -48,7 +49,7 @@ function TenantDropdownItem({ tenantData, isSelected, onClick }: Props) { {tag === TenantTag.Development ? ( ) : ( - + )} diff --git a/packages/console/src/consts/tenants.ts b/packages/console/src/consts/tenants.ts index 3ef691431..5024bb2c4 100644 --- a/packages/console/src/consts/tenants.ts +++ b/packages/console/src/consts/tenants.ts @@ -31,6 +31,7 @@ export const defaultTenantResponse: TenantResponse = { planId: defaultSubscriptionPlanId, currentPeriodStart: dayjs().toDate(), currentPeriodEnd: dayjs().add(1, 'month').toDate(), + isEnterprisePlan: false, }, usage: { activeUsers: 0, diff --git a/packages/console/src/pages/ApiResources/components/CreateForm/Footer.tsx b/packages/console/src/pages/ApiResources/components/CreateForm/Footer.tsx index 6e927b9cd..2b7906280 100644 --- a/packages/console/src/pages/ApiResources/components/CreateForm/Footer.tsx +++ b/packages/console/src/pages/ApiResources/components/CreateForm/Footer.tsx @@ -13,6 +13,7 @@ import Button from '@/ds-components/Button'; import TextLink from '@/ds-components/TextLink'; import useApiResourcesUsage from '@/hooks/use-api-resources-usage'; import useUserPreferences from '@/hooks/use-user-preferences'; +import { isPaidPlan } from '@/utils/subscription'; import styles from './index.module.scss'; @@ -24,7 +25,7 @@ type Props = { function Footer({ isCreationLoading, onClickCreate }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscriptionUsage: { resourcesLimit }, currentSku, } = useContext(SubscriptionDataContext); @@ -60,7 +61,8 @@ function Footer({ isCreationLoading, onClickCreate }: Props) { if ( isAddOnAvailable && hasReachedLimit && - planId === ReservedPlanId.Pro && + // Just in case the enterprise plan has reached the resource limit, we still need to show charge notice. + isPaidPlan(planId, isEnterprisePlan) && !apiResourceUpsellNoticeAcknowledged ) { return ( diff --git a/packages/console/src/pages/EnterpriseSso/SsoCreationModal/index.tsx b/packages/console/src/pages/EnterpriseSso/SsoCreationModal/index.tsx index dc78727a6..f22f8f6cc 100644 --- a/packages/console/src/pages/EnterpriseSso/SsoCreationModal/index.tsx +++ b/packages/console/src/pages/EnterpriseSso/SsoCreationModal/index.tsx @@ -31,6 +31,7 @@ import useApi, { type RequestError } from '@/hooks/use-api'; import useUserPreferences from '@/hooks/use-user-preferences'; import modalStyles from '@/scss/modal.module.scss'; import { trySubmitSafe } from '@/utils/form'; +import { isPaidPlan } from '@/utils/subscription'; import SsoConnectorRadioGroup from './SsoConnectorRadioGroup'; import styles from './index.module.scss'; @@ -50,7 +51,7 @@ const duplicateConnectorNameErrorCode = 'single_sign_on.duplicate_connector_name function SsoCreationModal({ isOpen, onClose: rawOnClose }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscriptionQuota, } = useContext(SubscriptionDataContext); const { @@ -160,7 +161,8 @@ function SsoCreationModal({ isOpen, onClose: rawOnClose }: Props) { footer={ conditional( isAddOnAvailable && - planId === ReservedPlanId.Pro && + // Just in case the enterprise plan has reached the resource limit, we still need to show charge notice. + isPaidPlan(planId, isEnterprisePlan) && !enterpriseSsoUpsellNoticeAcknowledged && ( {t('upsell.add_on.footer.enterprise_sso', { price: enterpriseSsoAddOnUnitPrice, - planName: t('subscription.pro_plan'), + planName: t( + isEnterprisePlan ? 'subscription.enterprise' : 'subscription.pro_plan' + ), })} diff --git a/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx b/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx index 8cedab786..e1b5a23c5 100644 --- a/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx +++ b/packages/console/src/pages/Organizations/CreateOrganizationModal/index.tsx @@ -21,6 +21,7 @@ import useApi from '@/hooks/use-api'; import useUserPreferences from '@/hooks/use-user-preferences'; import modalStyles from '@/scss/modal.module.scss'; import { trySubmitSafe } from '@/utils/form'; +import { isPaidPlan } from '@/utils/subscription'; import styles from './index.module.scss'; @@ -33,7 +34,7 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) { const api = useApi(); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscriptionQuota, } = useContext(SubscriptionDataContext); const { @@ -85,7 +86,8 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) { footer={ cond( isAddOnAvailable && - planId === ReservedPlanId.Pro && + // Just in case the enterprise plan has reached the resource limit, we still need to show charge notice. + isPaidPlan(planId, isEnterprisePlan) && !organizationUpsellNoticeAcknowledged && ( {t('upsell.add_on.footer.organization', { price: organizationAddOnUnitPrice, - planName: t('subscription.pro_plan'), + planName: t( + isEnterprisePlan ? 'subscription.enterprise' : 'subscription.pro_plan' + ), })} diff --git a/packages/console/src/pages/TenantSettings/BillingHistory/index.tsx b/packages/console/src/pages/TenantSettings/BillingHistory/index.tsx index 416f6d861..4bde6724d 100644 --- a/packages/console/src/pages/TenantSettings/BillingHistory/index.tsx +++ b/packages/console/src/pages/TenantSettings/BillingHistory/index.tsx @@ -1,3 +1,4 @@ +import { ReservedPlanId } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import dayjs from 'dayjs'; import { useCallback, useContext, useMemo } from 'react'; @@ -50,7 +51,19 @@ function BillingHistory() { { title: , dataIndex: 'planName', - render: ({ skuId, periodStart, periodEnd }) => { + render: ({ skuId: rawSkuId, periodStart, periodEnd }) => { + /** + * @remarks + * The `skuId` should be either ReservedPlanId.Dev, ReservedPlanId.Pro, ReservedPlanId.Admin, ReservedPlanId.Free, or a random string. + * Except for the random string, which corresponds to the custom enterprise plan, other `skuId` values correspond to specific Reserved Plans. + */ + const skuId = + rawSkuId && + // eslint-disable-next-line no-restricted-syntax + (Object.values(ReservedPlanId).includes(rawSkuId as ReservedPlanId) + ? rawSkuId + : ReservedPlanId.Enterprise); + return ( currentSubscription.upcomingInvoice?.subtotal ?? currentSku.unitPrice ?? 0, - [currentSku.unitPrice, currentSubscription.upcomingInvoice] + () => upcomingInvoice?.subtotal ?? unitPrice ?? 0, + [unitPrice, upcomingInvoice] ); if (!periodicUsage) { @@ -53,10 +59,10 @@ function CurrentPlan({ periodicUsage: rawPeriodicUsage }: Props) {
- +
- +
@@ -65,7 +71,7 @@ function CurrentPlan({ periodicUsage: rawPeriodicUsage }: Props) { - {currentSubscription.isAddOnAvailable && ( + {isAddOnAvailable && !isEnterprisePlan && ( )} (); @@ -118,7 +122,14 @@ function SwitchPlanActionBar({ onSubscriptionUpdated, currentSkuId, logtoSkus }: const isCurrentSku = currentSkuId === skuId; const isDowngrade = isDowngradePlan(currentSkuId, skuId); - return ( + // Let user contact us for Pro plan when they are currently on Enterprise plan. + return isEnterprisePlan && skuId === ReservedPlanId.Pro ? ( + + ) : ( diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx index d4f07a230..c69d59c17 100644 --- a/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx +++ b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx @@ -20,6 +20,7 @@ import { useConfirmModal } from '@/hooks/use-confirm-modal'; import useUserPreferences from '@/hooks/use-user-preferences'; import modalStyles from '@/scss/modal.module.scss'; import { hasReachedSubscriptionQuotaLimit } from '@/utils/quota'; +import { isPaidPlan } from '@/utils/subscription'; import InviteEmailsInput from '../InviteEmailsInput'; import useEmailInputUtils from '../InviteEmailsInput/hooks'; @@ -42,7 +43,7 @@ function InviteMemberModal({ isOpen, onClose }: Props) { const { parseEmailOptions } = useEmailInputUtils(); const { show } = useConfirmModal(); const { - currentSubscription: { planId, isAddOnAvailable }, + currentSubscription: { planId, isAddOnAvailable, isEnterprisePlan }, currentSubscriptionQuota, currentSubscriptionUsage: { tenantMembersLimit }, mutateSubscriptionQuotaAndUsages, @@ -138,7 +139,8 @@ function InviteMemberModal({ isOpen, onClose }: Props) { conditional( isAddOnAvailable && hasTenantMembersReachedLimit && - planId === ReservedPlanId.Pro && + // Just in case the enterprise plan has reached the resource limit, we still need to show charge notice. + isPaidPlan(planId, isEnterprisePlan) && !tenantMembersUpsellNoticeAcknowledged && ( logtoSkus.filter(({ id }) => featuredPlanIds.includes(id)); + +export const isPaidPlan = (planId: string, isEnterprisePlan: boolean) => + planId === ReservedPlanId.Pro || isEnterprisePlan; diff --git a/packages/core/package.json b/packages/core/package.json index 8abf83039..511c484db 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -97,7 +97,7 @@ "zod": "^3.23.8" }, "devDependencies": { - "@logto/cloud": "0.2.5-582d792", + "@logto/cloud": "0.2.5-20fd0a2", "@silverhand/eslint-config": "6.0.1", "@silverhand/ts-config": "6.0.0", "@types/adm-zip": "^0.5.5", diff --git a/packages/core/src/libraries/quota.ts b/packages/core/src/libraries/quota.ts index 3c4716d06..1209a3709 100644 --- a/packages/core/src/libraries/quota.ts +++ b/packages/core/src/libraries/quota.ts @@ -17,11 +17,15 @@ export type QuotaLibrary = ReturnType; /** * @remarks * Should report usage changes to the Cloud only when the following conditions are met: - * 1. The tenant is on the Pro plan. + * 1. The tenant is either on Pro plan or Enterprise plan. * 2. The usage key is add-on related usage key. */ -const shouldReportSubscriptionUpdates = (planId: string, key: keyof SubscriptionQuota) => - planId === ReservedPlanId.Pro && isReportSubscriptionUpdatesUsageKey(key); +const shouldReportSubscriptionUpdates = ( + planId: string, + isEnterprisePlan: boolean, + key: keyof SubscriptionQuota +) => + (planId === ReservedPlanId.Pro || isEnterprisePlan) && isReportSubscriptionUpdatesUsageKey(key); export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => { const guardTenantUsageByKey = async (key: keyof SubscriptionUsage) => { @@ -41,10 +45,11 @@ export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => { planId, quota: fullQuota, usage: fullUsage, + isEnterprisePlan, } = await getTenantSubscriptionData(cloudConnection); - // Do not block Pro plan from adding add-on resources. - if (shouldReportSubscriptionUpdates(planId, key)) { + // Do not block Pro/Enterprise plan from adding add-on resources. + if (shouldReportSubscriptionUpdates(planId, isEnterprisePlan, key)) { return; } @@ -159,9 +164,11 @@ export const createQuotaLibrary = (cloudConnection: CloudConnectionLibrary) => { return; } - const { planId, isAddOnAvailable } = await getTenantSubscriptionData(cloudConnection); + const { planId, isAddOnAvailable, isEnterprisePlan } = await getTenantSubscriptionData( + cloudConnection + ); - if (shouldReportSubscriptionUpdates(planId, key) && isAddOnAvailable) { + if (shouldReportSubscriptionUpdates(planId, isEnterprisePlan, key) && isAddOnAvailable) { await reportSubscriptionUpdates(cloudConnection, key); } }; diff --git a/packages/core/src/utils/subscription/index.ts b/packages/core/src/utils/subscription/index.ts index 34f5416ff..d53a59520 100644 --- a/packages/core/src/utils/subscription/index.ts +++ b/packages/core/src/utils/subscription/index.ts @@ -23,6 +23,7 @@ export const getTenantSubscriptionData = async ( cloudConnection: CloudConnectionLibrary ): Promise<{ planId: string; + isEnterprisePlan: boolean; isAddOnAvailable?: boolean; quota: SubscriptionQuota; usage: SubscriptionUsage; @@ -30,12 +31,13 @@ export const getTenantSubscriptionData = async ( roles: Record; }> => { const client = await cloudConnection.getClient(); - const [{ planId, isAddOnAvailable }, { quota, usage, resources, roles }] = await Promise.all([ - client.get('/api/tenants/my/subscription'), - client.get('/api/tenants/my/subscription-usage'), - ]); + const [{ planId, isAddOnAvailable, isEnterprisePlan }, { quota, usage, resources, roles }] = + await Promise.all([ + client.get('/api/tenants/my/subscription'), + client.get('/api/tenants/my/subscription-usage'), + ]); - return { planId, isAddOnAvailable, quota, usage, resources, roles }; + return { planId, isEnterprisePlan, isAddOnAvailable, quota, usage, resources, roles }; }; export const reportSubscriptionUpdates = async ( diff --git a/packages/phrases/src/locales/ar/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ar/translation/admin-console/subscription/index.ts index 70a79f365..08943b5d0 100644 --- a/packages/phrases/src/locales/ar/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ar/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan: 'خطة Pro', pro_plan_description: 'للاستفادة من الأعمال بدون قلق مع Logto.', enterprise: 'الشركات', + /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', current_plan: 'الخطة الحالية', current_plan_description: 'هذه هي الخطة الحالية الخاصة بك. يمكنك بسهولة رؤية استخدام الخطة الخاصة بك ، والتحقق من فاتورتك القادمة ، وإجراء التغييرات على الخطة حسب الحاجة.', diff --git a/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts index 5050e91a0..2a624a788 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: 'Für Unternehmen, die sorgenfrei von Logto profitieren möchten.', enterprise: 'Unternehmen', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts index 84d95b940..5f49e721e 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts @@ -8,6 +8,8 @@ const subscription = { pro_plan: 'Pro plan', pro_plan_description: 'For businesses benefit worry-free with Logto.', enterprise: 'Enterprise', + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', admin_plan: 'Admin plan', dev_plan: 'Development plan', current_plan: 'Current plan', diff --git a/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts index 987be9e7b..e771bbc76 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Benefíciese sin preocupaciones con Logto para empresas.', enterprise: 'Empresa', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts index 79338d7a0..36c8f5269 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Pour les entreprises qui bénéficient de Logto sans soucis.', enterprise: 'Entreprise', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts index 6d9116bad..1d9f06d9a 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Per aziende che beneficiano di Logto senza preoccupazioni.', enterprise: 'Azienda', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts index 7821ef9f3..2e3b3a785 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'ビジネスが安心してLogtoを利用できるプランです。', enterprise: 'エンタープライズ', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts index 2313f18e3..3aa271ba7 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: 'Logto와 함께 걱정 없이 비즈니스 혜택을 받으세요.', enterprise: '기업용', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts index 00fea6ebe..d1a53a618 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Dla firm, ciesz się bezstresową obsługą Logto.', enterprise: 'Przedsiębiorstwo', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts index 9ace00cbf..75009d105 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Para empresas se beneficiarem tranquilo com o Logto.', enterprise: 'Empresa', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts index b62f1ca38..1064da065 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: 'Para empresas que desejam se beneficiar sem preocupações com o Logto.', enterprise: 'Empresa', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts index 8120b0a7c..b0ecd6d85 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: 'Позволяет бизнесу использовать Logto без забот.', enterprise: 'Enterprise', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts index 4444a6791..82928ae94 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts @@ -9,6 +9,9 @@ const subscription = { pro_plan_description: "Endişesiz bir şekilde Logto'dan faydalanan işletmeler için.", enterprise: 'Kurumsal', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts index a51a37723..2599292f6 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: '适用于企业付费无忧。', enterprise: '企业', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts index 6a59c5e9b..eaab53f32 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: '供企業放心使用Logto。', enterprise: '企業', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts index d912ea3aa..a2ffa3063 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts @@ -8,6 +8,9 @@ const subscription = { pro_plan_description: '企業無憂享受 Logto 服務。', enterprise: '企業版', /** UNTRANSLATED */ + enterprise_description: + 'For large-scale organizations requiring advanced features, full customization, and dedicated support to power mission-critical applications. Tailored to your needs for ultimate security, compliance, and performance.', + /** UNTRANSLATED */ admin_plan: 'Admin plan', /** UNTRANSLATED */ dev_plan: 'Development plan', diff --git a/packages/schemas/src/consts/subscriptions.ts b/packages/schemas/src/consts/subscriptions.ts index 0161b7c94..5071b2fe5 100644 --- a/packages/schemas/src/consts/subscriptions.ts +++ b/packages/schemas/src/consts/subscriptions.ts @@ -18,6 +18,7 @@ export enum ReservedPlanId { */ Hobby = 'hobby', Pro = 'pro', + Enterprise = 'enterprise', /** * @deprecated * Should not use this plan ID, we only use this tag as a record for the legacy `pro` plan since we will rename the `hobby` plan to be `pro`. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 782a9d5d9..a189d24d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2568,8 +2568,8 @@ importers: specifier: ^29.5.0 version: 29.5.0 '@logto/cloud': - specifier: 0.2.5-582d792 - version: 0.2.5-582d792(zod@3.23.8) + specifier: 0.2.5-20fd0a2 + version: 0.2.5-20fd0a2(zod@3.23.8) '@logto/connector-kit': specifier: workspace:^4.0.0 version: link:../toolkit/connector-kit @@ -3064,8 +3064,8 @@ importers: version: 3.23.8 devDependencies: '@logto/cloud': - specifier: 0.2.5-582d792 - version: 0.2.5-582d792(zod@3.23.8) + specifier: 0.2.5-20fd0a2 + version: 0.2.5-20fd0a2(zod@3.23.8) '@silverhand/eslint-config': specifier: 6.0.1 version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3) @@ -5571,6 +5571,10 @@ packages: '@logto/client@2.7.2': resolution: {integrity: sha512-jsmuDl9QpXfR3uLEMPE67tvYoL5XcjJi+4yGqucYPjd4GH6SUHp3N9skk8C/OyygnKDPLY+ttwD0LaIbpGvn+Q==} + '@logto/cloud@0.2.5-20fd0a2': + resolution: {integrity: sha512-j0f2RDpi/OEI59WXKnih7QeFSywNFV91PkulZdmcGa8HCRNmht94siw+LILzheg6bzwfvHU/aN4tJYL1/Px1BA==} + engines: {node: ^20.9.0} + '@logto/cloud@0.2.5-582d792': resolution: {integrity: sha512-0fIZzqwyjQguTS0a5+XbgVZlGEB/MXIf6pbuBDkHh6JHlMTJ/XH041rWX+e+nMk5N7/Xk2XXS+d2RJUWumnmpw==} engines: {node: ^20.9.0} @@ -15240,6 +15244,13 @@ snapshots: camelcase-keys: 7.0.2 jose: 5.6.3 + '@logto/cloud@0.2.5-20fd0a2(zod@3.23.8)': + dependencies: + '@silverhand/essentials': 2.9.1 + '@withtyped/server': 0.14.0(zod@3.23.8) + transitivePeerDependencies: + - zod + '@logto/cloud@0.2.5-582d792(zod@3.23.8)': dependencies: '@silverhand/essentials': 2.9.1