diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 6ec9e7e..16ed1c5 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,28 +1,3 @@ -### T-15 · Replace `window` event bus for session expiry with React context callback - -**Where found:** `frontend/src/api/client.ts` — `window.dispatchEvent(new Event(SESSION_EXPIRED_EVENT))`; `frontend/src/providers/AuthProvider.tsx` — `window.addEventListener(SESSION_EXPIRED_EVENT, ...)` - -**Why this is needed:** Using `window` as a side-channel bypasses React's component tree, breaks in non-browser environments (SSR, test environments without full JSDOM), and creates an invisible coupling between the API client and the auth provider. It also fires globally — any code anywhere that dispatches `bangui:session-expired` on window will trigger a logout, with no tracing possible. - -**Goal:** The API client receives an `onUnauthorized` callback injected at the provider level, called directly instead of via a DOM event. - -**What to do:** -1. Add an `onUnauthorized` callback to the API client (e.g. a module-level setter `setUnauthorizedHandler(fn)`). -2. In `AuthProvider`, call `setUnauthorizedHandler(handleSessionExpired)` on mount and reset it on unmount. -3. In `client.ts`, call the handler directly instead of `window.dispatchEvent`. -4. Remove `SESSION_EXPIRED_EVENT` string constant and the `window.addEventListener` in `AuthProvider`. - -**Possible traps and issues:** -- The module-level handler setter is still global state — an alternative is to pass the handler as a parameter to `request()` via a context object, but that changes the API signature more significantly. -- Tests that mock `window.dispatchEvent` need updating. -- SSR / Vitest environments that already mock `window` may need adjustment. - -**Docs changes needed:** None. - -**Doc references:** `frontend/src/api/client.ts`, `frontend/src/providers/AuthProvider.tsx` - ---- - ### T-16 · Centralise `PAGE_SIZE` frontend constants **Where found:** `frontend/src/hooks/useBans.ts:14` (`PAGE_SIZE = 100`); `frontend/src/pages/HistoryPage.tsx:45` (`PAGE_SIZE = 50`) diff --git a/frontend/src/hooks/useBans.ts b/frontend/src/hooks/useBans.ts index 6a3dd9c..5574e87 100644 --- a/frontend/src/hooks/useBans.ts +++ b/frontend/src/hooks/useBans.ts @@ -8,11 +8,9 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { fetchBans } from "../api/dashboard"; import { handleFetchError } from "../utils/fetchError"; +import { BAN_PAGE_SIZE } from "../utils/constants"; import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban"; -/** Items per page for the ban table. */ -const PAGE_SIZE = 100; - /** Return value shape for {@link useBans}. */ export interface UseBansResult { /** Ban items for the current page. */ @@ -67,7 +65,7 @@ export function useBans( setError(null); try { - const data = await fetchBans(timeRange, page, PAGE_SIZE, origin, source, controller.signal); + const data = await fetchBans(timeRange, page, BAN_PAGE_SIZE, origin, source, controller.signal); if (controller.signal.aborted) return; setBanItems(data.items); setTotal(data.total); diff --git a/frontend/src/pages/HistoryPage.tsx b/frontend/src/pages/HistoryPage.tsx index 5511d8b..926e54b 100644 --- a/frontend/src/pages/HistoryPage.tsx +++ b/frontend/src/pages/HistoryPage.tsx @@ -32,6 +32,7 @@ import { import { DashboardFilterBar } from "../components/DashboardFilterBar"; import { useHistory } from "../hooks/useHistory"; import { IpDetailView } from "./history/IpDetailView"; +import { HISTORY_PAGE_SIZE } from "../utils/constants"; import type { HistoryBanItem, TimeRange } from "../types/history"; import type { BanOriginFilter } from "../types/ban"; @@ -42,8 +43,6 @@ import type { BanOriginFilter } from "../types/ban"; /** Ban counts at or above this threshold are highlighted. */ const HIGH_BAN_THRESHOLD = 5; -const PAGE_SIZE = 50; - // --------------------------------------------------------------------------- // Styles // --------------------------------------------------------------------------- @@ -206,7 +205,7 @@ export function HistoryPage(): React.JSX.Element { const { items, total, page: currentPage, loading, error, setPage: setCurrentPage, refresh } = useHistory( page, - PAGE_SIZE, + HISTORY_PAGE_SIZE, range, originFilter !== "all" ? originFilter : undefined, jailFilter.trim() || undefined, @@ -228,7 +227,7 @@ export function HistoryPage(): React.JSX.Element { setPage(1); }, [range, originFilter, jailFilter, ipFilter]); - const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + const totalPages = Math.max(1, Math.ceil(total / HISTORY_PAGE_SIZE)); // If an IP is selected, show the detail view. if (selectedIp !== null) { diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index c2ec297..02e4813 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -15,3 +15,9 @@ export const TIME_RANGE_LABELS: Record = { "30d": "Last 30 days", "365d": "Last 365 days", } as const; + +/** Items per page for the ban dashboard table. */ +export const BAN_PAGE_SIZE = 100 as const; + +/** Items per page for the history table. */ +export const HISTORY_PAGE_SIZE = 50 as const;