diff --git a/client/src/components/chat-archive-button/chat-archive-button.component.jsx b/client/src/components/chat-archive-button/chat-archive-button.component.jsx index 2b8bcd054..6572cbefc 100644 --- a/client/src/components/chat-archive-button/chat-archive-button.component.jsx +++ b/client/src/components/chat-archive-button/chat-archive-button.component.jsx @@ -4,8 +4,17 @@ import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatArchiveButton({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatArchiveButton({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const { t } = useTranslation(); const [updateConversation] = useMutation(TOGGLE_CONVERSATION_ARCHIVE); @@ -36,3 +45,5 @@ export default function ChatArchiveButton({ conversation, bodyshop }) { ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatArchiveButton); diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx index a8f95f59d..fe71ee46e 100644 --- a/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx +++ b/client/src/components/chat-conversation-list/chat-conversation-list.component.jsx @@ -64,7 +64,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation, ); }; - // TODO: Can go back into virtuoso for additional fetch + // CAN DO: Can go back into virtuoso for additional fetch // endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom return ( diff --git a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx index 944ade8b0..821ad1603 100644 --- a/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx +++ b/client/src/components/chat-conversation-title-tags/chat-conversation-title-tags.component.jsx @@ -6,8 +6,17 @@ import { logImEXEvent } from "../../firebase/firebase.utils"; import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationTitleTags({ jobConversations, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationTitleTags({ jobConversations, bodyshop }) { const [removeJobConversation] = useMutation(REMOVE_CONVERSATION_TAG); const { socket } = useContext(SocketContext); @@ -66,3 +75,5 @@ export default function ChatConversationTitleTags({ jobConversations, bodyshop } ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitleTags); diff --git a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx index 7754ea347..7e5b045f5 100644 --- a/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx +++ b/client/src/components/chat-conversation-title/chat-conversation-title.component.jsx @@ -6,19 +6,27 @@ import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conv import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatPrintButton from "../chat-print-button/chat-print-button.component"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationTitle({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationTitle({ conversation }) { return ( {conversation && conversation.phone_num} - + - - - + + + ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationTitle); diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx index 4188fbabe..3334b5cbd 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -5,8 +5,17 @@ import ChatMessageListComponent from "../chat-messages-list/chat-message-list.co import ChatSendMessage from "../chat-send-message/chat-send-message.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; import "./chat-conversation.styles.scss"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatConversationComponent({ +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatConversationComponent({ subState, conversation, messages, @@ -31,3 +40,5 @@ export default function ChatConversationComponent({ ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationComponent); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 3b7f8385b..e2f7c6ae9 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -1,6 +1,6 @@ import { useApolloClient, useQuery } from "@apollo/client"; import axios from "axios"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useState } from "react"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import SocketContext from "../../contexts/SocketIO/socketContext"; @@ -14,8 +14,6 @@ const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); -export default connect(mapStateToProps, null)(ChatConversationContainer); - export function ChatConversationContainer({ bodyshop, selectedConversation }) { const client = useApolloClient(); const { socket } = useContext(SocketContext); @@ -30,38 +28,40 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { fetchPolicy: "network-only" }); - // Utility to update Apollo cache - const updateCacheWithReadMessages = (conversationId, messageIds) => { - if (!conversationId || !messageIds || messageIds.length === 0) return; + const updateCacheWithReadMessages = useCallback( + (conversationId, messageIds) => { + if (!conversationId || !messageIds || messageIds.length === 0) return; - messageIds.forEach((messageId) => { + // Mark individual messages as read + messageIds.forEach((messageId) => { + client.cache.modify({ + id: client.cache.identify({ __typename: "messages", id: messageId }), + fields: { + read() { + return true; // Mark message as read + } + } + }); + }); + + // Update aggregate unread count for the conversation client.cache.modify({ - id: client.cache.identify({ __typename: "messages", id: messageId }), + id: client.cache.identify({ __typename: "conversations", id: conversationId }), fields: { - read() { - return true; // Mark message as read + messages_aggregate(existingAggregate) { + return { + ...existingAggregate, + aggregate: { + ...existingAggregate.aggregate, + count: 0 // No unread messages remaining + } + }; } } }); - }); - - // Optionally update aggregate unread count - client.cache.modify({ - id: client.cache.identify({ __typename: "conversations", id: conversationId }), - fields: { - messages_aggregate(existingAggregate) { - const updatedAggregate = { - ...existingAggregate, - aggregate: { - ...existingAggregate.aggregate, - count: 0 // No unread messages remaining - } - }; - return updatedAggregate; - } - } - }); - }; + }, + [client.cache] + ); // Handle WebSocket events useEffect(() => { @@ -80,7 +80,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { return () => { socket.off("conversation-changed", handleConversationChange); }; - }, [socket, client]); + }, [socket, client, updateCacheWithReadMessages]); // Handle joining/leaving conversation useEffect(() => { @@ -143,7 +143,8 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) { conversation={convoData ? convoData.conversations_by_pk : {}} messages={convoData ? convoData.conversations_by_pk.messages : []} handleMarkConversationAsRead={handleMarkConversationAsRead} - bodyshop={bodyshop} /> ); } + +export default connect(mapStateToProps, null)(ChatConversationContainer); diff --git a/client/src/components/chat-label/chat-label.component.jsx b/client/src/components/chat-label/chat-label.component.jsx index e577bbf23..0764869ee 100644 --- a/client/src/components/chat-label/chat-label.component.jsx +++ b/client/src/components/chat-label/chat-label.component.jsx @@ -5,8 +5,17 @@ import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatLabel({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = (dispatch) => ({}); + +export function ChatLabel({ conversation, bodyshop }) { const [loading, setLoading] = useState(false); const [editing, setEditing] = useState(false); const [value, setValue] = useState(conversation.label); @@ -67,3 +76,5 @@ export default function ChatLabel({ conversation, bodyshop }) { ); } } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatLabel); diff --git a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx index 6e01ea5ed..8d3476c7d 100644 --- a/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx +++ b/client/src/components/chat-tag-ro/chat-tag-ro.container.jsx @@ -9,8 +9,17 @@ import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries"; import ChatTagRo from "./chat-tag-ro.component"; import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors.js"; +import { connect } from "react-redux"; -export default function ChatTagRoContainer({ conversation, bodyshop }) { +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); + +const mapDispatchToProps = () => ({}); + +export function ChatTagRoContainer({ conversation, bodyshop }) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const { socket } = useContext(SocketContext); @@ -86,3 +95,5 @@ export default function ChatTagRoContainer({ conversation, bodyshop }) { ); } + +export default connect(mapStateToProps, mapDispatchToProps)(ChatTagRoContainer);