From 2fcb8e1b6832cb5d0a2876b08a96c0d86544b253 Mon Sep 17 00:00:00 2001 From: sumona-banerjeee Date: Mon, 20 Apr 2026 17:47:19 +0530 Subject: [PATCH] added the p2pinvite for the starts a chat --- src/app/chat/page.tsx | 365 +++++++++++++++++++--------------- src/context/SocketContext.tsx | 22 ++ 2 files changed, 228 insertions(+), 159 deletions(-) diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index ead4a86..9508d34 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -3,7 +3,11 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/context/AuthContext"; -import { useSocket, type ChatMessage, type RoomNotice } from "@/context/SocketContext"; +import { + useSocket, + type ChatMessage, + type RoomNotice, +} from "@/context/SocketContext"; import { fetchConversations, createConversation, @@ -55,10 +59,12 @@ export default function ChatPage() { p2pReady, setMessages, clearP2PReady, + p2pInvite, + clearP2PInvite, } = useSocket(); const [text, setText] = useState(""); - const [roomSearch, setRoomSearch] = useState(""); + // const [roomSearch, setRoomSearch] = useState(""); const [userSearch, setUserSearch] = useState(""); const [activeTab, setActiveTab] = useState<"rooms" | "users">("rooms"); const [showCreateDropdown, setShowCreateDropdown] = useState(false); @@ -81,7 +87,7 @@ export default function ChatPage() { return debounced; }; - const debouncedRoomSearch = useDebounce(roomSearch, 300); + // const debouncedRoomSearch = useDebounce(roomSearch, 300); const debouncedUserSearch = useDebounce(userSearch, 300); // Auth guard @@ -99,33 +105,33 @@ export default function ChatPage() { .then((data) => { const loadedRooms = Array.isArray(data) ? data.map((c) => { - const isGroup = c.type === "group"; - let roomName = c.name; + const isGroup = c.type === "group"; + let roomName = c.name; - if (!isGroup) { - let otherUserName = "Unknown User"; - if (typeof c.participants?.[0] === "object") { - const otherUser = c.participants.find( - (p: any) => p._id !== user.userId - ); - otherUserName = otherUser?.name || otherUserName; - } else { - const otherUserId = c.participants?.find( - (id: string) => id !== user.userId - ); - const otherUser = allUsers.find((u) => u._id === otherUserId); - otherUserName = otherUser?.name || otherUserName; + if (!isGroup) { + let otherUserName = "Unknown User"; + if (typeof c.participants?.[0] === "object") { + const otherUser = c.participants.find( + (p: any) => p._id !== user.userId, + ); + otherUserName = otherUser?.name || otherUserName; + } else { + const otherUserId = c.participants?.find( + (id: string) => id !== user.userId, + ); + const otherUser = allUsers.find((u) => u._id === otherUserId); + otherUserName = otherUser?.name || otherUserName; + } + roomName = otherUserName; } - roomName = otherUserName; - } - return { - id: c._id || c.id, - name: roomName || "Unnamed Group", - isGroup, - participants: c.participants || [], - }; - }) + return { + id: c._id || c.id, + name: roomName || "Unnamed Group", + isGroup, + participants: c.participants || [], + }; + }) : []; setRooms(loadedRooms); @@ -179,122 +185,124 @@ export default function ChatPage() { }, [user]); // ─── SSE: real-time group invite / removal ─────────────────────────────── -useEffect(() => { - if (!user?.token) return; + useEffect(() => { + if (!user?.token) return; - console.log("[SSE HOOK] Initializing authenticated SSE for user:", user.username); + console.log( + "[SSE HOOK] Initializing authenticated SSE for user:", + user.username, + ); - const cleanup = subscribeToSSE(user.token, { - // Someone added this user to a group conversation - onGroupInvite: ({ conversation }) => { - console.log("[SSE HOOK] onGroupInvite TRIGGERED"); + const cleanup = subscribeToSSE(user.token, { + // Someone added this user to a group conversation + onGroupInvite: ({ conversation }) => { + console.log("[SSE HOOK] onGroupInvite TRIGGERED"); - if (!conversation) { - console.warn("[SSE HOOK] groupInvite missing conversation payload"); - return; - } - - console.log("[SSE HOOK] groupInvite payload:", conversation); - - const convId: string = conversation._id ?? conversation.id; - const isGroup = conversation.type === "group"; - - console.log("[SSE HOOK] convId:", convId, "isGroup:", isGroup); - - // Resolve display name - let roomName = conversation.name || "Unnamed Group"; - - if (!isGroup) { - const other = (conversation.participants ?? []).find( - (p: any) => (p._id ?? p) !== user.userId - ); - roomName = other?.name ?? "Direct Message"; - } - - console.log("[SSE HOOK] roomName resolved:", roomName); - - // Hydrate participants array - const participants: { _id: string; name: string }[] = ( - conversation.participants ?? [] - ).map((p: any) => - typeof p === "object" - ? { _id: p._id, name: p.name ?? p._id } - : { _id: p, name: p } - ); - - console.log("[SSE HOOK] participants:", participants); - - const newRoom: Room = { - id: convId, - name: roomName, - isGroup, - participants, - }; - - console.log("[SSE HOOK] newRoom created:", newRoom); - - // Add to sidebar if not already there - setRooms((prev) => { - const exists = prev.find((r) => r.id === convId); - if (exists) { - console.log("[SSE HOOK] room already exists, skipping add"); - return prev; + if (!conversation) { + console.warn("[SSE HOOK] groupInvite missing conversation payload"); + return; } - console.log("[SSE HOOK] adding new room to sidebar"); - return [...prev, newRoom]; - }); + console.log("[SSE HOOK] groupInvite payload:", conversation); - console.log("[SSE HOOK] joining room:", convId); - joinRoom(convId); - }, + const convId: string = conversation._id ?? conversation.id; + const isGroup = conversation.type === "group"; - // This user was removed from a group - onGroupRemoved: ({ conversation }) => { - console.log("[SSE HOOK] onGroupRemoved TRIGGERED"); + console.log("[SSE HOOK] convId:", convId, "isGroup:", isGroup); - if (!conversation) { - console.warn("[SSE HOOK] groupRemoved missing conversation payload"); - return; - } + // Resolve display name + let roomName = conversation.name || "Unnamed Group"; - console.log("[SSE HOOK] groupRemoved payload:", conversation); + if (!isGroup) { + const other = (conversation.participants ?? []).find( + (p: any) => (p._id ?? p) !== user.userId, + ); + roomName = other?.name ?? "Direct Message"; + } - const roomId = conversation._id ?? conversation.id; + console.log("[SSE HOOK] roomName resolved:", roomName); - console.log("[SSE HOOK] removing roomId:", roomId); + // Hydrate participants array + const participants: { _id: string; name: string }[] = ( + conversation.participants ?? [] + ).map((p: any) => + typeof p === "object" + ? { _id: p._id, name: p.name ?? p._id } + : { _id: p, name: p }, + ); - // Leave socket room - leaveRoom(roomId); - console.log("[SSE HOOK] left room:", roomId); + console.log("[SSE HOOK] participants:", participants); - // Remove from sidebar - setRooms((prev) => { - const filtered = prev.filter((r) => r.id !== roomId); - console.log("[SSE HOOK] updated rooms after removal:", filtered); - return filtered; - }); + const newRoom: Room = { + id: convId, + name: roomName, + isGroup, + participants, + }; - // Clear selection if the removed room was active - if (currentRoom === roomId) { - console.log("[SSE HOOK] clearing current room selection"); - setCurrentRoom(null); - } + console.log("[SSE HOOK] newRoom created:", newRoom); - // Drop its messages from state - setMessages((prev) => { - const filtered = prev.filter((m) => m.roomId !== roomId); - console.log("[SSE HOOK] messages cleaned for room:", roomId); - return filtered; - }); - }, - }); + // Add to sidebar if not already there + setRooms((prev) => { + const exists = prev.find((r) => r.id === convId); + if (exists) { + console.log("[SSE HOOK] room already exists, skipping add"); + return prev; + } - return cleanup; -}, [user?.token, joinRoom, leaveRoom, setCurrentRoom, setMessages]); + console.log("[SSE HOOK] adding new room to sidebar"); + return [...prev, newRoom]; + }); + console.log("[SSE HOOK] joining room:", convId); + joinRoom(convId); + }, - // React to P2P Ready event + // This user was removed from a group + onGroupRemoved: ({ conversation }) => { + console.log("[SSE HOOK] onGroupRemoved TRIGGERED"); + + if (!conversation) { + console.warn("[SSE HOOK] groupRemoved missing conversation payload"); + return; + } + + console.log("[SSE HOOK] groupRemoved payload:", conversation); + + const roomId = conversation._id ?? conversation.id; + + console.log("[SSE HOOK] removing roomId:", roomId); + + // Leave socket room + leaveRoom(roomId); + console.log("[SSE HOOK] left room:", roomId); + + // Remove from sidebar + setRooms((prev) => { + const filtered = prev.filter((r) => r.id !== roomId); + console.log("[SSE HOOK] updated rooms after removal:", filtered); + return filtered; + }); + + // Clear selection if the removed room was active + if (currentRoom === roomId) { + console.log("[SSE HOOK] clearing current room selection"); + setCurrentRoom(null); + } + + // Drop its messages from state + setMessages((prev) => { + const filtered = prev.filter((m) => m.roomId !== roomId); + console.log("[SSE HOOK] messages cleaned for room:", roomId); + return filtered; + }); + }, + }); + + return cleanup; + }, [user?.token, joinRoom, leaveRoom, setCurrentRoom, setMessages]); + + // React to P2P Ready event (When YOU start a chat with someone) useEffect(() => { if (!p2pReady) return; const targetUser = allUsers.find((u) => u._id === p2pReady.targetUserId); @@ -304,7 +312,12 @@ useEffect(() => { if (prev.find((r) => r.id === p2pReady.roomId)) return prev; return [ ...prev, - { id: p2pReady.roomId, name: roomName, isGroup: false, participants: [] }, + { + id: p2pReady.roomId, + name: roomName, + isGroup: false, + participants: [], + }, ]; }); @@ -312,6 +325,31 @@ useEffect(() => { clearP2PReady(); }, [p2pReady, allUsers, clearP2PReady]); + // React to P2P Invite event (When SOMEONE ELSE starts a chat with you) + // This completely replaces polling and allows instant updates via Socket.IO + useEffect(() => { + if (!p2pInvite) return; + const targetUser = allUsers.find((u) => u._id === p2pInvite.fromUserId); + const roomName = targetUser?.name || "Direct Message"; + + setRooms((prev) => { + if (prev.find((r) => r.id === p2pInvite.roomId)) return prev; + return [ + ...prev, + { + id: p2pInvite.roomId, + name: roomName, + isGroup: false, + participants: [], + }, + ]; + }); + + // We do NOT call setActiveTab("rooms") here because we don't want to + // interrupt User 2's screen just because User 1 sent an invite. + clearP2PInvite(); + }, [p2pInvite, allUsers, clearP2PInvite]); + // Scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); @@ -339,23 +377,23 @@ useEffect(() => { return () => document.removeEventListener("mousedown", handleClickOutside); }, []); - // Filtered rooms - const filteredRooms = useMemo(() => { + // Filtered rooms - temporarily disabled + /* const filteredRooms = useMemo(() => { if (!debouncedRoomSearch.trim()) return rooms; return rooms.filter((r) => - r.name.toLowerCase().includes(debouncedRoomSearch.toLowerCase()) + r.name.toLowerCase().includes(debouncedRoomSearch.toLowerCase()), ); - }, [rooms, debouncedRoomSearch]); + }, [rooms, debouncedRoomSearch]); */ // Messages for current room const roomMessages = useMemo( () => messages.filter((m) => m.roomId === currentRoom), - [messages, currentRoom] + [messages, currentRoom], ); const roomNotices = useMemo( () => notices.filter((n) => n.roomId === currentRoom), - [notices, currentRoom] + [notices, currentRoom], ); // Handlers @@ -396,7 +434,7 @@ useEffect(() => { participants: created.participants || [], }; setRooms((prev) => - prev.find((r) => r.id === newRoom.id) ? prev : [...prev, newRoom] + prev.find((r) => r.id === newRoom.id) ? prev : [...prev, newRoom], ); setNewRoomName(""); setShowCreateDropdown(false); @@ -415,7 +453,9 @@ useEffect(() => { const hydrated = rawParticipants.map((p: any) => { if (typeof p === "string") { const found = allUsers.find((u) => u._id === p); - return found ? { _id: found._id, name: found.name } : { _id: p, name: p }; + return found + ? { _id: found._id, name: found.name } + : { _id: p, name: p }; } if (!p.name) { const found = allUsers.find((u) => u._id === (p._id ?? p)); @@ -426,8 +466,8 @@ useEffect(() => { setRooms((prev) => prev.map((r) => - r.id === convId ? { ...r, participants: hydrated } : r - ) + r.id === convId ? { ...r, participants: hydrated } : r, + ), ); } catch (err: any) { console.error("Add participant failed:", err.message); @@ -442,7 +482,9 @@ useEffect(() => { const hydrated = rawParticipants.map((p: any) => { if (typeof p === "string") { const found = allUsers.find((u) => u._id === p); - return found ? { _id: found._id, name: found.name } : { _id: p, name: p }; + return found + ? { _id: found._id, name: found.name } + : { _id: p, name: p }; } if (!p.name) { const found = allUsers.find((u) => u._id === (p._id ?? p)); @@ -453,8 +495,8 @@ useEffect(() => { setRooms((prev) => prev.map((r) => - r.id === convId ? { ...r, participants: hydrated } : r - ) + r.id === convId ? { ...r, participants: hydrated } : r, + ), ); } catch (err: any) { console.error("Remove participant failed:", err.message); @@ -505,12 +547,12 @@ useEffect(() => { const manageParticipants = selectedRoom?.participants ?? []; const addableUsers = allUsers.filter( - (u) => !manageParticipants.some((p: any) => (p._id ?? p) === u._id) + (u) => !manageParticipants.some((p: any) => (p._id ?? p) === u._id), ); return (
- { /* LEFT SIDEBAR */} + {/* LEFT SIDEBAR */}
{/* HEADER */}
@@ -576,19 +618,21 @@ useEffect(() => {
@@ -597,22 +641,22 @@ useEffect(() => { {/* ROOM LIST */} {activeTab === "rooms" && (
-
+ {/*
setRoomSearch(e.target.value)} className="rounded-full bg-muted" /> -
-
- {filteredRooms.length === 0 && ( +
*/} +
+ {rooms.length === 0 && (

No conversations yet. Start a chat from People tab!

)} - {filteredRooms.map((room) => { + {rooms.map((room) => { const isActive = currentRoom === room.id; return (
{ ${isActive ? "bg-primary text-primary-foreground" : "hover:bg-muted"}`} >
{room.isGroup ? ( { !debouncedUserSearch.trim() || u.name ?.toLowerCase() - .includes(debouncedUserSearch.toLowerCase()) + .includes(debouncedUserSearch.toLowerCase()), ) .map((u) => (
{ lastDate = msgDate; const isMe = - msg.senderId === user?.userId || - msg.senderId === mySocketId; + msg.senderId === user?.userId || msg.senderId === mySocketId; return (
@@ -879,7 +923,9 @@ useEffect(() => {
)} -
+
{!isMe && ( @@ -888,10 +934,11 @@ useEffect(() => { )}
{msg.text}
@@ -939,4 +986,4 @@ useEffect(() => {
); -} \ No newline at end of file +} diff --git a/src/context/SocketContext.tsx b/src/context/SocketContext.tsx index 23daf12..2047471 100644 --- a/src/context/SocketContext.tsx +++ b/src/context/SocketContext.tsx @@ -38,6 +38,13 @@ export interface P2PReadyData { message: string; } +// P2P Invite +export interface P2PInviteData { + roomId: string; + fromUserId: string; + message: string; +} + //context value interface SocketContextValue { @@ -61,6 +68,9 @@ interface SocketContextValue { p2pReady: P2PReadyData | null; clearP2PReady: () => void; + + p2pInvite: P2PInviteData | null; + clearP2PInvite: () => void; } //context @@ -81,6 +91,7 @@ export function SocketProvider({ children }: { children: ReactNode }) { const [notices, setNotices] = useState([]); const [currentRoom, setCurrentRoom] = useState(null); const [p2pReady, setP2pReady] = useState(null); + const [p2pInvite, setP2pInvite] = useState(null); //socket connection @@ -153,6 +164,11 @@ export function SocketProvider({ children }: { children: ReactNode }) { setCurrentRoom(data.roomId); }); + socket.on("p2pInvite", (data: P2PInviteData) => { + console.log("[Socket] P2P Invite:", data.message); + setP2pInvite(data); + }); + //new message socket.on("roomMessage", (data: ChatMessage) => { @@ -206,6 +222,10 @@ export function SocketProvider({ children }: { children: ReactNode }) { setP2pReady(null); }, []); + const clearP2PInvite = useCallback(() => { + setP2pInvite(null); + }, []); + //provider return ( @@ -226,6 +246,8 @@ export function SocketProvider({ children }: { children: ReactNode }) { clearMessages, p2pReady, clearP2PReady, + p2pInvite, + clearP2PInvite, }} > {children}