From dcfcf71ca4109612d882eb72a52c93aa8779aa45 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 29 Apr 2020 16:46:23 -0700 Subject: [PATCH] Refactored Messaging as a part of BOD-14. Breaking changes remain. --- .../chat-affix/chat-affix.component.jsx | 43 ++++ .../chat-affix/chat-affix.container.jsx | 32 +++ .../chat-conversation-list.component.jsx | 64 +++--- .../chat-conversation-list.styles.scss | 10 + .../chat-conversation.closed.component.jsx | 38 ---- .../chat-conversation.component.jsx | 26 +-- .../chat-conversation.container.jsx | 26 ++- .../chat-conversation.open.component.jsx | 36 ---- .../chat-dock/chat-dock.container.jsx | 32 --- .../chat-dock/chat-dock.styles.scss | 188 ------------------ .../chat-messages-button.component.jsx | 47 ----- .../chat-messages-button.container.jsx | 27 --- .../chat-message-list.component.jsx | 6 +- .../chat-message-list.styles.scss | 128 ++++++++++++ .../chat-open-button.component.jsx | 22 +- .../chat-popup/chat-popup.component.jsx | 49 +++++ .../chat-popup/chat-popup.styles.scss | 4 + .../chat-send-message.component.jsx | 14 +- client/src/graphql/conversations.queries.js | 2 + .../pages/manage/manage.page.component.jsx | 6 +- .../src/redux/messaging/messaging.actions.js | 33 +-- .../src/redux/messaging/messaging.reducer.js | 84 +------- .../redux/messaging/messaging.selectors.js | 18 +- client/src/redux/messaging/messaging.types.js | 7 +- 24 files changed, 393 insertions(+), 549 deletions(-) create mode 100644 client/src/components/chat-affix/chat-affix.component.jsx create mode 100644 client/src/components/chat-affix/chat-affix.container.jsx create mode 100644 client/src/components/chat-conversation-list/chat-conversation-list.styles.scss delete mode 100644 client/src/components/chat-conversation/chat-conversation.closed.component.jsx delete mode 100644 client/src/components/chat-conversation/chat-conversation.open.component.jsx delete mode 100644 client/src/components/chat-dock/chat-dock.container.jsx delete mode 100644 client/src/components/chat-dock/chat-dock.styles.scss delete mode 100644 client/src/components/chat-messages-button/chat-messages-button.component.jsx delete mode 100644 client/src/components/chat-messages-button/chat-messages-button.container.jsx create mode 100644 client/src/components/chat-messages-list/chat-message-list.styles.scss create mode 100644 client/src/components/chat-popup/chat-popup.component.jsx create mode 100644 client/src/components/chat-popup/chat-popup.styles.scss diff --git a/client/src/components/chat-affix/chat-affix.component.jsx b/client/src/components/chat-affix/chat-affix.component.jsx new file mode 100644 index 000000000..2e29c37a6 --- /dev/null +++ b/client/src/components/chat-affix/chat-affix.component.jsx @@ -0,0 +1,43 @@ +import { MessageOutlined } from "@ant-design/icons"; +import { Badge, Card } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; +import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; +import ChatPopupComponent from '../chat-popup/chat-popup.component' + +const mapStateToProps = createStructuredSelector({ + chatVisible: selectChatVisible, +}); +const mapDispatchToProps = (dispatch) => ({ + toggleChatVisible: () => dispatch(toggleChatVisible()), +}); + +export function ChatAffixComponent({ + chatVisible, + toggleChatVisible, + conversationList, + unreadCount, +}) { + const { t } = useTranslation(); + + return ( + + + {chatVisible ? ( + + ) : ( +
toggleChatVisible()} + style={{ cursor: "pointer" }}> + + {t("messaging.labels.messaging")} +
+ )} +
+
+ ); +} +export default connect(mapStateToProps, mapDispatchToProps)(ChatAffixComponent); diff --git a/client/src/components/chat-affix/chat-affix.container.jsx b/client/src/components/chat-affix/chat-affix.container.jsx new file mode 100644 index 000000000..4787c6d90 --- /dev/null +++ b/client/src/components/chat-affix/chat-affix.container.jsx @@ -0,0 +1,32 @@ +import { useSubscription } from "@apollo/react-hooks"; +import React from "react"; +import { CONVERSATION_LIST_SUBSCRIPTION } from "../../graphql/conversations.queries"; +import AlertComponent from "../alert/alert.component"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import ChatAffixComponent from "./chat-affix.component"; +import { Affix } from "antd"; + +export default function ChatAffixContainer() { + const { loading, error, data } = useSubscription( + CONVERSATION_LIST_SUBSCRIPTION + ); + if (loading) return ; + if (error) return ; + + return ( + +
+ { + return (acc = acc + val.messages_aggregate.aggregate.count); + }, 0)) || + 0 + } + /> +
+
+ ); +} 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 5b76a552f..e115116a9 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 @@ -1,41 +1,45 @@ -import { ShrinkOutlined } from "@ant-design/icons"; -import { Badge } from "antd"; +import { Badge, List } from "antd"; import React from "react"; import { connect } from "react-redux"; -import { - openConversation, - toggleChatVisible -} from "../../redux/messaging/messaging.actions"; -import PhoneNumberFormatter from "../../utils/PhoneFormatter"; +import { setSelectedConversation } from "../../redux/messaging/messaging.actions"; +import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; +import { createStructuredSelector } from "reselect"; +import "./chat-conversation-list.styles.scss"; -const mapDispatchToProps = dispatch => ({ - toggleChatVisible: () => dispatch(toggleChatVisible()), - openConversation: number => dispatch(openConversation(number)) +const mapStateToProps = createStructuredSelector({ + selectedConversation: selectSelectedConversation, +}); + +const mapDispatchToProps = (dispatch) => ({ + setSelectedConversation: (conversationId) => + dispatch(setSelectedConversation(conversationId)), }); export function ChatConversationListComponent({ - toggleChatVisible, conversationList, - openConversation + selectedConversation, + setSelectedConversation, }) { return ( -
- toggleChatVisible()} /> - {conversationList.map(item => ( - -
- openConversation({ phone_num: item.phone_num, id: item.id }) - }> -
- {item.phone_num} -
-
-
- ))} -
+ ( + setSelectedConversation(item.id)} + className={`chat-list-item ${ + item.id === selectedConversation + ? "chat-list-selected-conversation" + : null + }`}> + {item.phone_num} + + + )} + /> ); } -export default connect(null, mapDispatchToProps)(ChatConversationListComponent); +export default connect( + mapStateToProps, + mapDispatchToProps +)(ChatConversationListComponent); diff --git a/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss new file mode 100644 index 000000000..724f1daf6 --- /dev/null +++ b/client/src/components/chat-conversation-list/chat-conversation-list.styles.scss @@ -0,0 +1,10 @@ +.chat-list-selected-conversation { + background-color: rgba(128, 128, 128, 0.2); +} + +.chat-list-item { + &:hover { + cursor: pointer; + color: #ff7a00; + } +} diff --git a/client/src/components/chat-conversation/chat-conversation.closed.component.jsx b/client/src/components/chat-conversation/chat-conversation.closed.component.jsx deleted file mode 100644 index ea6ccce1b..000000000 --- a/client/src/components/chat-conversation/chat-conversation.closed.component.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import { CloseCircleFilled } from "@ant-design/icons"; -import React from "react"; -import { connect } from "react-redux"; -import { - closeConversation, - sendMessage, - toggleConversationVisible -} from "../../redux/messaging/messaging.actions"; -import PhoneFormatter from "../../utils/PhoneFormatter"; - -const mapDispatchToProps = dispatch => ({ - toggleConversationVisible: conversationId => - dispatch(toggleConversationVisible(conversationId)), - closeConversation: phone => dispatch(closeConversation(phone)), - sendMessage: message => dispatch(sendMessage(message)) -}); - -function ChatConversationClosedComponent({ - conversation, - toggleConversationVisible, - closeConversation -}) { - return ( -
toggleConversationVisible(conversation.id)}> - {conversation.phone_num} - closeConversation(conversation.phone_num)} - /> -
- ); -} - -export default connect( - null, - mapDispatchToProps -)(ChatConversationClosedComponent); diff --git a/client/src/components/chat-conversation/chat-conversation.component.jsx b/client/src/components/chat-conversation/chat-conversation.component.jsx index b017f6d19..c9445ef63 100644 --- a/client/src/components/chat-conversation/chat-conversation.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.component.jsx @@ -1,27 +1,27 @@ import { Badge, Card } from "antd"; import React from "react"; -import ChatConversationClosedComponent from "./chat-conversation.closed.component"; -import ChatConversationOpenComponent from "./chat-conversation.open.component"; +import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component"; +import ChatSendMessage from "../chat-send-message/chat-send-message.component"; +import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx"; +import AlertComponent from "../alert/alert.component"; export default function ChatConversationComponent({ - conversation, messages, subState, - unreadCount + conversation, + unreadCount, }) { + const [loading, error] = subState; + + if (loading) return ; + if (error) return ; + return (
+ - {conversation.open ? ( - - ) : ( - - )} +
diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 90d0ae032..f3ee27d8d 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -3,18 +3,30 @@ import React from "react"; import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries"; import ChatConversationComponent from "./chat-conversation.component"; -export default function ChatConversationContainer({ conversation }) { +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; +const mapStateToProps = createStructuredSelector({ + selectedConversation: selectSelectedConversation, +}); + +export default connect(mapStateToProps, null)(ChatConversationContainer); + +export function ChatConversationContainer({ selectedConversation }) { + console.log( + "ChatConversationContainer -> selectedConversation", + selectedConversation + ); const { loading, error, data } = useSubscription( CONVERSATION_SUBSCRIPTION_BY_PK, { - variables: { conversationId: conversation.id } + variables: { conversationId: selectedConversation }, } ); return ( ({ - toggleConversationVisible: conversation => - dispatch(toggleConversationVisible(conversation)) -}); - -export function ChatConversationOpenComponent({ - conversation, - messages, - subState, - toggleConversationVisible -}) { - const [loading, error] = subState; - - if (loading) return ; - if (error) return ; - - return ( -
- toggleConversationVisible(conversation.id)} - /> - - -
- ); -} -export default connect(null, mapDispatchToProps)(ChatConversationOpenComponent); diff --git a/client/src/components/chat-dock/chat-dock.container.jsx b/client/src/components/chat-dock/chat-dock.container.jsx deleted file mode 100644 index 8357a149c..000000000 --- a/client/src/components/chat-dock/chat-dock.container.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Affix } from "antd"; -import React from "react"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectConversations } from "../../redux/messaging/messaging.selectors"; -import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; -import ChatMessagesButtonContainer from "../chat-messages-button/chat-messages-button.container"; -import "./chat-dock.styles.scss"; - -const mapStateToProps = createStructuredSelector({ - activeConversations: selectConversations -}); - -export function ChatOverlayContainer({ activeConversations }) { - return ( - -
- - {activeConversations - ? activeConversations.map(conversation => ( - - )) - : null} -
-
- ); -} - -export default connect(mapStateToProps, null)(ChatOverlayContainer); diff --git a/client/src/components/chat-dock/chat-dock.styles.scss b/client/src/components/chat-dock/chat-dock.styles.scss deleted file mode 100644 index 97ab7395c..000000000 --- a/client/src/components/chat-dock/chat-dock.styles.scss +++ /dev/null @@ -1,188 +0,0 @@ -.chat-dock { - z-index: 5; - //overflow-x: scroll; - // overflow-y: hidden; - width: 100%; - display: flex; - align-items: baseline; -} - -.chat-conversation { - margin: 2em 1em 0em 1em; -} - -.chat-conversation-open { - height: 500px; -} -// .chat-messages { -// height: 80%; -// overflow-x: hidden; -// overflow-y: scroll; -// flex-grow: 1; - -// ul { -// list-style: none; -// margin: 0; -// padding: 0; -// } - -// ul li { -// display: inline-block; -// clear: both; -// padding: 3px 10px; -// border-radius: 30px; -// margin-bottom: 2px; -// } - -// .inbound { -// background: #eee; -// float: left; -// } - -// .outbound { -// float: right; -// background: #0084ff; -// color: #fff; -// } - -// .inbound + .outbound { -// border-bottom-right-radius: 5px; -// } - -// .outbound + .outbound { -// border-top-right-radius: 5px; -// border-bottom-right-radius: 5px; -// } - -// .outbound:last-of-type { -// border-bottom-right-radius: 30px; -// } -// } - -.messages { - height: auto; - min-height: calc(100% - 10px); - max-height: calc(100% - 93px); - overflow-y: scroll; - overflow-x: hidden; -} -@media screen and (max-width: 735px) { - .messages { - max-height: calc(100% - 105px); - } -} -.messages::-webkit-scrollbar { - width: 8px; - background: transparent; -} -.messages::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.3); -} -.messages ul li { - display: inline-block; - clear: both; - //float: left; - margin: 5px; - width: calc(100% - 25px); - font-size: 0.9em; -} -.messages ul li:nth-last-child(1) { - margin-bottom: 20px; -} -.messages ul li.sent img { - margin: 6px 8px 0 0; -} -.messages ul li.sent p { - background: #435f7a; - color: #f5f5f5; -} -.messages ul li.replies img { - float: right; - margin: 6px 0 0 8px; -} -.messages ul li.replies p { - background: #f5f5f5; - float: right; -} -.messages ul li img { - width: 22px; - border-radius: 50%; - float: left; -} -.messages ul li p { - display: inline-block; - padding: 10px 15px; - border-radius: 20px; - max-width: 205px; - line-height: 130%; -} -@media screen and (min-width: 735px) { - .messages ul li p { - max-width: 300px; - } -} -.message-input { - position: absolute; - bottom: 0; - width: 100%; - z-index: 99; -} -.message-input .wrap { - position: relative; -} -.message-input .wrap input { - font-family: "proxima-nova", "Source Sans Pro", sans-serif; - float: left; - border: none; - width: calc(100% - 90px); - padding: 11px 32px 10px 8px; - font-size: 0.8em; - color: #32465a; -} -@media screen and (max-width: 735px) { - .message-input .wrap input { - padding: 15px 32px 16px 8px; - } -} -.message-input .wrap input:focus { - outline: none; -} -.message-input .wrap .attachment { - position: absolute; - right: 60px; - z-index: 4; - margin-top: 10px; - font-size: 1.1em; - color: #435f7a; - opacity: 0.5; - cursor: pointer; -} -@media screen and (max-width: 735px) { - .message-input .wrap .attachment { - margin-top: 17px; - right: 65px; - } -} -.message-input .wrap .attachment:hover { - opacity: 1; -} -.message-input .wrap button { - float: right; - border: none; - width: 50px; - padding: 12px 0; - cursor: pointer; - background: #32465a; - color: #f5f5f5; -} -@media screen and (max-width: 735px) { - .message-input .wrap button { - padding: 16px 0; - } -} -.message-input .wrap button:hover { - background: #435f7a; -} -.message-input .wrap button:focus { - outline: none; -} diff --git a/client/src/components/chat-messages-button/chat-messages-button.component.jsx b/client/src/components/chat-messages-button/chat-messages-button.component.jsx deleted file mode 100644 index 8b95eb12b..000000000 --- a/client/src/components/chat-messages-button/chat-messages-button.component.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import { MessageFilled } from "@ant-design/icons"; -import { Badge, Card } from "antd"; -import React from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; -import { selectChatVisible } from "../../redux/messaging/messaging.selectors"; -import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; - -const mapStateToProps = createStructuredSelector({ - chatVisible: selectChatVisible -}); -const mapDispatchToProps = dispatch => ({ - toggleChatVisible: () => dispatch(toggleChatVisible()) -}); - -export function ChatWindowComponent({ - chatVisible, - toggleChatVisible, - conversationList, - unreadCount -}) { - const { t } = useTranslation(); - return ( -
- - - {chatVisible ? ( - - ) : ( -
toggleChatVisible()}> - - {t("messaging.labels.messaging")} -
- )} -
-
-
- ); -} -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatWindowComponent); diff --git a/client/src/components/chat-messages-button/chat-messages-button.container.jsx b/client/src/components/chat-messages-button/chat-messages-button.container.jsx deleted file mode 100644 index a8f3ffe88..000000000 --- a/client/src/components/chat-messages-button/chat-messages-button.container.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useSubscription } from "@apollo/react-hooks"; -import React from "react"; -import { CONVERSATION_LIST_SUBSCRIPTION } from "../../graphql/conversations.queries"; -import AlertComponent from "../alert/alert.component"; -import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import ChatMessagesButtonComponent from "./chat-messages-button.component"; - -export default function ChatMessagesButtonContainer() { - const { loading, error, data } = useSubscription( - CONVERSATION_LIST_SUBSCRIPTION - ); - if (loading) return ; - if (error) return ; - - return ( - { - return (acc = acc + val.messages_aggregate.aggregate.count); - }, 0)) || - 0 - } - /> - ); -} diff --git a/client/src/components/chat-messages-list/chat-message-list.component.jsx b/client/src/components/chat-messages-list/chat-message-list.component.jsx index 9e69d7af4..59a7a04f6 100644 --- a/client/src/components/chat-messages-list/chat-message-list.component.jsx +++ b/client/src/components/chat-messages-list/chat-message-list.component.jsx @@ -1,17 +1,17 @@ import { CheckCircleOutlined, CheckOutlined } from "@ant-design/icons"; import React, { useEffect, useRef } from "react"; +import "./chat-message-list.styles.scss"; export default function ChatMessageListComponent({ messages }) { const messagesEndRef = useRef(null); const scrollToBottom = () => { - console.log("use"); !!messagesEndRef.current && messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); }; useEffect(scrollToBottom, [messages]); - const StatusRender = status => { + const StatusRender = (status) => { switch (status) { case "sent": return ; @@ -27,7 +27,7 @@ export default function ChatMessageListComponent({ messages }) { return (
    - {messages.map(item => ( + {messages.map((item) => (
  • diff --git a/client/src/components/chat-messages-list/chat-message-list.styles.scss b/client/src/components/chat-messages-list/chat-message-list.styles.scss new file mode 100644 index 000000000..bb66601e6 --- /dev/null +++ b/client/src/components/chat-messages-list/chat-message-list.styles.scss @@ -0,0 +1,128 @@ +.messages { + height: auto; + min-height: calc(100% - 10px); + max-height: calc(100% - 93px); + overflow-y: scroll; + overflow-x: hidden; + } + @media screen and (max-width: 735px) { + .messages { + max-height: calc(100% - 105px); + } + } + .messages::-webkit-scrollbar { + width: 8px; + background: transparent; + } + .messages::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.3); + } + .messages ul li { + display: inline-block; + clear: both; + //float: left; + margin: 5px; + width: calc(100% - 25px); + font-size: 0.9em; + } + .messages ul li:nth-last-child(1) { + margin-bottom: 20px; + } + .messages ul li.sent img { + margin: 6px 8px 0 0; + } + .messages ul li.sent p { + background: #435f7a; + color: #f5f5f5; + } + .messages ul li.replies img { + float: right; + margin: 6px 0 0 8px; + } + .messages ul li.replies p { + background: #f5f5f5; + float: right; + } + .messages ul li img { + width: 22px; + border-radius: 50%; + float: left; + } + .messages ul li p { + display: inline-block; + padding: 10px 15px; + border-radius: 20px; + max-width: 205px; + line-height: 130%; + } + @media screen and (min-width: 735px) { + .messages ul li p { + max-width: 300px; + } + } + .message-input { + position: absolute; + bottom: 0; + width: 100%; + z-index: 99; + } + .message-input .wrap { + position: relative; + } + .message-input .wrap input { + font-family: "proxima-nova", "Source Sans Pro", sans-serif; + float: left; + border: none; + width: calc(100% - 90px); + padding: 11px 32px 10px 8px; + font-size: 0.8em; + color: #32465a; + } + @media screen and (max-width: 735px) { + .message-input .wrap input { + padding: 15px 32px 16px 8px; + } + } + .message-input .wrap input:focus { + outline: none; + } + .message-input .wrap .attachment { + position: absolute; + right: 60px; + z-index: 4; + margin-top: 10px; + font-size: 1.1em; + color: #435f7a; + opacity: 0.5; + cursor: pointer; + } + @media screen and (max-width: 735px) { + .message-input .wrap .attachment { + margin-top: 17px; + right: 65px; + } + } + .message-input .wrap .attachment:hover { + opacity: 1; + } + .message-input .wrap button { + float: right; + border: none; + width: 50px; + padding: 12px 0; + cursor: pointer; + background: #32465a; + color: #f5f5f5; + } + @media screen and (max-width: 735px) { + .message-input .wrap button { + padding: 16px 0; + } + } + .message-input .wrap button:hover { + background: #435f7a; + } + .message-input .wrap button:focus { + outline: none; + } + \ No newline at end of file diff --git a/client/src/components/chat-open-button/chat-open-button.component.jsx b/client/src/components/chat-open-button/chat-open-button.component.jsx index 236be79ed..d9f9d77ec 100644 --- a/client/src/components/chat-open-button/chat-open-button.component.jsx +++ b/client/src/components/chat-open-button/chat-open-button.component.jsx @@ -1,22 +1,16 @@ +import { MessageFilled } from "@ant-design/icons"; import React from "react"; import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { openConversation } from "../../redux/messaging/messaging.actions"; -import { MessageFilled } from "@ant-design/icons"; -const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser + +const mapDispatchToProps = (dispatch) => ({ + //openConversation: (phone) => dispatch(openConversation(phone)), }); -const mapDispatchToProps = dispatch => ({ - openConversation: phone => dispatch(openConversation(phone)) -}); -export default connect( - mapStateToProps, - mapDispatchToProps -)(function ChatOpenButton({ openConversation, phone }) { +export function ChatOpenButton({ phone }) { return ( openConversation(phone)} + onClick={() => alert("TODO FIX ME" + phone)} /> ); -}); +} +export default connect(null, mapDispatchToProps)(ChatOpenButton); diff --git a/client/src/components/chat-popup/chat-popup.component.jsx b/client/src/components/chat-popup/chat-popup.component.jsx new file mode 100644 index 000000000..d6810c107 --- /dev/null +++ b/client/src/components/chat-popup/chat-popup.component.jsx @@ -0,0 +1,49 @@ +import { ShrinkOutlined } from "@ant-design/icons"; +import { Col, Row } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleChatVisible } from "../../redux/messaging/messaging.actions"; +import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component"; +import ChatConversationContainer from "../chat-conversation/chat-conversation.container"; +import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors"; +import "./chat-popup.styles.scss"; + +const mapStateToProps = createStructuredSelector({ + selectedConversation: selectSelectedConversation, +}); +const mapDispatchToProps = (dispatch) => ({ + toggleChatVisible: () => dispatch(toggleChatVisible()), +}); + +export function ChatPopupComponent({ + conversationList, + selectedConversation, + toggleChatVisible, +}) { + const { t } = useTranslation(); + return ( +
    +
    + + {t("messaging.labels.messaging")} + + toggleChatVisible()} + style={{ float: "right" }} + /> +
    + + + + + + + {selectedConversation ? : null} + + +
    + ); +} +export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent); diff --git a/client/src/components/chat-popup/chat-popup.styles.scss b/client/src/components/chat-popup/chat-popup.styles.scss new file mode 100644 index 000000000..787469fe2 --- /dev/null +++ b/client/src/components/chat-popup/chat-popup.styles.scss @@ -0,0 +1,4 @@ +.chat-popup { + width: 40vw; + height: 50vh; +} diff --git a/client/src/components/chat-send-message/chat-send-message.component.jsx b/client/src/components/chat-send-message/chat-send-message.component.jsx index 940de722f..e2eb91e83 100644 --- a/client/src/components/chat-send-message/chat-send-message.component.jsx +++ b/client/src/components/chat-send-message/chat-send-message.component.jsx @@ -8,10 +8,10 @@ import { sendMessage } from "../../redux/messaging/messaging.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop + bodyshop: selectBodyshop, }); -const mapDispatchToProps = dispatch => ({ - sendMessage: message => dispatch(sendMessage(message)) +const mapDispatchToProps = (dispatch) => ({ + sendMessage: (message) => dispatch(sendMessage(message)), }); function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { @@ -29,7 +29,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { to: conversation.phone_num, body: message, messagingServiceSid: bodyshop.messagingservicesid, - conversationid: conversation.id + conversationid: conversation.conversationId, }); }; @@ -43,8 +43,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { value={message} disabled={conversation.isSending} placeholder={t("messaging.labels.typeamessage")} - onChange={e => setMessage(e.target.value)} - onPressEnter={event => { + onChange={(e) => setMessage(e.target.value)} + onPressEnter={(event) => { event.preventDefault(); if (!!!event.shiftKey) handleEnter(); }} @@ -54,7 +54,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { indicator={ diff --git a/client/src/graphql/conversations.queries.js b/client/src/graphql/conversations.queries.js index af7d0d138..f0ad0cfa7 100644 --- a/client/src/graphql/conversations.queries.js +++ b/client/src/graphql/conversations.queries.js @@ -32,6 +32,8 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql` count } } + id + phone_num } } `; diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index b77550d79..dfde28fad 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -10,6 +10,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com import "./manage.page.styles.scss"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; +import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container") @@ -22,9 +23,6 @@ const ProfilePage = lazy(() => import("../profile/profile.container.page")); const JobsAvailablePage = lazy(() => import("../jobs-available/jobs-available.page.container") ); -const ChatDockContainer = lazy(() => - import("../../components/chat-dock/chat-dock.container") -); const ScheduleContainer = lazy(() => import("../schedule/schedule.page.container") ); @@ -231,7 +229,7 @@ export default function Manage({ match }) {
    - + ); diff --git a/client/src/redux/messaging/messaging.actions.js b/client/src/redux/messaging/messaging.actions.js index dfa0f7748..25ef4ab97 100644 --- a/client/src/redux/messaging/messaging.actions.js +++ b/client/src/redux/messaging/messaging.actions.js @@ -1,36 +1,25 @@ import MessagingActionTypes from "./messaging.types"; export const toggleChatVisible = () => ({ - type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE + type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE, //payload: user }); -export const toggleConversationVisible = conversationId => ({ - type: MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE, - payload: conversationId -}); - -export const openConversation = phone => ({ - type: MessagingActionTypes.OPEN_CONVERSATION, - payload: phone -}); - -export const closeConversation = phone => ({ - type: MessagingActionTypes.CLOSE_CONVERSATION, - payload: phone -}); - -export const sendMessage = message => ({ +export const sendMessage = (message) => ({ type: MessagingActionTypes.SEND_MESSAGE, - payload: message + payload: message, }); -export const sendMessageSuccess = message => ({ +export const sendMessageSuccess = (message) => ({ type: MessagingActionTypes.SEND_MESSAGE_SUCCESS, - payload: message + payload: message, }); -export const sendMessageFailure = error => ({ +export const sendMessageFailure = (error) => ({ type: MessagingActionTypes.SEND_MESSAGE_FAILURE, - payload: error + payload: error, +}); +export const setSelectedConversation = (conversationId) => ({ + type: MessagingActionTypes.SET_SELECTED_CONVERSATION, + payload: conversationId, }); diff --git a/client/src/redux/messaging/messaging.reducer.js b/client/src/redux/messaging/messaging.reducer.js index 7fcfaa2b7..7d1cfea90 100644 --- a/client/src/redux/messaging/messaging.reducer.js +++ b/client/src/redux/messaging/messaging.reducer.js @@ -2,23 +2,8 @@ import MessagingActionTypes from "./messaging.types"; const INITIAL_STATE = { visible: false, - unread: 0, - conversations: [ - // { - // phone_num: "6049992002", - // id: "519ba10d-6467-4fa5-9c22-59ae891edeb6", - // open: false, - // isSending: false, - // sendingError: null - // }, - // { - // phone_num: "6049992991", - // id: "ab57deba-eeb9-40db-b5ae-23f3ce8d7c7b", - // open: false, - // isSending: false, - // sendingError: null - // } - ], + selectedConversationId: null, + isSending: false, error: null, }; @@ -29,77 +14,24 @@ const messagingReducer = (state = INITIAL_STATE, action) => { ...state, visible: !state.visible, }; - case MessagingActionTypes.SET_CHAT_VISIBLE: - return { - ...state, - visible: true, - }; + case MessagingActionTypes.SET_SELECTED_CONVERSATION: + return { ...state, selectedConversationId: action.payload }; case MessagingActionTypes.SEND_MESSAGE: return { ...state, - conversations: state.conversations.map((c) => - c.id === action.payload.conversationid ? { ...c, isSending: true } : c - ), + error: null, + isSending: true, }; case MessagingActionTypes.SEND_MESSAGE_SUCCESS: return { ...state, - conversations: state.conversations.map((c) => - c.id === action.payload.conversationid - ? { ...c, isSending: false } - : c - ), + isSending: false, }; case MessagingActionTypes.SEND_MESSAGE_FAILURE: return { ...state, - conversations: state.conversations.map((c) => - c.id === action.payload.conversationid - ? { ...c, isSending: false, sendingError: action.payload.error } - : c - ), + error: action.payload, }; - case MessagingActionTypes.OPEN_CONVERSATION: - if ( - state.conversations.find( - (c) => c.phone_num === action.payload.phone_num - ) - ) - return { - ...state, - conversations: state.conversations.map((c) => - c.phone_num === action.payload.phone_num ? { ...c, open: true } : c - ), - }; - else - return { - ...state, - conversations: [ - ...state.conversations, - { - phone_num: action.payload.phone_num, - id: action.payload.id, - open: true, - isSending: false, - sendingError: null, - }, - ], - }; - case MessagingActionTypes.CLOSE_CONVERSATION: - return { - ...state, - conversations: state.conversations.filter( - (c) => c.phone_num !== action.payload - ), - }; - case MessagingActionTypes.TOGGLE_CONVERSATION_VISIBLE: - return { - ...state, - conversations: state.conversations.map((c) => - c.id === action.payload ? { ...c, open: !c.open } : c - ), - }; - default: return state; } diff --git a/client/src/redux/messaging/messaging.selectors.js b/client/src/redux/messaging/messaging.selectors.js index b47f6743c..90d326e90 100644 --- a/client/src/redux/messaging/messaging.selectors.js +++ b/client/src/redux/messaging/messaging.selectors.js @@ -1,13 +1,23 @@ import { createSelector } from "reselect"; -const selectMessaging = state => state.messaging; +const selectMessaging = (state) => state.messaging; export const selectChatVisible = createSelector( [selectMessaging], - messaging => messaging.visible + (messaging) => messaging.visible ); -export const selectConversations = createSelector( +export const selectIsSending = createSelector( [selectMessaging], - messaging => messaging.conversations + (messaging) => messaging.isSending +); + +export const selectError = createSelector( + [selectMessaging], + (messaging) => messaging.error +); + +export const selectSelectedConversation = createSelector( + [selectMessaging], + (messaging) => messaging.selectedConversationId ); diff --git a/client/src/redux/messaging/messaging.types.js b/client/src/redux/messaging/messaging.types.js index 59a35590a..c6c3caf16 100644 --- a/client/src/redux/messaging/messaging.types.js +++ b/client/src/redux/messaging/messaging.types.js @@ -1,11 +1,8 @@ const MessagingActionTypes = { TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE", - SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE", - OPEN_CONVERSATION: "OPEN_CONVERSATION", - CLOSE_CONVERSATION: "CLOSE_CONVERSATION", - TOGGLE_CONVERSATION_VISIBLE: "TOGGLE_CONVERSATION_VISIBLE", SEND_MESSAGE: "SEND_MESSAGE", SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS", - SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE" + SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE", + SET_SELECTED_CONVERSATION: 'SET_SELECTED_CONVERSATION' }; export default MessagingActionTypes;