Working UI with auth
This commit is contained in:
@@ -1,89 +1,159 @@
|
||||
// context/AuthContext.tsx
|
||||
"use client";
|
||||
import React, { useState, useEffect, createContext, useContext, ReactNode } from 'react';
|
||||
import { API_BASE_URL } from '@/constants/api';
|
||||
import { UserDetails, UserRoles, ErrorResponse, AuthContextType } from '@/types'; // Import types
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
ReactNode,
|
||||
useCallback
|
||||
} from 'react';
|
||||
import { API_BASE_URL } from '@/constants/api'; // Assuming you have this
|
||||
import { authAwareFetch } from '@/utils/AuthAwareFetch'; // Import authAwareFetch
|
||||
|
||||
export const AuthContext = createContext<AuthContextType | null>(null);
|
||||
// Define your User and UserRoles types if not already in types.ts
|
||||
interface User {
|
||||
username: string;
|
||||
role: string; // Or UserRoles enum
|
||||
}
|
||||
|
||||
export enum UserRoles {
|
||||
USER = 'user',
|
||||
MOD = 'mod',
|
||||
ADMIN = 'admin',
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
loading: boolean;
|
||||
login: (username: string, password: string) => Promise<boolean>;
|
||||
logout: () => void;
|
||||
hasPermission: (requiredRole: UserRoles) => boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [user, setUser] = useState<UserDetails | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [loading, setLoading] = useState<boolean>(true); // Start as true to indicate initial check
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = localStorage.getItem('jwt_token');
|
||||
const storedUser = localStorage.getItem('user_data');
|
||||
if (storedToken && storedUser) {
|
||||
setToken(storedToken);
|
||||
try {
|
||||
setUser(JSON.parse(storedUser));
|
||||
} catch (error) {
|
||||
console.error("Failed to parse stored user data:", error);
|
||||
localStorage.removeItem('user_data');
|
||||
}
|
||||
// Function to perform the actual logout steps
|
||||
const performLogout = useCallback(() => {
|
||||
setUser(null);
|
||||
setToken(null);
|
||||
if (typeof window !== 'undefined') { // Ensure localStorage is available
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
}
|
||||
setLoading(false);
|
||||
console.log("User logged out.");
|
||||
}, []);
|
||||
|
||||
// Effect to load token from localStorage on initial mount
|
||||
useEffect(() => {
|
||||
let isMounted = true; // Flag to prevent state updates on unmounted component
|
||||
if (typeof window !== 'undefined' && isMounted) { // Ensure localStorage is available
|
||||
const storedToken = localStorage.getItem('token');
|
||||
const storedUser = localStorage.getItem('user');
|
||||
|
||||
if (storedToken && storedUser) {
|
||||
try {
|
||||
const parsedUser: User = JSON.parse(storedUser);
|
||||
setToken(storedToken);
|
||||
setUser(parsedUser);
|
||||
console.log("Token and user loaded from localStorage.");
|
||||
} catch (e) {
|
||||
console.error("Failed to parse user from localStorage, logging out:", e);
|
||||
performLogout(); // Clear corrupted data
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMounted) { // Only set loading to false if component is still mounted
|
||||
setLoading(false); // Authentication check is complete
|
||||
}
|
||||
|
||||
return () => { isMounted = false; }; // Cleanup for unmount to prevent memory leaks
|
||||
}, [performLogout]); // Dependency on performLogout for stable reference
|
||||
|
||||
const login = async (username: string, password: string): Promise<boolean> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
const data = await response.json();
|
||||
// Use authAwareFetch but explicitly mark as a login attempt
|
||||
// This will prevent authAwareFetch from triggering logout if it receives a 401
|
||||
// because that 401 is an expected outcome for invalid credentials.
|
||||
const response = await authAwareFetch(
|
||||
'/auth/login', // Your login endpoint
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ username, password }),
|
||||
isLoginAttempt: true, // Crucial: tell authAwareFetch this is a login attempt
|
||||
},
|
||||
performLogout // Pass the logout function
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
setToken(data.access_token);
|
||||
const tempUser: UserDetails = { id: data.user_id || 'some-id', username: data.username, role: data.role || UserRoles.USER };
|
||||
setUser(tempUser);
|
||||
localStorage.setItem('jwt_token', data.access_token);
|
||||
localStorage.setItem('user_data', JSON.stringify(tempUser));
|
||||
const data = await response.json();
|
||||
const receivedToken = data.access_token; // Adjust based on your API response
|
||||
const receivedUser: User = { // Adjust based on your API response
|
||||
username: data.username,
|
||||
role: data.role,
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('token', receivedToken);
|
||||
localStorage.setItem('user', JSON.stringify(receivedUser));
|
||||
}
|
||||
setToken(receivedToken);
|
||||
setUser(receivedUser);
|
||||
console.log("Login successful.");
|
||||
return true;
|
||||
} else {
|
||||
const errorData = data as ErrorResponse;
|
||||
console.error('Login failed:', errorData.message || errorData.detail || response.statusText);
|
||||
const errorData = await response.json();
|
||||
console.error('Login failed:', errorData.message || response.statusText);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Network error during login:', error);
|
||||
} catch (err) {
|
||||
console.error('Network error during login:', err);
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
localStorage.removeItem('jwt_token');
|
||||
localStorage.removeItem('user_data');
|
||||
};
|
||||
const logout = useCallback(() => {
|
||||
performLogout();
|
||||
}, [performLogout]);
|
||||
|
||||
const hasPermission = useCallback((requiredRole: UserRoles) => {
|
||||
if (!user) return false;
|
||||
// Simple role-based permission check. You might need a more complex hierarchy.
|
||||
// Example: Admin has all permissions, Mod has Mod and User permissions.
|
||||
const userRoleIndex = Object.values(UserRoles).indexOf(user.role as UserRoles);
|
||||
const requiredRoleIndex = Object.values(UserRoles).indexOf(requiredRole);
|
||||
console.log(userRoleIndex, requiredRoleIndex, user)
|
||||
return userRoleIndex >= requiredRoleIndex;
|
||||
}, [user]);
|
||||
|
||||
const hasPermission = (requiredRole: UserRoles): boolean => {
|
||||
if (!user || !user.role) return false;
|
||||
const roleOrder: Record<UserRoles, number> = {
|
||||
[UserRoles.USER]: 0,
|
||||
[UserRoles.MOD]: 1,
|
||||
[UserRoles.ADMIN]: 2
|
||||
};
|
||||
return roleOrder[user.role] >= roleOrder[requiredRole];
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ token, user, loading, login, logout, hasPermission }}>
|
||||
<AuthContext.Provider value={{ user, token, loading, login, logout, hasPermission }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuth = (): AuthContextType => {
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === null) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
Reference in New Issue
Block a user