0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(console): add token usage notification banner (#6898)

* feat(console): add token usage notification banner

add token usage notificaiton banner

* style(console): fix usage error style

fix usage error style
This commit is contained in:
simeng-li 2024-12-23 11:19:42 +08:00 committed by GitHub
parent 6dae2bf4f6
commit 74c9282495
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 126 additions and 2 deletions

View file

@ -24,7 +24,7 @@
color: var(--color-text);
&.quotaExceeded {
color: var(--color-danger-default);
color: var(--color-error);
}
}

View file

@ -159,7 +159,10 @@ function PlanUsageCard({
<div
className={classNames(
styles.description,
typeof usagePercent === 'number' && usagePercent >= 1 && styles.quotaExceeded
typeof usagePercent === 'number' &&
usagePercent >= 1 &&
!isPaidTenant &&
styles.quotaExceeded
)}
>
<Trans

View file

@ -0,0 +1,110 @@
import { useContext, useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { toastResponseError } from '@/cloud/hooks/use-cloud-api';
import { type NewSubscriptionPeriodicUsage } from '@/cloud/types/router';
import SkuName from '@/components/SkuName';
import { subscriptionPage } from '@/consts/pages';
import { latestProPlanId } from '@/consts/subscriptions';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import { TenantsContext } from '@/contexts/TenantsProvider';
import InlineNotification from '@/ds-components/InlineNotification';
import { useConfirmModal } from '@/hooks/use-confirm-modal';
import useSubscribe from '@/hooks/use-subscribe';
import { NotEligibleSwitchSkuModalContent } from '@/pages/TenantSettings/components/NotEligibleSwitchPlanModalContent';
import { isPaidPlan, parseExceededSkuQuotaLimitError } from '@/utils/subscription';
type Props = {
readonly className?: string;
readonly periodicUsage: NewSubscriptionPeriodicUsage;
};
function TokenLimitExceededNotification({ periodicUsage, className }: Props) {
const { currentTenantId } = useContext(TenantsContext);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
logtoSkus,
currentSubscriptionQuota,
currentSubscription: { planId, isEnterprisePlan },
} = useContext(SubscriptionDataContext);
const { subscribe } = useSubscribe();
const { show } = useConfirmModal();
const [isLoading, setIsLoading] = useState(false);
const proSku = useMemo(() => logtoSkus.find(({ id }) => id === latestProPlanId), [logtoSkus]);
const { tokenLimit } = currentSubscriptionQuota;
const tokenUsagePercent = useMemo(() => {
// Unlimited
if (tokenLimit === null) {
return 0;
}
return periodicUsage.tokenLimit / tokenLimit;
}, [periodicUsage.tokenLimit, tokenLimit]);
if (
tokenUsagePercent < 0.9 || // Usage is less than 90%
isPaidPlan(planId, isEnterprisePlan) || // Add-on enabled
!proSku // Pro SKU not found
) {
return null;
}
const isExceeded = tokenUsagePercent >= 1;
return (
<InlineNotification
severity={isExceeded ? 'error' : 'alert'}
action="subscription.upgrade_pro"
className={className}
isActionLoading={isLoading}
onClick={async () => {
try {
setIsLoading(true);
await subscribe({
skuId: proSku.id,
planId: proSku.id,
tenantId: currentTenantId,
callbackPage: subscriptionPage,
});
setIsLoading(false);
} catch (error: unknown) {
setIsLoading(false);
const [result, exceededSkuQuotaKeys] = await parseExceededSkuQuotaLimitError(error);
if (result) {
await show({
ModalContent: () => (
<NotEligibleSwitchSkuModalContent
targetSku={proSku}
exceededSkuQuotaKeys={exceededSkuQuotaKeys}
/>
),
title: 'subscription.not_eligible_modal.upgrade_title',
confirmButtonText: 'general.got_it',
confirmButtonType: 'primary',
isCancelButtonVisible: false,
});
return;
}
void toastResponseError(error);
}
}}
>
<Trans
components={{
planName: <SkuName skuId={planId} />,
}}
>
{t(`subscription.token_usage_notification.${isExceeded ? 'exceeded' : 'close_to_limit'}`)}
</Trans>
</InlineNotification>
);
}
export default TokenLimitExceededNotification;

View file

@ -13,6 +13,7 @@ import { isPaidPlan } from '@/utils/subscription';
import AddOnUsageChangesNotification from './AddOnUsageChangesNotification';
import MauLimitExceedNotification from './MauLimitExceededNotification';
import PaymentOverdueNotification from './PaymentOverdueNotification';
import TokenLimitExceededNotification from './TokenLimitExceededNotification';
import styles from './index.module.scss';
type Props = {
@ -58,6 +59,10 @@ function CurrentPlan({ periodicUsage, usageAddOnSkus }: Props) {
{isPaidPlan(planId, isEnterprisePlan) && !isEnterprisePlan && (
<AddOnUsageChangesNotification className={styles.notification} />
)}
<TokenLimitExceededNotification
periodicUsage={periodicUsage}
className={styles.notification}
/>
<MauLimitExceedNotification periodicUsage={periodicUsage} className={styles.notification} />
<PaymentOverdueNotification className={styles.notification} />
</FormCard>

View file

@ -69,6 +69,12 @@ const subscription = {
subscription_check_timeout: 'Subscription check timed out. Please refresh later.',
no_subscription: 'No subscription',
usage,
token_usage_notification: {
exceeded:
'You have exceeded your <planName/> token usage limit. Users will not be able to access the Logto service properly. Please upgrade your plan to premium promptly to avoid any inconvenience.',
close_to_limit:
'You almost reached your <planName/> token usage limit. Logto will stop granting tokens when the limit is reached. Please upgrade your plan to premium to avoid any inconvenience.',
},
};
export default Object.freeze(subscription);