Chat, scrolling, sockets and timestamps updated
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
const WebSocket = require("ws");
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
|
||||
const clients = new Map();
|
||||
|
||||
wss.on("connection", (ws, req) => {
|
||||
console.log("New client connected");
|
||||
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
if (parsedMessage.type === "register") {
|
||||
clients.set(parsedMessage.userId, ws);
|
||||
console.log(`User registered: ${parsedMessage.userId}`);
|
||||
} else if (parsedMessage.type === "message") {
|
||||
const recipientWs = clients.get(parsedMessage.recipientId);
|
||||
if (recipientWs) {
|
||||
recipientWs.send(
|
||||
JSON.stringify({
|
||||
senderId: parsedMessage.senderId,
|
||||
message: parsedMessage.message,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.error(`User ${parsedMessage.recipientId} is not connected.`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error processing message:", err.message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Client disconnected");
|
||||
[...clients.entries()].forEach(([userId, clientWs]) => {
|
||||
if (clientWs === ws) {
|
||||
clients.delete(userId);
|
||||
console.log(`User disconnected: ${userId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
31
src/backend/server.js
Normal file
31
src/backend/server.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const express = require("express");
|
||||
const http = require("http");
|
||||
const { Server } = require("socket.io");
|
||||
require("dotenv").config();
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("New connection:", socket.id);
|
||||
|
||||
// For messages
|
||||
socket.on("sendMessage", (message) => {
|
||||
console.log("Message received:", message);
|
||||
socket.broadcast.emit("receiveMessage", message);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("User disconnected:", socket.id);
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.send("Socket.IO Server is running");
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 8099;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
@@ -1,76 +1,92 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Card from "./Card.jsx";
|
||||
import {get} from "../../backend/api";
|
||||
import {displayMessage} from "../../backend/notifications/notifications";
|
||||
import {MessageType} from "../../backend/notifications/message";
|
||||
import { get } from "../../backend/api";
|
||||
import { displayMessage } from "../../backend/notifications/notifications";
|
||||
import { MessageType } from "../../backend/notifications/message";
|
||||
|
||||
const ChatsList = (props) => {
|
||||
const { chats } = props;
|
||||
const { chats } = props;
|
||||
const [customChats, setCustomChats] = useState([]);
|
||||
|
||||
const [customChats, setCustomChats] = useState([]);
|
||||
const updateList = async () => {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) return null;
|
||||
|
||||
const updateList = async () => {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {return null;}
|
||||
const updatedChats = await Promise.all(
|
||||
chats.map(async (chat) => {
|
||||
const interlocutorId = chat.id1 === username ? chat.id2 : chat.id1;
|
||||
|
||||
const updatedChats = await Promise.all(
|
||||
chats.map(async (chat) => {
|
||||
const interlocutorId = chat.id1 === username ? chat.id2 : chat.id1
|
||||
const { ok, data } = await get("/auth/" + interlocutorId);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return null;
|
||||
}
|
||||
|
||||
const {ok, data} = await get('/auth/' + interlocutorId);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return null;
|
||||
}
|
||||
const interlocutor = data.user;
|
||||
|
||||
const interlocutor = data.user;
|
||||
const lastMessage =
|
||||
chat.messages.length > 0
|
||||
? chat.messages[chat.messages.length - 1]
|
||||
: { data: "", timestamp: new Date(0).toISOString() };
|
||||
|
||||
return {
|
||||
id: interlocutorId,
|
||||
name: interlocutor.nickname,
|
||||
lastMessage: chat.messages.length > 0 ? chat.messages[chat.messages.length - 1].data : "",
|
||||
}
|
||||
})
|
||||
);
|
||||
return {
|
||||
id: interlocutorId,
|
||||
name: interlocutor.nickname,
|
||||
lastMessageData: lastMessage.data,
|
||||
lastMessageTimestamp: lastMessage.timestamp,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setCustomChats(updatedChats.filter(chat => chat !== null));
|
||||
};
|
||||
const validChats = updatedChats.filter((chat) => chat !== null);
|
||||
|
||||
useEffect(() => {updateList().then();}, [chats])
|
||||
validChats.sort(
|
||||
(a, b) =>
|
||||
new Date(b.lastMessageTimestamp) - new Date(a.lastMessageTimestamp)
|
||||
);
|
||||
|
||||
setCustomChats(validChats);
|
||||
};
|
||||
|
||||
const colorMap = {
|
||||
orange: 'FFA500FF',
|
||||
aqua: '00FFFFFF',
|
||||
crimson: 'DC143CFF',
|
||||
red: 'FF0000FF',
|
||||
violet: '8A2BE2FF',
|
||||
seagreen: '20B2AAFF',
|
||||
green: 'ADFF2FFF',
|
||||
blue: '0000FFFF',
|
||||
pink: 'FF1493FF',
|
||||
cyan: '72FAFAFF'
|
||||
}
|
||||
useEffect(() => {
|
||||
updateList().then();
|
||||
}, [chats]);
|
||||
|
||||
function getColor(chatId) {
|
||||
const keys = Object.keys(colorMap);
|
||||
const numericId = Array.from(chatId).reduce((sum, char) => sum + char.charCodeAt(0), 0);
|
||||
const index = numericId % keys.length;
|
||||
return colorMap[keys[index]];
|
||||
}
|
||||
const colorMap = {
|
||||
orange: "FFA500FF",
|
||||
aqua: "00FFFFFF",
|
||||
crimson: "DC143CFF",
|
||||
red: "FF0000FF",
|
||||
violet: "8A2BE2FF",
|
||||
seagreen: "20B2AAFF",
|
||||
green: "ADFF2FFF",
|
||||
blue: "0000FFFF",
|
||||
pink: "FF1493FF",
|
||||
cyan: "72FAFAFF",
|
||||
};
|
||||
|
||||
function getColor(chatId) {
|
||||
const keys = Object.keys(colorMap);
|
||||
const numericId = Array.from(chatId).reduce(
|
||||
(sum, char) => sum + char.charCodeAt(0),
|
||||
0
|
||||
);
|
||||
const index = numericId % keys.length;
|
||||
return colorMap[keys[index]];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ChatsList">
|
||||
{customChats.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
name={item.name}
|
||||
lastMessage={item.lastMessage}
|
||||
id={item.id}
|
||||
color={getColor(item.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="ChatsList">
|
||||
{customChats.map((item, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
name={item.name}
|
||||
lastMessage={item.lastMessageData}
|
||||
id={item.id}
|
||||
color={getColor(item.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import React, { useEffect, useState, useRef } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./css/Chat.css";
|
||||
import { FaPaperPlane, FaSmile } from "react-icons/fa";
|
||||
import {get, post} from "../backend/api";
|
||||
import {displayMessage} from "../backend/notifications/notifications";
|
||||
import {MessageType} from "../backend/notifications/message";
|
||||
import { get, post } from "../backend/api";
|
||||
import { displayMessage } from "../backend/notifications/notifications";
|
||||
import { MessageType } from "../backend/notifications/message";
|
||||
import io from "socket.io-client";
|
||||
|
||||
const emojis = [
|
||||
"😀",
|
||||
@@ -88,8 +89,7 @@ const Chat = () => {
|
||||
const [myId, setMyId] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
// const id = parseInt(localStorage.getItem("interlocutorId"), 10) || 0;
|
||||
const id = localStorage.getItem("interlocutorId")
|
||||
const id = localStorage.getItem("interlocutorId");
|
||||
setInterlocutorId(id);
|
||||
|
||||
const username = localStorage.getItem("username");
|
||||
@@ -100,36 +100,24 @@ const Chat = () => {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
socket.current = new WebSocket("ws://localhost:8080");
|
||||
socket.current = io("http://localhost:8099");
|
||||
|
||||
socket.current.onopen = () => {
|
||||
console.log("WebSocket connected");
|
||||
socket.current.send(
|
||||
JSON.stringify({ type: "register", userId: "yourUserId" })
|
||||
);
|
||||
};
|
||||
socket.current.on("receiveMessage", (message) => {
|
||||
setMessages((prev) => [...prev, message]);
|
||||
});
|
||||
|
||||
socket.current.onmessage = (event) => {
|
||||
const receivedData = JSON.parse(event.data);
|
||||
setMessages((prev) => [...prev, receivedData]);
|
||||
};
|
||||
|
||||
socket.current.onerror = (event) => {
|
||||
console.error("WebSocket error observed:", event);
|
||||
};
|
||||
|
||||
socket.current.onclose = () => {
|
||||
console.log("WebSocket closed");
|
||||
};
|
||||
socket.current.on("connect_error", (err) => {
|
||||
console.error("Connection Error:", err.message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.current.close();
|
||||
socket.current.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
retrieveMessages().then();
|
||||
}, [myId, interlocutorId])
|
||||
}, [myId, interlocutorId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatRef.current) {
|
||||
@@ -137,25 +125,24 @@ const Chat = () => {
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
// The function for sending message to the DB
|
||||
async function sendMessageToDB (messageData) {
|
||||
const { ok, data } = post('/chat/message/' + myId + '/' + interlocutorId, { message: messageData });
|
||||
|
||||
async function sendMessageToDB(messageData) {
|
||||
const { ok, data } = await post(
|
||||
"/chat/message/" + myId + "/" + interlocutorId,
|
||||
{ message: messageData }
|
||||
);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// The function retrieves messages from the DB for the current chat
|
||||
async function retrieveMessages () {
|
||||
if (!myId || !interlocutorId) {return;}
|
||||
const { ok, data } = await get('/chat/item/' + myId + '/' + interlocutorId);
|
||||
async function retrieveMessages() {
|
||||
if (!myId || !interlocutorId) return;
|
||||
|
||||
const { ok, data } = await get("/chat/item/" + myId + "/" + interlocutorId);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
setMessages(data.chat.messages);
|
||||
}
|
||||
|
||||
@@ -165,15 +152,11 @@ const Chat = () => {
|
||||
senderId: myId,
|
||||
recipientId: interlocutorId,
|
||||
data: newMessage,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
timestamp: new Date().toLocaleString(),
|
||||
};
|
||||
socket.current.send(JSON.stringify(messageData));
|
||||
socket.current.emit("sendMessage", messageData);
|
||||
setMessages((prev) => [...prev, messageData]);
|
||||
|
||||
sendMessageToDB(messageData).then();
|
||||
|
||||
console.log('format:', messageData);
|
||||
|
||||
setNewMessage("");
|
||||
}
|
||||
};
|
||||
@@ -198,8 +181,7 @@ const Chat = () => {
|
||||
className="home-button"
|
||||
>
|
||||
Home
|
||||
</button>{" "}
|
||||
{}
|
||||
</button>
|
||||
</div>
|
||||
<div className="chat-messages" ref={chatRef}>
|
||||
{messages.map((msg, index) => (
|
||||
@@ -210,8 +192,7 @@ const Chat = () => {
|
||||
}`}
|
||||
>
|
||||
<div className="message-content">
|
||||
<b>{msg.senderId === myId ? "You" : "They"}:</b>{" "}
|
||||
{msg.data}
|
||||
<b>{msg.senderId === myId ? "You" : "They"}:</b> {msg.data}
|
||||
</div>
|
||||
<span className="message-timestamp">{msg.timestamp}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +1,86 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import HomeTitle from "../components/home/HomeTitle.jsx";
|
||||
import ChatsList from "../components/home/ChatsList.jsx";
|
||||
import Header from "../components/home/Header.jsx";
|
||||
import {displayMessage} from "../backend/notifications/notifications";
|
||||
import {MessageType} from "../backend/notifications/message";
|
||||
import {get, post} from "../backend/api";
|
||||
import { displayMessage } from "../backend/notifications/notifications";
|
||||
import { MessageType } from "../backend/notifications/message";
|
||||
import { get, post } from "../backend/api";
|
||||
import InputField from "../components/reg/InputField.jsx";
|
||||
import Search from "../components/home/Search.jsx";
|
||||
import {URLs} from "../__data__/urls";
|
||||
import { URLs } from "../__data__/urls";
|
||||
|
||||
const Home = () => {
|
||||
const [chats, setChats] = useState([])
|
||||
const [interlocutor, setInterlocutor] = useState("")
|
||||
const [chats, setChats] = useState([]);
|
||||
const [interlocutor, setInterlocutor] = useState("");
|
||||
|
||||
async function retrieveChats() {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
const {ok, data} = await get('/chat/list/' + username);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
setChats(data.chats);
|
||||
async function retrieveChats() {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
async function createChat(alias) {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
displayMessage("Sent", MessageType.INFO);
|
||||
|
||||
const {ok, data} = await post('/chat/item/' + username + '/' + alias);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
} else {
|
||||
localStorage.setItem('message', 'Successfully opened chat!');
|
||||
localStorage.setItem('interlocutorId', alias);
|
||||
window.location.href = URLs.chat.url;
|
||||
}
|
||||
const { ok, data } = await get("/chat/list/" + username);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
useEffect(() => {retrieveChats().then()}, [])
|
||||
const sortedChats = data.chats.sort((a, b) => {
|
||||
const lastMessageA = new Date(a.lastMessageTimestamp);
|
||||
const lastMessageB = new Date(b.lastMessageTimestamp);
|
||||
return lastMessageB - lastMessageA;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="homeWrapper">
|
||||
<div className="headerPos">
|
||||
<Header/>
|
||||
</div>
|
||||
setChats(sortedChats);
|
||||
}
|
||||
|
||||
<HomeTitle/>
|
||||
async function createChat(alias) {
|
||||
const username = localStorage.getItem("username");
|
||||
if (!username) {
|
||||
displayMessage("You're not logged in!", MessageType.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
<div className="search-input">
|
||||
<InputField
|
||||
title="Create new chat"
|
||||
value={interlocutor}
|
||||
setValue={setInterlocutor}
|
||||
placeholder="Enter the username (id)"
|
||||
/>
|
||||
</div>
|
||||
displayMessage("Sent", MessageType.INFO);
|
||||
|
||||
<Search search={createChat} item={interlocutor}/>
|
||||
const { ok, data } = await post("/chat/item/" + username + "/" + alias);
|
||||
if (!ok) {
|
||||
displayMessage(data.message, MessageType.ERROR);
|
||||
} else {
|
||||
localStorage.setItem("message", "Successfully opened chat!");
|
||||
localStorage.setItem("interlocutorId", alias);
|
||||
window.location.href = URLs.chat.url;
|
||||
}
|
||||
}
|
||||
|
||||
<p>Your chats</p>
|
||||
<ChatsList chats={chats} />
|
||||
useEffect(() => {
|
||||
retrieveChats();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="homeWrapper">
|
||||
<div className="headerPos">
|
||||
<Header />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
<HomeTitle />
|
||||
|
||||
<div className="search-input">
|
||||
<InputField
|
||||
title="Create new chat"
|
||||
value={interlocutor}
|
||||
setValue={setInterlocutor}
|
||||
placeholder="Enter the username (id)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Search search={createChat} item={interlocutor} />
|
||||
|
||||
<p>Your chats</p>
|
||||
<ChatsList chats={chats} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
body {
|
||||
background: linear-gradient(to right, #e0f7fa, #fffde7);
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -11,10 +15,13 @@
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 2px solid #0056b3;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
@@ -24,6 +31,7 @@
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
@@ -33,6 +41,12 @@
|
||||
max-width: 70%;
|
||||
word-wrap: break-word;
|
||||
display: inline-block;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.message-bubble:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.sent {
|
||||
@@ -58,8 +72,9 @@
|
||||
|
||||
.message-timestamp {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
color: black;
|
||||
text-align: right;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user