diff --git a/README.md b/README.md index a8d3593..20a7fff 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The Team Management App is a web-based application designed to facilitate team c ## Live Links - [Frontend](https://team-manager-eight.vercel.app) -- [Backend](https://team-management-app-server-with-redis.onrender.com) +- [Backend](https://api-team-manager.onrender.com) ###### Login to website diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fd28bf5..b2321ba 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,6 +26,7 @@ "react-icons": "^4.10.1", "react-redux": "^9.1.0", "react-select": "^5.8.0", + "socket.io-client": "^4.7.5", "sweetalert2": "^11.7.18", "swiper": "^11.0.5", "tailwind-scrollbar": "^3.0.5" @@ -1569,6 +1570,12 @@ "integrity": "sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==", "dev": true }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -2671,7 +2678,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2800,6 +2806,28 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -4811,8 +4839,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -6073,6 +6100,34 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -6996,6 +7051,35 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index bddb525..42934c8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,6 +27,7 @@ "react-icons": "^4.10.1", "react-redux": "^9.1.0", "react-select": "^5.8.0", + "socket.io-client": "^4.7.5", "sweetalert2": "^11.7.18", "swiper": "^11.0.5", "tailwind-scrollbar": "^3.0.5" diff --git a/frontend/src/components/pages/dashboard/invitation/PendingInvitation.tsx b/frontend/src/components/pages/dashboard/invitation/PendingInvitation.tsx index 561208f..8a6c980 100644 --- a/frontend/src/components/pages/dashboard/invitation/PendingInvitation.tsx +++ b/frontend/src/components/pages/dashboard/invitation/PendingInvitation.tsx @@ -27,7 +27,7 @@ const PendingInvitation = () => { memberId: user?.id, }); if (result?.data?.success) { - // socket.emit("notification", result?.data?.data); + socket.emit("notification", result?.data?.data); Swal.fire({ position: "center", icon: "success", @@ -45,7 +45,7 @@ const PendingInvitation = () => { }); if (result?.data?.success) { // send notification viw socket - // socket.emit("notification", result?.data?.data); + socket.emit("notification", result?.data?.data); Swal.fire({ position: "center", diff --git a/frontend/src/components/pages/dashboard/leaveRequest/ProjectLeaveRequest.tsx b/frontend/src/components/pages/dashboard/leaveRequest/ProjectLeaveRequest.tsx index 739d2b0..d3f2a4f 100644 --- a/frontend/src/components/pages/dashboard/leaveRequest/ProjectLeaveRequest.tsx +++ b/frontend/src/components/pages/dashboard/leaveRequest/ProjectLeaveRequest.tsx @@ -12,8 +12,6 @@ const ProjectLeaveRequest = () => { ); const requests = requestsData?.data; - console.log("project", requests); - return (
{requests?.length > 0 && diff --git a/frontend/src/components/pages/projects/desktop/AddMemberToProject.tsx b/frontend/src/components/pages/projects/desktop/AddMemberToProject.tsx index 5bf6055..71b94cb 100644 --- a/frontend/src/components/pages/projects/desktop/AddMemberToProject.tsx +++ b/frontend/src/components/pages/projects/desktop/AddMemberToProject.tsx @@ -40,7 +40,7 @@ const AddMemberToProject = ({ isOpen, setIsOpen, projectId, team }: Props) => { const result: any = await addNewMember(memberData); if (result?.data?.success) { - // socket.emit("notification", result?.data?.data); + socket.emit("notification", result?.data?.data); Swal.fire({ position: "center", icon: "success", diff --git a/frontend/src/components/pages/projects/desktop/ProjectPage.tsx b/frontend/src/components/pages/projects/desktop/ProjectPage.tsx index 8c2db5a..c4f3f27 100644 --- a/frontend/src/components/pages/projects/desktop/ProjectPage.tsx +++ b/frontend/src/components/pages/projects/desktop/ProjectPage.tsx @@ -34,8 +34,6 @@ const Projects = () => { user?.id ); - console.log("Project for task", project); - const handleLeaveRequest = async () => { Swal.fire({ title: "So sad", @@ -69,9 +67,9 @@ const Projects = () => { }, [query?.id]); // connect to socket team room - // useEffect(() => { - // socket.emit("task-room", project?.id); - // }, [socket, project?.id]); + useEffect(() => { + socket.emit("task-room", project?.id); + }, [socket, project?.id]); return (
diff --git a/frontend/src/components/pages/tasks/CreateTaskModal.tsx b/frontend/src/components/pages/tasks/CreateTaskModal.tsx index aabab65..eebe5e0 100644 --- a/frontend/src/components/pages/tasks/CreateTaskModal.tsx +++ b/frontend/src/components/pages/tasks/CreateTaskModal.tsx @@ -31,9 +31,9 @@ const CreateTaskModal = ({ isOpen, setIsOpen, project, status }: any) => { data.project = project.id; const result: any = await createTask(data); if (result?.data?.success) { - // window.location.reload(); - // socket.emit("task", result?.data?.data?.data); - // socket.emit("notification", result?.data?.data?.notification); + window.location.reload(); + socket.emit("task", result?.data?.data?.data); + socket.emit("notification", result?.data?.data?.notification); Swal.fire({ position: "center", icon: "success", diff --git a/frontend/src/components/pages/tasks/ParentTask.tsx b/frontend/src/components/pages/tasks/ParentTask.tsx index 52d9d46..aab0eec 100644 --- a/frontend/src/components/pages/tasks/ParentTask.tsx +++ b/frontend/src/components/pages/tasks/ParentTask.tsx @@ -94,12 +94,7 @@ const ParentTask = ({ project }: Props) => { // Fetch tasks only if project id is available if (project?.id) { fetch( - `https://api-team-manager.onrender.com/task/by-project/${project.id}`, - { - headers: { - authorization: token || "", - }, - } + `https://api-team-manager.onrender.com/task/by-project/${project.id}` ) .then((res) => res.json()) .then((data: any) => { @@ -163,16 +158,15 @@ const ParentTask = ({ project }: Props) => { }; // Listen for new tasks from socket - // useEffect(() => { - // socket.on("task", (newTask: any) => { - // console.log("New task data", newTask); - // addNewTask(newTask); - // }); - - // return () => { - // socket.off("task"); - // }; - // }, [socket]); + useEffect(() => { + socket.on("task", (newTask: any) => { + addNewTask(newTask); + }); + + return () => { + socket.off("task"); + }; + }, [socket]); return ( diff --git a/frontend/src/components/pages/teams/addMember/AddMemberModal.tsx b/frontend/src/components/pages/teams/addMember/AddMemberModal.tsx index ab7a42e..9b21974 100644 --- a/frontend/src/components/pages/teams/addMember/AddMemberModal.tsx +++ b/frontend/src/components/pages/teams/addMember/AddMemberModal.tsx @@ -53,7 +53,7 @@ const AddMemberModal = ({ isOpen, setIsOpen, team }: any) => { if (result?.data?.success) { closeModal(); // send invitation notification - // socket.emit("notification", result?.data?.data); + socket.emit("notification", result?.data?.data); Swal.fire({ position: "center", icon: "success", diff --git a/frontend/src/components/pages/teams/collaborations/common/MessageForm.tsx b/frontend/src/components/pages/teams/collaborations/common/MessageForm.tsx index 9275026..1db1d59 100644 --- a/frontend/src/components/pages/teams/collaborations/common/MessageForm.tsx +++ b/frontend/src/components/pages/teams/collaborations/common/MessageForm.tsx @@ -83,7 +83,7 @@ const MessageForm = ({ teamId, type }: Props) => { }; const emitData: IMessage = { ...message, poster }; - // socket.emit("message", emitData); + socket.emit("message", emitData); setRealTimeMessages((prev: IMessage[]) => [...prev, emitData]); setImagePreview([]); diff --git a/frontend/src/components/pages/teams/collaborations/common/ShowMessages.tsx b/frontend/src/components/pages/teams/collaborations/common/ShowMessages.tsx index 8d14eb2..62c4b0d 100644 --- a/frontend/src/components/pages/teams/collaborations/common/ShowMessages.tsx +++ b/frontend/src/components/pages/teams/collaborations/common/ShowMessages.tsx @@ -26,7 +26,6 @@ const ShowMessages = ({ messages }: Props) => { const [editedMessage, setEditedMessage] = useState(""); const [imageModalOpen, setImageModalOpen] = useState(false); const [selectedImage, setSelectedImage] = useState(""); - const [onlineUsers, setOnlineUsers] = useState({}); const [isEditMessage, setIsEditMessage] = useState<{ id: string | undefined; status: boolean; @@ -67,30 +66,30 @@ const ShowMessages = ({ messages }: Props) => { } }; - // useEffect(() => { - // const handleMessage = (data: IMessage) => { - // setRealTimeMessages((prev: IMessage[]) => [...prev, data]); - // }; + useEffect(() => { + const handleMessage = (data: IMessage) => { + setRealTimeMessages((prev: IMessage[]) => [...prev, data]); + }; - // socket.on("message", handleMessage); + socket?.on("message", handleMessage); - // return () => { - // socket.off("message", handleMessage); - // }; - // }, [setRealTimeMessages, socket]); + return () => { + socket?.off("message", handleMessage); + }; + }, [setRealTimeMessages, socket]); // keep updated message in state - // useEffect(() => { - // setRealTimeMessages(messages); - // }, [messages, setRealTimeMessages]); + useEffect(() => { + setRealTimeMessages(messages); + }, [messages, setRealTimeMessages]); // keep user in the bottom of the message - // useEffect(() => { - // if (messagesContainerRef.current) { - // messagesContainerRef.current.scrollTop = - // messagesContainerRef.current.scrollHeight; - // } - // }, [realTimeMessages, socket]); + useEffect(() => { + if (messagesContainerRef.current) { + messagesContainerRef.current.scrollTop = + messagesContainerRef.current.scrollHeight; + } + }, [realTimeMessages, socket]); return (
{ }; // connect to socket team room - // useEffect(() => { - // socket.emit("join-room", team?.id); - // }, [socket, team?.id]); + useEffect(() => { + socket?.emit("join-room", team?.id); + }, [socket, team?.id]); useEffect(() => { setActiveNav(router?.query?.collaborate); diff --git a/frontend/src/components/shared/Navbar.tsx b/frontend/src/components/shared/Navbar.tsx index ab2c705..bde9768 100644 --- a/frontend/src/components/shared/Navbar.tsx +++ b/frontend/src/components/shared/Navbar.tsx @@ -18,7 +18,6 @@ const Navbar = () => { const { theme, setTheme } = useTheme(); const { data }: any = useLoggedInUserQuery({}); const user: IUser = data?.data; - console.log("User from navbar", user); const [isOpen, setIsOpen] = useState(false); const [toggle, setToggle] = useState(false); const { data: notifiedData } = useGetNotificationQuery(user?.id); @@ -32,16 +31,15 @@ const Navbar = () => { window.location.replace("/"); }; - // useEffect(() => { - // const handleNotification = (data: INotification) => { - // console.log("New notification", data); - // setUnreadNotifications((prev: INotification[]) => [...prev, data]); - // }; - // socket.on("notification", handleNotification); - // return () => { - // socket.off("notification", handleNotification); - // }; - // }, [socket]); + useEffect(() => { + const handleNotification = (data: INotification) => { + setUnreadNotifications((prev: INotification[]) => [...prev, data]); + }; + socket?.on("notification", handleNotification); + return () => { + socket?.off("notification", handleNotification); + }; + }, [socket]); useEffect(() => { const unread = notifications.filter((notified) => !notified.read); diff --git a/frontend/src/context/SocketContext.tsx b/frontend/src/context/SocketContext.tsx index 157fba6..914c927 100644 --- a/frontend/src/context/SocketContext.tsx +++ b/frontend/src/context/SocketContext.tsx @@ -1,14 +1,14 @@ -import useGetLoggedInUser from "@/hooks/useGetLoggedInUser"; import { IContext } from "@/interfaces/context.interface"; import { IMessage } from "@/interfaces/message.interface"; -import { IUser } from "@/interfaces/user.interface"; import { ReactNode, createContext, useEffect, useState } from "react"; +import { io } from "socket.io-client"; const initValues: IContext = { realTimeMessages: [], setRealTimeMessages: (messages: IMessage[]) => {}, refetchTask: false, setRefetchTask: () => false, + socket: "", }; export const SocketContext = createContext(initValues); @@ -18,35 +18,42 @@ type Props = { }; const SocketProvider = ({ children }: Props) => { - const socket: any = {}; + const socket: any = io("https://api-team-manager.onrender.com"); const [realTimeMessages, setRealTimeMessages] = useState([]); + const [user, setUser] = useState({}); const [refetchTask, setRefetchTask] = useState(false); - const user: IUser = useGetLoggedInUser(); - const [activeUsers, setActiveUsers] = useState([]); - - // connect to socket notification room - // useEffect(() => { - // socket.emit("notification-room", user?.id); - // }, [socket, user?.id]); - - // // connect to socket active - // useEffect(() => { - // socket.emit("active", user?.id); - // }, [socket, user?.id]); - - // // connect to socket active - // useEffect(() => { - // socket.on("activeUsers", (data: any) => { - // setActiveUsers(data); - // console.log("Active users", data); - // }); - // }, [socket]); + + useEffect(() => { + const fetchUser = async () => { + try { + const res = await fetch( + "https://api-team-manager.onrender.com/user/auth", + { + method: "GET", + credentials: "include", + } + ); + const data = await res.json(); + setUser(data?.data); + } catch (error) { + console.log("Failed to fetch user"); + } + }; + fetchUser(); + }, []); + + useEffect(() => { + if (user?.id) { + socket.emit("notification-room", user.id); + } + }, [user]); const values: IContext = { realTimeMessages, setRealTimeMessages, refetchTask, setRefetchTask, + socket, }; return ( diff --git a/frontend/src/hooks/useGetLoggedInUser.ts b/frontend/src/hooks/useGetLoggedInUser.ts deleted file mode 100644 index a2e3743..0000000 --- a/frontend/src/hooks/useGetLoggedInUser.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useEffect, useState } from "react"; -import { IUser, userInitData } from "@/interfaces/user.interface"; - -const useGetLoggedInUser = () => { - const [user, setUser] = useState(userInitData); - useEffect(() => { - const fetchUser = async () => { - try { - const res = await fetch( - "https://api-team-manager.onrender.com/user/auth", - { - method: "GET", - credentials: "include", - } - ); - const data = await res.json(); - console.log("User from useGetLoggedInUser hook", data); - setUser(data?.data); - } catch (error) { - console.log("Failed to fetch user"); - } - console.log("Will call"); - }; - fetchUser(); - }, []); - - return user; -}; - -export default useGetLoggedInUser; diff --git a/frontend/src/interfaces/context.interface.ts b/frontend/src/interfaces/context.interface.ts index e0202ef..878fb43 100644 --- a/frontend/src/interfaces/context.interface.ts +++ b/frontend/src/interfaces/context.interface.ts @@ -5,4 +5,5 @@ export type IContext = { setRealTimeMessages: (messages: IMessage[]) => void; refetchTask: false; setRefetchTask: (status: boolean) => void; + socket: any; };