File "studentAuthSlice.ts"

Full Path: /home/trinadezambia/public_html/student_panel/src/components/store/slices/studentAuthSlice.ts
File size: 5.73 KB
MIME-type: text/x-java
Charset: utf-8

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
  updateAxiosToken,
  clearAxiosToken,
  updateAxiosSchoolCode,
  clearAxiosSchoolCode,
} from '@/lib/api/student/axiosConfig';
import { StudentUser } from '@/lib/api/student/functions';

// Define the student auth state interface
// Store complete user data from API response
interface StudentAuthState {
  isAuthenticated: boolean;
  user: StudentUser | null; // Store complete user object from API
  token: string | null;
  schoolCode: string | null; // Store school code for API headers
  loading: boolean;
  error: string | null;
}

// LocalStorage keys for auth persistence
const AUTH_STORAGE_KEY = 'student_auth_state';

// Helper function to save auth state to localStorage
const saveAuthToLocalStorage = (
  user: StudentUser | null,
  token: string | null,
  schoolCode: string | null
) => {
  if (typeof window !== 'undefined') {
    if (user && token && schoolCode) {
      localStorage.setItem(
        AUTH_STORAGE_KEY,
        JSON.stringify({ user, token, schoolCode })
      );
    } else {
      localStorage.removeItem(AUTH_STORAGE_KEY);
    }
  }
};

// Helper function to load auth state from localStorage
// This should only be called on the client side after mount
export const loadAuthFromLocalStorage = (): {
  user: StudentUser | null;
  token: string | null;
  schoolCode: string | null;
} | null => {
  if (typeof window !== 'undefined') {
    try {
      const stored = localStorage.getItem(AUTH_STORAGE_KEY);
      if (stored) {
        return JSON.parse(stored);
      }
    } catch {
      // Use console.log to prevent error overlay in development
      localStorage.removeItem(AUTH_STORAGE_KEY);
    }
  }
  return null;
};

// Initial state - start with unauthenticated to match server render
// This prevents hydration mismatch between server and client
// Auth state will be rehydrated from localStorage after client mount
const initialState: StudentAuthState = {
  isAuthenticated: false,
  user: null,
  token: null,
  schoolCode: null,
  loading: false,
  error: null,
};

// Create the student auth slice
const studentAuthSlice = createSlice({
  name: 'studentAuth',
  initialState,
  reducers: {
    // Rehydrate auth state from localStorage after client mount
    // This action should be dispatched once after the app mounts on the client
    // It prevents hydration mismatch by loading persisted state after initial render
    rehydrateAuth: (state) => {
      const storedAuth = loadAuthFromLocalStorage();
      if (storedAuth?.token && storedAuth?.user) {
        state.isAuthenticated = true;
        state.user = storedAuth.user;
        state.token = storedAuth.token;
        state.schoolCode = storedAuth.schoolCode;

        // Update axios config with restored auth data
        updateAxiosToken(storedAuth.token);
        if (storedAuth.schoolCode) {
          updateAxiosSchoolCode(storedAuth.schoolCode);
        }
      }
    },

    // Set loading state
    setLoading: (state, action: PayloadAction<boolean>) => {
      state.loading = action.payload;
    },

    // Set error message
    setError: (state, action: PayloadAction<string | null>) => {
      state.error = action.payload;
    },

    // Login success
    loginSuccess: (
      state,
      action: PayloadAction<{ user: StudentAuthState['user']; token: string }>
    ) => {
      state.isAuthenticated = true;
      state.user = action.payload.user;
      state.token = action.payload.token;
      // Extract school code from user data
      state.schoolCode = action.payload.user?.school?.code || null;
      state.loading = false;
      state.error = null;

      // Save auth state to localStorage for persistence
      saveAuthToLocalStorage(
        action.payload.user,
        action.payload.token,
        state.schoolCode
      );

      // Update axios token and school code for future requests
      updateAxiosToken(action.payload.token);
      updateAxiosSchoolCode(state.schoolCode);
    },

    // Logout
    logout: (state) => {
      state.isAuthenticated = false;
      state.user = null;
      state.token = null;
      state.schoolCode = null;
      state.loading = false;
      state.error = null;

      // Clear auth state from localStorage
      saveAuthToLocalStorage(null, null, null);

      // Clear axios token and school code
      clearAxiosToken();
      clearAxiosSchoolCode();
    },

    // Clear error
    clearError: (state) => {
      state.error = null;
    },

    // Login action (for handling login process)
    loginStart: (state) => {
      state.loading = true;
      state.error = null;
    },

    // Login failure
    loginFailure: (state, action: PayloadAction<string>) => {
      state.loading = false;
      state.error = action.payload;
      state.isAuthenticated = false;
      state.user = null;
      state.token = null;
      state.schoolCode = null;

      // Clear axios token and school code on failure
      clearAxiosToken();
      clearAxiosSchoolCode();
    },

    // Update user profile data without affecting token or auth status
    updateUserProfile: (state, action: PayloadAction<StudentAuthState['user']>) => {
      state.user = action.payload;
      // Also update school code if it changed
      if (action.payload?.school?.code) {
        state.schoolCode = action.payload.school.code;
        saveAuthToLocalStorage(
          action.payload,
          state.token,
          action.payload.school.code
        );
      } else {
        saveAuthToLocalStorage(action.payload, state.token, state.schoolCode);
      }
    },
  },
});

// Export actions
export const {
  rehydrateAuth,
  setLoading,
  setError,
  loginSuccess,
  logout,
  clearError,
  loginStart,
  loginFailure,
  updateUserProfile,
} = studentAuthSlice.actions;

// Export reducer
export default studentAuthSlice.reducer;