diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx index 4502ebc2a..5f870ed75 100644 --- a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx +++ b/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/index.tsx @@ -6,12 +6,12 @@ import { useContext, useState } from 'react'; import Plus from '@/assets/icons/plus.svg'; import TenantLandingPageImageDark from '@/assets/images/tenant-landing-page-dark.svg'; import TenantLandingPageImage from '@/assets/images/tenant-landing-page.svg'; +import CreateTenantModal from '@/components/CreateTenantModal'; import { TenantsContext } from '@/contexts/TenantsProvider'; import Button from '@/ds-components/Button'; import DynamicT from '@/ds-components/DynamicT'; import useTheme from '@/hooks/use-theme'; -import CreateTenantModal from './CreateTenantModal'; import * as styles from './index.module.scss'; type Props = { @@ -52,6 +52,7 @@ function TenantLandingPageContent({ className }: Props) { /> { if (tenant) { diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.module.scss b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.module.scss new file mode 100644 index 000000000..beebb550d --- /dev/null +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.module.scss @@ -0,0 +1,83 @@ +@use '@/scss/underscore' as _; + +.container { + flex: 1; + border-radius: 12px; + border: 1px solid var(--color-divider); + display: flex; + flex-direction: column; +} + +.planInfo { + padding: _.unit(6); + border-bottom: 1px solid var(--color-divider); + + > div:not(:first-child) { + margin-top: _.unit(4); + } + + .title { + font: var(--font-headline-2); + } + + .priceInfo { + > div:not(:first-child) { + margin-top: _.unit(1); + } + + .priceLabel { + font: var(--font-body-3); + color: var(--color-text-secondary); + } + + .price { + font: var(--font-headline-3); + } + + .unitPrices { + margin-left: _.unit(3); + } + } + + .description { + margin-top: _.unit(1); + font: var(--font-body-2); + color: var(--color-text-secondary); + height: 40px; + } +} + +.content { + flex: 1; + padding: _.unit(6); + display: flex; + flex-direction: column; + + .tip { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: _.unit(4); + + &.exceedFreeTenantsTip { + font: var(--font-body-2); + color: var(--color-text-secondary); + } + + .link { + font: var(--font-label-2); + display: flex; + align-items: center; + } + + .linkIcon { + width: 16px; + height: 16px; + } + } + + .list { + flex: 1; + padding-bottom: _.unit(8); + } +} diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.tsx b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.tsx new file mode 100644 index 000000000..d7342bb9b --- /dev/null +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/PlanCardItem/index.tsx @@ -0,0 +1,120 @@ +import { maxFreeTenantLimit } from '@logto/schemas'; +import classNames from 'classnames'; +import { useContext, useMemo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import ArrowRight from '@/assets/icons/arrow-right.svg'; +import PlanDescription from '@/components/PlanDescription'; +import PlanName from '@/components/PlanName'; +import PlanQuotaList from '@/components/PlanQuotaList'; +import { ReservedPlanId } from '@/consts/subscriptions'; +import { TenantsContext } from '@/contexts/TenantsProvider'; +import Button from '@/ds-components/Button'; +import DangerousRaw from '@/ds-components/DangerousRaw'; +import DynamicT from '@/ds-components/DynamicT'; +import TextLink from '@/ds-components/TextLink'; +import { type SubscriptionPlanQuota, type SubscriptionPlan } from '@/types/subscriptions'; + +import * as styles from './index.module.scss'; + +const featuredQuotaKeys: Array = [ + 'mauLimit', + 'machineToMachineLimit', + 'standardConnectorsLimit', + 'rolesLimit', + 'scopesPerRoleLimit', + 'auditLogsRetentionDays', +]; + +type Props = { + plan: SubscriptionPlan; + onSelect: () => void; +}; + +function PlanCardItem({ plan, onSelect }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.create_tenant' }); + const { tenants } = useContext(TenantsContext); + const { stripeProducts, id: planId, name: planName, quota } = plan; + + const basePrice = useMemo( + () => stripeProducts.find(({ type }) => type === 'flat')?.price.unitAmountDecimal ?? 0, + [stripeProducts] + ); + + const tierPrices = useMemo(() => { + const prices = stripeProducts + .filter(({ type }) => type !== 'flat') + .map(({ price: { unitAmountDecimal } }) => `$${Number(unitAmountDecimal) / 100}`); + + return prices.length > 0 ? prices.join(' ') : '$0.00'; + }, [stripeProducts]); + + const isFreePlan = planId === ReservedPlanId.free; + + // Todo: @xiaoyijun filter our all free tenants + const isFreeTenantExceeded = tenants.length >= maxFreeTenantLimit; + + return ( +
+
+
+ +
+
+
{t('base_price')}
+
+ ${t('monthly_price', { value: Number(basePrice) / 100 })} +
+
+ {t('mau_unit_price')} + {tierPrices} +
+
+
+ +
+
+
+ + {isFreePlan && isFreeTenantExceeded && ( +
+ {t('free_tenants_limit', { count: maxFreeTenantLimit })} +
+ )} + {!isFreePlan && ( +
+ } + target="_blank" + className={styles.link} + > + + +
+ )} +
+
+ ); +} + +export default PlanCardItem; diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.module.scss b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.module.scss new file mode 100644 index 000000000..8699b3095 --- /dev/null +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.module.scss @@ -0,0 +1,9 @@ +@use '@/scss/underscore' as _; + +.container { + display: flex; + justify-content: space-between; + align-items: stretch; + gap: _.unit(7); +} + diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx new file mode 100644 index 000000000..ffdc2c5f7 --- /dev/null +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx @@ -0,0 +1,90 @@ +import { type TenantInfo } from '@logto/schemas/models'; +import { toast } from 'react-hot-toast'; +import { Trans, useTranslation } from 'react-i18next'; +import Modal from 'react-modal'; + +import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; +import { ReservedPlanId } from '@/consts/subscriptions'; +import DangerousRaw from '@/ds-components/DangerousRaw'; +import ModalLayout from '@/ds-components/ModalLayout'; +import TextLink from '@/ds-components/TextLink'; +import useSubscriptionPlans from '@/hooks/use-subscription-plans'; +import * as modalStyles from '@/scss/modal.module.scss'; +import { type SubscriptionPlan } from '@/types/subscriptions'; + +import { type CreateTenantData } from '../type'; + +import PlanCardItem from './PlanCardItem'; +import * as styles from './index.module.scss'; + +type Props = { + tenantData?: CreateTenantData; + onClose: (tenant?: TenantInfo) => void; +}; + +function SelectTenantPlanModal({ tenantData, onClose }: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const { data: subscriptionPlans } = useSubscriptionPlans(); + const cloudApi = useCloudApi(); + if (!subscriptionPlans || !tenantData) { + return null; + } + + const handleSelectPlan = async (plan: SubscriptionPlan) => { + const { id: planId } = plan; + if (planId === ReservedPlanId.free) { + try { + const { name, tag } = tenantData; + const newTenant = await cloudApi.post('/api/tenants', { body: { name, tag } }); + + onClose(newTenant); + } catch (error: unknown) { + toast.error(error instanceof Error ? error.message : String(error)); + } + } + // Todo @xiaoyijun implement checkout + }; + + return ( + { + onClose(); + }} + > + + , + }} + > + {t('upsell.create_tenant.description')} + + + } + size="xlarge" + onClose={onClose} + > +
+ {subscriptionPlans.map((plan) => ( + { + void handleSelectPlan(plan); + }} + /> + ))} +
+
+
+ ); +} + +export default SelectTenantPlanModal; diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.module.scss b/packages/console/src/components/CreateTenantModal/index.module.scss similarity index 100% rename from packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.module.scss rename to packages/console/src/components/CreateTenantModal/index.module.scss diff --git a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.tsx b/packages/console/src/components/CreateTenantModal/index.tsx similarity index 76% rename from packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.tsx rename to packages/console/src/components/CreateTenantModal/index.tsx index 5fedbf900..6e3a89bbc 100644 --- a/packages/console/src/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal/index.tsx +++ b/packages/console/src/components/CreateTenantModal/index.tsx @@ -1,6 +1,7 @@ import type { AdminConsoleKey } from '@logto/phrases'; import { Theme } from '@logto/schemas'; import { TenantTag, type TenantInfo } from '@logto/schemas/models'; +import { useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; @@ -9,6 +10,7 @@ import Modal from 'react-modal'; import CreateTenantHeaderIconDark from '@/assets/icons/create-tenant-header-dark.svg'; import CreateTenantHeaderIcon from '@/assets/icons/create-tenant-header.svg'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; +import { isProduction } from '@/consts/env'; import Button from '@/ds-components/Button'; import FormField from '@/ds-components/FormField'; import ModalLayout from '@/ds-components/ModalLayout'; @@ -17,11 +19,15 @@ import TextInput from '@/ds-components/TextInput'; import useTheme from '@/hooks/use-theme'; import * as modalStyles from '@/scss/modal.module.scss'; +import SelectTenantPlanModal from './SelectTenantPlanModal'; import * as styles from './index.module.scss'; +import { type CreateTenantData } from './type'; type Props = { isOpen: boolean; onClose: (tenant?: TenantInfo) => void; + // eslint-disable-next-line react/boolean-prop-naming + skipPlanSelection?: boolean; }; const tagOptions: Array<{ title: AdminConsoleKey; value: TenantTag }> = [ @@ -39,12 +45,14 @@ const tagOptions: Array<{ title: AdminConsoleKey; value: TenantTag }> = [ }, ]; -function CreateTenantModal({ isOpen, onClose }: Props) { +function CreateTenantModal({ isOpen, onClose, skipPlanSelection = false }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + const [tenantData, setTenantData] = useState(); const theme = useTheme(); - const methods = useForm>({ + const methods = useForm({ defaultValues: { tag: TenantTag.Development }, }); + const { reset, control, @@ -55,7 +63,7 @@ function CreateTenantModal({ isOpen, onClose }: Props) { const cloudApi = useCloudApi(); - const onSubmit = handleSubmit(async (data) => { + const createTenant = async (data: CreateTenantData) => { try { const { name, tag } = data; const newTenant = await cloudApi.post('/api/tenants', { body: { name, tag } }); @@ -64,7 +72,15 @@ function CreateTenantModal({ isOpen, onClose }: Props) { } catch (error: unknown) { toast.error(error instanceof Error ? error.message : String(error)); } - }); + }; + + /** + * Note: create tenant directly if it's from landing page, + * since we want the user to get into the console as soon as possible + */ + const shouldSkipPlanSelection = skipPlanSelection || isProduction; + + const onCreateClick = handleSubmit(shouldSkipPlanSelection ? createTenant : setTenantData); return ( } onClose={onClose} @@ -122,6 +138,18 @@ function CreateTenantModal({ isOpen, onClose }: Props) { + { + setTenantData(undefined); + if (tenant) { + /** + * Note: only close the create tenant modal when tenant is created successfully + */ + onClose(tenant); + } + }} + /> ); diff --git a/packages/console/src/components/CreateTenantModal/type.ts b/packages/console/src/components/CreateTenantModal/type.ts new file mode 100644 index 000000000..750d7f85e --- /dev/null +++ b/packages/console/src/components/CreateTenantModal/type.ts @@ -0,0 +1,3 @@ +import { type TenantInfo } from '@logto/schemas/models'; + +export type CreateTenantData = Pick; diff --git a/packages/console/src/components/PlanDescription/index.module.scss b/packages/console/src/components/PlanDescription/index.module.scss deleted file mode 100644 index 0c3d1440c..000000000 --- a/packages/console/src/components/PlanDescription/index.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use '@/scss/underscore' as _; - -.description { - margin-top: _.unit(1); - font: var(--font-body-2); - color: var(--color-text-secondary); -} diff --git a/packages/console/src/components/PlanDescription/index.tsx b/packages/console/src/components/PlanDescription/index.tsx index 7f51cf709..17f84c581 100644 --- a/packages/console/src/components/PlanDescription/index.tsx +++ b/packages/console/src/components/PlanDescription/index.tsx @@ -3,8 +3,6 @@ import { type TFuncKey } from 'i18next'; import DynamicT from '@/ds-components/DynamicT'; import { ReservedPlanName } from '@/types/subscriptions'; -import * as styles from './index.module.scss'; - const registeredPlanDescriptionPhrasesMap: Record< string, TFuncKey<'translation', 'admin_console.subscription'> | undefined @@ -23,11 +21,7 @@ function PlanDescription({ planName }: Props) { return null; } - return ( -
- -
- ); + return ; } export default PlanDescription; diff --git a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.module.scss b/packages/console/src/components/PlanQuotaList/QuotaItem/index.module.scss similarity index 62% rename from packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.module.scss rename to packages/console/src/components/PlanQuotaList/QuotaItem/index.module.scss index 4d542cfe4..243f1f652 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.module.scss +++ b/packages/console/src/components/PlanQuotaList/QuotaItem/index.module.scss @@ -2,8 +2,9 @@ .item { margin-left: _.unit(4); + font: var(--font-body-2); - &.withChangeState { + &.withIcon { list-style-type: none; margin-left: unset; } @@ -16,14 +17,18 @@ .icon { width: 16px; height: 16px; + + &.notCapable { + color: var(--color-error); + } + + &.capable { + color: var(--color-on-success-container); + } } - &.notCapable { + .lineThrough { text-decoration: line-through; - - .icon { - color: var(--color-error-hover); - } } } } diff --git a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.tsx b/packages/console/src/components/PlanQuotaList/QuotaItem/index.tsx similarity index 70% rename from packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.tsx rename to packages/console/src/components/PlanQuotaList/QuotaItem/index.tsx index 63b76f4f1..280e6a122 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/QuotaDiffItem/index.tsx +++ b/packages/console/src/components/PlanQuotaList/QuotaItem/index.tsx @@ -4,11 +4,12 @@ import { useTranslation } from 'react-i18next'; import DescendArrow from '@/assets/icons/descend-arrow.svg'; import Failed from '@/assets/icons/failed.svg'; +import Success from '@/assets/icons/success.svg'; import { quotaItemUnlimitedPhrasesMap, quotaItemPhrasesMap, quotaItemLimitedPhrasesMap, -} from '@/pages/TenantSettings/Subscription/quota-item-phrases'; +} from '@/consts/quota-item-phrases'; import { type SubscriptionPlanQuota } from '@/types/subscriptions'; import * as styles from './index.module.scss'; @@ -17,23 +18,26 @@ type Props = { hasIcon?: boolean; quotaKey: keyof SubscriptionPlanQuota; quotaValue: SubscriptionPlanQuota[keyof SubscriptionPlanQuota]; + isDiffItem?: boolean; }; -function QuotaDiffItem({ hasIcon = false, quotaKey, quotaValue }: Props) { +function QuotaItem({ hasIcon, quotaKey, quotaValue, isDiffItem }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.subscription.quota_item' }); const isUnlimited = quotaValue === null; const isNotCapable = quotaValue === 0 || quotaValue === false; const isLimited = Boolean(quotaValue); - const Icon = isNotCapable ? Failed : DescendArrow; + const Icon = isNotCapable ? Failed : isDiffItem ? DescendArrow : Success; return ( -
  • - - {hasIcon && } - +
  • + + {hasIcon && ( + + )} + {isUnlimited && <>{t(quotaItemUnlimitedPhrasesMap[quotaKey])}} {isNotCapable && <>{t(quotaItemPhrasesMap[quotaKey])}} {isLimited && ( @@ -50,4 +54,4 @@ function QuotaDiffItem({ hasIcon = false, quotaKey, quotaValue }: Props) { ); } -export default QuotaDiffItem; +export default QuotaItem; diff --git a/packages/console/src/components/PlanQuotaList/index.module.scss b/packages/console/src/components/PlanQuotaList/index.module.scss new file mode 100644 index 000000000..1695e5396 --- /dev/null +++ b/packages/console/src/components/PlanQuotaList/index.module.scss @@ -0,0 +1,12 @@ +@use '@/scss/underscore' as _; + +.list { + margin-block: 0; + padding-inline: 0; + + > li { + &:not(:first-child) { + margin-top: _.unit(3); + } + } +} diff --git a/packages/console/src/components/PlanQuotaList/index.tsx b/packages/console/src/components/PlanQuotaList/index.tsx new file mode 100644 index 000000000..2f1098ada --- /dev/null +++ b/packages/console/src/components/PlanQuotaList/index.tsx @@ -0,0 +1,47 @@ +import classNames from 'classnames'; +import { useMemo } from 'react'; + +import { type SubscriptionPlanQuota } from '@/types/subscriptions'; + +import QuotaItem from './QuotaItem'; +import * as styles from './index.module.scss'; + +type Props = { + quota: Partial; + featuredQuotaKeys?: Array; + className?: string; + isDiff?: boolean; + hasIcon?: boolean; +}; + +function PlanQuotaList({ quota, featuredQuotaKeys, isDiff, hasIcon, className }: Props) { + const items = useMemo(() => { + // Todo: @xiaoyijun LOG-6540 order keys + // eslint-disable-next-line no-restricted-syntax + const entries = Object.entries(quota) as Array< + [keyof SubscriptionPlanQuota, SubscriptionPlanQuota[keyof SubscriptionPlanQuota]] + >; + + const featuredEntries = featuredQuotaKeys + ? entries.filter(([key]) => featuredQuotaKeys.includes(key)) + : entries; + + return featuredEntries; + }, [quota, featuredQuotaKeys]); + + return ( +
      + {items.map(([quotaKey, quotaValue]) => ( + + ))} +
    + ); +} + +export default PlanQuotaList; diff --git a/packages/console/src/pages/TenantSettings/Subscription/quota-item-phrases.ts b/packages/console/src/consts/quota-item-phrases.ts similarity index 100% rename from packages/console/src/pages/TenantSettings/Subscription/quota-item-phrases.ts rename to packages/console/src/consts/quota-item-phrases.ts diff --git a/packages/console/src/consts/subscriptions.ts b/packages/console/src/consts/subscriptions.ts index 045bc6b11..fd84079e9 100644 --- a/packages/console/src/consts/subscriptions.ts +++ b/packages/console/src/consts/subscriptions.ts @@ -6,7 +6,7 @@ import { SubscriptionPlanTableGroupKey, } from '@/types/subscriptions'; -enum ReservedPlanId { +export enum ReservedPlanId { free = 'free', hobby = 'hobby', pro = 'pro', diff --git a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx index 8ee900a8f..6e149dab1 100644 --- a/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx +++ b/packages/console/src/containers/AppContent/components/Topbar/TenantSelector/index.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import KeyboardArrowDown from '@/assets/icons/keyboard-arrow-down.svg'; import PlusSign from '@/assets/icons/plus.svg'; import Tick from '@/assets/icons/tick.svg'; -import CreateTenantModal from '@/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal'; +import CreateTenantModal from '@/components/CreateTenantModal'; import { TenantsContext } from '@/contexts/TenantsProvider'; import Divider from '@/ds-components/Divider'; import Dropdown, { DropdownItem } from '@/ds-components/Dropdown'; diff --git a/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.module.scss b/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.module.scss index 9d494f06a..61012a4f4 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.module.scss +++ b/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.module.scss @@ -6,6 +6,12 @@ .name { font: var(--font-title-1); } + + .description { + margin-top: _.unit(1); + font: var(--font-body-2); + color: var(--color-text-secondary); + } } .notification { diff --git a/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.tsx index a616fbbb5..a1db0f81f 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/CurrentPlan/index.tsx @@ -25,7 +25,9 @@ function CurrentPlan({ subscription, subscriptionPlan, subscriptionUsage }: Prop
    - +
    + +
    li { - &:not(:first-child) { - margin-top: _.unit(3); - } - } - } } diff --git a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffCard/index.tsx similarity index 50% rename from packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/index.tsx rename to packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffCard/index.tsx index 6b75cd060..6b20e404a 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffList/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/PlanQuotaDiffCard/index.tsx @@ -1,10 +1,9 @@ -import { useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import PlanName from '@/components/PlanName'; +import PlanQuotaList from '@/components/PlanQuotaList'; import { type SubscriptionPlanQuota } from '@/types/subscriptions'; -import QuotaDiffItem from './QuotaDiffItem'; import * as styles from './index.module.scss'; type Props = { @@ -13,17 +12,11 @@ type Props = { isTarget?: boolean; }; -function PlanQuotaDiffList({ planName, quotaDiff, isTarget = false }: Props) { +function PlanQuotaDiffCard({ planName, quotaDiff, isTarget = false }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.subscription.downgrade_modal', }); - // Todo: @xiaoyijun LOG-6540 order keys - // eslint-disable-next-line no-restricted-syntax - const entries = useMemo(() => Object.entries(quotaDiff), [quotaDiff]) as Array< - [keyof SubscriptionPlanQuota, SubscriptionPlanQuota[keyof SubscriptionPlanQuota]] - >; - return (
    @@ -35,18 +28,9 @@ function PlanQuotaDiffList({ planName, quotaDiff, isTarget = false }: Props) { {t(isTarget ? 'after' : 'before')}
    -
      - {entries.map(([quotaKey, quotaValue]) => ( - - ))} -
    +
    ); } -export default PlanQuotaDiffList; +export default PlanQuotaDiffCard; diff --git a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/index.tsx index 2ff99a350..662ca7a98 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/DowngradeConfirmModalContent/index.tsx @@ -5,7 +5,7 @@ import { Trans, useTranslation } from 'react-i18next'; import PlanName from '@/components/PlanName'; import { type SubscriptionPlan } from '@/types/subscriptions'; -import PlanQuotaDiffList from './PlanQuotaDiffList'; +import PlanQuotaDiffCard from './PlanQuotaDiffCard'; import * as styles from './index.module.scss'; type Props = { @@ -42,8 +42,8 @@ function DowngradeConfirmModalContent({ currentPlan, targetPlan }: Props) {
    - - + +
    ); diff --git a/packages/console/src/pages/TenantSettings/Subscription/NotEligibleDowngradeModalContent/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/NotEligibleDowngradeModalContent/index.tsx index 338c81f76..9ec4edfb6 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/NotEligibleDowngradeModalContent/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/NotEligibleDowngradeModalContent/index.tsx @@ -2,11 +2,13 @@ import { conditional } from '@silverhand/essentials'; import { Trans, useTranslation } from 'react-i18next'; import PlanName from '@/components/PlanName'; +import { + quotaItemLimitedPhrasesMap, + quotaItemNotEligiblePhrasesMap, +} from '@/consts/quota-item-phrases'; import DynamicT from '@/ds-components/DynamicT'; import { type SubscriptionPlan, type SubscriptionPlanQuota } from '@/types/subscriptions'; -import { quotaItemLimitedPhrasesMap, quotaItemNotEligiblePhrasesMap } from '../quota-item-phrases'; - import * as styles from './index.module.scss'; const excludedQuotaKeys = new Set([