- Introduced `SocketProvider` to manage WebSocket connection and context across the app. - Added `NotificationsListener` component to handle real-time notifications and display feedback via `toast`. - Enabled event subscriptions for projects, groups, collaborators, and user actions.
192 lines
4.9 KiB
TypeScript
192 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { createContext, useContext, useEffect, useState, ReactNode } from "react";
|
|
import { io, Socket } from "socket.io-client";
|
|
import { useAuth } from "./auth-context";
|
|
|
|
// Define the SocketContext type
|
|
interface SocketContextType {
|
|
socket: Socket | null;
|
|
isConnected: boolean;
|
|
joinProject: (projectId: string) => void;
|
|
leaveProject: (projectId: string) => void;
|
|
// Event listeners
|
|
onProjectUpdated: (callback: (data: any) => void) => () => void;
|
|
onCollaboratorAdded: (callback: (data: any) => void) => () => void;
|
|
onGroupCreated: (callback: (data: any) => void) => () => void;
|
|
onGroupUpdated: (callback: (data: any) => void) => () => void;
|
|
onPersonAddedToGroup: (callback: (data: any) => void) => () => void;
|
|
onPersonRemovedFromGroup: (callback: (data: any) => void) => () => void;
|
|
onNotification: (callback: (data: any) => void) => () => void;
|
|
}
|
|
|
|
// Create the SocketContext
|
|
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
|
|
|
// Create a provider component
|
|
export function SocketProvider({ children }: { children: ReactNode }) {
|
|
const [socket, setSocket] = useState<Socket | null>(null);
|
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
|
const { user, isAuthenticated } = useAuth();
|
|
|
|
// Initialize socket connection when user is authenticated
|
|
useEffect(() => {
|
|
if (!isAuthenticated || !user) {
|
|
return;
|
|
}
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
|
|
|
// Create socket connection
|
|
const socketInstance = io(API_URL, {
|
|
withCredentials: true,
|
|
query: {
|
|
userId: user.id,
|
|
},
|
|
});
|
|
|
|
// Set up event listeners
|
|
socketInstance.on('connect', () => {
|
|
console.log('Socket connected');
|
|
setIsConnected(true);
|
|
});
|
|
|
|
socketInstance.on('disconnect', () => {
|
|
console.log('Socket disconnected');
|
|
setIsConnected(false);
|
|
});
|
|
|
|
socketInstance.on('connect_error', (error) => {
|
|
console.error('Socket connection error:', error);
|
|
setIsConnected(false);
|
|
});
|
|
|
|
// Save socket instance
|
|
setSocket(socketInstance);
|
|
|
|
// Clean up on unmount
|
|
return () => {
|
|
socketInstance.disconnect();
|
|
setSocket(null);
|
|
setIsConnected(false);
|
|
};
|
|
}, [isAuthenticated, user]);
|
|
|
|
// Join a project room
|
|
const joinProject = (projectId: string) => {
|
|
if (socket && isConnected) {
|
|
socket.emit('project:join', projectId);
|
|
}
|
|
};
|
|
|
|
// Leave a project room
|
|
const leaveProject = (projectId: string) => {
|
|
if (socket && isConnected) {
|
|
socket.emit('project:leave', projectId);
|
|
}
|
|
};
|
|
|
|
// Event listeners with cleanup
|
|
const onProjectUpdated = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('project:updated', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('project:updated', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onCollaboratorAdded = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('project:collaboratorAdded', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('project:collaboratorAdded', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onGroupCreated = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('group:created', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('group:created', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onGroupUpdated = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('group:updated', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('group:updated', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onPersonAddedToGroup = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('group:personAdded', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('group:personAdded', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onPersonRemovedFromGroup = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('group:personRemoved', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('group:personRemoved', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
const onNotification = (callback: (data: any) => void) => {
|
|
if (socket) {
|
|
socket.on('notification:new', callback);
|
|
}
|
|
return () => {
|
|
if (socket) {
|
|
socket.off('notification:new', callback);
|
|
}
|
|
};
|
|
};
|
|
|
|
// Create the context value
|
|
const value = {
|
|
socket,
|
|
isConnected,
|
|
joinProject,
|
|
leaveProject,
|
|
onProjectUpdated,
|
|
onCollaboratorAdded,
|
|
onGroupCreated,
|
|
onGroupUpdated,
|
|
onPersonAddedToGroup,
|
|
onPersonRemovedFromGroup,
|
|
onNotification,
|
|
};
|
|
|
|
return <SocketContext.Provider value={value}>{children}</SocketContext.Provider>;
|
|
}
|
|
|
|
// Create a hook to use the SocketContext
|
|
export function useSocket() {
|
|
const context = useContext(SocketContext);
|
|
if (context === undefined) {
|
|
throw new Error("useSocket must be used within a SocketProvider");
|
|
}
|
|
return context;
|
|
} |