diff --git a/src/components/AppContent.tsx b/src/components/AppContent.tsx index f974cb3..5c2998d 100644 --- a/src/components/AppContent.tsx +++ b/src/components/AppContent.tsx @@ -1,7 +1,8 @@ +// components/AppContent.tsx "use client"; import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; -import { useAuth } from '@/context/AuthContext'; +import { useAuth } from '@/context/AuthContext'; // Correct path to useAuth import LoginPage from '@/components/LoginPage'; import BotsManagement from '@/components/BotsManagement'; import SystemsManagement from '@/components/SystemsManagement'; @@ -9,57 +10,68 @@ import { UserRoles } from '@/types'; const AppContent: React.FC = () => { const { user, loading, logout, hasPermission, token } = useAuth(); - const [currentPage, setCurrentPage] = useState<'login' | 'management'>(loading ? 'login' : (user ? 'management' : 'login')); + const [activeManagementTab, setActiveManagementTab] = useState<'bots' | 'systems'>('bots'); - useEffect(() => { - if (!loading) { - if (user && hasPermission(UserRoles.MOD)) { - setCurrentPage('management'); - } else { - setCurrentPage('login'); - } - } - }, [user, loading, hasPermission]); + // Function to handle logout and redirect to login page + const handleLogoutAndRedirect = () => { + logout(); + // No need to set currentPage, as the AppContent will re-render based on useAuth().user becoming null + }; + + // Display a loading indicator while AuthContext is determining authentication status if (loading) { return
Loading Authentication...
; } + // Once loading is false, if no user is authenticated, display the LoginPage + if (!user) { + return ; + } + + // If a user is authenticated but lacks the required permission, display an access denied message + if (user && !hasPermission(UserRoles.MOD)) { + return ( +
+

Access Denied

+

+ You do not have sufficient permissions to view this page. Your role: {user.role}. Required: {UserRoles.MOD}. +

+ +
+ ); + } + + // If loading is false, and we have a user with MOD permission and a token, render the main app content return (

Radio App Admin

