Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 80 additions & 78 deletions src/components/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(() => {
Expand Down
145 changes: 92 additions & 53 deletions src/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand All @@ -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;
Expand Down Expand Up @@ -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") {
Expand Down
Loading