All files / src/contexts AuthContext.jsx

100% Statements 31/31
100% Branches 8/8
100% Functions 5/5
100% Lines 30/30

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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                                          1x                     1x   9x   9x     9x 5x 5x   5x 2x 2x 2x     1x 1x     3x 3x                   9x 2x 2x 2x 2x                 9x 1x 1x 1x 1x     9x                                   1x 13x 13x 9x    
import React, { createContext, useState, useContext, useEffect } from "react";
 
/**
 * @typedef {Object} AuthUser
 * @property {number}  id         - Database row ID.
 * @property {string}  name       - User's first name.
 * @property {string}  lastName   - User's last name.
 * @property {string}  email      - User's email address.
 * @property {string}  personType - `'PF'` (individual) or `'PJ'` (company).
 */
 
/**
 * @typedef {Object} AuthContextValue
 * @property {AuthUser|null} user       - Currently authenticated user, or `null`.
 * @property {string|null}    accessToken - Current bearer access token.
 * @property {boolean}       isLoggedIn - `true` when a user session is active.
 * @property {function({ user: AuthUser, accessToken: string }): void} login  - Persists a user session.
 * @property {function(): void}          logout - Clears the current session.
 */
 
/** @type {React.Context<AuthContextValue>} */
const AuthContext = createContext(undefined);
 
/**
 * Provides authentication state to the component tree.
 *
 * On mount the provider restores any previously persisted session from
 * `localStorage` so that the user stays logged in across page reloads.
 *
 * @param {{ children: React.ReactNode }} props
 * @returns {JSX.Element}
 */
export const AuthProvider = ({ children }) => {
  /** @type {[AuthUser|null, React.Dispatch<React.SetStateAction<AuthUser|null>>]} */
  const [user, setUser] = useState(null);
  /** @type {[string|null, React.Dispatch<React.SetStateAction<string|null>>]} */
  const [accessToken, setAccessToken] = useState(null);
 
  // Restore session from localStorage on initial mount
  useEffect(() => {
    const savedUser = localStorage.getItem("auth_user");
    const savedToken = localStorage.getItem("auth_token");
 
    if (savedUser && savedToken) {
      try {
        setUser(JSON.parse(savedUser));
        setAccessToken(savedToken);
      } catch {
        // Corrupt data — ignore and start with no session
        localStorage.removeItem("auth_user");
        localStorage.removeItem("auth_token");
      }
    } else {
      localStorage.removeItem("auth_user");
      localStorage.removeItem("auth_token");
    }
  }, []);
 
  /**
   * Logs a user in by storing their data in React state and localStorage.
   *
   * @param {{ user: AuthUser, accessToken: string }} authData - Authenticated user details and bearer token.
   * @returns {void}
   */
  const login = (authData) => {
    setUser(authData.user);
    setAccessToken(authData.accessToken);
    localStorage.setItem("auth_user", JSON.stringify(authData.user));
    localStorage.setItem("auth_token", authData.accessToken);
  };
 
  /**
   * Logs the current user out by clearing React state and removing the
   * persisted session from localStorage.
   *
   * @returns {void}
   */
  const logout = () => {
    setUser(null);
    setAccessToken(null);
    localStorage.removeItem("auth_user");
    localStorage.removeItem("auth_token");
  };
 
  return (
    <AuthContext.Provider value={{ user, accessToken, login, logout, isLoggedIn: !!user && !!accessToken }}>
      {children}
    </AuthContext.Provider>
  );
};
 
/**
 * Custom hook that returns the current {@link AuthContextValue}.
 *
 * Must be called from a component rendered inside {@link AuthProvider}.
 *
 * @returns {AuthContextValue}
 * @throws {Error} If called outside an `AuthProvider` tree.
 *
 * @example
 * const { isLoggedIn, user, login, logout } = useAuth();
 */
export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("use Auth must be used within an AuthProvider");
  return ctx;
};