- {user && ( -
- Logged in as: {user.username} ({user.role}) - -
- )} +
+ Logged in as: {user.username} ({user.role}) + +
- {currentPage === 'login' && } - {currentPage === 'management' && user && token && hasPermission(UserRoles.MOD) && ( -
- - -
- )} - {currentPage === 'management' && user && !hasPermission(UserRoles.MOD) && ( -
- You do not have permission to view this page. Your role: {user.role}. Required: {UserRoles.MOD}. - -
- )} - {currentPage === 'management' && !user && ( -
- Session might have expired. Please login again. - -
- )} +
+ + +
+ + {activeManagementTab === 'bots' && } + {activeManagementTab === 'systems' && }
); diff --git a/src/components/BotsManagement.tsx b/src/components/BotsManagement.tsx index ad7cf55..bd742fc 100644 --- a/src/components/BotsManagement.tsx +++ b/src/components/BotsManagement.tsx @@ -1,3 +1,4 @@ +// BotsManagement.tsx "use client"; import React, { useState, useEffect } from 'react'; import Link from 'next/link'; @@ -10,17 +11,19 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from ' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { API_BASE_URL } from '@/constants/api'; -import { DiscordId, System, ErrorResponse } from '@/types'; // Import types +import { DiscordId, System, ErrorResponse } from '@/types'; +import { authAwareFetch } from '@/utils/AuthAwareFetch'; interface BotsManagementProps { - token: string; + token: string | null; // Allow token to be null initially + logoutUser: () => void; } -const BotsManagement: React.FC = ({ token }) => { +const BotsManagement: React.FC = ({ token, logoutUser }) => { const [bots, setBots] = useState([]); const [discordIds, setDiscordIds] = useState([]); const [systems, setSystems] = useState([]); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(true); // Set to true initially const [error, setError] = useState(''); const [isAddIdDialogOpen, setIsAddIdDialogOpen] = useState(false); @@ -39,15 +42,22 @@ const BotsManagement: React.FC = ({ token }) => { const [assignDismissAction, setAssignDismissAction] = useState<'assign' | 'dismiss'>('assign'); const fetchData = async (): Promise => { + // Only attempt to fetch if a token is present + if (!token) { + setLoading(false); // Stop loading if no token + setError('Authentication token is missing. Please log in.'); + return; + } + setLoading(true); setError(''); let accumulatedError = ''; try { const [nodesRes, discordIdsRes, systemsRes] = await Promise.all([ - fetch(`${API_BASE_URL}/nodes/`, { headers: { Authorization: `Bearer ${token}` } }), - fetch(`${API_BASE_URL}/bots/tokens/`, { headers: { Authorization: `Bearer ${token}` } }), - fetch(`${API_BASE_URL}/systems/`, { headers: { Authorization: `Bearer ${token}` } }), + authAwareFetch('/nodes/', { token }, logoutUser), + authAwareFetch('/bots/tokens/', { token }, logoutUser), + authAwareFetch('/systems/', { token }, logoutUser), ]); if (nodesRes.ok) { @@ -91,6 +101,11 @@ const BotsManagement: React.FC = ({ token }) => { } } catch (err: any) { + // Catch specific unauthorized errors and let authAwareFetch handle logout + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError('Failed to fetch data. Check server connection or console.'); console.error(err); } finally { @@ -99,30 +114,43 @@ const BotsManagement: React.FC = ({ token }) => { }; useEffect(() => { + // This effect runs whenever 'token' changes. + // If token becomes available, it triggers fetchData. if (token) { fetchData(); + } else { + // If token is explicitly null (e.g., after logout or initial load without a token) + setLoading(false); + // No need to set error "Please log in" here, as AppContent handles the redirect + // This is a component internal message if it somehow renders without a token when it shouldn't + setError('Authentication required to load data.'); + setBots([]); // Clear data if token is gone + setDiscordIds([]); + setSystems([]); } - }, [token]); + }, [token]); // Dependency on token ensures fetchData is called when token becomes available const handleAddId = async (): Promise => { setError(''); + if (!token) { + setError('Authentication token is missing. Please log in.'); + return; + } try { const payload: Omit = { ...newIdData, guild_ids: newIdData.guild_ids.split(',').map(id => id.trim()).filter(id => id), }; const method = editingId ? 'PUT' : 'POST'; - const url = editingId ? `${API_BASE_URL}/bots/token/${editingId._id}` : `${API_BASE_URL}/bots/token`; + const url = editingId ? `/bots/token/${editingId._id}` : `/bots/token`; - const response = await fetch(url, { + const response = await authAwareFetch(url, { method, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), - }); + token, + }, logoutUser); if (response.ok) { fetchData(); @@ -138,6 +166,10 @@ const BotsManagement: React.FC = ({ token }) => { setError(errorMsg); } } catch (err: any) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError(`Network error during ${editingId ? 'update' : 'add'} Discord ID.`); console.error(err); } @@ -146,11 +178,12 @@ const BotsManagement: React.FC = ({ token }) => { const handleDeleteId = async (id: string): Promise => { if (!window.confirm('Are you sure you want to delete this Discord ID?')) return; setError(''); + if (!token) { + setError('Authentication token is missing. Please log in.'); + return; + } try { - const response = await fetch(`${API_BASE_URL}/bots/token/${id}`, { - method: 'DELETE', - headers: { Authorization: `Bearer ${token}` }, - }); + const response = await authAwareFetch(`/bots/token/${id}`, { method: 'DELETE', token }, logoutUser); if (response.ok) { fetchData(); } else { @@ -162,6 +195,10 @@ const BotsManagement: React.FC = ({ token }) => { setError(errorMsg); } } catch (err: any) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError('Network error during delete Discord ID.'); console.error(err); } @@ -173,16 +210,18 @@ const BotsManagement: React.FC = ({ token }) => { setError("Bot Client ID and System must be selected."); return; } + if (!token) { + setError('Authentication token is missing. Please log in.'); + return; + } try { const endpoint = assignDismissAction === 'assign' ? 'assign' : 'dismiss'; - const response = await fetch(`${API_BASE_URL}/systems/${selectedSystemId}/${endpoint}`, { + const response = await authAwareFetch(`/systems/${selectedSystemId}/${endpoint}`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: selectedBotClientId }), - }); + token, + }, logoutUser); if (response.ok) { fetchData(); setIsAssignDismissDialogOpen(false); @@ -197,13 +236,21 @@ const BotsManagement: React.FC = ({ token }) => { setError(errorMsg); } } catch (err: any) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError(`Network error during ${assignDismissAction} bot.`); console.error(err); } }; - if (loading) return

Loading bots and Discord IDs...

; - if (error && !loading) return

{error}

; + // Only show loading if actively fetching or waiting for token + if (loading && token) return

Loading bots and Discord IDs...

; + // Show error if there's an error + if (error) return

{error}

; + // If not loading and no token, it implies no action can be taken or a login is needed + if (!loading && !token) return

Please log in to view and manage bots.

