Skip to content

Commit

Permalink
getting conversations
Browse files Browse the repository at this point in the history
  • Loading branch information
inodinwetrust10 committed Jun 18, 2024
1 parent b76fa79 commit 063d948
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 63 deletions.
42 changes: 39 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.23.1"
"react-router-dom": "^6.23.1",
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
42 changes: 24 additions & 18 deletions src/components/messages/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
const Message = ({ message }: { message?: any }) => {
const fromMe = message.fromMe;
const chatClass = fromMe ? "chat-end" : "chat-start";
const img = fromMe
? "https://avatar.iran.liara.run/public/boy?username=johndoe"
: "https://avatar.iran.liara.run/public/boy?username=janedoe";
import { useAuthContext } from "../../context/AuthContext";
import useConversation, { MessageType } from "../../zustand/useConversation";
import { extractTime } from "../../utils/extractTime";

const bubbleBg = fromMe ? "bg-blue-500" : "";
return (
<div className={`chat ${chatClass}`}>
<div className='hidden md:block chat-image avatar'>
<div className='w-6 md:w-10 rounded-full'>
<img alt='Tailwind CSS chat bubble component' src={img} />
</div>
</div>
<p className={`chat-bubble text-white ${bubbleBg} text-sm md:text-md`}>{message.body}</p>
<span className='chat-footer opacity-50 text-xs flex gap-1 items-center text-white'>22:59</span>
</div>
);
const Message = ({ message }: { message: MessageType }) => {
const { authUser } = useAuthContext();
const { selectedConversation } = useConversation();

const fromMe = message?.senderId === authUser?.id;
const img = fromMe ? authUser?.profilePic : selectedConversation?.profilePic;
const chatClass = fromMe ? "chat-end" : "chat-start";

const bubbleBg = fromMe ? "bg-blue-500" : "";
const shakeClass = message.shouldShake ? "shake" : "";
return (
<div className={`chat ${chatClass}`}>
<div className='hidden md:block chat-image avatar'>
<div className='w-6 md:w-10 rounded-full'>
<img alt='Tailwind CSS chat bubble component' src={img} />
</div>
</div>
<p className={`chat-bubble text-white ${bubbleBg} ${shakeClass} text-sm md:text-md`}>{message.body}</p>
<span className='chat-footer opacity-50 text-xs flex gap-1 items-center text-white'>{extractTime(message.createdAt)}</span>
</div>
);
};
export default Message;
58 changes: 34 additions & 24 deletions src/components/messages/MessageContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import { MessageCircle } from "lucide-react";
import { useAuthContext } from "../../context/AuthContext";
import useConversation from "../../zustand/useConversation";
import MessageInput from "./MessageInput";
import Messages from "./Messages";

// import { MessageCircle } from "lucide-react";

const MessageContainer = () => {
return (
<div className='w-full flex flex-col'>
<>
{/* Header */}
<div className='bg-slate-500 px-4 py-2 mb-2'>
<span className='label-text'>To:</span> <span className='text-gray-900 font-bold'>John doe</span>
</div>
const { selectedConversation } = useConversation();

<Messages />
<MessageInput />
</>
</div>
);
return (
<div className='w-full flex flex-col'>
{!selectedConversation ? (
<NoChatSelected />
) : (

<>
{/* Header */}
<div className='bg-slate-500 px-4 py-2 mb-2'>
<span className='label-text'>To:</span> <span className='text-gray-900 font-bold'>{selectedConversation.fullname}</span>
</div>

<Messages />
<MessageInput />
</>
)}
</div>
);
};
export default MessageContainer;

// const NoChatSelected = () => {
// return (
// <div className='flex items-center justify-center w-full h-full'>
// <div className='px-4 text-center sm:text-lg md:text-xl text-gray-200 font-semibold flex flex-col items-center gap-2'>
// <p>Welcome 👋 John Doe ❄</p>
// <p>Select a chat to start messaging</p>
// <MessageCircle className='text-3xl md:text-6xl text-center' />
// </div>
// </div>
// );
// };
const NoChatSelected = () => {
const { authUser } = useAuthContext();
return (
<div className='flex items-center justify-center w-full h-full'>
<div className='px-4 text-center sm:text-lg md:text-xl text-gray-200 font-semibold flex flex-col items-center gap-2'>
<p>Welcome 👋 {authUser?.fullname}</p>
<p>Select a chat to start messaging</p>
<MessageCircle className='text-3xl md:text-6xl text-center' />
</div>
</div>
);
};
24 changes: 16 additions & 8 deletions src/components/messages/Messages.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { DUMMY_MESSAGES } from "../../dummy_data/dummy";
import useChatScroll from "../../hooks/useChatScroll";
import useGetMessages from "../../hooks/useGetMessages";
import MessageSkeleton from "../skeletons/MessageSkeleton";
import Message from "./Message";

const Messages = () => {
return (
<div className='px-4 flex-1 overflow-auto'>
{DUMMY_MESSAGES.map((message) => (
<Message key={message.id} message={message} />
))}
</div>
);
const { loading, messages } = useGetMessages();
const ref = useChatScroll(messages) as React.MutableRefObject<HTMLDivElement>;
return (
<div className='px-4 flex-1 overflow-auto' ref={ref}>
{loading && [...Array(3)].map((_, idx) => <MessageSkeleton key={idx} />)}

{!loading && messages.map((message) => <Message key={message.id} message={message} />)}

{!loading && messages.length === 0 && (
<p className='text-center text-white'>Send a message to start the conversation</p>
)}
</div>
);
};
export default Messages;
17 changes: 12 additions & 5 deletions src/components/sidebar/Conversation.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
const Conversation = ({ conversation }: { conversation: any }) => {
import useConversation, { ConversationType } from "../../zustand/useConversation";

const Conversation = ({ conversation,emoji }: { conversation: ConversationType;emoji:string }) => {
const { setSelectedConversation, selectedConversation } = useConversation();
const isSelected = selectedConversation?.id === conversation.id;
const isOnline = false;
return (
<>
<div className='flex gap-2 items-center hover:bg-sky-500 rounded p-2 py-1 cursor-pointer'>
<div className='avatar online'>
<div
className={`flex gap-2 items-center hover:bg-sky-500 rounded p-2
py-1 cursor-pointer ${isSelected ? "bg-sky-500" : ""}`}onClick={() => setSelectedConversation(conversation)}>
<div className={`avatar ${isOnline? "online":""}`}>
<div className='w-8 md:w-12 rounded-full'>
<img src={conversation.profilePic} alt='user avatar' />
</div>
</div>

<div className='flex flex-col flex-1'>
<div className='flex gap-3 justify-between'>
<p className='font-bold text-gray-200 text-sm md:text-md'>{conversation.fullName}</p>
<span className='text-xl hidden md:inline-block'>{conversation.emoji}</span>
<p className='font-bold text-gray-200 text-sm md:text-md'>{conversation.fullname}</p>
<span className='text-xl hidden md:inline-block'>{emoji}</span>
</div>
</div>
</div>
Expand Down
10 changes: 6 additions & 4 deletions src/components/sidebar/Conversations.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { DUMMY_CONVERSATIONS } from "../../dummy_data/dummy";
import useGetConversations from "../../hooks/useGetConversations";
import { getRandomEmoji } from "../../utils/emojis";
import Conversation from "./Conversation";

const Conversations = () => {
const { conversations, loading } = useGetConversations();
return (
<div className='py-2 flex flex-col overflow-auto'>
{DUMMY_CONVERSATIONS.map((conversation) => (
<Conversation key={conversation.id} conversation={conversation} />
))}
{conversations.map((conversation) => (
<Conversation key={conversation.id} conversation={conversation} emoji={getRandomEmoji()} /> ))}
{loading ? <span className='loading loading-spinner mx-auto' /> : null}
</div>
);
};
Expand Down
17 changes: 17 additions & 0 deletions src/hooks/useChatScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect, useRef } from "react";

function useChatScroll(dep: any) {
const ref = useRef<HTMLElement>();

useEffect(() => {
setTimeout(() => {
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight;
}
}, 100);
}, [dep]);

return ref;
}

export default useChatScroll;
31 changes: 31 additions & 0 deletions src/hooks/useGetConversations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { ConversationType } from "../zustand/useConversation";

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

useEffect(() => {
const getConversations = async () => {
setLoading(true);
try {
const res = await fetch("/api/message/conversations");
const data = await res.json();
if (data.error) {
throw new Error(data.error);
}
setConversations(data);
} catch (error: any) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

getConversations();
}, []);

return { loading, conversations };
};
export default useGetConversations;
31 changes: 31 additions & 0 deletions src/hooks/useGetMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } 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 () => {
if (!selectedConversation) return;
setLoading(true);
setMessages([]);
try {
const res = await fetch(`/api/message/${selectedConversation.id}`);
const data = await res.json();
if (!res.ok) throw new Error(data.error || "An error occurred");
setMessages(data);
} catch (error: any) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

getMessages();
}, [selectedConversation, setMessages]);

return { messages, loading };
};
export default useGetMessages;
Loading

0 comments on commit 063d948

Please sign in to comment.