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; + } + } +}