mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 02:11:28 +00:00
fix(ui): normalize ghcp quota tooltip and labels
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user