All files / utils staffTools.tsx

100% Statements 22/22
100% Branches 8/8
100% Functions 7/7
100% Lines 20/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                5x     96x 96x 96x             91x                       158x 158x       158x   158x       158x   88x         158x       158x   88x         158x 13x 13x 13x         13x     158x                                  
import { useCallback, useState, type ReactNode } from "react";
import { useCurrentUser, hasRole } from "main/utils/currentUser";
import {
  DEFAULT_STAFF_TOOL_SETTINGS,
  StaffToolsContext,
  type StaffToolSettings,
} from "main/utils/staffToolsContext";
 
const STORAGE_KEY = "staffTools";
 
function readStoredSettings(): StaffToolSettings {
  try {
    const parsed = JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? "");
    return {
      debugMode: parsed.debugMode === true,
      enableEditing: parsed.enableEditing === true,
    };
  } catch {
    // No stored value, corrupt JSON, or sessionStorage unavailable
    // (e.g. blocked by browser settings): start from the defaults.
    return DEFAULT_STAFF_TOOL_SETTINGS;
  }
}
 
// Provides the session-scoped Admin/Instructor tool toggles (see
// StaffToolSettings). Mount it only on the pages whose content the tools act
// on (the concept-graph pages); everywhere else the context's default keeps
// the tools off and the Footer toggles hidden. The flags are clamped to false
// for regular users even if sessionStorage says otherwise, and the toggles
// persist for the lifetime of the browser tab (sessionStorage), never
// server-side.
export function StaffToolsProvider({ children }: { children: ReactNode }) {
  const currentUser = useCurrentUser();
  const canUseStaffTools =
    hasRole(currentUser, "ROLE_ADMIN") ||
    hasRole(currentUser, "ROLE_INSTRUCTOR");
 
  const [settings, setSettings] =
    useState<StaffToolSettings>(readStoredSettings);
  const [newConceptHandler, setNewConceptHandler] = useState<
    (() => void) | null
  >(null);
 
  const registerNewConceptHandler = useCallback(
    (handler: (() => void) | null) => {
      setNewConceptHandler(() => handler);
    },
    [],
  );
 
  const [realignConceptsHandler, setRealignConceptsHandler] = useState<
    (() => void) | null
  >(null);
 
  const registerRealignConceptsHandler = useCallback(
    (handler: (() => void) | null) => {
      setRealignConceptsHandler(() => handler);
    },
    [],
  );
 
  const setStaffTool = (tool: keyof StaffToolSettings, value: boolean) => {
    const next = { ...settings, [tool]: value };
    try {
      sessionStorage.setItem(STORAGE_KEY, JSON.stringify(next));
    } catch {
      // sessionStorage not available; the toggle still works for this render
      // tree, it just won't survive a reload.
    }
    setSettings(next);
  };
 
  return (
    <StaffToolsContext.Provider
      value={{
        debugMode: canUseStaffTools && settings.debugMode,
        enableEditing: canUseStaffTools && settings.enableEditing,
        canUseStaffTools,
        setStaffTool,
        newConceptHandler,
        registerNewConceptHandler,
        realignConceptsHandler,
        registerRealignConceptsHandler,
      }}
    >
      {children}
    </StaffToolsContext.Provider>
  );
}