BOD-14 Backend server work + sending of messages WIP for front end

This commit is contained in:
Patrick Fic
2020-03-25 15:50:46 -07:00
parent 5b5ffe21cd
commit 84a5820d8d
32 changed files with 892 additions and 274 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -22,6 +22,7 @@ export const QUERY_BODYSHOP = gql`
zip_post
region_config
md_responsibility_centers
messagingservicesid
employees {
id
first_name

View File

@@ -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} />;
}

View File

@@ -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
});

View File

@@ -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
}
]
};

View File

@@ -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)]);
}

View File

@@ -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;