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:
@@ -38,7 +38,7 @@ import { cn, hasModifier } from '@/lib/utils';
|
|||||||
import { McpDropdownContent } from '@/components/mcp/McpDropdown';
|
import { McpDropdownContent } from '@/components/mcp/McpDropdown';
|
||||||
import { McpIcon } from '@/components/icons/McpIcon';
|
import { McpIcon } from '@/components/icons/McpIcon';
|
||||||
import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
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 { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||||
import { updateDesktopSettings } from '@/lib/persistence';
|
import { updateDesktopSettings } from '@/lib/persistence';
|
||||||
@@ -447,14 +447,15 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
|||||||
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
||||||
: null;
|
: null;
|
||||||
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
||||||
|
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
|
||||||
return (
|
return (
|
||||||
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
|
<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="flex min-w-0 items-center justify-between gap-3">
|
||||||
<div className="min-w-0 flex items-center gap-2">
|
<div className="min-w-0 flex items-center gap-2">
|
||||||
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
|
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
|
||||||
{window.resetAfterFormatted ?? window.resetAtFormatted ? (
|
{resetLabel ? (
|
||||||
<span className="truncate typography-micro text-muted-foreground">
|
<span className="truncate typography-micro text-muted-foreground">
|
||||||
{window.resetAfterFormatted ?? window.resetAtFormatted}
|
{resetLabel}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -2248,14 +2249,15 @@ export const Header: React.FC<HeaderProps> = ({
|
|||||||
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
||||||
: null;
|
: null;
|
||||||
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
||||||
|
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
|
||||||
return (
|
return (
|
||||||
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
|
<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="flex min-w-0 items-center justify-between gap-3">
|
||||||
<div className="min-w-0 flex items-center gap-2">
|
<div className="min-w-0 flex items-center gap-2">
|
||||||
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
|
<span className="truncate typography-ui-label text-foreground">{formatWindowLabel(label)}</span>
|
||||||
{(window.resetAfterFormatted ?? window.resetAtFormatted) ? (
|
{resetLabel ? (
|
||||||
<span className="truncate typography-micro text-muted-foreground">
|
<span className="truncate typography-micro text-muted-foreground">
|
||||||
{window.resetAfterFormatted ?? window.resetAtFormatted}
|
{resetLabel}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
|
|||||||
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||||
import { Icon } from "@/components/icon/Icon";
|
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 { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
|
||||||
import { useUpdateStore } from '@/stores/useUpdateStore';
|
import { useUpdateStore } from '@/stores/useUpdateStore';
|
||||||
import { updateDesktopSettings } from '@/lib/persistence';
|
import { updateDesktopSettings } from '@/lib/persistence';
|
||||||
@@ -850,7 +850,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="flex items-center justify-between typography-micro text-muted-foreground text-[10px]">
|
<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>
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { UsageWindow } from '@/types';
|
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 { UsageProgressBar } from './UsageProgressBar';
|
||||||
import { PaceIndicator } from './PaceIndicator';
|
import { PaceIndicator } from './PaceIndicator';
|
||||||
import { useQuotaStore } from '@/stores/useQuotaStore';
|
import { useQuotaStore } from '@/stores/useQuotaStore';
|
||||||
@@ -27,7 +27,7 @@ export const UsageCard: React.FC<UsageCardProps> = ({
|
|||||||
const displayPercent = displayMode === 'remaining' ? window.remainingPercent : window.usedPercent;
|
const displayPercent = displayMode === 'remaining' ? window.remainingPercent : window.usedPercent;
|
||||||
const barLabel = displayMode === 'remaining' ? 'remaining' : 'used';
|
const barLabel = displayMode === 'remaining' ? 'remaining' : 'used';
|
||||||
const percentLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
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 windowLabel = formatWindowLabel(title);
|
||||||
|
|
||||||
const paceInfo = React.useMemo(() => {
|
const paceInfo = React.useMemo(() => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export {
|
|||||||
clampPercent,
|
clampPercent,
|
||||||
formatPercent,
|
formatPercent,
|
||||||
formatQuotaValueLabel,
|
formatQuotaValueLabel,
|
||||||
|
formatQuotaResetLabel,
|
||||||
resolveUsageTone,
|
resolveUsageTone,
|
||||||
formatWindowLabel,
|
formatWindowLabel,
|
||||||
calculatePace,
|
calculatePace,
|
||||||
|
|||||||
@@ -19,6 +19,42 @@ export const formatQuotaValueLabel = (
|
|||||||
return valueLabel ?? formatPercent(percent);
|
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' => {
|
export const resolveUsageTone = (percent: number | null): 'safe' | 'warn' | 'critical' => {
|
||||||
if (percent === null) {
|
if (percent === null) {
|
||||||
return 'safe';
|
return 'safe';
|
||||||
|
|||||||
Reference in New Issue
Block a user