Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Eleazarguitar18/kantuta_pos_front/llms.txt

Use this file to discover all available pages before exploring further.

AuthContext is the single source of truth for the authenticated session in Kantuta POS. It owns the JWT access token, the current user object, and two storage helpers — loginStorage and logoutStorage — that keep React state and localStorage in sync. It also runs an inactivity timer that automatically logs the user out after 45 minutes without any keyboard, mouse, or scroll activity.

AuthContextType Interface

The shape of the context value is defined inline inside AuthContext.tsx (the file in src/context/auth/types/AuthContextType.ts holds a legacy stub):
interface AuthContextType {
  user: User | null;
  token: string | null;
  loginStorage: (token: string, refresh: string, user: User) => void;
  logoutStorage: () => void;
  isAuthenticated: boolean;
}
FieldTypeDescription
userUser | nullDeserialized user object from localStorage on mount
tokenstring | nullThe JWT access token; null if expired or not present
isAuthenticatedbooleantrue only when token is a non-empty, non-literal string and user is non-null
loginStoragefunctionPersists all three credentials to localStorage and updates React state
logoutStoragefunctionClears all credentials from localStorage and redirects to /signin

Consuming the Context

Import and call the useAuth() hook from anywhere inside the AuthContextProvider tree:
import { useAuth } from '../context/auth/AuthContext';

function MyComponent() {
  const { user, isAuthenticated, logoutStorage } = useAuth();

  if (!isAuthenticated) return null;

  return (
    <div>
      <span>Welcome, {user?.nombre}</span>
      <button onClick={logoutStorage}>Sign out</button>
    </div>
  );
}
useAuth() throws an error if called outside AuthContextProvider:
useAuth debe usarse dentro de un AuthProvider
Always confirm that the component is rendered below <AuthContextProvider> in the tree before calling this hook.

How loginStorage and logoutStorage Work

1

loginStorage — persisting credentials

Called by the sign-in page after a successful API response. Stores three keys in localStorage and updates React state atomically:
const loginStorage = (newToken: string, refreshToken: string, newUser: User) => {
  setToken(newToken);
  setUser(newUser);
  localStorage.setItem('access_token', newToken);
  localStorage.setItem('refresh_token', refreshToken);
  localStorage.setItem('user', JSON.stringify(newUser));
};
After this call, isAuthenticated flips to true on the next render.
2

logoutStorage — clearing credentials

Clears all three localStorage keys, resets the React state to null, and performs a hard redirect to /signin. The hard redirect (window.location.href) ensures the entire React tree re-mounts in an unauthenticated state:
const logoutStorage = () => {
  setToken(null);
  setUser(null);
  localStorage.removeItem('access_token');
  localStorage.removeItem('refresh_token');
  localStorage.removeItem('user');
  window.location.href = '/signin';
};
Access tokens, refresh tokens, and the user JSON are stored in localStorage, not in httpOnly cookies. This means they are readable by any JavaScript running on the same origin. Ensure the application is served over HTTPS and that no third-party scripts have access to the origin in production.

Token Expiry Decoding

On every page load, AuthContextProvider checks whether the stored access_token has already expired before restoring it into React state. The check manually decodes the JWT payload without any external library:
const isTokenExpired = (token: string | null) => {
  if (!token) return true;
  try {
    // Decode the Base64url-encoded payload (middle segment of the JWT)
    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const jsonPayload = decodeURIComponent(
      atob(base64)
        .split('')
        .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
        .join('')
    );
    const payload = JSON.parse(jsonPayload);
    // exp is in seconds; Date.now() is in milliseconds
    if (payload.exp && payload.exp * 1000 < Date.now()) {
      return true;
    }
    return false;
  } catch (e) {
    return true; // treat malformed tokens as expired
  }
};
This function is called in two places:
  1. State initializer — the useState lazy initializer for token calls isTokenExpired before returning the stored value. If the token is expired, it removes all three localStorage keys and returns null.
  2. Mount effect — a useEffect with an empty dependency array re-runs the check after the initial render to catch any edge case where the initializer ran before the DOM was ready.

Inactivity Auto-Logout (45 Minutes)

A second useEffect sets up an activity-based reset timer. Any user interaction resets the countdown; if 45 minutes pass with no activity, logoutStorage() is called automatically.
useEffect(() => {
  const resetTimer = () => {
    if (timeoutRef.current) clearTimeout(timeoutRef.current as any);
    timeoutRef.current = setTimeout(() => {
      logoutStorage();
    }, 45 * 60 * 1000); // 45 minutes
  };

  resetTimer(); // start the timer immediately on mount

  const events = ['mousemove', 'keydown', 'scroll', 'click'];
  const handleActivity = () => { resetTimer(); };

  events.forEach(evt => window.addEventListener(evt, handleActivity));

  return () => {
    if (timeoutRef.current) clearTimeout(timeoutRef.current as any);
    events.forEach(evt => window.removeEventListener(evt, handleActivity));
  };
}, []);
The 45-minute inactivity timer starts as soon as AuthContextProvider mounts — even before the user has signed in. Once the user signs in, the timer continues running and resets on any mousemove, keydown, scroll, or click event on the window. The timer reference is held in a useRef so it does not trigger re-renders when it resets.

isAuthenticated Guard Logic

The isAuthenticated boolean is more defensive than a simple !!token check:
isAuthenticated: !!token && token !== 'null' && token !== 'undefined' && !!user,
This guards against the edge case where localStorage.getItem('access_token') returns the literal string "null" or "undefined" — which can happen if code elsewhere accidentally calls localStorage.setItem('access_token', String(null)).

Role-Based Access with useRole

For component-level role checks, use the useRole hook instead of reading user.role directly. It normalizes the role to lowercase and provides convenience booleans:
import { useRole } from '../hooks/useRole';

function AdminOnlyPanel() {
  const { roleName, isAdmin, isOperator, hasRole } = useRole();

  if (!isAdmin) return null;

  return <AdminDashboard />;
}
useRole internally calls useAuth(), so it also requires being inside AuthContextProvider.

Build docs developers (and LLMs) love