Refactored Messaging as a part of BOD-14. Breaking changes remain.
This commit is contained in:
43
client/src/components/chat-affix/chat-affix.component.jsx
Normal file
43
client/src/components/chat-affix/chat-affix.component.jsx
Normal file
@@ -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 (
|
||||||
|
<Badge count={unreadCount}>
|
||||||
|
<Card size='small'>
|
||||||
|
{chatVisible ? (
|
||||||
|
<ChatPopupComponent conversationList={conversationList} />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
onClick={() => toggleChatVisible()}
|
||||||
|
style={{ cursor: "pointer" }}>
|
||||||
|
<MessageOutlined />
|
||||||
|
<strong>{t("messaging.labels.messaging")}</strong>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatAffixComponent);
|
||||||
32
client/src/components/chat-affix/chat-affix.container.jsx
Normal file
32
client/src/components/chat-affix/chat-affix.container.jsx
Normal file
@@ -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 <LoadingSpinner />;
|
||||||
|
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Affix offsetBottom={5}>
|
||||||
|
<div>
|
||||||
|
<ChatAffixComponent
|
||||||
|
conversationList={(data && data.conversations) || []}
|
||||||
|
unreadCount={
|
||||||
|
(data &&
|
||||||
|
data.conversations.reduce((acc, val) => {
|
||||||
|
return (acc = acc + val.messages_aggregate.aggregate.count);
|
||||||
|
}, 0)) ||
|
||||||
|
0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Affix>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,41 +1,45 @@
|
|||||||
import { ShrinkOutlined } from "@ant-design/icons";
|
import { Badge, List } from "antd";
|
||||||
import { Badge } from "antd";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||||
openConversation,
|
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
toggleChatVisible
|
import { createStructuredSelector } from "reselect";
|
||||||
} from "../../redux/messaging/messaging.actions";
|
import "./chat-conversation-list.styles.scss";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapStateToProps = createStructuredSelector({
|
||||||
toggleChatVisible: () => dispatch(toggleChatVisible()),
|
selectedConversation: selectSelectedConversation,
|
||||||
openConversation: number => dispatch(openConversation(number))
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setSelectedConversation: (conversationId) =>
|
||||||
|
dispatch(setSelectedConversation(conversationId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ChatConversationListComponent({
|
export function ChatConversationListComponent({
|
||||||
toggleChatVisible,
|
|
||||||
conversationList,
|
conversationList,
|
||||||
openConversation
|
selectedConversation,
|
||||||
|
setSelectedConversation,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className='chat-overlay-open'>
|
<List
|
||||||
<ShrinkOutlined onClick={() => toggleChatVisible()} />
|
bordered
|
||||||
{conversationList.map(item => (
|
dataSource={conversationList}
|
||||||
<Badge count={item.messages_aggregate.aggregate.count || 0}>
|
renderItem={(item) => (
|
||||||
<div
|
<List.Item
|
||||||
key={item.id}
|
onClick={() => setSelectedConversation(item.id)}
|
||||||
style={{ cursor: "pointer", display: "block" }}
|
className={`chat-list-item ${
|
||||||
onClick={() =>
|
item.id === selectedConversation
|
||||||
openConversation({ phone_num: item.phone_num, id: item.id })
|
? "chat-list-selected-conversation"
|
||||||
}>
|
: null
|
||||||
<div>
|
}`}>
|
||||||
<PhoneNumberFormatter>{item.phone_num}</PhoneNumberFormatter>
|
{item.phone_num}
|
||||||
</div>
|
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||||
</div>
|
</List.Item>
|
||||||
</Badge>
|
)}
|
||||||
))}
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(null, mapDispatchToProps)(ChatConversationListComponent);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ChatConversationListComponent);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.chat-list-selected-conversation {
|
||||||
|
background-color: rgba(128, 128, 128, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-list-item {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #ff7a00;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 (
|
|
||||||
<div
|
|
||||||
className='chat-conversation-closed'
|
|
||||||
onClick={() => toggleConversationVisible(conversation.id)}>
|
|
||||||
<PhoneFormatter>{conversation.phone_num}</PhoneFormatter>
|
|
||||||
<CloseCircleFilled
|
|
||||||
onClick={() => closeConversation(conversation.phone_num)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
null,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ChatConversationClosedComponent);
|
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
import { Badge, Card } from "antd";
|
import { Badge, Card } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ChatConversationClosedComponent from "./chat-conversation.closed.component";
|
import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component";
|
||||||
import ChatConversationOpenComponent from "./chat-conversation.open.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({
|
export default function ChatConversationComponent({
|
||||||
conversation,
|
|
||||||
messages,
|
messages,
|
||||||
subState,
|
subState,
|
||||||
unreadCount
|
conversation,
|
||||||
|
unreadCount,
|
||||||
}) {
|
}) {
|
||||||
|
const [loading, error] = subState;
|
||||||
|
|
||||||
|
if (loading) return <LoadingSkeleton />;
|
||||||
|
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='chat-conversation'>
|
<div className='chat-conversation'>
|
||||||
<Badge count={unreadCount}>
|
<Badge count={unreadCount}>
|
||||||
|
<ChatSendMessage conversation={conversation} />
|
||||||
<Card size='small'>
|
<Card size='small'>
|
||||||
{conversation.open ? (
|
<ChatMessageListComponent messages={messages} />
|
||||||
<ChatConversationOpenComponent
|
|
||||||
messages={messages}
|
|
||||||
conversation={conversation}
|
|
||||||
subState={subState}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ChatConversationClosedComponent conversation={conversation} />
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,18 +3,30 @@ import React from "react";
|
|||||||
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
|
import { CONVERSATION_SUBSCRIPTION_BY_PK } from "../../graphql/conversations.queries";
|
||||||
import ChatConversationComponent from "./chat-conversation.component";
|
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(
|
const { loading, error, data } = useSubscription(
|
||||||
CONVERSATION_SUBSCRIPTION_BY_PK,
|
CONVERSATION_SUBSCRIPTION_BY_PK,
|
||||||
{
|
{
|
||||||
variables: { conversationId: conversation.id }
|
variables: { conversationId: selectedConversation },
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatConversationComponent
|
<ChatConversationComponent
|
||||||
subState={[loading, error]}
|
subState={[loading, error]}
|
||||||
conversation={conversation}
|
|
||||||
unreadCount={
|
unreadCount={
|
||||||
(data &&
|
(data &&
|
||||||
data.conversations_by_pk &&
|
data.conversations_by_pk &&
|
||||||
@@ -23,6 +35,14 @@ export default function ChatConversationContainer({ conversation }) {
|
|||||||
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
|
data.conversations_by_pk.messages_aggregate.aggregate.count) ||
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
conversation={{
|
||||||
|
conversationId: selectedConversation,
|
||||||
|
phone_num:
|
||||||
|
(data &&
|
||||||
|
data.conversations_by_pk &&
|
||||||
|
data.conversations_by_pk.phone_num) ||
|
||||||
|
"",
|
||||||
|
}}
|
||||||
messages={
|
messages={
|
||||||
(data &&
|
(data &&
|
||||||
data.conversations_by_pk &&
|
data.conversations_by_pk &&
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { toggleConversationVisible } from "../../redux/messaging/messaging.actions";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
|
||||||
import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component";
|
|
||||||
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
|
||||||
import { ShrinkOutlined } from "@ant-design/icons";
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
toggleConversationVisible: conversation =>
|
|
||||||
dispatch(toggleConversationVisible(conversation))
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ChatConversationOpenComponent({
|
|
||||||
conversation,
|
|
||||||
messages,
|
|
||||||
subState,
|
|
||||||
toggleConversationVisible
|
|
||||||
}) {
|
|
||||||
const [loading, error] = subState;
|
|
||||||
|
|
||||||
if (loading) return <LoadingSpinner />;
|
|
||||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='chat-conversation-open'>
|
|
||||||
<ShrinkOutlined
|
|
||||||
onClick={() => toggleConversationVisible(conversation.id)}
|
|
||||||
/>
|
|
||||||
<ChatMessageListComponent messages={messages} />
|
|
||||||
<ChatSendMessage conversation={conversation} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(null, mapDispatchToProps)(ChatConversationOpenComponent);
|
|
||||||
@@ -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 (
|
|
||||||
<Affix offsetBottom={0}>
|
|
||||||
<div className='chat-dock'>
|
|
||||||
<ChatMessagesButtonContainer />
|
|
||||||
{activeConversations
|
|
||||||
? activeConversations.map(conversation => (
|
|
||||||
<ChatConversationContainer
|
|
||||||
conversation={conversation}
|
|
||||||
key={conversation.id}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
</Affix>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(ChatOverlayContainer);
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<div className='chat-conversation'>
|
|
||||||
<Badge count={unreadCount}>
|
|
||||||
<Card size='small'>
|
|
||||||
{chatVisible ? (
|
|
||||||
<ChatConversationListComponent
|
|
||||||
conversationList={conversationList}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div onClick={() => toggleChatVisible()}>
|
|
||||||
<MessageFilled />
|
|
||||||
<strong>{t("messaging.labels.messaging")}</strong>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ChatWindowComponent);
|
|
||||||
@@ -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 <LoadingSpinner />;
|
|
||||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChatMessagesButtonComponent
|
|
||||||
conversationList={(data && data.conversations) || []}
|
|
||||||
unreadCount={
|
|
||||||
(data &&
|
|
||||||
data.conversations.reduce((acc, val) => {
|
|
||||||
return (acc = acc + val.messages_aggregate.aggregate.count);
|
|
||||||
}, 0)) ||
|
|
||||||
0
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { CheckCircleOutlined, CheckOutlined } from "@ant-design/icons";
|
import { CheckCircleOutlined, CheckOutlined } from "@ant-design/icons";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import "./chat-message-list.styles.scss";
|
||||||
|
|
||||||
export default function ChatMessageListComponent({ messages }) {
|
export default function ChatMessageListComponent({ messages }) {
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
console.log("use");
|
|
||||||
!!messagesEndRef.current &&
|
!!messagesEndRef.current &&
|
||||||
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(scrollToBottom, [messages]);
|
useEffect(scrollToBottom, [messages]);
|
||||||
const StatusRender = status => {
|
const StatusRender = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "sent":
|
case "sent":
|
||||||
return <CheckOutlined style={{ margin: "2px", float: "right" }} />;
|
return <CheckOutlined style={{ margin: "2px", float: "right" }} />;
|
||||||
@@ -27,7 +27,7 @@ export default function ChatMessageListComponent({ messages }) {
|
|||||||
return (
|
return (
|
||||||
<div className='messages'>
|
<div className='messages'>
|
||||||
<ul>
|
<ul>
|
||||||
{messages.map(item => (
|
{messages.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`${item.isoutbound ? "replies" : "sent"}`}>
|
className={`${item.isoutbound ? "replies" : "sent"}`}>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,22 +1,16 @@
|
|||||||
|
import { MessageFilled } from "@ant-design/icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { openConversation } from "../../redux/messaging/messaging.actions";
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
import { MessageFilled } from "@ant-design/icons";
|
//openConversation: (phone) => dispatch(openConversation(phone)),
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
export function ChatOpenButton({ phone }) {
|
||||||
openConversation: phone => dispatch(openConversation(phone))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(function ChatOpenButton({ openConversation, phone }) {
|
|
||||||
return (
|
return (
|
||||||
<MessageFilled
|
<MessageFilled
|
||||||
style={{ margin: 4 }}
|
style={{ margin: 4 }}
|
||||||
onClick={() => openConversation(phone)}
|
onClick={() => alert("TODO FIX ME" + phone)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
export default connect(null, mapDispatchToProps)(ChatOpenButton);
|
||||||
|
|||||||
49
client/src/components/chat-popup/chat-popup.component.jsx
Normal file
49
client/src/components/chat-popup/chat-popup.component.jsx
Normal file
@@ -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 (
|
||||||
|
<div className='chat-popup'>
|
||||||
|
<div style={{ overflow: "auto" }}>
|
||||||
|
<strong style={{ float: "left" }}>
|
||||||
|
{t("messaging.labels.messaging")}
|
||||||
|
</strong>
|
||||||
|
<ShrinkOutlined
|
||||||
|
onClick={() => toggleChatVisible()}
|
||||||
|
style={{ float: "right" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>
|
||||||
|
<ChatConversationListComponent conversationList={conversationList} />
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
{selectedConversation ? <ChatConversationContainer /> : null}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatPopupComponent);
|
||||||
4
client/src/components/chat-popup/chat-popup.styles.scss
Normal file
4
client/src/components/chat-popup/chat-popup.styles.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.chat-popup {
|
||||||
|
width: 40vw;
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
@@ -8,10 +8,10 @@ import { sendMessage } from "../../redux/messaging/messaging.actions";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
sendMessage: message => dispatch(sendMessage(message))
|
sendMessage: (message) => dispatch(sendMessage(message)),
|
||||||
});
|
});
|
||||||
|
|
||||||
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
||||||
@@ -29,7 +29,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
|||||||
to: conversation.phone_num,
|
to: conversation.phone_num,
|
||||||
body: message,
|
body: message,
|
||||||
messagingServiceSid: bodyshop.messagingservicesid,
|
messagingServiceSid: bodyshop.messagingservicesid,
|
||||||
conversationid: conversation.id
|
conversationid: conversation.conversationId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
|||||||
value={message}
|
value={message}
|
||||||
disabled={conversation.isSending}
|
disabled={conversation.isSending}
|
||||||
placeholder={t("messaging.labels.typeamessage")}
|
placeholder={t("messaging.labels.typeamessage")}
|
||||||
onChange={e => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
onPressEnter={event => {
|
onPressEnter={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!!!event.shiftKey) handleEnter();
|
if (!!!event.shiftKey) handleEnter();
|
||||||
}}
|
}}
|
||||||
@@ -54,7 +54,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
|||||||
indicator={
|
indicator={
|
||||||
<LoadingOutlined
|
<LoadingOutlined
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24
|
fontSize: 24,
|
||||||
}}
|
}}
|
||||||
spin
|
spin
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
|
|||||||
count
|
count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
id
|
||||||
|
phone_num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
|||||||
import "./manage.page.styles.scss";
|
import "./manage.page.styles.scss";
|
||||||
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
|
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
|
||||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||||
|
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
|
||||||
|
|
||||||
const ManageRootPage = lazy(() =>
|
const ManageRootPage = lazy(() =>
|
||||||
import("../manage-root/manage-root.page.container")
|
import("../manage-root/manage-root.page.container")
|
||||||
@@ -22,9 +23,6 @@ const ProfilePage = lazy(() => import("../profile/profile.container.page"));
|
|||||||
const JobsAvailablePage = lazy(() =>
|
const JobsAvailablePage = lazy(() =>
|
||||||
import("../jobs-available/jobs-available.page.container")
|
import("../jobs-available/jobs-available.page.container")
|
||||||
);
|
);
|
||||||
const ChatDockContainer = lazy(() =>
|
|
||||||
import("../../components/chat-dock/chat-dock.container")
|
|
||||||
);
|
|
||||||
const ScheduleContainer = lazy(() =>
|
const ScheduleContainer = lazy(() =>
|
||||||
import("../schedule/schedule.page.container")
|
import("../schedule/schedule.page.container")
|
||||||
);
|
);
|
||||||
@@ -231,7 +229,7 @@ export default function Manage({ match }) {
|
|||||||
<Footer>
|
<Footer>
|
||||||
<FooterComponent />
|
<FooterComponent />
|
||||||
</Footer>
|
</Footer>
|
||||||
<ChatDockContainer />
|
<ChatAffixContainer />
|
||||||
<BackTop />
|
<BackTop />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,36 +1,25 @@
|
|||||||
import MessagingActionTypes from "./messaging.types";
|
import MessagingActionTypes from "./messaging.types";
|
||||||
|
|
||||||
export const toggleChatVisible = () => ({
|
export const toggleChatVisible = () => ({
|
||||||
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE
|
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
|
||||||
//payload: user
|
//payload: user
|
||||||
});
|
});
|
||||||
|
|
||||||
export const toggleConversationVisible = conversationId => ({
|
export const sendMessage = (message) => ({
|
||||||
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 => ({
|
|
||||||
type: MessagingActionTypes.SEND_MESSAGE,
|
type: MessagingActionTypes.SEND_MESSAGE,
|
||||||
payload: message
|
payload: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendMessageSuccess = message => ({
|
export const sendMessageSuccess = (message) => ({
|
||||||
type: MessagingActionTypes.SEND_MESSAGE_SUCCESS,
|
type: MessagingActionTypes.SEND_MESSAGE_SUCCESS,
|
||||||
payload: message
|
payload: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sendMessageFailure = error => ({
|
export const sendMessageFailure = (error) => ({
|
||||||
type: MessagingActionTypes.SEND_MESSAGE_FAILURE,
|
type: MessagingActionTypes.SEND_MESSAGE_FAILURE,
|
||||||
payload: error
|
payload: error,
|
||||||
|
});
|
||||||
|
export const setSelectedConversation = (conversationId) => ({
|
||||||
|
type: MessagingActionTypes.SET_SELECTED_CONVERSATION,
|
||||||
|
payload: conversationId,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,23 +2,8 @@ import MessagingActionTypes from "./messaging.types";
|
|||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
visible: false,
|
visible: false,
|
||||||
unread: 0,
|
selectedConversationId: null,
|
||||||
conversations: [
|
isSending: false,
|
||||||
// {
|
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
],
|
|
||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,77 +14,24 @@ const messagingReducer = (state = INITIAL_STATE, action) => {
|
|||||||
...state,
|
...state,
|
||||||
visible: !state.visible,
|
visible: !state.visible,
|
||||||
};
|
};
|
||||||
case MessagingActionTypes.SET_CHAT_VISIBLE:
|
case MessagingActionTypes.SET_SELECTED_CONVERSATION:
|
||||||
return {
|
return { ...state, selectedConversationId: action.payload };
|
||||||
...state,
|
|
||||||
visible: true,
|
|
||||||
};
|
|
||||||
case MessagingActionTypes.SEND_MESSAGE:
|
case MessagingActionTypes.SEND_MESSAGE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
conversations: state.conversations.map((c) =>
|
error: null,
|
||||||
c.id === action.payload.conversationid ? { ...c, isSending: true } : c
|
isSending: true,
|
||||||
),
|
|
||||||
};
|
};
|
||||||
case MessagingActionTypes.SEND_MESSAGE_SUCCESS:
|
case MessagingActionTypes.SEND_MESSAGE_SUCCESS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
conversations: state.conversations.map((c) =>
|
isSending: false,
|
||||||
c.id === action.payload.conversationid
|
|
||||||
? { ...c, isSending: false }
|
|
||||||
: c
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
case MessagingActionTypes.SEND_MESSAGE_FAILURE:
|
case MessagingActionTypes.SEND_MESSAGE_FAILURE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
conversations: state.conversations.map((c) =>
|
error: action.payload,
|
||||||
c.id === action.payload.conversationid
|
|
||||||
? { ...c, isSending: false, sendingError: action.payload.error }
|
|
||||||
: c
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import { createSelector } from "reselect";
|
import { createSelector } from "reselect";
|
||||||
|
|
||||||
const selectMessaging = state => state.messaging;
|
const selectMessaging = (state) => state.messaging;
|
||||||
|
|
||||||
export const selectChatVisible = createSelector(
|
export const selectChatVisible = createSelector(
|
||||||
[selectMessaging],
|
[selectMessaging],
|
||||||
messaging => messaging.visible
|
(messaging) => messaging.visible
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectConversations = createSelector(
|
export const selectIsSending = createSelector(
|
||||||
[selectMessaging],
|
[selectMessaging],
|
||||||
messaging => messaging.conversations
|
(messaging) => messaging.isSending
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectError = createSelector(
|
||||||
|
[selectMessaging],
|
||||||
|
(messaging) => messaging.error
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSelectedConversation = createSelector(
|
||||||
|
[selectMessaging],
|
||||||
|
(messaging) => messaging.selectedConversationId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
const MessagingActionTypes = {
|
const MessagingActionTypes = {
|
||||||
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
|
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: "SEND_MESSAGE",
|
||||||
SEND_MESSAGE_SUCCESS: "SEND_MESSAGE_SUCCESS",
|
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;
|
export default MessagingActionTypes;
|
||||||
|
|||||||
Reference in New Issue
Block a user