D360-Frontend/src/hooks/useAuth.tsx

180 lines
5.3 KiB
TypeScript

import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
import type { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { authService, type AppUser, type UserRole } from '@/api';
interface AuthSession {
user: AppUser;
access_token: string;
}
interface AuthContextType {
user: AppUser | null;
session: AuthSession | null;
userRole: UserRole;
loading: boolean;
signIn: (username: string, password: string) => Promise<{ error: { message: string } | null }>;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
/**
* Maps the API user_id to the application's UserRole.
* 1 -> helpdesk
* 2 -> bank_customer
* 3 -> manufacturing_customer
*/
const mapUserIdToRole = (userId: string): UserRole => {
switch (userId) {
case '1':
return 'helpdesk';
case '2':
return 'bank_customer';
case '3':
return 'manufacturing_customer';
default:
console.warn(`Unknown user_id: ${userId}. Defaulting to null role.`);
return null;
}
};
export const AuthProvider = ({ children }: { children: ReactNode }) => {
// OPTIMIZATION: Initialize from localStorage synchronously to avoid blocking render
const getInitialSession = (): AuthSession | null => {
try {
const storedSession = localStorage.getItem('dealer360_session');
if (storedSession) {
return JSON.parse(storedSession) as AuthSession;
}
} catch (e) {
localStorage.removeItem('dealer360_session');
}
return null;
};
const initialSession = getInitialSession();
const [user, setUser] = useState<AppUser | null>(initialSession?.user || null);
const [session, setSession] = useState<AuthSession | null>(initialSession);
const [userRole, setUserRole] = useState<UserRole>(initialSession?.user?.role || null);
const [loading, setLoading] = useState(false); // Start as false since we sync synchronously
const navigate = useNavigate();
// Helper to sync state from localStorage
const syncStateFromStorage = useCallback(() => {
const storedSession = localStorage.getItem('dealer360_session');
if (storedSession) {
try {
const parsedSession = JSON.parse(storedSession) as AuthSession;
setSession(parsedSession);
setUser(parsedSession.user);
setUserRole(parsedSession.user.role);
} catch (e) {
localStorage.removeItem('dealer360_session');
setSession(null);
setUser(null);
setUserRole(null);
}
} else {
setSession(null);
setUser(null);
setUserRole(null);
}
}, []);
useEffect(() => {
// Initial sync (already done synchronously, but keep for consistency)
// This is mainly for storage event listeners
// Listen for storage changes (multi-tab support)
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'dealer360_session') {
syncStateFromStorage();
// If logged out in another tab, redirect to login
if (!e.newValue && !window.location.pathname.includes('/login')) {
navigate('/login');
}
}
};
window.addEventListener('storage', handleStorageChange);
return () => window.removeEventListener('storage', handleStorageChange);
}, [syncStateFromStorage, navigate]);
const signIn = async (username: string, password: string): Promise<{ error: { message: string } | null }> => {
try {
const response = await authService.login({ username, password });
if (response.success && response.data) {
const { user_id, email, username: apiUsername, company_name, token } = response.data;
const role = mapUserIdToRole(user_id);
const appUser: AppUser = {
id: user_id,
username: apiUsername,
email: email,
role: role,
company_name: company_name,
};
const authSession: AuthSession = {
user: appUser,
access_token: token,
};
// Store session in localStorage for persistence
localStorage.setItem('dealer360_session', JSON.stringify(authSession));
setUser(appUser);
setSession(authSession);
setUserRole(role);
return { error: null };
}
return { error: { message: response.message || 'Login failed' } };
} catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'An unexpected error occurred during login';
return { error: { message: errorMessage } };
}
};
const signOut = async () => {
// Clear state
localStorage.removeItem('dealer360_session');
setUser(null);
setSession(null);
setUserRole(null);
// Attempt to notify backend
try {
await authService.logout();
} catch (e) {
// Silent fail
}
navigate('/login');
};
// Memoize context value to prevent unnecessary re-renders
const contextValue = useMemo(
() => ({ user, session, userRole, loading, signIn, signOut }),
[user, session, userRole, loading, signIn, signOut]
);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};