Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable save changes button unless settings have changed #9386

Merged
merged 14 commits into from
Sep 19, 2024

Conversation

mdmoore
Copy link
Member

@mdmoore mdmoore commented Sep 4, 2024

Fixes #9305

Changes proposed in this Pull Request

This ensures that the "Save Changes" button on WooPayments settings pages starts off disabled and only becomes active once a change is made, following the pattern in WooCommerce core settings. This functionality is achieved by managing a state variable that tracks whether any form elements have been modified. The hasChanges boolean is set to true whenever a form field is altered, enabling the Save button accordingly.

The PR includes a lot of changed files so it might be easier to review via each commit.

The settings pages changed are:

  1. WooPayments Settings - Payments > Settings
  2. Multi-currency Settings - WooCommerce > Settings > Multi-currency*
  3. Advanced Fraud Protection Settings - WooPayments > Settings > Fraud Protection > Advanced > Configure
  4. Express Checkouts - WooCommerce > Settings > Express Checkouts > Customize

Testing instructions

In general, testing should involve checking that the Save changes button is disabled by default, then testing that it becomes enabled when any setting is changed. This touches every setting so I think it's important to test all of them. The risk with missing one is that the settings couldn't be saved, which could be confusing to merchants.

WooPayments Settings

Check that the save button is enabled after changing each form field. The page will need to be refreshed after each change.

Advanced Fraud Protection Settings

  1. From the main WooPayments settings page, select Advanced fraud settings, then click Configure.
  2. Toggle each setting, checking that the Save Changes button is enabled.
  3. Toggle Purchase Price Threshold, save the changes, refresh the page and make changes to the input fields so that the Save Changes button is enabled.
  4. Repeat the above for the Order Items Threshold setting.

Multi-currency Settings

  1. Check that adding and removing currencies enable the Save Changes button, via both the Add/remove currencies button/modal and the delete icon.
  2. Click manage on any currency and check that the Save Button is enabled when changing form fields.

Express Checkouts

  1. Click customize on WooPay and check that changes to form fields are enabling the Save Changes button.
  2. Repeat the above with Apple Pay/Google Pay

  • Run npm run changelog to add a changelog file, choose patch to leave it empty if the change is not significant. You can add multiple changelog files in one PR by running this command a few times.
  • Covered with tests (or have a good reason not to test in description ☝️)
  • Tested on mobile (or does not apply)

Post merge

@botwoo
Copy link
Collaborator

botwoo commented Sep 4, 2024

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 9386 or branch name update/9305-settings-disable-save-changes-button in your-test.site/wp-admin/admin.php?page=jetpack-beta&plugin=woocommerce-payments

Option 2. Jurassic Ninja - available for logged-in A12s

🚀 Launch a JN site with this branch 🚀

ℹ️ Install this Tampermonkey script to get more options.


Build info:

  • Latest commit: 0ba210f
  • Build time: 2024-09-18 19:02:20 UTC

Note: the build is updated when a new commit is pushed to this PR.

Copy link
Contributor

github-actions bot commented Sep 4, 2024

Size Change: +409 B (0%)

Total Size: 1.33 MB

