Skip to content

Commit 30a1d96

Browse files
authored
Disable save changes button unless settings have changed (#9386)
1 parent f330e3f commit 30a1d96

34 files changed

+175
-28
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: add
3+
4+
Disable save changes button until a setting has changed.

client/components/csv-export-modal/test/index.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe( 'RefundModal', () => {
4444

4545
mockUseSettings.mockReturnValue( {
4646
isLoading: false,
47+
isDirty: false,
4748
isSaving: false,
4849
saveSettings: ( a ) => a,
4950
} );

client/data/settings/hooks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ export const useSettings = () => {
328328
const isSaving = useSelect( ( select ) =>
329329
select( STORE_NAME ).isSavingSettings()
330330
);
331+
const isDirty = useSelect( ( select ) => select( STORE_NAME ).isDirty() );
331332

332333
const isLoading = useSelect( ( select ) => {
333334
select( STORE_NAME ).getSettings();
@@ -342,6 +343,7 @@ export const useSettings = () => {
342343
isLoading,
343344
saveSettings,
344345
isSaving,
346+
isDirty,
345347
};
346348
};
347349

client/data/settings/reducer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import ACTION_TYPES from './action-types';
77

88
const defaultState = {
9+
isDirty: false,
910
isSaving: false,
1011
savingError: null,
1112
data: {},
@@ -20,12 +21,14 @@ export const receiveSettings = (
2021
return {
2122
...state,
2223
data: action.data,
24+
isDirty: false,
2325
};
2426

2527
case ACTION_TYPES.SET_SETTINGS_VALUES:
2628
return {
2729
...state,
2830
savingError: null,
31+
isDirty: true,
2932
data: {
3033
...state.data,
3134
...action.payload,
@@ -35,13 +38,16 @@ export const receiveSettings = (
3538
case ACTION_TYPES.SET_IS_SAVING_SETTINGS:
3639
return {
3740
...state,
41+
isDirty:
42+
action.isSaving || action.error ? state.isDirty : false,
3843
isSaving: action.isSaving,
3944
savingError: action.error,
4045
};
4146

4247
case ACTION_TYPES.SET_SELECTED_PAYMENT_METHOD:
4348
return {
4449
...state,
50+
isDirty: true,
4551
data: {
4652
...state.data,
4753
enabled_payment_method_ids: state.data.enabled_payment_method_ids.concat(
@@ -53,6 +59,7 @@ export const receiveSettings = (
5359
case ACTION_TYPES.SET_UNSELECTED_PAYMENT_METHOD:
5460
return {
5561
...state,
62+
isDirty: true,
5663
data: {
5764
...state.data,
5865
enabled_payment_method_ids: state.data.enabled_payment_method_ids.filter(

client/data/settings/selectors.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export const isSavingSettings = ( state ) => {
4949
return getSettingsState( state ).isSaving || false;
5050
};
5151

52+
export const isDirty = ( state ) => {
53+
return getSettingsState( state ).isDirty || false;
54+
};
55+
5256
export const getAccountStatementDescriptor = ( state ) => {
5357
return getSettings( state ).account_statement_descriptor || '';
5458
};

client/data/settings/test/hooks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ describe( 'Settings hooks tests', () => {
177177
hasFinishedResolution: jest.fn(),
178178
isResolving: jest.fn(),
179179
isSavingSettings: jest.fn(),
180+
isDirty: jest.fn(),
180181
};
181182
} );
182183

client/data/settings/test/reducer.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe( 'Settings reducer tests', () => {
2828
isSaving: false,
2929
data: {},
3030
savingError: null,
31+
isDirty: false,
3132
} );
3233
} );
3334

@@ -60,6 +61,7 @@ describe( 'Settings reducer tests', () => {
6061

6162
test( 'leaves fields other than `data` unchanged', () => {
6263
const oldState = {
64+
isDirty: false,
6365
foo: 'bar',
6466
data: {
6567
baz: 'quux',
@@ -74,6 +76,7 @@ describe( 'Settings reducer tests', () => {
7476
const state = reducer( oldState, updateSettings( newSettings ) );
7577

7678
expect( state ).toEqual( {
79+
isDirty: false,
7780
foo: 'bar',
7881
data: {
7982
quuz: 'corge',
@@ -101,6 +104,7 @@ describe( 'Settings reducer tests', () => {
101104

102105
test( 'leaves other fields unchanged', () => {
103106
const oldState = {
107+
isDirty: false,
104108
foo: 'bar',
105109
isSaving: false,
106110
savingError: {},
@@ -112,6 +116,7 @@ describe( 'Settings reducer tests', () => {
112116
);
113117

114118
expect( state ).toEqual( {
119+
isDirty: false,
115120
foo: 'bar',
116121
savingError: null,
117122
isSaving: true,
@@ -137,6 +142,7 @@ describe( 'Settings reducer tests', () => {
137142

138143
test( 'leaves other fields unchanged', () => {
139144
const oldState = {
145+
isDirty: false,
140146
foo: 'bar',
141147
data: {
142148
is_manual_capture_enabled: false,
@@ -151,6 +157,7 @@ describe( 'Settings reducer tests', () => {
151157
);
152158

153159
expect( state ).toEqual( {
160+
isDirty: true,
154161
savingError: null,
155162
foo: 'bar',
156163
data: {
@@ -181,6 +188,7 @@ describe( 'Settings reducer tests', () => {
181188

182189
test( 'leaves other fields unchanged', () => {
183190
const oldState = {
191+
isDirty: false,
184192
foo: 'bar',
185193
data: {
186194
account_statement_descriptor: 'Statement',
@@ -195,6 +203,7 @@ describe( 'Settings reducer tests', () => {
195203
);
196204

197205
expect( state ).toEqual( {
206+
isDirty: true,
198207
foo: 'bar',
199208
savingError: null,
200209
data: {
@@ -224,6 +233,7 @@ describe( 'Settings reducer tests', () => {
224233

225234
test( 'leaves other fields unchanged', () => {
226235
const oldState = {
236+
isDirty: false,
227237
foo: 'bar',
228238
data: {
229239
is_payment_request_enabled: false,
@@ -238,6 +248,7 @@ describe( 'Settings reducer tests', () => {
238248
);
239249

240250
expect( state ).toEqual( {
251+
isDirty: true,
241252
foo: 'bar',
242253
savingError: null,
243254
data: {
@@ -271,6 +282,7 @@ describe( 'Settings reducer tests', () => {
271282

272283
test( 'leaves other fields unchanged', () => {
273284
const oldState = {
285+
isDirty: false,
274286
foo: 'bar',
275287
data: {
276288
payment_request_enabled_locations: initPaymentRequestState,
@@ -285,6 +297,7 @@ describe( 'Settings reducer tests', () => {
285297
);
286298

287299
expect( state ).toEqual( {
300+
isDirty: true,
288301
foo: 'bar',
289302
data: {
290303
payment_request_enabled_locations: enableAllpaymentRequestState,
@@ -350,6 +363,7 @@ describe( 'Settings reducer tests', () => {
350363
'leaves other fields unchanged `%j`',
351364
( setting ) => {
352365
const oldState = {
366+
isDirty: false,
353367
foo: 'bar',
354368
data: {
355369
[ setting.stateKey ]: setting.settingValue,
@@ -364,6 +378,7 @@ describe( 'Settings reducer tests', () => {
364378
);
365379

366380
expect( state ).toEqual( {
381+
isDirty: true,
367382
foo: 'bar',
368383
savingError: null,
369384
data: {
@@ -378,6 +393,7 @@ describe( 'Settings reducer tests', () => {
378393
describe( 'SET_IS_WOOPAY_ENABLED', () => {
379394
test( 'toggles `data.is_woopay_enabled`', () => {
380395
const oldState = {
396+
isDirty: true,
381397
data: {
382398
is_woopay_enabled: false,
383399
},
@@ -391,6 +407,7 @@ describe( 'Settings reducer tests', () => {
391407

392408
test( 'leaves other fields unchanged', () => {
393409
const oldState = {
410+
isDirty: false,
394411
foo: 'bar',
395412
data: {
396413
is_woopay_enabled: false,
@@ -402,6 +419,7 @@ describe( 'Settings reducer tests', () => {
402419
const state = reducer( oldState, updateIsWooPayEnabled( true ) );
403420

404421
expect( state ).toEqual( {
422+
isDirty: true,
405423
foo: 'bar',
406424
savingError: null,
407425
data: {
@@ -430,6 +448,7 @@ describe( 'Settings reducer tests', () => {
430448

431449
test( 'leaves other fields unchanged', () => {
432450
const oldState = {
451+
isDirty: false,
433452
foo: 'bar',
434453
data: {
435454
woopay_custom_message: '',
@@ -444,6 +463,7 @@ describe( 'Settings reducer tests', () => {
444463
);
445464

446465
expect( state ).toEqual( {
466+
isDirty: true,
447467
foo: 'bar',
448468
data: {
449469
woopay_custom_message: 'test',
@@ -469,6 +489,7 @@ describe( 'Settings reducer tests', () => {
469489

470490
test( 'leaves other fields unchanged', () => {
471491
const oldState = {
492+
isDirty: false,
472493
foo: 'bar',
473494
data: {
474495
woopay_store_logo: '',
@@ -480,6 +501,7 @@ describe( 'Settings reducer tests', () => {
480501
const state = reducer( oldState, updateWooPayStoreLogo( 'test' ) );
481502

482503
expect( state ).toEqual( {
504+
isDirty: true,
483505
foo: 'bar',
484506
data: {
485507
woopay_store_logo: 'test',

client/disputes/test/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ describe( 'Disputes list', () => {
176176
isLoading: false,
177177
isSaving: false,
178178
saveSettings: ( a ) => a,
179+
isDirty: false,
179180
} );
180181

181182
global.wcpaySettings = {

client/settings/fraud-protection/advanced-settings/cards/order-items-threshold.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp
3737
protectionSettingsUI,
3838
setProtectionSettingsUI,
3939
setProtectionSettingsChanged,
40+
setIsDirty,
4041
} = useContext( FraudPreventionSettingsContext );
4142

4243
const settingUI = useMemo(
@@ -97,7 +98,10 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp
9798
placeholder={ '0' }
9899
value={ minItemsCount }
99100
type="number"
100-
onChange={ setMinItemsCount }
101+
onChange={ ( value ) => {
102+
setMinItemsCount( value );
103+
setIsDirty( true );
104+
} }
101105
onKeyDown={ ( e ) =>
102106
/^[+-.,e]$/m.test( e.key ) && e.preventDefault()
103107
}
@@ -121,7 +125,10 @@ const OrderItemsThresholdCustomForm: React.FC< OrderItemsThresholdCustomFormProp
121125
placeholder={ '0' }
122126
type="number"
123127
value={ maxItemsCount }
124-
onChange={ setMaxItemsCount }
128+
onChange={ ( value ) => {
129+
setMaxItemsCount( value );
130+
setIsDirty( true );
131+
} }
125132
onKeyDown={ ( e ) =>
126133
/^[+-.,e]$/m.test( e.key ) && e.preventDefault()
127134
}

client/settings/fraud-protection/advanced-settings/cards/purchase-price-threshold.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo
5656
protectionSettingsUI,
5757
setProtectionSettingsUI,
5858
setProtectionSettingsChanged,
59+
setIsDirty,
5960
} = useContext( FraudPreventionSettingsContext );
6061

6162
const settingUI = useMemo(
@@ -111,7 +112,10 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo
111112
prefix={ currencySymbol }
112113
placeholder={ '0.00' }
113114
value={ minAmount.toString() }
114-
onChange={ ( val ) => setMinAmount( Number( val ) ) }
115+
onChange={ ( val ) => {
116+
setMinAmount( Number( val ) );
117+
setIsDirty( true );
118+
} }
115119
help={ __(
116120
'Leave blank for no limit',
117121
'woocommerce-payments'
@@ -130,7 +134,10 @@ const PurchasePriceThresholdCustomForm: React.FC< PurchasePriceThresholdCustomFo
130134
prefix={ currencySymbol }
131135
placeholder={ '0.00' }
132136
value={ maxAmount.toString() }
133-
onChange={ ( val ) => setMaxAmount( Number( val ) ) }
137+
onChange={ ( val ) => {
138+
setMaxAmount( Number( val ) );
139+
setIsDirty( true );
140+
} }
134141
help={ __(
135142
'Leave blank for no limit',
136143
'woocommerce-payments'

0 commit comments

Comments
 (0)