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
58 changes: 46 additions & 12 deletions src/components/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1960,21 +1960,56 @@ const loadOlderMessages = async (container?: HTMLDivElement | null) => {
setIsProfileOpen(true);

try {
const profile = await fetchUserProfile(userId);
const token = localStorage.getItem("access_token");

// Try the generic profile endpoint with exhaustive field names
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/profile/${userId}`;
const response = await fetch(url, {
headers: { Authorization: `Bearer ${token || ""}` },
});

let profile: any = null;

if (response.ok) {
profile = await response.json();
} else {
// Fallback to the existing fetchUserProfile helper
profile = await fetchUserProfile(userId);
}

if (!profile) throw new Error("Profile not found");

// Exhaustive extraction — covers user/users nesting and flat shapes
const resolvedUsername =
profile.user?.username ||
profile.users?.username ||
profile.username ||
profile.fullname ||
profile.name ||
fallbackName ||
"Unknown User";

const resolvedAvatar =
profile.user?.avatar_url ||
profile.users?.avatar_url ||
profile.avatar_url ||
fallbackAvatar ||
"/User_profil.png";

const resolvedBio =
profile.user?.bio ||
profile.users?.bio ||
profile.bio ||
profile.about ||
"No bio yet...";

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...",
username: resolvedUsername,
avatarUrl: resolvedAvatar,
about: resolvedBio,
roles: Array.isArray(profile.roles)
? profile.roles
.map((role: any) =>
Expand All @@ -1984,16 +2019,15 @@ const loadOlderMessages = async (container?: HTMLDivElement | null) => {
: [],
};
});
} catch (profileError) {
console.error("Failed to open DM user profile:", profileError);
} catch (error) {
console.error("openUserProfile fetch failed:", error);
setSelectedUser((prev) =>
prev ? { ...prev, about: "No bio available." } : null
);
}
},
[]
);

// Mark thread as read when user opens a DM
useEffect(() => {
if (!activeDmId || !currentUser?.id) return;
Expand Down
148 changes: 77 additions & 71 deletions src/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -578,115 +578,121 @@ 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";
console.log("openProfile called:", { userId, username }); // ← ADD

// 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;
}
}
const safeUsername = username || "Unknown";
const safeAvatar = fallbackAvatar || "/User_profil.png";

// 1. Open the modal instantly
setSelectedUser({
id: realUserId,
id: userId,
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;
if (!token || !serverId) {
console.log("No token or serverId:", { token: !!token, serverId }); // ← ADD
return;
}

const url = `${process.env.NEXT_PUBLIC_API_URL}/api/newserver/${serverId}/members/${userId}`;
console.log("Fetching:", url); // ← ADD

const url = `${process.env.NEXT_PUBLIC_API_URL}/api/newserver/${serverId}/members/${realUserId}`;
const response = await fetch(url, {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});

if (!response.ok) throw new Error("Failed to fetch user");
console.log("Response status:", res.status); // ← ADD

const memberData = await response.json();
if (!res.ok) throw new Error(`HTTP ${res.status}`);

// Pure fallback, absolutely NO URL modifications!
const freshAvatar =
memberData.user?.avatar_url ||
memberData.users?.avatar_url ||
memberData.avatar_url;
const data = await res.json();
console.log("Member data received:", data); // ← ADD

setSelectedUser({
id: realUserId,
id: userId,
username:
memberData.user?.username ||
memberData.users?.username ||
data.user?.username ||
data.users?.username ||
data.username ||
safeUsername,
avatarUrl: freshAvatar || safeAvatar,
about:
memberData.user?.bio ||
memberData.users?.bio ||
memberData.bio ||
"No bio yet...",
avatarUrl:
data.user?.avatar_url ||
data.users?.avatar_url ||
data.avatar_url ||
safeAvatar,
about: data.user?.bio || data.users?.bio || data.bio || "No bio yet...",
roles:
memberData.roles?.map((role: any) => ({
id: role.id || role.role_id,
name: role.name,
color: role.color || "#374151",
data.roles?.map((r: any) => ({
id: r.id || r.role_id,
name: r.name,
color: r.color || "#374151",
})) || [],
});
} catch (error) {
} catch (err) {
console.error("openProfile error:", err);
setSelectedUser((prev) =>
prev ? { ...prev, about: "No bio available." } : null
);
}
},
[serverId, messages] // Make sure messages is in the dependency array!
[serverId, messages]
);
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
// Step 1: Check already-loaded messages for real UUID
const fromMessages = messages.find(
(msg) =>
msg.username?.toLowerCase() === username.toLowerCase() &&
msg.senderId &&
!String(msg.senderId).startsWith("temp-") &&
// Make sure senderId is a real UUID, not another username string
String(msg.senderId) !== msg.username
);

let mockMessage: Message;

if (existingMessage) {
mockMessage = existingMessage;
} else {
mockMessage = {
id: `temp-${userId}`,
content: "",
senderId: userId,
timestamp: new Date().toISOString(),
username,
avatarUrl: avatarCacheRef.current[userId]?.url || "/User_profil.png",
};
if (fromMessages?.senderId) {
const avatarUrl =
avatarCacheRef.current[String(fromMessages.senderId)]?.url ||
fromMessages.avatarUrl ||
"/User_profil.png";
await openProfile(String(fromMessages.senderId), username, avatarUrl);
return;
}

await openProfile(userId, username, avatarUrl);
// Step 2: Resolve UUID from server members list
if (serverId) {
try {
const members = await getServerMembers(serverId);
const match = members?.find(
(m: any) =>
m?.users?.username?.toLowerCase() === username.toLowerCase()
);

if (match) {
const realId =
match.user_id || match.userId || match.users?.id || match.id;

const avatarUrl =
match.users?.avatar_url || match.avatar_url || "/User_profil.png";

if (realId) {
await openProfile(String(realId), username, avatarUrl);
return;
}
}
} catch (err) {
console.error("Failed to resolve UUID from members list:", err);
}
}


await openProfile(userId, username, "/User_profil.png");
},
[openProfile]
[openProfile, messages, serverId]
);
const handleRoleMentionClick = useCallback(
async (roleName: string) => {
Expand Down
Loading