; return ( @@ -252,8 +299,8 @@ const BotsManagement: React.FC = ({ token }) => {

Manage Discord IDs

- - + + @@ -287,10 +334,11 @@ const BotsManagement: React.FC = ({ token }) => { guild_ids: dId.guild_ids?.join(', '), }); setIsAddIdDialogOpen(true); - }}> + }} + disabled={!token}> Edit - @@ -359,7 +407,9 @@ const BotsManagement: React.FC = ({ token }) => { - + @@ -413,7 +463,7 @@ const BotsManagement: React.FC = ({ token }) => { - + diff --git a/src/components/IndividualClientPage.tsx b/src/components/IndividualClientPage.tsx index 0ed9803..20854b0 100644 --- a/src/components/IndividualClientPage.tsx +++ b/src/components/IndividualClientPage.tsx @@ -9,16 +9,18 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from ' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; +import { authAwareFetch } from '@/utils/AuthAwareFetch'; interface IndividualClientPageProps { clientId: string; - token: string; + token: string | null; // Allow token to be null initially + logoutUser: () => void; } -const IndividualClientPage: React.FC = ({ clientId, token }) => { +const IndividualClientPage: React.FC = ({ clientId, token, logoutUser }) => { const [message, setMessage] = useState(''); const [error, setError] = useState(''); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); // Set to true initially const [currentClientDiscordStatus, setCurrentClientDiscordStatus] = useState('Unknown'); const [currentClientOp25Status, setCurrentClientOp25Status] = useState('Unknown'); @@ -38,11 +40,15 @@ const IndividualClientPage: React.FC = ({ clientId, t const fetchClientStatus = async () => { + if (!token) { + setLoading(false); + setCurrentClientDiscordStatus('Authentication token is missing.'); + setCurrentClientOp25Status('Authentication token is missing.'); + return; + } + setLoading(true); try { - setLoading(true); - const response = await fetch(`${API_BASE_URL}/nodes/${clientId}/status`, { - headers: { Authorization: `Bearer ${token}` }, - }); + const response = await authAwareFetch(`/nodes/${clientId}/status`, { token }, logoutUser); if (response.ok) { const data: NodeStatusResponse = await response.json(); console.log(data) @@ -53,6 +59,10 @@ const IndividualClientPage: React.FC = ({ clientId, t setCurrentClientOp25Status('Failed to fetch status'); } } catch (err) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setCurrentClientDiscordStatus('Error fetching status'); setCurrentClientOp25Status('Error fetching status'); } finally { @@ -62,13 +72,16 @@ const IndividualClientPage: React.FC = ({ clientId, t const fetchSystems = async (): Promise => { setError(''); + if (!token) { + setLoading(false); + setError('Authentication token is missing. Please log in.'); + return; + } + setLoading(true); try { - const response = await fetch(`${API_BASE_URL}/systems/`, { - headers: { Authorization: `Bearer ${token}` }, - }); + const response = await authAwareFetch('/systems/', { token }, logoutUser); if (response.ok) { const data: System[] = await response.json(); - // Filter systems to those available on the current clientId const filteredSystems = data.filter(system => system.avail_on_nodes.includes(clientId)); setAvailableSystems(filteredSystems); } else { @@ -80,32 +93,47 @@ const IndividualClientPage: React.FC = ({ clientId, t setError(errorMsg); } } catch (err: any) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError('Failed to fetch systems. Check server connection or console.'); console.error(err); + } finally { + setLoading(false); } }; useEffect(() => { - fetchClientStatus(); - fetchSystems(); // Fetch systems when the component mounts or clientId/token changes - }, [clientId, token]); + if (token) { + fetchClientStatus(); + fetchSystems(); + } else { + setLoading(false); + setError('Please log in to manage this client.'); + } + }, [clientId, token, logoutUser]); // Added logoutUser to dependencies to avoid lint warnings const handleAction = async ( action: 'join' | 'leave' | 'op25_start' | 'op25_stop' | 'op25_set', - // Optional parameters for 'join' and 'leave' actions payloadData?: { server_id?: string; channel_id?: string; guild_id?: string } ): Promise => { setMessage(''); setError(''); setLoading(true); + if (!token) { + setError('Authentication token is missing. Please log in.'); + setLoading(false); + return; + } + try { const httpOptions: RequestInit = { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, } } @@ -139,21 +167,25 @@ const IndividualClientPage: React.FC = ({ clientId, t }); } - const response = await fetch(`${API_BASE_URL}/nodes/${clientId}/${action}`, httpOptions); + const response = await authAwareFetch(`/nodes/${clientId}/${action}`, { ...httpOptions, token }, logoutUser); const data = await response.json(); if (response.ok) { setMessage(`Client "${clientId}" successfully ${action === 'op25_set' ? 'set config for system ' + selectedSystem : action + 'ed'}.`); fetchClientStatus(); - setIsSetConfigDialogOpen(false); // Close set config dialog on success - setIsJoinDiscordDialogOpen(false); // Close join discord dialog on success - setIsLeaveDiscordDialogOpen(false); // Close leave discord dialog on success + setIsSetConfigDialogOpen(false); + setIsJoinDiscordDialogOpen(false); + setIsLeaveDiscordDialogOpen(false); } else { const errorData = data as ErrorResponse; setError(`Failed to ${action} client "${clientId}": ${errorData.message || (typeof errorData.detail === 'string' ? errorData.detail : JSON.stringify(errorData.detail)) || response.statusText}`); } } catch (err: any) { + if (err.message === 'Unauthorized: Session expired or invalid token.') { + setLoading(false); // already handled by authAwareFetch + return; + } setError(`Network error during ${action} client: ${err.message}`); console.error(err); } finally { @@ -162,8 +194,8 @@ const IndividualClientPage: React.FC = ({ clientId, t }; const handleJoinClick = () => { - setDiscordServerId(''); // Clear previous values - setDiscordChannelId(''); // Clear previous values + setDiscordServerId(''); + setDiscordChannelId(''); setIsJoinDiscordDialogOpen(true); }; @@ -172,7 +204,7 @@ const IndividualClientPage: React.FC = ({ clientId, t }; const handleLeaveClick = () => { - setLeaveGuildId(''); // Clear previous value + setLeaveGuildId(''); setIsLeaveDiscordDialogOpen(true); }; @@ -187,18 +219,22 @@ const IndividualClientPage: React.FC = ({ clientId, t Manage Client: {clientId} + {error &&

{error}

} + {loading &&

Processing request...

} + {!loading && !token &&

Please log in to manage this client.

} +

Current Discord Status: {currentClientDiscordStatus}

{message &&

{message}

} - {error &&

{error}

} - {loading &&

Processing request...

}
{/* Set Config Dialog */} @@ -261,7 +295,7 @@ const IndividualClientPage: React.FC = ({ clientId, t - + }} className="mb-4" disabled={!token}>Add New System
@@ -226,10 +256,12 @@ const SystemsManagement: React.FC = ({ token }) => { whitelist: system.whitelist ? system.whitelist.join(', ') : '', }); setIsAddSystemDialogOpen(true); - }}> + }} + disabled={!token} + > Edit - @@ -343,7 +375,7 @@ const SystemsManagement: React.FC = ({ token }) => { - + diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 862d428..58525ea 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -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(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; + logout: () => void; + hasPermission: (requiredRole: UserRoles) => boolean; +} + +const AuthContext = createContext(undefined); interface AuthProviderProps { children: ReactNode; } export const AuthProvider: React.FC = ({ children }) => { + const [user, setUser] = useState(null); const [token, setToken] = useState(null); - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(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 => { + 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.USER]: 0, - [UserRoles.MOD]: 1, - [UserRoles.ADMIN]: 2 - }; - return roleOrder[user.role] >= roleOrder[requiredRole]; - }; return ( - + {children} ); }; -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; }; \ No newline at end of file diff --git a/src/utils/AuthAwareFetch.ts b/src/utils/AuthAwareFetch.ts new file mode 100644 index 0000000..bf6544f --- /dev/null +++ b/src/utils/AuthAwareFetch.ts @@ -0,0 +1,36 @@ +// utils/AuthAwareFetch.ts +import { API_BASE_URL } from '@/constants/api'; // Make sure this path is correct + +interface AuthAwareFetchOptions extends RequestInit { + token?: string; + isLoginAttempt?: boolean; // New option to bypass 401 handling for login +} + +export const authAwareFetch = async ( + url: string, + options: AuthAwareFetchOptions = {}, + logoutUser: () => void // Function to call on 401 +): Promise => { + const { token, isLoginAttempt, headers, ...rest } = options; + + const requestHeaders = { + ...headers, + ...(token && { Authorization: `Bearer ${token}` }), + }; + + console.log(requestHeaders, rest, url) + + const response = await fetch(`${API_BASE_URL}${url}`, { + headers: requestHeaders, + ...rest, + }); + + if (response.status === 401 && !isLoginAttempt) { + console.error('Authentication failed (401). Logging out...'); + logoutUser(); + // Potentially throw an error or return a specific response to indicate logout + throw new Error('Unauthorized: Session expired or invalid token.'); + } + + return response; +}; \ No newline at end of file