From 8a890b42f7a9a4f6b1fea855373cdeb7c1930e6b Mon Sep 17 00:00:00 2001 From: Sukma Nugraha Date: Sun, 14 Jul 2019 12:37:45 +0700 Subject: [PATCH 1/3] Disable "Save Changes" button again if there is no changes. --- src/projects/reducers/project.js | 44 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js index e90c5e021..1b9250649 100644 --- a/src/projects/reducers/project.js +++ b/src/projects/reducers/project.js @@ -61,6 +61,19 @@ const parseErrorObj = (action) => { } } +// remove empty object, null/undefined value and empty string recursively from passed obj +const deepClean = obj => _.transform(obj, (result, value, key) => { + const isCollection = _.isObject(value) + const cleaned = isCollection ? deepClean(value) : value + // exclude if empty object, null, undefined or empty string + if ((isCollection && _.isEmpty(cleaned)) || _.isNil(value) || value === '') { + return + } + _.isArray(result) ? result.push(cleaned) : (result[key] = cleaned) +}) + +const clean = obj => _.isObject(obj) ? deepClean(obj) : obj + /** * Updates a product in the phase list without mutations * @@ -631,13 +644,38 @@ export const projectState = function (state=initialState, action) { if (key === 'screens' || key === 'features' || key === 'capabilities') { return srcValue// srcValue contains the changed values from action payload } + + // project's name might contain ampersand + if (key === 'name') { + return _.escape(srcValue) + } + + // the number of budget qty properties in srcValue + // needs to be matched up with corresponding value in deliverables + if (key === 'budgetDetails') { + if (srcValue.deliverables) { + const allowed = srcValue.deliverables.map(v => { + if (v === 'dev') return 'development' + else if (v === 'data-science') return 'dataScience' + return v + }) + const filtered = Object.keys(srcValue) + .filter(key => allowed.includes(key)) + .reduce((obj, key) => { + obj[key] = srcValue[key] + return obj + }, {}) + return { ...filtered, deliverables: srcValue.deliverables } + } + return srcValue + } } ) // dont' compare this properties as they could be not added to `projectNonDirty` // or mutated somewhere in the app - const skipProperties = ['members'] - const clearUpdatedProject = _.omit(updatedProject, skipProperties) - const clearUpdatedNonDirtyProject = _.omit(state.projectNonDirty, skipProperties) + const skipProperties = ['members', 'invites'] + const clearUpdatedProject = clean(_.omit(updatedProject, skipProperties)) + const clearUpdatedNonDirtyProject = clean(_.omit(state.projectNonDirty, skipProperties)) if (!_.isEqual(clearUpdatedProject, clearUpdatedNonDirtyProject)) { updatedProject.isDirty = true } From 391d26ce24b8fa04c329514300622ea988deff5b Mon Sep 17 00:00:00 2001 From: Sukma Nugraha Date: Sun, 14 Jul 2019 16:38:32 +0700 Subject: [PATCH 2/3] Added reset values in case of deselected option --- src/projects/reducers/project.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js index 1b9250649..4d2eff7ef 100644 --- a/src/projects/reducers/project.js +++ b/src/projects/reducers/project.js @@ -669,6 +669,20 @@ export const projectState = function (state=initialState, action) { } return srcValue } + + // reset the values when deselected + if (key === 'deploymentTargets') { + if (!action.payload.deliverables) { + srcValue = [] + } + return srcValue + } + if (key === 'progressiveResponsive') { + if (!(action.payload.targetDevices && action.payload.targetDevices.includes('web-browser'))) { + srcValue = '' + } + return srcValue + } } ) // dont' compare this properties as they could be not added to `projectNonDirty` From 84b914caeb74d33a8128b1f171c3be70407544e3 Mon Sep 17 00:00:00 2001 From: Sukma Nugraha Date: Mon, 15 Jul 2019 19:46:14 +0700 Subject: [PATCH 3/3] Using general fix without hardcoded fields. --- src/helpers/utils.js | 20 +++++++- .../EditProjectForm/EditProjectForm.jsx | 14 +++++- src/projects/reducers/project.js | 48 +------------------ 3 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 7e56cda89..4a26306aa 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -55,4 +55,22 @@ export const compareEmail = (email1, email2, options = { UNIQUE_GMAIL_VALIDATION */ export const compareHandles = (handle1, handle2) => ( (handle1 || '').toLowerCase() === (handle2 || '').toLowerCase() -) \ No newline at end of file +) + +// remove empty object, null/undefined value and empty string recursively from passed obj +const deepClean = obj => _.transform(obj, (result, value, key) => { + const isCollection = _.isObject(value) + const cleaned = isCollection ? deepClean(value) : value + // exclude if empty object, null, undefined or empty string + if ((isCollection && _.isEmpty(cleaned)) || _.isNil(value) || value === '') { + return + } + _.isArray(result) ? result.push(cleaned) : (result[key] = cleaned) +}) + +/** + * Helper method to clean given object from null, undefined or empty property. + * + * @param {Object} obj the object to clean + */ +export const clean = obj => _.isObject(obj) ? deepClean(obj) : obj diff --git a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx index 584045f7b..c5b7b2375 100644 --- a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx +++ b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx @@ -23,6 +23,7 @@ import { STEP_VISIBILITY, STEP_STATE, } from '../../../../helpers/wizardHelper' +import { clean } from '../../../../helpers/utils' import './EditProjectForm.scss' @@ -151,6 +152,17 @@ class EditProjectForm extends Component { template: updatedTemplate, project: hidedSomeNodes ? nextProps.project : this.state.project, }) + + // re-check again if any hidden values when an option is deselected + const updatedProject = clean(removeValuesOfHiddenNodes(updatedTemplate, nextProps.project)) + const skipProperties = ['members', 'invites'] + const clearUpdatedProject = clean(_.omit(updatedProject, [...skipProperties, 'isDirty'])) + const clearUpdatedNonDirtyProject = clean(_.omit(nextProps.projectNonDirty, skipProperties)) + const isDirty = !_.isEqual(clearUpdatedProject, clearUpdatedNonDirtyProject) + // update the state, always use this flag to check if changed + this.setState({ + isProjectDirty: isDirty, + }) } } } @@ -206,7 +218,7 @@ class EditProjectForm extends Component { } isChanged() { - return !!this.props.project.isDirty + return !!this.state.isProjectDirty } enableButton() { diff --git a/src/projects/reducers/project.js b/src/projects/reducers/project.js index 4d2eff7ef..1abf506ec 100644 --- a/src/projects/reducers/project.js +++ b/src/projects/reducers/project.js @@ -25,6 +25,7 @@ import { } from '../../config/constants' import _ from 'lodash' import update from 'react-addons-update' +import { clean } from '../../helpers/utils' const initialState = { isLoading: true, @@ -61,19 +62,6 @@ const parseErrorObj = (action) => { } } -// remove empty object, null/undefined value and empty string recursively from passed obj -const deepClean = obj => _.transform(obj, (result, value, key) => { - const isCollection = _.isObject(value) - const cleaned = isCollection ? deepClean(value) : value - // exclude if empty object, null, undefined or empty string - if ((isCollection && _.isEmpty(cleaned)) || _.isNil(value) || value === '') { - return - } - _.isArray(result) ? result.push(cleaned) : (result[key] = cleaned) -}) - -const clean = obj => _.isObject(obj) ? deepClean(obj) : obj - /** * Updates a product in the phase list without mutations * @@ -649,40 +637,6 @@ export const projectState = function (state=initialState, action) { if (key === 'name') { return _.escape(srcValue) } - - // the number of budget qty properties in srcValue - // needs to be matched up with corresponding value in deliverables - if (key === 'budgetDetails') { - if (srcValue.deliverables) { - const allowed = srcValue.deliverables.map(v => { - if (v === 'dev') return 'development' - else if (v === 'data-science') return 'dataScience' - return v - }) - const filtered = Object.keys(srcValue) - .filter(key => allowed.includes(key)) - .reduce((obj, key) => { - obj[key] = srcValue[key] - return obj - }, {}) - return { ...filtered, deliverables: srcValue.deliverables } - } - return srcValue - } - - // reset the values when deselected - if (key === 'deploymentTargets') { - if (!action.payload.deliverables) { - srcValue = [] - } - return srcValue - } - if (key === 'progressiveResponsive') { - if (!(action.payload.targetDevices && action.payload.targetDevices.includes('web-browser'))) { - srcValue = '' - } - return srcValue - } } ) // dont' compare this properties as they could be not added to `projectNonDirty`