Skip to content

Commit 12bce5d

Browse files
committed
Video-62-Implement-Live-Chat
1 parent dc6a1c4 commit 12bce5d

File tree

8 files changed

+449
-4
lines changed

8 files changed

+449
-4
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,7 @@ $ npm start
409409
61. Create Dashboard Screen
410410
1. Create chart data in backend
411411
2. Build Chart screen
412+
62. Implement Live Chat With Customers
413+
1. use socket io to create backend
414+
2. create chat box component
415+
3. create support screen

backend/server.js

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import http from 'http';
2+
import { Server } from 'socket.io';
13
import express from 'express';
24
import mongoose from 'mongoose';
35
import dotenv from 'dotenv';
@@ -43,6 +45,83 @@ app.use((err, req, res, next) => {
4345
});
4446

4547
const port = process.env.PORT || 5000;
46-
app.listen(port, () => {
48+
49+
const httpServer = http.Server(app);
50+
const io = new Server(httpServer, { cors: { origin: '*' } });
51+
const users = [];
52+
53+
io.on('connection', (socket) => {
54+
console.log('connection', socket.id);
55+
socket.on('disconnect', () => {
56+
const user = users.find((x) => x.socketId === socket.id);
57+
if (user) {
58+
user.online = false;
59+
console.log('Offline', user.name);
60+
const admin = users.find((x) => x.isAdmin && x.online);
61+
if (admin) {
62+
io.to(admin.socketId).emit('updateUser', user);
63+
}
64+
}
65+
});
66+
socket.on('onLogin', (user) => {
67+
const updatedUser = {
68+
...user,
69+
online: true,
70+
socketId: socket.id,
71+
messages: [],
72+
};
73+
const existUser = users.find((x) => x._id === updatedUser._id);
74+
if (existUser) {
75+
existUser.socketId = socket.id;
76+
existUser.online = true;
77+
} else {
78+
users.push(updatedUser);
79+
}
80+
console.log('Online', user.name);
81+
const admin = users.find((x) => x.isAdmin && x.online);
82+
if (admin) {
83+
io.to(admin.socketId).emit('updateUser', updatedUser);
84+
}
85+
if (updatedUser.isAdmin) {
86+
io.to(updatedUser.socketId).emit('listUsers', users);
87+
}
88+
});
89+
90+
socket.on('onUserSelected', (user) => {
91+
const admin = users.find((x) => x.isAdmin && x.online);
92+
if (admin) {
93+
const existUser = users.find((x) => x._id === user._id);
94+
io.to(admin.socketId).emit('selectUser', existUser);
95+
}
96+
});
97+
98+
socket.on('onMessage', (message) => {
99+
if (message.isAdmin) {
100+
const user = users.find((x) => x._id === message._id && x.online);
101+
if (user) {
102+
io.to(user.socketId).emit('message', message);
103+
user.messages.push(message);
104+
}
105+
} else {
106+
const admin = users.find((x) => x.isAdmin && x.online);
107+
if (admin) {
108+
io.to(admin.socketId).emit('message', message);
109+
const user = users.find((x) => x._id === message._id && x.online);
110+
user.messages.push(message);
111+
} else {
112+
io.to(socket.id).emit('message', {
113+
name: 'Admin',
114+
body: 'Sorry. I am not online right now',
115+
});
116+
}
117+
}
118+
});
119+
});
120+
121+
httpServer.listen(port, () => {
47122
console.log(`Serve at http://localhost:${port}`);
48123
});
124+
125+
// app.listen(port, () => {
126+
// console.log(`Serve at http://localhost:${port}`);
127+
// });

frontend/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"react-router-dom": "^5.2.0",
1919
"react-scripts": "3.4.3",
2020
"redux": "^4.0.5",
21-
"redux-thunk": "^2.3.0"
21+
"redux-thunk": "^2.3.0",
22+
"socket.io-client": "^4.0.1"
2223
},
2324
"scripts": {
2425
"start": "react-scripts start",

frontend/src/App.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import LoadingBox from './components/LoadingBox';
2929
import MessageBox from './components/MessageBox';
3030
import MapScreen from './screens/MapScreen';
3131
import DashboardScreen from './screens/DashboardScreen';
32+
import SupportScreen from './screens/SupportScreen';
33+
import ChatBox from './components/ChatBox';
3234

3335
function App() {
3436
const cart = useSelector((state) => state.cart);
@@ -135,6 +137,9 @@ function App() {
135137
<li>
136138
<Link to="/userlist">Users</Link>
137139
</li>
140+
<li>
141+
<Link to="/support">Support</Link>
142+
</li>
138143
</ul>
139144
</div>
140145
)}
@@ -236,6 +241,7 @@ function App() {
236241
path="/dashboard"
237242
component={DashboardScreen}
238243
></AdminRoute>
244+
<AdminRoute path="/support" component={SupportScreen}></AdminRoute>
239245

240246
<SellerRoute
241247
path="/productlist/seller"
@@ -248,7 +254,10 @@ function App() {
248254

249255
<Route path="/" component={HomeScreen} exact></Route>
250256
</main>
251-
<footer className="row center">All right reserved</footer>
257+
<footer className="row center">
258+
{userInfo && !userInfo.isAdmin && <ChatBox userInfo={userInfo} />}
259+
<div>All right reserved</div>{' '}
260+
</footer>
252261
</div>
253262
</BrowserRouter>
254263
);

frontend/src/components/ChatBox.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import socketIOClient from 'socket.io-client';
3+
4+
const ENDPOINT =
5+
window.location.host.indexOf('localhost') >= 0
6+
? 'http://127.0.0.1:5000'
7+
: window.location.host;
8+
9+
export default function ChatBox(props) {
10+
const { userInfo } = props;
11+
const [socket, setSocket] = useState(null);
12+
const uiMessagesRef = useRef(null);
13+
const [isOpen, setIsOpen] = useState(false);
14+
const [messageBody, setMessageBody] = useState('');
15+
const [messages, setMessages] = useState([
16+
{ name: 'Admin', body: 'Hello there, Please ask your question.' },
17+
]);
18+
19+
useEffect(() => {
20+
if (uiMessagesRef.current) {
21+
uiMessagesRef.current.scrollBy({
22+
top: uiMessagesRef.current.clientHeight,
23+
left: 0,
24+
behavior: 'smooth',
25+
});
26+
}
27+
if (socket) {
28+
socket.emit('onLogin', {
29+
_id: userInfo._id,
30+
name: userInfo.name,
31+
isAdmin: userInfo.isAdmin,
32+
});
33+
socket.on('message', (data) => {
34+
setMessages([...messages, { body: data.body, name: data.name }]);
35+
});
36+
}
37+
}, [messages, isOpen, socket]);
38+
39+
const supportHandler = () => {
40+
setIsOpen(true);
41+
console.log(ENDPOINT);
42+
const sk = socketIOClient(ENDPOINT);
43+
setSocket(sk);
44+
};
45+
const submitHandler = (e) => {
46+
e.preventDefault();
47+
if (!messageBody.trim()) {
48+
alert('Error. Please type message.');
49+
} else {
50+
setMessages([...messages, { body: messageBody, name: userInfo.name }]);
51+
setMessageBody('');
52+
setTimeout(() => {
53+
socket.emit('onMessage', {
54+
body: messageBody,
55+
name: userInfo.name,
56+
isAdmin: userInfo.isAdmin,
57+
_id: userInfo._id,
58+
});
59+
}, 1000);
60+
}
61+
};
62+
const closeHandler = () => {
63+
setIsOpen(false);
64+
};
65+
return (
66+
<div className="chatbox">
67+
{!isOpen ? (
68+
<button type="button" onClick={supportHandler}>
69+
<i className="fa fa-support" />
70+
</button>
71+
) : (
72+
<div className="card card-body">
73+
<div className="row">
74+
<strong>Support </strong>
75+
<button type="button" onClick={closeHandler}>
76+
<i className="fa fa-close" />
77+
</button>
78+
</div>
79+
<ul ref={uiMessagesRef}>
80+
{messages.map((msg, index) => (
81+
<li key={index}>
82+
<strong>{`${msg.name}: `}</strong> {msg.body}
83+
</li>
84+
))}
85+
</ul>
86+
<div>
87+
<form onSubmit={submitHandler} className="row">
88+
<input
89+
value={messageBody}
90+
onChange={(e) => setMessageBody(e.target.value)}
91+
type="text"
92+
placeholder="type message"
93+
/>
94+
<button type="submit">Send</button>
95+
</form>
96+
</div>
97+
</div>
98+
)}
99+
</div>
100+
);
101+
}

frontend/src/index.css

+75
Original file line numberDiff line numberDiff line change
@@ -392,3 +392,78 @@ img.large {
392392
.summary-title.color3 {
393393
background-color: #e0e0f0;
394394
}
395+
396+
/* Chatbox */
397+
.chatbox {
398+
color: #000000;
399+
position: fixed;
400+
right: 1rem;
401+
bottom: 1rem;
402+
}
403+
.chatbox ul {
404+
overflow: scroll;
405+
max-height: 20rem;
406+
}
407+
.chatbox li {
408+
margin-bottom: 1rem;
409+
}
410+
.chatbox input {
411+
width: calc(100% - 9rem);
412+
}
413+
414+
.support-users {
415+
background: #f0f0f0;
416+
height: 100%;
417+
}
418+
.support-users li {
419+
background-color: #f8f8f8;
420+
}
421+
.support-users button {
422+
background-color: transparent;
423+
border: none;
424+
text-align: left;
425+
}
426+
.support-users li {
427+
margin: 0;
428+
background-color: #f0f0f0;
429+
border-bottom: 0.1rem #c0c0c0 solid;
430+
}
431+
432+
.support-users li:hover {
433+
background-color: #f0f0f0;
434+
}
435+
.support-users li.selected {
436+
background-color: #c0c0c0;
437+
}
438+
.support-messages {
439+
padding: 1rem;
440+
}
441+
.support-messages input {
442+
width: calc(100% - 9rem);
443+
}
444+
.support-messages ul {
445+
height: calc(100vh - 18rem);
446+
max-height: calc(100vh - 18rem);
447+
overflow: scroll;
448+
}
449+
.support-messages li {
450+
margin-bottom: 1rem;
451+
}
452+
453+
.support-users span {
454+
width: 2rem;
455+
height: 2rem;
456+
border-radius: 50%;
457+
position: absolute;
458+
margin-left: -25px;
459+
margin-top: 10px;
460+
}
461+
.support-users .offline {
462+
background-color: #808080;
463+
}
464+
.support-users .online {
465+
background-color: #20a020;
466+
}
467+
.support-users .unread {
468+
background-color: #f02020;
469+
}

0 commit comments

Comments
 (0)