Skip to content

Commit

Permalink
FEAT : start listing socket event for user online/offline status and …
Browse files Browse the repository at this point in the history
…user typing status
  • Loading branch information
shyamtala003 committed Apr 28, 2024
1 parent 265f2ba commit 0896059
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 16 deletions.
23 changes: 20 additions & 3 deletions src/components/messages/MessageHeader.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import useTypingStatus from "../../hooks/socket/useTypingStatus";
import { useConversation } from "../../stores/useConversation";

const MessageHeader = () => {
const { conversation: user } = useConversation();
const { conversation: user, onlineUsers } = useConversation();
let isOnline = onlineUsers.includes(user._id);
const isTyping = useTypingStatus(user._id);

return (
<div className="flex items-center justify-between px-4 py-3 border-b border-white border-opacity-15">
<div className="flex items-center gap-2">
<div className="avatar online">
<div className={`avatar ${isOnline && "online"}`}>
<div className="w-10 rounded-full">
<img src={user?.profilePicture} />
</div>
</div>
<p className="text-white text-md">{user?.name}</p>

<div className="flex flex-col items-start justify-center">
<p className="text-white text-md">{user?.name}</p>

{/* code for typing status */}
<div
className={`collapse ${
isTyping ? "collapse-open" : "collapse-close"
}`}>
<p className="p-0 text-sm font-semibold text-green-400 collapse-content">
typing...
</p>
</div>
</div>
</div>
</div>
);
Expand Down
46 changes: 44 additions & 2 deletions src/components/messages/SendMessageForm.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { BsFillSendFill } from "react-icons/bs";
import { useEffect, useState } from "react";
import { useConversation } from "../../stores/useConversation";
import useApiCall from "../../hooks/useApiCall";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import { useForm } from "react-hook-form";
import scrollDown from "../../utils/scrollDown";
import { socket } from "../../socket/socket";
import { useAuth } from "../../stores/useAuth";

const schema = yup.object().shape({
message: yup.string().required("username is required"),
message: yup.string().required("Message is required"),
});

const SendMessageForm = () => {
const { conversation, setMessage } = useConversation();
const { userId } = useAuth();
const { apiCall, loading } = useApiCall();
const [isTyping, setIsTyping] = useState(false);

// use form hook for form management
const { handleSubmit, register, formState, reset } = useForm({
Expand All @@ -35,11 +40,48 @@ const SendMessageForm = () => {
scrollDown("messageContainer");
return reset();
}

useEffect(() => {
let typingTimeout;

const handleKeyDown = () => {
setIsTyping(true);
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
setIsTyping(false);
}, 1000); // 1 second timeout
};

document.addEventListener("keydown", handleKeyDown);

return () => {
document.removeEventListener("keydown", handleKeyDown);
clearTimeout(typingTimeout);
};
}, [userId]);

useEffect(() => {
if (isTyping) {
socket.emit("typing", {
senderId: userId,
receiverId: conversation._id,
});
} else {
socket.emit("stopTyping", {
senderId: userId,
receiverId: conversation._id,
});
}
}, [isTyping, userId, conversation._id]);

return (
<div className="sticky w-full bottom-1">
<form
className="flex items-center w-full px-4 py-3"
onSubmit={handleSubmit(sendMessage)}>
onSubmit={handleSubmit(sendMessage)}
onKeyDown={() => setIsTyping(true)}>
{" "}
{/* Listen for keydown event */}
<input
type="text"
name="message"
Expand Down
30 changes: 23 additions & 7 deletions src/components/sidebar/ConversationItem.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { memo } from "react";
import { useConversation } from "../../stores/useConversation";
import useTypingStatus from "../../hooks/socket/useTypingStatus";

const ConversationItem = ({ user }) => {
let { setSelectedConversation, conversation } = useConversation();

const ConversationItem = memo(function ConversationItem({ user }) {
let { setSelectedConversation, conversation, onlineUsers } =
useConversation();
let isOnline = onlineUsers.includes(user._id);
const isTyping = useTypingStatus(user._id);
function setConversation() {
if (conversation === null || conversation?.id !== user._id)
return setSelectedConversation(user);
Expand All @@ -14,15 +17,28 @@ const ConversationItem = ({ user }) => {
onClick={setConversation}
className="flex items-center px-2 py-3 transition-all duration-200 ease-in-out border-b border-white cursor-pointer border-opacity-20 hover:backdrop-blur-xl hover:rounded-lg hover:border-transparent">
<div className="flex items-center gap-2">
<div className="avatar online">
<div className={`avatar ${isOnline && "online"}`}>
<div className="rounded-full w-11">
<img src={user?.profilePicture} />
</div>
</div>
<p className="text-white text-md">{user?.name}</p>

<div className="flex flex-col items-start justify-center">
<p className="text-white text-md">{user?.name}</p>

{/* code for typing status */}
<div
className={`collapse ${
isTyping ? "collapse-open" : "collapse-close"
}`}>
<p className="p-0 text-sm font-semibold text-green-400 collapse-content">
typing...
</p>
</div>
</div>
</div>
</div>
);
};
});

export default memo(ConversationItem);
export default ConversationItem;
1 change: 1 addition & 0 deletions src/components/sidebar/Conversations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const Conversations = () => {
const fetchConversation = useCallback(async () => {
let response = await apiCall("get", "/api/v1/users");
return setConversations(response.data.user);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/socket/useTypingStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect, useState } from "react";
import { socket } from "../../socket/socket";

const useTypingStatus = (userId) => {
const [isTyping, setIsTyping] = useState(false);

useEffect(() => {
const handleTyping = ({ senderId }) => {
if (senderId === userId) {
setIsTyping(true);
}
};

const handleStopTyping = ({ senderId }) => {
if (senderId === userId) {
setIsTyping(false);
}
};

// Listen for "typing" and "stopTyping" events
socket.on("typing", handleTyping);
socket.on("stopTyping", handleStopTyping);

// Cleanup: Remove event listeners when component unmounts
return () => {
socket.off("typing", handleTyping);
socket.off("stopTyping", handleStopTyping);
};
}, [userId]);

return isTyping;
};

export default useTypingStatus;
10 changes: 6 additions & 4 deletions src/stores/useConversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { create } from "zustand";
export const useConversation = create((set) => ({
conversation: null,
messages: [],
onlineUsers: [],

// 1. select selected conversation
setSelectedConversation(conversation) {
Expand All @@ -21,12 +22,13 @@ export const useConversation = create((set) => ({
set((state) => ({ messages: [...state.messages, message] }));
},

// reset message array
// 4.reset message array
setCleanMessage() {
set(() => ({ messages: [] }));
},

// setBasicDetails(rest) {
// set((state) => ({ ...state, ...rest }));
// },
// 5.set online user
setOnlineUser(onlineUsers) {
set(() => ({ onlineUsers }));
},
}));

0 comments on commit 0896059

Please sign in to comment.