Skip to content

Commit bb698f3

Browse files
committed
Add like and dislike counter
1 parent d46c28b commit bb698f3

7 files changed

Lines changed: 173 additions & 80 deletions

File tree

chat-app/client/src/components/Chat/Chat.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState } from "react";
22
import Message from "../Message/Message";
33
import "./Chat.css";
44

5-
const Chat = ({ messages, sendMessage }) => {
5+
const Chat = ({ messages, sendMessage, reactToMessage }) => {
66
const [input, setInput] = useState("");
77

88
const handleSend = () => {
@@ -14,12 +14,16 @@ const Chat = ({ messages, sendMessage }) => {
1414
return (
1515
<div className="container">
1616
<ul className="messages">
17-
{messages.map((message) => (
17+
{messages.map((message, i) => (
1818
<Message
19+
key={i}
20+
i={i}
21+
index={message.index}
1922
message={message.text}
2023
messageTime={message.time}
2124
likes={message.likes}
2225
dislikes={message.dislikes}
26+
reactToMessage={reactToMessage}
2327
/>
2428
))}
2529
</ul>

chat-app/client/src/components/Message/Message.jsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import formatTime from "../../utils/formatTime";
22
import "./Message.css";
33

4-
const Message = ({ message, messageTime, likes, dislikes }) => {
4+
const Message = ({
5+
message,
6+
messageTime,
7+
likes,
8+
dislikes,
9+
reactToMessage,
10+
i,
11+
}) => {
512
const { date, time } = formatTime(messageTime);
613

714
return (
@@ -10,11 +17,21 @@ const Message = ({ message, messageTime, likes, dislikes }) => {
1017
<div className="message-info">
1118
<p>{`${date} ${time}`}</p>
1219
<div className="message-like">
13-
<button className="like-button">&#128077;</button>
20+
<button
21+
className="like-button"
22+
onClick={() => reactToMessage(i, "likes")}
23+
>
24+
&#128077;
25+
</button>
1426
<p>Likes: {likes}</p>
1527
</div>
1628
<div className="message-like">
17-
<button className="like-button">&#128078;</button>
29+
<button
30+
className="like-button"
31+
onClick={() => reactToMessage(i, "dislikes")}
32+
>
33+
&#128078;
34+
</button>
1835
<p>Dislikes: {dislikes}</p>
1936
</div>
2037
</div>
Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
import { useEffect, useRef, useState } from "react";
22

3-
const usePoll = (url) => {
3+
const useLongPoll = (url) => {
44
const [messages, setMessages] = useState([]);
55
const cursorRef = useRef(0);
66
const activeRef = useRef(true);
77

88
useEffect(() => {
9+
fetch(`${url}/poll/history`)
10+
.then((r) => r.json())
11+
.then((history) => {
12+
setMessages(history);
13+
cursorRef.current = history.length;
14+
});
15+
916
const poll = async () => {
1017
while (activeRef.current) {
1118
try {
1219
const res = await fetch(`${url}/poll?since=${cursorRef.current}`);
1320
const data = await res.json();
1421

1522
if (data.messages.length > 0) {
16-
cursorRef.current = data.cursor;
17-
setMessages((prev) => [...prev, ...data.messages]);
23+
data.messages.forEach((msg) => {
24+
if (msg.index !== undefined) {
25+
setMessages((prev) =>
26+
prev.map((m, i) => (i === msg.index ? { ...m, ...msg } : m)),
27+
);
28+
} else {
29+
cursorRef.current = data.cursor;
30+
setMessages((prev) => [...prev, msg]);
31+
}
32+
});
1833
}
1934
} catch {
2035
await new Promise((r) => setTimeout(r, 2000));
@@ -29,17 +44,25 @@ const usePoll = (url) => {
2944
}, [url]);
3045

3146
const sendMessage = async (text) => {
32-
const message = { text, time: new Date().toISOString() };
33-
setMessages((prev) => [...prev, { ...message }]);
34-
35-
await fetch(`${url}/message`, {
47+
const msg = { text, time: new Date().toISOString(), likes: 0, dislikes: 0 };
48+
setMessages((prev) => [...prev, { ...msg, self: true }]);
49+
await fetch(`${url}/poll/message`, {
3650
method: "POST",
3751
headers: { "Content-Type": "application/json" },
38-
body: JSON.stringify(message),
52+
body: JSON.stringify(msg),
3953
});
4054
};
4155

42-
return { messages, sendMessage };
56+
const reactToMessage = async (index, type) => {
57+
setMessages((prev) =>
58+
prev.map((msg, i) =>
59+
i === index ? { ...msg, [type]: msg[type] + 1 } : msg,
60+
),
61+
);
62+
await fetch(`${url}/poll/message/${index}/${type}`, { method: "PATCH" });
63+
};
64+
65+
return { messages, sendMessage, reactToMessage };
4366
};
4467

45-
export default usePoll;
68+
export default useLongPoll;

chat-app/client/src/hooks/useSocket.jsx

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,38 @@ const useSocket = (url) => {
99
socketRef.current = io(url);
1010

1111
socketRef.current.on("history", (history) => {
12-
setMessages(history.map((message) => ({ ...message })));
12+
setMessages(history);
1313
});
1414

15-
socketRef.current.on("message", (message) => {
16-
setMessages((prev) => [...prev, message]);
15+
socketRef.current.on("message", (msg) => {
16+
setMessages((prev) => [...prev, msg]);
17+
});
18+
19+
socketRef.current.on("react", ({ index, type, value }) => {
20+
setMessages((prev) =>
21+
prev.map((msg, i) => (i === index ? { ...msg, [type]: value } : msg)),
22+
);
1723
});
1824

1925
return () => socketRef.current.disconnect();
2026
}, [url]);
2127

2228
const sendMessage = (text) => {
23-
const message = {
24-
text,
25-
time: new Date().toISOString(),
26-
};
27-
socketRef.current.emit("message", message);
28-
setMessages((prev) => [...prev, { ...message }]);
29+
const msg = { text, time: new Date().toISOString(), likes: 0, dislikes: 0 };
30+
socketRef.current.emit("message", msg);
31+
setMessages((prev) => [...prev, { ...msg, self: true }]);
32+
};
33+
34+
const reactToMessage = (index, type) => {
35+
setMessages((prev) =>
36+
prev.map((msg, i) =>
37+
i === index ? { ...msg, [type]: msg[type] + 1 } : msg,
38+
),
39+
);
40+
socketRef.current.emit("react", { index, type });
2941
};
3042

31-
return { messages, sendMessage };
43+
return { messages, sendMessage, reactToMessage };
3244
};
3345

3446
export default useSocket;

chat-app/client/src/pages/Poll.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ import usePoll from "../hooks/usePoll";
22
import Chat from "../components/Chat/Chat";
33

44
const Poll = () => {
5-
const { messages, sendMessage } = usePoll("http://localhost:3000");
6-
return <Chat messages={messages} sendMessage={sendMessage} />;
5+
const { messages, sendMessage, reactToMessage } = usePoll(
6+
"http://localhost:3000",
7+
);
8+
return (
9+
<Chat
10+
messages={messages}
11+
sendMessage={sendMessage}
12+
reactToMessage={reactToMessage}
13+
/>
14+
);
715
};
816

917
export default Poll;

chat-app/client/src/pages/Socket.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ import useSocket from "../hooks/useSocket";
22
import Chat from "../components/Chat/Chat";
33

44
const Socket = () => {
5-
const { messages, sendMessage } = useSocket("http://localhost:3000");
6-
return <Chat messages={messages} sendMessage={sendMessage} />;
5+
const { messages, sendMessage, reactToMessage } = useSocket(
6+
"http://localhost:3000",
7+
);
8+
return (
9+
<Chat
10+
messages={messages}
11+
sendMessage={sendMessage}
12+
reactToMessage={reactToMessage}
13+
/>
14+
);
715
};
816

917
export default Socket;

chat-app/server/index.js

Lines changed: 72 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,107 @@
11
import express from 'express';
2+
import cors from 'cors';
23
import { createServer } from 'http';
34
import { Server } from 'socket.io';
4-
import cors from 'cors';
55
import chat from './data/chat.json' with { type: 'json' };
66

77
const app = express();
8-
const server = createServer(app);
9-
const io = new Server(server, {
10-
cors: {
11-
origin: '*',
12-
},
13-
});
14-
15-
const socketChat = [...chat];
16-
const pollChat = [...chat];
17-
18-
let lastMessageIndex = 0;
19-
const pollClients = [];
8+
const httpServer = createServer(app);
9+
const io = new Server(httpServer, { cors: { origin: '*' } });
2010

2111
app.use(cors());
12+
app.use(express.json());
2213

23-
app.get('/healthcheck', (req, res) => res.send('Server is running'));
14+
const pollStore = { messages: JSON.parse(JSON.stringify(chat)), clients: [] };
15+
const socketStore = { messages: JSON.parse(JSON.stringify(chat)) };
2416

25-
app.get('/history', (req, res) => {
26-
res.json(pollChat);
17+
app.get('/poll/history', (req, res) => {
18+
res.json(pollStore.messages);
2719
});
2820

2921
app.get('/poll', (req, res) => {
30-
const clientLastIndex = parseInt(req.query.lastIndex) || 0;
22+
const since = parseInt(req.query.since) || 0;
23+
const newMessages = pollStore.messages.filter((_, i) => i >= since);
3124

32-
if (pollChat.length > clientLastIndex) {
25+
if (newMessages.length > 0) {
3326
return res.json({
34-
messages: pollChat.slice(clientLastIndex),
35-
lastIndex: pollChat.length,
27+
messages: newMessages,
28+
cursor: pollStore.messages.length,
3629
});
3730
}
3831

39-
const timeoutId = setTimeout(() => {
40-
const index = pollClients.indexOf(timeoutId);
41-
if (index > -1) {
42-
pollClients.splice(index, 1);
43-
}
44-
res.json({
45-
messages: [],
46-
lastIndex: pollChat.length,
47-
});
48-
}, 30000);
32+
pollStore.clients.push({ res });
4933

50-
pollClients.push(timeoutId);
34+
req.on('close', () => {
35+
const i = pollStore.clients.findIndex((c) => c.res === res);
36+
if (i !== -1) pollStore.clients.splice(i, 1);
37+
});
5138

52-
res.on('close', () => {
53-
const index = pollClients.indexOf(timeoutId);
54-
if (index > -1) {
55-
pollClients.splice(index, 1);
56-
clearTimeout(timeoutId);
39+
setTimeout(() => {
40+
const i = pollStore.clients.findIndex((c) => c.res === res);
41+
if (i !== -1) {
42+
pollStore.clients.splice(i, 1);
43+
res.json({ messages: [], cursor: since });
5744
}
58-
});
45+
}, 30000);
5946
});
6047

61-
app.post('/message', express.json(), (req, res) => {
48+
app.post('/poll/message', (req, res) => {
6249
const msg = req.body;
63-
pollChat.push(msg);
50+
pollStore.messages.push(msg);
51+
pollStore.clients.forEach(({ res }) =>
52+
res.json({ messages: [msg], cursor: pollStore.messages.length }),
53+
);
54+
pollStore.clients.length = 0;
55+
res.sendStatus(200);
56+
});
6457

65-
pollClients.forEach((timeoutId) => clearTimeout(timeoutId));
66-
pollClients.length = 0;
67-
io.emit('message', msg);
58+
app.patch('/poll/message/:index/likes', (req, res) => {
59+
console.log('http poll');
60+
const msg = pollStore.messages[req.params.index];
61+
if (!msg) return res.sendStatus(404);
62+
msg.likes++;
63+
pollStore.clients.forEach(({ res }) =>
64+
res.json({
65+
messages: [{ ...msg, index: Number(req.params.index) }],
66+
cursor: pollStore.messages.length,
67+
}),
68+
);
69+
pollStore.clients.length = 0;
70+
res.sendStatus(200);
71+
});
6872

69-
res.json({ success: true });
73+
app.patch('/poll/message/:index/dislikes', (req, res) => {
74+
const msg = pollStore.messages[req.params.index];
75+
if (!msg) return res.sendStatus(404);
76+
msg.dislikes++;
77+
pollStore.clients.forEach(({ res }) =>
78+
res.json({
79+
messages: [{ ...msg, index: Number(req.params.index) }],
80+
cursor: pollStore.messages.length,
81+
}),
82+
);
83+
pollStore.clients.length = 0;
84+
res.sendStatus(200);
7085
});
7186

7287
io.on('connection', (socket) => {
73-
socket.emit('history', socketChat);
88+
socket.emit(
89+
'history',
90+
socketStore.messages.map((msg, index) => ({ ...msg, index })),
91+
);
7492

7593
socket.on('message', (msg) => {
76-
socketChat.push(msg);
77-
socket.broadcast.emit('message', msg);
94+
socketStore.messages.push(msg);
95+
socket.broadcast.emit('message', { ...msg, self: false });
96+
});
7897

79-
pollClients.forEach((timeoutId) => clearTimeout(timeoutId));
80-
pollClients.length = 0;
98+
socket.on('react', ({ index, type }) => {
99+
console.log('ws');
100+
const msg = socketStore.messages[index];
101+
if (!msg) return;
102+
msg[type]++;
103+
socket.broadcast.emit('react', { index, type, value: msg[type] });
81104
});
82105
});
83106

84-
server.listen(3000, () => {
85-
console.log('server running at http://localhost:3000');
86-
});
107+
httpServer.listen(3000, () => console.log('Server running on port: 3000'));

0 commit comments

Comments
 (0)