Skip to content

Commit

Permalink
Fixed frontend functionality for get conversations, added send messag…
Browse files Browse the repository at this point in the history
…es & get messages
  • Loading branch information
Keagan Aromin authored and Keagan Aromin committed Jan 12, 2025
1 parent 27965df commit b8eea68
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 26 deletions.
22 changes: 16 additions & 6 deletions frontend/src/components/messages/Message.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import {useAuthContext} from '../../context/AuthContext'
import React from "react";
import useConversation from '../../zustand/useConversation';
import {extractTime} from "../../utils/extractTime"

const Message = ({message}) => {
const {authUser} = useAuthContext();
const {selectedConversation} = useConversation();
const fromMe = message.senderId === authUser._id;
const formattedTime = extractTime(message.createdAt);
const chatClassName = fromMe? 'chat-end' : 'chat-start';
const profilePic = fromMe ? authUser.profilePic : selectedConversation?.profilePic;
const bubbleBgColor = fromMe ? 'bg-blue-500' : "";


const Message = () => {
return (
<div className="chat chat-end">
<div className={`chat ${chatClassName}`}>
<div className="chat-image avatar">
<div className="w-10 rounded-full">
<img
alt="Tailwind CSS chat bubble component"
src={
"https://cdn0.iconfinder.com/data/icons/communication-line-10/24/account_profile_user_contact_person_avatar_placeholder-512.png"
}
src={profilePic}
/>
</div>
</div>
<div className={`chat-bubble text-white bg-blue-500`}>Hi! What's up?</div>
<div className={`chat-bubble text-white ${bubbleBgColor}`}>{message.message}</div>
<div className="chat-footer opacity-50 text-xs flex gap-1 items-center">
1:12
</div>
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/components/messages/MessageInput.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import React from "react";
import {useState} from "react";
import { BsSend } from "react-icons/bs";
import useSendMessage from "../../hooks/useSendMessage";

const MessageInput = () => {
const [message, setMessage] = useState("");
const {loading, sendMessage} = useSendMessage();
const handleSubmit = async (e) => {
e.preventDefault()
if (!message) return;
await sendMessage(message);
setMessage("");
}

return (
<form className="px-4 my-3">
<form className="px-4 my-3" onSubmit={handleSubmit}>
<div className="w-full relative">
<input
type="text"
className="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 text-white"
placeholder="Send a message"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button
type="submit"
className="absolute inset-y-0 end-0 flex items-center pe-3"
>
<BsSend />
{loading ? <div className = 'loading-loading-spinner'></div> : <BsSend />}
</button>
</div>
</form>
Expand Down
37 changes: 25 additions & 12 deletions frontend/src/components/messages/Messages.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import React from "react";
import React, { useEffect, useRef } from "react";
import Message from "./Message";
import useGetMessages from "../../hooks/useGetMessages";
import MessageSkeleton from "../skeletons/MessageSkeleton";

const Messages = () => {
const {messages, loading}= useGetMessages();
const lastMessageRef = useRef();

useEffect(() => {
setTimeout(() => {
lastMessageRef.current?.scrollIntoView({behavior: "smooth"});
}, 100)

}, [messages])
return (
<div className="px-4 flex-1 overflow-auto">
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
{!loading &&
messages.length > 0 &&
messages.map((message) => (
<div key = {message._id}
ref = {lastMessageRef}
>
<Message message={message}/>
</div>
))}
{loading && [...Array(3)].map((_, idx) => <MessageSkeleton key={idx} />)}
{!loading && messages.length === 0 && (
<p className='text-center'>Send a message to start the conversation</p>
)}
</div>
);
};
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/components/skeletons/MessageSkeleton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const MessageSkeleton = () => {
return (
<>
<div className='flex gap-3 items-center'>
<div className='skeleton w-10 h-10 rounded-full shrink-0'></div>
<div className='flex flex-col gap-1'>
<div className='skeleton h-4 w-40'></div>
<div className='skeleton h-4 w-40'></div>
</div>
</div>
<div className='flex gap-3 items-center justify-end'>
<div className='flex flex-col gap-1'>
<div className='skeleton h-4 w-40'></div>
</div>
<div className='skeleton w-10 h-10 rounded-full shrink-0'></div>
</div>
</>
);
};
export default MessageSkeleton;
9 changes: 6 additions & 3 deletions frontend/src/hooks/useGetConversations.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast';

const useGetConversations = () => {
const [loading, setLoading] = useState(false);
const [conversations, setConversations] = useState([]);

//only runs when the component mounts because empty dependence array parameter []
useEffect(() => {
const getConversations = async () => {
setLoading(true);
try {
const res = await fetch('/api/users');
const res = await fetch('/api/users'); //sends GET request to /api/users endpoint which leads to getUsersFromSidebar function in user.controller.js
const data = await res.json();
if (data.error) {
throw new Error(data.error);
Expand All @@ -18,13 +19,15 @@ const useGetConversations = () => {

} catch (error) {
toast.error(error.message);

} finally {
setLoading(false);
}
}
getConversations();
},[]);

//encapsulation, don't return the updater functions
//loading will not be true until the await functions pull through (finally will still run even if you get an error)
return { loading, conversations };
}

Expand Down
29 changes: 29 additions & 0 deletions frontend/src/hooks/useGetMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState, useEffect } from "react";
import useConversation from "../zustand/useConversation"
import toast from "react-hot-toast";

const useGetMessages = () => {
const [loading, setLoading] = useState(false)
const {messages, setMessages, selectedConversation} = useConversation();

useEffect(() => {
const getMessages = async () => {
setLoading(true)
try {
const res = await fetch(`/api/messages/${selectedConversation._id}`) //GET
const data = await res.json();
if(data.error) throw new Error(data.error)
setMessages(data)
} catch (error) {
toast.error(error.message)
} finally {
setLoading(false)
}
}

if (selectedConversation?._id) getMessages()
}, [selectedConversation?._id, setMessages])
return {messages, loading}
}

export default useGetMessages
33 changes: 33 additions & 0 deletions frontend/src/hooks/useSendMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState } from 'react'
import useConversation from '../zustand/useConversation.js'
import toast from 'react-hot-toast'

const useSendMessage = () => {
const [loading, setLoading] = useState(false)
const {messages, setMessages, selectedConversation} = useConversation();

const sendMessage = async (message) => {
setLoading(true)
try {
const res = await fetch(`/api/messages/send/${selectedConversation._id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({message})
})
const data = await res.json()
if (data.error) throw new Error(data.error)

//... (spread opreator) expands elements so that you can add next one without nesting array
setMessages([...messages, data])
} catch (error) {
toast.error(error.message)
} finally {
setLoading(false)
}
}
return {sendMessage, loading};
}

export default useSendMessage;
11 changes: 11 additions & 0 deletions frontend/src/utils/extractTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function extractTime(dateString) {
const date = new Date(dateString);
const hours = padZero(date.getHours());
const minutes = padZero(date.getMinutes());
return `${hours}:${minutes}`;
}

// Helper function to pad single-digit numbers with a leading zero
function padZero(number) {
return number.toString().padStart(2, "0");
}
4 changes: 2 additions & 2 deletions frontend/src/zustand/useConversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import {create} from 'zustand';

const useConversation = create((set) => ({
selectedConversation: null,
setSelectedConversation: (selectedConversation) => set({selectedConversation}),
setSelectedConversation: (selectedConversation) => set({selectedConversation}), //hook for selectedConversation
messages:[],
setMessages: (messages) => set({messages}),
setMessages: (messages) => set({messages}), //hook for messages
}));

export default useConversation;

0 comments on commit b8eea68

Please sign in to comment.