diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx index bb73d03c4..07e338d4f 100644 --- a/client/src/App/App.container.jsx +++ b/client/src/App/App.container.jsx @@ -29,7 +29,6 @@ export default class AppContainer extends Component { connectionParams: async () => { //const token = localStorage.getItem("token"); const token = await auth.currentUser.getIdToken(true); - console.log("token", token); if (token) { return { headers: { @@ -42,7 +41,6 @@ export default class AppContainer extends Component { }); const subscriptionMiddleware = { applyMiddleware: async (options, next) => { - console.log("SubMiddleware"); options.authToken = await auth.currentUser.getIdToken(true); next(); } 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 15c29fc11..0459f291a 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 @@ -24,7 +24,7 @@ export function ChatConversationListComponent({ console.log("conversationList", conversationList); return ( -
+
Title diff --git a/client/src/components/chat-conversation/chat-conversation.closed.component.jsx b/client/src/components/chat-conversation/chat-conversation.closed.component.jsx index 13df9d252..4f926228c 100644 --- a/client/src/components/chat-conversation/chat-conversation.closed.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.closed.component.jsx @@ -21,12 +21,13 @@ function ChatConversationClosedComponent({ closeConversation }) { return ( -
+
toggleConversationVisible(conversation.phone_num)}> {conversation.phone_num}
-
- ) : null - } - style={{ - width: conversation.open ? "400px" : "175px", - margin: "0px 10px" - }} - size='small'> + {conversation.open ? ( ); } - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChatConversationComponent); diff --git a/client/src/components/chat-conversation/chat-conversation.container.jsx b/client/src/components/chat-conversation/chat-conversation.container.jsx index 8500fdb4e..076aa1d31 100644 --- a/client/src/components/chat-conversation/chat-conversation.container.jsx +++ b/client/src/components/chat-conversation/chat-conversation.container.jsx @@ -13,7 +13,6 @@ const mapDispatchToProps = dispatch => ({ }); export function ChatConversationContainer({ conversation }) { - console.log("conversation", conversation); const { loading, error, data } = useSubscription(MESSAGES_SUBSCRIPTION, { variables: { conversationId: conversation.id } }); diff --git a/client/src/components/chat-conversation/chat-conversation.open.component.jsx b/client/src/components/chat-conversation/chat-conversation.open.component.jsx index 4e148c349..cf2496d71 100644 --- a/client/src/components/chat-conversation/chat-conversation.open.component.jsx +++ b/client/src/components/chat-conversation/chat-conversation.open.component.jsx @@ -1,7 +1,8 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import AlertComponent from "../alert/alert.component"; import ChatSendMessage from "../chat-send-message/chat-send-message.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import { CheckOutlined, CheckCircleOutlined } from "@ant-design/icons"; export default function ChatConversationOpenComponent({ conversation, @@ -9,24 +10,47 @@ export default function ChatConversationOpenComponent({ subState }) { const [loading, error] = subState; + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + console.log("use"); + !!messagesEndRef.current && + messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(scrollToBottom, [messages]); + if (loading) return ; if (error) return ; + const StatusRender = status => { + switch (status) { + case "sent": + return ; + case "delivered": + return ( + + ); + default: + return null; + } + }; + return ( -
-
+
+
    {messages.map(item => (
  • -
    -

    - {item.text}
    {item.status} -

    -
    +

    + {item.text} + {StatusRender(item.status)} +

  • ))} +
diff --git a/client/src/components/chat-conversation/chat-conversation.styles.scss b/client/src/components/chat-conversation/chat-conversation.styles.scss index a7e128649..59fee6569 100644 --- a/client/src/components/chat-conversation/chat-conversation.styles.scss +++ b/client/src/components/chat-conversation/chat-conversation.styles.scss @@ -1,6 +1,77 @@ +.chat-overlay-wrapper { + width: 95vw; +} +.chat-overlay-scroller { + overflow-x: scroll; + overflow-y: hidden; + vertical-align: bottom; +} +.chat-overlay { + margin: 0px 12px; + display: inline-block; +} +.chat-overlay-open { + width: 400px; + height: 33vh; + display: flex; + flex-flow: column; +} +.chat-overlay-closed { + display: flex; + justify-content: space-between; + vertical-align: middle; + width: 150px; + cursor: pointer; +} + +// .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% - 93px); + min-height: calc(100% - 10px); max-height: calc(100% - 93px); overflow-y: scroll; overflow-x: hidden; @@ -21,7 +92,7 @@ display: inline-block; clear: both; //float: left; - margin: 5px 15px 5px 15px; + margin: 5px; width: calc(100% - 25px); font-size: 0.9em; } diff --git a/client/src/components/chat-overlay/chat-overlay.component.jsx b/client/src/components/chat-overlay/chat-overlay.component.jsx index b40750fdb..72c2c2941 100644 --- a/client/src/components/chat-overlay/chat-overlay.component.jsx +++ b/client/src/components/chat-overlay/chat-overlay.component.jsx @@ -1,5 +1,5 @@ -import { Badge, Card } from "antd"; import { MessageFilled } from "@ant-design/icons"; +import { Card } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import ChatConversationListContainer from "../chat-conversation-list/chat-conversation-list.container"; @@ -10,30 +10,21 @@ export default function ChatWindowComponent({ }) { const { t } = useTranslation(); return ( -
- - - {chatVisible ? ( -
- -
- ) : ( -
toggleChatVisible()}> - - - {t("messaging.labels.messaging")} - -
- )} -
-
-
+ + {chatVisible ? ( + + + + ) : ( +
toggleChatVisible()}> + + + {t("messaging.labels.messaging")} + +
+ )} +
); } diff --git a/client/src/components/chat-overlay/chat-overlay.container.jsx b/client/src/components/chat-overlay/chat-overlay.container.jsx index ea0db264c..2d9856d28 100644 --- a/client/src/components/chat-overlay/chat-overlay.container.jsx +++ b/client/src/components/chat-overlay/chat-overlay.container.jsx @@ -26,21 +26,23 @@ export function ChatOverlayContainer({ }) { return ( -
- - - - {activeConversations - ? activeConversations.map(conversation => ( - - )) - : null} +
+
+ + + + {activeConversations + ? activeConversations.map(conversation => ( + + )) + : null} +
); 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 24bff6664..940de722f 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 @@ -1,5 +1,6 @@ -import { Input } from "antd"; -import React, { useState } from "react"; +import { Input, Spin } from "antd"; +import { LoadingOutlined } from "@ant-design/icons"; +import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; @@ -15,6 +16,12 @@ const mapDispatchToProps = dispatch => ({ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { const [message, setMessage] = useState(""); + + useEffect(() => { + if (conversation.isSending === false) { + setMessage(""); + } + }, [conversation, setMessage]); const { t } = useTranslation(); const handleEnter = () => { @@ -24,22 +31,35 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage }) { messagingServiceSid: bodyshop.messagingservicesid, conversationid: conversation.id }); - setMessage(""); }; return (
a} autoSize={{ minRows: 1, maxRows: 4 }} value={message} + disabled={conversation.isSending} placeholder={t("messaging.labels.typeamessage")} - // enterButton={} onChange={e => setMessage(e.target.value)} onPressEnter={event => { + event.preventDefault(); if (!!!event.shiftKey) handleEnter(); }} /> + + } + />
); } diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 787842fc8..353799588 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -13,10 +13,11 @@ const Sdiv = styled.div` width: 80%; top: 10%; left: 10%; - // background-color: #ffcc00; + // background-color: #ffcc00; `; const ResponsiveReactGridLayout = WidthProvider(Responsive); + export default function DashboardGridComponent() { const [state, setState] = useState({ layout: [ @@ -38,19 +39,18 @@ export default function DashboardGridComponent() { console.log("breakpoint, cols", breakpoint, cols); // setState({ ...state, breakpoint: breakpoint, cols: cols }); }; - + if (true) return null; return ( The Grid. { console.log("layout", layout); setState({ ...state, layout }); - }} - > + }}> {state.layout.map((item, index) => { return ( diff --git a/client/src/graphql/messages.queries.js b/client/src/graphql/messages.queries.js index 83dc52d12..f44ebbd4b 100644 --- a/client/src/graphql/messages.queries.js +++ b/client/src/graphql/messages.queries.js @@ -2,7 +2,10 @@ import { gql } from "apollo-boost"; export const MESSAGES_SUBSCRIPTION = gql` subscription MESSAGES_SUBSCRIPTION($conversationId: uuid!) { - messages(where: { conversationid: { _eq: $conversationId } }) { + messages( + where: { conversationid: { _eq: $conversationId } } + order_by: { created_at: asc } + ) { text created_at id diff --git a/client/src/redux/messaging/messaging.actions.js b/client/src/redux/messaging/messaging.actions.js index dc8f2bc0e..dfa0f7748 100644 --- a/client/src/redux/messaging/messaging.actions.js +++ b/client/src/redux/messaging/messaging.actions.js @@ -25,6 +25,11 @@ export const sendMessage = message => ({ payload: message }); +export const sendMessageSuccess = message => ({ + type: MessagingActionTypes.SEND_MESSAGE_SUCCESS, + payload: message +}); + export const sendMessageFailure = error => ({ type: MessagingActionTypes.SEND_MESSAGE_FAILURE, payload: error diff --git a/client/src/redux/messaging/messaging.reducer.js b/client/src/redux/messaging/messaging.reducer.js index 123b88d95..cf1e8b65a 100644 --- a/client/src/redux/messaging/messaging.reducer.js +++ b/client/src/redux/messaging/messaging.reducer.js @@ -7,12 +7,16 @@ const INITIAL_STATE = { { phone_num: "6049992002", id: "519ba10d-6467-4fa5-9c22-59ae891edeb6", - open: false + open: false, + isSending: false, + sendingError: null }, { phone_num: "6049992991", id: "ab57deba-eeb9-40db-b5ae-23f3ce8d7c7b", - open: false + open: false, + isSending: false, + sendingError: null } ], error: null @@ -30,6 +34,31 @@ const messagingReducer = (state = INITIAL_STATE, action) => { ...state, visible: true }; + case MessagingActionTypes.SEND_MESSAGE: + return { + ...state, + conversations: state.conversations.map(c => + c.id === action.payload.conversationid ? { ...c, isSending: true } : c + ) + }; + case MessagingActionTypes.SEND_MESSAGE_SUCCESS: + return { + ...state, + conversations: state.conversations.map(c => + c.id === action.payload.conversationid + ? { ...c, isSending: false } + : c + ) + }; + 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 + ) + }; case MessagingActionTypes.OPEN_CONVERSATION: if ( state.conversations.find(c => c.phone_num === action.payload.phone_num) @@ -48,7 +77,9 @@ const messagingReducer = (state = INITIAL_STATE, action) => { { phone_num: action.payload.phone_num, id: action.payload.id, - open: true + open: true, + isSending: false, + sendingError: null } ] }; @@ -66,8 +97,7 @@ const messagingReducer = (state = INITIAL_STATE, action) => { c.phone_num === action.payload ? { ...c, open: !c.open } : c ) }; - case MessagingActionTypes.SEND_MESSAGE_FAILURE: - return { ...state, error: action.payload }; + default: return state; } diff --git a/client/src/redux/messaging/messaging.sagas.js b/client/src/redux/messaging/messaging.sagas.js index b5af1f479..f167b42f6 100644 --- a/client/src/redux/messaging/messaging.sagas.js +++ b/client/src/redux/messaging/messaging.sagas.js @@ -1,20 +1,22 @@ import { all, call, put, takeLatest } from "redux-saga/effects"; -import { sendMessageFailure } from "./messaging.actions"; +import { sendMessageFailure, sendMessageSuccess } from "./messaging.actions"; import MessagingActionTypes from "./messaging.types"; import axios from "axios"; +import { sendEmailFailure } from "../email/email.actions"; export function* onSendMessage() { yield takeLatest(MessagingActionTypes.SEND_MESSAGE, sendMessage); } export function* sendMessage({ payload }) { - console.log("In the saga."); try { - console.log("Message Contents", payload); - axios.post("/sms/send", payload).then(response => { - console.log(JSON.stringify(response)); - }); + const response = yield call(axios.post, "/sms/send", payload); + if (response.status === 200) { + yield put(sendMessageSuccess(payload)); + } else { + yield put(sendEmailFailure(response.data)); + } } catch (error) { - console.log("Error in sendMessage saga."); + console.log("Error in sendMessage saga.", error); yield put(sendMessageFailure(error)); } } diff --git a/client/src/redux/messaging/messaging.types.js b/client/src/redux/messaging/messaging.types.js index 83d0403b1..59a35590a 100644 --- a/client/src/redux/messaging/messaging.types.js +++ b/client/src/redux/messaging/messaging.types.js @@ -5,6 +5,7 @@ const MessagingActionTypes = { 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" }; export default MessagingActionTypes; diff --git a/client/src/redux/user/user.sagas.js b/client/src/redux/user/user.sagas.js index a9b5aa396..6d9e54612 100644 --- a/client/src/redux/user/user.sagas.js +++ b/client/src/redux/user/user.sagas.js @@ -14,21 +14,9 @@ import { } from "./user.actions"; import UserActionTypes from "./user.types"; -// export function* getSnapshotFromUserAuth(userAuth) { -// try { -// const userRef = yield call(createUserProfileDocument, userAuth); -// //const userSnapshot = yield userRef.get(); -// } catch (error) { -// yield put(signInFailure(error)); -// } -// } - export function* signInWithEmail({ payload: { email, password } }) { try { const { user } = yield auth.signInWithEmailAndPassword(email, password); - let token = yield user.getIdToken(); - // localStorage.setItem("token", token); - //window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date()); yield put( signInSuccess({ uid: user.uid, @@ -54,10 +42,6 @@ export function* isUserAuthenticated() { yield put(unauthorizedUser()); return; } - let token = yield user.getIdToken(); - //localStorage.setItem("token", token); - //window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date()); - yield put( signInSuccess({ uid: user.uid, diff --git a/server/sms/receive.js b/server/sms/receive.js index c73f2d881..7f5f3c733 100644 --- a/server/sms/receive.js +++ b/server/sms/receive.js @@ -5,8 +5,13 @@ const phone = require("phone"); exports.receive = (req, res) => { //Perform request validation - if (!req.body || !req.body.MessagingServiceSid || !req.body.SmsMessageSid) { - res.sendStatus(400); + if ( + !!!req.body || + !!!req.body.MessagingServiceSid || + !!!req.body.SmsMessageSid + ) { + res.status(400); + res.json({ success: false, error: "Malformed Request" }); } else { client .request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { @@ -43,17 +48,17 @@ exports.receive = (req, res) => { client .request(queries.INSERT_MESSAGE, { msg: newMessage }) .then(r2 => { - res.sendStatus(200); + res.status(200).end(); }) .catch(e2 => { console.log("e2", e2); - res.json(e2); + res.status(500).json(e2); }); } }) .catch(e1 => { console.log("e1", e1); - res.json(e1); + res.status(500).json(e1); }); } }; diff --git a/server/sms/send.js b/server/sms/send.js index 46b500c00..51d418980 100644 --- a/server/sms/send.js +++ b/server/sms/send.js @@ -10,15 +10,8 @@ const client = twilio( const gqlClient = require("../graphql-client/graphql-client").client; exports.send = (req, res) => { - console.log("Sending an SMS!"); const { to, messagingServiceSid, body, conversationid } = req.body; - console.log( - "to, messagingServiceSid, body, conversationid", - to, - messagingServiceSid, - body, - conversationid - ); + console.log("[Sending Sms] " + conversationid + " | " + body); if (!!to && !!messagingServiceSid && !!body && !!conversationid) { client.messages .create({ @@ -36,6 +29,7 @@ exports.send = (req, res) => { gqlClient .request(queries.INSERT_MESSAGE, { msg: newMessage }) .then(r2 => { + console.log("Responding GQL Message ID", JSON.stringify(r2)); res.sendStatus(200); }) .catch(e2 => { @@ -48,6 +42,8 @@ exports.send = (req, res) => { console.log("e1", e1); }); } else { - res.json({ success: false, message: "Missing required parameter(s)." }); + res + .status(400) + .json({ success: false, message: "Missing required parameter(s)." }); } };