fix(ui): normalize ghcp quota tooltip and labels

This commit is contained in:
Tam Nhu Tran
2026-02-20 22:40:22 +07:00
parent 8d9d4987dc
commit 2c5b2af55a
4 changed files with 51 additions and 16 deletions
@@ -4,6 +4,7 @@
import {
cn,
formatQuotaPercent,
getCodexQuotaBreakdown,
getProviderMinQuota,
getProviderResetTime,
@@ -110,6 +111,7 @@ export function AccountCard({
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
{ label: 'Wk', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
].filter((row): row is { label: string; value: number } => row.value !== null);
const minQuotaLabel = minQuota !== null ? formatQuotaPercent(minQuota) : null;
// Tier badge (AGY only) - show P for Pro, U for Ultra
const showTierBadge =
@@ -240,7 +242,7 @@ export function AccountCard({
: 'text-red-500'
)}
>
{minQuota}%
{minQuotaLabel}%
</span>
</div>
{account.provider === 'codex' && codexQuotaRows.length > 0 && (
@@ -32,6 +32,7 @@ import {
} from 'lucide-react';
import {
cn,
formatQuotaPercent,
getCodexQuotaBreakdown,
getProviderMinQuota,
getProviderResetTime,
@@ -129,6 +130,7 @@ export function AccountItem({
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
{ label: 'Weekly', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
].filter((row): row is { label: string; value: number } => row.value !== null);
const minQuotaLabel = minQuota !== null ? formatQuotaPercent(minQuota) : null;
return (
<div
@@ -376,7 +378,9 @@ export function AccountItem({
className="h-2 flex-1"
indicatorClassName={getQuotaColor(minQuota)}
/>
<span className="text-xs font-medium w-10 text-right">{minQuota}%</span>
<span className="text-xs font-medium w-10 text-right">
{minQuotaLabel}%
</span>
</div>
)}
</TooltipTrigger>
@@ -6,6 +6,7 @@
import { Clock } from 'lucide-react';
import {
cn,
formatQuotaPercent,
formatResetTime,
getCodexQuotaBreakdown,
getCodexWindowDisplayLabel,
@@ -24,6 +25,16 @@ interface QuotaTooltipContentProps {
resetTime: string | null;
}
function formatPlanLabel(planType: string | null | undefined): string | null {
if (!planType) return null;
const normalized = planType
.split(/[\s_-]+/g)
.map((part) => part.trim())
.filter((part) => part.length > 0)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1));
return normalized.length > 0 ? normalized.join(' ') : planType;
}
/**
* Renders provider-specific quota tooltip content
* Uses type guards for proper TypeScript narrowing
@@ -131,24 +142,32 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
{ label: 'Completions', snapshot: quota.snapshots.completions },
];
const effectiveResetTime = quota.quotaResetDate ?? resetTime;
const planLabel = formatPlanLabel(quota.planType);
return (
<div className="text-xs space-y-1">
<p className="font-medium">Quota Snapshots:</p>
{quota.planType && <p className="text-muted-foreground">Plan: {quota.planType}</p>}
{snapshotRows.map(({ label, snapshot }) => (
<div key={label} className="flex justify-between gap-4">
<span className={cn(snapshot.percentRemaining < 20 && 'text-red-500')}>
{label}
{snapshot.unlimited ? ' (Unlimited)' : ''}
</span>
<span className={cn('font-mono', snapshot.percentRemaining < 20 && 'text-red-500')}>
{snapshot.unlimited
? 'inf'
: `${snapshot.percentRemaining}% (${snapshot.remaining}/${snapshot.entitlement})`}
</span>
</div>
))}
{planLabel && <p className="text-muted-foreground">Plan: {planLabel}</p>}
{snapshotRows.map(({ label, snapshot }) => {
const isLow = snapshot.percentRemaining < 20;
return (
<div key={label} className="space-y-0.5">
<div className="flex justify-between gap-4">
<span className={cn(isLow && 'text-red-500')}>{label}</span>
<span className={cn('font-mono', isLow && 'text-red-500')}>
{snapshot.unlimited
? 'Unlimited'
: `${formatQuotaPercent(snapshot.percentRemaining)}%`}
</span>
</div>
{!snapshot.unlimited && (
<div className="text-[11px] text-muted-foreground">
{snapshot.remaining}/{snapshot.entitlement} remaining
</div>
)}
</div>
);
})}
<ResetTimeIndicator resetTime={effectiveResetTime} />
</div>
);
+10
View File
@@ -13,6 +13,16 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Format quota percentage for UI display.
* Uses rounded whole numbers to keep quota labels compact and consistent.
*/
export function formatQuotaPercent(value: number): string {
if (!Number.isFinite(value)) return '0';
const clamped = Math.max(0, Math.min(100, value));
return `${Math.round(clamped)}`;
}
// Vibrant Tones Palette
const VIBRANT_TONES = [
'#f94144', // Strawberry Red