Last active
August 28, 2017 18:57
-
-
Save angelo510/a69caf5a958506eadd335e253d8e9f55 to your computer and use it in GitHub Desktop.
React+Redux+Saga Code Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
SET_CHANNEL_FILTER, | |
SET_CHANNEL_TYPE, | |
SET_CONNECTIONS_LIST, | |
TOGGLE_ADD_CONNECTION_DIALOG, | |
FETCH_SOCIAL_URL, | |
CONNECTION_CALLBACK, | |
SET_SOCIAL_URLS, | |
REMOVE_CONNECTION, | |
SET_SUB_CALLBACK, | |
SET_SUB_CHANNEL, | |
CREATE_SUB_CHANNELS, | |
CLEAR_SUB_DATA, | |
GET_WORDPRESS_BLOGS, | |
VALIDATE_CONNECTIONS, | |
} from './constants'; | |
export function setChannelFilter(channelFilter) { | |
return { type: SET_CHANNEL_FILTER, channelFilter }; | |
} | |
export function setChannelType(channelType) { | |
return { type: SET_CHANNEL_TYPE, channelType }; | |
} | |
export function setConnectionsList(connections) { | |
return { type: SET_CONNECTIONS_LIST, connections }; | |
} | |
export function toggleDialog(shown) { | |
return { type: TOGGLE_ADD_CONNECTION_DIALOG, shown }; | |
} | |
export function getSocialUrl() { | |
return { type: FETCH_SOCIAL_URL }; | |
} | |
export function removeConnection(connectionId) { | |
return { type: REMOVE_CONNECTION, connectionId }; | |
} | |
export function connectionCallback(channelObject) { | |
return { type: CONNECTION_CALLBACK, channelObject }; | |
} | |
export function setSocialUrls(urls) { | |
return { type: SET_SOCIAL_URLS, urls }; | |
} | |
export function setSubCallback(sub) { | |
return { type: SET_SUB_CALLBACK, sub }; | |
} | |
export function setSubChannel(subChannel) { | |
return { type: SET_SUB_CHANNEL, subChannel }; | |
} | |
export function createSubChannels(data) { | |
return { type: CREATE_SUB_CHANNELS, data }; | |
} | |
export function clearSubData() { | |
return { type: CLEAR_SUB_DATA }; | |
} | |
export function validateConnections(id) { | |
return { type: VALIDATE_CONNECTIONS, id }; | |
} | |
export function getWordpressBlogs(data) { | |
return { type: GET_WORDPRESS_BLOGS, data }; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component, PropTypes } from 'react'; | |
import { connect } from 'react-redux'; | |
import { createStructuredSelector } from 'reselect'; | |
import { Field, FieldArray, change, reduxForm, reset, blur } from 'redux-form'; | |
import { Modal } from 'react-bootstrap'; | |
import _, { forEach, filter } from 'lodash'; | |
import { normalizePhoneDisplay } from '../../../app/common/helper/functions'; | |
import Input from '../../components/Input'; | |
import AddEmailNotificationForm from '../../components/AddEmailNotificationForm'; | |
import CenteredModal from '../../components/CenteredModal/index'; | |
import LoadingSpinner from '../../components/LoadingSpinner'; | |
import { addEmailNotificationUser, fetchClientAdmins } from '../../containers/App/actions'; | |
import { selectCurrentUser, selectClientSites } from '../../containers/App/selectors'; | |
import { selectSyncErrorBool, selectSyncErrors, selectValues } from '../../common/selectors/form.selector'; | |
import { selectEditedStudy, selectAddNotificationProcess, selectHomePageClientAdmins } from '../../containers/HomePage/selectors'; | |
import StudyAddForm from '../../components/StudyAddForm'; | |
import { | |
changeStudyAdd, | |
resetChangeStudyAddState, | |
} from '../../containers/HomePage/AdminDashboard/actions'; | |
import { | |
selectChangeStudyAddProcess, | |
selectUpdatedStudyAd, | |
} from '../../containers/HomePage/AdminDashboard/selectors'; | |
import RenderEmailsList from './RenderEmailsList'; | |
import formValidator from './validator'; | |
const formName = 'editStudy'; | |
const mapDispatchToProps = (dispatch) => ({ | |
addEmailNotificationUser: (payload) => dispatch(addEmailNotificationUser(payload)), | |
fetchClientAdmins: (id) => dispatch(fetchClientAdmins(id)), | |
change: (name, value) => dispatch(change(formName, name, value)), | |
blur: (field, value) => dispatch(blur(formName, field, value)), | |
submitStudyAdd: (values) => dispatch(changeStudyAdd(values)), | |
resetChangeAddState: () => dispatch(resetChangeStudyAddState()), | |
resetForm: () => dispatch(reset(formName)), | |
}); | |
@reduxForm({ form: formName, validate: formValidator }) | |
@connect(null, mapDispatchToProps) | |
class EditStudyForm extends Component { // eslint-disable-line react/prefer-stateless-function | |
static propTypes = { | |
change: PropTypes.func.isRequired, | |
blur: React.PropTypes.func.isRequired, | |
currentUser: PropTypes.object, | |
formError: PropTypes.bool, | |
formErrors: PropTypes.object, | |
formValues: PropTypes.object, | |
editedStudy: PropTypes.object, | |
siteUsers: PropTypes.array, | |
handleSubmit: PropTypes.func, | |
show: PropTypes.bool, | |
onShow: PropTypes.func, | |
onHide: PropTypes.func, | |
resetForm: PropTypes.func, | |
onSubmit: PropTypes.func, | |
fields: PropTypes.object, | |
selectedStudyId: PropTypes.number, | |
selectedSiteId: PropTypes.number, | |
clientSites: PropTypes.object, | |
addEmailNotificationUser: PropTypes.func, | |
addNotificationProcess: PropTypes.object, | |
clientAdmins: PropTypes.object, | |
fetchClientAdmins: PropTypes.func.isRequired, | |
submitStudyAdd: PropTypes.func.isRequired, | |
changeStudyAddProcess: PropTypes.any, | |
updatedStudyAd: PropTypes.any, | |
resetChangeAddState: PropTypes.func.isRequired, | |
}; | |
constructor(props) { | |
super(props); | |
this.resetState = this.resetState.bind(this); | |
this.handleCloseModal = this.handleCloseModal.bind(this); | |
this.handleFormSubmit = this.handleFormSubmit.bind(this); | |
this.renderEmailList = this.renderEmailList.bind(this); | |
this.addEmailNotificationClick = this.addEmailNotificationClick.bind(this); | |
this.closeAddEmailModal = this.closeAddEmailModal.bind(this); | |
this.addEmailNotificationSubmit = this.addEmailNotificationSubmit.bind(this); | |
this.selectAll = this.selectAll.bind(this); | |
this.selectEmail = this.selectEmail.bind(this); | |
this.openStudyAddModal = this.openStudyAddModal.bind(this); | |
this.closeStudyAddModal = this.closeStudyAddModal.bind(this); | |
this.openStudyPreviewModal = this.openStudyPreviewModal.bind(this); | |
this.closeStudyPreviewModal = this.closeStudyPreviewModal.bind(this); | |
this.uploadStudyAdd = this.uploadStudyAdd.bind(this); | |
this.onPhoneBlur = this.onPhoneBlur.bind(this); | |
this.handleFileChange = this.handleFileChange.bind(this); | |
this.state = { | |
updatedStudyAd: null, | |
addEmailModalShow: false, | |
studyAddModalOpen: false, | |
studyPreviewModalOpen: false, | |
}; | |
} | |
componentWillMount() { | |
const { change, currentUser, fetchClientAdmins, siteUsers } = this.props; | |
if (currentUser && currentUser.roleForClient.isAdmin) { | |
fetchClientAdmins(currentUser.roleForClient.client_id); | |
} | |
change('emailNotifications', siteUsers); | |
} | |
componentWillReceiveProps(newProps) { | |
const { clientAdmins, clientSites, change, resetChangeAddState } = this.props; | |
if (newProps.selectedStudyId && newProps.selectedStudyId !== this.props.selectedStudyId) { | |
const fields = []; | |
let currentStudy = null; | |
let isAllChecked = true; | |
_.forEach(clientSites.details, (site) => { | |
if (site.id === newProps.selectedSiteId) { | |
_.forEach(site.studies, (study) => { | |
if (study.id === newProps.selectedStudyId) { | |
currentStudy = study; | |
this.setState({ currentStudy }); | |
} | |
}); | |
if (clientAdmins) { | |
// add admin users to the list | |
_.forEach(clientAdmins.details, (role) => { | |
const isChecked = _.find(currentStudy.studyNotificationEmails, (item) => (item.user_id === role.userId)); | |
if (!isChecked) { | |
isAllChecked = false; | |
} | |
fields.push({ | |
firstName: role.firstName, | |
lastName: role.lastName, | |
userId: role.userId, | |
isChecked, | |
}); | |
}); | |
} | |
// add site users to the list | |
_.forEach(site.roles, (role) => { | |
if (role.user) { | |
const isChecked = _.find(currentStudy.studyNotificationEmails, (item) => (item.user_id === role.user.id)); | |
if (!isChecked) { | |
isAllChecked = false; | |
} | |
// if user has been deleted, their role still exists, but they aren't fetched anymore | |
fields.push({ | |
firstName: role.user.firstName, | |
lastName: role.user.lastName, | |
userId: role.user.id, | |
isChecked, | |
}); | |
} | |
}); | |
} | |
}); | |
change('recruitmentPhone', normalizePhoneDisplay(currentStudy.recruitmentPhone)); | |
change('emailNotifications', fields); | |
change('checkAllInput', isAllChecked); | |
this.setState({ | |
updatedStudyAd: null, | |
fileSrc: (currentStudy.image || null), | |
}); | |
} | |
if (this.props.addNotificationProcess.saving && !newProps.addNotificationProcess.saving && newProps.addNotificationProcess.savedUser) { | |
let addFields = this.props.formValues.emailNotifications; | |
const values = { | |
firstName: newProps.addNotificationProcess.savedUser.firstName, | |
lastName: newProps.addNotificationProcess.savedUser.lastName, | |
userId: newProps.addNotificationProcess.savedUser.id, | |
isChecked: true, | |
}; | |
if (!addFields) { | |
addFields = [values]; | |
} else { | |
addFields.push(values); | |
} | |
change('emailNotifications', addFields); | |
} | |
if (!newProps.changeStudyAddProcess.saving && newProps.changeStudyAddProcess.success) { | |
resetChangeAddState(); | |
} | |
if (newProps.updatedStudyAd && this.state.updatedStudyAd !== newProps.updatedStudyAd) { | |
const currentStudy = this.state.currentStudy; | |
currentStudy.image = newProps.updatedStudyAd; | |
this.setState({ updatedStudyAd: newProps.updatedStudyAd, currentStudy }); | |
this.closeStudyAddModal(); | |
} | |
} | |
onPhoneBlur(event) { | |
const { blur } = this.props; | |
const formattedPhoneNumber = normalizePhoneDisplay(event.target.value); | |
blur('recruitmentPhone', formattedPhoneNumber); | |
} | |
handleFileChange(e) { | |
if (e.target.files[0]) { | |
this.setState({ fileName: e.target.files[0].name, fileSrc: URL.createObjectURL(e.target.files[0]) }); | |
} | |
} | |
resetState() { | |
const resetState = { | |
exposureLevel: null, | |
campaignLength: null, | |
condenseTwoWeeks: false, | |
patientMessagingSuite: false, | |
callTracking: false, | |
minDate: 'none', | |
isReset: false, | |
emailFields: null, | |
}; | |
this.setState(resetState, () => { | |
this.props.resetForm(); | |
}); | |
} | |
handleCloseModal() { | |
this.props.onHide(false); | |
this.resetState(); | |
} | |
handleFormSubmit(event) { | |
event.preventDefault(); | |
const { onSubmit } = this.props; | |
onSubmit(); | |
} | |
addEmailNotificationClick() { | |
this.setState({ addEmailModalShow: true }); | |
this.props.onHide(true); | |
} | |
closeAddEmailModal() { | |
this.setState({ addEmailModalShow: false }); | |
this.props.onShow(); | |
} | |
addEmailNotificationSubmit(values) { | |
const { currentUser, selectedStudyId, selectedSiteId } = this.props; | |
this.props.addEmailNotificationUser({ | |
...values, | |
clientId: currentUser.roleForClient.client_id, | |
addForNotification: true, | |
studyId: selectedStudyId, | |
clientRole:{ | |
siteId: selectedSiteId, | |
}, | |
}); | |
this.closeAddEmailModal(); | |
} | |
selectAll(e) { | |
if (this.props.formValues.emailNotifications) { | |
const { change } = this.props; | |
forEach(this.props.formValues.emailNotifications, (value, index) => { | |
change(`emailNotifications[${index}].isChecked`, e); | |
}); | |
} | |
} | |
selectEmail(e) { | |
const { change } = this.props; | |
if (this.props.formValues.checkAllInput && !e) { | |
change('checkAllInput', false); | |
} else if (!this.props.formValues.checkAllInput && e) { | |
const checkedArr = filter(this.props.formValues.emailNotifications, (o) => o.isChecked); | |
if ((checkedArr.length + 1) === this.props.formValues.emailNotifications.length) { | |
change('checkAllInput', true); | |
} | |
} | |
} | |
closeStudyAddModal() { | |
this.setState({ studyAddModalOpen: false }); | |
this.props.onShow(); | |
} | |
openStudyAddModal() { | |
this.setState({ studyAddModalOpen: true }); | |
this.props.onHide(true); | |
} | |
closeStudyPreviewModal() { | |
this.setState({ studyPreviewModalOpen: false }); | |
} | |
openStudyPreviewModal() { | |
this.setState({ studyPreviewModalOpen: true }); | |
} | |
uploadStudyAdd(e) { | |
if (e.type !== 'application/pdf') { | |
e.toBlob((blob) => { | |
this.props.submitStudyAdd({ file: blob, study_id: this.state.currentStudy.id }); | |
}); | |
} else { | |
this.props.submitStudyAdd({ file: e, study_id: this.state.currentStudy.id }); | |
} | |
} | |
renderEmailList() { | |
const { change, formValues } = this.props; | |
return ( | |
<div className="emails-list-holder"> | |
<FieldArray | |
name="emailNotifications" | |
component={RenderEmailsList} | |
change={change} | |
formValues={formValues} | |
addEmailNotification={this.addEmailNotificationClick} | |
closeEmailNotification={this.closeAddEmailModal} | |
emailFields={this.state.emailFields} | |
/> | |
</div> | |
); | |
} | |
render() { | |
const { editedStudy, changeStudyAddProcess } = this.props; | |
const image = (this.state.currentStudy && this.state.currentStudy.image) ? this.state.currentStudy.image : null; | |
const fileSrc = this.state.updatedStudyAd || image; | |
const preview = | |
(<div className="img-preview"> | |
<a | |
className="lightbox-opener" | |
onClick={this.openStudyPreviewModal} | |
> | |
<img src={fileSrc} id="img-preview" alt="preview" /> | |
</a> | |
</div>); | |
return ( | |
<div> | |
<Modal | |
className="edit-study-modal" | |
id="edit-study" | |
dialogComponentClass={CenteredModal} | |
show={this.props.show} | |
onHide={this.handleCloseModal} | |
backdrop | |
keyboard | |
> | |
<Modal.Header> | |
<Modal.Title>Edit Information</Modal.Title> | |
<a className="lightbox-close close" onClick={this.handleCloseModal}> | |
<i className="icomoon-icon_close" /> | |
</a> | |
</Modal.Header> | |
<Modal.Body> | |
<div className="form-study"> | |
<div className="scroll jcf--scrollable"> | |
<div className="holder-inner"> | |
<form className="form-edit-study" onSubmit={this.handleFormSubmit}> | |
<div className="edit-study form-fields"> | |
<div className="field-row"> | |
<strong className="label required"> | |
<label>RECRUITMENT PHONE</label> | |
</strong> | |
<div className="field"> | |
<Field | |
name="recruitmentPhone" | |
component={Input} | |
type="text" | |
onBlur={this.onPhoneBlur} | |
/> | |
</div> | |
</div> | |
<div className="field-row"> | |
<strong className="label"> | |
<label>EMAIL NOTIFICATIONS</label> | |
</strong> | |
<div className="field"> | |
{this.renderEmailList()} | |
</div> | |
</div> | |
<div className="field-row"> | |
<strong className="label"> | |
<label>STUDY AD</label> | |
</strong> | |
<div className="field"> | |
{ fileSrc && | |
preview | |
} | |
<a | |
className="btn btn-gray upload-btn" | |
onClick={this.openStudyAddModal} | |
> | |
update study ad | |
</a> | |
{/* TODO need to put an error message up so that people know to upload a file. */} | |
{/* formError | |
? <div className="has-error">{formErrors.studyAd}</div> | |
: null | |
*/} | |
</div> | |
</div> | |
<div className="clearfix"> | |
<button | |
type="submit" | |
className="btn btn-default btn-submit pull-right" | |
disabled={editedStudy.submitting} | |
> | |
{editedStudy.submitting | |
? <span><LoadingSpinner showOnlyIcon size={20} /></span> | |
: <span>Update</span> | |
} | |
</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
</Modal.Body> | |
</Modal> | |
<Modal | |
dialogComponentClass={CenteredModal} | |
show={this.state.addEmailModalShow} | |
onHide={this.closeAddEmailModal} | |
backdrop | |
keyboard | |
> | |
<Modal.Header> | |
<Modal.Title>ADD EMAIL NOTIFICATION</Modal.Title> | |
<a className="lightbox-close close" onClick={this.closeAddEmailModal}> | |
<i className="icomoon-icon_close" /> | |
</a> | |
</Modal.Header> | |
<Modal.Body> | |
<AddEmailNotificationForm onSubmit={this.addEmailNotificationSubmit} /> | |
</Modal.Body> | |
</Modal> | |
<Modal | |
className="study-add-modal avatar-modal" | |
dialogComponentClass={CenteredModal} | |
show={this.state.studyAddModalOpen} | |
onHide={this.closeStudyAddModal} | |
backdrop | |
keyboard | |
> | |
<Modal.Header> | |
<Modal.Title>UPDATE STUDY AD</Modal.Title> | |
<a className="lightbox-close close" onClick={this.closeStudyAddModal}> | |
<i className="icomoon-icon_close" /> | |
</a> | |
</Modal.Header> | |
<Modal.Body> | |
<StudyAddForm | |
handleSubmit={this.uploadStudyAdd} | |
changeStudyAddProcess={changeStudyAddProcess} | |
/> | |
</Modal.Body> | |
</Modal> | |
<Modal | |
className="study-preview-modal preview-modal" | |
dialogComponentClass={CenteredModal} | |
show={this.state.studyPreviewModalOpen} | |
onHide={this.closeStudyPreviewModal} | |
backdrop | |
keyboard | |
> | |
<Modal.Header> | |
<Modal.Title>Image Preview</Modal.Title> | |
<a className="lightbox-close close" onClick={this.closeStudyPreviewModal}> | |
<i className="icomoon-icon_close" /> | |
</a> | |
</Modal.Header> | |
<Modal.Body> | |
<div className="img-holder"> | |
<img src={fileSrc} alt="modal-preview" /> | |
</div> | |
</Modal.Body> | |
</Modal> | |
</div> | |
); | |
} | |
} | |
const mapStateToProps = createStructuredSelector({ | |
addNotificationProcess: selectAddNotificationProcess(), | |
currentUser: selectCurrentUser(), | |
formError: selectSyncErrorBool(formName), | |
clientAdmins: selectHomePageClientAdmins(), | |
formErrors: selectSyncErrors(formName), | |
formValues: selectValues(formName), | |
editedStudy: selectEditedStudy(), | |
clientSites: selectClientSites(), | |
updatedStudyAd: selectUpdatedStudyAd(), | |
changeStudyAddProcess: selectChangeStudyAddProcess(), | |
}); | |
export default connect(mapStateToProps, mapDispatchToProps)(EditStudyForm); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { connect } from 'react-redux'; | |
import { createStructuredSelector } from 'reselect'; | |
import { UserCanConnections } from 'config.routes/UserRoutePermissions'; | |
import { | |
makeSelectAccountConnections, | |
} from 'containers/Main/selectors'; | |
import AddConnectionDialog from './AddConnectionDialog'; | |
import ConnectionsControlBar from './ConnectionsControlBar'; | |
import ConnectionsList from './ConnectionsList'; | |
import { | |
setChannelFilter, | |
setChannelType, | |
setConnectionsList, | |
toggleDialog, | |
getSocialUrl, | |
removeConnection, | |
connectionCallback, | |
setSubCallback, | |
setSubChannel, | |
createSubChannels, | |
clearSubData, | |
getWordpressBlogs, | |
validateConnections, | |
} from './actions'; | |
import { | |
makeSelectChannelFilter, | |
makeSelectChannelType, | |
makeSelectDialogShown, | |
makeSelectSocialUrls, | |
makeSelectSubCallback, | |
makeSelectSubChannel, | |
makeSelectSubChannels, | |
} from './selectors'; | |
const styles = require('./styles.scss'); | |
class Connections extends React.Component { | |
constructor(props) { | |
super(props); | |
this.handleDialogToggle = this.handleDialogToggle.bind(this); | |
this.removeConnection = this.removeConnection.bind(this); | |
this.setChannelFilter = this.setChannelFilter.bind(this); | |
this.setChannelType = this.setChannelType.bind(this); | |
} | |
componentDidMount() { | |
this.props.getSocialUrl(); | |
window.addEventListener('message', receiveMessage.bind(this), false); | |
function receiveMessage(event) { | |
if (event.origin.includes('dev2')) { | |
this.props.connectionCallback(event.data); | |
} | |
} | |
this.props.validateConnections(this.props.params.account_id); | |
} | |
setChannelFilter(channelFilter) { | |
this.props.setChannelFilter(channelFilter); | |
} | |
setChannelType(channelType) { | |
this.props.setChannelType(channelType); | |
} | |
getFilteredConnections() { | |
return this.props.connections.filter((connection) => { | |
let matched = true; | |
if (this.props.channelFilter) { | |
matched = matched && (connection.display_name.toLowerCase().indexOf(this.props.channelFilter.toLowerCase()) > -1); | |
} | |
if (this.props.channelType) { | |
matched = matched && (connection.channel === this.props.channelType); | |
} | |
return matched; | |
}); | |
} | |
getChannelTypes() { | |
const types = []; | |
this.props.connections.forEach((connection) => { | |
if (types.indexOf(connection.channel) === -1) { | |
types.push(connection.channel); | |
} | |
}); | |
types.sort(); | |
return types; | |
} | |
removeConnection(connectionId) { | |
const connections = this.props.connections.slice(); | |
let connectionIndex; | |
connections.forEach((connection, index) => { | |
if (connection.connection_id === connectionId) { | |
connectionIndex = index; | |
} | |
}); | |
if (connectionIndex !== undefined) { | |
connections.splice(connectionIndex, 1); | |
} | |
this.props.removeChannel(connectionId); | |
this.props.setConnectionsListShown(connections); | |
} | |
handleDialogToggle() { | |
this.props.toggleDialogShown(!this.props.dialogShown); | |
this.props.clearSubData(); | |
} | |
render() { | |
return ( | |
<div className={styles.containerDiv} > | |
<ConnectionsControlBar | |
handleDialogToggle={this.handleDialogToggle} channels={this.getChannelTypes()} | |
setChannelFilter={this.setChannelFilter} setChannelType={this.setChannelType} | |
channelFilter={this.props.channelFilter} channelType={this.props.channelType} | |
/> | |
<ConnectionsList getSubConnections={this.getSubConnections} connections={this.getFilteredConnections()} removeConnection={this.removeConnection} /> | |
<AddConnectionDialog | |
getWordpressBlogs={this.props.getWordpressBlogs} createSubChannels={this.props.createSubChannels} | |
subChannels={this.props.subChannels} setSubChannel={this.props.setSubChannel} subChannel={this.props.subChannel} | |
setSubCallback={this.props.setSubCallback} subCallback={this.props.subCallback} handleDialogToggle={this.handleDialogToggle} | |
dialogShown={this.props.dialogShown} socialUrls={this.props.socialUrls} | |
/> | |
</div> | |
); | |
} | |
} | |
Connections.propTypes = { | |
getSocialUrl: React.PropTypes.func, | |
connectionCallback: React.PropTypes.func, | |
dialogShown: React.PropTypes.bool, | |
clearSubData: React.PropTypes.func, | |
connections: React.PropTypes.array, | |
toggleDialogShown: React.PropTypes.func, | |
removeChannel: React.PropTypes.func, | |
setConnectionsListShown: React.PropTypes.func, | |
setChannelFilter: React.PropTypes.func, | |
setChannelType: React.PropTypes.func, | |
socialUrls: React.PropTypes.object, | |
subCallback: React.PropTypes.bool, | |
setSubCallback: React.PropTypes.func, | |
subChannels: React.PropTypes.oneOfType([ | |
React.PropTypes.array, | |
React.PropTypes.object, | |
]), | |
createSubChannels: React.PropTypes.func, | |
getWordpressBlogs: React.PropTypes.func, | |
channelType: React.PropTypes.string, | |
channelFilter: React.PropTypes.string, | |
setSubChannel: React.PropTypes.func, | |
subChannel: React.PropTypes.object, | |
validateConnections: React.PropTypes.func, | |
params: React.PropTypes.shape({ | |
account_id: React.PropTypes.string, | |
}), | |
}; | |
export function mapDispatchToProps(dispatch) { | |
return { | |
setChannelFilter: (channelFilter) => dispatch(setChannelFilter(channelFilter)), | |
setChannelType: (channelType) => dispatch(setChannelType(channelType)), | |
setConnectionsListShown: (connections) => dispatch(setConnectionsList(connections)), | |
toggleDialogShown: (isShown) => dispatch(toggleDialog(isShown)), | |
getSocialUrl: () => dispatch(getSocialUrl()), | |
removeChannel: (channelId) => dispatch(removeConnection(channelId)), | |
connectionCallback: (channelObject) => dispatch(connectionCallback(channelObject)), | |
setSubCallback: (sub) => dispatch(setSubCallback(sub)), | |
setSubChannel: (subChannel) => dispatch(setSubChannel(subChannel)), | |
createSubChannels: (data) => dispatch(createSubChannels(data)), | |
clearSubData: () => dispatch(clearSubData()), | |
getWordpressBlogs: (data) => dispatch(getWordpressBlogs(data)), | |
validateConnections: (id) => dispatch(validateConnections(id)), | |
}; | |
} | |
const mapStateToProps = createStructuredSelector({ | |
channelFilter: makeSelectChannelFilter(), | |
channelType: makeSelectChannelType(), | |
connections: makeSelectAccountConnections(), | |
dialogShown: makeSelectDialogShown(), | |
socialUrls: makeSelectSocialUrls(), | |
subCallback: makeSelectSubCallback(), | |
subChannel: makeSelectSubChannel(), | |
subChannels: makeSelectSubChannels(), | |
}); | |
export default UserCanConnections(connect(mapStateToProps, mapDispatchToProps)(Connections)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { fromJS } from 'immutable'; | |
import { | |
SET_CHANNEL_FILTER, | |
SET_CHANNEL_TYPE, | |
SET_CONNECTIONS_LIST, | |
TOGGLE_ADD_CONNECTION_DIALOG, | |
SET_SOCIAL_URLS, | |
SET_SUB_CALLBACK, | |
SET_SUB_CHANNEL, | |
SET_SUB_CHANNELS, | |
CLEAR_SUB_DATA, | |
} from './constants'; | |
const initialState = fromJS({ | |
channelFilter: '', | |
channelType: '', | |
dialogShown: false, | |
connections: [], | |
socialUrls: {}, | |
subCallback: false, | |
subChannel: {}, | |
subChannels: [], | |
}); | |
function connectionsReducer(state = initialState, action) { | |
switch (action.type) { | |
case SET_CHANNEL_FILTER: | |
return state | |
.set('channelFilter', action.channelFilter); | |
case SET_CHANNEL_TYPE: | |
return state | |
.set('channelType', action.channelType); | |
case SET_CONNECTIONS_LIST: | |
return state | |
.set('connections', action.connections); | |
case SET_SOCIAL_URLS: | |
return state | |
.set('socialUrls', action.urls); | |
case TOGGLE_ADD_CONNECTION_DIALOG: | |
return state | |
.set('dialogShown', action.shown); | |
case SET_SUB_CALLBACK: | |
return state | |
.set('subCallback', action.sub); | |
case SET_SUB_CHANNEL: | |
return state | |
.set('subChannel', action.subChannel); | |
case SET_SUB_CHANNELS: | |
return state | |
.set('subChannels', action.subChannels); | |
case CLEAR_SUB_DATA: | |
return state | |
.set('subChannels', []) | |
.set('subChannel', {}) | |
.set('subCallback', false); | |
default: return state; | |
} | |
} | |
export default connectionsReducer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { takeLatest } from 'redux-saga'; | |
import { take, call, put, cancel, select } from 'redux-saga/effects'; | |
import { LOCATION_CHANGE } from 'react-router-redux'; | |
import { makeSelectCurrentAccount } from 'containers/Main/selectors'; | |
import { toastr } from 'lib/react-redux-toastr'; | |
import { SET_CONNECTIONS_LIST } from 'containers/Main/constants'; | |
import { | |
getData, | |
putData, | |
postData, | |
} from 'utils/request'; | |
import { makeSelectSubCallback } from './selectors'; | |
import { | |
FETCH_SOCIAL_URL, | |
SET_SOCIAL_URLS, | |
REMOVE_CONNECTION, | |
CONNECTION_CALLBACK, | |
TOGGLE_ADD_CONNECTION_DIALOG, | |
SET_SUB_CHANNEL, | |
SET_SUB_CHANNELS, | |
CREATE_SUB_CHANNELS, | |
CLEAR_SUB_DATA, | |
GET_WORDPRESS_BLOGS, | |
VALIDATE_CONNECTIONS, | |
VALIDATE_CONNECTIONS_SUCCESS, | |
} from './constants'; | |
export function* getSocialUrls(action, dispatch) { // eslint-disable-line no-unused-vars | |
const currentAccount = yield select(makeSelectCurrentAccount()); | |
const data = { | |
payload: { | |
account_id: currentAccount.account_id, | |
callback_function: 'postMessage', | |
}, | |
}; | |
const params = serialize(data); | |
const requestUrl = `/connection_api/social_urls?${params}`; | |
const result = yield call(getData, requestUrl); | |
if (result.data.status === 'success') { | |
const urls = result.data.urls; | |
yield put({ type: SET_SOCIAL_URLS, urls }); | |
} | |
} | |
export function* connectChannel() { | |
const watcher = yield takeLatest(FETCH_SOCIAL_URL, getSocialUrls); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export function* setRemoveChannel(action, dispatch) { // eslint-disable-line no-unused-vars | |
const connectionId = action.connectionId; | |
const data = { | |
payload: { | |
status: '4', | |
}, | |
}; | |
const requestUrl = `/connection_api/connection/${connectionId}`; | |
const response = yield call(putData, requestUrl, data); | |
if (response.data.status === 'success') { | |
toastr.success('Success!', 'Connection removed'); | |
} else { | |
toastr.fail('Error!', 'Something went wrong, please try again.'); | |
} | |
} | |
export function* createSubChannels(action, dispatch) { // eslint-disable-line no-unused-vars | |
const currentAccount = yield select(makeSelectCurrentAccount()); | |
const data = action.data; | |
let apiUrl = ''; | |
let channelId = ''; | |
if (data.channel === 'facebook') { | |
apiUrl = 'create_facebook_page'; | |
channelId = 'facebook_page_id'; | |
} else if (data.channel === 'pinterest') { | |
apiUrl = 'create_pinterest_board'; | |
channelId = 'pinterest_board_id'; | |
} else if (data.channel === 'linkedin') { | |
apiUrl = 'create_linkedin_company'; | |
channelId = 'linkedin_company_id'; | |
} else if (data.channel === 'googleplus') { | |
apiUrl = 'create_google_page'; | |
channelId = 'google_page_id'; | |
} else { | |
apiUrl = 'create_wordpress_blog'; | |
channelId = 'wordpress'; | |
} | |
const requestUrl = `/connection_api/${apiUrl}`; | |
for (let i = 0; i < data.subChannels.length; i += 1) { | |
if (data.subChannels[i].status) { | |
const apiData = { | |
payload: { | |
status: 1, | |
}, | |
}; | |
const connectionId = data.subChannels[i].connection_id; | |
const url = `/connection_api/connection/${connectionId}`; | |
yield call(putData, url, apiData); | |
} else if (channelId === 'wordpress') { | |
const apiData = { | |
payload: { | |
account_id: currentAccount.account_id, | |
...data.subChannels[i], | |
}, | |
}; | |
yield call(postData, requestUrl, apiData); | |
} else { | |
const apiData = { | |
payload: { | |
parent_connection_id: data.subChannels[i].parent_connection_id, | |
[channelId]: data.subChannels[i].connection_uid, | |
account_id: currentAccount.account_id, | |
}, | |
}; | |
yield call(postData, requestUrl, apiData); | |
} | |
} | |
const connectionData = { | |
payload: { | |
status: [1, 3, 5], | |
postable: false, | |
account_id: currentAccount.account_id, | |
}, | |
}; | |
const params = serialize(connectionData); | |
const connectionsUrl = `/connection_api/connections?${params}`; | |
const connectionsCall = yield call(getData, connectionsUrl); | |
const connections = connectionsCall.data.connections; | |
const shown = false; | |
yield put({ type: SET_CONNECTIONS_LIST, connections }); | |
yield put({ type: TOGGLE_ADD_CONNECTION_DIALOG, shown }); | |
yield put({ type: CLEAR_SUB_DATA }); | |
toastr.success('Success!', 'Connections added'); | |
} | |
export function* getSubConnections(connection) { | |
const currentAccount = yield select(makeSelectCurrentAccount()); | |
const data = { | |
payload: { | |
account_id: currentAccount.account_id, | |
connection_id: connection.connection_id, | |
}, | |
}; | |
const params = serialize(data); | |
const requestUrl = `/connection_api/sub_connections?${params}`; | |
const subConnectionsCall = yield call(getData, requestUrl); | |
if (subConnectionsCall.data.status === 'success') { | |
const subChannels = subConnectionsCall.data.sub_connections; | |
yield put({ type: SET_SUB_CHANNELS, subChannels }); | |
} | |
} | |
export function* fetchWordpressBlogs(action, dispatch) { // eslint-disable-line no-unused-vars | |
const data = action.data; | |
const requestUrl = '/connection_api/wordpress_blogs'; | |
const result = yield call(postData, requestUrl, data); | |
if (result.data.status === 'success') { | |
const subChannels = result.data.blogs; | |
yield put({ type: SET_SUB_CHANNELS, subChannels }); | |
} | |
} | |
export function* validateConnections(action) { | |
const accountId = action.id; | |
const data = { | |
payload: { | |
account_id: accountId, | |
status: [1, 3, 5], | |
postable: 1, | |
}, | |
}; | |
const params = serialize(data); | |
const result = yield call(getData, `/connection_api/connections?${params}`); | |
if (result.data.status === 'success') { | |
const connections = result.data.connections; | |
yield put({ type: VALIDATE_CONNECTIONS_SUCCESS, connections }); | |
} | |
} | |
export function* setConnectionCallback(action, dispatch) { // eslint-disable-line no-unused-vars | |
const currentAccount = yield select(makeSelectCurrentAccount()); | |
const subChannel = action.channelObject; | |
const sub = yield select(makeSelectSubCallback()); | |
if (sub) { | |
yield put({ type: SET_SUB_CHANNEL, subChannel }); | |
yield call(getSubConnections, subChannel); | |
} else { | |
const connectionData = { | |
payload: { | |
status: [1, 3, 5], | |
postable: false, | |
account_id: currentAccount.account_id, | |
}, | |
}; | |
const params = serialize(connectionData); | |
const connectionsUrl = `/connection_api/connections?${params}`; | |
const connectionsCall = yield call(getData, connectionsUrl); | |
const connections = connectionsCall.data.connections; | |
const shown = false; | |
yield put({ type: SET_CONNECTIONS_LIST, connections }); | |
yield put({ type: TOGGLE_ADD_CONNECTION_DIALOG, shown }); | |
toastr.success('Success!', 'Connection added'); | |
} | |
} | |
export function* connectChannelCallback() { | |
const watcher = yield takeLatest(CONNECTION_CALLBACK, setConnectionCallback); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export function* removeChannel() { | |
const watcher = yield takeLatest(REMOVE_CONNECTION, setRemoveChannel); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export function* watchSubChannels() { | |
const watcher = yield takeLatest(CREATE_SUB_CHANNELS, createSubChannels); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export function* watchWordpress() { | |
const watcher = yield takeLatest(GET_WORDPRESS_BLOGS, fetchWordpressBlogs); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export function* getConnections() { | |
const watcher = yield takeLatest(VALIDATE_CONNECTIONS, validateConnections); | |
yield take(LOCATION_CHANGE); | |
yield cancel(watcher); | |
} | |
export default [ | |
connectChannel, | |
removeChannel, | |
connectChannelCallback, | |
watchSubChannels, | |
watchWordpress, | |
getConnections, | |
]; | |
const serialize = function serialize(obj, prefix) { | |
const str = []; | |
let p; | |
for (p in obj) { // eslint-disable-line no-restricted-syntax | |
if (Object.prototype.hasOwnProperty.call(obj, p)) { | |
const k = prefix ? `${prefix}[${p}]` : p, v = obj[p]; // eslint-disable-line | |
str.push((v !== null && typeof v === 'object') ? | |
serialize(v, k) : | |
`${encodeURIComponent(k)}=${encodeURIComponent(v)}`); | |
} | |
} | |
return str.join('&'); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment