diff --git a/src/components/IncomingMessageList/ConversationPreviewModal.jsx b/src/components/IncomingMessageList/ConversationPreviewModal.jsx
new file mode 100644
index 000000000..748f08cd5
--- /dev/null
+++ b/src/components/IncomingMessageList/ConversationPreviewModal.jsx
@@ -0,0 +1,178 @@
+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 loadData from '../../containers//hoc/load-data'
+import wrapMutations from '../../containers/hoc/wrap-mutations'
+import MessageResponse from './MessageResponse';
+
+const styles = StyleSheet.create({
+ conversationRow: {
+ color: 'white',
+ padding: '10px',
+ borderRadius: '5px',
+ fontWeight: 'normal',
+ }
+})
+
+class MessageList extends Component {
+ componentDidMount() {
+ this.refs.messageWindow.scrollTo(0, this.refs.messageWindow.scrollHeight)
+ }
+
+ componentDidUpdate() {
+ this.refs.messageWindow.scrollTo(0, this.refs.messageWindow.scrollHeight)
+ }
+
+ 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 = {
+ messages: PropTypes.arrayOf(PropTypes.object),
+}
+
+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 (
+
+
+
+
+ )
+ }
+}
+
+ConversationPreviewBody.propTypes = {
+ conversation: PropTypes.object
+}
+
+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 (
+
+ )
+ }
+}
+
+ConversationPreviewModal.propTypes = {
+ conversation: PropTypes.object,
+ onRequestClose: PropTypes.func
+}
+
+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
new file mode 100644
index 000000000..dca1e60d2
--- /dev/null
+++ b/src/components/IncomingMessageList/MessageResponse.jsx
@@ -0,0 +1,164 @@
+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 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'
+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: '',
+ isSending: false,
+ sendError: ''
+ }
+
+ this.handleCloseErrorDialog = this.handleCloseErrorDialog.bind(this)
+ }
+
+ 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 }) => {
+ 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 response = await this.props.mutations.sendMessage(message, contact.id)
+ const { messages } = response.data.sendMessage
+ this.props.messagesChanged(messages)
+ finalState.messageText = ''
+ } catch (e) {
+ finalState.sendError = e.message
+ }
+
+ this.setState(finalState)
+ }
+
+ handleCloseErrorDialog() {
+ this.setState({ sendError: '' })
+ }
+
+ handleClickSendMessageButton = () => {
+ this.refs.messageForm.submit()
+ }
+
+ render() {
+ const messageSchema = yup.object({
+ messageText: yup.string().required('Can\'t send empty message').max(window.MAX_MESSAGE_LENGTH)
+ })
+
+ const { messageText, isSending } = this.state
+ const isSendDisabled = isSending || messageText.trim() === ''
+
+ const errorActions = [
+
+ ]
+
+ return (
+
+
+
+
+
+ {this.state.sendError}
+
+
+ )
+ }
+}
+
+MessageResponse.propTypes = {
+ conversation: PropTypes.object,
+ messagesChanged: PropTypes.func
+}
+
+const mapMutationsToProps = () => ({
+ 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.jsx b/src/components/IncomingMessageList/index.jsx
similarity index 87%
rename from src/components/IncomingMessageList.jsx
rename to src/components/IncomingMessageList/index.jsx
index 12ee039c4..f5f85605e 100644
--- a/src/components/IncomingMessageList.jsx
+++ b/src/components/IncomingMessageList/index.jsx
@@ -1,24 +1,25 @@
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 => {
+ 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() {
@@ -228,31 +234,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}
-
- )
- })}
-
- )}
-
+ />
)
}
@@ -302,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',
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()'