mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
refactor(console): treat hobby plan as the new pro plan (#5126)
This commit is contained in:
parent
0691669d6f
commit
ed1692959f
12 changed files with 60 additions and 22 deletions
|
@ -8,7 +8,7 @@ import { useContext, useMemo, useState } from 'react';
|
|||
import Modal from 'react-modal';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import { isCloud } from '@/consts/env';
|
||||
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
|
@ -170,7 +170,8 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
|
|||
<FeatureTag
|
||||
isVisible={isStandardConnectorDisabled}
|
||||
for="upsell"
|
||||
plan={ReservedPlanId.Hobby}
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
plan={isDevFeaturesEnabled ? ReservedPlanId.Pro : ReservedPlanId.Hobby}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { TenantTag } from '@logto/schemas';
|
||||
import { condArray } from '@silverhand/essentials';
|
||||
|
||||
import TenantEnvTag from '@/components/TenantEnvTag';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import Divider from '@/ds-components/Divider';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import Tag from '@/ds-components/Tag';
|
||||
|
@ -22,7 +24,8 @@ const descriptionMap: Record<TenantTag, AdminConsoleKey> = {
|
|||
|
||||
const availableProductionPlanNames = [
|
||||
ReservedPlanName.Free,
|
||||
ReservedPlanName.Hobby,
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
...condArray(!isDevFeaturesEnabled && ReservedPlanName.Hobby),
|
||||
ReservedPlanName.Pro,
|
||||
];
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import PlanDescription from '@/components/PlanDescription';
|
|||
import PlanName from '@/components/PlanName';
|
||||
import PlanQuotaList from '@/components/PlanQuotaList';
|
||||
import { pricingLink } from '@/consts';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { comingSoonQuotaKeys } from '@/consts/plan-quotas';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import Button from '@/ds-components/Button';
|
||||
|
@ -76,10 +77,13 @@ function PlanCardItem({ plan, onSelect }: Props) {
|
|||
<div className={styles.price}>
|
||||
${t('monthly_price', { value: Number(basePrice) / 100 })}
|
||||
</div>
|
||||
<div className={styles.priceLabel}>
|
||||
{t('mau_unit_price')}
|
||||
<span className={styles.unitPrices}>{tierPrices}</span>
|
||||
</div>
|
||||
{/* Todo @xiaoyijun [Pricing] Remove feature flag */}
|
||||
{!isDevFeaturesEnabled && (
|
||||
<div className={styles.priceLabel}>
|
||||
{t('mau_unit_price')}
|
||||
<span className={styles.unitPrices}>{tierPrices}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<PlanDescription planId={planId} />
|
||||
|
@ -125,9 +129,11 @@ function PlanCardItem({ plan, onSelect }: Props) {
|
|||
onClick={onSelect}
|
||||
/>
|
||||
</div>
|
||||
{planId === ReservedPlanId.Pro && (
|
||||
<div className={styles.mostPopularTag}>{t('most_popular')}</div>
|
||||
)}
|
||||
{planId === ReservedPlanId.Pro ||
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
(isDevFeaturesEnabled && planId === ReservedPlanId.Hobby && (
|
||||
<div className={styles.mostPopularTag}>{t('most_popular')}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import Modal from 'react-modal';
|
|||
import { useCloudApi, toastResponseError } from '@/cloud/hooks/use-cloud-api';
|
||||
import { type TenantResponse } from '@/cloud/types/router';
|
||||
import { pricingLink } from '@/consts';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import DangerousRaw from '@/ds-components/DangerousRaw';
|
||||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
import TextLink from '@/ds-components/TextLink';
|
||||
|
@ -72,7 +73,7 @@ function SelectTenantPlanModal({ tenantData, onClose }: Props) {
|
|||
</Trans>
|
||||
</DangerousRaw>
|
||||
}
|
||||
size="xlarge"
|
||||
size={isDevFeaturesEnabled ? 'large' : 'xlarge'}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Suspense, useCallback, useContext } from 'react';
|
|||
|
||||
import { type Guide, type GuideMetadata } from '@/assets/docs/guides/types';
|
||||
import FeatureTag from '@/components/FeatureTag';
|
||||
import { isCloud } from '@/consts/env';
|
||||
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { subscriptionPage } from '@/consts/pages';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import Button from '@/ds-components/Button';
|
||||
|
@ -77,7 +77,11 @@ function GuideCard({ data, onClick, hasBorder, hasButton }: Props) {
|
|||
<div className={styles.flexRow}>
|
||||
<div className={styles.name}>{name}</div>
|
||||
{hasPaywall && (
|
||||
<FeatureTag isVisible={isM2mDisabled} for="upsell" plan={ReservedPlanId.Hobby} />
|
||||
<FeatureTag
|
||||
isVisible={isM2mDisabled}
|
||||
for="upsell"
|
||||
plan={isDevFeaturesEnabled ? ReservedPlanId.Pro : ReservedPlanId.Hobby}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.description} title={description}>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ReservedPlanId } from '@logto/schemas';
|
||||
import { type TFuncKey } from 'i18next';
|
||||
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
|
||||
const registeredPlanDescriptionPhrasesMap: Record<
|
||||
|
@ -8,7 +9,7 @@ const registeredPlanDescriptionPhrasesMap: Record<
|
|||
TFuncKey<'translation', 'admin_console.subscription'> | undefined
|
||||
> = {
|
||||
[ReservedPlanId.Free]: 'free_plan_description',
|
||||
[ReservedPlanId.Hobby]: 'hobby_plan_description',
|
||||
[ReservedPlanId.Hobby]: isDevFeaturesEnabled ? 'pro_plan_description' : 'hobby_plan_description',
|
||||
[ReservedPlanId.Pro]: 'pro_plan_description',
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { type TFuncKey } from 'i18next';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import titleize from 'titleize';
|
||||
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { ReservedPlanName } from '@/types/subscriptions';
|
||||
|
||||
const registeredPlanNamePhraseMap: Record<
|
||||
|
@ -10,7 +11,8 @@ const registeredPlanNamePhraseMap: Record<
|
|||
> = {
|
||||
quotaKey: undefined,
|
||||
[ReservedPlanName.Free]: 'free_plan',
|
||||
[ReservedPlanName.Hobby]: 'hobby_plan',
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
[ReservedPlanName.Hobby]: isDevFeaturesEnabled ? 'pro_plan' : 'hobby_plan',
|
||||
[ReservedPlanName.Pro]: 'pro_plan',
|
||||
[ReservedPlanName.Enterprise]: 'enterprise',
|
||||
};
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { ReservedPlanId } from '@logto/schemas';
|
||||
import { condArray } from '@silverhand/essentials';
|
||||
|
||||
import { isDevFeaturesEnabled as isDevelopmentFeaturesEnabled } from './env';
|
||||
|
||||
/**
|
||||
* In console, only featured plans are shown in the plan selection component.
|
||||
|
@ -6,7 +9,8 @@ import { ReservedPlanId } from '@logto/schemas';
|
|||
export const featuredPlanIds: string[] = [
|
||||
ReservedPlanId.Free,
|
||||
ReservedPlanId.Hobby,
|
||||
ReservedPlanId.Pro,
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
...condArray(!isDevelopmentFeaturesEnabled && ReservedPlanId.Pro),
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,7 @@ import FeatureTag from '@/components/FeatureTag';
|
|||
import { type SelectedGuide } from '@/components/Guide/GuideCard';
|
||||
import GuideCardGroup from '@/components/Guide/GuideCardGroup';
|
||||
import { useAppGuideMetadata } from '@/components/Guide/hooks';
|
||||
import { isCloud } from '@/consts/env';
|
||||
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import { CheckboxGroup } from '@/ds-components/Checkbox';
|
||||
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
|
||||
|
@ -105,7 +105,8 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: P
|
|||
<FeatureTag
|
||||
isVisible={isM2mDisabledForCurrentPlan}
|
||||
for="upsell"
|
||||
plan={ReservedPlanId.Hobby}
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
plan={isDevFeaturesEnabled ? ReservedPlanId.Pro : ReservedPlanId.Hobby}
|
||||
className={styles.proTag}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useContext } from 'react';
|
|||
|
||||
import ApplicationIcon from '@/components/ApplicationIcon';
|
||||
import FeatureTag from '@/components/FeatureTag';
|
||||
import { isCloud } from '@/consts/env';
|
||||
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
|
||||
|
||||
|
@ -34,7 +34,8 @@ function TypeDescription({ title, subtitle, description, type, size = 'large' }:
|
|||
<FeatureTag
|
||||
isVisible={!currentPlan?.quota.machineToMachineLimit}
|
||||
for="upsell"
|
||||
plan={ReservedPlanId.Hobby}
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
plan={isDevFeaturesEnabled ? ReservedPlanId.Pro : ReservedPlanId.Hobby}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -13,6 +13,7 @@ import FeatureTag from '@/components/FeatureTag';
|
|||
import PlanName from '@/components/PlanName';
|
||||
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
|
||||
import RoleScopesTransfer from '@/components/RoleScopesTransfer';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import Button from '@/ds-components/Button';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
|
@ -229,7 +230,8 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
<FeatureTag
|
||||
isVisible={!currentPlan?.quota.machineToMachineLimit}
|
||||
for="upsell"
|
||||
plan={ReservedPlanId.Hobby}
|
||||
// Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
plan={isDevFeaturesEnabled ? ReservedPlanId.Pro : ReservedPlanId.Hobby}
|
||||
className={styles.proTag}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ReservedPlanId } from '@logto/schemas';
|
|||
import { useContext, useMemo, useState } from 'react';
|
||||
|
||||
import { toastResponseError } from '@/cloud/hooks/use-cloud-api';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { subscriptionPage } from '@/consts/pages';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
|
@ -26,7 +27,18 @@ function MauLimitExceededNotification({ activeUsers, currentPlan, className }: P
|
|||
const { data: subscriptionPlans } = useSubscriptionPlans();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const proPlan = useMemo(
|
||||
() => subscriptionPlans?.find(({ id }) => id === ReservedPlanId.Pro),
|
||||
() =>
|
||||
subscriptionPlans?.find(({ id }) => {
|
||||
/**
|
||||
* Todo @xiaoyijun [Pricing] Remove feature flag
|
||||
* Note: In new pricing version, we treat Hobby plan as the new pro plan.
|
||||
*/
|
||||
if (isDevFeaturesEnabled) {
|
||||
return id === ReservedPlanId.Hobby;
|
||||
}
|
||||
|
||||
return id === ReservedPlanId.Pro;
|
||||
}),
|
||||
[subscriptionPlans]
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue