Skip to content

Commit ee1dbab

Browse files
authored
Merge pull request #10 from oslabs-beta/liz-test-redux
Add Live Chatroom Function
2 parents f094bff + 252f6db commit ee1dbab

File tree

5 files changed

+200
-7
lines changed

5 files changed

+200
-7
lines changed

app/src/components/bottom/BottomTabs.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import CustomizationPanel from '../../containers/CustomizationPanel';
88
import CreationPanel from './CreationPanel';
99
import ContextManager from '../ContextAPIManager/ContextManager';
1010
import StateManager from '../StateManagement/StateManagement';
11+
import Chatroom from './ChatRoom';
1112
import Box from '@mui/material/Box';
1213
import Tree from '../../tree/TreeChart';
1314
import FormControl from '@mui/material/FormControl';
@@ -22,6 +23,7 @@ const BottomTabs = (props): JSX.Element => {
2223
const dispatch = useDispatch();
2324
const state = useSelector((store: RootState) => store.appState);
2425
const contextParam = useSelector((store: RootState) => store.contextSlice);
26+
const collaborationRoom = useSelector((store: RootState) => store.roomSlice);
2527

2628
const [tab, setTab] = useState(0);
2729
const classes = useStyles();
@@ -95,6 +97,11 @@ const BottomTabs = (props): JSX.Element => {
9597
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
9698
label="State Manager"
9799
/>
100+
<Tab
101+
disableRipple
102+
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
103+
label="Live Chat"
104+
/>
98105
</Tabs>
99106
<div className={classes.projectTypeWrapper}>
100107
<FormControl size="small">
@@ -133,6 +140,23 @@ const BottomTabs = (props): JSX.Element => {
133140
isThemeLight={props.isThemeLight}
134141
/>
135142
)}
143+
{tab === 6 &&
144+
(collaborationRoom.userJoined ? (
145+
<Chatroom />
146+
) : (
147+
<div
148+
style={{
149+
display: 'flex',
150+
justifyContent: 'center',
151+
alignItems: 'center',
152+
height: '100%'
153+
}}
154+
>
155+
<p style={{ color: 'white', fontSize: '18px' }}>
156+
Please join a collaboration room to enable this function
157+
</p>
158+
</div>
159+
))}
136160
</div>
137161
</div>
138162
);
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React from 'react';
2+
import { useState, useRef, useEffect } from 'react';
3+
import { useSelector } from 'react-redux';
4+
import { RootState } from '../../redux/store';
5+
import { emitEvent } from '../../helperFunctions/socket';
6+
7+
const Chatroom = (props): JSX.Element => {
8+
const userName = useSelector((store: RootState) => store.roomSlice.userName);
9+
const roomCode = useSelector((store: RootState) => store.roomSlice.roomCode);
10+
const messages = useSelector((store: RootState) => store.roomSlice.messages);
11+
12+
const [inputContent, setInputContent] = useState('');
13+
14+
const wrapperStyles = {
15+
border: `2px solid #f2fbf8`,
16+
borderRadius: '8px',
17+
width: '70%',
18+
height: '100%',
19+
display: 'column',
20+
padding: '20px',
21+
backgroundColor: '#42464C',
22+
overflow: 'auto'
23+
};
24+
25+
const inputContainerStyles = {
26+
width: '100%',
27+
paddingLeft: '30px',
28+
paddingTop: '10px',
29+
display: 'flex',
30+
justifyContent: 'center'
31+
};
32+
33+
const inputStyles = {
34+
width: '70%',
35+
padding: '10px',
36+
border: 'none',
37+
borderRadius: '5px',
38+
backgroundColor: '#333333',
39+
color: 'white'
40+
};
41+
42+
const buttonStyles = {
43+
padding: '10px',
44+
marginLeft: '10px',
45+
backgroundColor: '#0070BA',
46+
color: 'white',
47+
border: 'none',
48+
borderRadius: '5px',
49+
cursor: 'pointer'
50+
};
51+
52+
const handleSubmit = (e) => {
53+
e.preventDefault();
54+
if (inputContent !== '') {
55+
emitEvent('send-chat-message', roomCode, {
56+
userName,
57+
message: inputContent
58+
});
59+
setInputContent('');
60+
}
61+
};
62+
63+
const handleMessageContainerStyle = (message: object) => {
64+
if (message['type'] === 'activity') {
65+
return { color: 'yellow' };
66+
} else {
67+
if (message['userName'] === userName) return { color: '#66C4EB' };
68+
return { color: 'white' };
69+
}
70+
};
71+
72+
const renderMessages = () => {
73+
return messages.map((message, index) => {
74+
if (message.type === 'activity') {
75+
return (
76+
<div key={index} style={handleMessageContainerStyle(message)}>
77+
{message.message}
78+
</div>
79+
);
80+
} else if (message.type === 'chat') {
81+
return (
82+
<div key={index} style={handleMessageContainerStyle(message)}>
83+
{message.userName === userName ? 'You' : message.userName}:{' '}
84+
{message.message}
85+
</div>
86+
);
87+
}
88+
return null;
89+
});
90+
};
91+
92+
const containerRef = useRef<HTMLDivElement>(null);
93+
94+
// Scroll to the bottom of the container whenever new messages are added
95+
useEffect(() => {
96+
if (containerRef.current) {
97+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
98+
}
99+
}, [messages]);
100+
101+
return (
102+
<div
103+
className="livechat-panel"
104+
style={{ paddingLeft: '10px', width: '100%', height: '100%' }}
105+
>
106+
<div style={{ justifyContent: 'center', display: 'flex', height: '80%' }}>
107+
<div id="message-container" style={wrapperStyles} ref={containerRef}>
108+
{renderMessages()}
109+
</div>
110+
</div>
111+
<div className="chatroom-input">
112+
<form
113+
id="send-container"
114+
style={inputContainerStyles}
115+
onSubmit={handleSubmit}
116+
>
117+
<input
118+
type="text"
119+
id="message-input"
120+
onChange={(e) => setInputContent(e.target.value)}
121+
value={inputContent}
122+
style={inputStyles}
123+
/>
124+
<button type="submit" id="send-button" style={buttonStyles}>
125+
Send
126+
</button>
127+
</form>
128+
</div>
129+
</div>
130+
);
131+
};
132+
133+
export default Chatroom;

