All files / client/src/utils localStorage.ts

87.17% Statements 34/39
83.33% Branches 10/12
100% Functions 7/7
87.17% Lines 34/39

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 91 92                          364x 127x   364x                         364x 2678x 2678x 605x     2073x 2073x 1x 1x     2072x 2072x         2072x     364x 414x 414x 414x 414x     364x 42x 42x           364x   364x 378x     364x 4x       364x 364x   364x 360x   360x 360x           360x    
import { merge } from "remeda";
import { z } from "zod";
import { LocalStorageState } from "client/types/reduxTypes";
import { ProgramType } from "shared/types/models/programItem";
import { StringToJsonSchema } from "client/utils/zodUtils";
import { ActiveProgramType } from "shared/config/clientConfigTypes";
import { getProgramTypeSelectOptions } from "client/utils/getProgramTypeSelectOptions";
import { Locale } from "shared/types/locale";
import {
  browserStoragePrefix,
  localStorageStateKey,
} from "shared/constants/browserStorage";
 
const isActive = (programType: ActiveProgramType): boolean =>
  getProgramTypeSelectOptions().includes(programType);
 
const SessionSchema = z
  .object({
    login: z.object({ jwt: z.string() }).optional(),
    admin: z
      .object({
        activeProgramTypes: z.array(z.enum(ProgramType).refine(isActive)),
      })
      .optional(),
  })
  .strict();
 
type LocalStorage = z.infer<typeof SessionSchema>;
 
export const loadSession = (): LocalStorage | undefined => {
  const serializedValue = localStorage.getItem(localStorageStateKey);
  if (!serializedValue) {
    return undefined;
  }
 
  const parseJsonResult = StringToJsonSchema.safeParse(serializedValue);
  if (!parseJsonResult.success) {
    clearSession();
    return undefined;
  }
 
  const result = SessionSchema.safeParse(parseJsonResult.data);
  Iif (!result.success) {
    clearSession();
    return undefined;
  }
 
  return result.data;
};
 
export const saveSession = (state: Partial<LocalStorageState>): void => {
  const previousSession = loadSession();
  const newSession = previousSession ? merge(previousSession, state) : state;
  const serializedState = JSON.stringify(newSession);
  localStorage.setItem(localStorageStateKey, serializedState);
};
 
export const clearSession = (): void => {
  localStorage.removeItem(localStorageStateKey);
  sessionStorage.clear();
};
 
// Dismissed admin message is stored separately from the zod-strict 'state' object so a public
// (logged-out) visitor can remember their dismissal without a session. We store the dismissed
// message text itself, so a new or edited admin message no longer matches and shows again
const dismissedAdminMessageKey = `${browserStoragePrefix}-dismissedAdminMessage`;
 
export const getDismissedAdminMessage = (): string => {
  return localStorage.getItem(dismissedAdminMessageKey) ?? "";
};
 
export const saveDismissedAdminMessage = (adminMessage: string): void => {
  localStorage.setItem(dismissedAdminMessageKey, adminMessage);
};
 
// Locale uses same 'languageKey' as i18next but i18next has separate logic for handling localStorage
const languageKey = "i18nextLng";
const LanguageValueSchema = z.enum(Locale);
 
export const getLocalStorageLocale = (): string => {
  const serializedValue = localStorage.getItem(languageKey);
 
  const result = LanguageValueSchema.safeParse(serializedValue);
  Iif (!result.success) {
    localStorage.removeItem(languageKey);
    location.reload();
    return Locale.EN;
  }
 
  return result.data;
};