Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useCallback, useMemo, useState } from "react";

import { apiPost } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { AlertError } from "@/components/AlertError";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import { EmptyState } from "@/components/EmptyState";
Expand Down Expand Up @@ -98,7 +99,7 @@ export default function AdminPage() {
toast.push("Admin pause toggle applied.", "info");
await refreshAfterAction();
} catch (e) {
const message = (e as Error).message;
const message = mapApiError(e).message;
setActionError(message);
toast.push(message, "error");
} finally {
Expand Down
3 changes: 2 additions & 1 deletion src/app/agents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { AlertError } from "@/components/AlertError";
import { EmptyState } from "@/components/EmptyState";
import { PageShell } from "@/components/PageShell";
Expand Down Expand Up @@ -78,7 +79,7 @@ export default function AgentsPage() {
})
.catch((e: Error) => {
if (cancelled) return;
setError(e.message ?? "failed to load");
setError(mapApiError(e, "failed to load").message);
setPageCount(1);
})
.finally(() => {
Expand Down
8 changes: 5 additions & 3 deletions src/app/api-keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { PageShell } from "@/components/PageShell";
import { useCallback, useEffect, useState, useMemo } from "react";
import { apiGet, apiPost, apiDelete } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { AlertError } from "@/components/AlertError";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import { CopyButton } from "@/components/CopyButton";
Expand Down Expand Up @@ -98,6 +99,7 @@ export default function ApiKeysPage() {
);

const items = fetchState.status === "ok" ? fetchState.items : null;
const error = fetchState.status === "error" ? fetchState.message : null;
const tableData = useMemo(() => items ?? [], [items]);

const load = useCallback(
Expand All @@ -107,7 +109,7 @@ export default function ApiKeysPage() {
// shows the empty state instead of rendering blank.
.then((b) => setFetchState({ status: "ok", items: b.items ?? [] }))
.catch((e: Error) =>
setFetchState({ status: "error", message: e.message })
setFetchState({ status: "error", message: mapApiError(e).message })
),
[]
);
Expand All @@ -132,7 +134,7 @@ export default function ApiKeysPage() {
setLabel("");
await load();
} catch (err) {
setActionError((err as Error).message);
setActionError(mapApiError(err).message);
}
};

Expand All @@ -142,7 +144,7 @@ export default function ApiKeysPage() {
await apiDelete(`/api/v1/api-keys/${prefix}`);
await load();
} catch (err) {
setActionError((err as Error).message);
setActionError(mapApiError(err).message);
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/app/events/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PageShell } from "@/components/PageShell";
import { SearchBar } from "@/components/SearchBar";
import { Spinner } from "@/components/Spinner";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { MAX_RENDERED_ROWS, safeFormatTimestamp, safeStringify } from "@/lib/format";
import { useDebounce } from "@/lib/useDebounce";

Expand Down Expand Up @@ -189,7 +190,7 @@ export default function EventsPage() {
})
.catch((e: unknown) => {
if (cancelled) return;
const message = e instanceof Error ? e.message : "Failed to load events";
const message = mapApiError(e, "Failed to load events").message;
setError(message);
setItems([]);
})
Expand Down
3 changes: 2 additions & 1 deletion src/app/export/ExportActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Spinner } from "@/components/Spinner";
import { useToast } from "@/components/ToastProvider";
import { useState, useMemo } from "react";
import { mapApiError } from "@/lib/mapApiError";

type ExportFormat = "json" | "csv";

Expand Down Expand Up @@ -105,7 +106,7 @@ export function ExportActions({ apiBase }: Props) {
URL.revokeObjectURL(blobUrl);
toast.push(`${format.toUpperCase()} export downloaded.`, "info");
} catch (err) {
setError((err as Error).message || "Export failed");
setError(mapApiError(err, "Export failed").message);
} finally {
setDownloading(null);
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/pageTitles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const pageTitles = {
transactions: "Transactions",
apiKeys: "API keys",
search: "Search",
onboarding: "Onboarding",
reports: "Reports",
} as const;

export const serviceTitle = (serviceId: string) => `Service ${serviceId}`;
Expand Down
3 changes: 2 additions & 1 deletion src/app/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useEffect, useMemo, useReducer, useRef, useState } from "react";
import { SearchBar } from "@/components/SearchBar";
import { Spinner } from "@/components/Spinner";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { MAX_RENDERED_ROWS } from "@/lib/format";
import { useDebounce } from "@/lib/useDebounce";
import { EmptyState } from "@/components/EmptyState";
Expand Down Expand Up @@ -108,7 +109,7 @@ export default function SearchPage() {
if (!isCurrentRequest()) return;
dispatchSearch({
type: "error",
error: (e as Error).message || "Search failed",
error: mapApiError(e, "Search failed").message,
});
});

