Skip to content

Commit

Permalink
fix(chat): users should be able to receive messages in real time
Browse files Browse the repository at this point in the history
  • Loading branch information
Heisjabo committed Jul 30, 2024
1 parent 968b444 commit f674ae7
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 46 deletions.
6 changes: 3 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ const App: React.FC = () => {
{location.pathname !== "/chat"
&& location.pathname !== "/login"
&& location.pathname !== "/register" && (
<Link to="/chat">
<div className="fixed bg-primary text-white shadow-md rounded px-3 py-3 z-50 right-6 bottom-6 cursor-pointer group">
<div onClick={() => (window.location.href = "/chat")}>
<div className="fixed bg-[#DB4444] text-white shadow-md rounded px-3 py-3 z-50 right-6 bottom-6 cursor-pointer group">
<IoChatbubbleEllipsesOutline className="text-[30px] text-white" />
</div>
</Link>
</div>
)}
</main>
);
Expand Down
5 changes: 5 additions & 0 deletions src/__test__/addProducts.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ describe("FileUpload component", () => {
new File(["dummy content"], "example.png", { type: "image/png" }),
];

jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useParams: jest.fn(),
}));

beforeEach(() => {
(useDropzone as jest.Mock).mockImplementation(() => ({
getRootProps: jest.fn(() => ({ onClick: () => {} })),
Expand Down
33 changes: 33 additions & 0 deletions src/__test__/productcard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,39 @@ describe("ProductCard Component", () => {
// expect(addToCartButton).toBeDefined();
});

test("renders rating and review count correctly", () => {
render(
<Provider store={store}>
<BrowserRouter>
<ProductCard product={product} />
</BrowserRouter>
</Provider>,
);

const rating = screen.getByTestId("rating");
const reviewCount = screen.getByTestId("review");

expect(rating).toBeDefined();
expect(reviewCount).toBeDefined();
});

test("truncates long product names", () => {
const longNameProduct = {
...product,
name: "This is a very long product name that should be truncated",
};
render(
<Provider store={store}>
<BrowserRouter>
<ProductCard product={longNameProduct} />
</BrowserRouter>
</Provider>,
);

const productName = screen.getByTestId("product-name");
expect(productName.textContent).toHaveLength(15);
});

test("renders wishlist and view details buttons", () => {
render(
<Provider store={store}>
Expand Down
9 changes: 9 additions & 0 deletions src/__test__/userList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import MockAdapter from "axios-mock-adapter";
import UserList from "../page-sections/UserList";
import chatSlice from "../redux/reducers/chatSlice";
import api from "../redux/api/api";
import { socket } from "../config/socket";

const mockApi = new MockAdapter(api);

jest.mock("../config/socket", () => ({
socket: {
on: jest.fn(),
off: jest.fn(),
},
}));

const mockStore = (initialState) =>
configureStore({
reducer: {
Expand Down
38 changes: 20 additions & 18 deletions src/components/dashboard/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FaAngleDown } from "react-icons/fa6";
import { BiSolidMessageDetail } from "react-icons/bi";
import React, { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";

import { getProfile } from "../../redux/reducers/profileSlice";
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
import { RootState } from "../../redux/store";
Expand Down Expand Up @@ -52,12 +53,10 @@ const Header: React.FC<HeaderProps> = ({ toggleSidebar }) => {

return (
<>
<header className="flex lg:w-[80%] lg:ml-[5%] px-8 fixed z-30 top-0 items-center w-full justify-between py-4 bg-white dark:bg-secondary-black">
<header className="flex lg:w-[80%] lg:ml-[5%] px-8 fixed z-30 top-0 items-center w-full justify-between py-4 bg-white">
<div className="relative flex items-center justify-between w-full lg:hidden">
<FiMenu className="text-black w-6 h-6 mr-3" onClick={toggleSidebar} />
<div className="flex items-center gap-4">

</div>
<div className="flex items-center gap-4" />
</div>
<div className="flex items-center justify-end lg:justify-between w-full">
<div className="hidden lg:block relative">
Expand All @@ -67,7 +66,7 @@ const Header: React.FC<HeaderProps> = ({ toggleSidebar }) => {
<input
type="text"
placeholder="Search..."
className="pl-10 pr-10 py-2 rounded-md dark:border-[0.3px] outline-none bg-[#F7F8FA] focus:outline-none"
className="pl-10 pr-10 py-2 rounded-md border-[0.3px] outline-none bg-[#F7F8FA] focus:outline-none"
/>
</div>
<div className="flex items-center gap-6">
Expand All @@ -82,19 +81,22 @@ const Header: React.FC<HeaderProps> = ({ toggleSidebar }) => {
</div>

<div
className="flex items-center space-x-1 h-full relative cursor-pointer"
ref={profileDropdownRef}
>
<img
src={profileImage}
alt="User Avatar"
className="w-10 h-10 object-cover rounded-full"
onClick={() => setShowDropdown(!showDropdown)}
/>
<h4>{profile?.fullName}</h4>
<FaAngleDown className="hidden lg:block" onClick={() => setShowDropdown(!showDropdown)} />
{showDropdown && <ProfileDropdown userInfo={userInfo} />}
</div>
className="flex items-center space-x-1 h-full relative cursor-pointer"
ref={profileDropdownRef}
>
<img
src={profileImage}
alt="User Avatar"
className="w-10 h-10 object-cover rounded-full"
onClick={() => setShowDropdown(!showDropdown)}
/>
<h4>{profile?.fullName}</h4>
<FaAngleDown
className="hidden lg:block"
onClick={() => setShowDropdown(!showDropdown)}
/>
{showDropdown && <ProfileDropdown userInfo={userInfo} />}
</div>
</div>
</div>
</header>
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const SideBar: React.FC<SidebarProps> = ({ isOpen }) => {
isOpen ? "translate-x-0" : "-translate-x-full"
} lg:translate-x-0`}
>
<div className="flex flex-col justify-between py-6 dark:bg-secondary-black min-h-screen">
<div className="flex flex-col justify-between py-6 min-h-screen">
<div className="flex flex-col justify-center items-center">
<Link to="/">
<div className="text-black font-bold justify-center py-2 flex items-center gap-1">
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const Spinner = () => (
<span className="inline-block dark:text-white">
<span className="inline-block">
<svg
width="20"
height="20"
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/admin/AdminSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const AdminSideBar: React.FC<SidebarProps> = ({ isOpen }) => {
isOpen ? "translate-x-0" : "-translate-x-full"
} lg:translate-x-0`}
>
<div className="flex flex-col justify-between min-h-screen py-6 dark:bg-secondary-black">
<div className="flex flex-col justify-between min-h-screen py-6">
<div className="flex flex-col items-center justify-center">
<div className="flex items-center justify-center gap-1 py-2 font-bold text-black">
<span className="font-[550] text-2xl">eagles</span>
Expand Down
5 changes: 1 addition & 4 deletions src/components/dashboard/products/ProductsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,7 @@ const ProductsTable: React.FC = () => {
</tr>
) : products.length === 0 ? (
<tr>
<td
colSpan={6}
className="text-center py-10 text-dark-gray dark:text-white"
>
<td colSpan={6} className="text-center py-10 text-dark-gray">
No products found.
</td>
</tr>
Expand Down
16 changes: 14 additions & 2 deletions src/page-sections/UserList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HiMiniUserGroup } from "react-icons/hi2";
import { useAppDispatch } from "../redux/hooks";
import Spinner from "../components/dashboard/Spinner";
import { fetchChats, fetchUsers } from "../redux/reducers/chatSlice";
import { socket } from "../config/socket";

interface UserListProps {
onUserSelect: (chat: any | null, user: any) => void;
Expand Down Expand Up @@ -38,6 +39,17 @@ const UserList: React.FC<UserListProps> = ({
useEffect(() => {
dispatch(fetchChats());
dispatch(fetchUsers());

const handlePrivateMessage = () => {
dispatch(fetchChats());
dispatch(fetchUsers());
};

socket.on("private message recieved", handlePrivateMessage);

return () => {
socket.off("private message recieved", handlePrivateMessage);
};
}, [dispatch]);

useEffect(() => {
Expand Down Expand Up @@ -114,7 +126,7 @@ const UserList: React.FC<UserListProps> = ({
<input
type="text"
placeholder="Search..."
className="px-10 py-2 rounded-md dark:border-[1px] text-gray-500 w-full outline-none bg-bg-gray dark:bg-transparent dark:border-dark-gray focus:outline-none"
className="px-10 py-2 rounded-md border-[1px] text-gray-500 w-full outline-none bg-bg-gray bg-transparent border-dark-gray focus:outline-none"
value={searchTerm}
onChange={handleSearchChange}
/>
Expand Down Expand Up @@ -193,7 +205,7 @@ const UserList: React.FC<UserListProps> = ({
{chat.messages.length > 0
? chat.messages[
chat.messages.length - 1
].message.substring(0, 20)
].message.substring(0, 15)
: "No messages yet"}
...
</p>
Expand Down
48 changes: 32 additions & 16 deletions src/pages/ChatPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from "react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { MdSportsGolf } from "react-icons/md";

import UserList from "../page-sections/UserList";
import ChatWindow from "../page-sections/ChatWindow";
Expand Down Expand Up @@ -31,8 +30,20 @@ const ChatPage: React.FC = () => {
setRefetch(true);
};

const handlePastMessages = (pastMessages: any[]) => {
setPublicMessages(
pastMessages.map((msg) => ({
isOwner: msg.sender === profile?.name,
message: msg.message,
sender: msg.sender,
createdAt: msg.createdAt,
})),
);
};

useEffect(() => {
dispatch(fetchUser());
socket.on("past messages", handlePastMessages);
}, [dispatch]);

useEffect(() => {
Expand All @@ -43,32 +54,31 @@ const ChatPage: React.FC = () => {
}, []);

useEffect(() => {
const handleConnect = () => {
socket.emit("private chats");
};
socket.on("past messages", handlePastMessages);
}, [isPublicChat]);

useEffect(() => {
const handleChatMessage = (msg: any) => {
if (!publicMessageIds.current.has(msg.id)) {
setPublicMessages((prevMessages) => [...prevMessages, msg]);
publicMessageIds.current.add(msg.id);
}
};
socket.on("chat message", handleChatMessage);
socket.on("past messages", handlePastMessages);

const handlePastMessages = (pastMessages: any[]) => {
setPublicMessages(
pastMessages.map((msg) => ({
isOwner: msg.sender === profile?.name,
message: msg.message,
sender: msg.sender,
createdAt: msg.createdAt,
})),
);
return () => {
socket.off("chat message", handleChatMessage);
socket.off("past messages", handlePastMessages);
};
}, [publicMessageIds, selectedChat]);

useEffect(() => {
const handleConnect = () => {
socket.emit("private chats");
};
socket.on("connect", handleConnect);
socket.on("chat message", handleChatMessage);
socket.on("past messages", handlePastMessages);
}, [publicMessages]);
}, [selectedChat]);

useEffect(() => {
const handlePrivateMessage = (message: any) => {
Expand Down Expand Up @@ -191,6 +201,12 @@ const ChatPage: React.FC = () => {
}));

try {
socket.emit("private chat message", {
sender: profile?.username,
userId: profile?.id,
receiverId: newChatUser.id,
message: text,
});
await dispatch(
sendMessage({ message: text, id: newChatUser.id }),
).unwrap();
Expand Down

0 comments on commit f674ae7

Please sign in to comment.