diff --git a/src/app/dashboard/audit/page.tsx b/src/app/dashboard/audit/page.tsx index ed27511..c588e75 100644 --- a/src/app/dashboard/audit/page.tsx +++ b/src/app/dashboard/audit/page.tsx @@ -1,12 +1,13 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useAuth } from "@/lib/useAuth"; import { listAuditLogsPage, type AuditLog } from "@/lib/audit"; import { errorMessage } from "@/lib/api"; import { Pagination } from "@/components/dashboard/Pagination"; import { DashboardShell } from "@/components/dashboard/DashboardShell"; import { PageSpinner } from "@/components/OctoSpinner"; +import { RelativeTime } from "@/components/RelativeTime"; const CATEGORIES = [ { value: "all", label: "All categories" }, @@ -36,18 +37,23 @@ export default function AuditLogsPage() { const [pageIndex, setPageIndex] = useState(0); const [nextCursor, setNextCursor] = useState(null); const [error, setError] = useState(null); + const requestId = useRef(0); const load = useCallback( (before: string | null) => { if (!token) return; + // Only the latest request may update state, so an older slow response is dropped. + const id = ++requestId.current; setLogs(null); setError(null); listAuditLogsPage(token, { category, search, before }) .then((page) => { + if (id !== requestId.current) return; setLogs(page.data); setNextCursor(page.next_cursor); }) .catch((err) => { + if (id !== requestId.current) return; setError(errorMessage(err, "Failed to load audit logs.")); setLogs([]); }); @@ -123,7 +129,6 @@ export default function AuditLogsPage() { Activity - User Category IP Address Time @@ -143,7 +148,6 @@ export default function AuditLogsPage() { · {l.target} )} - {user.email} - {timeAgo(l.created_at)} + )) @@ -202,7 +206,7 @@ function Row({ return ( {children} @@ -210,16 +214,3 @@ function Row({ ); } - -function timeAgo(iso: string): string { - const diff = Date.now() - new Date(iso).getTime(); - const s = Math.floor(diff / 1000); - if (s < 60) return "just now"; - const m = Math.floor(s / 60); - if (m < 60) return `${m} minute${m === 1 ? "" : "s"} ago`; - const h = Math.floor(m / 60); - if (h < 24) return `${h} hour${h === 1 ? "" : "s"} ago`; - const d = Math.floor(h / 24); - if (d < 30) return `${d} day${d === 1 ? "" : "s"} ago`; - return new Date(iso).toLocaleDateString(); -} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 5270499..bc08db4 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useAuth } from "@/lib/useAuth"; import { displayName } from "@/lib/auth"; @@ -30,12 +30,16 @@ export default function DashboardHome() { const [pageIndex, setPageIndex] = useState(0); const [nextCursor, setNextCursor] = useState(null); const [loadingPage, setLoadingPage] = useState(false); + const cancelPrevLoad = useRef<(() => void) | null>(null); const [loadError, setLoadError] = useState(false); const loadPage = useCallback( (before: string | null) => { if (!token) return; let aborted = false; + // Cancel the previous request so a slow older page can't overwrite the current one. + cancelPrevLoad.current?.(); + cancelPrevLoad.current = () => { aborted = true; }; setLoadingPage(true); setWallets(null); setLoadError(false); @@ -280,7 +284,7 @@ function WalletCard({ {sponsorEnabled ? "Enabled" : "Off"} - {/* Daily-budget cap is rendered only when the API returns a numeric budget. + {/* Daily-budget cap is rendered only when the API returns a numeric budget. The progress bar for daily-spend consumption is intentionally omitted for now because the current API response does not include a "fees_spent_today_stroops" field. When that lands, swap this label for a fill-bar the same way the wallet diff --git a/src/app/dashboard/wallets/[id]/page.tsx b/src/app/dashboard/wallets/[id]/page.tsx index e9961fd..db54d97 100644 --- a/src/app/dashboard/wallets/[id]/page.tsx +++ b/src/app/dashboard/wallets/[id]/page.tsx @@ -100,12 +100,11 @@ export default function WalletOverview({ // Silently re-fetch balances + recent transactions in the background so a new deposit shows up // without a manual refresh — no loading indicator here, that's only for explicit refresh actions. const pollFn = useCallback( - async () => { + async (signal: AbortSignal) => { if (!token) return; - const controller = new AbortController(); await Promise.all([ - getBalances(token, id, { signal: controller.signal }).then(setBalances).catch(() => {}), - listRecentTransactions(token, id, { signal: controller.signal }).then(setTxns).catch(() => {}), + getBalances(token, id, { signal }).then(setBalances).catch(() => {}), + listRecentTransactions(token, id, { signal }).then(setTxns).catch(() => {}), ]); }, [token, id],