fix(audit): frontend HIGHs — surface fetch errors, kill href=#, invalidate queries, toast over alert

R2-H10: webhook-delivery-log and audit-log-list both swallowed fetch
errors silently — failed loads showed spinner forever or stale data.
Both now set a loadError state, show an inline retry banner, and fire
a toast.error. Same applies to audit-log loadMore.

R2-H11: audit-log-card rendered as `<a href="#">` — tapping on mobile
inserted `#` in the URL and scrolled to top (back-button trap).
ListCard now treats `href` as optional and renders a non-link `<div>`
when omitted; audit-log-card no longer passes href.

R2-H12: smart-archive-dialog only invalidated ['clients'] / ['berths']
/ ['interests']. Detail header kept showing Archived=false until hard
reload. Now also invalidates ['clients', clientId] and removes the
['client-archive-dossier', clientId] cache so re-open re-fetches.

R2-H13: client-list bulk mutation used native alert() on partial
failure (blocking the page) and had no onError handler. Replaced with
toast.warning / toast.success / toast.error.

1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 22:18:14 +02:00
parent a8c6c071e6
commit 5fc68a5f34
6 changed files with 79 additions and 31 deletions

View File

@@ -42,6 +42,7 @@ export function WebhookDeliveryLog({ webhookId }: Props) {
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState<string | null>(null);
const [retrying, setRetrying] = useState<string | null>(null);
const { can } = usePermissions();
const canReplay = can('admin', 'manage_webhooks');
@@ -63,14 +64,17 @@ export function WebhookDeliveryLog({ webhookId }: Props) {
async function load(p: number) {
setLoading(true);
setLoadError(null);
try {
const result = await apiFetch<{ data: Delivery[]; total: number }>(
`/api/v1/admin/webhooks/${webhookId}/deliveries?page=${p}&limit=25`,
);
setDeliveries(result.data);
setTotal(result.total);
} catch {
// ignore
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to load deliveries';
setLoadError(msg);
toast.error(msg);
} finally {
setLoading(false);
}
@@ -85,6 +89,17 @@ export function WebhookDeliveryLog({ webhookId }: Props) {
return <p className="text-sm text-muted-foreground">Loading deliveries...</p>;
}
if (loadError && deliveries.length === 0) {
return (
<div className="rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm space-y-2">
<p className="text-destructive">Couldn&rsquo;t load deliveries: {loadError}</p>
<Button size="sm" variant="outline" onClick={() => void load(page)}>
Retry
</Button>
</div>
);
}
if (!loading && deliveries.length === 0) {
return <p className="text-sm text-muted-foreground">No deliveries yet.</p>;
}