diff --git a/frontend/src/components/ScrollableChat.js b/frontend/src/components/ScrollableChat.js new file mode 100644 index 0000000..9a21ea4 --- /dev/null +++ b/frontend/src/components/ScrollableChat.js @@ -0,0 +1,53 @@ +import { Avatar, Tooltip } from "@chakra-ui/react"; +import React from "react"; +import { + isLastMessage, + isSameSender, + isSameSenderMargin, + isSameUser, +} from "../config/ChatLogics"; +import { ChatState } from "../Context/ChatProvider"; + +const ScrollableChat = ({ messages }) => { + const { user } = ChatState(); + + return ( +
+ {messages && + messages.map((m, i) => ( +
+ {(isSameSender(messages, m, i, user._id) || + isLastMessage(messages, i, user._id)) && ( + + + + )} + + + {m.content} + +
+ ))} +
+ ); +}; + +export default ScrollableChat; diff --git a/frontend/src/components/SingleChat.js b/frontend/src/components/SingleChat.js index 6ec8323..d957327 100644 --- a/frontend/src/components/SingleChat.js +++ b/frontend/src/components/SingleChat.js @@ -1,14 +1,108 @@ -import { Box, IconButton, Text } from "@chakra-ui/react"; +import { + Box, + FormControl, + IconButton, + Input, + Spinner, + Text, + useToast, +} from "@chakra-ui/react"; import { ArrowBackIcon } from "@chakra-ui/icons"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { ChatState } from "../Context/ChatProvider"; import { getSender, getSenderFull } from "../config/ChatLogics"; import ProfileModal from "./miscellaneous/ProfileModal"; import UpdateGroupChatModal from "./miscellaneous/UpdateGroupChatModal"; +import axios from "axios"; +import "./style.css"; +import ScrollableChat from "./ScrollableChat"; const SingleChat = ({ fetchAgain, setFetchAgain }) => { const { user, selectedChat, setSelectedChat } = ChatState(); + const toast = useToast(); + + const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(); + const [loading, setLoading] = useState(false); + + const fetchMessages = async () => { + if (!selectedChat) return; + + try { + setLoading(true); + + const config = { + headers: { + Authorization: `Bearer ${user.token}`, + }, + }; + + const { data } = await axios.get( + `/api/message/${selectedChat._id}`, + config + ); + setMessages(data); + // console.log(data); + setLoading(false); + } catch (error) { + toast({ + title: "Error Occured", + description: "Failed to load the messages", + duration: 2000, + status: "error", + isClosable: true, + position: "bottom", + }); + setLoading(false); + } + }; + + useEffect(() => { + fetchMessages(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedChat]); + + const sendMessage = async (e) => { + if (e.key === "Enter" && newMessage) { + try { + const config = { + headers: { + Authorization: `Bearer ${user.token}`, + }, + }; + + setNewMessage(""); + const { data } = await axios.post( + "api/message", + { + content: newMessage, + chatId: selectedChat._id, + }, + config + ); + + // console.log(data); + setMessages([...messages, data]); + } catch (error) { + toast({ + title: "Error Occured", + description: "Failed to send the message", + duration: 2000, + status: "error", + isClosable: true, + position: "bottom", + }); + } + } + }; + + const typingHandler = (e) => { + setNewMessage(e.target.value); + + // Typing indicator Logic + }; + return ( <> {selectedChat ? ( @@ -30,8 +124,12 @@ const SingleChat = ({ fetchAgain, setFetchAgain }) => { /> {selectedChat.isGroupChat ? ( <> - {selectedChat.chatName} - + {selectedChat.chatName} + ) : ( <> @@ -50,7 +148,30 @@ const SingleChat = ({ fetchAgain, setFetchAgain }) => { bg="#E8E8E8" borderRadius="lg" overflowY="hidden" - > + > + {loading ? ( + + ) : ( +
+ +
+ )} + + + + ) : ( { +const UpdateGroupChatModal = ({ fetchAgain, setFetchAgain, fetchMessages }) => { const { isOpen, onOpen, onClose } = useDisclosure(); const { user, selectedChat, setSelectedChat } = ChatState(); @@ -66,6 +66,7 @@ const UpdateGroupChatModal = ({ fetchAgain, setFetchAgain }) => { userToRemove._id === user._id ? setSelectedChat() : setSelectedChat(data); setFetchAgain(!fetchAgain); + fetchMessages() setLoading(false); } catch (error) { toast({ diff --git a/frontend/src/components/style.css b/frontend/src/components/style.css new file mode 100644 index 0000000..73d435c --- /dev/null +++ b/frontend/src/components/style.css @@ -0,0 +1,7 @@ +.messages{ + display: flex; + flex-direction: column; + overflow-y: scroll; + scrollbar-width: none; + /* background-color: #20f099b7; */ +} \ No newline at end of file diff --git a/frontend/src/config/ChatLogics.js b/frontend/src/config/ChatLogics.js index 58475bc..8412d1c 100644 --- a/frontend/src/config/ChatLogics.js +++ b/frontend/src/config/ChatLogics.js @@ -5,3 +5,41 @@ export const getSender = (loggedUser, users) => { export const getSenderFull = (loggedUser, users) => { return users[0]._id === loggedUser._id ? users[1] : users[0]; }; + +export const isSameSender = (messages, m, i, userId) => { + return ( + i < messages.length - 1 && + (messages[i + 1].sender._id !== m.sender._id || + messages[i + 1].sender._id === undefined) && + messages[i].sender._id !== userId + ); +}; + +export const isLastMessage = (messages, i, userId) => { + return ( + i === messages.length - 1 && + messages[messages.length - 1].sender._id !== userId && + messages[messages.length - 1].sender._id + ); +}; + +export const isSameSenderMargin = (messages, m, i, userId) => { + if ( + i < messages.length - 1 && + messages[i + 1].sender._id === m.sender._id && + messages[i].sender._id !== userId + ) + return 33; + else if ( + (i < messages.length - 1 && + messages[i + 1].sender._id !== m.sender._id && + messages[i].sender._id !== userId) || + (i === messages.length - 1 && messages[i].sender._id !== userId) + ) + return 0; + else return "auto"; +}; + +export const isSameUser = (messages, m, i) => { + return i > 0 && messages[i - 1].sender._id === m.sender._id; +};