added the p2pinvite for the starts a chat
This commit is contained in:
parent
6698a9eeed
commit
2fcb8e1b68
@ -3,7 +3,11 @@
|
|||||||
import { useState, useEffect, useRef, useMemo } from "react";
|
import { useState, useEffect, useRef, useMemo } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/context/AuthContext";
|
||||||
import { useSocket, type ChatMessage, type RoomNotice } from "@/context/SocketContext";
|
import {
|
||||||
|
useSocket,
|
||||||
|
type ChatMessage,
|
||||||
|
type RoomNotice,
|
||||||
|
} from "@/context/SocketContext";
|
||||||
import {
|
import {
|
||||||
fetchConversations,
|
fetchConversations,
|
||||||
createConversation,
|
createConversation,
|
||||||
@ -55,10 +59,12 @@ export default function ChatPage() {
|
|||||||
p2pReady,
|
p2pReady,
|
||||||
setMessages,
|
setMessages,
|
||||||
clearP2PReady,
|
clearP2PReady,
|
||||||
|
p2pInvite,
|
||||||
|
clearP2PInvite,
|
||||||
} = useSocket();
|
} = useSocket();
|
||||||
|
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const [roomSearch, setRoomSearch] = useState("");
|
// const [roomSearch, setRoomSearch] = useState("");
|
||||||
const [userSearch, setUserSearch] = useState("");
|
const [userSearch, setUserSearch] = useState("");
|
||||||
const [activeTab, setActiveTab] = useState<"rooms" | "users">("rooms");
|
const [activeTab, setActiveTab] = useState<"rooms" | "users">("rooms");
|
||||||
const [showCreateDropdown, setShowCreateDropdown] = useState(false);
|
const [showCreateDropdown, setShowCreateDropdown] = useState(false);
|
||||||
@ -81,7 +87,7 @@ export default function ChatPage() {
|
|||||||
return debounced;
|
return debounced;
|
||||||
};
|
};
|
||||||
|
|
||||||
const debouncedRoomSearch = useDebounce(roomSearch, 300);
|
// const debouncedRoomSearch = useDebounce(roomSearch, 300);
|
||||||
const debouncedUserSearch = useDebounce(userSearch, 300);
|
const debouncedUserSearch = useDebounce(userSearch, 300);
|
||||||
|
|
||||||
// Auth guard
|
// Auth guard
|
||||||
@ -99,33 +105,33 @@ export default function ChatPage() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
const loadedRooms = Array.isArray(data)
|
const loadedRooms = Array.isArray(data)
|
||||||
? data.map((c) => {
|
? data.map((c) => {
|
||||||
const isGroup = c.type === "group";
|
const isGroup = c.type === "group";
|
||||||
let roomName = c.name;
|
let roomName = c.name;
|
||||||
|
|
||||||
if (!isGroup) {
|
if (!isGroup) {
|
||||||
let otherUserName = "Unknown User";
|
let otherUserName = "Unknown User";
|
||||||
if (typeof c.participants?.[0] === "object") {
|
if (typeof c.participants?.[0] === "object") {
|
||||||
const otherUser = c.participants.find(
|
const otherUser = c.participants.find(
|
||||||
(p: any) => p._id !== user.userId
|
(p: any) => p._id !== user.userId,
|
||||||
);
|
);
|
||||||
otherUserName = otherUser?.name || otherUserName;
|
otherUserName = otherUser?.name || otherUserName;
|
||||||
} else {
|
} else {
|
||||||
const otherUserId = c.participants?.find(
|
const otherUserId = c.participants?.find(
|
||||||
(id: string) => id !== user.userId
|
(id: string) => id !== user.userId,
|
||||||
);
|
);
|
||||||
const otherUser = allUsers.find((u) => u._id === otherUserId);
|
const otherUser = allUsers.find((u) => u._id === otherUserId);
|
||||||
otherUserName = otherUser?.name || otherUserName;
|
otherUserName = otherUser?.name || otherUserName;
|
||||||
|
}
|
||||||
|
roomName = otherUserName;
|
||||||
}
|
}
|
||||||
roomName = otherUserName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: c._id || c.id,
|
id: c._id || c.id,
|
||||||
name: roomName || "Unnamed Group",
|
name: roomName || "Unnamed Group",
|
||||||
isGroup,
|
isGroup,
|
||||||
participants: c.participants || [],
|
participants: c.participants || [],
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
setRooms(loadedRooms);
|
setRooms(loadedRooms);
|
||||||
@ -179,122 +185,124 @@ export default function ChatPage() {
|
|||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
// ─── SSE: real-time group invite / removal ───────────────────────────────
|
// ─── SSE: real-time group invite / removal ───────────────────────────────
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user?.token) return;
|
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, {
|
const cleanup = subscribeToSSE(user.token, {
|
||||||
// Someone added this user to a group conversation
|
// Someone added this user to a group conversation
|
||||||
onGroupInvite: ({ conversation }) => {
|
onGroupInvite: ({ conversation }) => {
|
||||||
console.log("[SSE HOOK] onGroupInvite TRIGGERED");
|
console.log("[SSE HOOK] onGroupInvite TRIGGERED");
|
||||||
|
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
console.warn("[SSE HOOK] groupInvite missing conversation payload");
|
console.warn("[SSE HOOK] groupInvite missing conversation payload");
|
||||||
return;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("[SSE HOOK] adding new room to sidebar");
|
console.log("[SSE HOOK] groupInvite payload:", conversation);
|
||||||
return [...prev, newRoom];
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[SSE HOOK] joining room:", convId);
|
const convId: string = conversation._id ?? conversation.id;
|
||||||
joinRoom(convId);
|
const isGroup = conversation.type === "group";
|
||||||
},
|
|
||||||
|
|
||||||
// This user was removed from a group
|
console.log("[SSE HOOK] convId:", convId, "isGroup:", isGroup);
|
||||||
onGroupRemoved: ({ conversation }) => {
|
|
||||||
console.log("[SSE HOOK] onGroupRemoved TRIGGERED");
|
|
||||||
|
|
||||||
if (!conversation) {
|
// Resolve display name
|
||||||
console.warn("[SSE HOOK] groupRemoved missing conversation payload");
|
let roomName = conversation.name || "Unnamed Group";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
console.log("[SSE HOOK] participants:", participants);
|
||||||
leaveRoom(roomId);
|
|
||||||
console.log("[SSE HOOK] left room:", roomId);
|
|
||||||
|
|
||||||
// Remove from sidebar
|
const newRoom: Room = {
|
||||||
setRooms((prev) => {
|
id: convId,
|
||||||
const filtered = prev.filter((r) => r.id !== roomId);
|
name: roomName,
|
||||||
console.log("[SSE HOOK] updated rooms after removal:", filtered);
|
isGroup,
|
||||||
return filtered;
|
participants,
|
||||||
});
|
};
|
||||||
|
|
||||||
// Clear selection if the removed room was active
|
console.log("[SSE HOOK] newRoom created:", newRoom);
|
||||||
if (currentRoom === roomId) {
|
|
||||||
console.log("[SSE HOOK] clearing current room selection");
|
|
||||||
setCurrentRoom(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop its messages from state
|
// Add to sidebar if not already there
|
||||||
setMessages((prev) => {
|
setRooms((prev) => {
|
||||||
const filtered = prev.filter((m) => m.roomId !== roomId);
|
const exists = prev.find((r) => r.id === convId);
|
||||||
console.log("[SSE HOOK] messages cleaned for room:", roomId);
|
if (exists) {
|
||||||
return filtered;
|
console.log("[SSE HOOK] room already exists, skipping add");
|
||||||
});
|
return prev;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return cleanup;
|
console.log("[SSE HOOK] adding new room to sidebar");
|
||||||
}, [user?.token, joinRoom, leaveRoom, setCurrentRoom, setMessages]);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!p2pReady) return;
|
if (!p2pReady) return;
|
||||||
const targetUser = allUsers.find((u) => u._id === p2pReady.targetUserId);
|
const targetUser = allUsers.find((u) => u._id === p2pReady.targetUserId);
|
||||||
@ -304,7 +312,12 @@ useEffect(() => {
|
|||||||
if (prev.find((r) => r.id === p2pReady.roomId)) return prev;
|
if (prev.find((r) => r.id === p2pReady.roomId)) return prev;
|
||||||
return [
|
return [
|
||||||
...prev,
|
...prev,
|
||||||
{ id: p2pReady.roomId, name: roomName, isGroup: false, participants: [] },
|
{
|
||||||
|
id: p2pReady.roomId,
|
||||||
|
name: roomName,
|
||||||
|
isGroup: false,
|
||||||
|
participants: [],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -312,6 +325,31 @@ useEffect(() => {
|
|||||||
clearP2PReady();
|
clearP2PReady();
|
||||||
}, [p2pReady, allUsers, 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
|
// Scroll to bottom on new messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
@ -339,23 +377,23 @@ useEffect(() => {
|
|||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Filtered rooms
|
// Filtered rooms - temporarily disabled
|
||||||
const filteredRooms = useMemo(() => {
|
/* const filteredRooms = useMemo(() => {
|
||||||
if (!debouncedRoomSearch.trim()) return rooms;
|
if (!debouncedRoomSearch.trim()) return rooms;
|
||||||
return rooms.filter((r) =>
|
return rooms.filter((r) =>
|
||||||
r.name.toLowerCase().includes(debouncedRoomSearch.toLowerCase())
|
r.name.toLowerCase().includes(debouncedRoomSearch.toLowerCase()),
|
||||||
);
|
);
|
||||||
}, [rooms, debouncedRoomSearch]);
|
}, [rooms, debouncedRoomSearch]); */
|
||||||
|
|
||||||
// Messages for current room
|
// Messages for current room
|
||||||
const roomMessages = useMemo(
|
const roomMessages = useMemo(
|
||||||
() => messages.filter((m) => m.roomId === currentRoom),
|
() => messages.filter((m) => m.roomId === currentRoom),
|
||||||
[messages, currentRoom]
|
[messages, currentRoom],
|
||||||
);
|
);
|
||||||
|
|
||||||
const roomNotices = useMemo(
|
const roomNotices = useMemo(
|
||||||
() => notices.filter((n) => n.roomId === currentRoom),
|
() => notices.filter((n) => n.roomId === currentRoom),
|
||||||
[notices, currentRoom]
|
[notices, currentRoom],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
@ -396,7 +434,7 @@ useEffect(() => {
|
|||||||
participants: created.participants || [],
|
participants: created.participants || [],
|
||||||
};
|
};
|
||||||
setRooms((prev) =>
|
setRooms((prev) =>
|
||||||
prev.find((r) => r.id === newRoom.id) ? prev : [...prev, newRoom]
|
prev.find((r) => r.id === newRoom.id) ? prev : [...prev, newRoom],
|
||||||
);
|
);
|
||||||
setNewRoomName("");
|
setNewRoomName("");
|
||||||
setShowCreateDropdown(false);
|
setShowCreateDropdown(false);
|
||||||
@ -415,7 +453,9 @@ useEffect(() => {
|
|||||||
const hydrated = rawParticipants.map((p: any) => {
|
const hydrated = rawParticipants.map((p: any) => {
|
||||||
if (typeof p === "string") {
|
if (typeof p === "string") {
|
||||||
const found = allUsers.find((u) => u._id === p);
|
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) {
|
if (!p.name) {
|
||||||
const found = allUsers.find((u) => u._id === (p._id ?? p));
|
const found = allUsers.find((u) => u._id === (p._id ?? p));
|
||||||
@ -426,8 +466,8 @@ useEffect(() => {
|
|||||||
|
|
||||||
setRooms((prev) =>
|
setRooms((prev) =>
|
||||||
prev.map((r) =>
|
prev.map((r) =>
|
||||||
r.id === convId ? { ...r, participants: hydrated } : r
|
r.id === convId ? { ...r, participants: hydrated } : r,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Add participant failed:", err.message);
|
console.error("Add participant failed:", err.message);
|
||||||
@ -442,7 +482,9 @@ useEffect(() => {
|
|||||||
const hydrated = rawParticipants.map((p: any) => {
|
const hydrated = rawParticipants.map((p: any) => {
|
||||||
if (typeof p === "string") {
|
if (typeof p === "string") {
|
||||||
const found = allUsers.find((u) => u._id === p);
|
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) {
|
if (!p.name) {
|
||||||
const found = allUsers.find((u) => u._id === (p._id ?? p));
|
const found = allUsers.find((u) => u._id === (p._id ?? p));
|
||||||
@ -453,8 +495,8 @@ useEffect(() => {
|
|||||||
|
|
||||||
setRooms((prev) =>
|
setRooms((prev) =>
|
||||||
prev.map((r) =>
|
prev.map((r) =>
|
||||||
r.id === convId ? { ...r, participants: hydrated } : r
|
r.id === convId ? { ...r, participants: hydrated } : r,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Remove participant failed:", err.message);
|
console.error("Remove participant failed:", err.message);
|
||||||
@ -505,12 +547,12 @@ useEffect(() => {
|
|||||||
|
|
||||||
const manageParticipants = selectedRoom?.participants ?? [];
|
const manageParticipants = selectedRoom?.participants ?? [];
|
||||||
const addableUsers = allUsers.filter(
|
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 (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
{ /* LEFT SIDEBAR */}
|
{/* LEFT SIDEBAR */}
|
||||||
<div className="w-1/3 border-r flex flex-col bg-background">
|
<div className="w-1/3 border-r flex flex-col bg-background">
|
||||||
{/* HEADER */}
|
{/* HEADER */}
|
||||||
<div className="flex items-center justify-between p-4">
|
<div className="flex items-center justify-between p-4">
|
||||||
@ -576,19 +618,21 @@ useEffect(() => {
|
|||||||
<div className="flex gap-4 px-4 text-sm font-medium border-b pb-2">
|
<div className="flex gap-4 px-4 text-sm font-medium border-b pb-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("rooms")}
|
onClick={() => setActiveTab("rooms")}
|
||||||
className={`pb-1 ${activeTab === "rooms"
|
className={`pb-1 ${
|
||||||
|
activeTab === "rooms"
|
||||||
? "text-primary border-b-2 border-primary"
|
? "text-primary border-b-2 border-primary"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Chats
|
Chats
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("users")}
|
onClick={() => setActiveTab("users")}
|
||||||
className={`pb-1 ${activeTab === "users"
|
className={`pb-1 ${
|
||||||
|
activeTab === "users"
|
||||||
? "text-primary border-b-2 border-primary"
|
? "text-primary border-b-2 border-primary"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
People
|
People
|
||||||
</button>
|
</button>
|
||||||
@ -597,22 +641,22 @@ useEffect(() => {
|
|||||||
{/* ROOM LIST */}
|
{/* ROOM LIST */}
|
||||||
{activeTab === "rooms" && (
|
{activeTab === "rooms" && (
|
||||||
<div className="flex flex-col flex-1 min-h-0">
|
<div className="flex flex-col flex-1 min-h-0">
|
||||||
<div className="p-4">
|
{/* <div className="p-4">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search conversation..."
|
placeholder="Search conversation..."
|
||||||
value={roomSearch}
|
value={roomSearch}
|
||||||
onChange={(e) => setRoomSearch(e.target.value)}
|
onChange={(e) => setRoomSearch(e.target.value)}
|
||||||
className="rounded-full bg-muted"
|
className="rounded-full bg-muted"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="flex-1 overflow-y-auto px-2 space-y-1">
|
<div className="flex-1 overflow-y-auto px-2 py-4 space-y-1">
|
||||||
{filteredRooms.length === 0 && (
|
{rooms.length === 0 && (
|
||||||
<p className="p-4 text-sm text-muted-foreground">
|
<p className="p-4 text-sm text-muted-foreground">
|
||||||
No conversations yet. Start a chat from People tab!
|
No conversations yet. Start a chat from People tab!
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filteredRooms.map((room) => {
|
{rooms.map((room) => {
|
||||||
const isActive = currentRoom === room.id;
|
const isActive = currentRoom === room.id;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -622,8 +666,9 @@ useEffect(() => {
|
|||||||
${isActive ? "bg-primary text-primary-foreground" : "hover:bg-muted"}`}
|
${isActive ? "bg-primary text-primary-foreground" : "hover:bg-muted"}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-full ${isActive ? "bg-white/20" : "bg-primary/10"
|
className={`w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-full ${
|
||||||
}`}
|
isActive ? "bg-white/20" : "bg-primary/10"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{room.isGroup ? (
|
{room.isGroup ? (
|
||||||
<Users
|
<Users
|
||||||
@ -673,7 +718,7 @@ useEffect(() => {
|
|||||||
!debouncedUserSearch.trim() ||
|
!debouncedUserSearch.trim() ||
|
||||||
u.name
|
u.name
|
||||||
?.toLowerCase()
|
?.toLowerCase()
|
||||||
.includes(debouncedUserSearch.toLowerCase())
|
.includes(debouncedUserSearch.toLowerCase()),
|
||||||
)
|
)
|
||||||
.map((u) => (
|
.map((u) => (
|
||||||
<div
|
<div
|
||||||
@ -866,8 +911,7 @@ useEffect(() => {
|
|||||||
lastDate = msgDate;
|
lastDate = msgDate;
|
||||||
|
|
||||||
const isMe =
|
const isMe =
|
||||||
msg.senderId === user?.userId ||
|
msg.senderId === user?.userId || msg.senderId === mySocketId;
|
||||||
msg.senderId === mySocketId;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={msg._id || idx}>
|
<div key={msg._id || idx}>
|
||||||
@ -879,7 +923,9 @@ useEffect(() => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={`flex ${isMe ? "justify-end" : "justify-start"}`}>
|
<div
|
||||||
|
className={`flex ${isMe ? "justify-end" : "justify-start"}`}
|
||||||
|
>
|
||||||
<div className="flex flex-col max-w-xs">
|
<div className="flex flex-col max-w-xs">
|
||||||
{!isMe && (
|
{!isMe && (
|
||||||
<span className="text-xs text-muted-foreground mb-1 ml-1">
|
<span className="text-xs text-muted-foreground mb-1 ml-1">
|
||||||
@ -888,10 +934,11 @@ useEffect(() => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`px-4 py-2 rounded-xl ${isMe
|
className={`px-4 py-2 rounded-xl ${
|
||||||
|
isMe
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
: "bg-muted"
|
: "bg-muted"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div>{msg.text}</div>
|
<div>{msg.text}</div>
|
||||||
<div className="text-[10px] text-right opacity-70 mt-1">
|
<div className="text-[10px] text-right opacity-70 mt-1">
|
||||||
@ -939,4 +986,4 @@ useEffect(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,13 @@ export interface P2PReadyData {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P2P Invite
|
||||||
|
export interface P2PInviteData {
|
||||||
|
roomId: string;
|
||||||
|
fromUserId: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
//context value
|
//context value
|
||||||
|
|
||||||
interface SocketContextValue {
|
interface SocketContextValue {
|
||||||
@ -61,6 +68,9 @@ interface SocketContextValue {
|
|||||||
|
|
||||||
p2pReady: P2PReadyData | null;
|
p2pReady: P2PReadyData | null;
|
||||||
clearP2PReady: () => void;
|
clearP2PReady: () => void;
|
||||||
|
|
||||||
|
p2pInvite: P2PInviteData | null;
|
||||||
|
clearP2PInvite: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//context
|
//context
|
||||||
@ -81,6 +91,7 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
const [notices, setNotices] = useState<RoomNotice[]>([]);
|
const [notices, setNotices] = useState<RoomNotice[]>([]);
|
||||||
const [currentRoom, setCurrentRoom] = useState<string | null>(null);
|
const [currentRoom, setCurrentRoom] = useState<string | null>(null);
|
||||||
const [p2pReady, setP2pReady] = useState<P2PReadyData | null>(null);
|
const [p2pReady, setP2pReady] = useState<P2PReadyData | null>(null);
|
||||||
|
const [p2pInvite, setP2pInvite] = useState<P2PInviteData | null>(null);
|
||||||
|
|
||||||
//socket connection
|
//socket connection
|
||||||
|
|
||||||
@ -153,6 +164,11 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
setCurrentRoom(data.roomId);
|
setCurrentRoom(data.roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("p2pInvite", (data: P2PInviteData) => {
|
||||||
|
console.log("[Socket] P2P Invite:", data.message);
|
||||||
|
setP2pInvite(data);
|
||||||
|
});
|
||||||
|
|
||||||
//new message
|
//new message
|
||||||
|
|
||||||
socket.on("roomMessage", (data: ChatMessage) => {
|
socket.on("roomMessage", (data: ChatMessage) => {
|
||||||
@ -206,6 +222,10 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
setP2pReady(null);
|
setP2pReady(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const clearP2PInvite = useCallback(() => {
|
||||||
|
setP2pInvite(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
//provider
|
//provider
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -226,6 +246,8 @@ export function SocketProvider({ children }: { children: ReactNode }) {
|
|||||||
clearMessages,
|
clearMessages,
|
||||||
p2pReady,
|
p2pReady,
|
||||||
clearP2PReady,
|
clearP2PReady,
|
||||||
|
p2pInvite,
|
||||||
|
clearP2PInvite,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user