Initial commit
This commit is contained in:
136
client/src/contexts/AuthContext.jsx
Normal file
136
client/src/contexts/AuthContext.jsx
Normal 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;
|
||||
};
|
||||
134
client/src/contexts/SocketContext.jsx
Normal file
134
client/src/contexts/SocketContext.jsx
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user