import { useCallback, useEffect, useRef, useState } from "react" interface ReportResult { key: string data: T | null loading: boolean error: string | null updatedAt: Date | null } /** Keep successful refresh data, but never show another query's results. */ export function useReportQuery(key: string, read: (signal: AbortSignal) => Promise, delay = 0) { const [result, setResult] = useState>({ key, data: null, loading: true, error: null, updatedAt: null }) const sequence = useRef(0) const controller = useRef(null) const timer = useRef | null>(null) const refresh = useCallback(async () => { if (timer.current !== null) clearTimeout(timer.current) controller.current?.abort() const current = ++sequence.current const request = new AbortController() controller.current = request const timeout = setTimeout(() => { if (current !== sequence.current) return request.abort() setResult(previous => ({ ...previous, loading: false, error: "The request took too long. Please try again." })) }, 30000) setResult(previous => ({ key, data: previous.key === key ? previous.data : null, updatedAt: previous.key === key ? previous.updatedAt : null, loading: true, error: null, })) try { const data = await read(request.signal) if (current !== sequence.current || request.signal.aborted) return setResult({ key, data, loading: false, error: null, updatedAt: new Date() }) } catch (error) { if (current !== sequence.current || request.signal.aborted) return setResult(previous => ({ ...previous, loading: false, error: error instanceof ReportReadError ? error.message : "The data could not be loaded. Please try again." })) } finally { clearTimeout(timeout) } }, [key, read]) useEffect(() => { if (delay > 0) timer.current = setTimeout(() => { void refresh() }, delay) else void refresh() return () => { if (timer.current !== null) clearTimeout(timer.current) ++sequence.current controller.current?.abort() } }, [refresh, delay]) const matches = result.key === key return { data: matches ? result.data : null, loading: !matches || result.loading, error: matches ? result.error : null, updatedAt: matches ? result.updatedAt : null, refresh, } } export class ReportReadError extends Error {} export async function readReportJson(url: string, signal: AbortSignal): Promise { const response = await fetch(url, { signal, credentials: "same-origin", headers: { "X-WP-Nonce": window.swiftCommerceData?.restNonce || "" }, }) if (!response.ok) { if (response.status === 401 || response.status === 403) { throw new ReportReadError("Access could not be verified. Reload the dashboard and try again.") } throw new ReportReadError("The server could not load this data. Please try again.") } const result = await response.json() if (result?.success !== true) throw new ReportReadError("The server did not return a successful report. Please try again.") return result as T }