Skip to content

Commit

Permalink
Merge pull request #88 from birongliu/dev
Browse files Browse the repository at this point in the history
Refactor chat components and update caching strategy
  • Loading branch information
bradleyhung authored Dec 8, 2024
2 parents 72d5f99 + 6db6bb5 commit 8cc6cda
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 47 deletions.
4 changes: 2 additions & 2 deletions backend/database/models/chatRoomModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Schema, model } from "mongoose";

const chatRoomModel = new Schema({
roomId: String,
recipients: [String],
recipients: [Object],
messages: [Object],
createdAt: { type: Date, default: Date.now() },
});
Expand Down Expand Up @@ -36,7 +36,7 @@ export async function sendMessage(ctx) {
message: [],
recipients: [reciver, sender],
});
if (message) chat.messages.push({ message, sender });
if (message) chat.messages.push(message);
await chat.save();
return chat;
}
Expand Down
3 changes: 1 addition & 2 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ io.on("connection", (socket) => {
console.log("Received message data:", data);
// Save message to the database
const newMessage = await sendMessage({
message: data.message,
roomId: data.roomId,
sender: data.sender,
reciver: data.reciver,
message: data.message,
});

// Emit the message to all connected clients
Expand All @@ -87,7 +87,6 @@ io.on("connection", (socket) => {
} catch (error) {
console.error("Error saving or emitting message:", error);
socket.emit("error", "Failed to send the message.");
callback({ status: "error", message: "Failed to send the message." });
}
});

Expand Down
96 changes: 57 additions & 39 deletions frontend/src/app/(users)/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client";

import { ContextUser, Room, UserContext } from "@/app/context/getUserContext";
import { useRouter, useParams } from "next/navigation";
import { FormEvent, useContext, useEffect, useRef, useState } from "react";
import { useRouter, useParams, redirect } from "next/navigation";
import { FormEvent, useContext, useEffect, useState } from "react";
import { SideBar } from "../page";
import FriendList from "@/app/ui/chat/FriendList";
import Image from "next/image";
Expand All @@ -20,7 +19,7 @@ export default function Chat() {
const { id } = useParams();
const context = useContext(UserContext);
const socket = useContext(SocketContext);
if(!socket || !context) return null;
if (!socket || !context) return null;
const room = context.rooms.find((r) => r.roomId === id);
return room ? (
<ChatRoom socket={socket.socket} room={room} context={context} />
Expand All @@ -29,28 +28,33 @@ export default function Chat() {
);
}

export function ChatRoom({
function ChatRoom({
room,
context,
socket
socket,
}: {
room: Room | undefined;
context: ContextUser;
socket: Socket,
socket: Socket;
}) {
const [activeRoom, setActiveRoom] = useState("");
const [message, setMessage] = useState<Message>({ message: "", sender: "", image: "" });
const [messages, setMessages] = useState<Message[]>(room ? room.messages : []);
const messageRef = useRef<HTMLDivElement>(null);
const [message, setMessage] = useState<Message>({
message: "",
sender: "",
image: "",
});
const [messages, setMessages] = useState<Message[]>(
room ? room.messages : []
);
const [error, setError] = useState("");
const [toggleFriendButton, setToggleFriendButton] = useState(false);
const other = room && room.recipients.find((r) => r.username !== context.username);

const [toggleFriendButton, setToggleFriendButton] = useState(true);
const other =
room && room.recipients.find((r) => r.username !== context.username);

const handleMessages = (e: FormEvent) => {
e.preventDefault();
console.log("in handle messages")
if(message.message.trim() === "") {
console.log("in handle messages");
if (message.message.trim() === "") {
setError("Message cannot be empty");
return;
}
Expand All @@ -63,25 +67,30 @@ export function ChatRoom({
const constructMessageWithRoom = {
message: constructMessage,
roomId: room?.roomId,
sender: context,
sender: {
id: context.id,
username: context.username,
imageUrl: context.imageUrl,
},
reciver: other,
}
socket.emit("room message", constructMessageWithRoom)
};
socket.emit("room message", constructMessageWithRoom);
setMessages((prevMessages) => [...prevMessages, constructMessage]);
setMessage({ message: "", sender: "", image: "", id: "" });
};
const scrollToBottom = (messageRef: HTMLDivElement | null) => {
if(messageRef) messageRef.scroll({ top: messageRef.scrollHeight, behavior: "smooth" });
}

useEffect(() => {
if (messageRef.current) {
messageRef.current.scroll({ top: messageRef.current.scrollHeight, behavior: "smooth" });
}
const initalize = () => {
if(!room) return;
if (!room) return;
const { roomId, messages } = room;
setActiveRoom(roomId ?? "");
setToggleFriendButton(true);
setMessages(messages);
}
initalize()
};
initalize();

socket.emit("join room", room && room.roomId);
socket.on("room message", (message: Message) => {
Expand All @@ -90,10 +99,10 @@ export function ChatRoom({

return () => {
socket.off("room message");
}
}, [socket, room])
};
}, [socket, room]);

const router = useRouter()
const router = useRouter();
return (
<div className="flex overflow-auto w-full h-full">
<SideBar
Expand All @@ -113,10 +122,8 @@ export function ChatRoom({
return;
}
setActiveRoom(id);
console.log("id", id);
router.replace(`/chat/${id}`);
setActiveRoom(id)
setToggleFriendButton(true);
router.push(`/chat/${id}`, { scroll: false });
}}
rooms={context.rooms
.filter(
Expand All @@ -127,7 +134,7 @@ export function ChatRoom({
let otherUser = value.recipients.find(
(u) => u.username !== context.username
);
if (!otherUser) otherUser = { id: "", imageUrl: "", username: ""};
if (!otherUser) otherUser = { id: "", imageUrl: "", username: "" };
return {
roomId: value.roomId,
id: otherUser.id,
Expand All @@ -138,7 +145,7 @@ export function ChatRoom({
/>
{activeRoom === room?.roomId && (
<div
className={` ${
className={`${
activeRoom === "" ? "hidden" : "block"
} w-full lg:w-[74%] h-full flex flex-col`}
>
Expand Down Expand Up @@ -193,12 +200,17 @@ export function ChatRoom({
/>
</div>
</div>
<div ref={messageRef} className="h-[75%] mt-5 relative overflow-auto rounded-xl bg-light-blue flex-col p-5 flex">
<div
ref={scrollToBottom}
className="h-[75%] mt-5 overflow-auto rounded-xl bg-light-blue flex-col p-5 flex"
>
{messages.map((message) => (
<div
key={message.id}
className={`flex mt-2 w-full items-center ${
message.sender === context.username ? "justify-end" : "justify-start"
message.sender === context.username
? "justify-end"
: "justify-start"
} gap-2`}
>
<Image
Expand All @@ -207,7 +219,9 @@ export function ChatRoom({
width={200}
height={200}
className={`w-12 h-12 bg-black rounded-full ${
message.sender === context.username ? "order-1" : "order-none"
message.sender === context.username
? "order-1"
: "order-none"
}`}
/>
<p className="bg-light-rose p-2 rounded-xl">
Expand All @@ -223,12 +237,16 @@ export function ChatRoom({
id="message"
value={message.message}
onChange={(e) => {
if(error) setError("");
console.log("message", e.target.value);
setMessage({ id: message.id, message: e.target.value, sender: context.username, image: context.imageUrl })
if (error) setError("");
setMessage({
id: message.id,
message: e.target.value,
sender: context.username,
image: context.imageUrl,
});
}}
className="w-full h-12 resize-none p-2 rounded-xl border-2 outline-none focus:ring-2 focus:ring-light-rose"
placeholder="Type your friend id"
placeholder={`Message ${other?.username}`}
/>
</form>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/(users)/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export function SideBar({
<h1 className="font-poppins text-xl text-bold">
{friend.username ?? ""}
</h1>

</li>
</button>
</div>
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/app/actions/deleteRoom-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use server';

import { revalidateTag } from "next/cache";

export default async function deleteRoomAction(id: string) {
console.log("in delete room action")
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/rooms/${id}`
const response = await fetch(url, {
method: "DELETE",
next: { tags: ["delete-rooms"] },
headers: {
"Content-Type": "application/json"
}
})
const data = await response.json()
revalidateTag("rooms")
return data.acknowledged
}
16 changes: 16 additions & 0 deletions frontend/src/app/actions/getUserFriend-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use server';
export default async function getUserFriend(username: string) {
const fetchFriend = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/users/${username}/friends`,
{
method: "GET",
cache: "force-cache",
next: { tags: ["friends"] },
headers: {
"Content-Type": "application/json",
},
}
);

return fetchFriend;
}
17 changes: 17 additions & 0 deletions frontend/src/app/actions/getUserRoom-action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use server';

export async function getUserRoom(id: string) {
const fetchRoom = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/rooms/${id}`,
{
method: "GET",
next: { tags: ["rooms"] },
cache: "force-cache",
headers: {
"Content-Type": "application/json",
},
}
);
return fetchRoom;
}

20 changes: 20 additions & 0 deletions frontend/src/app/context/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'

export const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
)
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
5 changes: 3 additions & 2 deletions frontend/src/app/context/getUserContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const UserContextProvider: React.FC<UserContextProviderProps> = ({
`${process.env.NEXT_PUBLIC_API_URL}/api/rooms/${user.id}`,
{
method: "GET",
cache: "reload",
cache: "force-cache",
headers: {
"Content-Type": "application/json",
},
Expand All @@ -54,7 +54,8 @@ export const UserContextProvider: React.FC<UserContextProviderProps> = ({
`${process.env.NEXT_PUBLIC_API_URL}/api/users/${user.username}/friends`,
{
method: "GET",
cache: "reload",
cache: "force-cache",

headers: {
"Content-Type": "application/json",
},
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/app/ui/chat/AddTalk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ export function AddMessagePopup({ username, addFriend }: { username: string, add
</button>

{isOpen && (
<div className="fixed inset-0 z-50 bg-black bg-opacity-50 z-100 flex items-center justify-center">
<div className="fixed inset-0 z-50 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-white rounded-lg p-8 max-w-md w-full">
<h2 className="text-2xl font-bold mb-4">Add by username</h2>

<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
Expand Down

0 comments on commit 8cc6cda

Please sign in to comment.