diff --git a/src/app/(app)/settings/hooks/admin-query-keys.ts b/src/app/(app)/settings/hooks/admin-query-keys.ts new file mode 100644 index 00000000..dd8a9b9d --- /dev/null +++ b/src/app/(app)/settings/hooks/admin-query-keys.ts @@ -0,0 +1,7 @@ +export const adminQueryKeys = { + apiKeys: { + all: ['admin', 'api-keys'] as const, + list: () => [...adminQueryKeys.apiKeys.all, 'list'] as const, + detail: (id: string) => [...adminQueryKeys.apiKeys.all, 'detail', id] as const, + }, +}; diff --git a/src/app/(app)/settings/hooks/use-api-keys.ts b/src/app/(app)/settings/hooks/use-api-keys.ts index 0dd526c6..b2a2050f 100644 --- a/src/app/(app)/settings/hooks/use-api-keys.ts +++ b/src/app/(app)/settings/hooks/use-api-keys.ts @@ -1,7 +1,9 @@ 'use client'; -import { useCallback, useState } from 'react'; +import { useCallback } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { apiClient } from '@/lib/api-client'; +import { adminQueryKeys } from './admin-query-keys'; export type ApiKeyRecord = { id: string; @@ -18,75 +20,124 @@ export type ApiKeyRecord = { }; export function useApiKeys() { - const [items, setItems] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const queryClient = useQueryClient(); - const fetchApiKeys = useCallback(async () => { - setIsLoading(true); - setError(null); - try { - const res = await apiClient.get('api/api-keys'); + const { + data: items = [], + isLoading, + error: queryError, + refetch, + } = useQuery({ + queryKey: adminQueryKeys.apiKeys.list(), + queryFn: async () => { + const res = await apiClient.get('/api/api-keys'); if (!res.ok || !Array.isArray(res.data)) { throw new Error(res.error || `Failed to load API keys (${res.status})`); } - setItems(res.data); return res.data; - } catch (e) { - const msg = e instanceof Error ? e.message : 'Failed to load API keys'; - setError(msg); - throw e; - } finally { - setIsLoading(false); - } - }, []); + }, + }); - const createApiKey = useCallback(async (data: { name: string; description?: string; expiresAt?: string | null }) => { - const payload = { - name: data.name, - description: data.description || undefined, - expiresAt: data.expiresAt ?? null, - }; - const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>('api/api-keys', payload); - if (!res.ok || !res.data) { - throw new Error(res.error || `Failed to create API key (${res.status})`); + const error = queryError instanceof Error ? queryError.message : queryError ? String(queryError) : null; + + const fetchApiKeys = useCallback(async () => { + const result = await refetch(); + if (result.error) { + throw result.error; } - return res.data; - }, []); + return result.data ?? []; + }, [refetch]); - const updateApiKey = useCallback( - async (id: string, data: { name: string; description?: string; expiresAt?: string | null }) => { + const createApiKeyMutation = useMutation({ + mutationFn: async (data: { name: string; description?: string; expiresAt?: string | null }) => { const payload = { name: data.name, description: data.description || undefined, expiresAt: data.expiresAt ?? null, }; - const res = await apiClient.put(`api/api-keys/${encodeURIComponent(id)}`, payload); + const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>('/api/api-keys', payload); + if (!res.ok || !res.data) { + throw new Error(res.error || `Failed to create API key (${res.status})`); + } + return res.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); + }, + }); + + const updateApiKeyMutation = useMutation({ + mutationFn: async ({ + id, + data, + }: { + id: string; + data: { name: string; description?: string; expiresAt?: string | null }; + }) => { + const payload = { + name: data.name, + description: data.description || undefined, + expiresAt: data.expiresAt ?? null, + }; + const res = await apiClient.put(`/api/api-keys/${encodeURIComponent(id)}`, payload); if (!res.ok || !res.data) { throw new Error(res.error || `Failed to update API key (${res.status})`); } return res.data; }, - [], + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); + }, + }); + + const regenerateApiKeyMutation = useMutation({ + mutationFn: async (id: string) => { + const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>( + `/api/api-keys/${encodeURIComponent(id)}/regenerate`, + ); + if (!res.ok || !res.data) { + throw new Error(res.error || `Failed to regenerate API key (${res.status})`); + } + return res.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); + }, + }); + + const setApiKeyEnabledMutation = useMutation({ + mutationFn: async ({ id, enabled }: { id: string; enabled: boolean }) => { + const res = await apiClient.put(`/api/api-keys/${encodeURIComponent(id)}`, { enabled }); + if (!res.ok || !res.data) { + throw new Error(res.error || `Failed to ${enabled ? 'enable' : 'disable'} API key (${res.status})`); + } + return res.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); + }, + }); + + const createApiKey = useCallback( + (data: { name: string; description?: string; expiresAt?: string | null }) => createApiKeyMutation.mutateAsync(data), + [createApiKeyMutation], ); - const regenerateApiKey = useCallback(async (id: string) => { - const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>( - `api/api-keys/${encodeURIComponent(id)}/regenerate`, - ); - if (!res.ok || !res.data) { - throw new Error(res.error || `Failed to regenerate API key (${res.status})`); - } - return res.data; - }, []); + const updateApiKey = useCallback( + (id: string, data: { name: string; description?: string; expiresAt?: string | null }) => + updateApiKeyMutation.mutateAsync({ id, data }), + [updateApiKeyMutation], + ); - const setApiKeyEnabled = useCallback(async (id: string, enabled: boolean) => { - const res = await apiClient.put(`api/api-keys/${encodeURIComponent(id)}`, { enabled }); - if (!res.ok || !res.data) { - throw new Error(res.error || `Failed to ${enabled ? 'enable' : 'disable'} API key (${res.status})`); - } - return res.data; - }, []); + const regenerateApiKey = useCallback( + (id: string) => regenerateApiKeyMutation.mutateAsync(id), + [regenerateApiKeyMutation], + ); + + const setApiKeyEnabled = useCallback( + (id: string, enabled: boolean) => setApiKeyEnabledMutation.mutateAsync({ id, enabled }), + [setApiKeyEnabledMutation], + ); return { items, isLoading, error, fetchApiKeys, createApiKey, updateApiKey, regenerateApiKey, setApiKeyEnabled }; }