From a5ef0d014e8c6e5f6357300cb0b6731bc9249c05 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Tue, 19 Dec 2023 10:10:32 +0800 Subject: [PATCH] refactor(console): update plan comparison table (#5115) --- packages/console/src/consts/env.ts | 2 +- packages/console/src/consts/plan-quotas.ts | 26 ++- .../PlanQuotaGroupKeyLabel/index.tsx | 0 .../PlanQuotaKeyLabel/index.tsx | 21 ++- .../index.module.scss | 58 +++--- .../PlanComparisonTable/index.tsx | 83 +++++++++ .../renderers/BasePrice.tsx | 27 +++ .../renderers/GenericFeatureFlag.tsx | 40 ++++ .../renderers/GenericQuotaLimit.tsx | 60 ++++++ .../renderers/MauUnitPrices.tsx | 36 ++++ .../QuotaValueWrapper/index.module.scss | 8 + .../renderers/QuotaValueWrapper/index.tsx | 29 +++ .../PlanComparisonTable/renderers/index.tsx | 155 +++++++++++++++ .../utils.ts | 2 + .../Subscription/PlanQuotaTable/index.tsx | 176 ------------------ .../TenantSettings/Subscription/index.tsx | 4 +- packages/console/src/types/subscriptions.ts | 12 +- 17 files changed, 518 insertions(+), 221 deletions(-) rename packages/console/src/pages/TenantSettings/Subscription/{PlanQuotaTable => PlanComparisonTable}/PlanQuotaGroupKeyLabel/index.tsx (100%) rename packages/console/src/pages/TenantSettings/Subscription/{PlanQuotaTable => PlanComparisonTable}/PlanQuotaKeyLabel/index.tsx (69%) rename packages/console/src/pages/TenantSettings/Subscription/{PlanQuotaTable => PlanComparisonTable}/index.module.scss (57%) create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/BasePrice.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericFeatureFlag.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericQuotaLimit.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/MauUnitPrices.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.module.scss create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.tsx create mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/index.tsx rename packages/console/src/pages/TenantSettings/Subscription/{PlanQuotaTable => PlanComparisonTable}/utils.ts (97%) delete mode 100644 packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.tsx diff --git a/packages/console/src/consts/env.ts b/packages/console/src/consts/env.ts index 171500e55..7af761d2a 100644 --- a/packages/console/src/consts/env.ts +++ b/packages/console/src/consts/env.ts @@ -4,6 +4,6 @@ import { yes } from '@silverhand/essentials'; export const isProduction = process.env.NODE_ENV === 'production'; export const isCloud = yes(process.env.IS_CLOUD); export const adminEndpoint = process.env.ADMIN_ENDPOINT; -// eslint-disable-next-line unicorn/prevent-abbreviations, import/no-unused-modules -- we love dev +// eslint-disable-next-line unicorn/prevent-abbreviations -- we love dev export const isDevFeaturesEnabled = !isProduction || yes(process.env.DEV_FEATURES_ENABLED) || yes(process.env.INTEGRATION_TEST); diff --git a/packages/console/src/consts/plan-quotas.ts b/packages/console/src/consts/plan-quotas.ts index bdaa63837..f96120fb9 100644 --- a/packages/console/src/consts/plan-quotas.ts +++ b/packages/console/src/consts/plan-quotas.ts @@ -1,4 +1,5 @@ import { ReservedPlanId } from '@logto/schemas'; +import { type Nullable, condArray } from '@silverhand/essentials'; import { type SubscriptionPlanTable, @@ -9,6 +10,8 @@ import { type SubscriptionPlanQuota, } from '@/types/subscriptions'; +import { isDevFeaturesEnabled as isDevelopmentFeaturesEnabled } from './env'; + type EnabledFeatureMap = Record; export const customCssEnabledMap: EnabledFeatureMap = { @@ -77,6 +80,13 @@ export const ticketSupportResponseTimeMap: Record = [ReservedPlanId.Pro]: 48, }; +// @Todo @xiaoyijun [Pricing] read token limit from backend when it's ready +export const tokenLimitMap: Record> = { + [ReservedPlanId.Free]: 500_000, + [ReservedPlanId.Hobby]: 1_000_000, + [ReservedPlanId.Pro]: 1_000_000, +}; + /** * Note: this is only for display purpose. * @@ -96,7 +106,8 @@ const enterprisePlanTable: SubscriptionPlanTable = { appLogoAndFaviconEnabled: true, darkModeEnabled: true, i18nEnabled: true, - mfaEnabled: true, + // Todo @xiaoyijun [Pricing] Remove feature flag + mfaEnabled: isDevelopmentFeaturesEnabled ? undefined : true, omniSignInEnabled: true, passwordSignInEnabled: true, passwordlessSignInEnabled: true, @@ -111,8 +122,10 @@ const enterprisePlanTable: SubscriptionPlanTable = { hooksLimit: undefined, communitySupportEnabled: true, ticketSupportResponseTime: undefined, - organizationsEnabled: true, - ssoEnabled: true, + // Todo @xiaoyijun [Pricing] Remove feature flag + organizationsEnabled: isDevelopmentFeaturesEnabled ? undefined : true, + // Todo @xiaoyijun [Pricing] Remove feature flag + ssoEnabled: isDevelopmentFeaturesEnabled ? undefined : true, }; /** @@ -125,7 +138,12 @@ export const enterprisePlanTableData: SubscriptionPlanTableData = { }; export const planTableGroupKeyMap: SubscriptionPlanTableGroupKeyMap = Object.freeze({ - [SubscriptionPlanTableGroupKey.base]: ['basePrice', 'mauUnitPrice', 'mauLimit'], + [SubscriptionPlanTableGroupKey.base]: [ + 'basePrice', + // TODO @xiaoyijun [Pricing] remove feature flag + ...condArray(!isDevelopmentFeaturesEnabled && 'mauUnitPrice'), + 'mauLimit', + ], [SubscriptionPlanTableGroupKey.applications]: ['applicationsLimit', 'machineToMachineLimit'], [SubscriptionPlanTableGroupKey.resources]: ['resourcesLimit', 'scopesPerResourceLimit'], [SubscriptionPlanTableGroupKey.branding]: [ diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/PlanQuotaGroupKeyLabel/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/PlanQuotaGroupKeyLabel/index.tsx similarity index 100% rename from packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/PlanQuotaGroupKeyLabel/index.tsx rename to packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/PlanQuotaGroupKeyLabel/index.tsx diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/PlanQuotaKeyLabel/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/PlanQuotaKeyLabel/index.tsx similarity index 69% rename from packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/PlanQuotaKeyLabel/index.tsx rename to packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/PlanQuotaKeyLabel/index.tsx index 8a904949c..4f8b543c7 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/PlanQuotaKeyLabel/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/PlanQuotaKeyLabel/index.tsx @@ -1,17 +1,22 @@ +import { cond, type Nullable } from '@silverhand/essentials'; import { type TFuncKey } from 'i18next'; +import { isDevFeaturesEnabled } from '@/consts/env'; import DynamicT from '@/ds-components/DynamicT'; import { type SubscriptionPlanTable } from '@/types/subscriptions'; const planQuotaKeyPhraseMap: { - [key in keyof Required]: TFuncKey< - 'translation', - 'admin_console.subscription.quota_table' + [key in keyof Required]: Nullable< + TFuncKey<'translation', 'admin_console.subscription.quota_table'> >; } = { basePrice: 'quota.base_price', mauUnitPrice: 'quota.mau_unit_price', mauLimit: 'quota.mau_limit', + /** + * Token limit is required in the plan quota table but we don't display it as a row data. + */ + tokenLimit: null, applicationsLimit: 'application.total', machineToMachineLimit: 'application.m2m', resourcesLimit: 'resource.resource_count', @@ -37,7 +42,12 @@ const planQuotaKeyPhraseMap: { auditLogsRetentionDays: 'audit_logs.retention', communitySupportEnabled: 'support.community', ticketSupportResponseTime: 'support.customer_ticket', - organizationsEnabled: 'organizations.organizations', + /** + * Todo @xiaoyijun [Pricing] Remove feature flag + */ + organizationsEnabled: isDevFeaturesEnabled + ? 'organizations.monthly_active_organization' + : 'organizations.organizations', }; type Props = { @@ -45,7 +55,8 @@ type Props = { }; function PlanQuotaKeyLabel({ quotaKey }: Props) { - return ; + const phraseKey = planQuotaKeyPhraseMap[quotaKey]; + return cond(phraseKey && ) ?? <>-; } export default PlanQuotaKeyLabel; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.module.scss b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.module.scss similarity index 57% rename from packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.module.scss rename to packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.module.scss index ed096be4e..8f998785d 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.module.scss +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.module.scss @@ -1,39 +1,50 @@ @use '@/scss/underscore' as _; - .container { padding: _.unit(3); border-radius: 16px; background-color: var(--color-layer-1); margin-bottom: _.unit(3); - .table { - tbody tr td { - border: none; - text-align: center; + table { + border: none; + border-spacing: 0; + table-layout: fixed; + width: 100%; - &:first-child { - border-radius: 6px 0 0 6px; - } - - &:last-child { - border-radius: 0 6px 6px 0; - } - } - - .headerTable { + thead { background-color: var(--color-layer-light); border-radius: 6px; + padding: 0 _.unit(3); - thead tr th { + tr th { font: var(--font-title-2); - text-align: center; + padding: _.unit(3); + + &:first-child { + border-radius: 6px 0 0 6px; + } + + &:last-child { + border-radius: 0 6px 6px 0; + } } } - .bodyTableWrapper { - border-radius: unset; - padding: 0; + tbody { + tr td { + font: var(--font-body-2); + text-align: center; + padding: _.unit(3); + + &:first-child { + border-radius: 6px 0 0 6px; + } + + &:last-child { + border-radius: 0 6px 6px 0; + } + } .groupLabel { font: var(--font-title-2); @@ -48,12 +59,6 @@ .colorRow { background-color: var(--color-layer-light); } - - .ticketSupport { - display: flex; - align-items: center; - justify-content: center; - } } } @@ -64,4 +69,3 @@ font-style: italic; } } - diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx new file mode 100644 index 000000000..714f49a93 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/index.tsx @@ -0,0 +1,83 @@ +import { cond } from '@silverhand/essentials'; +import { Fragment, useMemo } from 'react'; + +import PlanName from '@/components/PlanName'; +import { isDevFeaturesEnabled } from '@/consts/env'; +import { enterprisePlanTableData, planTableGroupKeyMap } from '@/consts/plan-quotas'; +import DynamicT from '@/ds-components/DynamicT'; +import { type SubscriptionPlanTableGroupKey, type SubscriptionPlan } from '@/types/subscriptions'; + +import PlanQuotaGroupKeyLabel from './PlanQuotaGroupKeyLabel'; +import PlanQuotaKeyLabel from './PlanQuotaKeyLabel'; +import * as styles from './index.module.scss'; +import { quotaValueRenderer } from './renderers'; +import { constructPlanTableDataArray } from './utils'; + +type Props = { + subscriptionPlans: SubscriptionPlan[]; +}; + +function PlanComparisonTable({ subscriptionPlans }: Props) { + const planTableDataArray = useMemo( + () => [ + ...constructPlanTableDataArray(subscriptionPlans), + // Note: enterprise plan table data is not included in the subscription plans, and it's only for display + enterprisePlanTableData, + ], + [subscriptionPlans] + ); + + return ( +
+ + + + + ))} + + + + {Object.entries(planTableGroupKeyMap).map(([groupKey, quotaKeys]) => ( + + + + + {quotaKeys.map((quotaKey, index) => ( + + + {planTableDataArray.map((tableData) => ( + + ))} + + ))} + + ))} + +
+ {planTableDataArray.map(({ name }) => ( + + +
+ {/* eslint-disable-next-line no-restricted-syntax */} + +
+ + + {quotaValueRenderer[quotaKey](tableData)} +
+ {cond( + // Todo @xiaoyijun [Pricing] Remove feature flag + !isDevFeaturesEnabled && ( +
+ +
+ ) + )} +
+ ); +} + +export default PlanComparisonTable; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/BasePrice.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/BasePrice.tsx new file mode 100644 index 000000000..249fa6790 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/BasePrice.tsx @@ -0,0 +1,27 @@ +import DynamicT from '@/ds-components/DynamicT'; + +import QuotaValueWrapper from './QuotaValueWrapper'; + +type Props = { + value?: string; +}; + +function BasePrice({ value }: Props) { + if (value === undefined) { + return ; + } + + /** + * `basePrice` is a string value representing the price in cents, we need to convert the value from cents to dollars. + */ + return ( + + + + ); +} + +export default BasePrice; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericFeatureFlag.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericFeatureFlag.tsx new file mode 100644 index 000000000..57e7fb2f4 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericFeatureFlag.tsx @@ -0,0 +1,40 @@ +import { cond } from '@silverhand/essentials'; +import { type TFuncKey } from 'i18next'; + +import Success from '@/assets/icons/success.svg'; +import DynamicT from '@/ds-components/DynamicT'; + +import QuotaValueWrapper from './QuotaValueWrapper'; + +type Props = { + isEnabled?: boolean; + isBeta?: boolean; + tipPhraseKey?: TFuncKey<'translation', 'admin_console.subscription.quota_table'>; + paymentType?: 'add-on' | 'usage'; +}; + +function GenericFeatureFlag({ isEnabled, isBeta, tipPhraseKey, paymentType }: Props) { + if (isEnabled === undefined) { + return ; + } + + return ( + )} + > + {isEnabled + ? cond(!isBeta && ) ?? ( + + ) + : '-'} + + ); +} + +export default GenericFeatureFlag; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericQuotaLimit.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericQuotaLimit.tsx new file mode 100644 index 000000000..08e77642b --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/GenericQuotaLimit.tsx @@ -0,0 +1,60 @@ +import { cond, type Nullable } from '@silverhand/essentials'; +import { type TFuncKey } from 'i18next'; + +import Success from '@/assets/icons/success.svg'; +import DynamicT from '@/ds-components/DynamicT'; + +import QuotaValueWrapper from './QuotaValueWrapper'; + +type Props = { + quota?: Nullable; + tipPhraseKey?: TFuncKey<'translation', 'admin_console.subscription.quota_table'>; + tipInterpolation?: Record; + hasCheckmark?: boolean; + formatter?: (quota: number) => string; +}; + +function GenericQuotaLimit({ + quota, + tipPhraseKey, + tipInterpolation, + hasCheckmark, + formatter, +}: Props) { + if (quota === undefined) { + return ; + } + + const tipContent = cond( + tipPhraseKey && ( + + ) + ); + + if (quota === null) { + return ( + + {hasCheckmark && } + + + ); + } + + return ( + + {quota === 0 ? ( + '-' + ) : ( + <> + {hasCheckmark && } + {formatter?.(quota) ?? quota.toLocaleString()} + + )} + + ); +} + +export default GenericQuotaLimit; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/MauUnitPrices.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/MauUnitPrices.tsx new file mode 100644 index 000000000..ebdf06d77 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/MauUnitPrices.tsx @@ -0,0 +1,36 @@ +import DynamicT from '@/ds-components/DynamicT'; + +type Props = { + prices?: string[]; +}; + +/** + * @deprecated + * The unit price is an array of string representing the price in cents. + * Each string represents the price of a tier. + * TODO: @xiaoyijun [Pricing] Remove the unit price after the new pricing feature is ready. + */ +function MauUnitPrices({ prices }: Props) { + console.log(prices); + if (prices === undefined) { + return ; + } + + return prices.length === 0 ? ( +
-
+ ) : ( +
+ {prices.map((value, index) => ( +
+ + +
+ ))} +
+ ); +} + +export default MauUnitPrices; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.module.scss b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.module.scss new file mode 100644 index 000000000..99f2a7540 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.module.scss @@ -0,0 +1,8 @@ +@use '@/scss/underscore' as _; + +.quotaValue { + display: flex; + align-items: center; + justify-content: center; + gap: _.unit(2); +} diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.tsx new file mode 100644 index 000000000..f1dd3f2af --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/QuotaValueWrapper/index.tsx @@ -0,0 +1,29 @@ +import { type ReactNode } from 'react'; + +import Tip from '@/assets/icons/tip.svg'; +import IconButton from '@/ds-components/IconButton'; +import { ToggleTip } from '@/ds-components/Tip'; + +import * as styles from './index.module.scss'; + +type Props = { + children: ReactNode; + tip?: ReactNode; +}; + +function QuotaValueWrapper({ children, tip }: Props) { + return ( +
+ {children} + {tip && ( + + + + + + )} +
+ ); +} + +export default QuotaValueWrapper; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/index.tsx new file mode 100644 index 000000000..012bbc507 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/renderers/index.tsx @@ -0,0 +1,155 @@ +import { ReservedPlanId } from '@logto/schemas'; +import { cond } from '@silverhand/essentials'; +import { t } from 'i18next'; +import { type ReactNode } from 'react'; + +import { isDevFeaturesEnabled } from '@/consts/env'; +import { type SubscriptionPlanTable, type SubscriptionPlanTableData } from '@/types/subscriptions'; + +import BasePrice from './BasePrice'; +import GenericFeatureFlag from './GenericFeatureFlag'; +import GenericQuotaLimit from './GenericQuotaLimit'; +import MauUnitPrices from './MauUnitPrices'; + +export const quotaValueRenderer: Record< + keyof SubscriptionPlanTable, + (planTableData: SubscriptionPlanTableData) => ReactNode +> = { + // Base + basePrice: ({ table: { basePrice } }) => , + mauUnitPrice: ({ table: { mauUnitPrice } }) => , + tokenLimit: () =>
, // Dummy: We don't display token limit as an item in the plan comparison table. + mauLimit: ({ id, table: { tokenLimit, mauLimit } }) => ( + + ), + // Applications + applicationsLimit: ({ table: { applicationsLimit } }) => ( + + ), + machineToMachineLimit: ({ id, table: { machineToMachineLimit } }) => ( + + ), + // Resources + resourcesLimit: ({ id, table: { resourcesLimit } }) => ( + + ), + scopesPerResourceLimit: ({ table: { scopesPerResourceLimit } }) => ( + + ), + // Branding + customDomainEnabled: ({ table: { customDomainEnabled } }) => ( + + ), + customCssEnabled: ({ table: { customCssEnabled } }) => ( + + ), + appLogoAndFaviconEnabled: ({ table: { appLogoAndFaviconEnabled } }) => ( + + ), + darkModeEnabled: ({ table: { darkModeEnabled } }) => ( + + ), + i18nEnabled: ({ table: { i18nEnabled } }) => , + // UserAuthentication + mfaEnabled: ({ table: { mfaEnabled } }) => ( + + ), + omniSignInEnabled: ({ table: { omniSignInEnabled } }) => ( + + ), + passwordSignInEnabled: ({ table: { passwordSignInEnabled } }) => ( + + ), + passwordlessSignInEnabled: ({ table: { passwordlessSignInEnabled } }) => ( + + ), + emailConnectorsEnabled: ({ table: { emailConnectorsEnabled } }) => ( + + ), + smsConnectorsEnabled: ({ table: { smsConnectorsEnabled } }) => ( + + ), + socialConnectorsLimit: ({ table: { socialConnectorsLimit } }) => ( + + ), + standardConnectorsLimit: ({ table: { standardConnectorsLimit } }) => ( + + ), + ssoEnabled: ({ table: { ssoEnabled } }) => ( + + ), + // Roles + userManagementEnabled: ({ table: { userManagementEnabled } }) => ( + + ), + rolesLimit: ({ table: { rolesLimit } }) => , + scopesPerRoleLimit: ({ table: { scopesPerRoleLimit } }) => ( + + ), + // Organizations + organizationsEnabled: ({ table: { organizationsEnabled } }) => ( + + ), + // Audit logs + auditLogsRetentionDays: ({ table: { auditLogsRetentionDays } }) => ( + t('admin_console.subscription.quota_table.days', { count: quota })} + /> + ), + // Hooks + hooksLimit: ({ table: { hooksLimit } }) => , + // Support + communitySupportEnabled: ({ table: { communitySupportEnabled } }) => ( + + ), + ticketSupportResponseTime: ({ table: { ticketSupportResponseTime } }) => ( + `(${quota}h)`} + /> + ), +}; diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/utils.ts b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/utils.ts similarity index 97% rename from packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/utils.ts rename to packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/utils.ts index d7dbe1862..379522773 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/utils.ts +++ b/packages/console/src/pages/TenantSettings/Subscription/PlanComparisonTable/utils.ts @@ -9,6 +9,7 @@ import { passwordSignInEnabledMap, passwordlessSignInEnabledMap, smsConnectorsEnabledMap, + tokenLimitMap, userManagementEnabledMap, } from '@/consts/plan-quotas'; import { type SubscriptionPlanTableData, type SubscriptionPlan } from '@/types/subscriptions'; @@ -46,6 +47,7 @@ export const constructPlanTableDataArray = ( emailConnectorsEnabled: emailConnectorsEnabledMap[id], smsConnectorsEnabled: smsConnectorsEnabledMap[id], userManagementEnabled: userManagementEnabledMap[id], + tokenLimit: tokenLimitMap[id], }, }; }); diff --git a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.tsx deleted file mode 100644 index d6b1e2286..000000000 --- a/packages/console/src/pages/TenantSettings/Subscription/PlanQuotaTable/index.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { conditional } from '@silverhand/essentials'; -import { useMemo } from 'react'; - -import Success from '@/assets/icons/success.svg'; -import PlanName from '@/components/PlanName'; -import { enterprisePlanTableData, planTableGroupKeyMap } from '@/consts/plan-quotas'; -import DynamicT from '@/ds-components/DynamicT'; -import Table from '@/ds-components/Table'; -import { type RowGroup, type Column } from '@/ds-components/Table/types'; -import { - type SubscriptionPlanTableRow, - type SubscriptionPlanTableGroupKey, - type SubscriptionPlan, -} from '@/types/subscriptions'; - -import PlanQuotaGroupKeyLabel from './PlanQuotaGroupKeyLabel'; -import PlanQuotaKeyLabel from './PlanQuotaKeyLabel'; -import * as styles from './index.module.scss'; -import { constructPlanTableDataArray } from './utils'; - -type Props = { - subscriptionPlans: SubscriptionPlan[]; -}; - -function PlanQuotaTable({ subscriptionPlans }: Props) { - const quotaTableRowGroups: Array> = useMemo(() => { - const tableDataArray = [ - ...constructPlanTableDataArray(subscriptionPlans), - // Note: enterprise plan table data is not included in the subscription plans, and it's only for display - enterprisePlanTableData, - ]; - - return Object.entries(planTableGroupKeyMap).map(([groupKey, quotaKeys]) => { - return { - key: groupKey, - // eslint-disable-next-line no-restricted-syntax - label: , - labelClassName: styles.groupLabel, - data: quotaKeys.map((key) => { - const tableData = Object.fromEntries( - tableDataArray.map(({ name, table }) => { - return [name, table[key]]; - }) - ); - - return { - quotaKey: key, - ...tableData, - }; - }), - }; - }); - }, [subscriptionPlans]); - - const quotaTableRowData = quotaTableRowGroups[0]?.data?.at(0); - - if (quotaTableRowData === undefined) { - return null; - } - - const planQuotaTableColumns = Object.keys(quotaTableRowData) satisfies Array< - keyof SubscriptionPlanTableRow - >; - - return ( -
- conditional(index % 2 === 0 && styles.colorRow)} - className={styles.table} - headerTableClassName={styles.headerTable} - bodyTableWrapperClassName={styles.bodyTableWrapper} - columns={ - planQuotaTableColumns.map((column) => { - const columnTitle = conditional(column !== 'quotaKey' && column); - - return { - title: conditional(columnTitle && ), - dataIndex: column, - className: conditional(column === 'quotaKey' && styles.quotaKeyColumn), - render: (row) => { - const { quotaKey } = row; - - if (column === 'quotaKey') { - return ; - } - - const quotaValue = row[column]; - - if (quotaValue === undefined) { - return ; - } - - if (quotaValue === null) { - return ; - } - - // For base price - if (typeof quotaValue === 'string') { - if (quotaKey === 'basePrice') { - return ( - - ); - } - - return quotaValue; - } - - // For mau unit price - if (Array.isArray(quotaValue)) { - return quotaValue.length === 0 ? ( - '-' - ) : ( -
- {quotaValue.map((value, index) => ( -
- - -
- ))} -
- ); - } - - if (typeof quotaValue === 'boolean') { - return quotaValue ? : '-'; - } - - // Note: handle number type - if (quotaValue === 0) { - return '-'; - } - - if (quotaKey === 'auditLogsRetentionDays') { - return ( - - ); - } - - if (quotaKey === 'ticketSupportResponseTime') { - return ( -
- - {`(${quotaValue}h)`} -
- ); - } - - return quotaValue.toLocaleString(); - }, - }; - }) satisfies Array> - } - /> -
- -
- - ); -} - -export default PlanQuotaTable; diff --git a/packages/console/src/pages/TenantSettings/Subscription/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/index.tsx index 5f09f8161..4c81e3963 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/index.tsx @@ -12,7 +12,7 @@ import { pickupFeaturedPlans } from '@/utils/subscription'; import Skeleton from '../components/Skeleton'; import CurrentPlan from './CurrentPlan'; -import PlanQuotaTable from './PlanQuotaTable'; +import PlanComparisonTable from './PlanComparisonTable'; import SwitchPlanActionBar from './SwitchPlanActionBar'; import * as styles from './index.module.scss'; @@ -59,7 +59,7 @@ function Subscription() { subscriptionPlan={currentSubscriptionPlan} subscriptionUsage={subscriptionUsage} /> - + ; mauUnitPrice: string[]; // UI and branding customCssEnabled: boolean; @@ -67,12 +73,6 @@ export type SubscriptionPlanTableGroupKeyMap = { [key in SubscriptionPlanTableGroupKey]: Array>; }; -type SubscriptionPlanTableValue = SubscriptionPlanTable[keyof SubscriptionPlanTable]; - -export type SubscriptionPlanTableRow = Record & { - quotaKey: keyof SubscriptionPlanTable; -}; - export const localCheckoutSessionGuard = z.object({ state: z.string(), sessionId: z.string(),