diff --git a/src/api/client.js b/src/api/client.js index 249f66d..a037857 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -902,6 +902,13 @@ export async function fetchFriendRequests() { } } +// Emit event when friends list changes +function emitFriendsChanged() { + try { + window.dispatchEvent(new CustomEvent("sociowire:friends-changed")); + } catch {} +} + export async function sendFriendRequest(username) { try { const res = await fetch(`${apiBase()}/friends/request`, { @@ -911,6 +918,7 @@ export async function sendFriendRequest(username) { body: JSON.stringify({ username }), }); const data = await res.json().catch(() => ({})); + if (res.ok) emitFriendsChanged(); return { ok: res.ok, ...data }; } catch { return { ok: false }; @@ -926,6 +934,7 @@ export async function acceptFriendRequest(username) { body: JSON.stringify({ username }), }); const data = await res.json().catch(() => ({})); + if (res.ok) emitFriendsChanged(); return { ok: res.ok, ...data }; } catch { return { ok: false }; @@ -941,6 +950,7 @@ export async function rejectFriendRequest(username) { body: JSON.stringify({ username }), }); const data = await res.json().catch(() => ({})); + if (res.ok) emitFriendsChanged(); return { ok: res.ok, ...data }; } catch { return { ok: false }; @@ -956,6 +966,7 @@ export async function cancelFriendRequest(username) { body: JSON.stringify({ username }), }); const data = await res.json().catch(() => ({})); + if (res.ok) emitFriendsChanged(); return { ok: res.ok, ...data }; } catch { return { ok: false }; @@ -971,6 +982,7 @@ export async function removeFriend(username) { body: JSON.stringify({ username }), }); const data = await res.json().catch(() => ({})); + if (res.ok) emitFriendsChanged(); return { ok: res.ok, ...data }; } catch { return { ok: false }; diff --git a/src/components/Chat/ChatContainer.css b/src/components/Chat/ChatContainer.css index 7a25a0a..35ef6df 100644 --- a/src/components/Chat/ChatContainer.css +++ b/src/components/Chat/ChatContainer.css @@ -432,8 +432,8 @@ body[data-theme="light"] .contacts-empty { .contacts-dropdown { right: 8px; - left: 8px; - width: auto; + left: auto; + width: 260px; bottom: 70px; } diff --git a/src/components/Chat/ChatContainer.jsx b/src/components/Chat/ChatContainer.jsx index c8cd1df..df2f91f 100644 --- a/src/components/Chat/ChatContainer.jsx +++ b/src/components/Chat/ChatContainer.jsx @@ -199,6 +199,15 @@ export default function ChatContainer() { }; loadFriends(); + + // Listen for friend changes to refresh the list + const handleFriendsChanged = () => { + loadFriends(); + }; + window.addEventListener("sociowire:friends-changed", handleFriendsChanged); + return () => { + window.removeEventListener("sociowire:friends-changed", handleFriendsChanged); + }; }, [authenticated]); // Load conversations to get initial unread counts diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index 471bffd..1a1a45b 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -194,25 +194,29 @@ function updateTruth(modalWrap, post) { if (label) label.textContent = `${score}`; } -function updateTruthVoteUI(modalWrap, stats) { +function updateTruthVoteUI(modalWrap, stats, updateScore = false) { if (!modalWrap || !stats) return; const aiEl = modalWrap.querySelector("[data-truth-ai]"); const userEl = modalWrap.querySelector("[data-truth-user]"); const countEl = modalWrap.querySelector("[data-truth-count]"); - const rail = modalWrap.querySelector(".sw-truth-rail"); - const marker = rail ? rail.querySelector(".sw-truth-rail-marker") : null; - const label = rail ? rail.querySelector(".sw-truth-rail-score") : null; const aiScore = Number.isFinite(stats.ai_score) ? Math.round(stats.ai_score) : stats.AIScore; const userScore = Number.isFinite(stats.user_score) ? Math.round(stats.user_score) : stats.UserScore; - const truthScoreValue = Number.isFinite(stats.truth_score) ? Math.round(stats.truth_score) : stats.TruthScore; const totalVotes = Number.isFinite(stats.total_votes) ? stats.total_votes : stats.TotalVotes; if (aiEl) aiEl.textContent = Number.isFinite(aiScore) ? String(aiScore) : "—"; if (userEl) userEl.textContent = Number.isFinite(userScore) ? String(userScore) : "—"; if (countEl) countEl.textContent = `${totalVotes || 0} votes`; - if (marker && Number.isFinite(truthScoreValue)) marker.style.top = `${100 - truthScoreValue}%`; - if (label && Number.isFinite(truthScoreValue)) label.textContent = `${truthScoreValue}`; + + // Only update the score display if explicitly requested (e.g., after user votes) + if (updateScore) { + const rail = modalWrap.querySelector(".sw-truth-rail"); + const marker = rail ? rail.querySelector(".sw-truth-rail-marker") : null; + const label = rail ? rail.querySelector(".sw-truth-rail-score") : null; + const truthScoreValue = Number.isFinite(stats.truth_score) ? Math.round(stats.truth_score) : stats.TruthScore; + if (marker && Number.isFinite(truthScoreValue)) marker.style.top = `${100 - truthScoreValue}%`; + if (label && Number.isFinite(truthScoreValue)) label.textContent = `${truthScoreValue}`; + } const vote = (stats.user_vote || stats.UserVote || "").toString(); const btns = modalWrap.querySelectorAll(".sw-truth-vote-btn"); @@ -1881,7 +1885,9 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { }); } - if (postID > 0) { + // Only refetch if initial data is missing critical fields + const hasCompleteData = postData?.title && (postData?.published_at || postData?.created_at); + if (postID > 0 && !hasCompleteData) { fetchPostById(postID).then((fresh) => { if (!fresh) return; postData = { ...postData, ...fresh }; @@ -1936,18 +1942,29 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) { } const rail = modalWrap.querySelector(".sw-truth-rail"); - if (rail && postID > 0) { + if (rail && postID > 0 && !rail.hasAttribute("data-vote-bound")) { + rail.setAttribute("data-vote-bound", "1"); const doVote = async (vote) => { if (!isAuthed()) { requestAuth("Please sign in or create a free account to vote on truth."); return; } + if (rail.classList.contains("sw-truth-vote-disabled")) { + return; // Already voting + } try { rail.classList.add("sw-truth-vote-disabled"); const stats = await votePostTruth(postID, vote); - if (stats) updateTruthVoteUI(modalWrap, stats); - } catch {} - rail.classList.remove("sw-truth-vote-disabled"); + if (stats) { + updateTruthVoteUI(modalWrap, stats, true); + } else { + console.warn("votePostTruth returned no stats for post", postID); + } + } catch (err) { + console.error("Truth vote error:", err); + } finally { + rail.classList.remove("sw-truth-vote-disabled"); + } }; const voteUp = rail.querySelector(".sw-truth-rail-btn-up"); const voteDown = rail.querySelector(".sw-truth-rail-btn-down"); diff --git a/src/styles/mapMarkers.css b/src/styles/mapMarkers.css index 2696ba2..34476f3 100644 --- a/src/styles/mapMarkers.css +++ b/src/styles/mapMarkers.css @@ -737,6 +737,7 @@ body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{ background:#0b1220; border:2px solid #fff; box-shadow: 0 4px 10px rgba(0,0,0,0.35); + top: 35%; } .sw-truth-rail-score{ @@ -750,6 +751,7 @@ body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{ margin: 0 auto; position: relative; left: -6px; + transition: opacity 0.2s ease-out; } .sw-truth-rail-btn{