From 4cf909c607471f338974a4bca41b607f1c70ba33 Mon Sep 17 00:00:00 2001 From: Askar Akhmetkhanov Date: Wed, 9 Oct 2024 17:37:50 +0300 Subject: [PATCH] Chat, scrolling, sockets and timestamps updated --- package-lock.json | 167 +++++++++++------------- package.json | 2 + src/backend/chat-server.js | 43 ------ src/backend/server.js | 31 +++++ src/components/home/ChatsList.jsx | 130 ++++++++++-------- src/pages/Chat.jsx | 71 ++++------ src/pages/Home.jsx | 125 +++++++++--------- src/pages/css/Chat.css | 21 ++- stubs/api/chat/chats.json | 210 +++++++++++++----------------- 9 files changed, 381 insertions(+), 419 deletions(-) delete mode 100644 src/backend/chat-server.js create mode 100644 src/backend/server.js diff --git a/package-lock.json b/package-lock.json index 3b25c8a..8d33643 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,8 @@ "react-icons": "^5.3.0", "react-router-dom": "^6.26.1", "react-toastify": "^10.0.5", + "socket.io": "^4.8.0", + "socket.io-client": "^4.8.0", "styled-components": "^6.1.13", "typescript": "^5.5.4", "ws": "^8.18.0" @@ -1853,30 +1855,6 @@ "node": ">=18" } }, - "node_modules/@brojs/cli/node_modules/i18next": { - "version": "23.15.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.1.tgz", - "integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, "node_modules/@brojs/cli/node_modules/i18next-browser-languagedetector": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", @@ -2691,19 +2669,6 @@ "ijl-cli": "bin/ijl-cli" } }, - "node_modules/@ijl/cli/node_modules/@types/react": { - "version": "17.0.83", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", - "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.0.2" - } - }, "node_modules/@ijl/cli/node_modules/loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", @@ -2717,35 +2682,6 @@ "node": ">=8.9.0" } }, - "node_modules/@ijl/cli/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@ijl/cli/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, "node_modules/@ijl/cli/node_modules/react-hot-loader": { "version": "4.13.1", "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.13.1.tgz", @@ -2774,17 +2710,6 @@ } } }, - "node_modules/@ijl/cli/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "node_modules/@ijl/cli/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -3071,12 +2996,14 @@ "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -3138,14 +3065,6 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@types/source-list-map": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.6.tgz", @@ -3735,6 +3654,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } @@ -4364,6 +4284,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -4783,9 +4704,10 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz", + "integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==", + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -4802,6 +4724,40 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz", + "integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -4814,6 +4770,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -8959,15 +8916,16 @@ } }, "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -9005,6 +8963,21 @@ } } }, + "node_modules/socket.io-client": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz", + "integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -10147,6 +10120,14 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz", + "integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index a351a3f..d322cac 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "react-icons": "^5.3.0", "react-router-dom": "^6.26.1", "react-toastify": "^10.0.5", + "socket.io": "^4.8.0", + "socket.io-client": "^4.8.0", "styled-components": "^6.1.13", "typescript": "^5.5.4", "ws": "^8.18.0" diff --git a/src/backend/chat-server.js b/src/backend/chat-server.js deleted file mode 100644 index 340aa0f..0000000 --- a/src/backend/chat-server.js +++ /dev/null @@ -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}`); - } - }); - }); -}); diff --git a/src/backend/server.js b/src/backend/server.js new file mode 100644 index 0000000..1ca3577 --- /dev/null +++ b/src/backend/server.js @@ -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}`); +}); diff --git a/src/components/home/ChatsList.jsx b/src/components/home/ChatsList.jsx index acb1f65..7cccfdc 100644 --- a/src/components/home/ChatsList.jsx +++ b/src/components/home/ChatsList.jsx @@ -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 ( -
- {customChats.map((item, index) => ( - - ))} -
+
+ {customChats.map((item, index) => ( + + ))} +
); }; diff --git a/src/pages/Chat.jsx b/src/pages/Chat.jsx index b8ad2ef..cfbbb29 100644 --- a/src/pages/Chat.jsx +++ b/src/pages/Chat.jsx @@ -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 - {" "} - {} +
{messages.map((msg, index) => ( @@ -210,8 +192,7 @@ const Chat = () => { }`} >
- {msg.senderId === myId ? "You" : "They"}:{" "} - {msg.data} + {msg.senderId === myId ? "You" : "They"}: {msg.data}
{msg.timestamp}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 2158801..3b8fed1 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -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 ( -
-
-
-
+ setChats(sortedChats); + } - + 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; + } + } -

Your chats

- + useEffect(() => { + retrieveChats(); + }, []); + + return ( +
+
+
- ) -} -export default Home \ No newline at end of file + + +
+ +
+ + + +

Your chats

+ +
+ ); +}; + +export default Home; diff --git a/src/pages/css/Chat.css b/src/pages/css/Chat.css index 4d39650..1eb8c80 100644 --- a/src/pages/css/Chat.css +++ b/src/pages/css/Chat.css @@ -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; } diff --git a/stubs/api/chat/chats.json b/stubs/api/chat/chats.json index cc31ed4..4e9d173 100644 --- a/stubs/api/chat/chats.json +++ b/stubs/api/chat/chats.json @@ -7,25 +7,25 @@ "data": "Hello Bob!", "senderId": "alice", "recipientId": "bobsm", - "timestamp": "07:00:00 AM" + "timestamp": "09.10.2024 07:00:00" }, { "data": "Hey Alice, how are you?", "senderId": "bobsm", "recipientId": "alice", - "timestamp": "07:05:00 AM" + "timestamp": "09.10.2024 07:05:00" }, { "data": "I'm good, thanks for asking.", "senderId": "alice", "recipientId": "bobsm", - "timestamp": "07:10:00 AM" + "timestamp": "09.10.2024 07:10:00" }, { "data": "Glad to hear!", "senderId": "bobsm", "recipientId": "alice", - "timestamp": "07:15:00 AM" + "timestamp": "09.10.2024 07:15:00" } ] }, @@ -37,25 +37,25 @@ "data": "How's the project going?", "senderId": "alice", "recipientId": "charl", - "timestamp": "07:20:00 AM" + "timestamp": "09.10.2024 07:20:00" }, { "data": "It's coming along, almost done!", "senderId": "charl", "recipientId": "alice", - "timestamp": "07:25:00 AM" + "timestamp": "09.10.2024 07:25:00" }, { "data": "That's great to hear!", "senderId": "alice", "recipientId": "charl", - "timestamp": "07:30:00 AM" + "timestamp": "09.10.2024 07:30:00" }, { "data": "Thanks for checking in.", "senderId": "charl", "recipientId": "alice", - "timestamp": "07:35:00 AM" + "timestamp": "09.10.2024 07:35:00" } ] }, @@ -67,55 +67,25 @@ "data": "Did you get the files?", "senderId": "david", "recipientId": "alice", - "timestamp": "07:40:00 AM" + "timestamp": "09.10.2024 07:40:00" }, { "data": "Yes, I did. Thank you!", "senderId": "alice", "recipientId": "david", - "timestamp": "07:45:00 AM" + "timestamp": "09.10.2024 07:45:00" }, { "data": "You're welcome.", "senderId": "david", "recipientId": "alice", - "timestamp": "07:50:00 AM" + "timestamp": "09.10.2024 07:50:00" }, { "data": "Let me know if you need anything else.", "senderId": "alice", "recipientId": "david", - "timestamp": "07:55:00 AM" - } - ] - }, - { - "id1": "alice", - "id2": "evead", - "messages": [ - { - "data": "Eve, do you have the meeting details?", - "senderId": "alice", - "recipientId": "evead", - "timestamp": "08:00:00 AM" - }, - { - "data": "Yes, I just sent them to you.", - "senderId": "evead", - "recipientId": "alice", - "timestamp": "08:05:00 AM" - }, - { - "data": "Got it, thanks!", - "senderId": "alice", - "recipientId": "evead", - "timestamp": "08:10:00 AM" - }, - { - "data": "You're welcome.", - "senderId": "evead", - "recipientId": "alice", - "timestamp": "08:15:00 AM" + "timestamp": "09.10.2024 07:55:00" } ] }, @@ -127,25 +97,25 @@ "data": "Can you review this document for me?", "senderId": "alice", "recipientId": "frank", - "timestamp": "08:20:00 AM" + "timestamp": "09.10.2024 08:20:00" }, { "data": "Sure, I'll take a look.", "senderId": "frank", "recipientId": "alice", - "timestamp": "08:25:00 AM" + "timestamp": "09.10.2024 08:25:00" }, { "data": "Thanks, much appreciated!", "senderId": "alice", "recipientId": "frank", - "timestamp": "08:30:00 AM" + "timestamp": "09.10.2024 08:30:00" }, { "data": "No problem.", "senderId": "frank", "recipientId": "alice", - "timestamp": "08:35:00 AM" + "timestamp": "09.10.2024 08:35:00" } ] }, @@ -157,25 +127,25 @@ "data": "Hey Grace, let's meet up for coffee!", "senderId": "alice", "recipientId": "grace", - "timestamp": "08:40:00 AM" + "timestamp": "09.10.2024 08:40:00" }, { "data": "Sounds good, when are you free?", "senderId": "grace", "recipientId": "alice", - "timestamp": "08:45:00 AM" + "timestamp": "09.10.2024 08:45:00" }, { "data": "How about tomorrow afternoon?", "senderId": "alice", "recipientId": "grace", - "timestamp": "08:50:00 AM" + "timestamp": "09.10.2024 08:50:00" }, { "data": "Works for me!", "senderId": "grace", "recipientId": "alice", - "timestamp": "08:55:00 AM" + "timestamp": "09.10.2024 08:55:00" } ] }, @@ -187,25 +157,25 @@ "data": "Hannah, do you have a moment?", "senderId": "alice", "recipientId": "hanna", - "timestamp": "09:00:00 AM" + "timestamp": "09.10.2024 09:00:00" }, { "data": "Sure, what's up?", "senderId": "hanna", "recipientId": "alice", - "timestamp": "09:05:00 AM" + "timestamp": "09.10.2024 09:05:00" }, { "data": "Just wanted to check on the report.", "senderId": "alice", "recipientId": "hanna", - "timestamp": "09:10:00 AM" + "timestamp": "09.10.2024 09:10:00" }, { "data": "I'll send it soon.", "senderId": "hanna", "recipientId": "alice", - "timestamp": "09:15:00 AM" + "timestamp": "09.10.2024 09:15:00" } ] }, @@ -217,25 +187,25 @@ "data": "Ian, have you completed the review?", "senderId": "alice", "recipientId": "ianda", - "timestamp": "09:20:00 AM" + "timestamp": "09.10.2024 09:20:00" }, { "data": "Yes, I sent my feedback.", "senderId": "ianda", "recipientId": "alice", - "timestamp": "09:25:00 AM" + "timestamp": "09.10.2024 09:25:00" }, { "data": "Thanks for that.", "senderId": "alice", "recipientId": "ianda", - "timestamp": "09:30:00 AM" + "timestamp": "09.10.2024 09:30:00" }, { "data": "Anytime!", "senderId": "ianda", "recipientId": "alice", - "timestamp": "09:35:00 AM" + "timestamp": "09.10.2024 09:35:00" } ] }, @@ -247,25 +217,25 @@ "data": "Jill, let's schedule a catch-up meeting.", "senderId": "alice", "recipientId": "jillt", - "timestamp": "09:40:00 AM" + "timestamp": "09.10.2024 09:40:00" }, { "data": "Sounds good, when works for you?", "senderId": "jillt", "recipientId": "alice", - "timestamp": "09:45:00 AM" + "timestamp": "09.10.2024 09:45:00" }, { "data": "Tomorrow afternoon?", "senderId": "alice", "recipientId": "jillt", - "timestamp": "09:50:00 AM" + "timestamp": "09.10.2024 09:50:00" }, { "data": "That works for me!", "senderId": "jillt", "recipientId": "alice", - "timestamp": "09:55:00 AM" + "timestamp": "09.10.2024 09:55:00" } ] }, @@ -277,25 +247,25 @@ "data": "Eve, did you send the schedule?", "senderId": "alice", "recipientId": "evead", - "timestamp": "10:00:00 AM" + "timestamp": "09.10.2024 10:00:00" }, { "data": "Yes, just sent it.", "senderId": "evead", "recipientId": "alice", - "timestamp": "10:05:00 AM" + "timestamp": "09.10.2024 10:05:00" }, { "data": "Thanks, much appreciated!", "senderId": "alice", "recipientId": "evead", - "timestamp": "10:10:00 AM" + "timestamp": "09.10.2024 10:10:00" }, { "data": "No problem!", "senderId": "evead", "recipientId": "alice", - "timestamp": "10:15:00 AM" + "timestamp": "09.10.2024 10:15:00" } ] }, @@ -307,25 +277,25 @@ "data": "How's everything going?", "senderId": "bobsm", "recipientId": "charl", - "timestamp": "10:20:00 AM" + "timestamp": "09.10.2024 10:20:00" }, { "data": "Pretty good, how about you?", "senderId": "charl", "recipientId": "bobsm", - "timestamp": "10:25:00 AM" + "timestamp": "09.10.2024 10:25:00" }, { "data": "Can't complain!", "senderId": "bobsm", "recipientId": "charl", - "timestamp": "10:30:00 AM" + "timestamp": "09.10.2024 10:30:00" }, { "data": "Glad to hear that.", "senderId": "charl", "recipientId": "bobsm", - "timestamp": "10:35:00 AM" + "timestamp": "09.10.2024 10:35:00" } ] }, @@ -337,25 +307,25 @@ "data": "Can you send the report?", "senderId": "bobsm", "recipientId": "david", - "timestamp": "10:40:00 AM" + "timestamp": "09.10.2024 10:40:00" }, { "data": "I'll send it in an hour.", "senderId": "david", "recipientId": "bobsm", - "timestamp": "10:45:00 AM" + "timestamp": "09.10.2024 10:45:00" }, { "data": "Perfect, thanks.", "senderId": "bobsm", "recipientId": "david", - "timestamp": "10:50:00 AM" + "timestamp": "09.10.2024 10:50:00" }, { "data": "No problem.", "senderId": "david", "recipientId": "bobsm", - "timestamp": "10:55:00 AM" + "timestamp": "09.10.2024 10:55:00" } ] }, @@ -367,25 +337,25 @@ "data": "Hey Eve, how's it going?", "senderId": "charl", "recipientId": "evead", - "timestamp": "11:00:00 AM" + "timestamp": "09.10.2024 11:00:00" }, { "data": "Good, how about you?", "senderId": "evead", "recipientId": "charl", - "timestamp": "11:05:00 AM" + "timestamp": "09.10.2024 11:05:00" }, { "data": "Can't complain!", "senderId": "charl", "recipientId": "evead", - "timestamp": "11:10:00 AM" + "timestamp": "09.10.2024 11:10:00" }, { "data": "Glad to hear.", "senderId": "evead", "recipientId": "charl", - "timestamp": "11:15:00 AM" + "timestamp": "09.10.2024 11:15:00" } ] }, @@ -397,25 +367,25 @@ "data": "Do you have time to talk today?", "senderId": "charl", "recipientId": "frank", - "timestamp": "11:20:00 AM" + "timestamp": "09.10.2024 11:20:00" }, { "data": "I have a meeting, but I can chat afterward.", "senderId": "frank", "recipientId": "charl", - "timestamp": "11:25:00 AM" + "timestamp": "09.10.2024 11:25:00" }, { "data": "Sounds good.", "senderId": "charl", "recipientId": "frank", - "timestamp": "11:30:00 AM" + "timestamp": "09.10.2024 11:30:00" }, { "data": "I'll message you after.", "senderId": "frank", "recipientId": "charl", - "timestamp": "11:35:00 AM" + "timestamp": "09.10.2024 11:35:00" } ] }, @@ -427,25 +397,25 @@ "data": "Did you review the document?", "senderId": "david", "recipientId": "frank", - "timestamp": "11:40:00 AM" + "timestamp": "09.10.2024 11:40:00" }, { "data": "Yes, it's all good.", "senderId": "frank", "recipientId": "david", - "timestamp": "11:45:00 AM" + "timestamp": "09.10.2024 11:45:00" }, { "data": "Great, thanks for the quick turnaround!", "senderId": "david", "recipientId": "frank", - "timestamp": "11:50:00 AM" + "timestamp": "09.10.2024 11:50:00" }, { "data": "No worries!", "senderId": "frank", "recipientId": "david", - "timestamp": "11:55:00 AM" + "timestamp": "09.10.2024 11:55:00" } ] }, @@ -457,25 +427,25 @@ "data": "Grace, can you send the updated schedule?", "senderId": "david", "recipientId": "grace", - "timestamp": "12:00:00 PM" + "timestamp": "09.10.2024 12:00:00" }, { "data": "Yes, I'll send it in a few minutes.", "senderId": "grace", "recipientId": "david", - "timestamp": "12:05:00 PM" + "timestamp": "09.10.2024 12:05:00" }, { "data": "Thanks, much appreciated!", "senderId": "david", "recipientId": "grace", - "timestamp": "12:10:00 PM" + "timestamp": "09.10.2024 12:10:00" }, { "data": "You're welcome!", "senderId": "grace", "recipientId": "david", - "timestamp": "12:15:00 PM" + "timestamp": "09.10.2024 12:15:00" } ] }, @@ -487,25 +457,25 @@ "data": "How are you today?", "senderId": "frank", "recipientId": "grace", - "timestamp": "12:20:00 PM" + "timestamp": "09.10.2024 12:20:00" }, { "data": "I'm doing well, thanks for asking.", "senderId": "grace", "recipientId": "frank", - "timestamp": "12:25:00 PM" + "timestamp": "09.10.2024 12:25:00" }, { "data": "Glad to hear that.", "senderId": "frank", "recipientId": "grace", - "timestamp": "12:30:00 PM" + "timestamp": "09.10.2024 12:30:00" }, { "data": "How about you?", "senderId": "grace", "recipientId": "frank", - "timestamp": "12:35:00 PM" + "timestamp": "09.10.2024 12:35:00" } ] }, @@ -517,25 +487,25 @@ "data": "Did you attend the meeting?", "senderId": "frank", "recipientId": "hanna", - "timestamp": "12:40:00 PM" + "timestamp": "09.10.2024 12:40:00" }, { "data": "Yes, it was productive.", "senderId": "hanna", "recipientId": "frank", - "timestamp": "12:45:00 PM" + "timestamp": "09.10.2024 12:45:00" }, { "data": "Good to hear!", "senderId": "frank", "recipientId": "hanna", - "timestamp": "12:50:00 PM" + "timestamp": "09.10.2024 12:50:00" }, { "data": "Indeed, lots to follow up on.", "senderId": "hanna", "recipientId": "frank", - "timestamp": "12:55:00 PM" + "timestamp": "09.10.2024 12:55:00" } ] }, @@ -547,25 +517,25 @@ "data": "Can we meet later today?", "senderId": "grace", "recipientId": "hanna", - "timestamp": "01:00:00 PM" + "timestamp": "09.10.2024 01:00:00" }, { "data": "Sure, what's a good time?", "senderId": "hanna", "recipientId": "grace", - "timestamp": "01:05:00 PM" + "timestamp": "09.10.2024 01:05:00" }, { - "data": "How about 3 PM?", + "data": "How about 3?", "senderId": "grace", "recipientId": "hanna", - "timestamp": "01:10:00 PM" + "timestamp": "09.10.2024 01:10:00" }, { "data": "Works for me.", "senderId": "hanna", "recipientId": "grace", - "timestamp": "01:15:00 PM" + "timestamp": "09.10.2024 01:15:00" } ] }, @@ -577,25 +547,25 @@ "data": "Ian, did you get the message I sent?", "senderId": "grace", "recipientId": "ianda", - "timestamp": "01:20:00 PM" + "timestamp": "09.10.2024 01:20:00" }, { "data": "Yes, I'll respond soon.", "senderId": "ianda", "recipientId": "grace", - "timestamp": "01:25:00 PM" + "timestamp": "09.10.2024 01:25:00" }, { "data": "Thanks, appreciate it!", "senderId": "grace", "recipientId": "ianda", - "timestamp": "01:30:00 PM" + "timestamp": "09.10.2024 01:30:00" }, { "data": "You're welcome!", "senderId": "ianda", "recipientId": "grace", - "timestamp": "01:35:00 PM" + "timestamp": "09.10.2024 01:35:00" } ] }, @@ -607,25 +577,25 @@ "data": "Ian, do you have a minute?", "senderId": "hanna", "recipientId": "ianda", - "timestamp": "01:40:00 PM" + "timestamp": "09.10.2024 01:40:00" }, { "data": "Yes, what do you need?", "senderId": "ianda", "recipientId": "hanna", - "timestamp": "01:45:00 PM" + "timestamp": "09.10.2024 01:45:00" }, { "data": "Just a quick update on the project.", "senderId": "hanna", "recipientId": "ianda", - "timestamp": "01:50:00 PM" + "timestamp": "09.10.2024 01:50:00" }, { "data": "I'll email you the details.", "senderId": "ianda", "recipientId": "hanna", - "timestamp": "01:55:00 PM" + "timestamp": "09.10.2024 01:55:00" } ] }, @@ -637,25 +607,25 @@ "data": "Jill, can we talk tomorrow?", "senderId": "hanna", "recipientId": "jillt", - "timestamp": "02:00:00 PM" + "timestamp": "09.10.2024 02:00:00" }, { - "data": "Yes, I'm free after 2 PM.", + "data": "Yes, I'm free after 2.", "senderId": "jillt", "recipientId": "hanna", - "timestamp": "02:05:00 PM" + "timestamp": "09.10.2024 02:05:00" }, { "data": "Perfect, see you then.", "senderId": "hanna", "recipientId": "jillt", - "timestamp": "02:10:00 PM" + "timestamp": "09.10.2024 02:10:00" }, { "data": "Looking forward to it.", "senderId": "jillt", "recipientId": "hanna", - "timestamp": "02:15:00 PM" + "timestamp": "09.10.2024 02:15:00" } ] }, @@ -667,25 +637,25 @@ "data": "Jill, I have the files you requested.", "senderId": "ianda", "recipientId": "jillt", - "timestamp": "02:20:00 PM" + "timestamp": "09.10.2024 02:20:00" }, { "data": "Thanks, please send them over.", "senderId": "jillt", "recipientId": "ianda", - "timestamp": "02:25:00 PM" + "timestamp": "09.10.2024 02:25:00" }, { "data": "I'll send them right now.", "senderId": "ianda", "recipientId": "jillt", - "timestamp": "02:30:00 PM" + "timestamp": "09.10.2024 02:30:00" }, { "data": "Great, thanks again!", "senderId": "jillt", "recipientId": "ianda", - "timestamp": "02:35:00 PM" + "timestamp": "09.10.2024 02:35:00" } ] }