From cfeb98c06f2ff82466741002f44259f3f1f31f82 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Mon, 8 Apr 2024 10:32:05 +0800 Subject: [PATCH] feat(console,core,phrases): add quota guard for cloud collaboration in console (#5644) --- .../connector-logto-email/package.json | 2 +- packages/console/package.json | 2 +- .../console/src/consts/quota-item-phrases.ts | 4 + packages/console/src/consts/tenants.ts | 1 + .../PlanComparisonTable/index.tsx | 33 ++++++--- .../Footer/index.module.scss | 16 ++++ .../InviteMemberModal/Footer/index.tsx | 74 +++++++++++++++++++ .../TenantMembers/InviteMemberModal/index.tsx | 37 +++------- .../TenantSettings/TenantMembers/hooks.ts | 66 +++++++++++++++++ .../TenantMembers/index.module.scss | 4 + .../TenantSettings/TenantMembers/index.tsx | 9 +++ packages/core/package.json | 2 +- packages/core/src/libraries/quota.ts | 1 + .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 7 ++ .../admin-console/subscription/quota-table.ts | 16 ++-- .../translation/admin-console/upsell/index.ts | 1 + .../admin-console/upsell/paywall.ts | 4 + .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../admin-console/subscription/quota-item.ts | 12 +++ .../admin-console/subscription/quota-table.ts | 22 ++++-- .../translation/admin-console/upsell/index.ts | 2 + .../admin-console/upsell/paywall.ts | 6 ++ .../schemas/src/types/logto-config/index.ts | 1 + pnpm-lock.yaml | 19 ++--- 75 files changed, 713 insertions(+), 174 deletions(-) create mode 100644 packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.module.scss create mode 100644 packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.tsx create mode 100644 packages/console/src/pages/TenantSettings/TenantMembers/hooks.ts diff --git a/packages/connectors/connector-logto-email/package.json b/packages/connectors/connector-logto-email/package.json index 10d2a7a91..8eeb46735 100644 --- a/packages/connectors/connector-logto-email/package.json +++ b/packages/connectors/connector-logto-email/package.json @@ -48,6 +48,6 @@ "access": "public" }, "devDependencies": { - "@logto/cloud": "0.2.5-1807f9c" + "@logto/cloud": "0.2.5-ab8a489" } } diff --git a/packages/console/package.json b/packages/console/package.json index b28527bfe..d9b9d59af 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -28,7 +28,7 @@ "@fontsource/roboto-mono": "^5.0.0", "@jest/types": "^29.5.0", "@logto/app-insights": "workspace:^1.4.0", - "@logto/cloud": "0.2.5-1807f9c", + "@logto/cloud": "0.2.5-ab8a489", "@logto/connector-kit": "workspace:^2.1.0", "@logto/core-kit": "workspace:^2.3.0", "@logto/language-kit": "workspace:^1.1.0", diff --git a/packages/console/src/consts/quota-item-phrases.ts b/packages/console/src/consts/quota-item-phrases.ts index 41ae37a20..a4ade8a7a 100644 --- a/packages/console/src/consts/quota-item-phrases.ts +++ b/packages/console/src/consts/quota-item-phrases.ts @@ -26,6 +26,7 @@ export const quotaItemPhrasesMap: Record< mfaEnabled: 'mfa_enabled.name', organizationsEnabled: 'organizations_enabled.name', ssoEnabled: 'sso_enabled.name', + tenantMembersLimit: 'tenant_members_limit.name', }; export const quotaItemUnlimitedPhrasesMap: Record< @@ -52,6 +53,7 @@ export const quotaItemUnlimitedPhrasesMap: Record< mfaEnabled: 'mfa_enabled.unlimited', organizationsEnabled: 'organizations_enabled.unlimited', ssoEnabled: 'sso_enabled.unlimited', + tenantMembersLimit: 'tenant_members_limit.unlimited', }; export const quotaItemLimitedPhrasesMap: Record< @@ -78,6 +80,7 @@ export const quotaItemLimitedPhrasesMap: Record< mfaEnabled: 'mfa_enabled.limited', organizationsEnabled: 'organizations_enabled.limited', ssoEnabled: 'sso_enabled.limited', + tenantMembersLimit: 'tenant_members_limit.limited', }; export const quotaItemNotEligiblePhrasesMap: Record< @@ -104,4 +107,5 @@ export const quotaItemNotEligiblePhrasesMap: Record< mfaEnabled: 'mfa_enabled.not_eligible', organizationsEnabled: 'organizations_enabled.not_eligible', ssoEnabled: 'sso_enabled.not_eligible', + tenantMembersLimit: 'tenant_members_limit.not_eligible', }; diff --git a/packages/console/src/consts/tenants.ts b/packages/console/src/consts/tenants.ts index fc54f4c98..119a2678b 100644 --- a/packages/console/src/consts/tenants.ts +++ b/packages/console/src/consts/tenants.ts @@ -66,6 +66,7 @@ export const defaultSubscriptionPlan: SubscriptionPlan = { ssoEnabled: true, ticketSupportResponseTime: 48, thirdPartyApplicationsLimit: null, + tenantMembersLimit: null, }, }; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx index d69291f2d..e7c362920 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx @@ -119,13 +119,15 @@ function PlanComparisonTable() { const orgPermissions = t('organizations.org_permissions'); const jitProvisioning = t('organizations.just_in_time_provisioning'); - // Audit logs - const auditLogRetention = t('audit_logs.retention'); + // Developers and platform + const webhooks = t('developers_and_platform.hooks'); + const auditLogRetention = t('developers_and_platform.audit_logs_retention'); const freePlanLogRetention = t('days', { count: freePlanAuditLogsRetentionDays }); const paidPlanLogRetention = t('days', { count: proPlanAuditLogsRetentionDays }); - - // Webhooks - const webhooks = t('hooks.hooks'); + const jwtClaims = t('developers_and_platform.jwt_claims'); + const tenantMembers = t('developers_and_platform.tenant_members'); + const tenantMembersLimit = t('included', { value: 3 }); + const tenantMembersPrice = t('per_member', { value: 8 }); // Compliance and support const community = t('support.community'); @@ -228,15 +230,21 @@ function PlanComparisonTable() { ], }, { - title: 'audit_logs.title', + title: 'developers_and_platform.title', rows: [ + { name: webhooks, data: ['1', '10', contact] }, { name: auditLogRetention, data: [freePlanLogRetention, paidPlanLogRetention, contact] }, + { name: jwtClaims, data: ['✓', '✓', '✓'] }, + { + name: tenantMembers, + data: [ + '1', + `${tenantMembersLimit}|${paidAddOnFeatureTip}|${tenantMembersPrice}`, + contact, + ], + }, ], }, - { - title: 'hooks.title', - rows: [{ name: webhooks, data: ['1', '10', contact] }], - }, { title: 'support.title', rows: [ @@ -275,8 +283,9 @@ function PlanComparisonTable() { - {data.map((value) => ( - + {data.map((value, index) => ( + // eslint-disable-next-line react/no-array-index-key + ))} diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.module.scss b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.module.scss new file mode 100644 index 000000000..c085bcdad --- /dev/null +++ b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.module.scss @@ -0,0 +1,16 @@ +@use '@/scss/underscore' as _; + +.container { + display: flex; + align-items: center; + gap: _.unit(6); + padding: _.unit(6); + background-color: var(--color-info-container); + margin: 0 _.unit(-6) _.unit(-6); + + .description { + flex: 1; + flex-shrink: 0; + font: var(--font-body-2); + } +} diff --git a/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.tsx b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.tsx new file mode 100644 index 000000000..dca3c0187 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/Footer/index.tsx @@ -0,0 +1,74 @@ +import { ReservedPlanId } from '@logto/schemas'; +import { useContext } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import ContactUsPhraseLink from '@/components/ContactUsPhraseLink'; +import QuotaGuardFooter from '@/components/QuotaGuardFooter'; +import { contactEmailLink } from '@/consts'; +import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; +import Button, { LinkButton } from '@/ds-components/Button'; + +import useTenantMembersUsage from '../../hooks'; + +import * as styles from './index.module.scss'; + +type Props = { + newInvitationCount?: number; + isLoading: boolean; + onSubmit: () => void; +}; + +function Footer({ newInvitationCount = 0, isLoading, onSubmit }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.paywall' }); + + const { currentPlan } = useContext(SubscriptionDataContext); + const { id: planId, quota } = currentPlan; + + const { hasTenantMembersReachedLimit, limit, usage } = useTenantMembersUsage(); + + if (planId === ReservedPlanId.Free && hasTenantMembersReachedLimit) { + return ( + + , + }} + > + {t('tenant_members')} + + + ); + } + + if ( + planId === ReservedPlanId.Development && + (hasTenantMembersReachedLimit || usage + newInvitationCount > limit) + ) { + // Display a custom "Contact us" footer instead of asking for upgrade + return ( +
+
+ {t('tenant_members_dev_plan', { limit: quota.tenantMembersLimit })} +
+ +
+ ); + } + + return ( +