mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat(quota): enhance quota reset functionality with UI updates and styling improvements
This commit is contained in:
@@ -56,7 +56,6 @@ export function QuotaProgressBar({
|
|||||||
export interface QuotaRenderHelpers {
|
export interface QuotaRenderHelpers {
|
||||||
styles: typeof styles;
|
styles: typeof styles;
|
||||||
QuotaProgressBar: (props: QuotaProgressBarProps) => ReactElement;
|
QuotaProgressBar: (props: QuotaProgressBarProps) => ReactElement;
|
||||||
resetQuotaAction?: ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QuotaCardProps<TState extends QuotaStatusState> {
|
interface QuotaCardProps<TState extends QuotaStatusState> {
|
||||||
@@ -149,14 +148,16 @@ export function QuotaCard<TState extends QuotaStatusState>({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : quota ? (
|
) : quota ? (
|
||||||
renderQuotaItems(quota, t, { styles, QuotaProgressBar, resetQuotaAction })
|
renderQuotaItems(quota, t, { styles, QuotaProgressBar })
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.quotaMessage}>{t(idleMessageKey)}</div>
|
<div className={styles.quotaMessage}>{t(idleMessageKey)}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{onRefresh && quotaStatus !== 'idle' && (
|
{(resetQuotaAction || (onRefresh && quotaStatus !== 'idle')) && (
|
||||||
<div className={styles.quotaCardActions}>
|
<div className={styles.quotaCardActions}>
|
||||||
|
{resetQuotaAction}
|
||||||
|
{onRefresh && quotaStatus !== 'idle' && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -170,6 +171,7 @@ export function QuotaCard<TState extends QuotaStatusState>({
|
|||||||
{!quotaLoading && <IconRefreshCw size={14} />}
|
{!quotaLoading && <IconRefreshCw size={14} />}
|
||||||
{t('auth_files.quota_refresh_single')}
|
{t('auth_files.quota_refresh_single')}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -360,7 +360,9 @@ export function QuotaSection<TState extends QuotaStatusState, TData>({
|
|||||||
const isResettingQuota = resettingQuotaName === item.name;
|
const isResettingQuota = resettingQuotaName === item.name;
|
||||||
const canUseQuotaAction =
|
const canUseQuotaAction =
|
||||||
!disabled && !item.disabled && itemQuota?.status !== 'loading';
|
!disabled && !item.disabled && itemQuota?.status !== 'loading';
|
||||||
const resetQuotaAction = config.resetQuota ? (
|
const showResetQuotaAction =
|
||||||
|
itemQuota !== undefined && Boolean(config.canResetQuota?.(itemQuota));
|
||||||
|
const resetQuotaAction = config.resetQuota && showResetQuotaAction ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ export interface QuotaConfig<TState, TData> {
|
|||||||
filterFn: (file: AuthFileItem) => boolean;
|
filterFn: (file: AuthFileItem) => boolean;
|
||||||
fetchQuota: (file: AuthFileItem, t: TFunction) => Promise<TData>;
|
fetchQuota: (file: AuthFileItem, t: TFunction) => Promise<TData>;
|
||||||
resetQuota?: (file: AuthFileItem, t: TFunction) => Promise<TData>;
|
resetQuota?: (file: AuthFileItem, t: TFunction) => Promise<TData>;
|
||||||
|
canResetQuota?: (quota: TState) => boolean;
|
||||||
storeSelector: (state: QuotaStore) => Record<string, TState>;
|
storeSelector: (state: QuotaStore) => Record<string, TState>;
|
||||||
storeSetter: keyof QuotaStore;
|
storeSetter: keyof QuotaStore;
|
||||||
buildLoadingState: () => TState;
|
buildLoadingState: () => TState;
|
||||||
@@ -857,10 +858,6 @@ const renderCodexItems = (
|
|||||||
const planLabel = getPlanLabel(planType);
|
const planLabel = getPlanLabel(planType);
|
||||||
const isPremiumPlan = PREMIUM_CODEX_PLAN_TYPES.has(normalizePlanType(planType) ?? '');
|
const isPremiumPlan = PREMIUM_CODEX_PLAN_TYPES.has(normalizePlanType(planType) ?? '');
|
||||||
const expiryLabel = subscriptionActiveUntil ? formatDateTimeValue(subscriptionActiveUntil) : '';
|
const expiryLabel = subscriptionActiveUntil ? formatDateTimeValue(subscriptionActiveUntil) : '';
|
||||||
const resetQuotaAction =
|
|
||||||
rateLimitResetCreditsAvailableCount !== null && rateLimitResetCreditsAvailableCount > 0
|
|
||||||
? helpers.resetQuotaAction
|
|
||||||
: null;
|
|
||||||
const nodes: ReactNode[] = [];
|
const nodes: ReactNode[] = [];
|
||||||
|
|
||||||
if (planLabel || expiryLabel || rateLimitResetCreditsAvailableCount !== null) {
|
if (planLabel || expiryLabel || rateLimitResetCreditsAvailableCount !== null) {
|
||||||
@@ -922,16 +919,6 @@ const renderCodexItems = (
|
|||||||
nodes.push(h('div', { key: 'plan', className: styleMap.codexPlan }, ...planNodes));
|
nodes.push(h('div', { key: 'plan', className: styleMap.codexPlan }, ...planNodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetQuotaAction) {
|
|
||||||
nodes.push(
|
|
||||||
h(
|
|
||||||
'div',
|
|
||||||
{ key: 'reset-credits-action', className: styleMap.quotaInlineActions },
|
|
||||||
resetQuotaAction
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windows.length === 0) {
|
if (windows.length === 0) {
|
||||||
nodes.push(
|
nodes.push(
|
||||||
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('codex_quota.empty_windows'))
|
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('codex_quota.empty_windows'))
|
||||||
@@ -1354,6 +1341,7 @@ export const CODEX_CONFIG: QuotaConfig<
|
|||||||
filterFn: (file) => isCodexFile(file) && !isDisabledAuthFile(file),
|
filterFn: (file) => isCodexFile(file) && !isDisabledAuthFile(file),
|
||||||
fetchQuota: fetchCodexQuota,
|
fetchQuota: fetchCodexQuota,
|
||||||
resetQuota: resetCodexQuota,
|
resetQuota: resetCodexQuota,
|
||||||
|
canResetQuota: (quota) => (quota.rateLimitResetCreditsAvailableCount ?? 0) > 0,
|
||||||
storeSelector: (state) => state.codexQuota,
|
storeSelector: (state) => state.codexQuota,
|
||||||
storeSetter: 'setCodexQuota',
|
storeSetter: 'setCodexQuota',
|
||||||
buildLoadingState: () => ({ status: 'loading', windows: [] }),
|
buildLoadingState: () => ({ status: 'loading', windows: [] }),
|
||||||
|
|||||||
@@ -155,20 +155,22 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
|||||||
const config = getQuotaConfig(quotaType) as unknown as {
|
const config = getQuotaConfig(quotaType) as unknown as {
|
||||||
i18nPrefix: string;
|
i18nPrefix: string;
|
||||||
resetQuota?: (file: AuthFileItem, t: TFunction) => Promise<unknown>;
|
resetQuota?: (file: AuthFileItem, t: TFunction) => Promise<unknown>;
|
||||||
|
canResetQuota?: (quota: unknown) => boolean;
|
||||||
renderQuotaItems: (quota: unknown, t: TFunction, helpers: unknown) => unknown;
|
renderQuotaItems: (quota: unknown, t: TFunction, helpers: unknown) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
const quotaStatus = quota?.status ?? 'idle';
|
const quotaStatus = quota?.status ?? 'idle';
|
||||||
const canRefreshQuota = !disableControls && !file.disabled && !resettingQuota;
|
const canRefreshQuota = !disableControls && !file.disabled && !resettingQuota;
|
||||||
const canResetQuota = canRefreshQuota && quotaStatus !== 'loading';
|
const canUseResetQuota = canRefreshQuota && quotaStatus !== 'loading';
|
||||||
const resetQuotaAction = config.resetQuota ? (
|
const showResetQuotaAction = quota !== undefined && Boolean(config.canResetQuota?.(quota));
|
||||||
|
const resetQuotaAction = config.resetQuota && showResetQuotaAction ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={styles.quotaResetCreditButton}
|
className={styles.quotaResetCreditButton}
|
||||||
onClick={() => resetQuotaForFile()}
|
onClick={() => resetQuotaForFile()}
|
||||||
disabled={!canResetQuota}
|
disabled={!canUseResetQuota}
|
||||||
loading={resettingQuota}
|
loading={resettingQuota}
|
||||||
title={t('codex_quota.reset_button')}
|
title={t('codex_quota.reset_button')}
|
||||||
aria-label={t('codex_quota.reset_button')}
|
aria-label={t('codex_quota.reset_button')}
|
||||||
@@ -206,11 +208,13 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
|||||||
(config.renderQuotaItems(quota, t, {
|
(config.renderQuotaItems(quota, t, {
|
||||||
styles,
|
styles,
|
||||||
QuotaProgressBar,
|
QuotaProgressBar,
|
||||||
resetQuotaAction,
|
|
||||||
}) as ReactNode)
|
}) as ReactNode)
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.quotaMessage}>{t(`${config.i18nPrefix}.idle`)}</div>
|
<div className={styles.quotaMessage}>{t(`${config.i18nPrefix}.idle`)}</div>
|
||||||
)}
|
)}
|
||||||
|
{quotaStatus !== 'idle' && resetQuotaAction && (
|
||||||
|
<div className={styles.quotaCardActions}>{resetQuotaAction}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -640,12 +640,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quotaInlineActions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding-top: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quotaResetCreditButton:global(.btn.btn-sm) {
|
.quotaResetCreditButton:global(.btn.btn-sm) {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding-inline: 12px;
|
padding-inline: 12px;
|
||||||
@@ -658,6 +652,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quotaCardActions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: $spacing-xs;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-top: $spacing-xs;
|
||||||
|
}
|
||||||
|
|
||||||
.quotaError {
|
.quotaError {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--danger-color);
|
color: var(--danger-color);
|
||||||
|
|||||||
@@ -392,15 +392,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.quotaInlineActions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding-top: $spacing-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quotaCardActions {
|
.quotaCardActions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: $spacing-xs;
|
||||||
|
flex-wrap: wrap;
|
||||||
padding-top: $spacing-xs;
|
padding-top: $spacing-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user