Fix auth disconnection add error states (#30)
* fix: properly disconnect auth * fix: add error handling and states
This commit is contained in:
@@ -11,6 +11,7 @@ export const OPEN_FORM = 'OPEN_FORM';
|
||||
export const CLOSE_FORM = 'CLOSE_FORM';
|
||||
export const UPDATE_DRAFT = 'UPDATE_DRAFT';
|
||||
export const RESET_DRAFTS = 'RESET_DRAFTS';
|
||||
export const DISCONNECT_AUTH = new AsyncActionType('ACCOUNT_SETTINGS', 'DISCONNECT_AUTH');
|
||||
|
||||
|
||||
// FETCH SETTINGS ACTIONS
|
||||
@@ -135,3 +136,21 @@ export const fetchTimeZonesSuccess = timeZones => ({
|
||||
payload: { timeZones },
|
||||
});
|
||||
|
||||
|
||||
// DISCONNECT AUTH ACTIONS
|
||||
|
||||
export const disconnectAuth = (url, providerId) => ({
|
||||
type: DISCONNECT_AUTH.BASE, payload: { url, providerId },
|
||||
});
|
||||
export const disconnectAuthBegin = () => ({
|
||||
type: DISCONNECT_AUTH.BEGIN,
|
||||
});
|
||||
export const disconnectAuthSuccess = () => ({
|
||||
type: DISCONNECT_AUTH.SUCCESS,
|
||||
});
|
||||
export const disconnectAuthFailure = providerId => ({
|
||||
type: DISCONNECT_AUTH.FAILURE, payload: { providerId },
|
||||
});
|
||||
export const disconnectAuthReset = () => ({
|
||||
type: DISCONNECT_AUTH.RESET,
|
||||
});
|
||||
|
||||
@@ -2,12 +2,22 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { Hyperlink, StatefulButton } from '@edx/paragon';
|
||||
import Alert from './Alert';
|
||||
|
||||
import { disconnectAuth } from '../actions';
|
||||
import { thirdPartyAuthSelector } from '../selectors';
|
||||
|
||||
class ThirdPartyAuth extends React.Component {
|
||||
renderConnectedProvider(url, name) {
|
||||
onClickDisconnect = (e) => {
|
||||
e.preventDefault();
|
||||
if (this.props.disconnectingState === 'pending') return;
|
||||
const providerId = e.currentTarget.getAttribute('data-provider-id');
|
||||
const disconnectUrl = e.currentTarget.getAttribute('data-disconnect-url');
|
||||
this.props.disconnectAuth(disconnectUrl, providerId);
|
||||
}
|
||||
|
||||
renderUnconnectedProvider(url, name) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h6>{name}</h6>
|
||||
@@ -23,7 +33,9 @@ class ThirdPartyAuth extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderUnconnectedProvider(url, name) {
|
||||
renderConnectedProvider(url, name, id) {
|
||||
const hasError = this.props.disconnectErrors[id];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h6>
|
||||
@@ -36,14 +48,34 @@ class ThirdPartyAuth extends React.Component {
|
||||
/>
|
||||
</span>
|
||||
</h6>
|
||||
<Hyperlink destination={url}>
|
||||
<FormattedMessage
|
||||
id="account.settings.sso.unlink.account"
|
||||
defaultMessage="Unlink {name} account"
|
||||
description="An action link to unlink a connected third party account"
|
||||
values={{ name }}
|
||||
/>
|
||||
</Hyperlink>
|
||||
{hasError ? (
|
||||
<Alert className="alert-danger">
|
||||
<FormattedMessage
|
||||
id="account.settings.sso.account.disconnect.error"
|
||||
defaultMessage="There was a problem disconnecting this account. Contact support if the problem persists."
|
||||
description="A message displayed when an error occurred while disconnecting a third party account"
|
||||
/>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<StatefulButton
|
||||
className="btn-link"
|
||||
state={this.props.disconnectingState}
|
||||
labels={{
|
||||
default: (
|
||||
<FormattedMessage
|
||||
id="account.settings.sso.unlink.account"
|
||||
defaultMessage="Unlink {name} account"
|
||||
description="An action link to unlink a connected third party account"
|
||||
values={{ name }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
onClick={this.onClickDisconnect}
|
||||
disabledStates={[]}
|
||||
data-disconnect-url={url}
|
||||
data-provider-id={id}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -55,8 +87,8 @@ class ThirdPartyAuth extends React.Component {
|
||||
<div className="form-group" key={id}>
|
||||
{
|
||||
connected ?
|
||||
this.renderUnconnectedProvider(disconnectUrl, name) :
|
||||
this.renderConnectedProvider(connectUrl, name)
|
||||
this.renderConnectedProvider(disconnectUrl, name, id) :
|
||||
this.renderUnconnectedProvider(connectUrl, name)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@@ -92,11 +124,18 @@ ThirdPartyAuth.propTypes = {
|
||||
connected: PropTypes.bool,
|
||||
id: PropTypes.string,
|
||||
})),
|
||||
disconnectingState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
|
||||
disconnectErrors: PropTypes.objectOf(PropTypes.bool),
|
||||
disconnectAuth: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ThirdPartyAuth.defaultProps = {
|
||||
providers: undefined,
|
||||
disconnectingState: null,
|
||||
disconnectErrors: {},
|
||||
};
|
||||
|
||||
|
||||
export default connect(thirdPartyAuthSelector)(ThirdPartyAuth);
|
||||
export default connect(thirdPartyAuthSelector, {
|
||||
disconnectAuth,
|
||||
})(ThirdPartyAuth);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
FETCH_TIME_ZONES,
|
||||
SAVE_PREVIOUS_SITE_LANGUAGE,
|
||||
RESET_PASSWORD,
|
||||
DISCONNECT_AUTH,
|
||||
UPDATE_DRAFT,
|
||||
RESET_DRAFTS,
|
||||
} from './actions';
|
||||
@@ -23,6 +24,8 @@ export const defaultState = {
|
||||
resetPasswordState: null,
|
||||
timeZones: [],
|
||||
countryTimeZones: [],
|
||||
disconnectingState: null,
|
||||
disconnectErrors: {},
|
||||
previousSiteLanguage: null,
|
||||
};
|
||||
|
||||
@@ -151,6 +154,32 @@ const accountSettingsReducer = (state = defaultState, action) => {
|
||||
countryTimeZones: action.payload.timeZones,
|
||||
};
|
||||
|
||||
|
||||
case DISCONNECT_AUTH.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
disconnectingState: 'pending',
|
||||
};
|
||||
case DISCONNECT_AUTH.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
disconnectingState: 'complete',
|
||||
};
|
||||
case DISCONNECT_AUTH.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
disconnectingState: 'error',
|
||||
disconnectErrors: {
|
||||
[action.payload.providerId]: true,
|
||||
},
|
||||
};
|
||||
case DISCONNECT_AUTH.RESET:
|
||||
return {
|
||||
...state,
|
||||
disconnectingState: null,
|
||||
disconnectErrors: {},
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ import {
|
||||
FETCH_TIME_ZONES,
|
||||
fetchTimeZones,
|
||||
fetchTimeZonesSuccess,
|
||||
DISCONNECT_AUTH,
|
||||
disconnectAuthBegin,
|
||||
disconnectAuthSuccess,
|
||||
disconnectAuthFailure,
|
||||
disconnectAuthReset,
|
||||
} from './actions';
|
||||
import { usernameSelector, userRolesSelector, siteLanguageSelector } from './selectors';
|
||||
|
||||
@@ -114,9 +119,23 @@ export function* handleFetchTimeZones(action) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* handleDisconnectAuth(action) {
|
||||
try {
|
||||
yield put(disconnectAuthReset());
|
||||
yield put(disconnectAuthBegin());
|
||||
const response = yield call(ApiService.postDisconnectAuth, action.payload.url);
|
||||
yield put(disconnectAuthSuccess(response));
|
||||
} catch (e) {
|
||||
logAPIErrorResponse(e);
|
||||
yield put(disconnectAuthFailure(action.payload.providerId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(FETCH_SETTINGS.BASE, handleFetchSettings);
|
||||
yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings);
|
||||
yield takeEvery(RESET_PASSWORD.BASE, handleResetPassword);
|
||||
yield takeEvery(FETCH_TIME_ZONES.BASE, handleFetchTimeZones);
|
||||
yield takeEvery(DISCONNECT_AUTH.BASE, handleDisconnectAuth);
|
||||
}
|
||||
|
||||
@@ -75,9 +75,8 @@ export const thirdPartyAuthSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => ({
|
||||
providers: accountSettings.authProviders,
|
||||
loading: accountSettings.thirdPartyAuthLoading,
|
||||
loaded: accountSettings.thirdPartyAuthLoaded,
|
||||
loadingError: accountSettings.thirdPartyAuthLoadingError,
|
||||
disconnectErrors: accountSettings.disconnectErrors,
|
||||
disconnectingState: accountSettings.disconnectingState,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -219,3 +219,8 @@ export async function postResetPassword(email) {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function postDisconnectAuth(url) {
|
||||
const { data } = await apiClient.post(url).catch(handleRequestError);
|
||||
return data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user