/** * `useHistory` hook — fetches and manages ban history data. */ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchHistory, fetchIpHistory } from "../api/history"; import { handleFetchError } from "../utils/fetchError"; import type { HistoryBanItem, HistoryQuery, IpDetailResponse, } from "../types/history"; // --------------------------------------------------------------------------- // useHistory — paginated list // --------------------------------------------------------------------------- export interface UseHistoryResult { items: HistoryBanItem[]; total: number; page: number; loading: boolean; error: string | null; setPage: (page: number) => void; refresh: () => void; } export function useHistory(query: HistoryQuery = {}): UseHistoryResult { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(query.page ?? 1); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortRef = useRef(null); const load = useCallback((): void => { abortRef.current?.abort(); abortRef.current = new AbortController(); setLoading(true); setError(null); fetchHistory({ ...query, page }) .then((resp) => { setItems(resp.items); setTotal(resp.total); }) .catch((err: unknown) => { handleFetchError(err, setError, "Failed to fetch history"); }) .finally((): void => { setLoading(false); }); }, [query, page]); useEffect((): (() => void) => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); return { items, total, page, loading, error, setPage, refresh: load }; } // --------------------------------------------------------------------------- // useIpHistory — per-IP detail // --------------------------------------------------------------------------- export interface UseIpHistoryResult { detail: IpDetailResponse | null; loading: boolean; error: string | null; refresh: () => void; } export function useIpHistory(ip: string): UseIpHistoryResult { const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const abortRef = useRef(null); const load = useCallback((): void => { abortRef.current?.abort(); abortRef.current = new AbortController(); setLoading(true); setError(null); fetchIpHistory(ip) .then((resp) => { setDetail(resp); }) .catch((err: unknown) => { handleFetchError(err, setError, "Failed to fetch IP history"); }) .finally((): void => { setLoading(false); }); }, [ip]); useEffect((): (() => void) => { load(); return (): void => { abortRef.current?.abort(); }; }, [load]); return { detail, loading, error, refresh: load }; }