Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin sweep reply #10

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions src/components/IncomingMessageList/ConversationPreviewModal.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div ref="messageWindow" style={{maxHeight: '300px', overflowY: 'scroll'}}>
{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 (
<p key={index} className={css(styles.conversationRow)} style={messageStyle}>
{message.text}
</p>
)
})}
</div>
)
}
}

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 (
<div>
<MessageList messages={this.state.messages} />
<MessageResponse conversation={this.props.conversation} messagesChanged={this.messagesChanged} />
</div>
)
}
}

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 = [
<FlatButton
label="Opt-Out"
secondary={true}
onClick={this.handleClickOptOut}
/>,
<FlatButton
label="Close"
primary={true}
onClick={this.props.onRequestClose}
/>
]

return (
<Dialog
title='Messages'
open={isOpen}
actions={primaryActions}
modal={false}
onRequestClose={this.props.onRequestClose}
>
<div>
{isOpen && <ConversationPreviewBody {...this.props} />}
<Dialog
title='Error Opting Out'
open={!!this.state.optOutError}
modal={false}
>
<p>{this.state.optOutError}</p>
</Dialog>
</div>
</Dialog>
)
}
}

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
})
164 changes: 164 additions & 0 deletions src/components/IncomingMessageList/MessageResponse.jsx
Original file line number Diff line number Diff line change
@@ -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 = [
<FlatButton
label="OK"
primary={true}
onClick={this.handleCloseErrorDialog}
/>
]

return (
<div className={css(styles.messageField)}>
<GSForm
ref='messageForm'
schema={messageSchema}
value={{ messageText: this.state.messageText }}
onSubmit={this.handleMessageFormSubmit}
onChange={this.handleMessageFormChange}
>
<div style={{position: 'relative'}}>
<div style={{position: 'absolute', right: 0, bottom: 0, width: '120px'}}>
<SendButton
threeClickEnabled={false}
onFinalTouchTap={this.handleClickSendMessageButton}
disabled={isSendDisabled}
/>
</div>
<div style={{marginRight: '120px'}}>
<Form.Field
name='messageText'
label='Send a response'
multiLine
fullWidth
disabled={isSending}
rowsMax={6}
/>
</div>
</div>
</GSForm>
<Dialog
title='Error Sending'
open={!!this.state.sendError}
actions={errorActions}
modal={false}
>
<p>{this.state.sendError}</p>
</Dialog>
</div>
)
}
}

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
})
Loading