Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 9 additions & 18 deletions src/app/dashboard/audit/page.tsx
Original file line number Diff line number Diff line change
@@ -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" },
Expand Down Expand Up @@ -36,18 +37,23 @@ export default function AuditLogsPage() {
const [pageIndex, setPageIndex] = useState(0);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [error, setError] = useState<string | null>(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([]);
});
Expand Down Expand Up @@ -123,7 +129,6 @@ export default function AuditLogsPage() {
<thead className="bg-surface-raised text-xs uppercase tracking-wide text-muted">
<tr>
<th className="px-5 py-3">Activity</th>
<th className="px-5 py-3">User</th>
<th className="px-5 py-3">Category</th>
<th className="px-5 py-3">IP Address</th>
<th className="px-5 py-3 text-right">Time</th>
Expand All @@ -143,7 +148,6 @@ export default function AuditLogsPage() {
<span className="text-muted"> · {l.target}</span>
)}
</td>
<td className="px-5 py-3.5 text-muted">{user.email}</td>
<td className="px-5 py-3.5">
<span className="inline-flex items-center gap-1.5 text-xs capitalize text-muted">
<span
Expand All @@ -164,7 +168,7 @@ export default function AuditLogsPage() {
)}
</td>
<td className="px-5 py-3.5 text-right text-xs text-muted">
{timeAgo(l.created_at)}
<RelativeTime date={l.created_at} />
</td>
</tr>
))
Expand Down Expand Up @@ -202,24 +206,11 @@ function Row({
return (
<tr>
<td
colSpan={colSpan ? 5 : 1}
colSpan={colSpan ? 4 : 1}
className="px-5 py-10 text-center text-sm text-muted"
>
{children}
</td>
</tr>
);
}

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();
}
8 changes: 6 additions & 2 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -30,12 +30,16 @@ export default function DashboardHome() {
const [pageIndex, setPageIndex] = useState(0);
const [nextCursor, setNextCursor] = useState<string | null>(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);
Expand Down Expand Up @@ -280,7 +284,7 @@ function WalletCard({
{sponsorEnabled ? "Enabled" : "Off"}
</span>
</div>
{/* 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
Expand Down
7 changes: 3 additions & 4 deletions src/app/dashboard/wallets/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading