BOD-14 Backend server work + sending of messages WIP for front end
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { Button } from "antd";
|
||||
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 style={{ display: "flex" }}>
|
||||
<div onClick={() => toggleConversationVisible(conversation.phone)}>
|
||||
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||
</div>
|
||||
<Button
|
||||
type='dashed'
|
||||
shape='circle-outline'
|
||||
onClick={() => closeConversation(conversation.phone)}>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(ChatConversationClosedComponent);
|
||||
@@ -1,22 +1,16 @@
|
||||
import { Button, Card, Input } from "antd";
|
||||
import Icon from '@ant-design/icons';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Card } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import twilio from "twilio";
|
||||
import {
|
||||
closeConversation,
|
||||
sendMessage,
|
||||
toggleConversationVisible
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatConversationOpenComponent from "./chat-conversation.open.component";
|
||||
import "./chat-conversation.styles.scss"; //https://bootsnipp.com/snippets/exR5v
|
||||
import { MdSend } from "react-icons/md";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const client = twilio(
|
||||
"ACf1b1aaf0e04740828b49b6e58467d787",
|
||||
"0bea5e29a6d77593183ab1caa01d23de"
|
||||
);
|
||||
import ChatConversationClosedComponent from "./chat-conversation.closed.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -24,35 +18,17 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleConversationVisible: conversationId =>
|
||||
dispatch(toggleConversationVisible(conversationId)),
|
||||
closeConversation: phone => dispatch(closeConversation(phone))
|
||||
closeConversation: phone => dispatch(closeConversation(phone)),
|
||||
sendMessage: message => dispatch(sendMessage(message))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(function ChatConversationComponent({
|
||||
|
||||
export function ChatConversationComponent({
|
||||
conversation,
|
||||
toggleConversationVisible,
|
||||
closeConversation
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const messages = [];
|
||||
|
||||
const [messages, setMessages] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
client.messages.list({ limit: 20 }, (error, items) => {
|
||||
setMessages(
|
||||
items.reduce((acc, value) => {
|
||||
acc.push({
|
||||
sid: value.sid,
|
||||
direction: value.direction,
|
||||
body: value.body
|
||||
});
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
});
|
||||
return () => {};
|
||||
}, [setMessages]);
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
@@ -60,15 +36,13 @@ export default connect(
|
||||
conversation.open ? (
|
||||
<div style={{ display: "flex" }}>
|
||||
<div
|
||||
onClick={() => toggleConversationVisible(conversation.phone)}
|
||||
>
|
||||
onClick={() => toggleConversationVisible(conversation.phone)}>
|
||||
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||
</div>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="circle-outline"
|
||||
onClick={() => closeConversation(conversation.phone)}
|
||||
>
|
||||
type='danger'
|
||||
shape='circle-outline'
|
||||
onClick={() => closeConversation(conversation.phone)}>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
@@ -78,44 +52,18 @@ export default connect(
|
||||
width: conversation.open ? "400px" : "175px",
|
||||
margin: "0px 10px"
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
size='small'>
|
||||
{conversation.open ? (
|
||||
<div>
|
||||
<div className="messages" style={{ height: "400px" }}>
|
||||
<ul>
|
||||
{messages.map(item => (
|
||||
<li
|
||||
key={item.sid}
|
||||
className={`${
|
||||
item.direction === "inbound" ? "sent" : "replies"
|
||||
}`}
|
||||
>
|
||||
<p> {item.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Input.Search
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
enterButton={<Icon component={MdSend} />}
|
||||
/>
|
||||
</div>
|
||||
<ChatConversationOpenComponent messages={messages} />
|
||||
) : (
|
||||
<div style={{ display: "flex" }}>
|
||||
<div onClick={() => toggleConversationVisible(conversation.phone)}>
|
||||
<PhoneFormatter>{conversation.phone}</PhoneFormatter>
|
||||
</div>
|
||||
<Button
|
||||
type="dashed"
|
||||
shape="circle-outline"
|
||||
onClick={() => closeConversation(conversation.phone)}
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</div>
|
||||
<ChatConversationClosedComponent conversation={conversation} />
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ChatConversationComponent);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import ChatSendMessage from "../chat-send-message/chat-send-message.component";
|
||||
export default function ChatConversationOpenComponent({
|
||||
conversation,
|
||||
messages
|
||||
}) {
|
||||
if (!!!messages) return <div>No Messages</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='messages' style={{ height: "400px" }}>
|
||||
<ul>
|
||||
{messages.map(item => (
|
||||
<li
|
||||
key={item.sid}
|
||||
className={`${
|
||||
item.direction === "inbound" ? "sent" : "replies"
|
||||
}`}>
|
||||
<p> {item.body}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<ChatSendMessage conversation={conversation} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Input } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { sendMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
sendMessage: message => dispatch(sendMessage(message))
|
||||
});
|
||||
|
||||
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) {
|
||||
const [message, setMessage] = useState("");
|
||||
const { t } = useTranslation();
|
||||
console.log("message", message);
|
||||
|
||||
const handleEnter = () => {
|
||||
console.log("Sending that message ");
|
||||
sendMessage({
|
||||
to: conversation.phone,
|
||||
body: message,
|
||||
messagingServiceSid: bodyshop.messagingservicesid
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex " }}>
|
||||
<Input.TextArea
|
||||
allowClear
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
// enterButton={}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
onPressEnter={event => {
|
||||
if (!!!event.shiftKey) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ChatSendMessageComponent);
|
||||
@@ -22,6 +22,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
zip_post
|
||||
region_config
|
||||
md_responsibility_centers
|
||||
messagingservicesid
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -5,20 +5,24 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import ManagePage from "./manage.page.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setBodyshop: bs => dispatch(setBodyshop(bs))
|
||||
});
|
||||
|
||||
function ManagePageContainer({ match, setBodyshop }) {
|
||||
const { error, data } = useQuery(QUERY_BODYSHOP, {
|
||||
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
if (data) setBodyshop(data.bodyshops[0]);
|
||||
}, [data, setBodyshop]);
|
||||
|
||||
if (loading)
|
||||
return <LoadingSpinner message={t("general.labels.loadingshop")} />;
|
||||
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||
return <ManagePage match={match} />;
|
||||
}
|
||||
|
||||
@@ -19,3 +19,13 @@ export const closeConversation = phone => ({
|
||||
type: MessagingActionTypes.CLOSE_CONVERSATION,
|
||||
payload: phone
|
||||
});
|
||||
|
||||
export const sendMessage = message => ({
|
||||
type: MessagingActionTypes.SEND_MESSAGE,
|
||||
payload: message
|
||||
});
|
||||
|
||||
export const sendMessageFailure = error => ({
|
||||
type: MessagingActionTypes.SEND_MESSAGE_FAILURE,
|
||||
payload: error
|
||||
});
|
||||
|
||||
@@ -3,8 +3,16 @@ import MessagingActionTypes from "./messaging.types";
|
||||
const INITIAL_STATE = {
|
||||
visible: false,
|
||||
conversations: [
|
||||
{ phone: "6049992002", open: false },
|
||||
{ phone: "6049992991", open: false }
|
||||
{
|
||||
phone: "6049992002",
|
||||
id: "519ba10d-6467-4fa5-9c22-59ae891edeb6",
|
||||
open: false
|
||||
},
|
||||
{
|
||||
phone: "6049992991",
|
||||
id: "ab57deba-eeb9-40db-b5ae-23f3ce8d7c7b",
|
||||
open: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import { all } from "redux-saga/effects";
|
||||
import { all, call, put, takeLatest } from "redux-saga/effects";
|
||||
import { sendMessageFailure } from "./messaging.actions";
|
||||
import MessagingActionTypes from "./messaging.types";
|
||||
import axios from "axios";
|
||||
|
||||
export function* onSendMessage() {
|
||||
yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage);
|
||||
}
|
||||
export function* sendMessage(payload) {
|
||||
try {
|
||||
console.log("Message Contents", payload);
|
||||
axios.post("/sms/send", payload).then(response => {
|
||||
console.log(JSON.stringify(response));
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error in sendMessage saga.");
|
||||
yield put(sendMessageFailure(error));
|
||||
}
|
||||
}
|
||||
|
||||
export function* messagingSagas() {
|
||||
yield all([]);
|
||||
yield all([call(onSendMessage)]);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const MessagingActionTypes = {
|
||||
OPEN_CONVERSATION: "OPEN_CONVERSATION",
|
||||
CLOSE_CONVERSATION: "CLOSE_CONVERSATION",
|
||||
TOGGLE_CONVERSATION_VISIBLE: "TOGGLE_CONVERSATION_VISIBLE",
|
||||
SEND_MESSAGE: "SEND_MESSAGE"
|
||||
SEND_MESSAGE: "SEND_MESSAGE",
|
||||
SEND_MESSAGE_FAILURE: "SEND_MESSAGE_FAILURE"
|
||||
};
|
||||
export default MessagingActionTypes;
|
||||
|
||||
Reference in New Issue
Block a user