From e8aab36c5987b3dacefefdcdf876b7609df411a9 Mon Sep 17 00:00:00 2001 From: yoution Date: Mon, 1 Mar 2021 23:36:39 +0800 Subject: [PATCH 1/2] fix: issue #4249 --- src/actions/loadUser.js | 24 +++++++++++++++---- src/api/users.js | 17 +++++++++++++ src/config/constants.js | 5 ++++ src/reducers/loadUser.js | 5 ++++ .../system/components/ChangeEmailForm.jsx | 5 ++-- .../system/components/SystemSettingsForm.jsx | 18 ++++++++++---- .../system/components/SystemSettingsForm.scss | 14 +++++++++++ .../containers/SystemSettingsContainer.jsx | 13 ++++++++-- 8 files changed, 87 insertions(+), 14 deletions(-) diff --git a/src/actions/loadUser.js b/src/actions/loadUser.js index b70c3eb88..0669fb51a 100644 --- a/src/actions/loadUser.js +++ b/src/actions/loadUser.js @@ -3,6 +3,8 @@ import { ACCOUNTS_APP_CONNECTOR_URL, LOAD_USER_SUCCESS, LOAD_USER_FAILURE, + LOAD_USER_CREDENTIAL, + LOAD_USER_CREDENTIAL_FAILURE, LOAD_ORG_CONFIG_SUCCESS, LOAD_ORG_CONFIG_FAILURE, ROLE_ADMINISTRATOR, @@ -16,7 +18,7 @@ import { ROLE_PRESALES, ROLE_PROJECT_MANAGER, ROLE_SOLUTION_ARCHITECT } from '../config/constants' import { getFreshToken, configureConnector, decodeToken } from 'tc-auth-lib' -import { getUserProfile } from '../api/users' +import { getUserProfile, getCredential } from '../api/users' import { fetchGroups } from '../api/groups' import { getOrgConfig } from '../api/orgConfig' import { EventTypes } from 'redux-segment' @@ -26,6 +28,18 @@ configureConnector({ frameId: 'tc-accounts-iframe' }) +export function getUserCredential(userId) { + return (dispatch) => { + return dispatch({ + type: LOAD_USER_CREDENTIAL, + payload: getCredential(userId) + }).catch((err) => { + console.log(err) + dispatch({ type: LOAD_USER_CREDENTIAL_FAILURE }) + }) + } +} + export function loadUser() { return ((dispatch, getState) => { const state = getState() @@ -125,9 +139,9 @@ export function loadUserSuccess(dispatch, token) { loadGroups(dispatch, currentUser.userId) }) .catch((err) => { - // if we fail to load user's profile, still dispatch user load success - // ideally it shouldn't happen, but if it is, we can render the page - // without profile information + // if we fail to load user's profile, still dispatch user load success + // ideally it shouldn't happen, but if it is, we can render the page + // without profile information console.log(err) dispatch({ type: LOAD_USER_SUCCESS, user : currentUser }) }) @@ -161,7 +175,7 @@ function loadGroups(dispatch, userId) { } }) .catch((err) => { - // if we fail to load groups + // if we fail to load groups console.log(err) }) } diff --git a/src/api/users.js b/src/api/users.js index 68a85159a..88b025020 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -17,6 +17,23 @@ export function getUserProfile(handle) { }) } + +/** + * Gets credential for the specified user id. + * + * NOTE: Only admins are authorized to use the underlying endpoint. + * + * @param {Number} userId The user id + * @return {Promise} Resolves to the linked accounts array. + */ +export function getCredential(userId) { + return axios.get(`${TC_API_URL}/v3/users/${userId}?fields=credential`) + .then(resp => { + return _.get(resp.data, 'result.content', {}) + }) +} + + /** * Update user profile * diff --git a/src/config/constants.js b/src/config/constants.js index c62adbaa8..da025d899 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -7,6 +7,11 @@ import { getCookie } from '../helpers/cookie' export const LOAD_USER_SUCCESS = 'LOAD_USER_SUCCESS' export const LOAD_USER_FAILURE = 'LOAD_USER_FAILURE' +export const LOAD_USER_CREDENTIAL = 'LOAD_USER_CREDENTIAL' +export const LOAD_USER_CREDENTIAL_SUCCESS = 'LOAD_USER_CREDENTIAL_SUCCESS' +export const LOAD_USER_CREDENTIAL_FAILURE = 'LOAD_USER_CREDENTIAL_FAILURE' + + // Load organization configs export const LOAD_ORG_CONFIG_SUCCESS = 'LOAD_ORG_CONFIG_SUCCESS' export const LOAD_ORG_CONFIG_FAILURE = 'LOAD_ORG_CONFIG_FAILURE' diff --git a/src/reducers/loadUser.js b/src/reducers/loadUser.js index 20e1a85d4..c9e9e5f1d 100644 --- a/src/reducers/loadUser.js +++ b/src/reducers/loadUser.js @@ -2,6 +2,7 @@ import _ from 'lodash' import { LOAD_USER_SUCCESS, LOAD_USER_FAILURE, + LOAD_USER_CREDENTIAL_SUCCESS, LOAD_ORG_CONFIG_SUCCESS, LOAD_ORG_CONFIG_FAILURE, SAVE_PROFILE_PHOTO_SUCCESS, @@ -18,6 +19,10 @@ export const initialState = { export default function(state = initialState, action) { switch (action.type) { + case LOAD_USER_CREDENTIAL_SUCCESS: + return Object.assign({}, state, { + credential: action.payload.credential + }) case LOAD_USER_SUCCESS: return Object.assign({}, state, { isLoading : false, diff --git a/src/routes/settings/routes/system/components/ChangeEmailForm.jsx b/src/routes/settings/routes/system/components/ChangeEmailForm.jsx index 717881b99..1ae89495b 100644 --- a/src/routes/settings/routes/system/components/ChangeEmailForm.jsx +++ b/src/routes/settings/routes/system/components/ChangeEmailForm.jsx @@ -98,7 +98,7 @@ class ChangeEmailForm extends React.Component { } render() { - const { settings, checkingEmail, checkedEmail, isEmailAvailable, isEmailChanging, emailSubmitted} = this.props + const {disabled, settings, checkingEmail, checkedEmail, isEmailAvailable, isEmailChanging, emailSubmitted} = this.props const { currentEmail, isValid, isFocused } = this.state const currentEmailAvailable = checkedEmail === currentEmail && isEmailAvailable const isCheckingCurrentEmail = checkingEmail === currentEmail @@ -140,7 +140,7 @@ class ChangeEmailForm extends React.Component { validationErrors={{ isEmail: 'Provide a correct email' }} - disabled={isEmailChanging || !hasPermission(PERMISSIONS.UPDATE_USER_EMAIL)} + disabled={disabled ||isEmailChanging || !hasPermission(PERMISSIONS.UPDATE_USER_EMAIL)} ref={(ref) => this.emailRef = ref} /> { isFocused && isCheckingCurrentEmail && ( @@ -174,6 +174,7 @@ class ChangeEmailForm extends React.Component { ChangeEmailForm.propTypes = { email: PropTypes.string, + disabled: PropTypes.bool, onSubmit: PropTypes.func.isRequired, checkingEmail: PropTypes.string, checkedEmail: PropTypes.string, diff --git a/src/routes/settings/routes/system/components/SystemSettingsForm.jsx b/src/routes/settings/routes/system/components/SystemSettingsForm.jsx index a2c6143ee..29f8fe6e7 100644 --- a/src/routes/settings/routes/system/components/SystemSettingsForm.jsx +++ b/src/routes/settings/routes/system/components/SystemSettingsForm.jsx @@ -9,7 +9,7 @@ const TCFormFields = FormsyForm.Fields class SystemSettingsForm extends Component { render() { - const { changePassword, checkEmailAvailability, changeEmail, systemSettings, resetPassword } = this.props + const { changePassword, checkEmailAvailability, changeEmail, systemSettings, resetPassword, usingSsoService } = this.props return (
@@ -40,20 +40,28 @@ class SystemSettingsForm extends Component { checkEmailAvailability={checkEmailAvailability} onSubmit={(email) => changeEmail(email)} {...systemSettings} + disabled={usingSsoService? true: systemSettings.disabled === true} /> + + {usingSsoService ?
+ Since you joined Topcoder using your <SSO Service> account, + any email updates will need to be handled by logging in to + your <SSO Service> account. +
: null}
-
+ {!usingSsoService ? +
Retrieve or change your password -
+
: null} -
+ {!usingSsoService ?
changePassword(data)} onReset={() => resetPassword()} {...systemSettings} /> -
+
: null} ) } diff --git a/src/routes/settings/routes/system/components/SystemSettingsForm.scss b/src/routes/settings/routes/system/components/SystemSettingsForm.scss index b5d6d54a7..8221af529 100644 --- a/src/routes/settings/routes/system/components/SystemSettingsForm.scss +++ b/src/routes/settings/routes/system/components/SystemSettingsForm.scss @@ -4,6 +4,19 @@ display: flex; flex-direction: column; padding-bottom: 30px; + + .error-message { + @include roboto-medium; + + display: block; + border-radius: 5px; + background-color: $tc-red-10; + color: $tc-red-110; + font-size: 15px; + padding: 15px; + margin: 15px 40px; + line-height: 20px; + } } .username { @@ -41,6 +54,7 @@ .input-container { flex-grow: 1; + @media screen and (max-width: $screen-md - 1px) { width: 100%; } diff --git a/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx b/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx index ff57ac633..2f1a40757 100644 --- a/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx +++ b/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx @@ -7,6 +7,7 @@ import { connect } from 'react-redux' import spinnerWhileLoading from '../../../../../components/LoadingSpinner' import SettingsPanel from '../../../components/SettingsPanel' import { checkEmailAvailability, changeEmail, changePassword, getSystemSettings, resetPassword } from '../../../actions' +import { getUserCredential } from '../../../../../actions/loadUser' import { requiresAuthentication } from '../../../../../components/AuthenticatedComponent' import SystemSettingsForm from '../components/SystemSettingsForm' import './SystemSettingsContainer.scss' @@ -16,7 +17,13 @@ const FormEnhanced = enhance(SystemSettingsForm) class SystemSettingsContainer extends Component { componentDidMount() { - this.props.getSystemSettings() + const { + getSystemSettings, + getUserCredential, + user + } = this.props + getSystemSettings() + getUserCredential(user.userId) } render() { @@ -41,11 +48,13 @@ const SystemSettingsContainerWithAuth = requiresAuthentication(SystemSettingsCon const mapStateToProps = ({ settings, loadUser }) => ({ systemSettings: settings.system, - user: loadUser.user + user: loadUser.user, + usingSsoService: _.get(loadUser, 'credential.hasPassword', false) === false, }) const mapDispatchToProps = { getSystemSettings, + getUserCredential, checkEmailAvailability, changeEmail, changePassword, From 94ab43cbcf2e739bda6d1c58eb61c2e224ee5339 Mon Sep 17 00:00:00 2001 From: yoution Date: Wed, 3 Mar 2021 22:32:43 +0800 Subject: [PATCH 2/2] fix issue #4249 --- src/config/constants.js | 1 + src/reducers/loadUser.js | 11 ++++++ .../system/components/ChangeEmailForm.jsx | 6 ++-- .../system/components/SystemSettingsForm.jsx | 35 +++++++++++-------- .../containers/SystemSettingsContainer.jsx | 3 +- 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index da025d899..13ed9c6f0 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -8,6 +8,7 @@ export const LOAD_USER_SUCCESS = 'LOAD_USER_SUCCESS' export const LOAD_USER_FAILURE = 'LOAD_USER_FAILURE' export const LOAD_USER_CREDENTIAL = 'LOAD_USER_CREDENTIAL' +export const LOAD_USER_CREDENTIAL_PENDING = 'LOAD_USER_CREDENTIAL_PENDING' export const LOAD_USER_CREDENTIAL_SUCCESS = 'LOAD_USER_CREDENTIAL_SUCCESS' export const LOAD_USER_CREDENTIAL_FAILURE = 'LOAD_USER_CREDENTIAL_FAILURE' diff --git a/src/reducers/loadUser.js b/src/reducers/loadUser.js index c9e9e5f1d..376d1345a 100644 --- a/src/reducers/loadUser.js +++ b/src/reducers/loadUser.js @@ -2,7 +2,9 @@ import _ from 'lodash' import { LOAD_USER_SUCCESS, LOAD_USER_FAILURE, + LOAD_USER_CREDENTIAL_PENDING, LOAD_USER_CREDENTIAL_SUCCESS, + LOAD_USER_CREDENTIAL_FAILURE, LOAD_ORG_CONFIG_SUCCESS, LOAD_ORG_CONFIG_FAILURE, SAVE_PROFILE_PHOTO_SUCCESS, @@ -19,10 +21,19 @@ export const initialState = { export default function(state = initialState, action) { switch (action.type) { + case LOAD_USER_CREDENTIAL_PENDING: + return Object.assign({}, state, { + isLoadingCredential: true, + }) case LOAD_USER_CREDENTIAL_SUCCESS: return Object.assign({}, state, { + isLoadingCredential: false, credential: action.payload.credential }) + case LOAD_USER_CREDENTIAL_FAILURE: + return Object.assign({}, state, { + isLoadingCredential: false, + }) case LOAD_USER_SUCCESS: return Object.assign({}, state, { isLoading : false, diff --git a/src/routes/settings/routes/system/components/ChangeEmailForm.jsx b/src/routes/settings/routes/system/components/ChangeEmailForm.jsx index 1ae89495b..9025826d4 100644 --- a/src/routes/settings/routes/system/components/ChangeEmailForm.jsx +++ b/src/routes/settings/routes/system/components/ChangeEmailForm.jsx @@ -98,7 +98,7 @@ class ChangeEmailForm extends React.Component { } render() { - const {disabled, settings, checkingEmail, checkedEmail, isEmailAvailable, isEmailChanging, emailSubmitted} = this.props + const {usingSsoService, settings, checkingEmail, checkedEmail, isEmailAvailable, isEmailChanging, emailSubmitted} = this.props const { currentEmail, isValid, isFocused } = this.state const currentEmailAvailable = checkedEmail === currentEmail && isEmailAvailable const isCheckingCurrentEmail = checkingEmail === currentEmail @@ -140,7 +140,7 @@ class ChangeEmailForm extends React.Component { validationErrors={{ isEmail: 'Provide a correct email' }} - disabled={disabled ||isEmailChanging || !hasPermission(PERMISSIONS.UPDATE_USER_EMAIL)} + disabled={usingSsoService || isEmailChanging || !hasPermission(PERMISSIONS.UPDATE_USER_EMAIL)} ref={(ref) => this.emailRef = ref} /> { isFocused && isCheckingCurrentEmail && ( @@ -174,7 +174,7 @@ class ChangeEmailForm extends React.Component { ChangeEmailForm.propTypes = { email: PropTypes.string, - disabled: PropTypes.bool, + usingSsoService: PropTypes.bool, onSubmit: PropTypes.func.isRequired, checkingEmail: PropTypes.string, checkedEmail: PropTypes.string, diff --git a/src/routes/settings/routes/system/components/SystemSettingsForm.jsx b/src/routes/settings/routes/system/components/SystemSettingsForm.jsx index 29f8fe6e7..5870ee742 100644 --- a/src/routes/settings/routes/system/components/SystemSettingsForm.jsx +++ b/src/routes/settings/routes/system/components/SystemSettingsForm.jsx @@ -40,28 +40,33 @@ class SystemSettingsForm extends Component { checkEmailAvailability={checkEmailAvailability} onSubmit={(email) => changeEmail(email)} {...systemSettings} - disabled={usingSsoService? true: systemSettings.disabled === true} + usingSsoService={usingSsoService} /> - {usingSsoService ?
- Since you joined Topcoder using your <SSO Service> account, - any email updates will need to be handled by logging in to - your <SSO Service> account. -
: null} + {usingSsoService && ( +
+ Since you joined Topcoder using your <SSO Service> account, + any email updates will need to be handled by logging in to + your <SSO Service> account. +
+ )} - {!usingSsoService ? + {!usingSsoService&& (
Retrieve or change your password -
: null} + + )} - {!usingSsoService ?
- changePassword(data)} - onReset={() => resetPassword()} - {...systemSettings} - /> -
: null} + {!usingSsoService && ( +
+ changePassword(data)} + onReset={() => resetPassword()} + {...systemSettings} + /> +
+ )} ) } diff --git a/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx b/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx index 2f1a40757..201be6729 100644 --- a/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx +++ b/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx @@ -12,7 +12,7 @@ import { requiresAuthentication } from '../../../../../components/AuthenticatedC import SystemSettingsForm from '../components/SystemSettingsForm' import './SystemSettingsContainer.scss' -const enhance = spinnerWhileLoading(props => !props.systemSettings.isLoading) +const enhance = spinnerWhileLoading(props => !props.systemSettings.isLoading && !props.isLoadingCredential) const FormEnhanced = enhance(SystemSettingsForm) class SystemSettingsContainer extends Component { @@ -49,6 +49,7 @@ const SystemSettingsContainerWithAuth = requiresAuthentication(SystemSettingsCon const mapStateToProps = ({ settings, loadUser }) => ({ systemSettings: settings.system, user: loadUser.user, + isLoadingCredential: loadUser.isLoadingCredential, usingSsoService: _.get(loadUser, 'credential.hasPassword', false) === false, })