← back to Dear Bubbe Admin
frontend/src/App.js
261 lines
import React, { useState, useEffect } from 'react'
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import axios from 'axios'
import io from 'socket.io-client'
// Components
import Login from './components/Login'
import Dashboard from './components/Dashboard'
import AlertsPage from './components/AlertsPage'
import UsersPage from './components/UsersPage'
import ReportsPage from './components/ReportsPage'
import MonitoringPage from './components/MonitoringPage'
import Sidebar from './components/Sidebar'
import Header from './components/Header'
import LiveAlertModal from './components/LiveAlertModal'
// Styles
import './App.css'
// API configuration
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://45.61.58.125:3015'
const SOCKET_URL = process.env.REACT_APP_SOCKET_URL || 'http://45.61.58.125:3015'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isAuthenticated: false,
user: null,
loading: true,
socket: null,
liveAlert: null,
stats: {},
error: null
}
// Set up axios defaults
axios.defaults.baseURL = API_BASE_URL
// Add token to requests if available
const token = localStorage.getItem('adminToken')
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
}
async componentDidMount() {
await this.checkAuthStatus()
}
componentWillUnmount() {
if (this.state.socket) {
this.state.socket.disconnect()
}
}
checkAuthStatus = async () => {
try {
const token = localStorage.getItem('adminToken')
if (!token) {
this.setState({ loading: false, isAuthenticated: false })
return
}
const response = await axios.get('/api/auth/me')
if (response.data.success) {
this.setState({
isAuthenticated: true,
user: response.data.user,
loading: false
})
this.connectWebSocket(token)
} else {
localStorage.removeItem('adminToken')
this.setState({ loading: false, isAuthenticated: false })
}
} catch (error) {
console.error('Auth check failed:', error)
localStorage.removeItem('adminToken')
this.setState({
loading: false,
isAuthenticated: false,
error: 'Authentication failed'
})
}
}
connectWebSocket = (token) => {
try {
const socket = io(SOCKET_URL, {
auth: { token },
transports: ['websocket', 'polling']
})
socket.on('connect', () => {
console.log('Connected to admin socket')
socket.emit('authenticate', token)
})
socket.on('auth_error', (error) => {
console.error('Socket auth error:', error)
this.handleLogout()
})
socket.on('stats', (stats) => {
this.setState({ stats })
})
socket.on('new_alert', (alert) => {
console.log('🚨 New alert received:', alert)
this.setState({ liveAlert: alert })
// Play alert sound if critical
if (alert.severity === 'critical' || alert.severity === 'high') {
this.playAlertSound()
}
})
socket.on('system_alert', (alert) => {
console.log('⚠️ System alert:', alert)
})
socket.on('threat_response', (data) => {
console.log('🚨 Threat response sent:', data)
})
socket.on('disconnect', () => {
console.log('Disconnected from admin socket')
})
this.setState({ socket })
} catch (error) {
console.error('Failed to connect WebSocket:', error)
}
}
playAlertSound = () => {
// Create a simple alert tone
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
const oscillator = audioContext.createOscillator()
const gainNode = audioContext.createGain()
oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)
oscillator.frequency.value = 800
oscillator.type = 'square'
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime)
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5)
oscillator.start(audioContext.currentTime)
oscillator.stop(audioContext.currentTime + 0.5)
} catch (error) {
console.log('Could not play alert sound:', error)
}
}
handleLogin = async (credentials) => {
try {
const response = await axios.post('/api/auth/login', credentials)
if (response.data.success) {
const { token, user } = response.data
localStorage.setItem('adminToken', token)
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
this.setState({
isAuthenticated: true,
user,
error: null
})
this.connectWebSocket(token)
return { success: true }
} else {
return { success: false, error: response.data.error }
}
} catch (error) {
const errorMessage = error.response?.data?.error || 'Login failed'
return { success: false, error: errorMessage }
}
}
handleLogout = () => {
localStorage.removeItem('adminToken')
delete axios.defaults.headers.common['Authorization']
if (this.state.socket) {
this.state.socket.disconnect()
}
this.setState({
isAuthenticated: false,
user: null,
socket: null,
stats: {},
error: null
})
}
closeLiveAlert = () => {
this.setState({ liveAlert: null })
}
render() {
const { isAuthenticated, user, loading, liveAlert, stats, error } = this.state
if (loading) {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<p>Loading Dear Bubbe Admin Dashboard...</p>
</div>
)
}
if (!isAuthenticated) {
return <Login onLogin={this.handleLogin} error={error} />
}
return (
<Router>
<div className="app">
<Sidebar />
<div className="main-content">
<Header user={user} stats={stats} onLogout={this.handleLogout} />
<div className="content">
<Routes>
<Route path="/" element={<Dashboard stats={stats} />} />
<Route path="/dashboard" element={<Dashboard stats={stats} />} />
<Route path="/alerts" element={<AlertsPage />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/monitoring" element={<MonitoringPage />} />
<Route path="/reports" element={<ReportsPage />} />
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</div>
</div>
{liveAlert && (
<LiveAlertModal
alert={liveAlert}
onClose={this.closeLiveAlert}
/>
)}
</div>
</Router>
)
}
}
export default App