From 5d2b5e7a7b7e838e64825ee8918efa20abf14186 Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 10:57:40 -0500 Subject: [PATCH 1/7] Move conversation review into its own component. --- .../ConversationPreviewModal.jsx | 62 +++++++++++++++++++ .../index.jsx} | 35 +++-------- 2 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 src/components/IncomingMessageList/ConversationPreviewModal.jsx rename src/components/{IncomingMessageList.jsx => IncomingMessageList/index.jsx} (90%) diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx new file mode 100644 index 000000000..2e697193b --- /dev/null +++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx @@ -0,0 +1,62 @@ +import React from 'react' +import type from 'prop-types' +import Dialog from 'material-ui/Dialog' + +const MessageList = (props) => { + return ( +
+ {props.messages.map((message, index) => { + const isFromContact = message.isFromContact + const style = { + color: isFromContact ? 'blue' : 'black', + textAlign: isFromContact ? 'left' : 'right' + } + + return ( +

+ {message.text} +

+ ) + })} +
+ ) +} + +MessageList.propTypes = { + messages: type.arrayOf(type.object), +} + +const ConversationPreviewBody = (props) => { + const { contact } = props + + return ( + + ) +} + +ConversationPreviewBody.propTypes = { + contact: type.object +} + +const ConversationPreviewModal = (props) => { + const { contact } = props, + isOpen = contact !== undefined + return ( + + {isOpen && } + + ) +} + +ConversationPreviewModal.propTypes = { + contact: type.object, + onRequestClose: type.func +} + +export default ConversationPreviewModal diff --git a/src/components/IncomingMessageList.jsx b/src/components/IncomingMessageList/index.jsx similarity index 90% rename from src/components/IncomingMessageList.jsx rename to src/components/IncomingMessageList/index.jsx index 12ee039c4..7605233c7 100644 --- a/src/components/IncomingMessageList.jsx +++ b/src/components/IncomingMessageList/index.jsx @@ -1,15 +1,15 @@ import React, { Component } from 'react' import type from 'prop-types' -import Dialog from 'material-ui/Dialog' import FlatButton from 'material-ui/FlatButton' import ActionOpenInNew from 'material-ui/svg-icons/action/open-in-new' -import loadData from '../containers/hoc/load-data' +import loadData from '../../containers/hoc/load-data' import { withRouter } from 'react-router' import gql from 'graphql-tag' -import LoadingIndicator from '../components/LoadingIndicator' +import LoadingIndicator from '../../components/LoadingIndicator' import DataTables from 'material-ui-datatables' +import ConversationPreviewModal from './ConversationPreviewModal'; -import { MESSAGE_STATUSES } from '../components/IncomingMessageFilter' +import { MESSAGE_STATUSES } from '../../components/IncomingMessageFilter' function prepareDataTableData(conversations) { return conversations.map(conversation => { @@ -228,31 +228,10 @@ export class IncomingMessageList extends Component { rowSizeList={[10, 30, 50, 100, 500, 1000, 2000]} selectedRows={this.state.selectedRows} /> - - {this.state.activeConversation !== undefined && ( -
- {this.state.activeConversation.messages.map((message, index) => { - const isFromContact = message.isFromContact - const style = { - color: isFromContact ? 'blue' : 'black', - textAlign: isFromContact ? 'left' : 'right' - } - - return ( -

- {message.text} -

- ) - })} -
- )} -
+ /> ) } From c62e3400838b37f079353c3c0cbed0a4a1391bd9 Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 12:34:31 -0500 Subject: [PATCH 2/7] Add reply form. Style message rows. --- .../ConversationPreviewModal.jsx | 161 ++++++++++++++++-- 1 file changed, 151 insertions(+), 10 deletions(-) diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx index 2e697193b..189123f1d 100644 --- a/src/components/IncomingMessageList/ConversationPreviewModal.jsx +++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx @@ -1,19 +1,51 @@ -import React from 'react' -import type from 'prop-types' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Form from 'react-formal' +import yup from 'yup' +import { StyleSheet, css } from 'aphrodite' import Dialog from 'material-ui/Dialog' +import FlatButton from 'material-ui/FlatButton' +import RaisedButton from 'material-ui/RaisedButton' +import Divider from 'material-ui/Divider' + +import GSForm from '../../components/forms/GSForm' +import SendButton from '../../components/SendButton' + +const styles = StyleSheet.create({ + mobile: { + '@media(min-width: 425px)': { + display: 'none !important' + } + }, + desktop: { + '@media(max-width: 450px)': { + display: 'none !important' + } + }, + conversationRow: { + color: 'white', + padding: '10px', + borderRadius: '5px', + fontWeight: 'normal', + }, + messageField: { + padding: '0px 8px', + }, +}) const MessageList = (props) => { return (
{props.messages.map((message, index) => { const isFromContact = message.isFromContact - const style = { - color: isFromContact ? 'blue' : 'black', - textAlign: isFromContact ? 'left' : 'right' + const messageStyle = { + marginLeft: isFromContact ? undefined : '60px', + marginRight: isFromContact ? '60px' : undefined, + backgroundColor: isFromContact ? '#AAAAAA' : 'rgb(33, 150, 243)', } return ( -

+

{message.text}

) @@ -23,28 +55,137 @@ const MessageList = (props) => { } MessageList.propTypes = { - messages: type.arrayOf(type.object), + messages: PropTypes.arrayOf(PropTypes.object), +} + +class MessageResponse extends Component { + constructor(props) { + super(props) + + this.state = { + messageText: '', + disabled: false + } + } + + createMessageToContact(text) { + const { assignment, contact, texter } = this.props + + return { + assignmentId: assignment.id, + contactNumber: contact.cell, + userId: texter.id, + text + } + } + + handleMessageFormChange = ({ messageText }) => this.setState({ messageText }) + + handleMessageFormSubmit = async ({ messageText }) => { + try { + const { contact } = this.props + const message = this.createMessageToContact(messageText) + if (this.state.disabled) { + return // stops from multi-send + } + console.log(`Sending message to ${contact.id}: ${message}`) + // this.setState({ disabled: true }) + // await this.props.mutations.sendMessage(message, contact.id) + } catch (e) { + this.handleSendMessageError(e) + } + } + + handleSendMessageError = (e) => { + console.error(e) + const newState = { + // snackbarActionTitle: 'Back to todos', + // snackbarOnTouchTap: this.goBackToTodos, + snackbarError: e.message + } + this.setState(newState) + } + + handleClickSendMessageButton = () => { + this.refs.messageForm.submit() + } + + render() { + const messageSchema = yup.object({ + messageText: yup.string().required("Can't send empty message").max(window.MAX_MESSAGE_LENGTH) + }) + + return ( +
+ +
+
+ +
+
+ +
+
+
+
+ ) + } +} + +MessageResponse.propTypes = { + contact: PropTypes.object, } const ConversationPreviewBody = (props) => { const { contact } = props return ( +
+ + +
) } ConversationPreviewBody.propTypes = { - contact: type.object + contact: PropTypes.object } const ConversationPreviewModal = (props) => { const { contact } = props, isOpen = contact !== undefined + + const actions = [ + + ] + return ( { } ConversationPreviewModal.propTypes = { - contact: type.object, - onRequestClose: type.func + contact: PropTypes.object, + onRequestClose: PropTypes.func } export default ConversationPreviewModal From 9e251e1be9baa0814fd666b0b1046424b1133599 Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 13:43:56 -0500 Subject: [PATCH 3/7] Implement send admin text. --- .../ConversationPreviewModal.jsx | 136 ++-------------- .../IncomingMessageList/MessageResponse.jsx | 154 ++++++++++++++++++ src/components/IncomingMessageList/index.jsx | 20 ++- src/server/api/conversations.js | 1 + 4 files changed, 181 insertions(+), 130 deletions(-) create mode 100644 src/components/IncomingMessageList/MessageResponse.jsx diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx index 189123f1d..b44f3fc4a 100644 --- a/src/components/IncomingMessageList/ConversationPreviewModal.jsx +++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx @@ -1,36 +1,19 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import Form from 'react-formal' -import yup from 'yup' import { StyleSheet, css } from 'aphrodite' import Dialog from 'material-ui/Dialog' import FlatButton from 'material-ui/FlatButton' -import RaisedButton from 'material-ui/RaisedButton' import Divider from 'material-ui/Divider' -import GSForm from '../../components/forms/GSForm' -import SendButton from '../../components/SendButton' +import MessageResponse from './MessageResponse'; const styles = StyleSheet.create({ - mobile: { - '@media(min-width: 425px)': { - display: 'none !important' - } - }, - desktop: { - '@media(max-width: 450px)': { - display: 'none !important' - } - }, conversationRow: { color: 'white', padding: '10px', borderRadius: '5px', fontWeight: 'normal', - }, - messageField: { - padding: '0px 8px', - }, + } }) const MessageList = (props) => { @@ -58,120 +41,25 @@ MessageList.propTypes = { messages: PropTypes.arrayOf(PropTypes.object), } -class MessageResponse extends Component { - constructor(props) { - super(props) - - this.state = { - messageText: '', - disabled: false - } - } - - createMessageToContact(text) { - const { assignment, contact, texter } = this.props - - return { - assignmentId: assignment.id, - contactNumber: contact.cell, - userId: texter.id, - text - } - } - - handleMessageFormChange = ({ messageText }) => this.setState({ messageText }) - - handleMessageFormSubmit = async ({ messageText }) => { - try { - const { contact } = this.props - const message = this.createMessageToContact(messageText) - if (this.state.disabled) { - return // stops from multi-send - } - console.log(`Sending message to ${contact.id}: ${message}`) - // this.setState({ disabled: true }) - // await this.props.mutations.sendMessage(message, contact.id) - } catch (e) { - this.handleSendMessageError(e) - } - } - - handleSendMessageError = (e) => { - console.error(e) - const newState = { - // snackbarActionTitle: 'Back to todos', - // snackbarOnTouchTap: this.goBackToTodos, - snackbarError: e.message - } - this.setState(newState) - } - - handleClickSendMessageButton = () => { - this.refs.messageForm.submit() - } - +class ConversationPreviewBody extends Component { render() { - const messageSchema = yup.object({ - messageText: yup.string().required("Can't send empty message").max(window.MAX_MESSAGE_LENGTH) - }) - return ( -
- -
-
- -
-
- -
-
-
+
+ + +
) } } -MessageResponse.propTypes = { - contact: PropTypes.object, -} - -const ConversationPreviewBody = (props) => { - const { contact } = props - - return ( -
- - - -
- ) -} - ConversationPreviewBody.propTypes = { - contact: PropTypes.object + conversation: PropTypes.object } const ConversationPreviewModal = (props) => { - const { contact } = props, - isOpen = contact !== undefined + const { conversation } = props, + isOpen = conversation !== undefined const actions = [ { autoScrollBodyContent onRequestClose={props.onRequestClose} > - {isOpen && } + {isOpen && }
) } ConversationPreviewModal.propTypes = { - contact: PropTypes.object, + conversation: PropTypes.object, onRequestClose: PropTypes.func } diff --git a/src/components/IncomingMessageList/MessageResponse.jsx b/src/components/IncomingMessageList/MessageResponse.jsx new file mode 100644 index 000000000..76049e0aa --- /dev/null +++ b/src/components/IncomingMessageList/MessageResponse.jsx @@ -0,0 +1,154 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Form from 'react-formal' +import yup from 'yup' +import gql from 'graphql-tag' +import { StyleSheet, css } from 'aphrodite' + +import loadData from '../../containers//hoc/load-data' +import wrapMutations from '../../containers/hoc/wrap-mutations' +import GSForm from '../../components/forms/GSForm' +import SendButton from '../../components/SendButton' + +const styles = StyleSheet.create({ + messageField: { + padding: '0px 8px', + }, +}) + +class MessageResponse extends Component { + constructor(props) { + super(props) + + this.state = { + messageText: '', + disabled: false + } + } + + createMessageToContact(text) { + const { contact, texter } = this.props.conversation + + return { + assignmentId: contact.assignmentId, + contactNumber: contact.cell, + userId: texter.id, + text + } + } + + handleMessageFormChange = ({ messageText }) => this.setState({ messageText }) + + handleMessageFormSubmit = async ({ messageText }) => { + try { + const { contact } = this.props.conversation + const message = this.createMessageToContact(messageText) + if (this.state.disabled) { + return // stops from multi-send + } + this.setState({ disabled: true }) + await this.props.mutations.sendMessage(message, contact.id) + } catch (e) { + this.handleSendMessageError(e) + } + } + + handleSendMessageError = (e) => { + console.error(e) + const newState = { + // snackbarActionTitle: 'Back to todos', + // snackbarOnTouchTap: this.goBackToTodos, + snackbarError: e.message + } + this.setState(newState) + } + + handleClickSendMessageButton = () => { + this.refs.messageForm.submit() + } + + render() { + const messageSchema = yup.object({ + messageText: yup.string().required("Can't send empty message").max(window.MAX_MESSAGE_LENGTH) + }) + + return ( +
+ +
+
+ +
+
+ +
+
+
+
+ ) + } +} + +MessageResponse.propTypes = { + conversation: PropTypes.object +} + +const mapMutationsToProps = () => ({ + createOptOut: (optOut, campaignContactId) => ({ + mutation: gql` + mutation createOptOut($optOut: OptOutInput!, $campaignContactId: String!) { + createOptOut(optOut: $optOut, campaignContactId: $campaignContactId) { + id + optOut { + id + createdAt + } + } + } + `, + variables: { + optOut, + campaignContactId + } + }), + sendMessage: (message, campaignContactId) => ({ + mutation: gql` + mutation sendMessage($message: MessageInput!, $campaignContactId: String!) { + sendMessage(message: $message, campaignContactId: $campaignContactId) { + id + messageStatus + messages { + id + createdAt + text + isFromContact + } + } + } + `, + variables: { + message, + campaignContactId + } + }) +}) + +export default loadData(wrapMutations(MessageResponse), { + mapMutationsToProps +}) diff --git a/src/components/IncomingMessageList/index.jsx b/src/components/IncomingMessageList/index.jsx index 7605233c7..f5f85605e 100644 --- a/src/components/IncomingMessageList/index.jsx +++ b/src/components/IncomingMessageList/index.jsx @@ -12,13 +12,14 @@ import ConversationPreviewModal from './ConversationPreviewModal'; import { MESSAGE_STATUSES } from '../../components/IncomingMessageFilter' function prepareDataTableData(conversations) { - return conversations.map(conversation => { + return conversations.map((conversation, index) => { return { campaignTitle: conversation.campaign.title, texter: conversation.texter.displayName, to: conversation.contact.firstName + ' ' + conversation.contact.lastName + (conversation.contact.optOut.cell ? '⛔️' : ''), status: conversation.contact.messageStatus, - messages: conversation.contact.messages + messages: conversation.contact.messages, + index } }) } @@ -154,7 +155,7 @@ export class IncomingMessageList extends Component { { event.stopPropagation() - this.handleOpenConversation(row) + this.handleOpenConversation(row.index) }} icon={} /> @@ -192,8 +193,13 @@ export class IncomingMessageList extends Component { this.props.onConversationSelected(rowsSelected, selectedConversations) } - handleOpenConversation(contact) { - this.setState({ activeConversation: contact }) + handleOpenConversation(index) { + const conversation = this.props.conversations.conversations.conversations[index] + const activeConversation = { + contact: conversation.contact, + texter: conversation.texter + } + this.setState({ activeConversation }) } handleCloseConversation() { @@ -229,7 +235,7 @@ export class IncomingMessageList extends Component { selectedRows={this.state.selectedRows} />
@@ -281,8 +287,10 @@ const mapQueriesToProps = ({ ownProps }) => ({ } contact { id + assignmentId firstName lastName + cell messageStatus messages { id diff --git a/src/server/api/conversations.js b/src/server/api/conversations.js index 4c9326bc5..12cfdb8b2 100644 --- a/src/server/api/conversations.js +++ b/src/server/api/conversations.js @@ -95,6 +95,7 @@ export async function getConversations( 'campaign_contact.id as cc_id', 'campaign_contact.first_name as cc_first_name', 'campaign_contact.last_name as cc_last_name', + 'campaign_contact.cell', 'campaign_contact.message_status', 'campaign_contact.is_opted_out', 'campaign_contact.updated_at', From 55999139ef8012d37c4a278d47a72eae20d59479 Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 14:11:16 -0500 Subject: [PATCH 4/7] Update message list with new message. --- .../ConversationPreviewModal.jsx | 18 ++++++++-- .../IncomingMessageList/MessageResponse.jsx | 34 +++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx index b44f3fc4a..8725f07d5 100644 --- a/src/components/IncomingMessageList/ConversationPreviewModal.jsx +++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx @@ -42,12 +42,26 @@ MessageList.propTypes = { } class ConversationPreviewBody extends Component { + constructor(props) { + super(props) + + this.state = { + messages: props.conversation.contact.messages + } + + this.messagesChanged = this.messagesChanged.bind(this) + } + + messagesChanged(messages) { + this.setState({ messages }) + } + render() { return (
- + - +
) } diff --git a/src/components/IncomingMessageList/MessageResponse.jsx b/src/components/IncomingMessageList/MessageResponse.jsx index 76049e0aa..a5a25f84e 100644 --- a/src/components/IncomingMessageList/MessageResponse.jsx +++ b/src/components/IncomingMessageList/MessageResponse.jsx @@ -22,7 +22,7 @@ class MessageResponse extends Component { this.state = { messageText: '', - disabled: false + isSending: false } } @@ -40,17 +40,24 @@ class MessageResponse extends Component { handleMessageFormChange = ({ messageText }) => this.setState({ messageText }) handleMessageFormSubmit = async ({ messageText }) => { + const { contact } = this.props.conversation + const message = this.createMessageToContact(messageText) + if (this.state.isSending) { + return // stops from multi-send + } + this.setState({ isSending: true }) + + const finalState = { isSending: false } try { - const { contact } = this.props.conversation - const message = this.createMessageToContact(messageText) - if (this.state.disabled) { - return // stops from multi-send - } - this.setState({ disabled: true }) - await this.props.mutations.sendMessage(message, contact.id) + const response = await this.props.mutations.sendMessage(message, contact.id) + const { messages } = response.data.sendMessage + this.props.messagesChanged(messages) + finalState.messageText = '' } catch (e) { this.handleSendMessageError(e) } + + this.setState(finalState) } handleSendMessageError = (e) => { @@ -69,9 +76,12 @@ class MessageResponse extends Component { render() { const messageSchema = yup.object({ - messageText: yup.string().required("Can't send empty message").max(window.MAX_MESSAGE_LENGTH) + messageText: yup.string().required('Can\'t send empty message').max(window.MAX_MESSAGE_LENGTH) }) + const { messageText, isSending } = this.state + const isSendDisabled = isSending || messageText.trim() === '' + return (
@@ -95,6 +105,7 @@ class MessageResponse extends Component { label='Send a response' multiLine fullWidth + disabled={isSending} rowsMax={6} />
@@ -106,7 +117,8 @@ class MessageResponse extends Component { } MessageResponse.propTypes = { - conversation: PropTypes.object + conversation: PropTypes.object, + messagesChanged: PropTypes.func } const mapMutationsToProps = () => ({ From 1902c4a561ae7c366fd91606855a0c00a64847e2 Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 14:29:18 -0500 Subject: [PATCH 5/7] Add error feedback. --- .../IncomingMessageList/MessageResponse.jsx | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/components/IncomingMessageList/MessageResponse.jsx b/src/components/IncomingMessageList/MessageResponse.jsx index a5a25f84e..3148afa46 100644 --- a/src/components/IncomingMessageList/MessageResponse.jsx +++ b/src/components/IncomingMessageList/MessageResponse.jsx @@ -4,6 +4,8 @@ import Form from 'react-formal' import yup from 'yup' import gql from 'graphql-tag' import { StyleSheet, css } from 'aphrodite' +import Dialog from 'material-ui/Dialog' +import FlatButton from 'material-ui/FlatButton' import loadData from '../../containers//hoc/load-data' import wrapMutations from '../../containers/hoc/wrap-mutations' @@ -22,8 +24,11 @@ class MessageResponse extends Component { this.state = { messageText: '', - isSending: false + isSending: false, + sendError: '' } + + this.handleCloseErrorDialog = this.handleCloseErrorDialog.bind(this) } createMessageToContact(text) { @@ -54,20 +59,14 @@ class MessageResponse extends Component { this.props.messagesChanged(messages) finalState.messageText = '' } catch (e) { - this.handleSendMessageError(e) + finalState.sendError = e.message } this.setState(finalState) } - handleSendMessageError = (e) => { - console.error(e) - const newState = { - // snackbarActionTitle: 'Back to todos', - // snackbarOnTouchTap: this.goBackToTodos, - snackbarError: e.message - } - this.setState(newState) + handleCloseErrorDialog() { + this.setState({ sendError: '' }) } handleClickSendMessageButton = () => { @@ -82,6 +81,14 @@ class MessageResponse extends Component { const { messageText, isSending } = this.state const isSendDisabled = isSending || messageText.trim() === '' + const errorActions = [ + + ] + return (
+ +

{this.state.sendError}

+
) } From bb9e285feb7b6d28cf14df082abea337aaf8110b Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 14:42:30 -0500 Subject: [PATCH 6/7] Set status correctly for sender sending multiple texts in a row. --- src/server/api/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api/schema.js b/src/server/api/schema.js index 9696859dd..74e6db09d 100644 --- a/src/server/api/schema.js +++ b/src/server/api/schema.js @@ -973,7 +973,7 @@ const rootMutations = { await messageInstance.save() - if (contact.message_status === 'needsResponse') { + if (contact.message_status === 'needsResponse' || contact.message_status === 'convo') { const service = serviceMap[messageInstance.service || process.env.DEFAULT_SERVICE] contact.message_status = 'convo' contact.updated_at = 'now()' From 4bb7bff023b57501bba1a0f8507400403280385a Mon Sep 17 00:00:00 2001 From: Benjamin Chrobot Date: Tue, 29 Jan 2019 15:26:54 -0500 Subject: [PATCH 7/7] Handle Opt-Out. Scroll messaging window to bottom. --- .../ConversationPreviewModal.jsx | 163 +++++++++++++----- .../IncomingMessageList/MessageResponse.jsx | 17 -- 2 files changed, 118 insertions(+), 62 deletions(-) diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx index 8725f07d5..748f08cd5 100644 --- a/src/components/IncomingMessageList/ConversationPreviewModal.jsx +++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx @@ -1,10 +1,12 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import gql from 'graphql-tag' import { StyleSheet, css } from 'aphrodite' import Dialog from 'material-ui/Dialog' import FlatButton from 'material-ui/FlatButton' -import Divider from 'material-ui/Divider' +import loadData from '../../containers//hoc/load-data' +import wrapMutations from '../../containers/hoc/wrap-mutations' import MessageResponse from './MessageResponse'; const styles = StyleSheet.create({ @@ -16,25 +18,35 @@ const styles = StyleSheet.create({ } }) -const MessageList = (props) => { - return ( -
- {props.messages.map((message, index) => { - const isFromContact = message.isFromContact - const messageStyle = { - marginLeft: isFromContact ? undefined : '60px', - marginRight: isFromContact ? '60px' : undefined, - backgroundColor: isFromContact ? '#AAAAAA' : 'rgb(33, 150, 243)', - } +class MessageList extends Component { + componentDidMount() { + this.refs.messageWindow.scrollTo(0, this.refs.messageWindow.scrollHeight) + } + + componentDidUpdate() { + this.refs.messageWindow.scrollTo(0, this.refs.messageWindow.scrollHeight) + } - return ( -

- {message.text} -

- ) - })} -
- ) + render() { + return ( +
+ {this.props.messages.map((message, index) => { + const isFromContact = message.isFromContact + const messageStyle = { + marginLeft: isFromContact ? undefined : '60px', + marginRight: isFromContact ? '60px' : undefined, + backgroundColor: isFromContact ? '#AAAAAA' : 'rgb(33, 150, 243)', + } + + return ( +

+ {message.text} +

+ ) + })} +
+ ) + } } MessageList.propTypes = { @@ -60,7 +72,6 @@ class ConversationPreviewBody extends Component { return (
-
) @@ -71,30 +82,70 @@ ConversationPreviewBody.propTypes = { conversation: PropTypes.object } -const ConversationPreviewModal = (props) => { - const { conversation } = props, - isOpen = conversation !== undefined - - const actions = [ - - ] - - return ( - - {isOpen && } - - ) +class ConversationPreviewModal extends Component { + constructor(props) { + super(props) + + this.state = { + optOutError: '' + } + } + + handleClickOptOut = async () => { + const { contact } = this.props.conversation + const optOut = { + cell: contact.cell, + assignmentId: contact.assignmentId + } + try { + const response = await this.props.mutations.createOptOut(optOut, campaignContactId) + if (response.errors) { + const errorText = response.errors.join('\n') + throw new Error(errorText) + } + } catch (error) { + this.setState({ optOutError: error.message }) + } + } + + render() { + const { conversation } = this.props, + isOpen = conversation !== undefined + + const primaryActions = [ + , + + ] + + return ( + +
+ {isOpen && } + +

{this.state.optOutError}

+
+
+
+ ) + } } ConversationPreviewModal.propTypes = { @@ -102,4 +153,26 @@ ConversationPreviewModal.propTypes = { onRequestClose: PropTypes.func } -export default ConversationPreviewModal +const mapMutationsToProps = () => ({ + createOptOut: (optOut, campaignContactId) => ({ + mutation: gql` + mutation createOptOut($optOut: OptOutInput!, $campaignContactId: String!) { + createOptOut(optOut: $optOut, campaignContactId: $campaignContactId) { + id + optOut { + id + createdAt + } + } + } + `, + variables: { + optOut, + campaignContactId + } + }) +}) + +export default loadData(wrapMutations(ConversationPreviewModal), { + mapMutationsToProps +}) diff --git a/src/components/IncomingMessageList/MessageResponse.jsx b/src/components/IncomingMessageList/MessageResponse.jsx index 3148afa46..dca1e60d2 100644 --- a/src/components/IncomingMessageList/MessageResponse.jsx +++ b/src/components/IncomingMessageList/MessageResponse.jsx @@ -137,23 +137,6 @@ MessageResponse.propTypes = { } const mapMutationsToProps = () => ({ - createOptOut: (optOut, campaignContactId) => ({ - mutation: gql` - mutation createOptOut($optOut: OptOutInput!, $campaignContactId: String!) { - createOptOut(optOut: $optOut, campaignContactId: $campaignContactId) { - id - optOut { - id - createdAt - } - } - } - `, - variables: { - optOut, - campaignContactId - } - }), sendMessage: (message, campaignContactId) => ({ mutation: gql` mutation sendMessage($message: MessageInput!, $campaignContactId: String!) {