Merged in release/2024-11-22 (pull request #1975)

Release/2024 11 22 into test-AIO - IO-3000
This commit is contained in:
Dave Richer
2024-11-28 20:18:56 +00:00
2 changed files with 76 additions and 48 deletions

View File

@@ -1,6 +1,6 @@
// Scripts for firebase and firebase messaging // Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js"); importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging.js"); importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
// Initialize the Firebase app in the service worker by passing the generated config // Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig; let firebaseConfig;

View File

@@ -1,10 +1,10 @@
import { useApolloClient, useQuery } from "@apollo/client"; import { gql, useApolloClient, useQuery, useSubscription } from "@apollo/client";
import axios from "axios"; import axios from "axios";
import React, { useCallback, useContext, useEffect, useState } from "react"; import React, { useCallback, useContext, useEffect, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketContext from "../../contexts/SocketIO/socketContext"; import SocketContext from "../../contexts/SocketIO/socketContext";
import { GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries"; import { GET_CONVERSATION_DETAILS, CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ChatConversationComponent from "./chat-conversation.component"; import ChatConversationComponent from "./chat-conversation.component";
@@ -14,11 +14,12 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
export function ChatConversationContainer({ bodyshop, selectedConversation }) { function ChatConversationContainer({ bodyshop, selectedConversation }) {
const client = useApolloClient(); const client = useApolloClient();
const { socket } = useContext(SocketContext); const { socket } = useContext(SocketContext);
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false); const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
// Fetch conversation details
const { const {
loading: convoLoading, loading: convoLoading,
error: convoError, error: convoError,
@@ -29,49 +30,85 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
nextFetchPolicy: "network-only" nextFetchPolicy: "network-only"
}); });
const updateCacheWithReadMessages = useCallback( // Subscription for conversation updates
(conversationId, messageIds) => { useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
if (!conversationId || !messageIds || messageIds.length === 0) return; skip: socket?.connected,
variables: { conversationId: selectedConversation },
onData: ({ data: subscriptionResult, client }) => {
// Extract the messages array from the result
const messages = subscriptionResult?.data?.messages;
if (!messages || messages.length === 0) {
console.warn("No messages found in subscription result.");
return;
}
// Mark individual messages as read messages.forEach((message) => {
messageIds.forEach((messageId) => { const messageRef = client.cache.identify(message);
// Write the new message to the cache
client.cache.writeFragment({
id: messageRef,
fragment: gql`
fragment NewMessage on messages {
id
status
text
isoutbound
image
image_path
userid
created_at
read
}
`,
data: message
});
// Update the conversation cache to include the new message
client.cache.modify({ client.cache.modify({
id: client.cache.identify({ __typename: "messages", id: messageId }), id: client.cache.identify({ __typename: "conversations", id: selectedConversation }),
fields: { fields: {
read() { messages(existingMessages = []) {
return true; // Mark message as read const alreadyExists = existingMessages.some((msg) => msg.__ref === messageRef);
if (alreadyExists) return existingMessages;
return [...existingMessages, { __ref: messageRef }];
} }
} }
}); });
}); });
}
});
const updateCacheWithReadMessages = useCallback(
(conversationId, messageIds) => {
if (!conversationId || !messageIds?.length) return;
messageIds.forEach((messageId) => {
client.cache.modify({
id: client.cache.identify({ __typename: "messages", id: messageId }),
fields: {
read: () => true
}
});
});
// Update aggregate unread count for the conversation
client.cache.modify({ client.cache.modify({
id: client.cache.identify({ __typename: "conversations", id: conversationId }), id: client.cache.identify({ __typename: "conversations", id: conversationId }),
fields: { fields: {
messages_aggregate(existingAggregate) { unreadcnt: () => 0
return {
...existingAggregate,
aggregate: {
...existingAggregate.aggregate,
count: 0 // No unread messages remaining
}
};
}
} }
}); });
}, },
[client.cache] [client.cache]
); );
// Handle WebSocket events // WebSocket event handlers
useEffect(() => { useEffect(() => {
if (!socket || !socket.connected) return; if (!socket?.connected) return;
const handleConversationChange = (data) => { const handleConversationChange = (data) => {
if (data.type === "conversation-marked-read") { if (data.type === "conversation-marked-read") {
const { conversationId, messageIds } = data; const { conversationId, messageIds } = data;
console.log("Conversation change received:", data);
updateCacheWithReadMessages(conversationId, messageIds); updateCacheWithReadMessages(conversationId, messageIds);
} }
}; };
@@ -81,11 +118,11 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
return () => { return () => {
socket.off("conversation-changed", handleConversationChange); socket.off("conversation-changed", handleConversationChange);
}; };
}, [socket, client, updateCacheWithReadMessages]); }, [socket, updateCacheWithReadMessages]);
// Handle joining/leaving conversation // Join and leave conversation via WebSocket
useEffect(() => { useEffect(() => {
if (!socket || !socket.connected) return; if (!socket?.connected || !selectedConversation || !bodyshop?.id) return;
socket.emit("join-bodyshop-conversation", { socket.emit("join-bodyshop-conversation", {
bodyshopId: bodyshop.id, bodyshopId: bodyshop.id,
@@ -98,17 +135,14 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
conversationId: selectedConversation conversationId: selectedConversation
}); });
}; };
}, [selectedConversation, bodyshop, socket]); }, [socket, bodyshop, selectedConversation]);
// Handle marking conversation as read // Mark conversation as read
const handleMarkConversationAsRead = async () => { const handleMarkConversationAsRead = async () => {
if (!convoData || !selectedConversation || markingAsReadInProgress) return; if (!convoData || markingAsReadInProgress) return;
const conversation = convoData.conversations_by_pk; const conversation = convoData.conversations_by_pk;
if (!conversation) { if (!conversation) return;
console.warn(`No data found for conversation ID: ${selectedConversation}`);
return;
}
const unreadMessageIds = conversation.messages const unreadMessageIds = conversation.messages
?.filter((message) => !message.read && !message.isoutbound) ?.filter((message) => !message.read && !message.isoutbound)
@@ -116,22 +150,16 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
if (unreadMessageIds?.length > 0) { if (unreadMessageIds?.length > 0) {
setMarkingAsReadInProgress(true); setMarkingAsReadInProgress(true);
try { try {
const payload = { await axios.post("/sms/markConversationRead", {
conversation, conversation,
imexshopid: bodyshop?.imexshopid, imexshopid: bodyshop?.imexshopid,
bodyshopid: bodyshop?.id bodyshopid: bodyshop?.id
}; });
console.log("Marking conversation as read:", payload);
await axios.post("/sms/markConversationRead", payload);
// Update local cache
updateCacheWithReadMessages(selectedConversation, unreadMessageIds); updateCacheWithReadMessages(selectedConversation, unreadMessageIds);
} catch (error) { } catch (error) {
console.error("Error marking conversation as read:", error.response?.data || error.message); console.error("Error marking conversation as read:", error.message);
} finally { } finally {
setMarkingAsReadInProgress(false); setMarkingAsReadInProgress(false);
} }
@@ -141,11 +169,11 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
return ( return (
<ChatConversationComponent <ChatConversationComponent
subState={[convoLoading, convoError]} subState={[convoLoading, convoError]}
conversation={convoData ? convoData.conversations_by_pk : {}} conversation={convoData?.conversations_by_pk || {}}
messages={convoData ? convoData.conversations_by_pk.messages : []} messages={convoData?.conversations_by_pk?.messages || []}
handleMarkConversationAsRead={handleMarkConversationAsRead} handleMarkConversationAsRead={handleMarkConversationAsRead}
/> />
); );
} }
export default connect(mapStateToProps, null)(ChatConversationContainer); export default connect(mapStateToProps)(ChatConversationContainer);