Skip to content

Commit

Permalink
feat: added message sent status (#146)
Browse files Browse the repository at this point in the history
* chore: updated eslintrc.json to disable globals error

* refactor: moved sendMessage logic to a separate file

* feat: implemented message feedback from server

* feat: added message sent status
  • Loading branch information
mathiasayivor authored Oct 17, 2022
1 parent f869900 commit b9cdf8d
Show file tree
Hide file tree
Showing 12 changed files with 550 additions and 294 deletions.
3 changes: 2 additions & 1 deletion client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"env": {
"browser": true,
"jest": true,
"node": true
"node": true,
"es6": true
},
"rules": {
"array-bracket-newline": "error",
Expand Down
54 changes: 25 additions & 29 deletions client/src/components/Anonymous.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,42 @@
import React from 'react';
import { BiDotsVerticalRounded } from 'react-icons/bi';
import Chat from 'components/Chat';
import { ChatProvider } from 'context/ChatContext';
import Dropdown from 'rsuite/Dropdown';
import Dropdown from 'rsuite/Dropdown';
import { useNavigate } from 'react-router-dom';


const centerItems = `flex items-center justify-center`;



const Anonymous = () => {

const navigate = useNavigate();

return (
<ChatProvider>
<div
className={`bg-[#011627] min-w-[calc(100%-120px)] ${centerItems} flex-col max-h-[100vh] text-[#FFF]`}
>
<div className="flex justify-between border-b-[2px] border-secondary pt-[50px] pr-[60px] pl-[60px] pb-[15px] h-[13%] w-[100%]">
<p className="text-[1em] font-semibold">Anonymous User</p>

<Dropdown placement="leftStart" style={{zIndex: 3 }} icon={ <BiDotsVerticalRounded className="fill-[#f5f5f5] scale-[1.8]"></BiDotsVerticalRounded>} noCaret>
<Dropdown.Item onClick={
()=> {
navigate("/");
}} >
Close Chat
</Dropdown.Item>
</Dropdown>

<div
className={`bg-[#011627] min-w-[calc(100%-120px)] ${centerItems} flex-col max-h-[100vh] text-[#FFF]`}
>
<div className="flex justify-between border-b-[2px] border-secondary pt-[50px] pr-[60px] pl-[60px] pb-[15px] h-[13%] w-[100%]">
<p className="text-[1em] font-semibold">Anonymous User</p>


</div>
<div
className={`flex-col w-[90%] h-[87%] ${centerItems} mt-auto`}
<Dropdown
placement="leftStart"
style={{ zIndex: 3 }}
icon={
<BiDotsVerticalRounded className="fill-[#f5f5f5] scale-[1.8]"></BiDotsVerticalRounded>
}
noCaret
>
<Chat />
</div>
<Dropdown.Item
onClick={() => {
navigate('/');
}}
>
Close Chat
</Dropdown.Item>
</Dropdown>
</div>
<div className={`flex-col w-[90%] h-[87%] ${centerItems} mt-auto`}>
<Chat />
</div>
</ChatProvider>
</div>
);
};

Expand Down
97 changes: 97 additions & 0 deletions client/src/components/BuddyMatcher.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useState, useContext, useEffect } from 'react';
import { ThreeDots } from 'react-loading-icons';
import { SocketContext } from 'context/Context';

import Anonymous from 'components/Anonymous';
import { useAuth } from 'src/context/AuthContext';
import { useChat } from 'src/context/ChatContext';

const BuddyMatcher = () => {
const { auth } = useAuth();
const { createChat } = useChat();

// eslint-disable-next-line no-unused-vars
const [isFound, setIsFound] = useState(false);
const socket = useContext(SocketContext);

const userID = auth.loginId;
const defaultLoadingText = 'Looking for a random buddy';
const [loadingText, setLoadingText] = useState(defaultLoadingText);
let timeout = null;

useEffect(() => {
if (loadingText === defaultLoadingText) {
timeout = setTimeout(() => {
setLoadingText(
'Taking too long? No chat buddy is currently available :(\nTry refreshing!'
);
}, 15000);
}

return () => {
clearTimeout(timeout);
};
}, [loadingText]);

useEffect(() => {
if (isFound) {
return;
}

if (!socket.connected) {
socket.connect();
}

// This is necessary else chat won't be restored after re-connections
socket.on('connect', () => {
// Here server will be informed that user is searching for
// another user
socket.emit('join', { loginId: auth.loginId, email: auth.email });
});
socket.connected && socket.emit('adding', { userID });
socket.emit('createRoom', `${userID}-in-search`);
// From here will get the info from server that user has joined the room

socket.on('joined', ({ roomId, userIds }) => {
localStorage.setItem('currentChatId', roomId);

createChat(roomId, userIds);
setIsFound(true);
});

socket.on('chat_restore', ({ chats, currentChatId }) => {
localStorage.setItem('currentChatId', currentChatId);
Object.values(chats).forEach((chat) => {
createChat(
chat.id,
chat.userIds,
chat.messages,
chat.createdAt
);
});

setIsFound(true);
console.log('chat restored!', chats, currentChatId);
});

return () => {
socket.off('connect').off('joined').off('chat_restore');
socket.disconnect();
};
}, []);

return isFound ? (
<Anonymous />
) : (
<div className="flex w-full justify-center items-center h-screen flex-col bg-primary">
<ThreeDots fill="rgb(255 159 28)" />
<div className="text-lg text-center">
{loadingText.split('\n').map((text) => (
<p key={text}>{text}</p>
))}
</div>
</div>
);
};

export default BuddyMatcher;
144 changes: 92 additions & 52 deletions client/src/components/Chat.jsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,59 @@
import { useState, useEffect, useRef, useContext, useMemo } from 'react';
import { useEffect, useRef, useContext, useMemo, useState } from 'react';
import { SocketContext } from 'context/Context';

import 'styles/chat.css';

import ScrollToBottom from 'react-scroll-to-bottom';
import { IoSend } from 'react-icons/io5';
import { v4 as uuid } from 'uuid';

import { useChat } from 'src/context/ChatContext';
import { useAuth } from 'src/context/AuthContext';
import useChatUtils from 'src/lib/chat';
import MessageStatus from './MessageStatus';

let senderId;
const Chat = () => {
const { messages: state, addMessage } = useChat();
const { auth } = useAuth();

const [currentChatId, setCurrentChatId] = useState(null);
const { messages: state, addMessage, updateMessage } = useChat();
const { auth, logout } = useAuth();
const socket = useContext(SocketContext);
const [sentMessages, setSentMessages] = useState([]);
const [receivedMessages, setReceivedMessages] = useState([]);

const { sendMessage } = useChatUtils(socket);
const inputRef = useRef('');
senderId = auth.loginId;
useEffect(() => {
const newMessageHandler = ({ id, senderId, message, time }) => {
addMessage({
id: senderId,
message,
time,
room: 'anon',
messageId: id,
});
const newMessageHandler = (message) => {
try {
addMessage(message);
} catch {
logout();
}
};

// This is used to recive message form other user.
socket.on('receive_message', newMessageHandler);
setCurrentChatId(localStorage.getItem('currentChatId'));

return () => {
socket.off('receive_message', newMessageHandler);
};
}, []);

useEffect(() => {
const userIDs = Object.keys(state).map((item) => item);
const available = userIDs.length === 0;
const sendID = userIDs.find((item) => item === senderId);
const receiverID = userIDs.find((item) => item !== senderId);
const handleMessages = () => {
sendID && setSentMessages([...state[sendID].messages]);
receiverID && setReceivedMessages([...state[receiverID].messages]);
};
!available && handleMessages();
}, [state]);

const sortedMessages = useMemo(
() =>
[...sentMessages, ...receivedMessages].sort((a, b) => {
const da = new Date(a.time),
db = new Date(b.time);
return da - db;
}),
[sentMessages, receivedMessages]
Object.values(state[currentChatId]?.messages ?? {})?.sort(
(a, b) => {
const da = new Date(a.time),
db = new Date(b.time);
return da - db;
}
),
[state, currentChatId]
);

// Here whenever user will submit message it will be send to the server
const handleSubmit = (e) => {
const handleSubmit = async (e) => {
e.preventDefault();

const d = new Date();
Expand All @@ -70,30 +62,63 @@ const Chat = () => {
if (message === '' || senderId === undefined || senderId === '123456') {
return;
}
if (socket.connected) {
socket.emit('send_message', { senderId, message, time });
} else {
console.log('Something went wrong on the server 4853789');

if (inputRef.current) {
inputRef.current.value = '';
inputRef.current.focus();
}

const tmpId = uuid();

try {
addMessage({
id: senderId,
senderId,
room: currentChatId,
id: tmpId,
message,
time,
room: 'anon',
status: 'pending',
});
} catch {
logout();
}

console.log(`sender: ${message}`);
// Socket.emit('privatemessage', message);
// addMessage({
// id: senderId,
// message,
// time,
// room: 'anon',
// });
try {
console.log('sending...');
const sentMessage = await sendMessage({
senderId,
message,
time,
});

if (inputRef.current) {
inputRef.current.value = '';
inputRef.current.focus();
try {
updateMessage(tmpId, sentMessage);
} catch {
logout();
}
} catch (e) {
try {
updateMessage(tmpId, {
senderId,
room: currentChatId,
id: tmpId,
message,
time,
status: 'failed',
});
} catch {
logout();
}
} finally {
console.log('send complete');
console.log(`sender: ${message}`);
// Socket.emit('privatemessage', message);
// addMessage({
// id: senderId,
// message,
// time,
// room: 'anon',
// });
}
};

Expand All @@ -110,7 +135,7 @@ const Chat = () => {
className="displayMessgaes h-[75%] w-[100%]"
>
{sortedMessages.map(
({ message, time, senderId: sender, id }) => (
({ senderId: sender, id, message, time, status }) => (
<div
key={id}
className={`message-block ${
Expand All @@ -121,7 +146,22 @@ const Chat = () => {
>
<div className="message">
<p className="content">{message}</p>
<p className="time">{getTime(time)}</p>
<div
className={`status ${
status === 'failed'
? 'text-red-600'
: 'text-white'
}`}
>
<MessageStatus
time={getTime(time)}
status={status ?? 'sent'}
iAmTheSender={
sender.toString() ===
senderId.toString()
}
/>
</div>
</div>
</div>
)
Expand Down
Loading

1 comment on commit b9cdf8d

@vercel
Copy link

@vercel vercel bot commented on b9cdf8d Oct 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.