diff --git a/src/components/ChatPage.tsx b/src/components/ChatPage.tsx index 26f90f8..501cb52 100644 --- a/src/components/ChatPage.tsx +++ b/src/components/ChatPage.tsx @@ -1360,44 +1360,44 @@ function MessagesPageContentInner() { router.push("/"); } }, [router]); - useEffect(() => { - const handleProfileUpdate = () => { - const userItem = localStorage.getItem("user"); - if (!userItem) return; - - const updatedUser = JSON.parse(userItem) as User; - setCurrentUser(updatedUser); - - setAllUsers((prev) => - prev.map((u) => - u.id === updatedUser.id - ? { - ...u, - fullname: updatedUser.fullname, - avatar_url: updatedUser.avatar_url - ? `${updatedUser.avatar_url}?t=${Date.now()}` - : u.avatar_url, - } - : u - ) - ); - }; + useEffect(() => { + const handleProfileUpdate = () => { + const userItem = localStorage.getItem("user"); + if (!userItem) return; + + const updatedUser = JSON.parse(userItem) as User; + setCurrentUser(updatedUser); + + setAllUsers((prev) => + prev.map((u) => + u.id === updatedUser.id + ? { + ...u, + fullname: updatedUser.fullname, + + avatar_url: updatedUser.avatar_url, + } + : u + ) + ); + }; - window.addEventListener("user-profile-updated", handleProfileUpdate); - return () => - window.removeEventListener("user-profile-updated", handleProfileUpdate); - }, []); - useEffect(() => { - if (!currentUser?.id || !currentUser.avatar_url) return; + window.addEventListener("user-profile-updated", handleProfileUpdate); + return () => + window.removeEventListener("user-profile-updated", handleProfileUpdate); + }, []); + useEffect(() => { + if (!currentUser?.id || !currentUser.avatar_url) return; - const bustedUrl = `${currentUser.avatar_url}?t=${Date.now()}`; - setAllUsers((prev) => - prev.map((u) => - u.id === currentUser.id ? { ...u, avatar_url: bustedUrl } : u - ) - ); - }, [currentUser?.avatar_url]); + setAllUsers((prev) => + prev.map((u) => + u.id === currentUser?.id + ? { ...u, avatar_url: currentUser.avatar_url } + : u + ) + ); + }, [currentUser?.avatar_url, currentUser?.id]); // Removed duplicate socket setup effect; handled in single effect above @@ -1946,51 +1946,53 @@ const loadOlderMessages = async (container?: HTMLDivElement | null) => { [router] ); - const openUserProfile = useCallback( - async (userId: string, fallbackName?: string, fallbackAvatar?: string) => { - if (!userId) return; + const openUserProfile = useCallback( + async (userId: string, fallbackName?: string, fallbackAvatar?: string) => { + if (!userId) return; - setSelectedUser({ - id: userId, - username: fallbackName || "Unknown User", - avatarUrl: fallbackAvatar || "/User_profil.png", - about: "Loading bio...", - roles: [], - }); - setIsProfileOpen(true); - + setSelectedUser({ + id: userId, + username: fallbackName || "Unknown User", + avatarUrl: fallbackAvatar || "/User_profil.png", + about: "Loading bio...", + roles: [], + }); + setIsProfileOpen(true); - try { - const profile = await fetchUserProfile(userId); - if (!profile) return; - - setSelectedUser((prev) => { - if (!prev || prev.id !== userId) return prev; - return { - id: userId, - username: - profile.username || - profile.fullname || - fallbackName || - "Unknown User", - avatarUrl: - profile.avatar_url || fallbackAvatar || "/User_profil.png", - about: profile.bio || "No bio yet...", - roles: Array.isArray(profile.roles) - ? profile.roles - .map((role: any) => - typeof role === "string" ? role : role?.name - ) - .filter(Boolean) - : [], - }; - }); - } catch (profileError) { - console.error("Failed to open DM user profile:", profileError); - } - }, - [] - ); + try { + const profile = await fetchUserProfile(userId); + if (!profile) throw new Error("Profile not found"); + + setSelectedUser((prev) => { + if (!prev || prev.id !== userId) return prev; + return { + id: userId, + username: + profile.username || + profile.fullname || + fallbackName || + "Unknown User", + avatarUrl: + profile.avatar_url || fallbackAvatar || "/User_profil.png", + about: profile.bio || "No bio yet...", + roles: Array.isArray(profile.roles) + ? profile.roles + .map((role: any) => + typeof role === "string" ? role : role?.name + ) + .filter(Boolean) + : [], + }; + }); + } catch (profileError) { + console.error("Failed to open DM user profile:", profileError); + setSelectedUser((prev) => + prev ? { ...prev, about: "No bio available." } : null + ); + } + }, + [] + ); // Mark thread as read when user opens a DM useEffect(() => { diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 9f0028d..4c94a74 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -570,61 +570,101 @@ export default forwardRef(function ChatWindow( username: string; avatarUrl: string; about?: string; - roles?: string[]; + roles?: { id: string; name: string; color: string }[]; } | null>(null); - const [isProfileOpen, setIsProfileOpen] = useState(false); - const openProfile = useCallback( - async (msg: Message) => { - if (!msg.senderId) return; - - // Set basic user info first - setSelectedUser({ - id: msg.senderId, - username: msg.username || "Unknown", - avatarUrl: msg.avatarUrl || "/User_profil.png", - about: "Loading bio...", - roles: [], - }); - setIsProfileOpen(true); - - // Fetch user details including roles - try { - const token = localStorage.getItem("access_token"); - if (!token || !serverId) return; - - const url = `${process.env.NEXT_PUBLIC_API_URL}/api/newserver/${serverId}/members/${msg.senderId}`; - const response = await fetch(url, { - headers: { Authorization: `Bearer ${token}` }, - }); - - if (!response.ok) return; - - const memberData = await response.json(); - - setSelectedUser({ - id: msg.senderId, - username: msg.username || "Unknown", - avatarUrl: msg.avatarUrl || "/User_profil.png", - about: memberData.user?.bio || "No bio yet...", - // ✅ FIX: Map the full object with color instead of just the name string! - roles: - memberData.roles?.map((role: any) => ({ - id: role.id || role.role_id, - name: role.name, - color: role.color || "#374151", - })) || [], - }); - } catch (error) { - console.error("Error fetching user details:", error); - } - }, - [serverId] - ); +const openProfile = useCallback( + async (userId: string, username?: string, fallbackAvatar?: string) => { + if (!userId) return; + + let realUserId = userId; + const safeUsername = username || "Unknown"; + let safeAvatar = fallbackAvatar || "/User_profil.png"; + + // Fix for @mentions: If userId doesn't have a hyphen, it's a username, not a UUID! + // We must find their real UUID in the local chat history first. + if (!realUserId.includes("-")) { + const found = messages.find( + (m) => m.username === realUserId || m.senderId === realUserId + ); + if (found && String(found.senderId).includes("-")) { + realUserId = String(found.senderId); + safeAvatar = found.avatarUrl || safeAvatar; + } else { + // If they aren't in chat history, we can't fetch their profile. Exit gracefully. + setSelectedUser({ + id: realUserId, + username: safeUsername, + avatarUrl: safeAvatar, + about: "No bio available.", + roles: [], + }); + setIsProfileOpen(true); + return; + } + } + + // 1. Open the modal instantly + setSelectedUser({ + id: realUserId, + username: safeUsername, + avatarUrl: safeAvatar, + about: "Loading bio...", + roles: [], + }); + setIsProfileOpen(true); + // 2. Fetch the rich user details + try { + const token = localStorage.getItem("access_token"); + if (!token || !serverId) return; + + const url = `${process.env.NEXT_PUBLIC_API_URL}/api/newserver/${serverId}/members/${realUserId}`; + const response = await fetch(url, { + headers: { Authorization: `Bearer ${token}` }, + }); + + if (!response.ok) throw new Error("Failed to fetch user"); + + const memberData = await response.json(); + + // Pure fallback, absolutely NO URL modifications! + const freshAvatar = + memberData.user?.avatar_url || + memberData.users?.avatar_url || + memberData.avatar_url; + + setSelectedUser({ + id: realUserId, + username: + memberData.user?.username || + memberData.users?.username || + safeUsername, + avatarUrl: freshAvatar || safeAvatar, + about: + memberData.user?.bio || + memberData.users?.bio || + memberData.bio || + "No bio yet...", + roles: + memberData.roles?.map((role: any) => ({ + id: role.id || role.role_id, + name: role.name, + color: role.color || "#374151", + })) || [], + }); + } catch (error) { + setSelectedUser((prev) => + prev ? { ...prev, about: "No bio available." } : null + ); + } + }, + [serverId, messages] // Make sure messages is in the dependency array! +); const handleUsernameClick = useCallback( async (userId: string, username: string) => { + const avatarUrl = avatarCacheRef.current[userId]?.url || "/User_profil.png"; const existingMessage = messages.find( (msg) => msg.senderId === userId || msg.username === username ); @@ -644,11 +684,10 @@ const handleUsernameClick = useCallback( }; } - await openProfile(mockMessage); // openProfile will handle fetching the roles! + await openProfile(userId, username, avatarUrl); }, - [messages, openProfile] + [openProfile] ); - const handleRoleMentionClick = useCallback( async (roleName: string) => { if (!serverId) return; @@ -1981,7 +2020,7 @@ const isReplyImage = (mediaUrl?: string | null, mediaType?: string) => { )} onReply={() => handleReply(msg)} onReplyPreviewClick={scrollToMessage} - onProfileClick={() => openProfile(msg)} + onProfileClick={() => openProfile(msg.senderId, msg.username, msg.avatarUrl)} messageRenderer={(content: string) => { if (typeof content !== "string") {