Standardise frontend hook fetch error handling and mark Task 12 done
This commit is contained in:
@@ -263,7 +263,7 @@ Reference: `Docs/Refactoring.md` for full analysis of each issue.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Task 12 — Standardise error handling in frontend hooks
|
### Task 12 — Standardise error handling in frontend hooks (✅ completed)
|
||||||
|
|
||||||
**Priority**: Low
|
**Priority**: Low
|
||||||
**Refactoring ref**: Refactoring.md §9
|
**Refactoring ref**: Refactoring.md §9
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchBanTrend } from "../api/dashboard";
|
import { fetchBanTrend } from "../api/dashboard";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { BanTrendBucket, BanOriginFilter, TimeRange } from "../types/ban";
|
import type { BanTrendBucket, BanOriginFilter, TimeRange } from "../types/ban";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -65,7 +66,7 @@ export function useBanTrend(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch trend data");
|
handleFetchError(err, setError, "Failed to fetch trend data");
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchBans } from "../api/dashboard";
|
import { fetchBans } from "../api/dashboard";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
||||||
|
|
||||||
/** Items per page for the ban table. */
|
/** Items per page for the ban table. */
|
||||||
@@ -63,7 +64,7 @@ export function useBans(
|
|||||||
setBanItems(data.items);
|
setBanItems(data.items);
|
||||||
setTotal(data.total);
|
setTotal(data.total);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
handleFetchError(err, setError, "Failed to fetch bans");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ import {
|
|||||||
updateBlocklist,
|
updateBlocklist,
|
||||||
updateSchedule,
|
updateSchedule,
|
||||||
} from "../api/blocklist";
|
} from "../api/blocklist";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type {
|
import type {
|
||||||
BlocklistSource,
|
BlocklistSource,
|
||||||
BlocklistSourceCreate,
|
BlocklistSourceCreate,
|
||||||
BlocklistSourceUpdate,
|
BlocklistSourceUpdate,
|
||||||
ImportLogListResponse,
|
ImportLogListResponse,
|
||||||
ImportRunResult,
|
ImportRunResult,
|
||||||
|
PreviewResponse,
|
||||||
ScheduleConfig,
|
ScheduleConfig,
|
||||||
ScheduleInfo,
|
ScheduleInfo,
|
||||||
} from "../types/blocklist";
|
} from "../types/blocklist";
|
||||||
@@ -65,7 +67,7 @@ export function useBlocklists(): UseBlocklistsReturn {
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (!ctrl.signal.aborted) {
|
if (!ctrl.signal.aborted) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load blocklists");
|
handleFetchError(err, setError, "Failed to load blocklists");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -144,7 +146,7 @@ export function useSchedule(): UseScheduleReturn {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load schedule");
|
handleFetchError(err, setError, "Failed to load schedule");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -200,7 +202,7 @@ export function useImportLog(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (!ctrl.signal.aborted) {
|
if (!ctrl.signal.aborted) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to load import log");
|
handleFetchError(err, setError, "Failed to load import log");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -242,7 +244,7 @@ export function useRunImport(): UseRunImportReturn {
|
|||||||
const result = await runImportNow();
|
const result = await runImportNow();
|
||||||
setLastResult(result);
|
setLastResult(result);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : "Import failed");
|
handleFetchError(err, setError, "Import failed");
|
||||||
} finally {
|
} finally {
|
||||||
setRunning(false);
|
setRunning(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import {
|
|||||||
flushLogs,
|
flushLogs,
|
||||||
previewLog,
|
previewLog,
|
||||||
reloadConfig,
|
reloadConfig,
|
||||||
|
restartFail2Ban,
|
||||||
testRegex,
|
testRegex,
|
||||||
updateGlobalConfig,
|
updateGlobalConfig,
|
||||||
updateJailConfig,
|
updateJailConfig,
|
||||||
updateServerSettings,
|
updateServerSettings,
|
||||||
} from "../api/config";
|
} from "../api/config";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type {
|
import type {
|
||||||
AddLogPathRequest,
|
AddLogPathRequest,
|
||||||
GlobalConfig,
|
GlobalConfig,
|
||||||
@@ -65,9 +67,7 @@ export function useJailConfigs(): UseJailConfigsResult {
|
|||||||
setTotal(resp.total);
|
setTotal(resp.total);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch jail configs");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -128,9 +128,7 @@ export function useJailConfigDetail(name: string): UseJailConfigDetailResult {
|
|||||||
setJail(resp.jail);
|
setJail(resp.jail);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch jail config");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -191,9 +189,7 @@ export function useGlobalConfig(): UseGlobalConfigResult {
|
|||||||
fetchGlobalConfig()
|
fetchGlobalConfig()
|
||||||
.then(setConfig)
|
.then(setConfig)
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch global config");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -251,9 +247,7 @@ export function useServerSettings(): UseServerSettingsResult {
|
|||||||
setSettings(resp.settings);
|
setSettings(resp.settings);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch server settings");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchJails } from "../api/jails";
|
import { fetchJails } from "../api/jails";
|
||||||
import { fetchJailConfigs } from "../api/config";
|
import { fetchJailConfigs } from "../api/config";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { JailConfig } from "../types/config";
|
import type { JailConfig } from "../types/config";
|
||||||
import type { JailSummary } from "../types/jail";
|
import type { JailSummary } from "../types/jail";
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ export function useConfigActiveStatus(): UseConfigActiveStatusResult {
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (ctrl.signal.aborted) return;
|
if (ctrl.signal.aborted) return;
|
||||||
setError(err instanceof Error ? err.message : "Failed to load status.");
|
handleFetchError(err, setError, "Failed to load active status.");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Generic config hook for loading and saving a single entity.
|
* Generic config hook for loading and saving a single entity.
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
|
|
||||||
export interface UseConfigItemResult<T, U> {
|
export interface UseConfigItemResult<T, U> {
|
||||||
data: T | null;
|
data: T | null;
|
||||||
@@ -46,7 +47,7 @@ export function useConfigItem<T, U>(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
setError(err instanceof Error ? err.message : "Failed to load data");
|
handleFetchError(err, setError, "Failed to load data");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, [fetchFn]);
|
}, [fetchFn]);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchBansByCountry } from "../api/map";
|
import { fetchBansByCountry } from "../api/map";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { DashboardBanItem, BanOriginFilter, TimeRange } from "../types/ban";
|
import type { DashboardBanItem, BanOriginFilter, TimeRange } from "../types/ban";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -77,7 +78,7 @@ export function useDashboardCountryData(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
handleFetchError(err, setError, "Failed to fetch dashboard country data");
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchHistory, fetchIpHistory } from "../api/history";
|
import { fetchHistory, fetchIpHistory } from "../api/history";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type {
|
import type {
|
||||||
HistoryBanItem,
|
HistoryBanItem,
|
||||||
HistoryQuery,
|
HistoryQuery,
|
||||||
@@ -44,9 +45,7 @@ export function useHistory(query: HistoryQuery = {}): UseHistoryResult {
|
|||||||
setTotal(resp.total);
|
setTotal(resp.total);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch history");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally((): void => {
|
.finally((): void => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -91,9 +90,7 @@ export function useIpHistory(ip: string): UseIpHistoryResult {
|
|||||||
setDetail(resp);
|
setDetail(resp);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch IP history");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally((): void => {
|
.finally((): void => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchBansByJail } from "../api/dashboard";
|
import { fetchBansByJail } from "../api/dashboard";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { BanOriginFilter, JailBanCount, TimeRange } from "../types/ban";
|
import type { BanOriginFilter, JailBanCount, TimeRange } from "../types/ban";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -65,9 +66,7 @@ export function useJailDistribution(
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
setError(
|
handleFetchError(err, setError, "Failed to fetch jail distribution");
|
||||||
err instanceof Error ? err.message : "Failed to fetch jail distribution",
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import {
|
import {
|
||||||
addIgnoreIp,
|
addIgnoreIp,
|
||||||
banIp,
|
banIp,
|
||||||
@@ -92,7 +93,7 @@ export function useJails(): UseJailsResult {
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (!ctrl.signal.aborted) {
|
if (!ctrl.signal.aborted) {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
handleFetchError(err, setError, "Failed to load jails");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -195,7 +196,7 @@ export function useJailDetail(name: string): UseJailDetailResult {
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (!ctrl.signal.aborted) {
|
if (!ctrl.signal.aborted) {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
handleFetchError(err, setError, "Failed to fetch jail detail");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -309,7 +310,7 @@ export function useJailBannedIps(jailName: string): UseJailBannedIpsResult {
|
|||||||
setItems(resp.items);
|
setItems(resp.items);
|
||||||
setTotal(resp.total);
|
setTotal(resp.total);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
handleFetchError(err, setError, "Failed to fetch jailed IPs");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -415,7 +416,7 @@ export function useActiveBans(): UseActiveBansResult {
|
|||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (!ctrl.signal.aborted) {
|
if (!ctrl.signal.aborted) {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
handleFetchError(err, setError, "Failed to fetch active bans");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -496,7 +497,7 @@ export function useIpLookup(): UseIpLookupResult {
|
|||||||
setResult(res);
|
setResult(res);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
setError(err instanceof Error ? err.message : String(err));
|
handleFetchError(err, setError, "Failed to lookup IP");
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { fetchMapColorThresholds, updateMapColorThresholds } from "../api/config";
|
import { fetchMapColorThresholds, updateMapColorThresholds } from "../api/config";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type {
|
import type {
|
||||||
MapColorThresholdsResponse,
|
MapColorThresholdsResponse,
|
||||||
MapColorThresholdsUpdate,
|
MapColorThresholdsUpdate,
|
||||||
@@ -26,7 +27,7 @@ export function useMapColorThresholds(): UseMapColorThresholdsResult {
|
|||||||
const data = await fetchMapColorThresholds();
|
const data = await fetchMapColorThresholds();
|
||||||
setThresholds(data);
|
setThresholds(data);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch map color thresholds");
|
handleFetchError(err, setError, "Failed to fetch map color thresholds");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchBansByCountry } from "../api/map";
|
import { fetchBansByCountry } from "../api/map";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { BansByCountryResponse, MapBanItem, TimeRange } from "../types/map";
|
import type { BansByCountryResponse, MapBanItem, TimeRange } from "../types/map";
|
||||||
import type { BanOriginFilter } from "../types/ban";
|
import type { BanOriginFilter } from "../types/ban";
|
||||||
|
|
||||||
@@ -68,9 +69,7 @@ export function useMapData(
|
|||||||
setData(resp);
|
setData(resp);
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
if (err instanceof Error && err.name !== "AbortError") {
|
handleFetchError(err, setError, "Failed to fetch map data");
|
||||||
setError(err.message);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.finally((): void => {
|
.finally((): void => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { fetchServerStatus } from "../api/dashboard";
|
import { fetchServerStatus } from "../api/dashboard";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import type { ServerStatus } from "../types/server";
|
import type { ServerStatus } from "../types/server";
|
||||||
|
|
||||||
/** How often to poll the status endpoint (milliseconds). */
|
/** How often to poll the status endpoint (milliseconds). */
|
||||||
@@ -45,7 +46,7 @@ export function useServerStatus(): UseServerStatusResult {
|
|||||||
setStatus(data.status);
|
setStatus(data.status);
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch server status");
|
handleFetchError(err, setError, "Failed to fetch server status");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { ApiError } from "../api/client";
|
import { ApiError } from "../api/client";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
import { getSetupStatus, submitSetup } from "../api/setup";
|
import { getSetupStatus, submitSetup } from "../api/setup";
|
||||||
import type {
|
import type {
|
||||||
SetupRequest,
|
SetupRequest,
|
||||||
@@ -44,9 +45,11 @@ export function useSetup(): UseSetupResult {
|
|||||||
const resp = await getSetupStatus();
|
const resp = await getSetupStatus();
|
||||||
setStatus(resp);
|
setStatus(resp);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const errorMessage = err instanceof Error ? err.message : "Failed to fetch setup status";
|
const fallback = "Failed to fetch setup status";
|
||||||
console.warn("Setup status check failed:", errorMessage);
|
handleFetchError(err, setError, fallback);
|
||||||
setError(errorMessage);
|
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
||||||
|
console.warn("Setup status check failed:", err instanceof Error ? err.message : fallback);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { fetchTimezone } from "../api/setup";
|
import { fetchTimezone } from "../api/setup";
|
||||||
|
import { handleFetchError } from "../utils/fetchError";
|
||||||
|
|
||||||
export interface UseTimezoneDataResult {
|
export interface UseTimezoneDataResult {
|
||||||
timezone: string;
|
timezone: string;
|
||||||
@@ -21,7 +22,7 @@ export function useTimezoneData(): UseTimezoneDataResult {
|
|||||||
const resp = await fetchTimezone();
|
const resp = await fetchTimezone();
|
||||||
setTimezone(resp.timezone);
|
setTimezone(resp.timezone);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to fetch timezone");
|
handleFetchError(err, setError, "Failed to fetch timezone");
|
||||||
setTimezone("UTC");
|
setTimezone("UTC");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
14
frontend/src/utils/fetchError.ts
Normal file
14
frontend/src/utils/fetchError.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Normalize fetch error handling across hooks.
|
||||||
|
*/
|
||||||
|
export function handleFetchError(
|
||||||
|
err: unknown,
|
||||||
|
setError: (value: string | null) => void,
|
||||||
|
fallback: string = "Unknown error",
|
||||||
|
): void {
|
||||||
|
if (err instanceof DOMException && err.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(err instanceof Error ? err.message : fallback);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user