Consolidate frontend storage keys into constants module
- Move magic strings from AuthProvider, MainLayout, and ThemeProvider to frontend/src/utils/constants.ts - Add STORAGE_KEY_AUTHENTICATED, STORAGE_KEY_SIDEBAR_COLLAPSED, and STORAGE_KEY_THEME constants with JSDoc descriptions - Update all three files to import and use centralized keys - Prevents key drift and typo regressions across the frontend Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<AuthContextValue | null>(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<boolean>(() => {
|
||||
const stored = sessionStorage.getItem(IS_AUTHENTICATED_KEY);
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY_AUTHENTICATED);
|
||||
return stored === "true";
|
||||
});
|
||||
const [isValidating, setIsValidating] = useState<boolean>(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<void> => {
|
||||
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);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -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<ThemeProviderContext | null>(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.
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
*/
|
||||
import type { BanOriginFilter, TimeRange } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const BAN_ORIGIN_FILTER_LABELS: Record<BanOriginFilter, string> = {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user