fix(compiler): migrate 6 list pages to useQuery (set-state-in-effect)

Replaces the useState + useEffect + apiFetch pattern with TanStack
Query in six admin list pages — same pattern, mechanical refactor:

- admin/tags/tag-list
- admin/ports/port-list
- admin/roles/role-list
- admin/users/user-list
- admin/document-templates/template-list
- admin/webhooks/page
- dashboard/timezone-drift-banner (also: detected-tz reads via
  useSyncExternalStore so render stays pure)

Side benefits: list refetches now share a query cache across tabs
(via @tanstack/query-broadcast-client-experimental that was wired
up earlier this branch), so when admin A edits a role in one tab,
admin B's tab sees the updated row without a manual reload.

set-state-in-effect warnings: 51 → 45.

Verified: tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 23:34:24 +02:00
parent d1c9469fa7
commit 6ca94ee3f1
7 changed files with 126 additions and 169 deletions

View File

@@ -1,6 +1,7 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { PageHeader } from '@/components/shared/page-header';
@@ -30,9 +31,10 @@ interface Webhook {
createdAt: string;
}
const WEBHOOKS_QUERY_KEY = ['admin', 'webhooks'] as const;
export default function WebhooksPage() {
const [webhooks, setWebhooks] = useState<Webhook[]>([]);
const [loading, setLoading] = useState(true);
const queryClient = useQueryClient();
const [formOpen, setFormOpen] = useState(false);
const [editTarget, setEditTarget] = useState<Webhook | null>(null);
const [deleteTarget, setDeleteTarget] = useState<Webhook | null>(null);
@@ -44,20 +46,12 @@ export default function WebhooksPage() {
masked: string;
} | null>(null);
const loadWebhooks = useCallback(async () => {
try {
const result = await apiFetch<{ data: Webhook[] }>('/api/v1/admin/webhooks');
setWebhooks(result.data);
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Failed to load webhooks');
} finally {
setLoading(false);
}
}, []);
const { data: webhooks = [], isLoading: loading } = useQuery<Webhook[]>({
queryKey: WEBHOOKS_QUERY_KEY,
queryFn: () => apiFetch<{ data: Webhook[] }>('/api/v1/admin/webhooks').then((r) => r.data),
});
useEffect(() => {
void loadWebhooks();
}, [loadWebhooks]);
const loadWebhooks = () => queryClient.invalidateQueries({ queryKey: WEBHOOKS_QUERY_KEY });
async function handleDelete() {
if (!deleteTarget) return;