mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
fix(console): fix add-on console issues
This commit is contained in:
parent
0004d682b0
commit
bc8317706f
5 changed files with 90 additions and 104 deletions
packages/console/src
components/PlanUsage
pages/TenantSettings/Subscription
|
@ -22,6 +22,10 @@
|
|||
.description {
|
||||
font: var(--font-title-3);
|
||||
color: var(--color-text);
|
||||
|
||||
&.quotaExceeded {
|
||||
color: var(--color-danger-default);
|
||||
}
|
||||
}
|
||||
|
||||
.usageTip {
|
|
@ -1,4 +1,5 @@
|
|||
import { type AdminConsoleKey } from '@logto/phrases';
|
||||
import { conditional, type Nullable } from '@silverhand/essentials';
|
||||
import classNames from 'classnames';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -15,15 +16,15 @@ import styles from './index.module.scss';
|
|||
|
||||
export type Props = {
|
||||
readonly usage: number | boolean;
|
||||
readonly quota?: number;
|
||||
readonly quota?: Nullable<number>;
|
||||
readonly usageKey: AdminConsoleKey;
|
||||
readonly titleKey: AdminConsoleKey;
|
||||
readonly tooltipKey: AdminConsoleKey;
|
||||
readonly tooltipKey?: AdminConsoleKey;
|
||||
readonly unitPrice: number;
|
||||
readonly className?: string;
|
||||
};
|
||||
|
||||
function ProPlanUsageCard({
|
||||
function PlanUsageCard({
|
||||
usage,
|
||||
quota,
|
||||
unitPrice,
|
||||
|
@ -34,32 +35,43 @@ function ProPlanUsageCard({
|
|||
}: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const usagePercent = conditional(
|
||||
typeof quota === 'number' && typeof usage === 'number' && usage / quota
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.card, className)}>
|
||||
<div className={styles.title}>
|
||||
<span>
|
||||
<DynamicT forKey={titleKey} />
|
||||
</span>
|
||||
<ToggleTip
|
||||
content={
|
||||
<Trans
|
||||
components={{
|
||||
a: <TextLink to="https://blog.logto.io/pricing-add-ons/" />,
|
||||
}}
|
||||
>
|
||||
{t(tooltipKey, {
|
||||
price: unitPrice,
|
||||
})}
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<IconButton size="small">
|
||||
<Tip />
|
||||
</IconButton>
|
||||
</ToggleTip>
|
||||
{tooltipKey && (
|
||||
<ToggleTip
|
||||
content={
|
||||
<Trans
|
||||
components={{
|
||||
a: <TextLink to="https://blog.logto.io/pricing-add-ons/" />,
|
||||
}}
|
||||
>
|
||||
{t(tooltipKey, {
|
||||
price: unitPrice,
|
||||
})}
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<IconButton size="small">
|
||||
<Tip />
|
||||
</IconButton>
|
||||
</ToggleTip>
|
||||
)}
|
||||
</div>
|
||||
{typeof usage === 'number' ? (
|
||||
<div className={styles.description}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.description,
|
||||
typeof usagePercent === 'number' && usagePercent >= 1 && styles.quotaExceeded
|
||||
)}
|
||||
>
|
||||
<Trans
|
||||
components={{
|
||||
span: <span className={styles.usageTip} />,
|
||||
|
@ -67,9 +79,13 @@ function ProPlanUsageCard({
|
|||
>
|
||||
{t(usageKey, {
|
||||
usage:
|
||||
quota && typeof quota === 'number'
|
||||
? `${formatNumber(usage)} / ${formatNumber(quota)}`
|
||||
: formatNumber(usage),
|
||||
quota === undefined
|
||||
? formatNumber(usage)
|
||||
: typeof quota === 'number'
|
||||
? `${formatNumber(usage)} / ${formatNumber(quota)}${
|
||||
usagePercent === undefined ? '' : ` (${(usagePercent * 100).toFixed(0)}%)`
|
||||
}`
|
||||
: `${formatNumber(usage)} / ${String(t('subscription.quota_table.unlimited'))}`,
|
||||
})}
|
||||
</Trans>
|
||||
</div>
|
||||
|
@ -86,4 +102,4 @@ function ProPlanUsageCard({
|
|||
);
|
||||
}
|
||||
|
||||
export default ProPlanUsageCard;
|
||||
export default PlanUsageCard;
|
|
@ -9,11 +9,6 @@
|
|||
> div:not(:first-child) {
|
||||
margin-top: _.unit(4);
|
||||
}
|
||||
|
||||
&.freeUser {
|
||||
flex: 0 0 calc((100% - _.unit(2)) / 2);
|
||||
max-width: calc((100% - _.unit(2)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.usage {
|
||||
|
@ -34,6 +29,11 @@
|
|||
flex: 0 0 calc((100% - _.unit(2) * 2) / 3);
|
||||
max-width: calc((100% - _.unit(2) * 2) / 3);
|
||||
max-height: 112px;
|
||||
|
||||
&.freeUser {
|
||||
flex: 0 0 calc((100% - _.unit(2)) / 2);
|
||||
max-width: calc((100% - _.unit(2)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.planCycle {
|
||||
|
|
|
@ -12,7 +12,7 @@ import DynamicT from '@/ds-components/DynamicT';
|
|||
import { type SubscriptionPlan } from '@/types/subscriptions';
|
||||
import { formatPeriod } from '@/utils/subscription';
|
||||
|
||||
import ProPlanUsageCard, { type Props as ProPlanUsageCardProps } from './ProPlanUsageCard';
|
||||
import PlanUsageCard, { type Props as PlanUsageCardProps } from './PlanUsageCard';
|
||||
import styles from './index.module.scss';
|
||||
import { usageKeys, usageKeyPriceMap, usageKeyMap, titleKeyMap, tooltipKeyMap } from './utils';
|
||||
|
||||
|
@ -56,28 +56,36 @@ function PlanUsage({ currentSubscription, currentPlan, periodicUsage: rawPeriodi
|
|||
periodicUsage.mauLimit,
|
||||
isDevFeaturesEnabled ? currentSubscriptionQuota.mauLimit : currentPlan.quota.mauLimit,
|
||||
];
|
||||
const [tokenUsage, tokenLimit] = [periodicUsage.tokenLimit, currentSubscriptionQuota.tokenLimit];
|
||||
|
||||
const mauUsagePercent = conditional(mauLimit && activeUsers / mauLimit) ?? 0;
|
||||
const tokenUsagePercent = conditional(tokenLimit && tokenUsage / tokenLimit) ?? 0;
|
||||
const mauUsagePercent = conditional(mauLimit && activeUsers / mauLimit);
|
||||
|
||||
const usages: ProPlanUsageCardProps[] = usageKeys.map((key) => ({
|
||||
usage:
|
||||
key === 'mauLimit' || key === 'tokenLimit'
|
||||
? periodicUsage[key]
|
||||
: currentSubscriptionUsage[key],
|
||||
usageKey: `subscription.usage.${usageKeyMap[key]}`,
|
||||
titleKey: `subscription.usage.${titleKeyMap[key]}`,
|
||||
tooltipKey: `subscription.usage.${tooltipKeyMap[key]}`,
|
||||
unitPrice: usageKeyPriceMap[key],
|
||||
...cond(
|
||||
key === 'tokenLimit' &&
|
||||
currentSubscriptionQuota.tokenLimit && { quota: currentSubscriptionQuota.tokenLimit }
|
||||
),
|
||||
}));
|
||||
const usages: PlanUsageCardProps[] = usageKeys
|
||||
// Show all usages for Pro plan and only show MAU and token usage for Free plan
|
||||
.filter(
|
||||
(key) =>
|
||||
currentSubscriptionFromNewPricingModel.planId === ReservedPlanId.Pro ||
|
||||
(currentSubscriptionFromNewPricingModel.planId === ReservedPlanId.Free &&
|
||||
(key === 'mauLimit' || key === 'tokenLimit'))
|
||||
)
|
||||
.map((key) => ({
|
||||
usage:
|
||||
key === 'mauLimit' || key === 'tokenLimit'
|
||||
? periodicUsage[key]
|
||||
: currentSubscriptionUsage[key],
|
||||
usageKey: `subscription.usage.${usageKeyMap[key]}`,
|
||||
titleKey: `subscription.usage.${titleKeyMap[key]}`,
|
||||
unitPrice: usageKeyPriceMap[key],
|
||||
...conditional(
|
||||
currentSubscriptionFromNewPricingModel.planId === ReservedPlanId.Pro && {
|
||||
tooltipKey: `subscription.usage.${tooltipKeyMap[key]}`,
|
||||
}
|
||||
),
|
||||
...cond(
|
||||
(key === 'tokenLimit' || key === 'mauLimit') && { quota: currentSubscriptionQuota[key] }
|
||||
),
|
||||
}));
|
||||
|
||||
return isDevFeaturesEnabled &&
|
||||
currentSubscriptionFromNewPricingModel.planId === ReservedPlanId.Pro ? (
|
||||
return isDevFeaturesEnabled ? (
|
||||
<div>
|
||||
<div className={classNames(styles.planCycle, styles.planCycleNewPricingModel)}>
|
||||
<DynamicT
|
||||
|
@ -93,62 +101,19 @@ function PlanUsage({ currentSubscription, currentPlan, periodicUsage: rawPeriodi
|
|||
</div>
|
||||
<div className={styles.newPricingModelUsage}>
|
||||
{usages.map((props, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<ProPlanUsageCard key={index} className={styles.cardItem} {...props} />
|
||||
<PlanUsageCard
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
className={classNames(
|
||||
styles.cardItem,
|
||||
currentSubscriptionFromNewPricingModel.planId === ReservedPlanId.Free &&
|
||||
styles.freeUser
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : isDevFeaturesEnabled ? (
|
||||
<div>
|
||||
<div className={classNames(styles.planCycle, styles.planCycleNewPricingModel)}>
|
||||
<DynamicT
|
||||
forKey="subscription.plan_cycle"
|
||||
interpolation={{
|
||||
period: formatPeriod({
|
||||
periodStart: currentPeriodStart,
|
||||
periodEnd: currentPeriodEnd,
|
||||
}),
|
||||
renewDate: dayjs(currentPeriodEnd).add(1, 'day').format('MMM D, YYYY'),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.newPricingModelUsage}>
|
||||
<div className={classNames(styles.container, styles.freeUser)}>
|
||||
<div className={styles.usage}>
|
||||
{`${activeUsers} / `}
|
||||
{mauLimit === null ? (
|
||||
<DynamicT forKey="subscription.quota_table.unlimited" />
|
||||
) : (
|
||||
mauLimit.toLocaleString()
|
||||
)}
|
||||
{` MAU (${(mauUsagePercent * 100).toFixed(2)}%)`}
|
||||
</div>
|
||||
<div className={styles.usageBar}>
|
||||
<div
|
||||
className={classNames(styles.usageBarInner, mauUsagePercent >= 1 && styles.overuse)}
|
||||
style={{ width: `${Math.min(mauUsagePercent, 1) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classNames(styles.container, styles.freeUser)}>
|
||||
<div className={styles.usage}>
|
||||
{`${tokenUsage} / `}
|
||||
{tokenLimit === null ? (
|
||||
<DynamicT forKey="subscription.quota_table.unlimited" />
|
||||
) : (
|
||||
tokenLimit.toLocaleString()
|
||||
)}
|
||||
{` Token usage (${(tokenUsagePercent * 100).toFixed(2)}%)`}
|
||||
</div>
|
||||
<div className={styles.usageBar}>
|
||||
<div
|
||||
className={classNames(styles.usageBarInner, tokenUsagePercent >= 1 && styles.overuse)}
|
||||
style={{ width: `${Math.min(tokenUsagePercent, 1) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.usage}>
|
||||
|
|
|
@ -4,11 +4,12 @@ import useSWR from 'swr';
|
|||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
|
||||
import PageMeta from '@/components/PageMeta';
|
||||
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { Skeleton } from '@/containers/ConsoleContent/Sidebar';
|
||||
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
import { pickupFeaturedLogtoSkus, pickupFeaturedPlans } from '@/utils/subscription';
|
||||
|
||||
import Skeleton from '../components/Skeleton';
|
||||
|
||||
import CurrentPlan from './CurrentPlan';
|
||||
import PlanComparisonTable from './PlanComparisonTable';
|
||||
import SwitchPlanActionBar from './SwitchPlanActionBar';
|
||||
|
|
Loading…
Add table
Reference in a new issue