From a8fd4bfc17b9ef4233437f8ba14b597d401a5412 Mon Sep 17 00:00:00 2001 From: Dipanita45 <132455672+Dipanita45@users.noreply.github.com> Date: Sun, 3 May 2026 10:55:25 +0530 Subject: [PATCH 1/3] Updated --- frontend/app/dashboard/page.tsx | 167 ++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 49 deletions(-) diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 4cf6392..ba36950 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -1,5 +1,13 @@ import Link from "next/link"; -import { Activity, FolderOpen, MessageSquare, ShieldCheck, Sparkles } from "lucide-react"; +import { + Activity, + FolderOpen, + MessageSquare, + ShieldCheck, + Sparkles, + TrendingUp, + Clock, +} from "lucide-react"; const links = [ { @@ -27,7 +35,7 @@ const stats = [ title: "Velocity", value: "+18%", detail: "Growth over last sprint", - icon: Activity, + icon: TrendingUp, }, { title: "Deploys", @@ -44,109 +52,170 @@ const stats = [ ]; const activity = [ - "Sprint planning started for Q2 roadmap.", - "New design review added to Marketing campaign.", - "Standup summary posted in team chat.", + { + text: "Sprint planning started for Q2 roadmap.", + time: "2h ago", + }, + { + text: "New design review added to Marketing campaign.", + time: "5h ago", + }, + { + text: "Standup summary posted in team chat.", + time: "Today", + }, ]; export default function Dashboard() { return (
-
+ + {/* HEADER */} +

Command Center

-

+

Dashboard

-

- Get a quick pulse on your team, upcoming work, and the most important projects right now. +

+ Monitor performance, manage projects, and stay in sync with your team.

-
- - New Sprint Plan +
+ + + New Sprint - - View Workspace + + Workspace
-
+ {/* STATS */} +
{stats.map((stat) => { const Icon = stat.icon; return ( -
-
+
+
-

- {stat.title} +

{stat.title}

+

+ {stat.value}

-

{stat.value}

-
- +
+
-

{stat.detail}

+ + {/* Progress bar (NEW) */} +
+
+
+ +

{stat.detail}

); })}
-
-
+ {/* MAIN GRID */} +
+ + {/* QUICK ACTIONS */} +
{links.map((item) => { const Icon = item.icon; return ( -
- +
+
+ +
+
+

+ {item.label} +

+

{item.desc}

+
-
-

{item.label}

-

{item.desc}

-
-

Open

); })}
-
-
+ {/* ACTIVITY */} +
+
-

- Recent activity -

-

What’s happening

+

Activity

+

+ Recent Updates +

- - Live updates - +
-
- {activity.map((item) => ( -
- {item} + {/* Timeline style */} +
+ {activity.map((item, i) => ( +
+
+
+

{item.text}

+

{item.time}

+
))}
- + - Open team chat + Go to chat + +
+
+ + {/* EXTRA SECTION (NEW) */} +
+
+
+

+ Boost your team productivity πŸš€ +

+

+ Plan better sprints and collaborate smarter with your team. +

+
+ + + Start Planning
); -} +} \ No newline at end of file From 9a3e00dc7939bd96e616250deaa6edce675b5091 Mon Sep 17 00:00:00 2001 From: Dipanita45 <132455672+Dipanita45@users.noreply.github.com> Date: Sun, 3 May 2026 11:07:27 +0530 Subject: [PATCH 2/3] Updated --- .../controllers/notification.controller.js | 21 ++++++++++++++++ backend/models/notification.model.js | 16 +++++++++++++ backend/routes/notification.routes.js | 7 ++++++ backend/services/socket.js | 23 ++++++++++++++++++ .../app/components/NotificationListener.tsx | 17 +++++++++++++ frontend/app/components/NotificationPanel.tsx | 24 +++++++++++++++++++ frontend/src/api/notification.ts | 7 ++++++ frontend/src/context/SocketContext.tsx | 19 +++++++++++++++ 8 files changed, 134 insertions(+) create mode 100644 backend/controllers/notification.controller.js create mode 100644 backend/models/notification.model.js create mode 100644 backend/routes/notification.routes.js create mode 100644 backend/services/socket.js create mode 100644 frontend/app/components/NotificationListener.tsx create mode 100644 frontend/app/components/NotificationPanel.tsx create mode 100644 frontend/src/api/notification.ts create mode 100644 frontend/src/context/SocketContext.tsx diff --git a/backend/controllers/notification.controller.js b/backend/controllers/notification.controller.js new file mode 100644 index 0000000..a778174 --- /dev/null +++ b/backend/controllers/notification.controller.js @@ -0,0 +1,21 @@ +const Notification = require("../models/notification.model"); + +exports.getNotifications = async (req, res) => { + try { + const { userId } = req.params; + const notifications = await Notification.find({ userId }).sort({ createdAt: -1 }); + res.json(notifications); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}; + +exports.markAsRead = async (req, res) => { + try { + const { id } = req.params; + await Notification.findByIdAndUpdate(id, { isRead: true }); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}; \ No newline at end of file diff --git a/backend/models/notification.model.js b/backend/models/notification.model.js new file mode 100644 index 0000000..05d55c0 --- /dev/null +++ b/backend/models/notification.model.js @@ -0,0 +1,16 @@ +const mongoose = require("mongoose"); + +const notificationSchema = new mongoose.Schema( + { + userId: { type: String, required: true }, + type: { + type: String, + enum: ["message", "like", "comment", "request", "system"], + }, + content: String, + isRead: { type: Boolean, default: false }, + }, + { timestamps: true } +); + +module.exports = mongoose.model("Notification", notificationSchema); \ No newline at end of file diff --git a/backend/routes/notification.routes.js b/backend/routes/notification.routes.js new file mode 100644 index 0000000..f55fde6 --- /dev/null +++ b/backend/routes/notification.routes.js @@ -0,0 +1,7 @@ +const router = require("express").Router(); +const controller = require("../controllers/notification.controller"); + +router.get("/:userId", controller.getNotifications); +router.patch("/:id/read", controller.markAsRead); + +module.exports = router; \ No newline at end of file diff --git a/backend/services/socket.js b/backend/services/socket.js new file mode 100644 index 0000000..254980a --- /dev/null +++ b/backend/services/socket.js @@ -0,0 +1,23 @@ +const { Server } = require("socket.io"); + +let io; + +const initSocket = (server) => { + io = new Server(server, { + cors: { origin: "*" }, + }); + + io.on("connection", (socket) => { + console.log("User connected:", socket.id); + + socket.on("join", (userId) => { + socket.join(userId); + }); + }); +}; + +const sendNotification = (userId, notification) => { + io.to(userId).emit("new_notification", notification); +}; + +module.exports = { initSocket, sendNotification }; \ No newline at end of file diff --git a/frontend/app/components/NotificationListener.tsx b/frontend/app/components/NotificationListener.tsx new file mode 100644 index 0000000..10b6bc7 --- /dev/null +++ b/frontend/app/components/NotificationListener.tsx @@ -0,0 +1,17 @@ +import { useEffect, useContext } from "react"; +import toast from "react-hot-toast"; +import { SocketContext } from "../context/SocketContext"; + +export default function NotificationListener() { + const socket = useContext(SocketContext); + + useEffect(() => { + socket.on("new_notification", (data: any) => { + toast.success(data.content); + }); + + return () => socket.off("new_notification"); + }, []); + + return null; +} \ No newline at end of file diff --git a/frontend/app/components/NotificationPanel.tsx b/frontend/app/components/NotificationPanel.tsx new file mode 100644 index 0000000..2a08370 --- /dev/null +++ b/frontend/app/components/NotificationPanel.tsx @@ -0,0 +1,24 @@ +import { markAsRead } from "../api/notification"; + +export default function NotificationPanel({ notifications, refresh }: any) { + const handleRead = async (id: string) => { + await markAsRead(id); + refresh(); + }; + + return ( +
+ {notifications.map((n: any) => ( +
handleRead(n._id)} + > + {n.content} +
+ ))} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/api/notification.ts b/frontend/src/api/notification.ts new file mode 100644 index 0000000..a41bd4e --- /dev/null +++ b/frontend/src/api/notification.ts @@ -0,0 +1,7 @@ +import axios from "axios"; + +export const getNotifications = (userId: string) => + axios.get(`/api/notifications/${userId}`); + +export const markAsRead = (id: string) => + axios.patch(`/api/notifications/${id}/read`); \ No newline at end of file diff --git a/frontend/src/context/SocketContext.tsx b/frontend/src/context/SocketContext.tsx new file mode 100644 index 0000000..618791e --- /dev/null +++ b/frontend/src/context/SocketContext.tsx @@ -0,0 +1,19 @@ +import { createContext, useEffect } from "react"; +import { io } from "socket.io-client"; + +const socket = io("http://localhost:5000"); + +export const SocketContext = createContext(socket); + +export const SocketProvider = ({ children }: any) => { + useEffect(() => { + const userId = localStorage.getItem("userId"); + if (userId) socket.emit("join", userId); + }, []); + + return ( + + {children} + + ); +}; \ No newline at end of file From bace6412b636161c0c44c2af3ae748ef37d60512 Mon Sep 17 00:00:00 2001 From: Dipanita45 <132455672+Dipanita45@users.noreply.github.com> Date: Tue, 5 May 2026 20:36:20 +0530 Subject: [PATCH 3/3] Updated --- frontend/app/chat/page.tsx | 297 ++++++++++++++++++ .../app/components/NotificationListener.tsx | 17 - frontend/app/components/NotificationPanel.tsx | 24 -- frontend/src/api/notification.ts | 7 - frontend/src/context/SocketContext.tsx | 19 -- 5 files changed, 297 insertions(+), 67 deletions(-) delete mode 100644 frontend/app/components/NotificationListener.tsx delete mode 100644 frontend/app/components/NotificationPanel.tsx delete mode 100644 frontend/src/api/notification.ts delete mode 100644 frontend/src/context/SocketContext.tsx diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index 6921153..35bbb5e 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -263,4 +263,301 @@ socket.on("newMessage", (msg) => {
); +}"use client"; + +import { useState, useEffect, useRef } from "react"; +import { io, Socket } from "socket.io-client"; +import { motion } from "framer-motion"; + +type Message = { + id?: string; + user: string; + text: string; + time?: string; + status?: string; +}; + +export default function ChatPage() { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [username, setUsername] = useState(""); + const [typingUser, setTypingUser] = useState(""); + const [onlineUsers, setOnlineUsers] = useState([]); + const [unreadCount, setUnreadCount] = useState(0); + + const socketRef = useRef(null); + const typingTimeoutRef = useRef(null); + + const containerRef = useRef(null); + const bottomRef = useRef(null); + const isAtBottomRef = useRef(true); + + const audioRef = useRef(null); + + // SOCKET SETUP + useEffect(() => { + const socket = io("http://localhost:5000"); + socketRef.current = socket; + + audioRef.current = new Audio("/ping.mp3"); + + // FETCH OLD MESSAGES + fetch("http://localhost:5000/api/chat") + .then((res) => res.json()) + .then((data) => { + const formatted = data.map((msg: any) => ({ + id: msg.id, + user: msg.username, + text: msg.text, + time: new Date(msg.created_at).toLocaleTimeString(), + status: msg.status || "sent", + })); + setMessages(formatted); + }); + + // NEW MESSAGE + socket.on("newMessage", (msg) => { + setMessages((prev) => { + const exists = prev.some((m) => m.id === msg.id); + if (exists) return prev; + + return [ + ...prev, + { + id: msg.id, + user: msg.username, + text: msg.text, + time: new Date(msg.created_at).toLocaleTimeString(), + status: msg.status || "sent", + }, + ]; + }); + + if (!isAtBottomRef.current) { + setUnreadCount((prev) => prev + 1); + audioRef.current?.play().catch(() => {}); + } + }); + + // TYPING + socket.on("typing", (user) => { + setTypingUser(user); + + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + + typingTimeoutRef.current = setTimeout(() => { + setTypingUser(""); + }, 1500); + }); + + // ONLINE USERS + socket.on("onlineUsers", (users) => { + setOnlineUsers(users); + }); + + // SEEN + socket.on("messageSeen", (messageId) => { + setMessages((prev) => + prev.map((msg) => + msg.id === messageId ? { ...msg, status: "seen" } : msg + ) + ); + }); + + return () => socket.disconnect(); + }, []); + + // JOIN USER + useEffect(() => { + if (socketRef.current && username.trim()) { + socketRef.current.emit("join", username); + } + }, [username]); + + // AUTO SCROLL + useEffect(() => { + if (isAtBottomRef.current) { + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + } + }, [messages]); + + // MARK LAST MESSAGE AS SEEN + useEffect(() => { + if (!socketRef.current) return; + const lastMsg = messages[messages.length - 1]; + if (lastMsg?.id) { + socketRef.current.emit("seen", lastMsg.id); + } + }, [messages]); + + // SEND MESSAGE (Optimistic UI) + async function handleSend() { + if (!input.trim() || !username.trim()) return; + + const tempId = Date.now().toString(); + + setMessages((prev) => [ + ...prev, + { + id: tempId, + user: username, + text: input, + time: new Date().toLocaleTimeString(), + status: "sending", + }, + ]); + + setInput(""); + + try { + await fetch("http://localhost:5000/api/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ text: input, username }), + }); + } catch (err) { + console.error(err); + } + } + + // TYPING + function handleTyping(e: any) { + setInput(e.target.value); + + if (socketRef.current && username.trim()) { + socketRef.current.emit("typing", username); + } + } + + return ( +
+ {/* HEADER */} +
+

Team Chat

+ + setUsername(e.target.value)} + placeholder="Enter name" + className="mb-2 p-2 border rounded w-full" + /> + +
+ Online: {onlineUsers.join(", ")} +
+
+ + {/* CHAT BOX */} +
{ + const el = containerRef.current; + if (!el) return; + + const atBottom = + el.scrollHeight - el.scrollTop - el.clientHeight < 100; + + isAtBottomRef.current = atBottom; + + if (atBottom) setUnreadCount(0); + }} + className="h-[60vh] md:h-[400px] overflow-y-auto space-y-2 border p-3 bg-gray-50 rounded" + > + {messages.map((msg, i) => { + const prevMsg = messages[i - 1]; + const isSameUser = prevMsg && prevMsg.user === msg.user; + const isMe = msg.user === username; + + return ( + +
+ {/* Avatar + Name */} + {!isMe && !isSameUser && ( +
+
+ {msg.user[0]?.toUpperCase()} +
+

{msg.user}

+
+ )} + +

{msg.text}

+ + {/* STATUS */} +

+ {msg.time} + {msg.status === "sending" && "πŸ•“"} + {msg.status === "sent" && "βœ“"} + {msg.status === "seen" && ( + βœ“βœ“ + )} +

+
+
+ ); + })} + +
+
+ + {/* UNREAD BUTTON */} + {unreadCount > 0 && ( +
+ +
+ )} + + {/* TYPING INDICATOR */} + {typingUser && typingUser !== username && ( +
+ {typingUser} typing + . + . + . +
+ )} + + {/* INPUT */} +
+ e.key === "Enter" && handleSend()} + placeholder="Type message" + className="flex-1 border p-2 rounded" + /> + +
+
+ ); } \ No newline at end of file diff --git a/frontend/app/components/NotificationListener.tsx b/frontend/app/components/NotificationListener.tsx deleted file mode 100644 index 10b6bc7..0000000 --- a/frontend/app/components/NotificationListener.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useContext } from "react"; -import toast from "react-hot-toast"; -import { SocketContext } from "../context/SocketContext"; - -export default function NotificationListener() { - const socket = useContext(SocketContext); - - useEffect(() => { - socket.on("new_notification", (data: any) => { - toast.success(data.content); - }); - - return () => socket.off("new_notification"); - }, []); - - return null; -} \ No newline at end of file diff --git a/frontend/app/components/NotificationPanel.tsx b/frontend/app/components/NotificationPanel.tsx deleted file mode 100644 index 2a08370..0000000 --- a/frontend/app/components/NotificationPanel.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { markAsRead } from "../api/notification"; - -export default function NotificationPanel({ notifications, refresh }: any) { - const handleRead = async (id: string) => { - await markAsRead(id); - refresh(); - }; - - return ( -
- {notifications.map((n: any) => ( -
handleRead(n._id)} - > - {n.content} -
- ))} -
- ); -} \ No newline at end of file diff --git a/frontend/src/api/notification.ts b/frontend/src/api/notification.ts deleted file mode 100644 index a41bd4e..0000000 --- a/frontend/src/api/notification.ts +++ /dev/null @@ -1,7 +0,0 @@ -import axios from "axios"; - -export const getNotifications = (userId: string) => - axios.get(`/api/notifications/${userId}`); - -export const markAsRead = (id: string) => - axios.patch(`/api/notifications/${id}/read`); \ No newline at end of file diff --git a/frontend/src/context/SocketContext.tsx b/frontend/src/context/SocketContext.tsx deleted file mode 100644 index 618791e..0000000 --- a/frontend/src/context/SocketContext.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext, useEffect } from "react"; -import { io } from "socket.io-client"; - -const socket = io("http://localhost:5000"); - -export const SocketContext = createContext(socket); - -export const SocketProvider = ({ children }: any) => { - useEffect(() => { - const userId = localStorage.getItem("userId"); - if (userId) socket.emit("join", userId); - }, []); - - return ( - - {children} - - ); -}; \ No newline at end of file