From 83558fef652705db2b54371e55f33be905648e7b Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Tue, 22 Sep 2020 18:09:04 +0530 Subject: [PATCH 1/5] feat: git#24-Task assigned member is not being synced to the legacy - Adding Submitter resource instead of updating the task.* fields as per the suggestions --- .../AssignedMember-Field/index.js | 7 ++-- src/components/ChallengeEditor/index.js | 34 ++++++++++++++----- src/containers/ChallengeEditor/index.js | 23 ++++++++----- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/components/ChallengeEditor/AssignedMember-Field/index.js b/src/components/ChallengeEditor/AssignedMember-Field/index.js index b6ea7b90..4731518d 100644 --- a/src/components/ChallengeEditor/AssignedMember-Field/index.js +++ b/src/components/ChallengeEditor/AssignedMember-Field/index.js @@ -8,12 +8,11 @@ import styles from './AssignedMember-Field.module.scss' import SelectUserAutocomplete from '../../SelectUserAutocomplete' const AssignedMemberField = ({ challenge, onChange, assignedMemberDetails, readOnly }) => { - const value = challenge.task.memberId ? { + const value = assignedMemberDetails ? { // if we know assigned member details, then show user `handle`, otherwise fallback to `userId` - label: assignedMemberDetails ? assignedMemberDetails.handle : `User id: ${challenge.task.memberId}`, - value: challenge.task.memberId + label: assignedMemberDetails.handle, + value: assignedMemberDetails.userId + '' } : null - return (
diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index 8924578c..f5d5d792 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -310,18 +310,18 @@ class ChallengeEditor extends Component { let assignedMemberDetails if (option && option.value) { - newChallenge.task = { + /* newChallenge.task = { ...oldChallenge.task, memberId: option.value // TODO uncomment as soon as issue in API is fixed https://github.com/topcoder-platform/challenge-api/issues/272 // isAssigned: true - } + } */ assignedMemberDetails = { handle: option.label, userId: parseInt(option.value, 10) } } else { - newChallenge.task = _.omit(oldChallenge.task, ['memberId', 'isAssigned']) + // newChallenge.task = _.omit(oldChallenge.task, ['memberId', 'isAssigned']) assignedMemberDetails = null } @@ -851,7 +851,7 @@ class ChallengeEditor extends Component { } async updateAllChallengeInfo (status, cb = () => {}) { - const { updateChallengeDetails } = this.props + const { updateChallengeDetails, assignedMemberDetails: oldAssignedMember } = this.props if (this.state.isSaving) return this.setState({ isSaving: true }) const challenge = this.collectChallengeData(status) @@ -861,9 +861,17 @@ class ChallengeEditor extends Component { const challengeId = this.getCurrentChallengeId() const action = await updateChallengeDetails(challengeId, challenge) const { copilot: previousCopilot, reviewer: previousReviewer } = this.state.draftChallenge.data - const { copilot, reviewer } = this.state.challenge + const { challenge: { copilot, reviewer }, assignedMemberDetails: assignedMember } = this.state if (copilot) await this.updateResource(challengeId, 'Copilot', copilot, previousCopilot) if (reviewer) await this.updateResource(challengeId, 'Reviewer', reviewer, previousReviewer) + console.log(oldAssignedMember, 'oldAssignedMember') + console.log(assignedMember, 'assignedMember') + const oldMemberHandle = _.get(oldAssignedMember, 'handle') + // assigned member has been updated + if (assignedMember && assignedMember.handle !== oldMemberHandle) { + await this.updateResource(challengeId, 'Submitter', assignedMember.handle, oldMemberHandle) + } + this.updateTimeLastSaved() const draftChallenge = { data: action.challengeDetails } draftChallenge.data.copilot = copilot @@ -911,11 +919,19 @@ class ChallengeEditor extends Component { roleId: resourceRole ? resourceRole.id : null } if (prevValue) { - const oldResource = _.pick(newResource, ['challengeId', 'roleId']) - oldResource.memberHandle = prevValue - await deleteResource(oldResource) + try { + const oldResource = _.pick(newResource, ['challengeId', 'roleId']) + oldResource.memberHandle = prevValue + await deleteResource(oldResource) + } catch (err) { + const errorMessage = _.get(err, 'response.data.message') + // ignore error where the resource does not exist already + if (errorMessage.indexOf('doesn\'t have resource with roleId') === -1) { + throw err + } + } } - + console.log('creating new resource') await createResource(newResource) } diff --git a/src/containers/ChallengeEditor/index.js b/src/containers/ChallengeEditor/index.js index fc7257ee..e8fa58d2 100644 --- a/src/containers/ChallengeEditor/index.js +++ b/src/containers/ChallengeEditor/index.js @@ -29,6 +29,7 @@ import { } from '../../actions/members' import { connect } from 'react-redux' +import { SUBMITTER_ROLE_UUID } from '../../config/constants' class ChallengeEditor extends Component { componentDidMount () { @@ -125,15 +126,21 @@ class ChallengeEditor extends Component { projectDetail, updateChallengeDetails, partiallyUpdateChallengeDetails, - createChallenge, - members + createChallenge + // members } = this.props const challengeId = _.get(match.params, 'challengeId', null) if (challengeId && (!challengeDetails || !challengeDetails.id)) { return () } - const assignedMemberId = _.get(challengeDetails, 'task.memberId') - const assignedMemberDetails = _.find(members, (member) => member.userId.toString() === assignedMemberId) + const submitters = challengeResources.filter(cr => cr.roleId === SUBMITTER_ROLE_UUID) + var assignedMemberDetails = null + if (submitters && submitters.length === 1) { + assignedMemberDetails = { + userId: submitters[0].memberId, + handle: submitters[0].memberHandle + } + } return
({ @@ -251,8 +258,8 @@ const mapStateToProps = ({ projects: { projectDetail }, challenges: { challengeD isLoading, attachments, token, - failedToLoad, - members + failedToLoad + // members }) const mapDispatchToProps = { From e313acd7b70ec86042f8a9ea6e6059aaf49aa989 Mon Sep 17 00:00:00 2001 From: maxceem Date: Tue, 22 Sep 2020 18:02:11 +0300 Subject: [PATCH 2/5] fix: error during creating a new task --- src/containers/ChallengeEditor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/ChallengeEditor/index.js b/src/containers/ChallengeEditor/index.js index e8fa58d2..81831317 100644 --- a/src/containers/ChallengeEditor/index.js +++ b/src/containers/ChallengeEditor/index.js @@ -133,7 +133,7 @@ class ChallengeEditor extends Component { if (challengeId && (!challengeDetails || !challengeDetails.id)) { return () } - const submitters = challengeResources.filter(cr => cr.roleId === SUBMITTER_ROLE_UUID) + const submitters = challengeResources && challengeResources.filter(cr => cr.roleId === SUBMITTER_ROLE_UUID) var assignedMemberDetails = null if (submitters && submitters.length === 1) { assignedMemberDetails = { From be8899e36075f2e953c7cd15e4da3c509871b82d Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Wed, 23 Sep 2020 10:41:31 +0530 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20git#746-Add=20"Provide=20Applicatio?= =?UTF-8?q?n=20Feedback"=20Capability=20to=20Work=20Manager=20App=20?= =?UTF-8?q?=E2=80=94=20Added=20Feedback=20and=20support=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sidebar/Sidebar.module.scss | 11 +++++++++++ src/components/Sidebar/index.js | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/components/Sidebar/Sidebar.module.scss b/src/components/Sidebar/Sidebar.module.scss index 8b87d068..4a53d4d2 100644 --- a/src/components/Sidebar/Sidebar.module.scss +++ b/src/components/Sidebar/Sidebar.module.scss @@ -44,3 +44,14 @@ border-left-color: $teal; } } + +p.supportLink { + font-family: Roboto,Helvetica,Arial,sans-serif; + font-weight: 400; + color: grey; + font-size: 12px; + line-height: 150%; + margin-top: 10px; + padding-bottom: 40px; + text-align: center; +} diff --git a/src/components/Sidebar/index.js b/src/components/Sidebar/index.js index b393bbd9..a9be7f96 100644 --- a/src/components/Sidebar/index.js +++ b/src/components/Sidebar/index.js @@ -20,6 +20,15 @@ const Sidebar = ({ Active challenges
+ +
+ Give Application Feedback +
+ +

+ Have an urgent issue?
+ E: support@topcoder.com +

) } From b2bfe9d1d0fdd604c60e4a36233178adb659e8f0 Mon Sep 17 00:00:00 2001 From: maxceem Date: Thu, 24 Sep 2020 19:13:14 +0300 Subject: [PATCH 4/5] feat: editing final deliverables ref issue #782 --- .../PrimaryButton/PrimaryButton.module.scss | 15 +++ src/components/Buttons/PrimaryButton/index.js | 7 +- .../FinalDeliverables-Field.module.scss | 119 ++++++------------ .../FinalDeliverables-Field/index.js | 46 ++++--- .../ChallengeEditor/TextEditor-Field/index.js | 3 + src/components/ChallengeEditor/index.js | 65 ++++++++-- 6 files changed, 136 insertions(+), 119 deletions(-) diff --git a/src/components/Buttons/PrimaryButton/PrimaryButton.module.scss b/src/components/Buttons/PrimaryButton/PrimaryButton.module.scss index f2a856c6..d14556cd 100644 --- a/src/components/Buttons/PrimaryButton/PrimaryButton.module.scss +++ b/src/components/Buttons/PrimaryButton/PrimaryButton.module.scss @@ -20,13 +20,28 @@ &.danger { background-color: $tc-red; + + &:disabled { + cursor: default; + background-color: $inactive; + } } &.info { background-color: $tc-blue-20; + + &:disabled { + cursor: default; + background-color: $inactive; + } } &.success { background-color: $tc-green-40; + + &:disabled { + cursor: default; + background-color: $inactive; + } } } diff --git a/src/components/Buttons/PrimaryButton/index.js b/src/components/Buttons/PrimaryButton/index.js index 48be4d1d..89c9a552 100644 --- a/src/components/Buttons/PrimaryButton/index.js +++ b/src/components/Buttons/PrimaryButton/index.js @@ -6,10 +6,10 @@ import cn from 'classnames' import styles from './PrimaryButton.module.scss' -const PrimaryButton = ({ type, text, link, onClick, submit }) => { +const PrimaryButton = ({ type, text, link, onClick, submit, disabled }) => { if (_.isEmpty(link)) { return ( - ) @@ -26,7 +26,8 @@ PrimaryButton.propTypes = { text: PropTypes.string.isRequired, link: PropTypes.string, onClick: PropTypes.func, - submit: PropTypes.bool + submit: PropTypes.bool, + disabled: PropTypes.bool } export default PrimaryButton diff --git a/src/components/ChallengeEditor/FinalDeliverables-Field/FinalDeliverables-Field.module.scss b/src/components/ChallengeEditor/FinalDeliverables-Field/FinalDeliverables-Field.module.scss index aa2f9e96..4dd15523 100644 --- a/src/components/ChallengeEditor/FinalDeliverables-Field/FinalDeliverables-Field.module.scss +++ b/src/components/ChallengeEditor/FinalDeliverables-Field/FinalDeliverables-Field.module.scss @@ -47,104 +47,55 @@ } } - .tcCheckbox { - @include tc-checkbox; - - .tc-checkbox-label { - @include roboto-light(); - - line-height: 17px; - font-weight: 300; - margin-left: 21px; - user-select: none; - cursor: pointer; - width: 195px; - font-size: 14px; - color: #3d3d3d; - } - - height: 18px; - width: 210px; + .fileTypeList { margin: 0; padding: 0; - vertical-align: bottom; - position: relative; - display: inline-block; - margin-bottom: 18px; - - input[type='checkbox'] { - display: none; - - &:checked ~ label { - background: $tc-blue-20; - } - - &:checked + label::after { - border-color: $white; - } - } - - label { - @include roboto-light(); - - line-height: 17px; - font-weight: 300; - cursor: pointer; - position: absolute; - display: inline-block; - width: 14px; - height: 14px; - top: 0; - left: 0; - border: none; - box-shadow: none; - background: $tc-gray-30; - transition: all 0.15s ease-in-out; - - &.readOnly { - cursor: auto; - } + } - &::after { - opacity: 0; - content: ''; - position: absolute; - width: 9px; - height: 5px; - background: transparent; - top: 2px; - left: 2px; - border-top: none; - border-right: none; - transform: rotate(-45deg); - transition: all 0.15s ease-in-out; - } + .fileTypeListEditable { - &:hover::after { - opacity: 0.3; - } - &:hover:read-only::after { - opacity: 0; - } + } - div { - margin-left: 24px; - width: 500px; - } - } + .fileTypeItem { + background-color: #2C95D7; + border-radius: 2px; + border: 1px solid #c9e6f2; + color: #ffffff; + display: inline-block; + font-size: 14px; + margin-right: 5px; + padding: 2px 30px 2px 5px; + position: relative; + vertical-align: top; } - .checkList { + .fileTypeDelete { + align-items: center; + background-color: #c6def1; + border: 0; + color: #2C95D7; + cursor: pointer; display: flex; - flex-direction: column; - margin-left: 30px; + border-radius: 50%; + font-size: 14px; + height: 16px; + justify-content: center; + position: absolute; + right: 5px; + top: 50%; + transform: translateY(-50%); + width: 16px; + + &:hover { + background-color: #fff; + } } form { display: flex; flex-direction: row; } - + .button { width: 137px; height: 40px; diff --git a/src/components/ChallengeEditor/FinalDeliverables-Field/index.js b/src/components/ChallengeEditor/FinalDeliverables-Field/index.js index df76eeb7..57ead390 100644 --- a/src/components/ChallengeEditor/FinalDeliverables-Field/index.js +++ b/src/components/ChallengeEditor/FinalDeliverables-Field/index.js @@ -17,12 +17,12 @@ class FinalDeliverablesField extends Component { } onChangeInput (value) { - this.setState({ newFileType: _.trim(value) }) + this.setState({ newFileType: value }) } onAddFileType (event) { if (!_.isEmpty(this.state.newFileType)) { - this.props.addFileType(this.state.newFileType) + this.props.addFileType(this.state.newFileType.trim()) this.setState({ newFileType: '' }) } @@ -31,7 +31,11 @@ class FinalDeliverablesField extends Component { } render () { - const { challenge, onUpdateCheckbox, readOnly } = this.props + const { challenge, readOnly, removeFileType } = this.props + const fileTypesMetadata = _.find(challenge.metadata, { name: 'fileTypes' }) + const fileTypes = (fileTypesMetadata && JSON.parse(fileTypesMetadata.value)) || [] + const isDuplicateValue = _.includes(fileTypes, this.state.newFileType.trim()) + return (
@@ -40,27 +44,18 @@ class FinalDeliverablesField extends Component {
-
- { - _.map(challenge.fileTypes, (type, index) => ( -
- onUpdateCheckbox(type.name, e.target.checked, 'fileTypes', index)} - readOnly={readOnly} - /> - -
- )) - } -
+ {!readOnly ? ( + + ) : ( + fileTypes.join(', ') + )}
{!readOnly && (
@@ -76,6 +71,7 @@ class FinalDeliverablesField extends Component {
@@ -93,8 +89,8 @@ FinalDeliverablesField.defaultProps = { FinalDeliverablesField.propTypes = { challenge: PropTypes.shape().isRequired, - onUpdateCheckbox: PropTypes.func.isRequired, addFileType: PropTypes.func.isRequired, + removeFileType: PropTypes.func.isRequired, readOnly: PropTypes.bool } diff --git a/src/components/ChallengeEditor/TextEditor-Field/index.js b/src/components/ChallengeEditor/TextEditor-Field/index.js index 74d2b57f..303b8968 100644 --- a/src/components/ChallengeEditor/TextEditor-Field/index.js +++ b/src/components/ChallengeEditor/TextEditor-Field/index.js @@ -29,6 +29,7 @@ class TextEditorField extends Component { challenge, onUpdateCheckbox, addFileType, + removeFileType, onUpdateDescription, onUpdateMultiSelect, shouldShowPrivateDescription, @@ -87,6 +88,7 @@ class TextEditorField extends Component { challenge={challenge} onUpdateCheckbox={onUpdateCheckbox} addFileType={addFileType} + removeFileType={removeFileType} readOnly={readOnly} /> -1) { + fileTypesMetadata = { ...newChallenge.metadata[fileTypesMetadataIndex] } + newChallenge.metadata[fileTypesMetadataIndex] = fileTypesMetadata + // if not yet, create an empty record in metadata + } else { + fileTypesMetadata = { name: 'fileTypes', value: '[]' } + newChallenge.metadata.push(fileTypesMetadata) + } + + // as values in metadata are always stored as string, we have to parse it, update and stringify again + const oldFileTypes = JSON.parse(fileTypesMetadata.value) + const newFileTypes = processValue(oldFileTypes) + fileTypesMetadata.value = JSON.stringify(newFileTypes) + this.setState({ challenge: newChallenge }) } + /** + * Add new file type + * @param {String} newFileType The new file type + */ + addFileType (newFileType) { + this.updateFileTypesMetadata((oldFileTypes) => { + const newFileTypes = [...oldFileTypes, newFileType] + + return newFileTypes + }) + } + + /** + * Remove file type + * @param {String} fileType file type + */ + removeFileType (fileType) { + this.updateFileTypesMetadata((oldFileTypes) => { + const newFileTypes = _.reject(oldFileTypes, (type) => type === fileType) + + return newFileTypes + }) + } + /** * Update Metadata * @param name Name of data @@ -1312,6 +1362,7 @@ class ChallengeEditor extends Component { challenge={challenge} onUpdateCheckbox={this.onUpdateCheckbox} addFileType={this.addFileType} + removeFileType={this.removeFileType} onUpdateInput={this.onUpdateInput} onUpdateDescription={this.onUpdateDescription} onUpdateMultiSelect={this.onUpdateMultiSelect} From a6d5092809e2305556d3a4abaaeac430dc246139 Mon Sep 17 00:00:00 2001 From: Vikas Agarwal Date: Fri, 25 Sep 2020 16:11:57 +0530 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20git#833-Save=20fails=20after=20chan?= =?UTF-8?q?ging=20assigned=20user=20for=20a=20task=20more=20than=20once=20?= =?UTF-8?q?=E2=80=94=20Fixed=20problem=20with=20reading=20old=20resource?= =?UTF-8?q?=20when=20assigned=20user=20is=20changed=20more=20than=20once?= =?UTF-8?q?=20without=20navigating=20away=20from=20the=20page.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 5 +++ package.json | 1 + src/actions/challenges.js | 54 ++++++++++++++++++++++++- src/components/ChallengeEditor/index.js | 44 ++------------------ src/config/constants.js | 10 +++++ src/config/store.js | 8 +++- src/containers/ChallengeEditor/index.js | 14 +++++-- src/reducers/challenges.js | 43 +++++++++++++++++++- src/services/challenges.js | 10 +++-- 9 files changed, 138 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c6ddc0f..6034dbbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14822,6 +14822,11 @@ "deep-diff": "^0.3.5" } }, + "redux-promise-middleware": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux-promise-middleware/-/redux-promise-middleware-4.2.1.tgz", + "integrity": "sha1-UMSW+bVGpotR+oUJvi4K1pvnYkY=" + }, "redux-thunk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", diff --git a/package.json b/package.json index 787e59a9..3635e879 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "react-tabs": "^3.0.0", "redux": "^4.0.1", "redux-logger": "^3.0.6", + "redux-promise-middleware": "4.2.1", "redux-thunk": "^2.3.0", "resolve": "1.8.1", "sass-loader": "7.1.0", diff --git a/src/actions/challenges.js b/src/actions/challenges.js index a18bfd91..6661b4c5 100644 --- a/src/actions/challenges.js +++ b/src/actions/challenges.js @@ -15,7 +15,9 @@ import { fetchChallengeTracks, updateChallenge, patchChallenge, - createChallenge as createChallengeAPI + createChallenge as createChallengeAPI, + createResource as createResourceAPI, + deleteResource as deleteResourceAPI } from '../services/challenges' import { LOAD_CHALLENGE_DETAILS_PENDING, @@ -32,6 +34,8 @@ import { LOAD_CHALLENGE_RESOURCES_PENDING, LOAD_CHALLENGE_RESOURCES_SUCCESS, LOAD_CHALLENGE_RESOURCES_FAILURE, + CREATE_CHALLENGE_RESOURCE, + DELETE_CHALLENGE_RESOURCE, REMOVE_ATTACHMENT, PAGE_SIZE, UPDATE_CHALLENGE_DETAILS_PENDING, @@ -452,3 +456,51 @@ export function loadResourceRoles () { }) } } + +export function deleteResource (challengeId, roleId, memberHandle) { + const resource = { + challengeId, + roleId, + memberHandle + } + return (dispatch, getState) => { + return dispatch({ + type: DELETE_CHALLENGE_RESOURCE, + payload: deleteResourceAPI(resource) + }) + } +} + +export function createResource (challengeId, roleId, memberHandle) { + const resource = { + challengeId, + roleId, + memberHandle + } + return (dispatch, getState) => { + return dispatch({ + type: CREATE_CHALLENGE_RESOURCE, + payload: createResourceAPI(resource) + }) + } +} + +export function replaceResourceInRole (challengeId, roleId, newMember, oldMember) { + return async (dispatch) => { + if (newMember === oldMember) { + return + } + if (oldMember) { + try { + await dispatch(deleteResource(challengeId, roleId, oldMember)) + } catch (error) { + const errorMessage = _.get(error, 'response.data.message') + // ignore error where the resource does not exist already + if (errorMessage.indexOf('doesn\'t have resource with roleId') === -1) { + return Promise.reject(new Error('Unable to delete resource')) + } + } + } + await dispatch(createResource(challengeId, roleId, newMember)) + } +} diff --git a/src/components/ChallengeEditor/index.js b/src/components/ChallengeEditor/index.js index f5d5d792..e26b3d43 100644 --- a/src/components/ChallengeEditor/index.js +++ b/src/components/ChallengeEditor/index.js @@ -6,7 +6,6 @@ import { Helmet } from 'react-helmet' import cn from 'classnames' import moment from 'moment' import { pick } from 'lodash/fp' -// import Modal from '../Modal' import { withRouter } from 'react-router-dom' import { toastr } from 'react-redux-toastr' import xss from 'xss' @@ -39,10 +38,6 @@ import dropdowns from './mock-data/dropdowns' import LastSavedDisplay from './LastSaved-Display' import styles from './ChallengeEditor.module.scss' import Track from '../Track' -import { - createResource, - deleteResource -} from '../../services/challenges' import ConfirmationModal from '../Modal/ConfirmationModal' import AlertModal from '../Modal/AlertModal' import PhaseInput from '../PhaseInput' @@ -306,22 +301,15 @@ class ChallengeEditor extends Component { */ onUpdateAssignedMember (option) { const { challenge: oldChallenge } = this.state - const newChallenge = { ...oldChallenge } + const newChallenge = { ...oldChallenge, task: { isAssigned: false, memberId: null, isTask: true } } let assignedMemberDetails if (option && option.value) { - /* newChallenge.task = { - ...oldChallenge.task, - memberId: option.value - // TODO uncomment as soon as issue in API is fixed https://github.com/topcoder-platform/challenge-api/issues/272 - // isAssigned: true - } */ assignedMemberDetails = { handle: option.label, userId: parseInt(option.value, 10) } } else { - // newChallenge.task = _.omit(oldChallenge.task, ['memberId', 'isAssigned']) assignedMemberDetails = null } @@ -864,14 +852,11 @@ class ChallengeEditor extends Component { const { challenge: { copilot, reviewer }, assignedMemberDetails: assignedMember } = this.state if (copilot) await this.updateResource(challengeId, 'Copilot', copilot, previousCopilot) if (reviewer) await this.updateResource(challengeId, 'Reviewer', reviewer, previousReviewer) - console.log(oldAssignedMember, 'oldAssignedMember') - console.log(assignedMember, 'assignedMember') const oldMemberHandle = _.get(oldAssignedMember, 'handle') // assigned member has been updated if (assignedMember && assignedMember.handle !== oldMemberHandle) { await this.updateResource(challengeId, 'Submitter', assignedMember.handle, oldMemberHandle) } - this.updateTimeLastSaved() const draftChallenge = { data: action.challengeDetails } draftChallenge.data.copilot = copilot @@ -910,29 +895,8 @@ class ChallengeEditor extends Component { async updateResource (challengeId, name, value, prevValue) { const resourceRole = this.getResourceRoleByName(name) - if (value === prevValue) { - return - } - const newResource = { - challengeId, - memberHandle: value, - roleId: resourceRole ? resourceRole.id : null - } - if (prevValue) { - try { - const oldResource = _.pick(newResource, ['challengeId', 'roleId']) - oldResource.memberHandle = prevValue - await deleteResource(oldResource) - } catch (err) { - const errorMessage = _.get(err, 'response.data.message') - // ignore error where the resource does not exist already - if (errorMessage.indexOf('doesn\'t have resource with roleId') === -1) { - throw err - } - } - } - console.log('creating new resource') - await createResource(newResource) + const roleId = resourceRole.id + await this.props.replaceResourceInRole(challengeId, roleId, value, prevValue) } updateAttachmentlist (challenge, attachments) { @@ -1141,7 +1105,6 @@ class ChallengeEditor extends Component { ) } } - if (!isNew && challenge.status !== 'New' && isLaunch && isConfirm) { draftModal = ( )) @@ -187,6 +190,7 @@ class ChallengeEditor extends Component { projectDetail={projectDetail} assignedMemberDetails={assignedMemberDetails} updateChallengeDetails={updateChallengeDetails} + replaceResourceInRole={replaceResourceInRole} partiallyUpdateChallengeDetails={partiallyUpdateChallengeDetails} /> )) @@ -246,7 +250,8 @@ ChallengeEditor.propTypes = { loadMemberDetails: PropTypes.func, updateChallengeDetails: PropTypes.func.isRequired, partiallyUpdateChallengeDetails: PropTypes.func.isRequired, - createChallenge: PropTypes.func.isRequired + createChallenge: PropTypes.func.isRequired, + replaceResourceInRole: PropTypes.func // members: PropTypes.arrayOf(PropTypes.shape()) } @@ -279,7 +284,8 @@ const mapDispatchToProps = { loadMemberDetails, updateChallengeDetails, partiallyUpdateChallengeDetails, - createChallenge + createChallenge, + replaceResourceInRole } export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ChallengeEditor)) diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index d3e73a7d..f9237977 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -23,7 +23,11 @@ import { UPDATE_CHALLENGE_DETAILS_FAILURE, UPDATE_CHALLENGE_DETAILS_SUCCESS, CREATE_CHALLENGE_SUCCESS, - CREATE_CHALLENGE_FAILURE + CREATE_CHALLENGE_FAILURE, + CREATE_CHALLENGE_RESOURCE_SUCCESS, + DELETE_CHALLENGE_RESOURCE_SUCCESS, + DELETE_CHALLENGE_RESOURCE_FAILURE, + CREATE_CHALLENGE_RESOURCE_FAILURE } from '../config/constants' const initialState = { @@ -164,6 +168,43 @@ export default function (state = initialState, action) { isLoading: false, failedToLoad: false } + case CREATE_CHALLENGE_RESOURCE_SUCCESS: { + const resource = action.payload + const challengeResources = _.clone(state.challengeResources) + challengeResources.push(resource) + return { + ...state, + challengeResources, + isLoading: false, + failedToLoad: false + } + } + case CREATE_CHALLENGE_RESOURCE_FAILURE: { + const resource = action.payload + console.log(resource) + return { ...state, isLoading: false, failedToCreate: true } + } + case DELETE_CHALLENGE_RESOURCE_SUCCESS: { + const resource = action.payload + const challengeResources = _.clone(state.challengeResources) + _.remove(challengeResources, + r => r.challengeId === resource.challengeId && r.roleId === resource.roleId && r.memberHandle === resource.memberHandle) + return { + ...state, + challengeResources, + isLoading: false, + failedToLoad: false + } + } + case DELETE_CHALLENGE_RESOURCE_FAILURE: { + const err = action.payload + const errorMessage = _.get(err, 'response.data.message') + // ignore error where the resource does not exist already + if (errorMessage.indexOf('doesn\'t have resource with roleId') === -1) { + return { ...state, isLoading: false, failedToDelete: true } + } + return { ...state, isLoading: false, failedToDelete: false } + } case LOAD_CHALLENGE_METADATA_SUCCESS: return { ...state, diff --git a/src/services/challenges.js b/src/services/challenges.js index 4a96c250..333920b8 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -172,8 +172,9 @@ export async function fetchChallengeTerms () { * @param challenge challenge data * @returns {Promise<*>} */ -export function createResource (resource) { - return axiosInstance.post(RESOURCES_API_URL, resource) +export async function createResource (resource) { + const resp = await axiosInstance.post(RESOURCES_API_URL, resource) + return _.get(resp, 'data', {}) } /** @@ -200,6 +201,7 @@ export async function fetchResourceRoles () { * @param {object} resource to delete * @returns {Promise<*>} */ -export function deleteResource (resource) { - return axiosInstance.delete(RESOURCES_API_URL, { data: resource }) +export async function deleteResource (resource) { + const resp = await axiosInstance.delete(RESOURCES_API_URL, { data: resource }) + return _.get(resp, 'data', {}) }