175 lines
5.5 KiB
JavaScript
175 lines
5.5 KiB
JavaScript
import { gql, useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
|
import axios from "axios";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { connect } from "react-redux";
|
|
import { createStructuredSelector } from "reselect";
|
|
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
|
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
import ChatConversationComponent from "./chat-conversation.component";
|
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
selectedConversation: selectSelectedConversation,
|
|
bodyshop: selectBodyshop
|
|
});
|
|
|
|
function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
|
const client = useApolloClient();
|
|
const { socket } = useSocket();
|
|
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
|
|
|
|
// Fetch conversation details
|
|
const {
|
|
loading: convoLoading,
|
|
error: convoError,
|
|
data: convoData
|
|
} = useQuery(GET_CONVERSATION_DETAILS, {
|
|
variables: { conversationId: selectedConversation },
|
|
fetchPolicy: "network-only",
|
|
nextFetchPolicy: "network-only"
|
|
});
|
|
|
|
// Subscription for conversation updates
|
|
useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
|
|
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;
|
|
}
|
|
|
|
messages.forEach((message) => {
|
|
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({
|
|
id: client.cache.identify({ __typename: "conversations", id: selectedConversation }),
|
|
fields: {
|
|
messages(existingMessages = []) {
|
|
const alreadyExists = existingMessages.some((msg) => msg.__ref === messageRef);
|
|
if (alreadyExists) return existingMessages;
|
|
return [...existingMessages, { __ref: messageRef }];
|
|
},
|
|
updated_at() {
|
|
return message.created_at;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
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
|
|
}
|
|
});
|
|
});
|
|
},
|
|
[client.cache]
|
|
);
|
|
|
|
// WebSocket event handlers
|
|
useEffect(() => {
|
|
if (!socket?.connected) return;
|
|
|
|
const handleConversationChange = (data) => {
|
|
if (data.type === "conversation-marked-read") {
|
|
const { conversationId, messageIds } = data;
|
|
updateCacheWithReadMessages(conversationId, messageIds);
|
|
}
|
|
};
|
|
|
|
socket.on("conversation-changed", handleConversationChange);
|
|
|
|
return () => {
|
|
socket.off("conversation-changed", handleConversationChange);
|
|
};
|
|
}, [socket, updateCacheWithReadMessages]);
|
|
|
|
// Join and leave conversation via WebSocket
|
|
useEffect(() => {
|
|
if (!socket?.connected || !selectedConversation || !bodyshop?.id) return;
|
|
|
|
socket.emit("join-bodyshop-conversation", {
|
|
bodyshopId: bodyshop.id,
|
|
conversationId: selectedConversation
|
|
});
|
|
|
|
return () => {
|
|
socket.emit("leave-bodyshop-conversation", {
|
|
bodyshopId: bodyshop.id,
|
|
conversationId: selectedConversation
|
|
});
|
|
};
|
|
}, [socket, bodyshop, selectedConversation]);
|
|
|
|
// Mark conversation as read
|
|
const handleMarkConversationAsRead = async () => {
|
|
if (!convoData || markingAsReadInProgress) return;
|
|
|
|
const conversation = convoData.conversations_by_pk;
|
|
if (!conversation) return;
|
|
|
|
const unreadMessageIds = conversation.messages
|
|
?.filter((message) => !message.read && !message.isoutbound)
|
|
.map((message) => message.id);
|
|
|
|
if (unreadMessageIds?.length > 0) {
|
|
setMarkingAsReadInProgress(true);
|
|
try {
|
|
await axios.post("/sms/markConversationRead", {
|
|
conversation,
|
|
imexshopid: bodyshop?.imexshopid,
|
|
bodyshopid: bodyshop?.id
|
|
});
|
|
|
|
updateCacheWithReadMessages(selectedConversation, unreadMessageIds);
|
|
} catch (error) {
|
|
console.error("Error marking conversation as read:", error.message);
|
|
} finally {
|
|
setMarkingAsReadInProgress(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ChatConversationComponent
|
|
subState={[convoLoading, convoError]}
|
|
conversation={convoData?.conversations_by_pk || {}}
|
|
messages={convoData?.conversations_by_pk?.messages || []}
|
|
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default connect(mapStateToProps)(ChatConversationContainer);
|