/** * Generic quota card component. */ import { useTranslation } from 'react-i18next'; import type { ReactElement, ReactNode } from 'react'; import type { TFunction } from 'i18next'; import type { AuthFileItem, ResolvedTheme, ThemeColors } from '@/types'; import { TYPE_COLORS } from '@/utils/quota'; import styles from '@/pages/QuotaPage.module.scss'; type QuotaStatus = 'idle' | 'loading' | 'success' | 'error'; export interface QuotaStatusState { status: QuotaStatus; error?: string; errorStatus?: number; } export interface QuotaProgressBarProps { percent: number | null; highThreshold: number; mediumThreshold: number; } export function QuotaProgressBar({ percent, highThreshold, mediumThreshold }: QuotaProgressBarProps) { const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); const normalized = percent === null ? null : clamp(percent, 0, 100); const fillClass = normalized === null ? styles.quotaBarFillMedium : normalized >= highThreshold ? styles.quotaBarFillHigh : normalized >= mediumThreshold ? styles.quotaBarFillMedium : styles.quotaBarFillLow; const widthPercent = Math.round(normalized ?? 0); return (
); } export interface QuotaRenderHelpers { styles: typeof styles; QuotaProgressBar: (props: QuotaProgressBarProps) => ReactElement; } interface QuotaCardProps { item: AuthFileItem; quota?: TState; resolvedTheme: ResolvedTheme; i18nPrefix: string; cardClassName: string; defaultType: string; renderQuotaItems: (quota: TState, t: TFunction, helpers: QuotaRenderHelpers) => ReactNode; } export function QuotaCard({ item, quota, resolvedTheme, i18nPrefix, cardClassName, defaultType, renderQuotaItems }: QuotaCardProps) { const { t } = useTranslation(); const displayType = item.type || item.provider || defaultType; const typeColorSet = TYPE_COLORS[displayType] || TYPE_COLORS.unknown; const typeColor: ThemeColors = resolvedTheme === 'dark' && typeColorSet.dark ? typeColorSet.dark : typeColorSet.light; const quotaStatus = quota?.status ?? 'idle'; const quotaErrorMessage = resolveQuotaErrorMessage( t, quota?.errorStatus, quota?.error || t('common.unknown_error') ); const getTypeLabel = (type: string): string => { const key = `auth_files.filter_${type}`; const translated = t(key); if (translated !== key) return translated; if (type.toLowerCase() === 'iflow') return 'iFlow'; return type.charAt(0).toUpperCase() + type.slice(1); }; return (
{getTypeLabel(displayType)} {item.name}
{quotaStatus === 'loading' ? (
{t(`${i18nPrefix}.loading`)}
) : quotaStatus === 'idle' ? (
{t(`${i18nPrefix}.idle`)}
) : quotaStatus === 'error' ? (
{t(`${i18nPrefix}.load_failed`, { message: quotaErrorMessage })}
) : quota ? ( renderQuotaItems(quota, t, { styles, QuotaProgressBar }) ) : (
{t(`${i18nPrefix}.idle`)}
)}
); } const resolveQuotaErrorMessage = ( t: TFunction, status: number | undefined, fallback: string ): string => { if (status === 404) return t('common.quota_update_required'); if (status === 403) return t('common.quota_check_credential'); return fallback; };