Expand Down
4 changes: 3 additions & 1 deletion src/app/services/[serviceId]/agents/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ describe("ServiceAgentsPage", () => {
);
});

expect(screen.getByText("Page 2 of 3")).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText("Page 2 of 3")).toBeInTheDocument();
});
expect(screen.getByText("26.")).toBeInTheDocument();
});

Expand Down
3 changes: 2 additions & 1 deletion src/app/services/[serviceId]/agents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useEffect, useMemo, useState, use, Suspense } from "react";
import Link from "next/link";
import { useSearchParams, useRouter } from "next/navigation";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { MAX_RENDERED_ROWS } from "@/lib/format";
import { EmptyState } from "@/components/EmptyState";
import { Pagination } from "@/components/Pagination";
Expand Down Expand Up @@ -61,7 +62,7 @@ function ServiceAgentsContent({
})
.catch((e: Error) => {
if (cancelled) return;
setError(e.message ?? "failed to load");
setError(mapApiError(e, "failed to load").message);
setPageCount(1);
})
.finally(() => {
Expand Down
5 changes: 3 additions & 2 deletions src/app/services/[serviceId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState, use } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { apiGet, apiPatch } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { PageShell } from "@/components/PageShell";
import { TextField } from "@/components/TextField";
import { Spinner } from "@/components/Spinner";
Expand Down Expand Up @@ -46,7 +47,7 @@ export default function EditServicePage({
setPrice(prefilled);
setOriginalPrice(prefilled);
} catch (e) {
setPrefillError((e as Error).message);
setPrefillError(mapApiError(e).message);
} finally {
setPrefillLoading(false);
}
Expand Down Expand Up @@ -94,7 +95,7 @@ export default function EditServicePage({
toast.push("Price updated.", "info");
router.push(`/services/${encodeURIComponent(serviceId)}`);
} catch (err) {
setError((err as Error).message);
setError(mapApiError(err).message);
} finally {
setSaving(false);
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/services/[serviceId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { use } from "react";
import Link from "next/link";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { ErrorMessage } from "@/components/ErrorMessage";
import { Badge } from "@/components/Badge";
import { CopyButton } from "@/components/CopyButton";
Expand All @@ -28,7 +29,7 @@ export default function ServiceDetailPage({
let cancelled = false;
apiGet<Service>(`/api/v1/services/${encodeURIComponent(serviceId)}`)
.then((s) => { if (!cancelled) setService(s); })
.catch((e) => { if (!cancelled) setError(e.message); });
.catch((e) => { if (!cancelled) setError(mapApiError(e).message); });
apiGet<Rollup>(`/api/v1/services/${encodeURIComponent(serviceId)}/usage`)
.then((r) => { if (!cancelled) setRollup(r); })
.catch(() => {
Expand Down
3 changes: 2 additions & 1 deletion src/app/services/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { apiGet } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { ErrorMessage } from "@/components/ErrorMessage";
import { EmptyState } from "@/components/EmptyState";
import { PageShell } from "@/components/PageShell";
Expand Down Expand Up @@ -240,7 +241,7 @@ export default function ServicesPage() {
})
.catch((e) => {
if (cancelled) return;
setError(e.message ?? "failed to load");
setError(mapApiError(e, "failed to load").message);
setPageCount(1);
})
.finally(() => {
Expand Down
44 changes: 22 additions & 22 deletions src/app/usage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@ import { ErrorMessage } from "@/components/ErrorMessage";
import { Spinner } from "@/components/Spinner";
import { PageShell } from "@/components/PageShell";
import { TextField } from "@/components/TextField";
import type { ApiError } from "@/lib/apiClient";
import { apiGet, apiPost } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import type { FormEvent } from "react";
import { useCallback, useMemo, useState } from "react";
import { parsePositiveInt } from "@/lib/validateNumber";
import { validateIdentifier } from "@/lib/validateId";
import { useUsageAnnouncement } from "./useUsageAnnouncement";
import {
PresetKey,
PRESET_RANGES,
toISODate,
buildDateRangeAnnouncement,
} from "./dateRange";
import { UsageDateRangeFilters } from "./UsageDateRangeFilters";
import {
type UsageRow,
UsageQueryRows,
deriveUsageRows,
} from "./UsageQueryRows";

type QueryResult = UsageRow;

Expand All @@ -26,24 +38,7 @@ type QueryStatus =
| { kind: "ok"; result: QueryResult | null }
| { kind: "error"; message: string; requestId?: string };

function describeError(error: unknown): {
message: string;
requestId?: string;
} {
const apiError = error as Partial<ApiError> | null | undefined;
return {
message:
typeof apiError?.message === "string" && apiError.message.length > 0
? apiError.message
: error instanceof Error
? error.message
: "request failed",
requestId:
typeof apiError?.requestId === "string" && apiError.requestId.length > 0
? apiError.requestId
: undefined,
};
}


export default function UsagePage() {
const [agent, setAgent] = useState("");
Expand Down Expand Up @@ -74,7 +69,12 @@ export default function UsagePage() {

const queryAnnouncement = useUsageAnnouncement(queryResult);

const applyPreset = (key: PresetKey) => {
const queryRows = useMemo(
() => deriveUsageRows(queryResult.kind === "ok" ? queryResult.result : null),
[queryResult],
);

const applyPreset = useCallback((key: PresetKey) => {
setActivePreset(key);
if (key === "custom") {
setStartDate("");
Expand Down Expand Up @@ -123,7 +123,7 @@ export default function UsagePage() {
});
setStatus({ kind: "ok", total: body?.total });
} catch (error) {
const { message, requestId } = describeError(error);
const { message, requestId } = mapApiError(error);
setStatus({ kind: "error", message, requestId });
}
};
Expand Down Expand Up @@ -154,7 +154,7 @@ export default function UsagePage() {
);
setQueryResult({ kind: "ok", result: result ?? null });
} catch (error) {
const { message, requestId } = describeError(error);
const { message, requestId } = mapApiError(error);
setQueryResult({ kind: "error", message, requestId });
}
};
Expand Down
9 changes: 5 additions & 4 deletions src/app/webhooks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { PageShell } from "@/components/PageShell";
import { useEffect, useState } from "react";
import { apiGet, apiPost, apiDelete } from "@/lib/apiClient";
import { mapApiError } from "@/lib/mapApiError";
import { AlertError } from "@/components/AlertError";
import { ConfirmDialog } from "@/components/ConfirmDialog";
import { TimeAgo } from "@/components/TimeAgo";
Expand All @@ -21,13 +22,13 @@ export default function WebhooksPage() {
const load = () =>
apiGet<{ items: Webhook[] }>("/api/v1/webhooks")
.then((b) => setItems(b.items))
.catch((e) => setError(e.message));
.catch((e) => setError(mapApiError(e).message));

useEffect(() => {
let cancelled = false;
apiGet<{ items: Webhook[] }>("/api/v1/webhooks")
.then((b) => { if (!cancelled) setItems(b.items); })
.catch((e) => { if (!cancelled) setError(e.message); });
.catch((e) => { if (!cancelled) setError(mapApiError(e).message); });
return () => { cancelled = true; };
}, []);

Expand All @@ -43,7 +44,7 @@ export default function WebhooksPage() {
setUrl("");
await load();
} catch (err) {
setError((err as Error).message);
setError(mapApiError(err).message);
}
};

Expand All @@ -52,7 +53,7 @@ export default function WebhooksPage() {
await apiDelete(`/api/v1/webhooks/${id}`);
await load();
} catch (err) {
setError((err as Error).message);
setError(mapApiError(err).message);
}
};

Expand Down
Loading
Loading