diff --git a/config/constants/dev.js b/config/constants/dev.js
index edb4438d6..0ab9baa0a 100644
--- a/config/constants/dev.js
+++ b/config/constants/dev.js
@@ -49,4 +49,5 @@ module.exports = {
TC_SYSTEM_USERID: process.env.DEV_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.DEV_MAINTENANCE_MODE,
+ TC_CDN_URL: process.env.TC_CDN_URL || 'https://d1aahxkjiobka8.cloudfront.net'
}
diff --git a/config/constants/master.js b/config/constants/master.js
index 6370c4827..e0aa0b448 100644
--- a/config/constants/master.js
+++ b/config/constants/master.js
@@ -49,4 +49,5 @@ module.exports = {
TC_SYSTEM_USERID: process.env.PROD_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.PROD_MAINTENANCE_MODE,
+ TC_CDN_URL: process.env.TC_CDN_URL || 'https://dlxczxztayxv6.cloudfront.net'
}
diff --git a/config/constants/qa.js b/config/constants/qa.js
index 44de22095..e9a131a3b 100644
--- a/config/constants/qa.js
+++ b/config/constants/qa.js
@@ -15,7 +15,7 @@ module.exports = {
ENV : 'QA',
PROJECTS_API_URL : 'http://api.topcoder-dev.com',
-
+
NEW_RELIC_APPLICATION_ID: process.env.TRAVIS_BRANCH ? '11199233' : '',
ARENA_URL : '//arena.topcoder-qa.com',
@@ -47,4 +47,6 @@ module.exports = {
CONNECT_MESSAGE_API_URL: 'https://api.topcoder-qa.com/v5',
TC_SYSTEM_USERID: process.env.QA_TC_SYSTEM_USERID,
MAINTENANCE_MODE: process.env.QA_MAINTENANCE_MODE,
+
+ TC_CDN_URL: process.env.TC_CDN_URL || 'https://d1aahxkjiobka8.cloudfront.net'
}
diff --git a/src/api/projectMembers.js b/src/api/projectMembers.js
index 6e6bdbfa3..90501f311 100644
--- a/src/api/projectMembers.js
+++ b/src/api/projectMembers.js
@@ -49,7 +49,7 @@ export function addProjectMember(projectId, newMember) {
export function updateProjectMember(projectId, memberId, updatedProps) {
- const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone'
+ const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email'
const url = `${PROJECTS_API_URL}/v5/projects/${projectId}/members/${memberId}/?fields=`
+ encodeURIComponent(fields)
return axios.patch(url, updatedProps)
@@ -68,7 +68,7 @@ export function removeProjectMember(projectId, memberId) {
}
export function getProjectMembers(projectId) {
- const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone'
+ const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email'
const url = `${PROJECTS_API_URL}/v5/projects/${projectId}/members/?fields=`
+ encodeURIComponent(fields)
return axios.get(url)
@@ -78,7 +78,7 @@ export function getProjectMembers(projectId) {
}
export function getProjectMember(projectId, memberId) {
- const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone'
+ const fields = 'id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email'
const url = `${PROJECTS_API_URL}/v5/projects/${projectId}/members/${memberId}?fields=`
+ encodeURIComponent(fields)
return axios.get(url)
diff --git a/src/components/PositiveNumberInput/PositiveNumberInput.jsx b/src/components/PositiveNumberInput/PositiveNumberInput.jsx
index 7f01cf7ff..b042858c4 100644
--- a/src/components/PositiveNumberInput/PositiveNumberInput.jsx
+++ b/src/components/PositiveNumberInput/PositiveNumberInput.jsx
@@ -7,10 +7,12 @@ class PositiveNumberInput extends React.PureComponent {
super(props)
this.isInputValid = true
+ this.previousValue = props.value || ''
this.onKeyDown = this.onKeyDown.bind(this)
this.onPaste = this.onPaste.bind(this)
this.onKeyUp = this.onKeyUp.bind(this)
+ this.onChange = this.onChange.bind(this)
}
onKeyDown(evt) {
@@ -44,9 +46,44 @@ class PositiveNumberInput extends React.PureComponent {
this.props.onKeyUp(evt)
}
+ onChange(evt) {
+ const { onChange } = this.props
+
+ this.enforceInputBelowMax(evt)
+ onChange(evt)
+ }
+
+ /**
+ * Makes sure the input value is kept below the max value
+ * @param {Event} evt The change event
+ */
+ enforceInputBelowMax(evt) {
+ const value = evt.target.value
+ if (this.isBelowMaxLimit(value)) {
+ this.previousValue = value
+ } else {
+ evt.target.value = this.previousValue
+ }
+ }
+
+ isBelowMaxLimit(text) {
+ const { max = Infinity } = this.props
+ return Number(text) <= max
+ }
+
render() {
const props = omit(this.props, ['onValidityChange'])
- return
+ return (
+
+ )
}
}
@@ -54,16 +91,17 @@ PositiveNumberInput.defaultProps = {
onKeyDown: noop,
onPaste: noop,
onKeyUp: noop,
- onValidityChange: noop
-
+ onValidityChange: noop,
+ onChange: noop,
}
PositiveNumberInput.propTypes = {
+ max: PT.number,
onKeyDown: PT.func,
onPaste: PT.func,
onKeyUp: PT.func,
- onValidityChange: PT.func
+ onValidityChange: PT.func,
+ onChange: PT.func,
}
-
export default PositiveNumberInput
diff --git a/src/components/TeamManagement/Dialog.js b/src/components/TeamManagement/Dialog.js
index 9f59364d0..809da1c1f 100644
--- a/src/components/TeamManagement/Dialog.js
+++ b/src/components/TeamManagement/Dialog.js
@@ -63,9 +63,8 @@ class Dialog extends React.Component {
isOpen
className="management-dialog"
overlayClassName="management-dialog-overlay"
- onRequestClose={onCancel}
- shouldCloseOnOverlayClick={!isLoading}
- shouldCloseOnEsc={!isLoading}
+ shouldCloseOnOverlayClick={false}
+ shouldCloseOnEsc={false}
contentLabel=""
>
diff --git a/src/components/User/UserTooltip.jsx b/src/components/User/UserTooltip.jsx
index 7030feb20..28696d139 100644
--- a/src/components/User/UserTooltip.jsx
+++ b/src/components/User/UserTooltip.jsx
@@ -1,6 +1,7 @@
import _ from 'lodash'
import React from 'react'
import PropTypes from 'prop-types'
+import cn from 'classnames'
import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip'
import Avatar from 'appirio-tech-react-components/components/Avatar/Avatar'
import { DOMAIN } from '../../config/constants'
@@ -53,7 +54,7 @@ const UserTooltip = ({ usr, id, previewAvatar, size, invitedLabel, showEmailOnly
)}
-
+
{!showEmailOnly &&
{userFullName}
}
diff --git a/src/components/User/UserTooltip.scss b/src/components/User/UserTooltip.scss
index 2d067e669..770d84f6f 100644
--- a/src/components/User/UserTooltip.scss
+++ b/src/components/User/UserTooltip.scss
@@ -127,12 +127,14 @@
display: flex;
flex-wrap: wrap;
align-content: flex-start;
- position: relative;
- width: 80%;
+ overflow: hidden;
}
.user-name-container {
- flex: 1 0 90%;
+ flex: 1 1 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
span {
font-size: 15px;
@@ -143,8 +145,10 @@
}
.user-handle-container {
- padding-right: 10px;
height: 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
&.with-email {
border-right: 1px solid $tc-gray-50;
@@ -163,7 +167,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- max-width: 80%;
a {
font-size: 12px;
@@ -186,6 +189,11 @@
}
}
}
+
+ .tt-col-user-data.with-invite-label .user-name-container,
+ .tt-col-user-data.with-invite-label .user-email-container.text-dark {
+ margin-right: 75px;
+ }
}
.sf-data-bottom-container {
diff --git a/src/config/constants.js b/src/config/constants.js
index cc7d303b6..49a2f2889 100644
--- a/src/config/constants.js
+++ b/src/config/constants.js
@@ -709,7 +709,7 @@ export const DIRECT_PROJECT_URL = `https://www.${DOMAIN}/direct/projectOverview?
export const SALESFORCE_PROJECT_LEAD_LINK = process.env.SALESFORCE_PROJECT_LEAD_LINK
export const TC_NOTIFICATION_URL = process.env.TC_NOTIFICATION_URL || `${TC_API_URL}/v5/notifications`
-export const TC_CDN_URL = process.env.NODE_ENV === 'development' ? 'https://d1aahxkjiobka8.cloudfront.net' : 'https://d2nl5eqipnb33q.cloudfront.net'
+export const TC_CDN_URL = process.env.TC_CDN_URL
export const RESET_PASSWORD_URL = `https://accounts.${DOMAIN}/connect/reset-password`
export const VERIFY_EMAIL_URL = `http://www.${DOMAIN}/settings/account/changeEmail`
diff --git a/src/config/permissions.js b/src/config/permissions.js
index 5ecd6cb72..cf5c0ff54 100644
--- a/src/config/permissions.js
+++ b/src/config/permissions.js
@@ -114,6 +114,7 @@ const TOPCODER_ALL = [
ROLE_ADMINISTRATOR,
ROLE_CONNECT_ADMIN,
ROLE_CONNECT_MANAGER,
+ ROLE_CONNECT_COPILOT_MANAGER,
ROLE_CONNECT_ACCOUNT_MANAGER,
ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE,
ROLE_PRESALES,
diff --git a/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestion.jsx b/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestion.jsx
index 610ecdb49..b6a25e5ee 100644
--- a/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestion.jsx
+++ b/src/projects/detail/components/TalentPickerQuestion/TalentPickerQuestion.jsx
@@ -48,6 +48,16 @@ class TalentPickerQuestion extends Component {
return v.people !== '0' && v.duration !== '0' && v.skills.length > 0
}) // validation body
},
+ noPartialFillsExist: (formValues, value) => {
+ return _.every(value, v => {
+ const isOneValueFilled = v.people > 0 || v.duration > 0 || (v.skills && v.skills.length)
+ const isAllValuesFilled = v.people > 0 && v.duration > 0 && v.skills && v.skills.length
+
+ // If one value is filled, all values should be filled to make this row valid. Partial fill is not valid
+ const isRowValid = !isOneValueFilled || isAllValuesFilled
+ return isRowValid
+ })
+ }
}
setValidations(validations)
}
diff --git a/src/projects/detail/components/TalentPickerRow/TalentPickerRow.jsx b/src/projects/detail/components/TalentPickerRow/TalentPickerRow.jsx
index de8ac2dfe..ce777caaf 100644
--- a/src/projects/detail/components/TalentPickerRow/TalentPickerRow.jsx
+++ b/src/projects/detail/components/TalentPickerRow/TalentPickerRow.jsx
@@ -1,5 +1,6 @@
import React from 'react'
import PT from 'prop-types'
+import cn from 'classnames'
import IconX from '../../../../assets/icons/ui-16px-1_bold-remove.svg'
import IconAdd from '../../../../assets/icons/ui-16px-1_bold-add.svg'
@@ -21,6 +22,9 @@ class TalentPickerRow extends React.PureComponent {
this.handleDurationChange = this.handleDurationChange.bind(this)
this.handleSkillChange = this.handleSkillChange.bind(this)
+ this.resetPeople = this.resetPeople.bind(this)
+ this.resetDuration = this.resetDuration.bind(this)
+
this.onAddRow = this.onAddRow.bind(this)
this.onDeleteRow = this.onDeleteRow.bind(this)
}
@@ -37,6 +41,20 @@ class TalentPickerRow extends React.PureComponent {
this.props.onChange(this.props.rowIndex, 'skills', value)
}
+ resetDuration() {
+ const { rowIndex, onChange, value } = this.props
+ if (!value.duration) {
+ onChange(rowIndex, 'duration', '0')
+ }
+ }
+
+ resetPeople() {
+ const { rowIndex, onChange, value } = this.props
+ if (!value.people) {
+ onChange(rowIndex, 'people', '0')
+ }
+ }
+
onAddRow() {
const { rowIndex, value, onAddRow: addRowHandler } = this.props
addRowHandler(rowIndex + 1, value.role)
@@ -49,6 +67,7 @@ class TalentPickerRow extends React.PureComponent {
render() {
const { value, canBeDeleted, roleSetting, rowIndex } = this.props
+ const isRowIncomplete = value.people > 0 || value.duration > 0 || (value.skills && value.skills.length)
/* Different columns are defined here and used in componsing mobile/desktop views below */
const roleColumn = (
@@ -81,11 +100,12 @@ class TalentPickerRow extends React.PureComponent {
People
)
@@ -96,11 +116,12 @@ class TalentPickerRow extends React.PureComponent {
Duration (months)
)
@@ -126,7 +147,7 @@ class TalentPickerRow extends React.PureComponent {
setValue={this.handleSkillChange}
getValue={() => value.skills}
onChange={_.noop}
- selectWrapperClass={styles.noMargin}
+ selectWrapperClass={cn(styles.noMargin, {[styles.skillHasError]: isRowIncomplete && !(value.skills && value.skills.length)})}
/>
)
diff --git a/src/projects/detail/components/TalentPickerRow/TalentPickerRow.scss b/src/projects/detail/components/TalentPickerRow/TalentPickerRow.scss
index a9d868114..dc8d08899 100644
--- a/src/projects/detail/components/TalentPickerRow/TalentPickerRow.scss
+++ b/src/projects/detail/components/TalentPickerRow/TalentPickerRow.scss
@@ -110,3 +110,12 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
+
+.skillHasError {
+ :global {
+ .react-select__control {
+ border-color: $tc-red-70;
+ }
+ }
+}