import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import io from 'socket.io-client'; import client from './shared/api/client'; import { checkSetup, getProfile, logout } from './shared/api/authApi'; import { LoadingSpinner } from './shared/components'; import AppProviders from './app/AppProviders'; import AppRoutes from './app/routes'; import './styles/base/App.css'; function AppContent() { const [isAuthenticated, setIsAuthenticated] = useState(false); const [setupComplete, setSetupComplete] = useState(null); const [socket, setSocket] = useState(null); const [loading, setLoading] = useState(true); const forceLogout = React.useCallback(() => { localStorage.removeItem('token'); setIsAuthenticated(false); }, []); // Global interceptor any 401/403 kicks the user back to login useEffect(() => { const interceptorId = client.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401 || error.response?.status === 403) { forceLogout(); } return Promise.reject(error); } ); return () => client.interceptors.response.eject(interceptorId); }, [forceLogout]); useEffect(() => { // Check if setup is complete and validate any stored token const loadSetupStatus = async () => { try { const response = await checkSetup(); // If we get a 200 response, setup is required if (response.status === 200) { setSetupComplete(false); } } catch (err) { if (err.response?.status === 400) { // 400 means setup is already complete setSetupComplete(true); } else { // Assume setup is complete if we can't reach the endpoint setSetupComplete(true); } } finally { setLoading(false); } }; // Validate the stored token against the server before trusting it const validateToken = async () => { const token = localStorage.getItem('token'); if (!token) return; try { await getProfile(); setIsAuthenticated(true); } catch { // Token is expired, revoked, or invalid, clear it localStorage.removeItem('token'); setIsAuthenticated(false); } }; loadSetupStatus(); validateToken(); // Initialize WebSocket connection in production the frontend is served // from the same origin as the backend, so no URL is needed. const backendUrl = process.env.REACT_APP_BACKEND_URL || undefined; const newSocket = io(backendUrl || window.location.origin, { reconnection: true, reconnectionDelay: 1000, reconnectionDelayMax: 5000, reconnectionAttempts: 5, }); newSocket.on('connect', () => { console.log('Connected to server'); }); setSocket(newSocket); return () => newSocket.close(); }, []); const handleLogin = (token) => { localStorage.setItem('token', token); setIsAuthenticated(true); }; const handleLogout = async () => { try { const token = localStorage.getItem('token'); if (token) { await logout(); } } catch (err) { // Logout even if the API call fails console.error('Logout API error:', err); } forceLogout(); }; const handleSetupComplete = () => { setSetupComplete(true); }; if (loading) { return (
); } return ( ); } function App() { return ( ); } export default App;