diff --git a/frontend/src/Components/Form/OAuthInputConnector.js b/frontend/src/Components/Form/OAuthInputConnector.js index d51fa0124..7568aae7a 100644 --- a/frontend/src/Components/Form/OAuthInputConnector.js +++ b/frontend/src/Components/Form/OAuthInputConnector.js @@ -48,11 +48,18 @@ class OAuthInputConnector extends Component { onPress = () => { const { + name, provider, - providerData + providerData, + section } = this.props; - this.props.startOAuth({ provider, providerData }); + this.props.startOAuth({ + name, + provider, + providerData, + section + }); } // @@ -69,9 +76,11 @@ class OAuthInputConnector extends Component { } OAuthInputConnector.propTypes = { + name: PropTypes.string.isRequired, result: PropTypes.object, provider: PropTypes.string.isRequired, providerData: PropTypes.object.isRequired, + section: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, startOAuth: PropTypes.func.isRequired, resetOAuth: PropTypes.func.isRequired diff --git a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js index fa8742503..0c1ca6566 100644 --- a/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js +++ b/frontend/src/Settings/Notifications/Notifications/EditNotificationModalContent.js @@ -184,6 +184,7 @@ function EditNotificationModalContent(props) { advancedSettings={advancedSettings} provider="notification" providerData={item} + section="settings.notifications" {...field} onChange={onFieldChange} /> diff --git a/frontend/src/Store/Actions/oAuthActions.js b/frontend/src/Store/Actions/oAuthActions.js index 8a8d2c596..d119190da 100644 --- a/frontend/src/Store/Actions/oAuthActions.js +++ b/frontend/src/Store/Actions/oAuthActions.js @@ -1,9 +1,11 @@ import $ from 'jquery'; import { createAction } from 'redux-actions'; +import { batchActions } from 'redux-batched-actions'; import requestAction from 'Utilities/requestAction'; import getSectionState from 'Utilities/State/getSectionState'; import updateSectionState from 'Utilities/State/updateSectionState'; import { createThunk, handleThunks } from 'Store/thunks'; +import { set } from 'Store/Actions/baseActions'; import createHandleActions from './Creators/createHandleActions'; // @@ -38,11 +40,31 @@ export const resetOAuth = createAction(RESET_OAUTH); // // Helpers -function showOAuthWindow(url) { +function showOAuthWindow(url, payload) { const deferred = $.Deferred(); const selfWindow = window; - window.open(url); + const newWindow = window.open(url); + + if ( + !newWindow || + newWindow.closed || + typeof newWindow.closed == 'undefined' + ) { + + // A fake validation error to mimic a 400 response from the API. + const error = { + status: 400, + responseJSON: [ + { + propertyName: payload.name, + errorMessage: 'Pop-ups are being blocked by your browser' + } + ] + }; + + return deferred.reject(error).promise(); + } selfWindow.onCompleteOauth = function(query, onComplete) { delete selfWindow.onCompleteOauth; @@ -84,10 +106,16 @@ function executeIntermediateRequest(payload, ajaxOptions) { export const actionHandlers = handleThunks({ [START_OAUTH]: function(getState, payload, dispatch) { + const { + name, + section: actionSection, + ...otherPayload + } = payload; + const actionPayload = { action: 'startOAuth', queryParams: { callbackUrl }, - ...payload + ...otherPayload }; dispatch(setOAuthValue({ @@ -101,13 +129,13 @@ export const actionHandlers = handleThunks({ startResponse = response; if (response.oauthUrl) { - return showOAuthWindow(response.oauthUrl); + return showOAuthWindow(response.oauthUrl, payload); } - return executeIntermediateRequest(payload, response).then((intermediateResponse) => { + return executeIntermediateRequest(otherPayload, response).then((intermediateResponse) => { startResponse = intermediateResponse; - return showOAuthWindow(intermediateResponse.oauthUrl); + return showOAuthWindow(intermediateResponse.oauthUrl, payload); }); }) .then((queryParams) => { @@ -117,7 +145,7 @@ export const actionHandlers = handleThunks({ ...startResponse, ...queryParams }, - ...payload + ...otherPayload }); }) .then((response) => { @@ -128,13 +156,33 @@ export const actionHandlers = handleThunks({ })); }); - promise.fail((xhr) => { - dispatch(setOAuthValue({ - authorizing: false, - result: null, - error: xhr + promise.done(() => { + // Clear any previously set save error. + dispatch(set({ + section: actionSection, + saveError: null })); }); + + promise.fail((xhr) => { + const actions = [ + setOAuthValue({ + authorizing: false, + result: null, + error: xhr + }) + ]; + + if (xhr.status === 400) { + // Set a save error so the UI can display validation errors to the user. + actions.splice(0, 0, set({ + section: actionSection, + saveError: xhr + })); + } + + dispatch(batchActions(actions)); + }); } });