fix: show quota reset times in local timezone (#1128)

Formats rate-limit reset timestamps in the client timezone
Keeps server-provided reset labels as fallbacks
Applies consistent reset labels across header, usage, and VS Code views
This commit is contained in:
Bohdan Triapitsyn
2026-05-15 01:03:36 +03:00
parent 7b8861144e
commit 29811ec03e
5 changed files with 48 additions and 9 deletions
+7 -5
View File
@@ -38,7 +38,7 @@ import { cn, hasModifier } from '@/lib/utils';
import { McpDropdownContent } from '@/components/mcp/McpDropdown';
import { McpIcon } from '@/components/icons/McpIcon';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { formatQuotaValueLabel, formatQuotaResetLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
import { updateDesktopSettings } from '@/lib/persistence';
@@ -447,14 +447,15 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
: null;
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
return (
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="min-w-0 flex items-center gap-2">
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
{window.resetAfterFormatted ?? window.resetAtFormatted ? (
{resetLabel ? (
<span className="truncate typography-micro text-muted-foreground">
{window.resetAfterFormatted ?? window.resetAtFormatted}
{resetLabel}
</span>
) : null}
</div>
@@ -2248,14 +2249,15 @@ export const Header: React.FC<HeaderProps> = ({
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
: null;
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
return (
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
<div className="flex min-w-0 items-center justify-between gap-3">
<div className="min-w-0 flex items-center gap-2">
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
{(window.resetAfterFormatted ?? window.resetAtFormatted) ? (
{resetLabel ? (
<span className="truncate typography-micro text-muted-foreground">
{window.resetAfterFormatted ?? window.resetAtFormatted}
{resetLabel}
</span>
) : null}
</div>
@@ -24,7 +24,7 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
import { Icon } from "@/components/icon/Icon";
import { formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { formatQuotaValueLabel, formatQuotaResetLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { updateDesktopSettings } from '@/lib/persistence';
@@ -850,7 +850,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
</div>
)}
<span className="flex items-center justify-between typography-micro text-muted-foreground text-[10px]">
<span>{window.resetAfterFormatted ?? window.resetAtFormatted ?? ''}</span>
<span>{formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted)}</span>
</span>
</span>
</DropdownMenuItem>
@@ -1,6 +1,6 @@
import React from 'react';
import type { UsageWindow } from '@/types';
import { formatQuotaValueLabel, formatWindowLabel, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { formatQuotaValueLabel, formatQuotaResetLabel, formatWindowLabel, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { UsageProgressBar } from './UsageProgressBar';
import { PaceIndicator } from './PaceIndicator';
import { useQuotaStore } from '@/stores/useQuotaStore';
@@ -27,7 +27,7 @@ export const UsageCard: React.FC<UsageCardProps> = ({
const displayPercent = displayMode === 'remaining' ? window.remainingPercent : window.usedPercent;
const barLabel = displayMode === 'remaining' ? 'remaining' : 'used';
const percentLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
const resetLabel = window.resetAfterFormatted ?? window.resetAtFormatted ?? '';
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
const windowLabel = formatWindowLabel(title);
const paceInfo = React.useMemo(() => {
+1
View File
@@ -4,6 +4,7 @@ export {
clampPercent,
formatPercent,
formatQuotaValueLabel,
formatQuotaResetLabel,
resolveUsageTone,
formatWindowLabel,
calculatePace,
+36
View File
@@ -19,6 +19,42 @@ export const formatQuotaValueLabel = (
return valueLabel ?? formatPercent(percent);
};
export const formatQuotaResetLabel = (
resetAt: number | null,
fallback?: string | null,
): string => {
if (!resetAt) {
return fallback ?? '';
}
try {
const resetDate = new Date(resetAt);
if (!Number.isFinite(resetDate.getTime())) {
return fallback ?? '';
}
const now = new Date();
const isToday = resetDate.toDateString() === now.toDateString();
if (isToday) {
return resetDate.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
});
}
return resetDate.toLocaleString(undefined, {
month: 'short',
day: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: '2-digit',
});
} catch {
return fallback ?? '';
}
};
export const resolveUsageTone = (percent: number | null): 'safe' | 'warn' | 'critical' => {
if (percent === null) {
return 'safe';