diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1187420eb..5b9dace6e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -128,7 +128,7 @@ workflows:
- build-dev
filters:
branches:
- only: ['dev']
+ only: ['dev', 'feature/add_msg_for_taas_projects']
- deployTest01:
context : org-global
diff --git a/config/constants/dev.js b/config/constants/dev.js
index d8c3e285f..5776dd80a 100644
--- a/config/constants/dev.js
+++ b/config/constants/dev.js
@@ -38,6 +38,7 @@ module.exports = {
FILE_PICKER_ACCEPT: process.env.FILE_PICKER_ACCEPT_DEV,
SALESFORCE_PROJECT_LEAD_LINK: 'https://c.cs18.visual.force.com/apex/ConnectLead?connectProjectId=',
+ SALESFORCE_BILLING_ACCOUNT_LINK: 'https://c.cs18.visual.force.com/apex/baredirect?id=',
CONNECT_SEGMENT_KEY: process.env.DEV_SEGMENT_KEY,
PREDIX_PROGRAM_ID : 3448,
diff --git a/config/constants/master.js b/config/constants/master.js
index 924643813..a594a3545 100644
--- a/config/constants/master.js
+++ b/config/constants/master.js
@@ -39,6 +39,7 @@ module.exports = {
FILE_PICKER_ACCEPT: process.env.FILE_PICKER_ACCEPT_PROD,
SALESFORCE_PROJECT_LEAD_LINK: 'https://topcoder.my.salesforce.com/apex/ConnectLead?connectProjectId=',
+ SALESFORCE_BILLING_ACCOUNT_LINK: 'https://topcoder.my.salesforce.com/apex/baredirect?id=',
CONNECT_SEGMENT_KEY: process.env.PROD_SEGMENT_KEY,
PREDIX_PROGRAM_ID : 3448,
IBM_COGNITIVE_PROGRAM_ID : 3449,
diff --git a/config/constants/qa.js b/config/constants/qa.js
index 0d94ada46..31d32cce2 100644
--- a/config/constants/qa.js
+++ b/config/constants/qa.js
@@ -38,6 +38,7 @@ module.exports = {
FILE_PICKER_ACCEPT: process.env.FILE_PICKER_ACCEPT_QA,
SALESFORCE_PROJECT_LEAD_LINK: 'https://c.cs18.visual.force.com/apex/ConnectLead?connectProjectId=',
+ SALESFORCE_BILLING_ACCOUNT_LINK: 'https://c.cs18.visual.force.com/apex/baredirect?id=',
CONNECT_SEGMENT_KEY: process.env.QA_SEGMENT_KEY,
PREDIX_PROGRAM_ID : 3448,
IBM_COGNITIVE_PROGRAM_ID : 3449,
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/components/MobileMenu/MobileMenu.jsx b/src/components/MobileMenu/MobileMenu.jsx
index 8f58407e8..199cba071 100644
--- a/src/components/MobileMenu/MobileMenu.jsx
+++ b/src/components/MobileMenu/MobileMenu.jsx
@@ -52,7 +52,7 @@ const MobileMenu = ({ user, onClose, menu }) => {
))}
-
Topcoder © 2018
+ Topcoder © {new Date().getFullYear()}
)
diff --git a/src/components/Select/Select.jsx b/src/components/Select/Select.jsx
index 92f300a7e..3c2e470ae 100644
--- a/src/components/Select/Select.jsx
+++ b/src/components/Select/Select.jsx
@@ -26,6 +26,7 @@ const Select = (props) => {
{...props}
createOptionPosition="first"
className={containerclass}
+ ref={props.createSelectRef}
classNamePrefix="react-select"
/>
)
@@ -36,6 +37,7 @@ const Select = (props) => {
createOptionPosition="first"
className={containerclass}
classNamePrefix="react-select"
+ isClearable
/>
)
}
diff --git a/src/components/TeamManagement/AutocompleteInput.jsx b/src/components/TeamManagement/AutocompleteInput.jsx
index d670dfc90..006466628 100644
--- a/src/components/TeamManagement/AutocompleteInput.jsx
+++ b/src/components/TeamManagement/AutocompleteInput.jsx
@@ -16,14 +16,18 @@ class AutocompleteInput extends React.Component {
const {
placeholder,
selectedMembers,
- disabled
+ createSelectRef,
+ disabled,
+ onBlur
} = this.props
return (
{},
disabled: false
}
@@ -50,6 +55,16 @@ AutocompleteInput.propTypes = {
*/
onUpdate: PropTypes.func,
+ /**
+ * Callback fired when input blur
+ */
+ onBlur: PropTypes.func,
+
+ /**
+ * Callback for pass select Ref to parent component
+ */
+ createSelectRef : PropTypes.func,
+
/**
* The current logged in user in the app.
* Used to determinate "You" label and access
diff --git a/src/components/TeamManagement/AutocompleteInputContainer.jsx b/src/components/TeamManagement/AutocompleteInputContainer.jsx
index 8bf201179..772f112fd 100644
--- a/src/components/TeamManagement/AutocompleteInputContainer.jsx
+++ b/src/components/TeamManagement/AutocompleteInputContainer.jsx
@@ -11,8 +11,11 @@ class AutocompleteInputContainer extends React.Component {
constructor(props) {
super(props)
this.debounceTimer = null
+ this.selectInstance = null
this.clearUserSuggestions = this.clearUserSuggestions.bind(this)
+ this.handleInputBlur = this.handleInputBlur.bind(this)
+ this.createSelectRef = this.createSelectRef.bind(this)
}
/**
@@ -66,13 +69,36 @@ class AutocompleteInputContainer extends React.Component {
this.clearUserSuggestions()
}
+ handleInputBlur(event) {
+ const {
+ selectedMembers
+ } = this.props
+ const value = event.target.value
+ const innerSelectInstance = this.selectInstance.select
+
+ if (value) {
+ const hasExist = _.find(selectedMembers, ({label}) => label === value)
+ if (!hasExist) {
+ // format new option from input value
+ const newOption = {value, label: value}
+ innerSelectInstance.select.selectOption(newOption)
+ }
+ }
+ }
+
+ createSelectRef(ref) {
+ this.selectInstance = ref
+ }
+
render() {
const { placeholder, currentUser, selectedMembers, disabled } = this.props
return (
({
- label: `${billingAccountObj.name} (${billingAccountObj.tcBillingAccountId})`,
+ label: `${billingAccountObj.name} (${billingAccountObj.tcBillingAccountId}) ${billingAccountObj.endDate ? ' - '+ moment(billingAccountObj.endDate).format('DD MMM YYYY'): ''}`,
value: billingAccountObj.tcBillingAccountId
})
@@ -72,7 +73,7 @@ class BillingAccountField extends React.Component {
handleChange(value) {
this.setState({ selectedBillingAccount: value })
- this.props.setValue(value.value)
+ this.props.setValue(value ? value.value : null)
}
render() {
@@ -89,18 +90,20 @@ class BillingAccountField extends React.Component {
value={this.state.selectedBillingAccount}
options={this.state.billingAccounts}
isDisabled={this.state.billingAccounts.length === 0}
+ showDropdownIndicator
/>
- {/* Hide this link because we haven't implemented a required page in SFDC yet */}
- {/*
+ )}
)
}
diff --git a/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx b/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx
index decf21b54..848e26938 100644
--- a/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx
+++ b/src/projects/detail/components/JobPickerRow/JobPickerRow.jsx
@@ -46,9 +46,7 @@ class JobPickerRow extends React.PureComponent {
{ value: 'designer', title: 'Designer'},
{ value: 'software-developer', title: 'Software Developer'},
{ value: 'data-scientist', title: 'Data Scientist'},
- { value: 'data-engineer', title: 'Data Engineer'},
- { value: 'qa', title: 'QA Tester'},
- { value: 'qa-engineer', title: 'QA Engineer'}
+ { value: 'data-engineer', title: 'Data Engineer'}
]
}
handleJobTitleChange(evt) {
diff --git a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss
index d4cc9163f..b597380b6 100644
--- a/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss
+++ b/src/projects/detail/components/SpecQuestionList/SpecQuestionList.scss
@@ -12,6 +12,10 @@
}
.spec-question-list-item {
+ &.question-type-jobs-picker {
+ margin-left: -12.5px;
+ margin-right: -12.5px;
+ }
padding: 0;
margin-bottom: 30px;
@@ -170,7 +174,7 @@
.dropdown-wrap.default {
background: $tc-gray-neutral-light;
border-color: $tc-gray-20;
- max-width: 300px;
+ max-width: 352px;
margin-left: 0px;
&.error {
diff --git a/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.jsx b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.jsx
index 59c6c6e9a..cfe6fba45 100644
--- a/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.jsx
+++ b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.jsx
@@ -5,8 +5,13 @@ import './TaasProjectWelcome.scss'
const TaasProjectWelcome = ({ projectId }) => {
const url = `${TAAS_APP_URL}/myteams/${projectId}`
return (
-
-
Go to your Talent Team
+
+
+
This is a Talent as a Service project. Click this button to navigate to a TaaS focused experience where you can get information specific to your TaaS team.
+
+
)
}
diff --git a/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss
index e0ed62c21..2e30473f3 100644
--- a/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss
+++ b/src/projects/detail/components/TaasProjectWelcome/TaasProjectWelcome.scss
@@ -5,11 +5,39 @@
align-items: center;
display: flex;
justify-content: center;
- height: 400px;
+ height: 100px;
:global(.tc-btn-lg) {
- height: 70px;
- line-height: 68px;
- font-size: 30px;
+ height: 50px;
+ line-height: 48px;
+ font-size: 20px;
}
+
}
+
+.text-container {
+ @include stand-alone-item-shadow;
+ background-color: $tc-white;
+ border-radius: $card-border-radius;
+ margin-top: 4 * $base-unit;
+ padding: 5 * $base-unit;
+
+ @media screen and (max-width: $screen-md - 1px) {
+ border-radius: 0;
+ }
+
+ p {
+ @include roboto;
+ font-size: 15px;
+ margin-top: 3 * $base-unit;
+ line-height: 25px;
+
+ b {
+ font-weight: bold;
+ }
+
+ i {
+ font-style: italic;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/reducers/loadUser.js b/src/reducers/loadUser.js
index 20e1a85d4..376d1345a 100644
--- a/src/reducers/loadUser.js
+++ b/src/reducers/loadUser.js
@@ -2,6 +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,
@@ -18,6 +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 717881b99..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 { 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={isEmailChanging || !hasPermission(PERMISSIONS.UPDATE_USER_EMAIL)}
+ disabled={usingSsoService || 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,
+ 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 a2c6143ee..5870ee742 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,33 @@ class SystemSettingsForm extends Component {
checkEmailAvailability={checkEmailAvailability}
onSubmit={(email) => changeEmail(email)}
{...systemSettings}
+ 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.
+
+ )}
-
+ {!usingSsoService&& (
+
Retrieve or change your password
-
+
+ )}
-
- changePassword(data)}
- onReset={() => resetPassword()}
- {...systemSettings}
- />
-
+ {!usingSsoService && (
+
+ changePassword(data)}
+ onReset={() => resetPassword()}
+ {...systemSettings}
+ />
+
+ )}
)
}
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..201be6729 100644
--- a/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx
+++ b/src/routes/settings/routes/system/containers/SystemSettingsContainer.jsx
@@ -7,16 +7,23 @@ 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'
-const enhance = spinnerWhileLoading(props => !props.systemSettings.isLoading)
+const enhance = spinnerWhileLoading(props => !props.systemSettings.isLoading && !props.isLoadingCredential)
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,14 @@ const SystemSettingsContainerWithAuth = requiresAuthentication(SystemSettingsCon
const mapStateToProps = ({ settings, loadUser }) => ({
systemSettings: settings.system,
- user: loadUser.user
+ user: loadUser.user,
+ isLoadingCredential: loadUser.isLoadingCredential,
+ usingSsoService: _.get(loadUser, 'credential.hasPassword', false) === false,
})
const mapDispatchToProps = {
getSystemSettings,
+ getUserCredential,
checkEmailAvailability,
changeEmail,
changePassword,