Filename Size Change
release/woocommerce-payments/dist/index.js 302 kB +101 B (0%)
release/woocommerce-payments/dist/multi-currency-switcher-block.js 60.6 kB +56 B (0%)
release/woocommerce-payments/dist/multi-currency.js 57.3 kB +80 B (0%)
release/woocommerce-payments/dist/order.js 42 kB +33 B (0%)
release/woocommerce-payments/dist/payment-gateways.js 38.4 kB +49 B (0%)
release/woocommerce-payments/dist/settings.js 223 kB +90 B (0%)
ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.08 kB
release/woocommerce-payments/assets/css/admin.rtl.css 1.08 kB
release/woocommerce-payments/assets/css/success.css 173 B
release/woocommerce-payments/assets/css/success.rtl.css 173 B
release/woocommerce-payments/dist/blocks-checkout-rtl.css 2.52 kB
release/woocommerce-payments/dist/blocks-checkout.css 2.52 kB
release/woocommerce-payments/dist/blocks-checkout.js 66.4 kB
release/woocommerce-payments/dist/cart-block.js 16.2 kB
release/woocommerce-payments/dist/cart.js 5.73 kB
release/woocommerce-payments/dist/checkout-rtl.css 927 B
release/woocommerce-payments/dist/checkout.css 927 B
release/woocommerce-payments/dist/checkout.js 32.2 kB
release/woocommerce-payments/dist/express-checkout-rtl.css 230 B
release/woocommerce-payments/dist/express-checkout.css 230 B
release/woocommerce-payments/dist/express-checkout.js 14.2 kB
release/woocommerce-payments/dist/frontend-tracks.js 858 B
release/woocommerce-payments/dist/index-rtl.css 39.2 kB
release/woocommerce-payments/dist/index.css 39.2 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.08 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 4.5 kB
release/woocommerce-payments/dist/multi-currency.css 4.5 kB
release/woocommerce-payments/dist/order-rtl.css 730 B
release/woocommerce-payments/dist/order.css 730 B
release/woocommerce-payments/dist/payment-gateways-rtl.css 1.35 kB
release/woocommerce-payments/dist/payment-gateways.css 1.35 kB
release/woocommerce-payments/dist/payment-request-rtl.css 230 B
release/woocommerce-payments/dist/payment-request.css 230 B
release/woocommerce-payments/dist/payment-request.js 13.6 kB
release/woocommerce-payments/dist/plugins-page-rtl.css 386 B
release/woocommerce-payments/dist/plugins-page.css 386 B
release/woocommerce-payments/dist/plugins-page.js 20.1 kB
release/woocommerce-payments/dist/product-details-rtl.css 433 B
release/woocommerce-payments/dist/product-details.css 436 B
release/woocommerce-payments/dist/product-details.js 11.5 kB
release/woocommerce-payments/dist/settings-rtl.css 11.6 kB
release/woocommerce-payments/dist/settings.css 11.5 kB
release/woocommerce-payments/dist/subscription-edit-page.js 703 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 20.2 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 730 B
release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 19.3 kB
release/woocommerce-payments/dist/tokenized-payment-request-rtl.css 230 B
release/woocommerce-payments/dist/tokenized-payment-request.css 230 B
release/woocommerce-payments/dist/tokenized-payment-request.js 14.4 kB
release/woocommerce-payments/dist/tos-rtl.css 235 B
release/woocommerce-payments/dist/tos.css 235 B
release/woocommerce-payments/dist/tos.js 21.8 kB
release/woocommerce-payments/dist/woopay-direct-checkout.js 6.14 kB
release/woocommerce-payments/dist/woopay-express-button.js 24 kB
release/woocommerce-payments/dist/woopay-rtl.css 4.54 kB
release/woocommerce-payments/dist/woopay.css 4.51 kB
release/woocommerce-payments/dist/woopay.js 71.3 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 625 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 814 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/jetpack-script-data.js 735 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/react-jsx-runtime.js 553 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.02 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/script-data.js 69 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/babel.config.js 163 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.css 2.45 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.js 14.2 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.rtl.css 2.45 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.js 280 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.rtl.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.css 625 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.js 333 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.rtl.css 626 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-users.js 417 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js 584 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.css 215 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css 721 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.js 412 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-users.js 621 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.04 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-empty-state.css 294 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 408 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.59 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 301 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 746 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 574 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 414 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 543 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 9.4 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.78 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.84 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 545 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.52 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.7 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/payment-method-restrictions.js 1.29 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/wcs-meta-boxes-order.js 507 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 358 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 428 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/view-subscription.js 1.38 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/wcs-cart.js 782 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.09 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.26 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 391 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.04 kB

