Skip to content

Instantly share code, notes, and snippets.

@angelo510
Last active August 28, 2017 18:57
Show Gist options
  • Save angelo510/a69caf5a958506eadd335e253d8e9f55 to your computer and use it in GitHub Desktop.
Save angelo510/a69caf5a958506eadd335e253d8e9f55 to your computer and use it in GitHub Desktop.
React+Redux+Saga Code Example
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 };
}
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);
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));
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;
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