app/src/components/left/RoomsContainer.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
setUserName,
4141
setUserJoined,
4242
setUserList,
43+
setMessages,
4344
setPassword
4445
} from '../../redux/reducers/slice/roomSlice';
4546
import { codePreviewCooperative } from '../../redux/reducers/slice/codePreviewSlice';
@@ -66,6 +67,7 @@ const RoomsContainer = () => {
6667
const userJoined = useSelector(
6768
(store: RootState) => store.roomSlice.userJoined
6869
);
70+
const messages = useSelector((store: RootState) => store.roomSlice.messages);
6971

7072
const initSocketConnection = (roomCode: string) => {
7173
// helper function to create socket connection
@@ -97,9 +99,18 @@ const RoomsContainer = () => {
9799
});
98100

99101
// update user list when there's a change: new join or leave the room
100-
socket.on('updateUserList', (newUserList) => {
102+
socket.on('updateUserList', (messageData) => {
101103
//console.log('user list received from server');
102-
dispatch(setUserList(newUserList));
104+
dispatch(setUserList(messageData.userList));
105+
});
106+
107+
socket.on('new chat message', (messageData) => {
108+
if (
109+
messages.length === 0 ||
110+
JSON.stringify(messageData) !== JSON.stringify(messages[-1])
111+
) {
112+
dispatch(setMessages(messageData));
113+
}
103114
});
104115

105116
// dispatch add child to local state when element has been added by another user

app/src/redux/reducers/slice/roomSlice.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const initialState = {
55
userName: '',
66
userList: [],
77
userJoined: false,
8+
messages: [],
89
password: ''
910
};
1011

@@ -24,6 +25,9 @@ const roomSlice = createSlice({
2425
setUserJoined: (state, action) => {
2526
state.userJoined = action.payload;
2627
},
28+
setMessages: (state, action) => {
29+
state.messages = [...state.messages, action.payload];
30+
},
2731
setPassword: (state, action) => {
2832
state.password = action.payload;
2933
}
@@ -35,6 +39,7 @@ export const {
3539
setUserName,
3640
setUserList,
3741
setUserJoined,
42+
setMessages,
3843
setPassword
3944
} = roomSlice.actions;
4045

server/server.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,15 @@ io.on('connection', (client) => {
132132
//send the message to all clients in room but the sender
133133
io.to(roomCode).emit(
134134
'updateUserList',
135-
Object.values(roomLists[roomCode]) // send updated userList to all users in room
135+
{
136+
userList: Object.values(roomLists[roomCode])
137+
} // send updated userList to all users in room
136138
);
139+
io.to(roomCode).emit('new chat message', {
140+
userName,
141+
message: `${userName} joined chat room`,
142+
type: 'activity'
143+
});
137144
}
138145
} catch (error) {
139146
//if joining event is having an error and time out
@@ -153,20 +160,33 @@ io.on('connection', (client) => {
153160
//disconnecting functionality
154161
client.on('disconnecting', () => {
155162
const roomCode = Array.from(client.rooms)[1]; //grabbing current room client was in when disconnecting
163+
const userName = roomLists[roomCode][client.id];
156164
delete roomLists[roomCode][client.id];
157165
//if room empty, delete room from room list
158166
if (!Object.keys(roomLists[roomCode]).length) {
159167
delete roomLists[roomCode];
160168
} else {
161169
//else emit updated user list
162-
io.to(roomCode).emit(
163-
'updateUserList',
164-
Object.values(roomLists[roomCode])
165-
);
170+
io.to(roomCode).emit('updateUserList', {
171+
userList: Object.values(roomLists[roomCode])
172+
});
173+
io.to(roomCode).emit('new chat message', {
174+
userName,
175+
message: `${userName} left chat room`,
176+
type: 'activity'
177+
});
166178
}
167179
});
168180

169181
//-------Socket events for state synchronization in collab room------------------
182+
client.on('send-chat-message', (roomCode: string, messageData: object) => {
183+
if (roomCode) {
184+
io.to(roomCode).emit('new chat message', {
185+
...messageData,
186+
type: 'chat'
187+
});
188+
}
189+
});
170190
client.on('addChildAction', (roomCode: string, childData: object) => {
171191
// console.log('child data received on server:', childData);
172192
if (roomCode) {

0 commit comments

Comments
 (0)