fix: respect time format preference in UI

Add shared time formatting helpers that apply the Appearance time format preference consistently. Update visible time labels across chat, quota usage, scheduled tasks, tunnels, context details, git views, PR metadata, and passkey settings to use the selected 12-hour, 24-hour, or automatic format. Leave date-only and non-UI formatting untouched so unrelated behavior does not change.
This commit is contained in:
Bohdan Triapitsyn
2026-06-03 18:20:35 +03:00
parent 297663c2d7
commit 874dc15263
14 changed files with 143 additions and 55 deletions
@@ -15,8 +15,9 @@ import {
type StoredPasskey,
} from '@/lib/passkeys';
import { useI18n } from '@/lib/i18n';
import { useUIStore, type TimeFormatPreference } from '@/stores/useUIStore';
const formatTimestamp = (timestamp: number | null, neverUsedText: string) => {
const formatTimestamp = (timestamp: number | null, neverUsedText: string, timeFormatPreference: TimeFormatPreference) => {
if (!timestamp || !Number.isFinite(timestamp)) {
return neverUsedText;
}
@@ -24,11 +25,13 @@ const formatTimestamp = (timestamp: number | null, neverUsedText: string) => {
return new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium',
timeStyle: 'short',
hour12: timeFormatPreference === 'auto' ? undefined : timeFormatPreference === '12h',
}).format(timestamp);
};
export const PasskeySettings: React.FC = () => {
const { t } = useI18n();
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
const [supportsPasskeys, setSupportsPasskeys] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(true);
const [isRegistering, setIsRegistering] = React.useState(false);
@@ -230,10 +233,10 @@ export const PasskeySettings: React.FC = () => {
<span className="typography-meta text-muted-foreground truncate">
{passkey.lastUsedAt
? t('settings.openchamber.passkeys.item.lastUsed', {
time: formatTimestamp(passkey.lastUsedAt, t('settings.openchamber.passkeys.time.neverUsed')),
time: formatTimestamp(passkey.lastUsedAt, t('settings.openchamber.passkeys.time.neverUsed'), timeFormatPreference),
})
: t('settings.openchamber.passkeys.item.added', {
time: formatTimestamp(passkey.createdAt, t('settings.openchamber.passkeys.time.neverUsed')),
time: formatTimestamp(passkey.createdAt, t('settings.openchamber.passkeys.time.neverUsed'), timeFormatPreference),
})}
</span>
<Button
@@ -14,6 +14,8 @@ import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { openExternalUrl } from '@/lib/url';
import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
import { formatTimeForPreference } from '@/lib/timeFormat';
import { useUIStore, type TimeFormatPreference } from '@/stores/useUIStore';
type TunnelState =
| 'checking'
@@ -209,8 +211,8 @@ const formatRemaining = (remainingMs: number): string => {
return `${seconds}s`;
};
const formatAbsoluteTime = (timestamp: number): string => {
return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const formatAbsoluteTime = (timestamp: number, timeFormatPreference: TimeFormatPreference): string => {
return formatTimeForPreference(timestamp, timeFormatPreference, { hour: '2-digit', precision: 'second' });
};
const normalizePresetHostname = (value: string): string => {
@@ -267,6 +269,7 @@ const createPresetId = (): string => {
export const TunnelSettings: React.FC = () => {
const { t } = useI18n();
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
const tUnsafe = React.useCallback((key: string) => t(key as Parameters<typeof t>[0]), [t]);
const [state, setState] = React.useState<TunnelState>('checking');
const [tunnelInfo, setTunnelInfo] = React.useState<TunnelInfo | null>(null);
@@ -1167,7 +1170,7 @@ export const TunnelSettings: React.FC = () => {
{modeLabel}
</span>
<span className="typography-meta text-muted-foreground/80">
{t('settings.openchamber.tunnel.session.redeemedAt', { time: formatAbsoluteTime(record.createdAt) })}
{t('settings.openchamber.tunnel.session.redeemedAt', { time: formatAbsoluteTime(record.createdAt, timeFormatPreference) })}
</span>
<span className="typography-meta text-foreground">
{record.isActive
@@ -5,6 +5,7 @@ import { UsageProgressBar } from './UsageProgressBar';
import { PaceIndicator } from './PaceIndicator';
import { useQuotaStore } from '@/stores/useQuotaStore';
import { Checkbox } from '@/components/ui/checkbox';
import { useUIStore } from '@/stores/useUIStore';
interface UsageCardProps {
title: string;
@@ -25,10 +26,11 @@ export const UsageCard: React.FC<UsageCardProps> = ({
}) => {
const displayMode = useQuotaStore((state) => state.displayMode);
const showPredValues = useQuotaStore((state) => state.showPredValues);
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
const displayPercent = displayMode === 'remaining' ? window.remainingPercent : window.usedPercent;
const barLabel = displayMode === 'remaining' ? 'remaining' : 'used';
const percentLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted, timeFormatPreference);
const windowLabel = formatWindowLabel(title);
const paceInfo = React.useMemo(() => {
@@ -16,14 +16,13 @@ import { getAllModelFamilies, getDisplayModelName, sortModelFamilies, groupModel
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { formatTimeForPreference } from '@/lib/timeFormat';
import { useUIStore, type TimeFormatPreference } from '@/stores/useUIStore';
const formatTime = (timestamp: number | null) => {
const formatTime = (timestamp: number | null, timeFormatPreference: TimeFormatPreference) => {
if (!timestamp) return '-';
try {
return new Date(timestamp).toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit'
});
return formatTimeForPreference(timestamp, timeFormatPreference, { fallback: '-' });
} catch {
return '-';
}
@@ -36,6 +35,7 @@ interface ModelInfo {
export const UsagePage: React.FC = () => {
const { t } = useI18n();
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
const results = useQuotaStore((state) => state.results);
const selectedProviderId = useQuotaStore((state) => state.selectedProviderId);
const setSelectedProvider = useQuotaStore((state) => state.setSelectedProvider);
@@ -163,7 +163,7 @@ export const UsagePage: React.FC = () => {
{isLoading ? (
<span className="animate-pulse">{t('settings.usage.page.header.refreshing')}</span>
) : (
t('settings.usage.page.header.lastUpdated', { time: formatTime(lastUpdated) })
t('settings.usage.page.header.lastUpdated', { time: formatTime(lastUpdated, timeFormatPreference) })
)}
</p>
</div>