T-16: Centralize PAGE_SIZE frontend constants
- Add BAN_PAGE_SIZE (100) and HISTORY_PAGE_SIZE (50) to frontend/src/utils/constants.ts - Replace local PAGE_SIZE definitions in useBans.ts and HistoryPage.tsx with imports - Eliminates risk of pagination constants silently diverging from backend defaults - Single source of truth for all pagination sizes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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
|
### 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`)
|
**Where found:** `frontend/src/hooks/useBans.ts:14` (`PAGE_SIZE = 100`); `frontend/src/pages/HistoryPage.tsx:45` (`PAGE_SIZE = 50`)
|
||||||
|
|||||||
@@ -8,11 +8,9 @@
|
|||||||
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 { handleFetchError } from "../utils/fetchError";
|
||||||
|
import { BAN_PAGE_SIZE } from "../utils/constants";
|
||||||
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
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}. */
|
/** Return value shape for {@link useBans}. */
|
||||||
export interface UseBansResult {
|
export interface UseBansResult {
|
||||||
/** Ban items for the current page. */
|
/** Ban items for the current page. */
|
||||||
@@ -67,7 +65,7 @@ export function useBans(
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
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;
|
if (controller.signal.aborted) return;
|
||||||
setBanItems(data.items);
|
setBanItems(data.items);
|
||||||
setTotal(data.total);
|
setTotal(data.total);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
import { DashboardFilterBar } from "../components/DashboardFilterBar";
|
import { DashboardFilterBar } from "../components/DashboardFilterBar";
|
||||||
import { useHistory } from "../hooks/useHistory";
|
import { useHistory } from "../hooks/useHistory";
|
||||||
import { IpDetailView } from "./history/IpDetailView";
|
import { IpDetailView } from "./history/IpDetailView";
|
||||||
|
import { HISTORY_PAGE_SIZE } from "../utils/constants";
|
||||||
import type { HistoryBanItem, TimeRange } from "../types/history";
|
import type { HistoryBanItem, TimeRange } from "../types/history";
|
||||||
import type { BanOriginFilter } from "../types/ban";
|
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. */
|
/** Ban counts at or above this threshold are highlighted. */
|
||||||
const HIGH_BAN_THRESHOLD = 5;
|
const HIGH_BAN_THRESHOLD = 5;
|
||||||
|
|
||||||
const PAGE_SIZE = 50;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Styles
|
// Styles
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -206,7 +205,7 @@ export function HistoryPage(): React.JSX.Element {
|
|||||||
const { items, total, page: currentPage, loading, error, setPage: setCurrentPage, refresh } =
|
const { items, total, page: currentPage, loading, error, setPage: setCurrentPage, refresh } =
|
||||||
useHistory(
|
useHistory(
|
||||||
page,
|
page,
|
||||||
PAGE_SIZE,
|
HISTORY_PAGE_SIZE,
|
||||||
range,
|
range,
|
||||||
originFilter !== "all" ? originFilter : undefined,
|
originFilter !== "all" ? originFilter : undefined,
|
||||||
jailFilter.trim() || undefined,
|
jailFilter.trim() || undefined,
|
||||||
@@ -228,7 +227,7 @@ export function HistoryPage(): React.JSX.Element {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
}, [range, originFilter, jailFilter, ipFilter]);
|
}, [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 an IP is selected, show the detail view.
|
||||||
if (selectedIp !== null) {
|
if (selectedIp !== null) {
|
||||||
|
|||||||
@@ -15,3 +15,9 @@ export const TIME_RANGE_LABELS: Record<TimeRange, string> = {
|
|||||||
"30d": "Last 30 days",
|
"30d": "Last 30 days",
|
||||||
"365d": "Last 365 days",
|
"365d": "Last 365 days",
|
||||||
} as const;
|
} 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user