compressed-size-action

@mdmoore
Copy link
Member Author

mdmoore commented Sep 6, 2024

While working on the Multi-Currency settings page today, I explored watching the data store for changes as trigger to enable the "Save Changes" button. This has a list of currencies that can become shorter or longer, so that seemed like a viable option. In the end it didn't work out because the list/data store is always change several times on init. So there's really no way to know what is the final loaded state of the list. Because of that, I opted to just manage state via the change events via the various form elements.

I thought this was finished but realized there's one more page under advanced Fraud settings.

@mdmoore mdmoore marked this pull request as ready for review September 11, 2024 22:48
@mdmoore mdmoore requested review from a team and frosso and removed request for a team September 11, 2024 22:49
Copy link
Contributor

@frosso frosso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdmoore while reviewing, I noticed that most of the actions that modify the "main" settings go through the ACTION_TYPES.SET_SETTINGS_VALUES action.
Which means that the reducer for the settings can mainly rely on this action to determine if the settings have been modified or not.
This should simplify the implementation by just relying on a isDirty (or hasChanges) flag in the setting's Redux store.

For multi-currency, the values are coming from local state - so another hasChanges/isDirty local state can be leveraged to flag when exchangeRateType/manualRate/priceCharmType change.

The "fraud protection" settings are... A bit messier, NGL. So what you have here (with a new value in the context provider to set when changes have been made) makes sense.
However, it could be further simplified by removing the hasChanges from the context value, since hasChanges is not consumed by any of the consumers of the context.

I made the changes in this POC because it was easier to showcase than to explain :D
I think the main advantage is that it touches less files and leverages the existing behavior from the redux store, resulting in just shy of half of the file changes. And if a new setting gets added onto the data store (which is probably unlikely), we don't need to manually call the setHasChanges action.

I'd like to get your input on it!

@@ -341,6 +344,8 @@ const FraudProtectionAdvancedSettingsPage: React.FC = () => {
setProtectionSettingsUI,
protectionSettingsChanged,
setProtectionSettingsChanged,
hasChanges,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice hasChanges to be used by any of the consumers of the fraud protections rules context.
Can it be removed from the context provider's value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @frosso! This is extremely helpful.

I didn't notice hasChanges to be used by any of the consumers of the fraud protections rules context.
Can it be removed from the context provider's value?

Yes, thanks for catching it!

I noticed that most of the actions that modify the "main" settings go through the ACTION_TYPES.SET_SETTINGS_VALUES action.
Which means that the reducer for the settings can mainly rely on this action to determine if the settings have been modified or not.
This should simplify the implementation by just relying on a isDirty (or hasChanges) flag in the setting's Redux store.

This is definitely preferred and what I was attempting with the multi-currency settings via the SET_CURRENCIES action. I abandoned that approach due to what I mentioned here. Basically, SET_CURRENCIES is the action dispatched when adding or removing currencies. But this action is dispatched as the component loads as well, so it wasn't reliable. I see that's not the case for the main settings, so that will work! I'll give this another look in the morning.

@mdmoore
Copy link
Member Author

mdmoore commented Sep 13, 2024

@frosso I was able to address your suggestions for the most part.

Main Settings
This now relying on the Redux store as you mentioned.

Multi-currency
Single Currency Settings
The single currency settings( clicking "manage" on any currency ) now use a local state. I was seeing some strange behavior when setting the state via the useEffects you recommend. For example, click “manage” on any currency and change to manual exchange rate, then save. From then forward, the save button will always be enabled after clicking “manage” to load the single currency settings again, even if you switch back to automatic rates and save. For this reason, I opted to set the state the individual onChange props.

Main Currency Settings
A local state works for the Store Settings section but the context is needed in order to react to currencies added via the modal as well as clicks on the delete button. It's not exactly necessary to enable the save here since added and removed currencies are already saved, however it feels strange to have a save button for some changes and not others. What's your opinion on that?

Fraud Settings
I've removed hasChanges/isDirty value now. Otherwise, it's the same, using the context.

@mdmoore mdmoore requested a review from frosso September 13, 2024 22:12
@mdmoore mdmoore force-pushed the update/9305-settings-disable-save-changes-button branch from 3f69a94 to 0554da1 Compare September 15, 2024 18:10
Copy link
Contributor

@frosso frosso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Single Currency Settings
The single currency settings( clicking "manage" on any currency ) now use a local state. I was seeing some strange behavior when setting the state via the useEffects you recommend [...] For this reason, I opted to set the state the individual onChange props.

Ah, gotcha - it does seem like I didn't account for something, in there.
That sounds good, using local state makes it more explicit :)

