Version 0.4
This commit is contained in:
148
frontend/src/App.jsx
Normal file
148
frontend/src/App.jsx
Normal file
@@ -0,0 +1,148 @@
|
||||
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 (
|
||||
<div className="loading-page">
|
||||
<LoadingSpinner size="large" text="Initializing status page..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<AppRoutes
|
||||
socket={socket}
|
||||
isAuthenticated={isAuthenticated}
|
||||
setupComplete={setupComplete}
|
||||
onLogin={handleLogin}
|
||||
onLogout={handleLogout}
|
||||
onSetupComplete={handleSetupComplete}
|
||||
/>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<AppProviders>
|
||||
<AppContent />
|
||||
</AppProviders>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user