diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 33ceb3c..a65addc 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,21 +1,3 @@ -## 21) Silent auth error swallow in fetch error utility -- Where found: - - [frontend/src/utils/fetchError.ts](frontend/src/utils/fetchError.ts) -- Why this is needed: - - Silent return can drop actionable errors when no auth handler is registered. -- Goal: - - Ensure auth errors are handled deterministically with fallback logging/handling. -- What to do: - - Enforce handler registration or fallback behavior. -- Possible traps and issues: - - Duplicate redirect flows if multiple auth handlers exist. -- Docs changes needed: - - Add auth error handling contract for utilities. -- Doc references: - - [frontend/src/providers/AuthProvider.tsx](frontend/src/providers/AuthProvider.tsx) - ---- - ## 22) Magic strings are scattered in frontend storage keys - Where found: - [frontend/src/providers/AuthProvider.tsx](frontend/src/providers/AuthProvider.tsx) diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 8b7da02..a280913 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -36,6 +36,7 @@ import { useAuth } from "../hooks/useAuth"; import { useServerStatus } from "../hooks/useServerStatus"; import { useBlocklistStatus } from "../hooks/useBlocklistStatus"; import { useThemeMode } from "../providers/ThemeProvider"; +import { STORAGE_KEY_SIDEBAR_COLLAPSED } from "../utils/constants"; // --------------------------------------------------------------------------- // Styles @@ -43,7 +44,6 @@ import { useThemeMode } from "../providers/ThemeProvider"; const SIDEBAR_FULL = "240px"; const SIDEBAR_COLLAPSED = "48px"; -const SIDEBAR_COLLAPSED_STORAGE_KEY = "bangui_sidebar_collapsed"; const useStyles = makeStyles({ root: { @@ -237,7 +237,7 @@ export function MainLayout(): React.JSX.Element { const readSavedCollapsed = (): boolean => { try { - const savedValue = localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY); + const savedValue = localStorage.getItem(STORAGE_KEY_SIDEBAR_COLLAPSED); if (savedValue === "true") { return true; } @@ -259,14 +259,14 @@ export function MainLayout(): React.JSX.Element { useEffect(() => { try { - localStorage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, String(collapsed)); + localStorage.setItem(STORAGE_KEY_SIDEBAR_COLLAPSED, String(collapsed)); } catch { // Local storage may be unavailable in some environments. } }, [collapsed]); useEffect(() => { - const savedValue = localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY); + const savedValue = localStorage.getItem(STORAGE_KEY_SIDEBAR_COLLAPSED); if (savedValue !== null) { return undefined; } diff --git a/frontend/src/providers/AuthProvider.tsx b/frontend/src/providers/AuthProvider.tsx index e8c7df5..7c221f8 100644 --- a/frontend/src/providers/AuthProvider.tsx +++ b/frontend/src/providers/AuthProvider.tsx @@ -44,6 +44,7 @@ import { useNavigate } from "react-router-dom"; import * as authApi from "../api/auth"; import { setUnauthorizedHandler } from "../api/client"; import { setAuthErrorHandler } from "../utils/fetchError"; +import { STORAGE_KEY_AUTHENTICATED } from "../utils/constants"; import { SessionValidationLoading } from "../components/SessionValidationLoading"; import { useSessionValidation } from "../hooks/useSessionValidation"; @@ -65,8 +66,6 @@ export interface AuthContextValue { export const AuthContext = createContext(null); -const IS_AUTHENTICATED_KEY = "bangui_authenticated"; - // --------------------------------------------------------------------------- // Provider // --------------------------------------------------------------------------- @@ -87,14 +86,14 @@ export function AuthProvider({ children: React.ReactNode; }): React.JSX.Element { const [isAuthenticated, setIsAuthenticated] = useState(() => { - const stored = sessionStorage.getItem(IS_AUTHENTICATED_KEY); + const stored = sessionStorage.getItem(STORAGE_KEY_AUTHENTICATED); return stored === "true"; }); const [isValidating, setIsValidating] = useState(isAuthenticated); const navigate = useNavigate(); const handleSessionExpired = useCallback((): void => { - sessionStorage.removeItem(IS_AUTHENTICATED_KEY); + sessionStorage.removeItem(STORAGE_KEY_AUTHENTICATED); setIsAuthenticated(false); navigate("/login", { replace: true }); }, [navigate]); @@ -136,7 +135,7 @@ export function AuthProvider({ const login = useCallback(async (password: string): Promise => { await authApi.login(password); - sessionStorage.setItem(IS_AUTHENTICATED_KEY, "true"); + sessionStorage.setItem(STORAGE_KEY_AUTHENTICATED, "true"); setIsAuthenticated(true); }, []); @@ -145,7 +144,7 @@ export function AuthProvider({ await authApi.logout(); } finally { // Always clear local state even if the API call fails (e.g. expired session). - sessionStorage.removeItem(IS_AUTHENTICATED_KEY); + sessionStorage.removeItem(STORAGE_KEY_AUTHENTICATED); setIsAuthenticated(false); } }, []); diff --git a/frontend/src/providers/ThemeProvider.tsx b/frontend/src/providers/ThemeProvider.tsx index fe466ff..4e1499f 100644 --- a/frontend/src/providers/ThemeProvider.tsx +++ b/frontend/src/providers/ThemeProvider.tsx @@ -1,5 +1,6 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import type { ReactNode } from "react"; +import { STORAGE_KEY_THEME } from "../utils/constants"; type ThemeMode = "light" | "dark"; @@ -10,12 +11,11 @@ interface ThemeProviderContext { hasExplicitPreference: boolean; } -const THEME_STORAGE_KEY = "bangui_theme"; const ThemeModeContext = createContext(null); function readSavedTheme(): ThemeMode | null { try { - const value = localStorage.getItem(THEME_STORAGE_KEY); + const value = localStorage.getItem(STORAGE_KEY_THEME); if (value === "dark" || value === "light") { return value; } @@ -40,7 +40,7 @@ export function ThemeProvider({ children }: ThemeProviderProps): React.JSX.Eleme const setColorMode = useCallback((mode: ThemeMode): void => { try { - localStorage.setItem(THEME_STORAGE_KEY, mode); + localStorage.setItem(STORAGE_KEY_THEME, mode); } catch { // Ignore storage failures. } diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 02e4813..ba1ad31 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -3,6 +3,10 @@ */ import type { BanOriginFilter, TimeRange } from "../types/ban"; +// --------------------------------------------------------------------------- +// UI Constants +// --------------------------------------------------------------------------- + export const BAN_ORIGIN_FILTER_LABELS: Record = { all: "All", blocklist: "Blocklist", @@ -21,3 +25,16 @@ export const BAN_PAGE_SIZE = 100 as const; /** Items per page for the history table. */ export const HISTORY_PAGE_SIZE = 50 as const; + +// --------------------------------------------------------------------------- +// Storage Keys +// --------------------------------------------------------------------------- + +/** SessionStorage key for authentication state persistence. */ +export const STORAGE_KEY_AUTHENTICATED = "bangui_authenticated" as const; + +/** LocalStorage key for sidebar collapsed state. */ +export const STORAGE_KEY_SIDEBAR_COLLAPSED = "bangui_sidebar_collapsed" as const; + +/** LocalStorage key for theme preference. */ +export const STORAGE_KEY_THEME = "bangui_theme" as const;