diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx
index e69c08c..fe2d42d 100644
--- a/src/account-settings/AccountSettingsPage.jsx
+++ b/src/account-settings/AccountSettingsPage.jsx
@@ -11,12 +11,13 @@ import {
import messages from './AccountSettingsPage.messages';
-import { fetchAccount } from './actions';
+import { fetchAccount, fetchThirdPartyAuthProviders } from './actions';
import { accountSettingsSelector } from './selectors';
import { PageLoading } from '../common';
import EditableField from './components/EditableField';
import PasswordReset from './components/PasswordReset';
+import ThirdPartyAuth from './components/ThirdPartyAuth';
import {
YEAR_OF_BIRTH_OPTIONS,
EDUCATION_LEVELS,
@@ -43,6 +44,7 @@ class AccountSettingsPage extends React.Component {
componentDidMount() {
this.props.fetchAccount();
+ this.props.fetchThirdPartyAuthProviders();
}
renderContent() {
@@ -103,6 +105,7 @@ class AccountSettingsPage extends React.Component {
reverseTransform={v => ([{ code: v }])}
label={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies'])}
/>
+
@@ -151,6 +154,7 @@ AccountSettingsPage.propTypes = {
loaded: PropTypes.bool,
loadingError: PropTypes.string,
fetchAccount: PropTypes.func.isRequired,
+ fetchThirdPartyAuthProviders: PropTypes.func.isRequired,
};
AccountSettingsPage.defaultProps = {
@@ -162,4 +166,5 @@ AccountSettingsPage.defaultProps = {
export default connect(accountSettingsSelector, {
fetchAccount,
+ fetchThirdPartyAuthProviders,
})(injectIntl(AccountSettingsPage));
diff --git a/src/account-settings/actions.js b/src/account-settings/actions.js
index ad36e45..e674b22 100644
--- a/src/account-settings/actions.js
+++ b/src/account-settings/actions.js
@@ -4,6 +4,7 @@ const { AsyncActionType } = utils;
export const FETCH_ACCOUNT = new AsyncActionType('ACCOUNT_SETTINGS', 'FETCH_ACCOUNT');
export const SAVE_ACCOUNT = new AsyncActionType('ACCOUNT_SETTINGS', 'SAVE_ACCOUNT');
+export const FETCH_THIRD_PARTY_AUTH_PROVIDERS = new AsyncActionType('ACCOUNT_SETTINGS', 'FETCH_THIRD_PARTY_AUTH_PROVIDERS');
export const RESET_PASSWORD = new AsyncActionType('ACCOUNT_SETTINGS', 'RESET_PASSWORD');
export const OPEN_FORM = 'OPEN_FORM';
export const CLOSE_FORM = 'CLOSE_FORM';
@@ -103,3 +104,19 @@ export const resetPasswordReset = () => ({
type: RESET_PASSWORD.RESET,
});
+
+// fetch third party auth providers
+
+export const fetchThirdPartyAuthProviders = () => ({
+ type: FETCH_THIRD_PARTY_AUTH_PROVIDERS.BASE,
+});
+export const fetchThirdPartyAuthProvidersBegin = () => ({
+ type: FETCH_THIRD_PARTY_AUTH_PROVIDERS.BEGIN,
+});
+export const fetchThirdPartyAuthProvidersSuccess = providers => ({
+ type: FETCH_THIRD_PARTY_AUTH_PROVIDERS.SUCCESS, payload: { providers },
+});
+export const fetchThirdPartyAuthProvidersFailure = error => ({
+ type: FETCH_THIRD_PARTY_AUTH_PROVIDERS.FAILURE, payload: { error },
+});
+
diff --git a/src/account-settings/components/ThirdPartyAuth.jsx b/src/account-settings/components/ThirdPartyAuth.jsx
new file mode 100644
index 0000000..50da282
--- /dev/null
+++ b/src/account-settings/components/ThirdPartyAuth.jsx
@@ -0,0 +1,151 @@
+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 { fetchThirdPartyAuthProviders } from '../actions';
+import { thirdPartyAuthSelector } from '../selectors';
+
+class ThirdPartyAuth extends React.Component {
+ componentDidMount() {
+ this.props.fetchThirdPartyAuthProviders();
+ }
+
+ renderConnectedProvider(url, name) {
+ return (
+
+ {name}
+
+
+
+
+ );
+ }
+
+ renderUnconnectedProvider(url, name) {
+ return (
+
+
+ {name}
+
+
+
+
+
+
+
+
+ );
+ }
+
+ renderProvider({
+ name, disconnectUrl, connectUrl, connected, id,
+ }) {
+ return (
+
+ {
+ connected ?
+ this.renderUnconnectedProvider(disconnectUrl, name) :
+ this.renderConnectedProvider(connectUrl, name)
+ }
+
+ );
+ }
+
+ renderNoProviders() {
+ return (
+
+ );
+ }
+
+ renderLoading() {
+ return (
+
+ );
+ }
+
+ renderLoadingError() {
+ return (
+
+ );
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
+
+ {this.props.providers.map(this.renderProvider, this)}
+ {this.props.loaded && this.props.providers.length === 0 ? this.renderNoProviders() : null}
+ {this.props.loading ? this.renderLoading() : null}
+ {this.props.loadingError ? this.renderLoadingError() : null}
+
+ );
+ }
+}
+
+
+ThirdPartyAuth.propTypes = {
+ fetchThirdPartyAuthProviders: PropTypes.func.isRequired,
+ providers: PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string,
+ disconnectUrl: PropTypes.string,
+ connectUrl: PropTypes.string,
+ connected: PropTypes.bool,
+ id: PropTypes.string,
+ })),
+ loading: PropTypes.bool,
+ loaded: PropTypes.bool,
+ loadingError: PropTypes.string,
+};
+
+ThirdPartyAuth.defaultProps = {
+ providers: [],
+ loading: false,
+ loaded: false,
+ loadingError: undefined,
+};
+
+
+export default connect(thirdPartyAuthSelector, {
+ fetchThirdPartyAuthProviders,
+})(ThirdPartyAuth);
diff --git a/src/account-settings/reducers.js b/src/account-settings/reducers.js
index cebd7d1..93c19e6 100644
--- a/src/account-settings/reducers.js
+++ b/src/account-settings/reducers.js
@@ -6,6 +6,7 @@ import {
RESET_PASSWORD,
UPDATE_DRAFT,
RESET_DRAFTS,
+ FETCH_THIRD_PARTY_AUTH_PROVIDERS,
} from './actions';
export const defaultState = {
@@ -130,6 +131,36 @@ const accountSettingsReducer = (state = defaultState, action) => {
resetPasswordState: 'complete',
};
+ case FETCH_THIRD_PARTY_AUTH_PROVIDERS.BEGIN:
+ return {
+ ...state,
+ thirdPartyAuthLoading: true,
+ thirdPartyAuthLoaded: false,
+ thirdPartyAuthLoadingError: null,
+ };
+ case FETCH_THIRD_PARTY_AUTH_PROVIDERS.SUCCESS:
+ return {
+ ...state,
+ authProviders: action.payload.providers,
+ thirdPartyAuthLoading: false,
+ thirdPartyAuthLoaded: true,
+ thirdPartyAuthLoadingError: null,
+ };
+ case FETCH_THIRD_PARTY_AUTH_PROVIDERS.FAILURE:
+ return {
+ ...state,
+ thirdPartyAuthLoading: false,
+ thirdPartyAuthLoaded: false,
+ thirdPartyAuthLoadingError: action.payload.error,
+ };
+ case FETCH_THIRD_PARTY_AUTH_PROVIDERS.RESET:
+ return {
+ ...state,
+ thirdPartyAuthLoading: false,
+ thirdPartyAuthLoaded: false,
+ thirdPartyAuthLoadingError: null,
+ };
+
default:
return state;
}
diff --git a/src/account-settings/sagas.js b/src/account-settings/sagas.js
index 2143a8b..d03c964 100644
--- a/src/account-settings/sagas.js
+++ b/src/account-settings/sagas.js
@@ -17,6 +17,10 @@ import {
RESET_PASSWORD,
resetPasswordBegin,
resetPasswordSuccess,
+ FETCH_THIRD_PARTY_AUTH_PROVIDERS,
+ fetchThirdPartyAuthProvidersBegin,
+ fetchThirdPartyAuthProvidersSuccess,
+ fetchThirdPartyAuthProvidersFailure,
} from './actions';
import { usernameSelector } from './selectors';
@@ -68,8 +72,21 @@ export function* handleResetPassword() {
}
}
+export function* handleFetchThirdPartyAuthProviders() {
+ try {
+ yield put(fetchThirdPartyAuthProvidersBegin());
+ const authProviders = yield call(ApiService.getThirdPartyAuthProviders);
+ yield put(fetchThirdPartyAuthProvidersSuccess(authProviders));
+ } catch (e) {
+ LoggingService.logAPIErrorResponse(e);
+ yield put(fetchThirdPartyAuthProvidersFailure(e.message));
+ yield put(push('/error'));
+ }
+}
+
export default function* saga() {
yield takeEvery(FETCH_ACCOUNT.BASE, handleFetchAccount);
yield takeEvery(SAVE_ACCOUNT.BASE, handleSaveAccount);
yield takeEvery(RESET_PASSWORD.BASE, handleResetPassword);
+ yield takeEvery(FETCH_THIRD_PARTY_AUTH_PROVIDERS.BASE, handleFetchThirdPartyAuthProviders);
}
diff --git a/src/account-settings/selectors.js b/src/account-settings/selectors.js
index fe66c64..0c4e6a0 100644
--- a/src/account-settings/selectors.js
+++ b/src/account-settings/selectors.js
@@ -63,3 +63,13 @@ export const resetPasswordSelector = createSelector(
email: accountSettings.values.email,
}),
);
+
+export const thirdPartyAuthSelector = createSelector(
+ accountSettingsSelector,
+ accountSettings => ({
+ providers: accountSettings.authProviders,
+ loading: accountSettings.thirdPartyAuthLoading,
+ loaded: accountSettings.thirdPartyAuthLoaded,
+ loadingError: accountSettings.thirdPartyAuthLoadingError,
+ }),
+);
diff --git a/src/account-settings/service.js b/src/account-settings/service.js
index 9c70299..2b5a8f7 100644
--- a/src/account-settings/service.js
+++ b/src/account-settings/service.js
@@ -64,3 +64,15 @@ export async function postResetPassword() {
return data;
}
+
+
+export async function getThirdPartyAuthProviders() {
+ const { data } = await apiClient.get(`${config.LMS_BASE_URL}/api/third_party_auth/v0/providers/user_status`)
+ .catch(handleRequestError);
+
+ return data.map(({ connect_url: connectUrl, disconnect_url: disconnectUrl, ...provider }) => ({
+ ...provider,
+ connectUrl: `${config.LMS_BASE_URL}${connectUrl}`,
+ disconnectUrl: `${config.LMS_BASE_URL}${disconnectUrl}`,
+ }));
+}