Initial commit

This commit is contained in:
2025-08-16 19:43:44 +02:00
commit ea9a2627b4
64 changed files with 9232 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import api from '../services/api';
const AuthContext = createContext();
const authReducer = (state, action) => {
switch (action.type) {
case 'LOGIN_START':
return { ...state, loading: true, error: null };
case 'LOGIN_SUCCESS':
return {
...state,
loading: false,
user: action.payload.user,
token: action.payload.token,
isAuthenticated: true
};
case 'LOGIN_FAILURE':
return {
...state,
loading: false,
error: action.payload,
user: null,
token: null,
isAuthenticated: false
};
case 'LOGOUT':
return {
...state,
user: null,
token: null,
isAuthenticated: false,
loading: false,
error: null
};
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_ERROR':
return { ...state, error: action.payload };
case 'UPDATE_USER':
return { ...state, user: { ...state.user, ...action.payload } };
default:
return state;
}
};
const initialState = {
user: null,
token: localStorage.getItem('token'),
isAuthenticated: false,
loading: true,
error: null
};
export const AuthProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, initialState);
useEffect(() => {
// Check if user is logged in on app start
const token = localStorage.getItem('token');
if (token) {
// Validate token and get user info
checkAuthStatus();
} else {
dispatch({ type: 'SET_LOADING', payload: false });
}
}, []);
const checkAuthStatus = async () => {
try {
const response = await api.get('/users/profile');
dispatch({
type: 'LOGIN_SUCCESS',
payload: {
user: response.data.data,
token: localStorage.getItem('token')
}
});
} catch (error) {
localStorage.removeItem('token');
dispatch({ type: 'LOGOUT' });
}
};
const login = async (credentials) => {
try {
dispatch({ type: 'LOGIN_START' });
const response = await api.post('/users/login', credentials);
const { user, token } = response.data.data;
localStorage.setItem('token', token);
dispatch({
type: 'LOGIN_SUCCESS',
payload: { user, token }
});
return { success: true };
} catch (error) {
const errorMessage = error.response?.data?.message || 'Login failed';
dispatch({ type: 'LOGIN_FAILURE', payload: errorMessage });
return { success: false, error: errorMessage };
}
};
const logout = () => {
localStorage.removeItem('token');
dispatch({ type: 'LOGOUT' });
};
const updateUser = (userData) => {
dispatch({ type: 'UPDATE_USER', payload: userData });
};
const value = {
...state,
login,
logout,
updateUser,
checkAuthStatus
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -0,0 +1,134 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { io } from 'socket.io-client';
import { useAuth } from './AuthContext';
import toast from 'react-hot-toast';
const SocketContext = createContext();
export const SocketProvider = ({ children }) => {
const [socket, setSocket] = useState(null);
const [connected, setConnected] = useState(false);
const [recentDetections, setRecentDetections] = useState([]);
const [deviceStatus, setDeviceStatus] = useState({});
const { isAuthenticated } = useAuth();
useEffect(() => {
if (isAuthenticated) {
// Initialize socket connection
const newSocket = io(process.env.NODE_ENV === 'production'
? window.location.origin
: 'http://localhost:3001'
);
newSocket.on('connect', () => {
console.log('Connected to server');
setConnected(true);
// Join dashboard room for general updates
newSocket.emit('join_dashboard');
toast.success('Connected to real-time updates');
});
newSocket.on('disconnect', () => {
console.log('Disconnected from server');
setConnected(false);
toast.error('Disconnected from server');
});
newSocket.on('connect_error', (error) => {
console.error('Connection error:', error);
setConnected(false);
toast.error('Failed to connect to server');
});
// Listen for drone detections
newSocket.on('drone_detection', (detection) => {
console.log('New drone detection:', detection);
setRecentDetections(prev => [detection, ...prev.slice(0, 49)]); // Keep last 50
// Show toast notification
toast.error(
`Drone detected by ${detection.device.name || `Device ${detection.device_id}`}`,
{
duration: 5000,
icon: '🚨',
}
);
});
// Listen for device heartbeats
newSocket.on('device_heartbeat', (heartbeat) => {
console.log('Device heartbeat:', heartbeat);
setDeviceStatus(prev => ({
...prev,
[heartbeat.device_id]: {
...heartbeat,
last_seen: new Date()
}
}));
});
// Listen for device updates
newSocket.on('device_updated', (device) => {
console.log('Device updated:', device);
toast.success(`Device ${device.name || device.id} updated`);
});
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
} else {
// Disconnect if not authenticated
if (socket) {
socket.disconnect();
setSocket(null);
setConnected(false);
}
}
}, [isAuthenticated]);
const joinDeviceRoom = (deviceId) => {
if (socket) {
socket.emit('join_device_room', deviceId);
}
};
const leaveDeviceRoom = (deviceId) => {
if (socket) {
socket.emit('leave_device_room', deviceId);
}
};
const clearRecentDetections = () => {
setRecentDetections([]);
};
const value = {
socket,
connected,
recentDetections,
deviceStatus,
joinDeviceRoom,
leaveDeviceRoom,
clearRecentDetections
};
return (
<SocketContext.Provider value={value}>
{children}
</SocketContext.Provider>
);
};
export const useSocket = () => {
const context = useContext(SocketContext);
if (!context) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
};