Main Currency Settings
A local state works for the Store Settings section but the context is needed in order to react to currencies added via the modal as well as clicks on the delete button. It's not exactly necessary to enable the save here since added and removed currencies are already saved, however it feels strange to have a save button for some changes and not others. What's your opinion on that?

It does seem that the "save changes" button is needed only for the "Store settings" section of the multi-currency screen.

This is the behavior I'm seeing:

Screen.Recording.2024-09-18.at.9.12.45.AM.mov

I think it could be a double-edged sword: on one hand, if a merchant adds/removes some currencies and the button becomes enabled, they might think that their changes haven't persisted and they could safely exit the page without having made any changes; on the other hand, it does make the page's behavior less consistent.

I don't think it's something we need to fix in this PR - the inconsistent behavior has been present for a long time.

But I'll point out that there are pages in WooCommerce that persist the changes immediately, keeping the "save changes" button in a disabled state.
The one that comes to mind is the page to manage shipping zones. When a new shipping zone is added/removed, the changes are persisted immediately.

Screen.Recording.2024-09-18.at.9.18.31.AM.mov

Whether that behavior is wanted or not, I think the multi-currency page would behave consistently with the shipping zone page if we keep the button disabled when a currency is added/removed :)

Copy link
Contributor

@frosso frosso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for entertaining my suggestions! I took a second pass at the code as well, looks good! Approving, since the other stuff is optional and up for debate :)

I did notice that the browser's "Changes you made may not be saved." dialog was still displayed even after the "save changes" button got disabled, but I don't think it was in the acceptance criteria.

Comment on lines 8 to 11
const MultiCurrencySettingsContext = createContext( {
isDirty: false,
setIsDirty: () => null,
} );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) it looks like these were added to make TS happy - maybe we should also add the other possible context values for consistency? (isSingleCurrencyScreenOpen, currencyCodeToShowSettingsFor, openSingleCurrencySettings, closeSingleCurrencySettings)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've emptied these out now, since we'll no longer use them 👍

@mdmoore
Copy link
Member Author

mdmoore commented Sep 19, 2024

if a merchant adds/removes some currencies and the button becomes enabled, they might think that their changes haven't persisted and they could safely exit the page without having made any changes

This is a good callout. I think this is a strong enough reason to leave it disabled for the already persisted changes. Your point on matching the behavior of other similar cases with settings helps as well. We can go with a local state and only worry about the "Store Settings".

I did notice that the browser's "Changes you made may not be saved." dialog was still displayed even after the "save changes" button got disabled

I noticed this as well. I looked into it and wasn't able to find a quick to solution to implement alongside the save button changes. It seems like adding a beforeunload event listener and event.preventDefault() is the common way to handle this, but that isn't working. I'll create another issue for this.

@mdmoore mdmoore added this pull request to the merge queue Sep 19, 2024
Merged via the queue into develop with commit 30a1d96 Sep 19, 2024
25 checks passed
@mdmoore mdmoore deleted the update/9305-settings-disable-save-changes-button branch September 19, 2024 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Save changes button on WooPayments pages are always active
3 participants