From cb2b6fcc84e24f64c0128e7f921f5f3606705436 Mon Sep 17 00:00:00 2001 From: Jie Hao Kwa Date: Tue, 8 Dec 2020 17:02:51 +0800 Subject: [PATCH 1/6] chore: save original settings state This commit saves the original settings configuration as a state variable so that we can compare the current settings fields with the original state to see if any changes were made. This allows us to warn the user if they attempt to navigate away from the page without saving their changes. This commit also removes the `footerSha` attribute from state since it is no longer required after PR #85 on the backend. --- src/layouts/Settings.jsx | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/layouts/Settings.jsx b/src/layouts/Settings.jsx index 225ce00d9..00df85807 100644 --- a/src/layouts/Settings.jsx +++ b/src/layouts/Settings.jsx @@ -68,7 +68,6 @@ const stateFields = { youtube: '', instagram: '', }, - footerSha: '', }; export default class Settings extends Component { @@ -104,30 +103,30 @@ export default class Settings extends Component { configFieldsRequired, footerContent, navigationContent, - footerSha, } = settings; - // set state properly - if (this._isMounted) this.setState((currState) => ({ - ...currState, - siteName, + const originalState = { ...configFieldsRequired, otherFooterSettings: { - ...currState.otherFooterSettings, contact_us: footerContent.contact_us, show_reach: footerContent.show_reach, feedback: footerContent.feedback, faq: footerContent.faq, }, socialMediaContent: { - ...currState.socialMediaContent, ...footerContent.social_media, }, navigationSettings: { - ...currState.navigationSettings, ...navigationContent, }, - footerSha, + } + + // set state properly + if (this._isMounted) this.setState((currState) => ({ + ...currState, + siteName, + originalState: _.cloneDeep(originalState), + ...originalState, })); } catch (err) { console.log(err); @@ -278,9 +277,6 @@ export default class Settings extends Component { colors, }; - // obtain sha value - const { footerSha } = state; - const params = { footerSettings: { ...otherFooterSettings, @@ -288,7 +284,6 @@ export default class Settings extends Component { }, configSettings, navigationSettings, - footerSha, }; await axios.post(`${process.env.REACT_APP_BACKEND_URL}/sites/${siteName}/settings`, params, { @@ -411,7 +406,6 @@ export default class Settings extends Component { socialMediaContent, otherFooterSettings, navigationSettings: { logo }, - footerSha, errors, } = this.state; const { 'primary-color': primaryColor, 'secondary-color': secondaryColor, 'media-colors': mediaColors } = colors; @@ -628,7 +622,7 @@ export default class Settings extends Component { label="Save" disabled={hasErrors} disabledStyle={elementStyles.formSaveButtonDisabled} - className={(hasErrors || !footerSha) + className={(hasErrors) ? elementStyles.formSaveButtonDisabled : elementStyles.formSaveButtonActive} callback={this.saveSettings} From 794d11722556e8c22b7ea6ea1f53d5a309db6f63 Mon Sep 17 00:00:00 2001 From: Jie Hao Kwa Date: Tue, 8 Dec 2020 23:24:20 +0800 Subject: [PATCH 2/6] chore: list out config fields when setting Settings state --- src/layouts/Settings.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/layouts/Settings.jsx b/src/layouts/Settings.jsx index 00df85807..57dff8de3 100644 --- a/src/layouts/Settings.jsx +++ b/src/layouts/Settings.jsx @@ -106,7 +106,17 @@ export default class Settings extends Component { } = settings; const originalState = { - ...configFieldsRequired, + // config fields + colors: configFieldsRequired.colors, + favicon: configFieldsRequired.favicon, + google_analytics: configFieldsRequired.google_analytics, + facebook_pixel: configFieldsRequired.facebook_pixel, + is_government: configFieldsRequired.is_government, + resources_name: configFieldsRequired.resources_name, + shareicon: configFieldsRequired.shareicon, + title: configFieldsRequired.title, + url: configFieldsRequired.url, + // footer fields otherFooterSettings: { contact_us: footerContent.contact_us, show_reach: footerContent.show_reach, @@ -116,6 +126,7 @@ export default class Settings extends Component { socialMediaContent: { ...footerContent.social_media, }, + // navigation fields navigationSettings: { ...navigationContent, }, From 381cb6eca8f51b82a7bd3c6a0e201e7a9ee7374f Mon Sep 17 00:00:00 2001 From: Jie Hao Kwa Date: Tue, 8 Dec 2020 23:49:37 +0800 Subject: [PATCH 3/6] feat: util function for deep comparing objects This commit introduces a util function i found on stack overflow, https://stackoverflow.com/a/39813128, which deep compares two objects and identifies which attributes have changed. --- src/utils.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/utils.js b/src/utils.js index d3ba51f17..61b1db94d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -500,4 +500,26 @@ export const checkIsOutOfViewport = (bounding, posArr) => { out.right = bounding.right > (window.innerWidth || document.documentElement.clientWidth); return posArr.some((pos) => {return out[pos]}) +} + +export const getObjectDiff = (obj1, obj2) => { + // const diff = Object.keys(obj1).reduce((result, key) => { + // if (!obj2.hasOwnProperty(key)) { + // result.push(key); + // } else if (_.isEqual(obj1[key], obj2[key])) { + // const resultKeyIndex = result.indexOf(key); + // result.splice(resultKeyIndex, 1); + // } + // return result; + // }, Object.keys(obj2)); + + // return diff; + const allkeys = _.union(_.keys(obj1), _.keys(obj2)); + const difference = _.reduce(allkeys, function (result, key) { + if ( !_.isEqual(obj1[key], obj2[key]) ) { + result[key] = {obj1: obj1[key], obj2: obj2[key]} + } + return result; + }, {}); + return difference } \ No newline at end of file From 5d7c2e4aa48e4f0740d29af08be85c319f68adc3 Mon Sep 17 00:00:00 2001 From: Jie Hao Kwa Date: Tue, 8 Dec 2020 23:52:35 +0800 Subject: [PATCH 4/6] feat: display warnings if there are unsaved changes on settings page This commit deep compares the current settings values with the original loaded settings values and displays a warning if the user attempts to navigate away from the page when there are unsaved changes. --- src/layouts/Settings.jsx | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/layouts/Settings.jsx b/src/layouts/Settings.jsx index 57dff8de3..c6593f281 100644 --- a/src/layouts/Settings.jsx +++ b/src/layouts/Settings.jsx @@ -17,6 +17,7 @@ import { toast } from 'react-toastify'; import Toast from '../components/Toast'; import { DEFAULT_ERROR_TOAST_MSG, + getObjectDiff, } from '../utils' const stateFields = { @@ -107,15 +108,15 @@ export default class Settings extends Component { const originalState = { // config fields + // resources_name: configFieldsRequired.resources_name, + // url: configFieldsRequired.url, colors: configFieldsRequired.colors, favicon: configFieldsRequired.favicon, google_analytics: configFieldsRequired.google_analytics, facebook_pixel: configFieldsRequired.facebook_pixel, is_government: configFieldsRequired.is_government, - resources_name: configFieldsRequired.resources_name, shareicon: configFieldsRequired.shareicon, title: configFieldsRequired.title, - url: configFieldsRequired.url, // footer fields otherFooterSettings: { contact_us: footerContent.contact_us, @@ -416,7 +417,8 @@ export default class Settings extends Component { colors, socialMediaContent, otherFooterSettings, - navigationSettings: { logo }, + navigationSettings, + originalState, errors, } = this.state; const { 'primary-color': primaryColor, 'secondary-color': secondaryColor, 'media-colors': mediaColors } = colors; @@ -427,6 +429,25 @@ export default class Settings extends Component { } = colorPicker; const { location } = this.props; + // construct settings object for comparison with original state + const currentSettings = { + colors, + favicon, + facebook_pixel, + google_analytics, + is_government, + shareicon, + title, + otherFooterSettings, + socialMediaContent, + navigationSettings, + } + + let settingsStateDiff + if (originalState) { + settingsStateDiff = getObjectDiff(currentSettings, originalState) + } + // retrieve errors const hasConfigErrors = _.some([errors.favicon, errors.shareicon, errors.is_government, errors.facebook_pixel, errors.google_analytics]); const hasNavigationErrors = _.some([errors.navigationSettings.logo]) @@ -436,7 +457,10 @@ export default class Settings extends Component { const hasErrors = hasConfigErrors || hasNavigationErrors || hasColorErrors || hasMediaColorErrors || hasSocialMediaErrors; return ( <> -
+
{/* main bottom section */}