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 { import {
cn, cn,
formatQuotaPercent,
getCodexQuotaBreakdown, getCodexQuotaBreakdown,
getProviderMinQuota, getProviderMinQuota,
getProviderResetTime, getProviderResetTime,
@@ -110,6 +111,7 @@ export function AccountCard({
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null }, { label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
{ label: 'Wk', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null }, { label: 'Wk', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
].filter((row): row is { label: string; value: number } => row.value !== 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 // Tier badge (AGY only) - show P for Pro, U for Ultra
const showTierBadge = const showTierBadge =
@@ -240,7 +242,7 @@ export function AccountCard({
: 'text-red-500' : 'text-red-500'
)} )}
> >
{minQuota}% {minQuotaLabel}%
</span> </span>
</div> </div>
{account.provider === 'codex' && codexQuotaRows.length > 0 && ( {account.provider === 'codex' && codexQuotaRows.length > 0 && (
@@ -32,6 +32,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { import {
cn, cn,
formatQuotaPercent,
getCodexQuotaBreakdown, getCodexQuotaBreakdown,
getProviderMinQuota, getProviderMinQuota,
getProviderResetTime, getProviderResetTime,
@@ -129,6 +130,7 @@ export function AccountItem({
{ label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null }, { label: '5h', value: codexBreakdown?.fiveHourWindow?.remainingPercent ?? null },
{ label: 'Weekly', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null }, { label: 'Weekly', value: codexBreakdown?.weeklyWindow?.remainingPercent ?? null },
].filter((row): row is { label: string; value: number } => row.value !== null); ].filter((row): row is { label: string; value: number } => row.value !== null);
const minQuotaLabel = minQuota !== null ? formatQuotaPercent(minQuota) : null;
return ( return (
<div <div
@@ -376,7 +378,9 @@ export function AccountItem({
className="h-2 flex-1" className="h-2 flex-1"
indicatorClassName={getQuotaColor(minQuota)} 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> </div>
)} )}
</TooltipTrigger> </TooltipTrigger>
@@ -6,6 +6,7 @@
import { Clock } from 'lucide-react'; import { Clock } from 'lucide-react';
import { import {
cn, cn,
formatQuotaPercent,
formatResetTime, formatResetTime,
getCodexQuotaBreakdown, getCodexQuotaBreakdown,
getCodexWindowDisplayLabel, getCodexWindowDisplayLabel,
@@ -24,6 +25,16 @@ interface QuotaTooltipContentProps {
resetTime: string | null; 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 * Renders provider-specific quota tooltip content
* Uses type guards for proper TypeScript narrowing * Uses type guards for proper TypeScript narrowing
@@ -131,24 +142,32 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
{ label: 'Completions', snapshot: quota.snapshots.completions }, { label: 'Completions', snapshot: quota.snapshots.completions },
]; ];
const effectiveResetTime = quota.quotaResetDate ?? resetTime; const effectiveResetTime = quota.quotaResetDate ?? resetTime;
const planLabel = formatPlanLabel(quota.planType);
return ( return (
<div className="text-xs space-y-1"> <div className="text-xs space-y-1">
<p className="font-medium">Quota Snapshots:</p> <p className="font-medium">Quota Snapshots:</p>
{quota.planType && <p className="text-muted-foreground">Plan: {quota.planType}</p>} {planLabel && <p className="text-muted-foreground">Plan: {planLabel}</p>}
{snapshotRows.map(({ label, snapshot }) => ( {snapshotRows.map(({ label, snapshot }) => {
<div key={label} className="flex justify-between gap-4"> const isLow = snapshot.percentRemaining < 20;
<span className={cn(snapshot.percentRemaining < 20 && 'text-red-500')}> return (
{label} <div key={label} className="space-y-0.5">
{snapshot.unlimited ? ' (Unlimited)' : ''} <div className="flex justify-between gap-4">
</span> <span className={cn(isLow && 'text-red-500')}>{label}</span>
<span className={cn('font-mono', snapshot.percentRemaining < 20 && 'text-red-500')}> <span className={cn('font-mono', isLow && 'text-red-500')}>
{snapshot.unlimited {snapshot.unlimited
? 'inf' ? 'Unlimited'
: `${snapshot.percentRemaining}% (${snapshot.remaining}/${snapshot.entitlement})`} : `${formatQuotaPercent(snapshot.percentRemaining)}%`}
</span> </span>
</div> </div>
))} {!snapshot.unlimited && (
<div className="text-[11px] text-muted-foreground">
{snapshot.remaining}/{snapshot.entitlement} remaining
</div>
)}
</div>
);
})}
<ResetTimeIndicator resetTime={effectiveResetTime} /> <ResetTimeIndicator resetTime={effectiveResetTime} />
</div> </div>
); );
+10
View File
@@ -13,6 +13,16 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); 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 // Vibrant Tones Palette
const VIBRANT_TONES = [ const VIBRANT_TONES = [
'#f94144', // Strawberry Red '#f94144', // Strawberry Red