diff --git a/app/src/agentworld/pages/FeedSection.tsx b/app/src/agentworld/pages/FeedSection.tsx index 63bd633793..7cf4e808ee 100644 --- a/app/src/agentworld/pages/FeedSection.tsx +++ b/app/src/agentworld/pages/FeedSection.tsx @@ -32,6 +32,7 @@ import { import { fetchWalletStatus } from '../../services/walletApi'; import { apiClient } from '../AgentWorldShell'; import ConfirmDialog from '../components/ConfirmDialog'; +import { relativeTime } from '../utils/relativeTime'; const log = debug('agentworld:feed'); @@ -65,17 +66,6 @@ type WalletResolution = { agentId: string | null; configured: WalletConfigured } // ── Helpers ─────────────────────────────────────────────────────────────────── -function relativeTime(iso: string): string { - const ms = Date.now() - new Date(iso).getTime(); - const mins = Math.floor(ms / 60000); - if (mins < 1) return 'just now'; - if (mins < 60) return `${mins}m ago`; - const hrs = Math.floor(mins / 60); - if (hrs < 24) return `${hrs}h ago`; - const days = Math.floor(hrs / 24); - return `${days}d ago`; -} - function isWalletLocked(message: string): boolean { return ( message.includes('wallet is not configured') || diff --git a/app/src/agentworld/pages/JobsSection.tsx b/app/src/agentworld/pages/JobsSection.tsx index e6ca1b8257..7e5979db3a 100644 --- a/app/src/agentworld/pages/JobsSection.tsx +++ b/app/src/agentworld/pages/JobsSection.tsx @@ -29,6 +29,7 @@ import { useT } from '../../lib/i18n/I18nContext'; import { fetchWalletStatus } from '../../services/walletApi'; import { apiClient } from '../AgentWorldShell'; import { explorerTxUrl } from '../hooks/useX402Buy'; +import { relativeTime } from '../utils/relativeTime'; // ── State types ─────────────────────────────────────────────────────────────── @@ -39,18 +40,6 @@ type JobsState = // ── Helpers ─────────────────────────────────────────────────────────────────── -// TODO: extract shared relativeTime helper once Feed/Ledger/Jobs all use it. -function relativeTime(iso: string): string { - const ms = Date.now() - new Date(iso).getTime(); - const mins = Math.floor(ms / 60000); - if (mins < 1) return 'just now'; - if (mins < 60) return `${mins}m ago`; - const hrs = Math.floor(mins / 60); - if (hrs < 24) return `${hrs}h ago`; - const days = Math.floor(hrs / 24); - return `${days}d ago`; -} - /** * Group the integer part of a numeric amount with thousands separators while * preserving the original decimals ("1000000" → "1,000,000", "0.50" → "0.50"). diff --git a/app/src/agentworld/pages/LedgerSection.tsx b/app/src/agentworld/pages/LedgerSection.tsx index 51e27989fc..5672884c2c 100644 --- a/app/src/agentworld/pages/LedgerSection.tsx +++ b/app/src/agentworld/pages/LedgerSection.tsx @@ -9,7 +9,7 @@ * Pattern mirrors FeedSection: useState + useEffect fetch, PanelScaffold * wrapper, StatusBlock for loading/error/empty states. */ -import { useEffect, useState } from 'react'; +import { Fragment, useEffect, useState } from 'react'; import PanelScaffold from '../../components/layout/PanelScaffold'; import { type GqlLedgerTransaction } from '../../lib/agentworld/invokeApiClient'; @@ -17,6 +17,7 @@ import { apiClient } from '../AgentWorldShell'; import { decimalsForAsset, resolveAssetSymbol } from '../assets'; import { formatUnits, friendlyNetwork } from '../components/X402ConfirmDialog'; import { explorerTxUrl } from '../hooks/useX402Buy'; +import { relativeTime } from '../utils/relativeTime'; // ── State types ─────────────────────────────────────────────────────────────── @@ -27,17 +28,6 @@ type LedgerState = // ── Helpers ─────────────────────────────────────────────────────────────────── -function relativeTime(iso: string): string { - const ms = Date.now() - new Date(iso).getTime(); - const mins = Math.floor(ms / 60000); - if (mins < 1) return 'just now'; - if (mins < 60) return `${mins}m ago`; - const hrs = Math.floor(mins / 60); - if (hrs < 24) return `${hrs}h ago`; - const days = Math.floor(hrs / 24); - return `${days}d ago`; -} - export function abbreviateAddress(addr: string | undefined): string { if (!addr) return '—'; if (addr.length <= 12) return addr; @@ -287,14 +277,12 @@ function TransactionRow({

Metadata

{Object.entries(tx.metadata).map(([key, val]) => ( - <> -
- {key} -
-
+ +
{key}
+
{typeof val === 'string' ? val : JSON.stringify(val)}
- + ))}
diff --git a/app/src/agentworld/utils/relativeTime.test.ts b/app/src/agentworld/utils/relativeTime.test.ts new file mode 100644 index 0000000000..29c8287d62 --- /dev/null +++ b/app/src/agentworld/utils/relativeTime.test.ts @@ -0,0 +1,33 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { relativeTime } from './relativeTime'; + +describe('relativeTime', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('returns "just now" for under 1 minute ago', () => { + vi.setSystemTime(new Date('2025-01-01T00:00:30Z')); + expect(relativeTime('2025-01-01T00:00:00Z')).toBe('just now'); + }); + + it('returns minutes for under 1 hour ago', () => { + vi.setSystemTime(new Date('2025-01-01T00:45:00Z')); + expect(relativeTime('2025-01-01T00:00:00Z')).toBe('45m ago'); + }); + + it('returns hours for under 24 hours ago', () => { + vi.setSystemTime(new Date('2025-01-01T05:00:00Z')); + expect(relativeTime('2025-01-01T00:00:00Z')).toBe('5h ago'); + }); + + it('returns days for 24+ hours ago', () => { + vi.setSystemTime(new Date('2025-01-04T00:00:00Z')); + expect(relativeTime('2025-01-01T00:00:00Z')).toBe('3d ago'); + }); +}); diff --git a/app/src/agentworld/utils/relativeTime.ts b/app/src/agentworld/utils/relativeTime.ts new file mode 100644 index 0000000000..c644fb5c3b --- /dev/null +++ b/app/src/agentworld/utils/relativeTime.ts @@ -0,0 +1,12 @@ +export function relativeTime(iso: string): string { + const date = new Date(iso); + if (isNaN(date.getTime())) return 'just now'; + const ms = Date.now() - date.getTime(); + const mins = Math.floor(ms / 60000); + if (mins < 1) return 'just now'; + if (mins < 60) return `${mins}m ago`; + const hrs = Math.floor(mins / 60); + if (hrs < 24) return `${hrs}h ago`; + const days = Math.floor(hrs / 24); + return `${days}d ago`; +}