diff --git a/client/src/common/contexts/SocketContext/SocketContext.jsx b/client/src/common/contexts/SocketContext/SocketContext.jsx
index 8a9c285..4c8b00d 100644
--- a/client/src/common/contexts/SocketContext/SocketContext.jsx
+++ b/client/src/common/contexts/SocketContext/SocketContext.jsx
@@ -1,30 +1,64 @@
-import {createContext} from "react";
-import {io} from "socket.io-client";
+import { createContext, useState, useEffect, useCallback } from 'react';
+import { io } from 'socket.io-client';
-export const SocketContext = createContext({});
+export const SocketContext = createContext();
export const SocketProvider = ({ children }) => {
- const socket = io("/", {autoConnect: false});
+ const [socket, setSocket] = useState(null);
+ const [connected, setConnected] = useState(false);
- const connect = () => {
- socket.connect();
+ const connect = useCallback(() => {
+ if (!socket) {
+ const newSocket = io(window.location.hostname === 'localhost' ? 'http://localhost:5287' : '/');
+
+ newSocket.on('connect', () => {
+ setConnected(true);
+ console.log('Socket connected');
+ });
+
+ newSocket.on('disconnect', () => {
+ setConnected(false);
+ console.log('Socket disconnected');
+ });
+
+ setSocket(newSocket);
}
+ }, [socket]);
- const send = (event, data) => {
- socket.emit(event, data);
+ const disconnect = useCallback(() => {
+ if (socket) {
+ socket.disconnect();
+ setSocket(null);
+ setConnected(false);
}
+ }, [socket]);
- const disconnect = () => {
- socket.disconnect();
+ const send = useCallback((event, data) => {
+ if (socket) {
+ socket.emit(event, data);
+ } else {
+ console.error('Cannot send event, socket not connected');
}
+ }, [socket]);
- const addListener = (event, callback) => {
- socket.on(event, callback);
+ const on = useCallback((event, callback) => {
+ if (socket) {
+ socket.on(event, callback);
}
+ return () => {
+ if (socket) socket.off(event, callback);
+ };
+ }, [socket]);
- return (
-
- {children}
-
- )
-}
\ No newline at end of file
+ useEffect(() => {
+ return () => {
+ if (socket) socket.disconnect();
+ };
+ }, [socket]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/pages/Game/Game.jsx b/client/src/pages/Game/Game.jsx
index a652052..53ff219 100644
--- a/client/src/pages/Game/Game.jsx
+++ b/client/src/pages/Game/Game.jsx
@@ -1,5 +1,6 @@
import "./styles.sass";
import {StateContext} from "@/common/contexts/StateContext";
+import {SocketContext} from "@/common/contexts/SocketContext";
import {useContext, useState, useEffect, useRef} from "react";
import MusicSlider from "@/pages/Game/components/MusicSlider";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
@@ -7,10 +8,59 @@ import {faMessage} from "@fortawesome/free-solid-svg-icons";
export const Game = () => {
const {setCurrentState} = useContext(StateContext);
- const [messages, setMessages] = useState([{sender: "Marco", text: "Hallo!"}]);
+ const {send, on, socket} = useContext(SocketContext);
+ const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState("");
+ const [connectedUsers, setConnectedUsers] = useState([]);
+ const [username, setUsername] = useState("");
const messageEndRef = useRef(null);
+ useEffect(() => {
+ const handleChatMessage = (messageData) => {
+ setMessages(prev => [...prev, messageData]);
+ };
+
+ const handleUserConnected = (userData) => {
+ setMessages(prev => [...prev, {
+ system: true,
+ text: `${userData.name} ist beigetreten`
+ }]);
+ setConnectedUsers(prev => [...prev, userData]);
+ };
+
+ const handleUserDisconnected = (userId) => {
+ setConnectedUsers(prev => {
+ const user = prev.find(u => u.id === userId);
+ if (user) {
+ setMessages(prevMsgs => [...prevMsgs, {
+ system: true,
+ text: `${user.name} hat den Raum verlassen`
+ }]);
+ }
+ return prev.filter(u => u.id !== userId);
+ });
+ };
+
+ if (socket && socket.id) {
+ send("get-user-info");
+ }
+
+ const handleUserInfo = (userInfo) => {
+ setUsername(userInfo.name);
+ };
+
+ const cleanupChatMessage = on("chat-message", handleChatMessage);
+ const cleanupUserConnected = on("user-connected", handleUserConnected);
+ const cleanupUserDisconnected = on("user-disconnected", handleUserDisconnected);
+ const cleanupUserInfo = on("user-info", handleUserInfo);
+
+ return () => {
+ cleanupChatMessage();
+ cleanupUserConnected();
+ cleanupUserDisconnected();
+ cleanupUserInfo();
+ };
+ }, [on, send, socket]);
useEffect(() => {
messageEndRef.current?.scrollIntoView({behavior: "smooth"});
@@ -18,7 +68,12 @@ export const Game = () => {
const handleSendMessage = () => {
if (inputValue.trim()) {
- setMessages([...messages, {sender: "User", text: inputValue}]);
+ const messageData = {
+ text: inputValue,
+ sender: username
+ };
+ send("send-message", messageData);
+ setMessages(prev => [...prev, messageData]);
setInputValue("");
}
};
@@ -47,9 +102,15 @@ export const Game = () => {
{messages.map((message, index) => (
-
-
{message.sender}:
-
{message.text}
+
+ {message.system ? (
+ {message.text}
+ ) : (
+ <>
+ {message.sender}:
+ {message.text}
+ >
+ )}
))}
diff --git a/client/src/pages/Game/styles.sass b/client/src/pages/Game/styles.sass
index 8925e12..de5fe3d 100644
--- a/client/src/pages/Game/styles.sass
+++ b/client/src/pages/Game/styles.sass
@@ -210,6 +210,16 @@
.message-text
margin-left: 5px
+ .message-text.system
+ font-style: italic
+ color: #888
+
+ .system-message
+ text-align: center
+ font-style: italic
+ color: #888
+ padding: 5px 0
+
.chat-input
display: flex
padding: 15px
diff --git a/client/src/pages/Home/Home.jsx b/client/src/pages/Home/Home.jsx
index d9583e7..2d5ed5e 100644
--- a/client/src/pages/Home/Home.jsx
+++ b/client/src/pages/Home/Home.jsx
@@ -5,35 +5,63 @@ import CreateForm from "@/pages/Home/components/CreateForm";
import {faArrowRightToBracket, faPlusSquare} from "@fortawesome/free-solid-svg-icons";
import {StateContext} from "@/common/contexts/StateContext";
import {SocketContext} from "@/common/contexts/SocketContext";
-import {useContext, useState} from "react";
+import {useContext, useState, useEffect} from "react";
export const Home = () => {
const {setCurrentState} = useContext(StateContext);
- const {connect, send} = useContext(SocketContext);
+ const {connect, send, on} = useContext(SocketContext);
const [homeState, setHomeState] = useState("initial"); // initial, joining, creating
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ connect();
+
+ const handleRoomCreated = (roomId) => {
+ console.log("Room created", roomId);
+ setCurrentState("Game");
+ };
+
+ const handleRoomJoined = (roomId) => {
+ console.log("Room joined", roomId);
+ setCurrentState("Game");
+ };
+
+ const handleRoomNotFound = (roomId) => {
+ setError(`Room ${roomId} not found`);
+ };
+
+ const cleanupRoomCreated = on("room-created", handleRoomCreated);
+ const cleanupRoomJoined = on("room-joined", handleRoomJoined);
+ const cleanupRoomNotFound = on("room-not-found", handleRoomNotFound);
+
+ return () => {
+ cleanupRoomCreated();
+ cleanupRoomJoined();
+ cleanupRoomNotFound();
+ };
+ }, [connect, setCurrentState, on]);
const handleJoinClick = () => {
setHomeState("joining");
+ setError("");
};
const handleCreateClick = () => {
setHomeState("creating");
+ setError("");
};
const handleBack = () => {
setHomeState("initial");
+ setError("");
};
const handleJoinSubmit = (name, roomCode) => {
- connect();
- send("joinRoom", {name, roomCode});
- setCurrentState("Game");
+ send("join-room", {roomId: roomCode, name});
};
const handleCreateSubmit = (name) => {
- connect();
- send("createRoom", {name});
- setCurrentState("Game");
+ send("create-room", {name});
};
return (
@@ -55,13 +83,13 @@ export const Home = () => {
{homeState === 'joining' && (
-
+
)}
{homeState === 'creating' && (
-
+
)}
diff --git a/client/src/pages/Home/components/JoinForm/JoinForm.jsx b/client/src/pages/Home/components/JoinForm/JoinForm.jsx
index 583d44b..7f3c570 100644
--- a/client/src/pages/Home/components/JoinForm/JoinForm.jsx
+++ b/client/src/pages/Home/components/JoinForm/JoinForm.jsx
@@ -1,13 +1,19 @@
import "@/common/styles/forms.sass";
-import {useState} from "react";
+import {useState, useEffect} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faArrowLeft, faArrowRightToBracket} from "@fortawesome/free-solid-svg-icons";
import FormInput from "@/common/components/FormInput";
-export const JoinForm = ({onSubmit, onBack}) => {
+export const JoinForm = ({onSubmit, onBack, error}) => {
const [name, setName] = useState("");
const [roomCode, setRoomCode] = useState("");
const [errors, setErrors] = useState({});
+
+ useEffect(() => {
+ if (error) {
+ setErrors(prev => ({...prev, roomCode: error}));
+ }
+ }, [error]);
const handleSubmit = (e) => {
e.preventDefault();
diff --git a/server/controller/room.js b/server/controller/room.js
index 01279d7..be452c7 100644
--- a/server/controller/room.js
+++ b/server/controller/room.js
@@ -8,7 +8,7 @@ module.exports.connectUserToRoom = (roomId, user) => {
} else {
rooms[roomId] = {members: [{...user, creator: true}], settings: {}};
}
- console.log(JSON.stringify(rooms));
+ console.log(`User ${user.name} connected to room ${roomId}`);
}
module.exports.getUserRoom = (userId) => {
@@ -18,6 +18,21 @@ module.exports.getUserRoom = (userId) => {
if (memberIndex !== -1) return roomId;
}
+ return null;
+}
+
+module.exports.getRoomUsers = (roomId) => {
+ if (rooms[roomId]) {
+ return rooms[roomId].members;
+ }
+ return [];
+}
+
+module.exports.getRoomCreator = (roomId) => {
+ if (rooms[roomId]) {
+ return rooms[roomId].members.find(member => member.creator);
+ }
+ return null;
}
module.exports.disconnectUser = (userId) => {
@@ -26,12 +41,17 @@ module.exports.disconnectUser = (userId) => {
const memberIndex = room.members.findIndex(member => member.id === userId);
if (memberIndex !== -1) {
- if (room.members[memberIndex].creator) {
- if (room.members.length > 1) room.members[1].creator = true;
+ if (room.members[memberIndex].creator && room.members.length > 1) {
+ room.members[1].creator = true;
}
room.members.splice(memberIndex, 1);
- if (room.members.length === 0) delete rooms[roomId];
+ console.log(`User ${userId} disconnected from room ${roomId}`);
+
+ if (room.members.length === 0) {
+ delete rooms[roomId];
+ console.log(`Room ${roomId} deleted because it's empty`);
+ }
break;
}
diff --git a/server/handler/connection.js b/server/handler/connection.js
index 08b10e6..17c600d 100644
--- a/server/handler/connection.js
+++ b/server/handler/connection.js
@@ -1,7 +1,8 @@
-const {connectUserToRoom, roomExists, disconnectUser, getUserRoom} = require("../controller/room");
+const {connectUserToRoom, roomExists, disconnectUser, getUserRoom, getRoomUsers} = require("../controller/room");
module.exports = (socket) => {
let currentRoomId = null;
+ let currentUser = null;
socket.on("disconnect", () => {
const roomId = getUserRoom(socket.id);
@@ -14,9 +15,10 @@ module.exports = (socket) => {
if (currentRoomId) return socket.emit("already-in-room", currentRoomId);
if (roomExists(roomId.toString())) {
- connectUserToRoom(roomId, {id: socket.id, name: name.toString()});
+ currentUser = {id: socket.id, name: name.toString()};
+ connectUserToRoom(roomId, currentUser);
socket.join(roomId);
- socket.to(roomId).emit("user-connected", {name: name.toString(), id: socket.id});
+ socket.to(roomId).emit("user-connected", currentUser);
socket.emit("room-joined", roomId);
currentRoomId = roomId;
} else {
@@ -27,10 +29,31 @@ module.exports = (socket) => {
socket.on("create-room", ({name}) => {
if (!name) return socket.emit("room-name-required");
const roomId = Math.random().toString(36).substring(7);
- connectUserToRoom(roomId, {id: socket.id, name: name?.toString()});
+ currentUser = {id: socket.id, name: name?.toString()};
+ connectUserToRoom(roomId, currentUser);
socket.join(roomId);
socket.emit("room-created", roomId);
currentRoomId = roomId;
});
+ socket.on("send-message", (messageData) => {
+ const roomId = getUserRoom(socket.id);
+ if (roomId) {
+ socket.to(roomId).emit("chat-message", messageData);
+ }
+ });
+
+ socket.on("get-user-info", () => {
+ if (currentUser) {
+ socket.emit("user-info", currentUser);
+ }
+ });
+
+ socket.on("get-room-users", () => {
+ const roomId = getUserRoom(socket.id);
+ if (roomId) {
+ const users = getRoomUsers(roomId);
+ socket.emit("room-users", users);
+ }
+ });
}
\ No newline at end of file