Skip to content

Commit

Permalink
Image support added for chat app
Browse files Browse the repository at this point in the history
  • Loading branch information
burakorkmez committed Sep 19, 2023
1 parent 807dcf1 commit 3b513f4
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 30 deletions.
8 changes: 8 additions & 0 deletions backend/controllers/messageController.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import Conversation from "../models/conversationModel.js";
import Message from "../models/messageModel.js";
import { getRecipientSocketId, io } from "../socket/socket.js";
import { v2 as cloudinary } from "cloudinary";

async function sendMessage(req, res) {
try {
const { recipientId, message } = req.body;
let { img } = req.body;
const senderId = req.user._id;

let conversation = await Conversation.findOne({
Expand All @@ -22,10 +24,16 @@ async function sendMessage(req, res) {
await conversation.save();
}

if (img) {
const uploadedResponse = await cloudinary.uploader.upload(img);
img = uploadedResponse.secure_url;
}

const newMessage = new Message({
conversationId: conversation._id,
sender: senderId,
text: message,
img: img || "",
});

await Promise.all([
Expand Down
4 changes: 4 additions & 0 deletions backend/models/messageModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const messageSchema = new mongoose.Schema(
type: Boolean,
default: false,
},
img: {
type: String,
default: "",
},
},
{ timestamps: true }
);
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/Conversation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from "@chakra-ui/react";
import { useRecoilState, useRecoilValue } from "recoil";
import userAtom from "../atoms/userAtom";
import { BsCheck2All } from "react-icons/bs";
import { BsCheck2All, BsFillImageFill } from "react-icons/bs";
import { selectedConversationAtom } from "../atoms/messagesAtom";

const Conversation = ({ conversation, isOnline }) => {
Expand Down Expand Up @@ -72,7 +72,9 @@ const Conversation = ({ conversation, isOnline }) => {
) : (
""
)}
{lastMessage.text.length > 18 ? lastMessage.text.substring(0, 18) + "..." : lastMessage.text}
{lastMessage.text.length > 18
? lastMessage.text.substring(0, 18) + "..."
: lastMessage.text || <BsFillImageFill size={16} />}
</Text>
</Stack>
</Flex>
Expand Down
77 changes: 66 additions & 11 deletions frontend/src/components/Message.jsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,87 @@
import { Avatar, Box, Flex, Text } from "@chakra-ui/react";
import { Avatar, Box, Flex, Image, Skeleton, Text } from "@chakra-ui/react";
import { selectedConversationAtom } from "../atoms/messagesAtom";
import { useRecoilValue } from "recoil";
import userAtom from "../atoms/userAtom";
import { BsCheck2All } from "react-icons/bs";
import { useState } from "react";

const Message = ({ ownMessage, message }) => {
const selectedConversation = useRecoilValue(selectedConversationAtom);
const user = useRecoilValue(userAtom);
console.log(user);
const [imgLoaded, setImgLoaded] = useState(false);
return (
<>
{ownMessage ? (
<Flex gap={2} alignSelf={"flex-end"}>
<Flex bg={"green.800"} maxW={"350px"} p={1} borderRadius={"md"}>
<Text color={"white"}>{message.text}</Text>
<Box alignSelf={"flex-end"} ml={1} color={message.seen ? "blue.400" : ""} fontWeight={"bold"}>
<BsCheck2All size={16} />
</Box>
</Flex>
{message.text && (
<Flex bg={"green.800"} maxW={"350px"} p={1} borderRadius={"md"}>
<Text color={"white"}>{message.text}</Text>
<Box
alignSelf={"flex-end"}
ml={1}
color={message.seen ? "blue.400" : ""}
fontWeight={"bold"}
>
<BsCheck2All size={16} />
</Box>
</Flex>
)}
{message.img && !imgLoaded && (
<Flex mt={5} w={"200px"}>
<Image
src={message.img}
hidden
onLoad={() => setImgLoaded(true)}
alt='Message image'
borderRadius={4}
/>
<Skeleton w={"200px"} h={"200px"} />
</Flex>
)}

{message.img && imgLoaded && (
<Flex mt={5} w={"200px"}>
<Image src={message.img} alt='Message image' borderRadius={4} />
<Box
alignSelf={"flex-end"}
ml={1}
color={message.seen ? "blue.400" : ""}
fontWeight={"bold"}
>
<BsCheck2All size={16} />
</Box>
</Flex>
)}

<Avatar src={user.profilePic} w='7' h={7} />
</Flex>
) : (
<Flex gap={2}>
<Avatar src={selectedConversation.userProfilePic} w='7' h={7} />

<Text maxW={"350px"} bg={"gray.400"} p={1} borderRadius={"md"} color={"black"}>
{message.text}
</Text>
{message.text && (
<Text maxW={"350px"} bg={"gray.400"} p={1} borderRadius={"md"} color={"black"}>
{message.text}
</Text>
)}
{message.img && !imgLoaded && (
<Flex mt={5} w={"200px"}>
<Image
src={message.img}
hidden
onLoad={() => setImgLoaded(true)}
alt='Message image'
borderRadius={4}
/>
<Skeleton w={"200px"} h={"200px"} />
</Flex>
)}

{message.img && imgLoaded && (
<Flex mt={5} w={"200px"}>
<Image src={message.img} alt='Message image' borderRadius={4} />
</Flex>
)}
</Flex>
)}
</>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/MessageContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const MessageContainer = () => {
if (selectedConversation._id === message.conversationId) {
setMessages((prev) => [...prev, message]);
}

console.log(message, selectedConversation._id);
setConversations((prev) => {
const updatedConversations = prev.map((conversation) => {
if (conversation._id === message.conversationId) {
Expand Down
90 changes: 74 additions & 16 deletions frontend/src/components/MessageInput.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
import { Input, InputGroup, InputRightElement } from "@chakra-ui/react";
import { useState } from "react";
import {
Flex,
Image,
Input,
InputGroup,
InputRightElement,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Spinner,
useDisclosure,
} from "@chakra-ui/react";
import { useRef, useState } from "react";
import { IoSendSharp } from "react-icons/io5";
import useShowToast from "../hooks/useShowToast";
import { conversationsAtom, selectedConversationAtom } from "../atoms/messagesAtom";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { BsFillImageFill } from "react-icons/bs";
import usePreviewImg from "../hooks/usePreviewImg";

const MessageInput = ({ setMessages }) => {
const [messageText, setMessageText] = useState("");
const showToast = useShowToast();
const selectedConversation = useRecoilValue(selectedConversationAtom);
const setConversations = useSetRecoilState(conversationsAtom);
const imageRef = useRef(null);
const { onClose } = useDisclosure();
const { handleImageChange, imgUrl, setImgUrl } = usePreviewImg();
const [isSending, setIsSending] = useState(false);

const handleSendMessage = async (e) => {
e.preventDefault();
if (!messageText) return;
if (!messageText && !imgUrl) return;
if (isSending) return;

setIsSending(true);

try {
const res = await fetch("/api/messages", {
Expand All @@ -24,6 +47,7 @@ const MessageInput = ({ setMessages }) => {
body: JSON.stringify({
message: messageText,
recipientId: selectedConversation.userId,
img: imgUrl,
}),
});
const data = await res.json();
Expand All @@ -50,24 +74,58 @@ const MessageInput = ({ setMessages }) => {
return updatedConversations;
});
setMessageText("");
setImgUrl("");
} catch (error) {
showToast("Error", error.message, "error");
} finally {
setIsSending(false);
}
};
return (
<form onSubmit={handleSendMessage}>
<InputGroup>
<Input
w={"full"}
placeholder='Type a message'
onChange={(e) => setMessageText(e.target.value)}
value={messageText}
/>
<InputRightElement onClick={handleSendMessage} cursor={"pointer"}>
<IoSendSharp />
</InputRightElement>
</InputGroup>
</form>
<Flex gap={2} alignItems={"center"}>
<form onSubmit={handleSendMessage} style={{ flex: 95 }}>
<InputGroup>
<Input
w={"full"}
placeholder='Type a message'
onChange={(e) => setMessageText(e.target.value)}
value={messageText}
/>
<InputRightElement onClick={handleSendMessage} cursor={"pointer"}>
<IoSendSharp />
</InputRightElement>
</InputGroup>
</form>
<Flex flex={5} cursor={"pointer"}>
<BsFillImageFill size={20} onClick={() => imageRef.current.click()} />
<Input type={"file"} hidden ref={imageRef} onChange={handleImageChange} />
</Flex>
<Modal
isOpen={imgUrl}
onClose={() => {
onClose();
setImgUrl("");
}}
>
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex mt={5} w={"full"}>
<Image src={imgUrl} />
</Flex>
<Flex justifyContent={"flex-end"} my={2}>
{!isSending ? (
<IoSendSharp size={24} cursor={"pointer"} onClick={handleSendMessage} />
) : (
<Spinner size={"md"} />
)}
</Flex>
</ModalBody>
</ModalContent>
</Modal>
</Flex>
);
};

Expand Down

0 comments on commit 3b513f4

Please sign in to comment.