diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx
index 01c5b3e85..95513e09c 100644
--- a/client/src/components/chat-popup/chat-popup.component.jsx
+++ b/client/src/components/chat-popup/chat-popup.component.jsx
@@ -1,7 +1,7 @@
import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined } from "@ant-design/icons";
import { useApolloClient, useLazyQuery, useQuery } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -27,32 +27,52 @@ const mapDispatchToProps = (dispatch) => ({
export function ChatPopupComponent({ chatVisible, selectedConversation, toggleChatVisible }) {
const { t } = useTranslation();
- const [pollInterval, setPollInterval] = useState(0);
const { socket } = useSocket();
- const client = useApolloClient(); // Apollo Client instance for cache operations
+ const client = useApolloClient();
- // Lazy query for conversations
- const [getConversations, { loading, data, refetch }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
+ // When socket is connected, we do NOT poll (socket should push updates).
+ // When disconnected, we poll as a fallback.
+ const [pollInterval, setPollInterval] = useState(0);
+
+ // Ensure conversations query runs once on initial page load (component mount).
+ const hasLoadedConversationsOnceRef = useRef(false);
+
+ // Preserve the last known unread aggregate count so the badge doesn't "vanish"
+ // when UNREAD_CONVERSATION_COUNT gets skipped after socket connects.
+ const [unreadAggregateCount, setUnreadAggregateCount] = useState(0);
+
+ // Lazy query for conversations (executed manually)
+ const [getConversations, { loading, data, refetch, called }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
- skip: !chatVisible,
+ notifyOnNetworkStatusChange: true,
...(pollInterval > 0 ? { pollInterval } : {})
});
- // Query for unread count when chat is not visible
- const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
+ // Query for unread count when chat is not visible and socket is not connected.
+ // (Once socket connects, we stop this query; we keep the last known value in state.)
+ useQuery(UNREAD_CONVERSATION_COUNT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
- pollInterval: 60 * 1000 // TODO: This is a fix for now, should be coming from sockets
+ skip: chatVisible || socket?.connected,
+ pollInterval: socket?.connected ? 0 : 60 * 1000,
+ onCompleted: (result) => {
+ const nextCount = result?.messages_aggregate?.aggregate?.count;
+ if (typeof nextCount === "number") setUnreadAggregateCount(nextCount);
+ },
+ onError: (err) => {
+ // Keep last known count; do not force badge to zero on transient failures
+ console.warn("UNREAD_CONVERSATION_COUNT failed:", err?.message || err);
+ }
});
- // Socket connection status
+ // Socket connection status -> polling strategy for CONVERSATION_LIST_QUERY
useEffect(() => {
const handleSocketStatus = () => {
if (socket?.connected) {
- setPollInterval(15 * 60 * 1000); // 15 minutes
+ setPollInterval(0); // skip polling if socket connected
} else {
- setPollInterval(60 * 1000); // 60 seconds
+ setPollInterval(60 * 1000); // fallback polling if disconnected
}
};
@@ -71,19 +91,32 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
};
}, [socket]);
- // Fetch conversations when chat becomes visible
+ // Run conversations query exactly once on initial load (component mount)
useEffect(() => {
- if (chatVisible)
- getConversations({
- variables: {
- offset: 0
- }
- }).catch((err) => {
- console.error(`Error fetching conversations: ${(err, err.message || "")}`);
- });
- }, [chatVisible, getConversations]);
+ if (hasLoadedConversationsOnceRef.current) return;
- // Get unread count from the cache
+ hasLoadedConversationsOnceRef.current = true;
+
+ getConversations({
+ variables: { offset: 0 }
+ }).catch((err) => {
+ console.error(`Error fetching conversations: ${err?.message || ""}`, err);
+ });
+ }, [getConversations]);
+
+ const handleManualRefresh = async () => {
+ try {
+ if (called && typeof refetch === "function") {
+ await refetch({ offset: 0 });
+ } else {
+ await getConversations({ variables: { offset: 0 } });
+ }
+ } catch (err) {
+ console.error(`Error refreshing conversations: ${err?.message || ""}`, err);
+ }
+ };
+
+ // Get unread count from the cache (preferred). Fallback to preserved aggregate count.
const unreadCount = (() => {
try {
const cachedData = client.readQuery({
@@ -91,18 +124,23 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
variables: { offset: 0 }
});
- if (!cachedData?.conversations) {
- return unreadData?.messages_aggregate?.aggregate?.count;
+ const conversations = cachedData?.conversations;
+
+ if (!Array.isArray(conversations) || conversations.length === 0) {
+ return unreadAggregateCount;
}
- // Aggregate unread message count
- return cachedData.conversations.reduce((total, conversation) => {
- const unread = conversation.messages_aggregate?.aggregate?.count || 0;
+ const hasUnreadCounts = conversations.some((c) => c?.messages_aggregate?.aggregate?.count != null);
+ if (!hasUnreadCounts) {
+ return unreadAggregateCount;
+ }
+
+ return conversations.reduce((total, conversation) => {
+ const unread = conversation?.messages_aggregate?.aggregate?.count || 0;
return total + unread;
}, 0);
- } catch (error) {
- console.warn("Unread count not found in cache:", error);
- return 0; // Fallback if not in cache
+ } catch {
+ return unreadAggregateCount;
}
})();
@@ -117,9 +155,12 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
- refetch()} />
+
+
+
{!socket?.connected && {t("messaging.labels.nopush")}}
+
toggleChatVisible()}
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}