From 5afdc815efdf148cad0fc77454e4dd8091279c88 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 16:19:44 +0100 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=90=9B=20Enable=20the=20PayPal=20me?= =?UTF-8?q?thod=20after=20onboarding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This gateway should be active for every merchant right after onboarding, regardless of seller-type of onboarding choices. --- modules/ppcp-settings/src/Service/SettingsDataManager.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/src/Service/SettingsDataManager.php b/modules/ppcp-settings/src/Service/SettingsDataManager.php index 2feefc4ee..4af72b7bd 100644 --- a/modules/ppcp-settings/src/Service/SettingsDataManager.php +++ b/modules/ppcp-settings/src/Service/SettingsDataManager.php @@ -211,7 +211,8 @@ class SettingsDataManager { $this->payment_methods->toggle_method_state( $method['id'], false ); } - // Always enable Venmo and Pay Later. + // Always enable PayPal, Venmo and Pay Later. + $this->payment_methods->toggle_method_state( PayPalGateway::ID, true ); $this->payment_methods->toggle_method_state( 'venmo', true ); $this->payment_methods->toggle_method_state( 'pay-later', true ); From 838fd6a1a041ad6f958a3ce152324f0515c5fce5 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 17:45:05 +0100 Subject: [PATCH 02/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20new=20hook-p?= =?UTF-8?q?attern=20to=20payment=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/payment/hooks.js | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 253710580..7cbd81382 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -7,21 +7,38 @@ * @file */ -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { STORE_NAME } from './constants'; import { createHooksForStore } from '../utils'; +import { useMemo } from '@wordpress/element'; + +/** + * Single source of truth for access Redux details. + * + * This hook returns a stable API to access actions, selectors and special hooks to generate + * getter- and setters for transient or persistent properties. + * + * @return {{select, dispatch, useTransient, usePersistent}} Store data API. + */ +const useStoreData = () => { + const select = useSelect( ( selectors ) => selectors( STORE_NAME ), [] ); + const dispatch = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + return useMemo( + () => ( { + select, + dispatch, + useTransient, + usePersistent, + } ), + [ select, dispatch, useTransient, usePersistent ] + ); +}; const useHooks = () => { - const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); - const { persist, setPersistent, changePaymentSettings } = - useDispatch( STORE_NAME ); - - // Read-only flags and derived state. - // Nothing here yet. - - // Transient accessors. - const [ isReady ] = useTransient( 'isReady' ); + const { usePersistent } = useStoreData(); // PayPal checkout. const [ paypal ] = usePersistent( 'ppcp-gateway' ); @@ -58,10 +75,6 @@ const useHooks = () => { ); return { - persist, - isReady, - setPersistent, - changePaymentSettings, paypal, venmo, payLater, @@ -88,9 +101,16 @@ const useHooks = () => { }; export const useStore = () => { - const { persist, isReady, setPersistent, changePaymentSettings } = - useHooks(); - return { persist, isReady, setPersistent, changePaymentSettings }; + const { useTransient, dispatch } = useStoreData(); + const { persist, setPersistent, changePaymentSettings } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { + persist, + setPersistent, + changePaymentSettings, + isReady, + }; }; export const usePaymentMethods = () => { From 3f5f587e888abd68469f995c98774e5c5c5aa6d1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 17:45:31 +0100 Subject: [PATCH 03/23] =?UTF-8?q?=E2=9C=A8=20First=20store-refresh=20actio?= =?UTF-8?q?n=20in=20payment=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/payment/actions.js | 13 +++++++++++++ .../resources/js/data/payment/hooks.js | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/payment/actions.js b/modules/ppcp-settings/resources/js/data/payment/actions.js index 1107d5bfb..d8398795a 100644 --- a/modules/ppcp-settings/resources/js/data/payment/actions.js +++ b/modules/ppcp-settings/resources/js/data/payment/actions.js @@ -94,3 +94,16 @@ export function persist() { } ); }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStoreSelector( 'persistentData' ); + + select.persistentData(); + }; +} diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 7cbd81382..5a330f1b2 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -102,11 +102,12 @@ const useHooks = () => { export const useStore = () => { const { useTransient, dispatch } = useStoreData(); - const { persist, setPersistent, changePaymentSettings } = dispatch; + const { persist, refresh, setPersistent, changePaymentSettings } = dispatch; const [ isReady ] = useTransient( 'isReady' ); return { persist, + refresh, setPersistent, changePaymentSettings, isReady, From 10527e507ace36f57e36baea94846efd5fee8ab4 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 17:45:45 +0100 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=8E=A8=20Comments=20and=20white=20s?= =?UTF-8?q?pace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/payment/reducer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-settings/resources/js/data/payment/reducer.js b/modules/ppcp-settings/resources/js/data/payment/reducer.js index d46106f6b..4da85fa4e 100644 --- a/modules/ppcp-settings/resources/js/data/payment/reducer.js +++ b/modules/ppcp-settings/resources/js/data/payment/reducer.js @@ -19,6 +19,7 @@ const defaultTransient = Object.freeze( { // Persistent: Values that are loaded from the DB. const defaultPersistent = Object.freeze( { + // Payment methods. 'ppcp-gateway': {}, venmo: {}, 'pay-later': {}, @@ -37,6 +38,8 @@ const defaultPersistent = Object.freeze( { 'ppcp-multibanco': {}, 'ppcp-pay-upon-invoice-gateway': {}, 'ppcp-oxxo-gateway': {}, + + // Custom payment method properties. paypalShowLogo: false, threeDSecure: 'no-3d-secure', fastlaneCardholderName: false, From b2b0bb8d7af0bd1a7f9ba6c7acd63239b02cb220 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 17:49:35 +0100 Subject: [PATCH 05/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Convert=20payment-st?= =?UTF-8?q?ore=20hooks=20to=20new=20code=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/payment/hooks.js | 109 ++++-------------- 1 file changed, 25 insertions(+), 84 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 5a330f1b2..582fd86f6 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -37,7 +37,21 @@ const useStoreData = () => { ); }; -const useHooks = () => { +export const useStore = () => { + const { useTransient, dispatch } = useStoreData(); + const { persist, refresh, setPersistent, changePaymentSettings } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { + persist, + refresh, + setPersistent, + changePaymentSettings, + isReady, + }; +}; + +export const usePaymentMethods = () => { const { usePersistent } = useStoreData(); // PayPal checkout. @@ -64,83 +78,6 @@ const useHooks = () => { const [ pui ] = usePersistent( 'ppcp-pay-upon-invoice-gateway' ); const [ oxxo ] = usePersistent( 'ppcp-oxxo-gateway' ); - // Custom modal data. - const [ paypalShowLogo ] = usePersistent( 'paypalShowLogo' ); - const [ threeDSecure ] = usePersistent( 'threeDSecure' ); - const [ fastlaneCardholderName ] = usePersistent( - 'fastlaneCardholderName' - ); - const [ fastlaneDisplayWatermark ] = usePersistent( - 'fastlaneDisplayWatermark' - ); - - return { - paypal, - venmo, - payLater, - creditCard, - advancedCreditCard, - fastlane, - applePay, - googlePay, - bancontact, - blik, - eps, - ideal, - mybank, - p24, - trustly, - multibanco, - pui, - oxxo, - paypalShowLogo, - threeDSecure, - fastlaneCardholderName, - fastlaneDisplayWatermark, - }; -}; - -export const useStore = () => { - const { useTransient, dispatch } = useStoreData(); - const { persist, refresh, setPersistent, changePaymentSettings } = dispatch; - const [ isReady ] = useTransient( 'isReady' ); - - return { - persist, - refresh, - setPersistent, - changePaymentSettings, - isReady, - }; -}; - -export const usePaymentMethods = () => { - const { - // PayPal Checkout. - paypal, - venmo, - payLater, - creditCard, - - // Online card payments. - advancedCreditCard, - fastlane, - applePay, - googlePay, - - // Local APMs. - bancontact, - blik, - eps, - ideal, - mybank, - p24, - trustly, - multibanco, - pui, - oxxo, - } = useHooks(); - const payPalCheckout = [ paypal, venmo, payLater, creditCard ]; const onlineCardPayments = [ advancedCreditCard, @@ -190,12 +127,16 @@ export const usePaymentMethods = () => { }; export const usePaymentMethodsModal = () => { - const { - paypalShowLogo, - threeDSecure, - fastlaneCardholderName, - fastlaneDisplayWatermark, - } = useHooks(); + const { usePersistent } = useStoreData(); + + const [ paypalShowLogo ] = usePersistent( 'paypalShowLogo' ); + const [ threeDSecure ] = usePersistent( 'threeDSecure' ); + const [ fastlaneCardholderName ] = usePersistent( + 'fastlaneCardholderName' + ); + const [ fastlaneDisplayWatermark ] = usePersistent( + 'fastlaneDisplayWatermark' + ); return { paypalShowLogo, From fe0f17a9ce7d016ef110cb97c87bdfa7764e35f5 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 14 Feb 2025 17:55:45 +0100 Subject: [PATCH 06/23] =?UTF-8?q?=E2=9C=A8=20Create=20refresh=20actions=20?= =?UTF-8?q?for=20all=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/_example/actions.js | 13 ++++++++++++ .../resources/js/data/common/actions-thunk.js | 13 ++++++++++++ .../js/data/pay-later-messaging/actions.js | 13 ++++++++++++ .../resources/js/data/payment/actions.js | 2 +- .../resources/js/data/settings/actions.js | 13 ++++++++++++ .../resources/js/data/styling/actions.js | 13 ++++++++++++ .../resources/js/data/todos/actions.js | 21 ++++++++++++++++++- 7 files changed, 86 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/_example/actions.js b/modules/ppcp-settings/resources/js/data/_example/actions.js index f9cd9a556..51b516bb1 100644 --- a/modules/ppcp-settings/resources/js/data/_example/actions.js +++ b/modules/ppcp-settings/resources/js/data/_example/actions.js @@ -82,3 +82,16 @@ export function persist() { } ); }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} diff --git a/modules/ppcp-settings/resources/js/data/common/actions-thunk.js b/modules/ppcp-settings/resources/js/data/common/actions-thunk.js index 8e7448af8..d49aa914a 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions-thunk.js +++ b/modules/ppcp-settings/resources/js/data/common/actions-thunk.js @@ -27,6 +27,19 @@ export function persist() { }; } +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} + /** * Side effect. Fetches the ISU-login URL for a sandbox account. * diff --git a/modules/ppcp-settings/resources/js/data/pay-later-messaging/actions.js b/modules/ppcp-settings/resources/js/data/pay-later-messaging/actions.js index f9cd9a556..51b516bb1 100644 --- a/modules/ppcp-settings/resources/js/data/pay-later-messaging/actions.js +++ b/modules/ppcp-settings/resources/js/data/pay-later-messaging/actions.js @@ -82,3 +82,16 @@ export function persist() { } ); }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} diff --git a/modules/ppcp-settings/resources/js/data/payment/actions.js b/modules/ppcp-settings/resources/js/data/payment/actions.js index d8398795a..dfa100f76 100644 --- a/modules/ppcp-settings/resources/js/data/payment/actions.js +++ b/modules/ppcp-settings/resources/js/data/payment/actions.js @@ -102,7 +102,7 @@ export function persist() { */ export function refresh() { return ( { dispatch, select } ) => { - dispatch.invalidateResolutionForStoreSelector( 'persistentData' ); + dispatch.invalidateResolutionForStore(); select.persistentData(); }; diff --git a/modules/ppcp-settings/resources/js/data/settings/actions.js b/modules/ppcp-settings/resources/js/data/settings/actions.js index 6633516bb..f99adae5a 100644 --- a/modules/ppcp-settings/resources/js/data/settings/actions.js +++ b/modules/ppcp-settings/resources/js/data/settings/actions.js @@ -84,3 +84,16 @@ export function persist() { } ); }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} diff --git a/modules/ppcp-settings/resources/js/data/styling/actions.js b/modules/ppcp-settings/resources/js/data/styling/actions.js index 9e1639c7f..72d4d9ea7 100644 --- a/modules/ppcp-settings/resources/js/data/styling/actions.js +++ b/modules/ppcp-settings/resources/js/data/styling/actions.js @@ -82,3 +82,16 @@ export function persist() { } ); }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} diff --git a/modules/ppcp-settings/resources/js/data/todos/actions.js b/modules/ppcp-settings/resources/js/data/todos/actions.js index 41ac372cd..864fe4173 100644 --- a/modules/ppcp-settings/resources/js/data/todos/actions.js +++ b/modules/ppcp-settings/resources/js/data/todos/actions.js @@ -39,6 +39,7 @@ export const setCompletedTodos = ( completedTodos ) => ( { // Thunks +// TODO: Possibly, this should be a resolver? export function fetchTodos() { return async () => { const response = await apiFetch( { path: REST_PATH } ); @@ -46,9 +47,14 @@ export function fetchTodos() { }; } +/** + * Thunk action creator. Triggers the persistence of store data to the server. + * + * @return {Function} The thunk function. + */ export function persist() { return async ( { select } ) => { - return await apiFetch( { + await apiFetch( { path: REST_PERSIST_PATH, method: 'POST', data: select.persistentData(), @@ -56,6 +62,19 @@ export function persist() { }; } +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} + export function resetDismissedTodos() { return async ( { dispatch } ) => { try { From 512574a9ad5f487f7d4602434dcfbf4bab6ec2b0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:18:15 +0100 Subject: [PATCH 07/23] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Impro?= =?UTF-8?q?ve=20debug=20tools?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-settings/resources/js/data/debug.js | 94 +++++++++++++++---- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/debug.js b/modules/ppcp-settings/resources/js/data/debug.js index 88bdf40ae..66530d7ec 100644 --- a/modules/ppcp-settings/resources/js/data/debug.js +++ b/modules/ppcp-settings/resources/js/data/debug.js @@ -18,6 +18,11 @@ export const addDebugTools = ( context, modules ) => { if ( ! context.debug ) { return } */ + const describe = ( fnName, fnInfo ) => { + // eslint-disable-next-line no-console + console.log( `\n%c${ fnName }:`, 'font-weight:bold', fnInfo, '\n\n' ); + }; + const debugApi = ( window.ppcpDebugger = window.ppcpDebugger || {} ); // Dump the current state of all our Redux stores. @@ -51,45 +56,89 @@ export const addDebugTools = ( context, modules ) => { // Reset all Redux stores to their initial state. debugApi.resetStore = () => { const stores = []; - const { isConnected } = wp.data.select( CommonStoreName ).merchant(); - if ( isConnected ) { - // Make sure the Onboarding wizard is "completed". - const onboarding = wp.data.dispatch( OnboardingStoreName ); - onboarding.setPersistent( 'completed', true ); - onboarding.persist(); + describe( + 'resetStore', + 'Reset all Redux stores to their DEFAULT state, without changing any server-side data. The default state is defined in the JS code.' + ); - // Reset all stores, except for the onboarding store. - stores.push( CommonStoreName ); - stores.push( PaymentStoreName ); - stores.push( SettingsStoreName ); - stores.push( StylingStoreName ); - stores.push( TodosStoreName ); - } else { - // Only reset the common & onboarding stores to restart the onboarding wizard. - stores.push( CommonStoreName ); + const { completed } = wp.data + .select( OnboardingStoreName ) + .persistentData(); + + // Reset all stores, except for the onboarding store. + stores.push( CommonStoreName ); + stores.push( PaymentStoreName ); + stores.push( SettingsStoreName ); + stores.push( StylingStoreName ); + stores.push( TodosStoreName ); + + // Only reset the onboarding store when the wizard is not completed. + if ( ! completed ) { stores.push( OnboardingStoreName ); } stores.forEach( ( storeName ) => { const store = wp.data.dispatch( storeName ); - // eslint-disable-next-line no-console - console.log( `Reset store: ${ storeName }...` ); - try { store.reset(); - store.persist(); + + // eslint-disable-next-line no-console + console.log( `Done: Store '${ storeName }' reset` ); } catch ( error ) { - console.error( ' ... Reset failed, skipping this store' ); + console.error( + `Failed: Could not reset store '${ storeName }'` + ); } } ); + + // eslint-disable-next-line no-console + console.log( '---- Complete ----\n\n' ); + }; + + debugApi.refreshStore = () => { + const stores = []; + + describe( + 'refreshStore', + 'Refreshes all Redux details with details provided by the server. This has a similar effect as reloading the page without saving' + ); + + stores.push( CommonStoreName ); + stores.push( PaymentStoreName ); + stores.push( SettingsStoreName ); + stores.push( StylingStoreName ); + stores.push( TodosStoreName ); + stores.push( OnboardingStoreName ); + + stores.forEach( ( storeName ) => { + const store = wp.data.dispatch( storeName ); + + try { + store.refresh(); + + // eslint-disable-next-line no-console + console.log( + `Done: Store '${ storeName }' refreshed from REST` + ); + } catch ( error ) { + console.error( + `Failed: Could not refresh store '${ storeName }' from REST` + ); + } + } ); + + // eslint-disable-next-line no-console + console.log( '---- Complete ----\n\n' ); }; // Disconnect the merchant and display the onboarding wizard. debugApi.disconnect = () => { const common = wp.data.dispatch( CommonStoreName ); + describe(); + common.disconnectMerchant(); // eslint-disable-next-line no-console @@ -102,6 +151,11 @@ export const addDebugTools = ( context, modules ) => { debugApi.onboardingMode = ( state ) => { const onboarding = wp.data.dispatch( OnboardingStoreName ); + describe( + 'onboardingMode', + 'Toggle between onboarding wizard and the settings screen.' + ); + onboarding.setPersistent( 'completed', ! state ); onboarding.persist(); }; From 2d3b37ee17a64455d606505f2be240e6cc0cc66c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:18:34 +0100 Subject: [PATCH 08/23] =?UTF-8?q?=E2=9C=A8=20Add=20missing=20refresh=20act?= =?UTF-8?q?ion=20to=20onboarding=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/onboarding/actions.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/modules/ppcp-settings/resources/js/data/onboarding/actions.js b/modules/ppcp-settings/resources/js/data/onboarding/actions.js index 0d2617095..70967168d 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/actions.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/actions.js @@ -87,3 +87,16 @@ export function persist() { } }; } + +/** + * Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values. + * + * @return {Function} The thunk function. + */ +export function refresh() { + return ( { dispatch, select } ) => { + dispatch.invalidateResolutionForStore(); + + select.persistentData(); + }; +} From 85360ab7f1cb2395d53f1fbc4e58f918183d8b4f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:20:00 +0100 Subject: [PATCH 09/23] =?UTF-8?q?=E2=9C=A8=20Add=20refresh=20hook=20to=20s?= =?UTF-8?q?ample=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/_example/hooks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-settings/resources/js/data/_example/hooks.js b/modules/ppcp-settings/resources/js/data/_example/hooks.js index 21253d137..9df5d43d3 100644 --- a/modules/ppcp-settings/resources/js/data/_example/hooks.js +++ b/modules/ppcp-settings/resources/js/data/_example/hooks.js @@ -39,9 +39,10 @@ const useStoreData = () => { export const useStore = () => { const { dispatch, useTransient } = useStoreData(); + const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); - return { persist: dispatch.persist, isReady }; + return { persist, refresh, isReady }; }; // TODO: Replace with real hook. From 161df3a85f0271eb61eb4dc4e5c80f66bf06bff9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:21:56 +0100 Subject: [PATCH 10/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Convert=20common=20h?= =?UTF-8?q?ooks=20to=20new=20code=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/common/hooks.js | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index 76a12cd2b..9a43989c1 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -13,8 +13,32 @@ import { useCallback, useEffect, useMemo, useState } from '@wordpress/element'; import { createHooksForStore } from '../utils'; import { STORE_NAME } from './constants'; -const useHooks = () => { +/** + * Single source of truth for access Redux details. + * + * This hook returns a stable API to access actions, selectors and special hooks to generate + * getter- and setters for transient or persistent properties. + * + * @return {{select, dispatch, useTransient, usePersistent}} Store data API. + */ +const useStoreData = () => { + const select = useSelect( ( selectors ) => selectors( STORE_NAME ), [] ); + const dispatch = useDispatch( STORE_NAME ); const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + return useMemo( + () => ( { + select, + dispatch, + useTransient, + usePersistent, + } ), + [ select, dispatch, useTransient, usePersistent ] + ); +}; + +const useHooks = () => { + const { useTransient, usePersistent, dispatch, select } = useStoreData(); const { persist, sandboxOnboardingUrl, @@ -23,7 +47,7 @@ const useHooks = () => { authenticateWithOAuth, startWebhookSimulation, checkWebhookSimulationState, - } = useDispatch( STORE_NAME ); + } = dispatch; // Transient accessors. const [ isReady ] = useTransient( 'isReady' ); @@ -38,18 +62,9 @@ const useHooks = () => { ); // Read-only properties. - const wooSettings = useSelect( - ( select ) => select( STORE_NAME ).wooSettings(), - [] - ); - const features = useSelect( - ( select ) => select( STORE_NAME ).features(), - [] - ); - const webhooks = useSelect( - ( select ) => select( STORE_NAME ).webhooks(), - [] - ); + const wooSettings = select.wooSettings(); + const features = select.features(); + const webhooks = select.webhooks(); const savePersistent = async ( setter, value ) => { setter( value ); @@ -82,6 +97,14 @@ const useHooks = () => { }; }; +export const useStore = () => { + const { dispatch, useTransient } = useStoreData(); + const { persist, refresh } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { persist, refresh, isReady }; +}; + export const useSandbox = () => { const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks(); @@ -204,7 +227,9 @@ export const useActiveHighlight = () => { return { activeHighlight, setActiveHighlight }; }; -// -- Not using the `useHooks()` data provider -- +/* + * Busy state management hooks + */ export const useBusyState = () => { const { startActivity, stopActivity } = useDispatch( STORE_NAME ); From 0867a3cc6360f3b3d8dc7200ee2256d655fcd6d9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:24:12 +0100 Subject: [PATCH 11/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Remove=20duplicate?= =?UTF-8?q?=20=E2=80=9Ccommon.isReady=E2=80=9D=20accessor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/Components/App.js | 2 +- .../js/Components/Screens/Settings/Tabs/TabOverview.js | 2 +- modules/ppcp-settings/resources/js/data/common/hooks.js | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/App.js b/modules/ppcp-settings/resources/js/Components/App.js index 1febe8674..6441d0a51 100644 --- a/modules/ppcp-settings/resources/js/Components/App.js +++ b/modules/ppcp-settings/resources/js/Components/App.js @@ -11,8 +11,8 @@ import { getQuery } from '../utils/navigation'; const SettingsApp = () => { const { isReady: onboardingIsReady, completed: onboardingCompleted } = OnboardingHooks.useSteps(); + const { isReady: merchantIsReady } = CommonHooks.useStore(); const { - isReady: merchantIsReady, merchant: { isSendOnlyCountry }, } = CommonHooks.useMerchantInfo(); diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js index a5a1014e6..de1c16971 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js @@ -28,7 +28,7 @@ import SpinnerOverlay from '../../../ReusableComponents/SpinnerOverlay'; const TabOverview = () => { const { isReady: areTodosReady } = TodosHooks.useTodos(); - const { isReady: merchantIsReady } = CommonHooks.useMerchantInfo(); + const { isReady: merchantIsReady } = CommonHooks.useStore(); if ( ! areTodosReady || ! merchantIsReady ) { return ; diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index 9a43989c1..49e5736a1 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -50,7 +50,6 @@ const useHooks = () => { } = dispatch; // Transient accessors. - const [ isReady ] = useTransient( 'isReady' ); const [ activeModal, setActiveModal ] = useTransient( 'activeModal' ); const [ activeHighlight, setActiveHighlight ] = useTransient( 'activeHighlight' ); @@ -72,7 +71,6 @@ const useHooks = () => { }; return { - isReady, activeModal, setActiveModal, activeHighlight, @@ -162,7 +160,7 @@ export const useWebhooks = () => { }; export const useMerchantInfo = () => { - const { isReady, features } = useHooks(); + const { features } = useHooks(); const merchant = useMerchant(); const { refreshMerchantData, setMerchant } = useDispatch( STORE_NAME ); @@ -187,7 +185,6 @@ export const useMerchantInfo = () => { }, [ refreshMerchantData, setMerchant ] ); return { - isReady, merchant, // Merchant details features, // Eligible merchant features verifyLoginStatus, // Callback From a9bba9fb92e86227fa9decee4bcb7ecf80949ba3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:28:35 +0100 Subject: [PATCH 12/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Convert=20settings?= =?UTF-8?q?=20hooks=20to=20new=20code=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/settings/hooks.js | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/settings/hooks.js b/modules/ppcp-settings/resources/js/data/settings/hooks.js index f161ee48e..a79f1fc6d 100644 --- a/modules/ppcp-settings/resources/js/data/settings/hooks.js +++ b/modules/ppcp-settings/resources/js/data/settings/hooks.js @@ -6,17 +6,38 @@ * * @file */ -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { STORE_NAME } from './constants'; import { createHooksForStore } from '../utils'; +import { useMemo } from '@wordpress/element'; + +/** + * Single source of truth for access Redux details. + * + * This hook returns a stable API to access actions, selectors and special hooks to generate + * getter- and setters for transient or persistent properties. + * + * @return {{select, dispatch, useTransient, usePersistent}} Store data API. + */ +const useStoreData = () => { + const select = useSelect( ( selectors ) => selectors( STORE_NAME ), [] ); + const dispatch = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + return useMemo( + () => ( { + select, + dispatch, + useTransient, + usePersistent, + } ), + [ select, dispatch, useTransient, usePersistent ] + ); +}; const useHooks = () => { - const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); - const { persist } = useDispatch( STORE_NAME ); - - // Read-only flags and derived state. - const [ isReady ] = useTransient( 'isReady' ); + const { usePersistent } = useStoreData(); // Persistent accessors. const [ invoicePrefix, setInvoicePrefix ] = @@ -47,8 +68,6 @@ const useHooks = () => { usePersistent( 'disabledCards' ); return { - persist, - isReady, invoicePrefix, setInvoicePrefix, authorizeOnly, @@ -79,8 +98,11 @@ const useHooks = () => { }; export const useStore = () => { - const { persist, isReady } = useHooks(); - return { persist, isReady }; + const { dispatch, useTransient } = useStoreData(); + const { persist, refresh } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { persist, refresh, isReady }; }; export const useSettings = () => { From 1bebfe5e62484a0b99912d884e1ad78a5ff97b67 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:31:21 +0100 Subject: [PATCH 13/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Convert=20styling=20?= =?UTF-8?q?hooks=20to=20new=20code=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/styling/hooks.js | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index 8227d0124..a9d7bcbd6 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -7,7 +7,7 @@ * @file */ -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { createHooksForStore } from '../utils'; @@ -21,12 +21,35 @@ import { STYLING_SHAPES, } from './configuration'; +/** + * Single source of truth for access Redux details. + * + * This hook returns a stable API to access actions, selectors and special hooks to generate + * getter- and setters for transient or persistent properties. + * + * @return {{select, dispatch, useTransient, usePersistent}} Store data API. + */ +const useStoreData = () => { + const select = useSelect( ( selectors ) => selectors( STORE_NAME ), [] ); + const dispatch = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + return useMemo( + () => ( { + select, + dispatch, + useTransient, + usePersistent, + } ), + [ select, dispatch, useTransient, usePersistent ] + ); +}; + const useHooks = () => { - const { useTransient } = createHooksForStore( STORE_NAME ); - const { persist, setPersistent } = useDispatch( STORE_NAME ); + const { useTransient, dispatch } = useStoreData(); + const { setPersistent } = dispatch; // Transient accessors. - const [ isReady ] = useTransient( 'isReady' ); const [ location, setLocation ] = useTransient( 'location' ); // Persistent accessors. @@ -61,8 +84,6 @@ const useHooks = () => { ); return { - persist, - isReady, location, setLocation, getLocationProp, @@ -71,8 +92,11 @@ const useHooks = () => { }; export const useStore = () => { - const { persist, isReady } = useHooks(); - return { persist, isReady }; + const { dispatch, useTransient } = useStoreData(); + const { persist, refresh } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { persist, refresh, isReady }; }; export const useStylingLocation = () => { From e5f83756ab7b71d8219e256317bacfcb4fa5013a Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:33:00 +0100 Subject: [PATCH 14/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Convert=20todo-store?= =?UTF-8?q?=20hooks=20to=20new=20code=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/todos/hooks.js | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/todos/hooks.js b/modules/ppcp-settings/resources/js/data/todos/hooks.js index 7fc5da5c1..79fc2e19a 100644 --- a/modules/ppcp-settings/resources/js/data/todos/hooks.js +++ b/modules/ppcp-settings/resources/js/data/todos/hooks.js @@ -10,6 +10,31 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { STORE_NAME } from './constants'; import { createHooksForStore } from '../utils'; +import { useMemo } from '@wordpress/element'; + +/** + * Single source of truth for access Redux details. + * + * This hook returns a stable API to access actions, selectors and special hooks to generate + * getter- and setters for transient or persistent properties. + * + * @return {{select, dispatch, useTransient, usePersistent}} Store data API. + */ +const useStoreData = () => { + const select = useSelect( ( selectors ) => selectors( STORE_NAME ), [] ); + const dispatch = useDispatch( STORE_NAME ); + const { useTransient, usePersistent } = createHooksForStore( STORE_NAME ); + + return useMemo( + () => ( { + select, + dispatch, + useTransient, + usePersistent, + } ), + [ select, dispatch, useTransient, usePersistent ] + ); +}; const ensureArray = ( value ) => { if ( ! value ) { @@ -19,12 +44,8 @@ const ensureArray = ( value ) => { }; const useHooks = () => { - const { useTransient } = createHooksForStore( STORE_NAME ); - const { fetchTodos, setDismissedTodos, setCompletedTodos, persist } = - useDispatch( STORE_NAME ); - - // Read-only flags and derived state. - const [ isReady ] = useTransient( 'isReady' ); + const { dispatch } = useStoreData(); + const { fetchTodos, setDismissedTodos, setCompletedTodos } = dispatch; // Get todos data from store const { todos, dismissedTodos, completedTodos } = useSelect( ( select ) => { @@ -62,8 +83,6 @@ const useHooks = () => { ); return { - persist, - isReady, todos: filteredTodos, dismissedTodos, completedTodos, @@ -74,8 +93,11 @@ const useHooks = () => { }; export const useStore = () => { - const { persist, isReady } = useHooks(); - return { persist, isReady }; + const { dispatch, useTransient } = useStoreData(); + const { persist, refresh } = dispatch; + const [ isReady ] = useTransient( 'isReady' ); + + return { persist, refresh, isReady }; }; export const useTodos = () => { From b9785d17055c0f60a7eee56e3f6a5023129f70b5 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 13:43:41 +0100 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Add?= =?UTF-8?q?=20simple=20filter-callback=20for=20dumpStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ppcp-settings/resources/js/data/debug.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/debug.js b/modules/ppcp-settings/resources/js/data/debug.js index 66530d7ec..286285892 100644 --- a/modules/ppcp-settings/resources/js/data/debug.js +++ b/modules/ppcp-settings/resources/js/data/debug.js @@ -26,7 +26,7 @@ export const addDebugTools = ( context, modules ) => { const debugApi = ( window.ppcpDebugger = window.ppcpDebugger || {} ); // Dump the current state of all our Redux stores. - debugApi.dumpStore = async () => { + debugApi.dumpStore = async ( cbFilter = null ) => { /* eslint-disable no-console */ if ( ! console?.groupCollapsed ) { console.error( 'console.groupCollapsed is not supported.' ); @@ -39,11 +39,19 @@ export const addDebugTools = ( context, modules ) => { console.group( `[STORE] ${ storeSelector }` ); const dumpStore = ( selector ) => { - const contents = wp.data.select( storeName )[ selector ](); + let contents = wp.data.select( storeName )[ selector ](); - console.groupCollapsed( `.${ selector }()` ); - console.table( contents ); - console.groupEnd(); + if ( cbFilter ) { + contents = cbFilter( contents, selector, storeName ); + + if ( undefined !== contents && null !== contents ) { + console.log( `.${ selector }() [filtered]`, contents ); + } + } else { + console.groupCollapsed( `.${ selector }()` ); + console.table( contents ); + console.groupEnd(); + } }; Object.keys( module.selectors ).forEach( dumpStore ); From 180e47fa0a68d5a8d50c098c82b04f4a33480daf Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:07:00 +0100 Subject: [PATCH 16/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20hooks=20in=20t?= =?UTF-8?q?he=20TabOverview=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Screens/Settings/Tabs/TabOverview.js | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js index de1c16971..238cc1c68 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js @@ -12,11 +12,12 @@ import { import { Content, ContentWrapper } from '../../../ReusableComponents/Elements'; import SettingsCard from '../../../ReusableComponents/SettingsCard'; import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge'; -import { useTodos } from '../../../../data/todos/hooks'; -import { useMerchantInfo } from '../../../../data/common/hooks'; -import { STORE_NAME as COMMON_STORE_NAME } from '../../../../data/common'; -import { STORE_NAME as TODOS_STORE_NAME } from '../../../../data/todos'; -import { CommonHooks, TodosHooks } from '../../../../data'; +import { + CommonStoreName, + TodosStoreName, + CommonHooks, + TodosHooks, +} from '../../../../data'; import { getFeatures } from '../Components/Overview/features-config'; @@ -27,9 +28,13 @@ import { import SpinnerOverlay from '../../../ReusableComponents/SpinnerOverlay'; const TabOverview = () => { - const { isReady: areTodosReady } = TodosHooks.useTodos(); + const { isReady: areTodosReady } = TodosHooks.useStore(); const { isReady: merchantIsReady } = CommonHooks.useStore(); + // TODO: Workaround before we implement the standard "persistentData" resolver. + // Calling this hook ensures that todos are loaded from the REST API at this point. + const {} = TodosHooks.useTodos(); + if ( ! areTodosReady || ! merchantIsReady ) { return ; } @@ -47,11 +52,12 @@ export default TabOverview; const OverviewTodos = () => { const [ isResetting, setIsResetting ] = useState( false ); - const { todos, isReady: areTodosReady, dismissTodo } = useTodos(); + const { todos, dismissTodo } = TodosHooks.useTodos(); + const { isReady: areTodosReady } = TodosHooks.useStore(); const { setActiveModal, setActiveHighlight } = - useDispatch( COMMON_STORE_NAME ); + useDispatch( CommonStoreName ); const { resetDismissedTodos, setDismissedTodos } = - useDispatch( TODOS_STORE_NAME ); + useDispatch( TodosStoreName ); const { createSuccessNotice } = useDispatch( noticesStore ); const showTodos = areTodosReady && todos.length > 0; @@ -118,9 +124,10 @@ const OverviewTodos = () => { const OverviewFeatures = () => { const [ isRefreshing, setIsRefreshing ] = useState( false ); - const { merchant, features: merchantFeatures } = useMerchantInfo(); + const { merchant, features: merchantFeatures } = + CommonHooks.useMerchantInfo(); const { refreshFeatureStatuses, setActiveModal } = - useDispatch( COMMON_STORE_NAME ); + useDispatch( CommonStoreName ); const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); From afcadb9508803f4eea1b516efaae4615a9e91161 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:08:07 +0100 Subject: [PATCH 17/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix=20hooks=20in=20t?= =?UTF-8?q?he=20TabOverview=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-settings/resources/js/data/todos/hooks.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/todos/hooks.js b/modules/ppcp-settings/resources/js/data/todos/hooks.js index 79fc2e19a..57172e24b 100644 --- a/modules/ppcp-settings/resources/js/data/todos/hooks.js +++ b/modules/ppcp-settings/resources/js/data/todos/hooks.js @@ -101,9 +101,8 @@ export const useStore = () => { }; export const useTodos = () => { - const { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady } = - useHooks(); - return { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady }; + const { todos, fetchTodos, dismissTodo, setTodoCompleted } = useHooks(); + return { todos, fetchTodos, dismissTodo, setTodoCompleted }; }; export const useDismissedTodos = () => { From 7185f5190ffe02aae6ecd4fd80086463386b072e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:08:47 +0100 Subject: [PATCH 18/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20data=20sanita?= =?UTF-8?q?tion=20logic=20into=20selector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/todos/hooks.js | 20 ++++--------------- .../resources/js/data/todos/selectors.js | 16 ++++++++++++--- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/todos/hooks.js b/modules/ppcp-settings/resources/js/data/todos/hooks.js index 57172e24b..f7bae751b 100644 --- a/modules/ppcp-settings/resources/js/data/todos/hooks.js +++ b/modules/ppcp-settings/resources/js/data/todos/hooks.js @@ -36,26 +36,14 @@ const useStoreData = () => { ); }; -const ensureArray = ( value ) => { - if ( ! value ) { - return []; - } - return Array.isArray( value ) ? value : Object.values( value ); -}; - const useHooks = () => { - const { dispatch } = useStoreData(); + const { dispatch, select } = useStoreData(); const { fetchTodos, setDismissedTodos, setCompletedTodos } = dispatch; // Get todos data from store - const { todos, dismissedTodos, completedTodos } = useSelect( ( select ) => { - const store = select( STORE_NAME ); - return { - todos: ensureArray( store.getTodos() ), - dismissedTodos: ensureArray( store.getDismissedTodos() ), - completedTodos: ensureArray( store.getCompletedTodos() ), - }; - }, [] ); + const todos = select.getTodos(); + const dismissedTodos = select.getDismissedTodos(); + const completedTodos = select.getCompletedTodos(); const dismissedSet = new Set( dismissedTodos ); diff --git a/modules/ppcp-settings/resources/js/data/todos/selectors.js b/modules/ppcp-settings/resources/js/data/todos/selectors.js index 2f42c6ffc..1066ba3a8 100644 --- a/modules/ppcp-settings/resources/js/data/todos/selectors.js +++ b/modules/ppcp-settings/resources/js/data/todos/selectors.js @@ -11,7 +11,17 @@ const EMPTY_OBJ = Object.freeze( {} ); const EMPTY_ARR = Object.freeze( [] ); const getState = ( state ) => state || EMPTY_OBJ; +const getArray = ( value ) => { + if ( Array.isArray( value ) ) { + return value; + } + if ( value ) { + return Object.values( value ); + } + return EMPTY_ARR; +}; +// TODO: Implement a persistentData resolver! export const persistentData = ( state ) => { return getState( state ).data || EMPTY_OBJ; }; @@ -23,15 +33,15 @@ export const transientData = ( state ) => { export const getTodos = ( state ) => { const todos = state?.todos || persistentData( state ).todos; - return todos || EMPTY_ARR; + return getArray( todos ); }; export const getDismissedTodos = ( state ) => { const dismissed = state?.dismissedTodos || persistentData( state ).dismissedTodos; - return dismissed || EMPTY_ARR; + return getArray( dismissed ); }; export const getCompletedTodos = ( state ) => { - return state?.completedTodos || EMPTY_ARR; // Only look at root state, not persistent data + return getArray( state?.completedTodos ); // Only look at root state, not persistent data }; From 2477e4d357a102858228eb45ca96ab21a8834004 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:09:12 +0100 Subject: [PATCH 19/23] =?UTF-8?q?=E2=9C=A8=20Add=20missing=20reset=20actio?= =?UTF-8?q?n=20to=20todo=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/todos/action-types.js | 6 +++ .../resources/js/data/todos/actions.js | 42 +++++++++++++++++-- .../resources/js/data/todos/reducer.js | 16 +++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/todos/action-types.js b/modules/ppcp-settings/resources/js/data/todos/action-types.js index 66d92218a..17bbe8a0f 100644 --- a/modules/ppcp-settings/resources/js/data/todos/action-types.js +++ b/modules/ppcp-settings/resources/js/data/todos/action-types.js @@ -5,6 +5,12 @@ */ export default { + /** + * Resets the store state to its initial values. + * Used when needing to clear all store data. + */ + RESET: 'ppcp/todos/RESET', + // Transient data SET_TRANSIENT: 'ppcp/todos/SET_TRANSIENT', SET_COMPLETED_TODOS: 'ppcp/todos/SET_COMPLETED_TODOS', diff --git a/modules/ppcp-settings/resources/js/data/todos/actions.js b/modules/ppcp-settings/resources/js/data/todos/actions.js index 864fe4173..25b6d7647 100644 --- a/modules/ppcp-settings/resources/js/data/todos/actions.js +++ b/modules/ppcp-settings/resources/js/data/todos/actions.js @@ -17,11 +17,47 @@ import { REST_RESET_DISMISSED_TODOS_PATH, } from './constants'; -export const setIsReady = ( isReady ) => ( { - type: ACTION_TYPES.SET_TRANSIENT, - payload: { isReady }, +/** + * Special. Resets all values in the store to initial defaults. + * + * @return {Object} The action. + */ +export const reset = () => ( { + type: ACTION_TYPES.RESET, } ); +/** + * Generic transient-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Object} The action. + */ +export const setTransient = ( prop, value ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { [ prop ]: value }, +} ); + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Object} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); + +/** + * Transient. Marks the store as "ready", i.e., fully initialized. + * + * @param {boolean} isReady Whether the store is ready + * @return {Object} The action. + */ +export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady ); + export const setTodos = ( todos ) => ( { type: ACTION_TYPES.SET_TODOS, payload: todos, diff --git a/modules/ppcp-settings/resources/js/data/todos/reducer.js b/modules/ppcp-settings/resources/js/data/todos/reducer.js index 6f4c74d46..f3cc412ac 100644 --- a/modules/ppcp-settings/resources/js/data/todos/reducer.js +++ b/modules/ppcp-settings/resources/js/data/todos/reducer.js @@ -52,6 +52,21 @@ const reducer = createReducer( defaultTransient, defaultPersistent, { [ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) => changeTransient( state, payload ), + /** + * Resets state to defaults while maintaining initialization status + * + * @param {Object} state Current state + * @return {Object} Reset state + */ + [ ACTION_TYPES.RESET ]: ( state ) => { + const cleanState = changeTransient( + changePersistent( state, defaultPersistent ), + defaultTransient + ); + cleanState.isReady = true; // Keep initialization flag + return cleanState; + }, + /** * Updates todos list * @@ -99,6 +114,7 @@ const reducer = createReducer( defaultTransient, defaultPersistent, { }, /** + * TODO: This is not used anywhere. Remove "SET_TODOS" and use this resolver instead. * Initializes persistent state with data from the server * * @param {Object} state Current state From 480d15a7fb127ba794e8c1fe5be3b56fb1c6551b Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:17:37 +0100 Subject: [PATCH 20/23] =?UTF-8?q?=E2=9C=A8=20Implement=20a=20full-store-re?= =?UTF-8?q?fresh=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/hooks/useSaveSettings.js | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js b/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js index 7237aa4df..5c51866e6 100644 --- a/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js +++ b/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js @@ -12,48 +12,41 @@ import { export const useSaveSettings = () => { const { withActivity } = CommonHooks.useBusyState(); - const { persist: persistPayment } = PaymentHooks.useStore(); - const { persist: persistSettings } = SettingsHooks.useStore(); - const { persist: persistStyling } = StylingHooks.useStore(); - const { persist: persistTodos } = TodosHooks.useStore(); - const { persist: persistPayLaterMessaging } = - PayLaterMessagingHooks.useStore(); + const paymentStore = PaymentHooks.useStore(); + const settingsStore = SettingsHooks.useStore(); + const stylingStore = StylingHooks.useStore(); + const todosStore = TodosHooks.useStore(); + const payLaterStore = PayLaterMessagingHooks.useStore(); - const persistActions = useMemo( + const storeActions = useMemo( () => [ { - key: 'persist-methods', - message: 'Save payment methods', - action: persistPayment, + key: 'methods', + message: 'Process payment methods', + store: paymentStore, }, { - key: 'persist-settings', - message: 'Save the settings', - action: persistSettings, + key: 'settings', + message: 'Process the settings', + store: settingsStore, }, { - key: 'persist-styling', - message: 'Save styling details', - action: persistStyling, + key: 'styling', + message: 'Process styling details', + store: stylingStore, }, { - key: 'persist-todos', - message: 'Save todos state', - action: persistTodos, + key: 'todos', + message: 'Process todos state', + store: todosStore, }, { - key: 'persist-pay-later-messaging', - message: 'Save pay later messaging details', - action: persistPayLaterMessaging, + key: 'pay-later-messaging', + message: 'Process pay later messaging details', + store: payLaterStore, }, ], - [ - persistPayLaterMessaging, - persistPayment, - persistSettings, - persistStyling, - persistTodos, - ] + [ payLaterStore, paymentStore, settingsStore, stylingStore, todosStore ] ); const persistAll = useCallback( () => { @@ -65,10 +58,19 @@ export const useSaveSettings = () => { */ document.getElementById( 'configurator-publishButton' )?.click(); - persistActions.forEach( ( { key, message, action } ) => { - withActivity( key, message, action ); + storeActions.forEach( ( { key, message, store } ) => { + withActivity( `persist-${ key }`, message, store.persist ); } ); - }, [ persistActions, withActivity ] ); + }, [ storeActions, withActivity ] ); - return { persistAll }; + const refreshAll = useCallback( () => { + storeActions.forEach( ( { key, message, store } ) => { + withActivity( `refresh-${ key }`, message, store.refresh ); + } ); + }, [ storeActions, withActivity ] ); + + return { + persistAll, + refreshAll, + }; }; From 68e3cad0e87ffbf2a137cefa99b3729f74b019ce Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:19:01 +0100 Subject: [PATCH 21/23] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20a=20hook=20as=20i?= =?UTF-8?q?ts=20responsibility=20changed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/Components/Screens/Settings/Components/Navigation.js | 4 ++-- .../js/hooks/{useSaveSettings.js => useStoreManager.js} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename modules/ppcp-settings/resources/js/hooks/{useSaveSettings.js => useStoreManager.js} (97%) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js index 6ea843c67..1b06eb222 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js @@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n'; import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import TopNavigation from '../../../ReusableComponents/TopNavigation'; -import { useSaveSettings } from '../../../../hooks/useSaveSettings'; +import { useStoreManager } from '../../../../hooks/useStoreManager'; import { CommonHooks } from '../../../../data'; import TabBar from '../../../ReusableComponents/TabBar'; import classNames from 'classnames'; @@ -20,7 +20,7 @@ const SettingsNavigation = ( { activePanel, setActivePanel, } ) => { - const { persistAll } = useSaveSettings(); + const { persistAll } = useStoreManager(); const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' ); diff --git a/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js b/modules/ppcp-settings/resources/js/hooks/useStoreManager.js similarity index 97% rename from modules/ppcp-settings/resources/js/hooks/useSaveSettings.js rename to modules/ppcp-settings/resources/js/hooks/useStoreManager.js index 5c51866e6..190b2ac88 100644 --- a/modules/ppcp-settings/resources/js/hooks/useSaveSettings.js +++ b/modules/ppcp-settings/resources/js/hooks/useStoreManager.js @@ -9,7 +9,7 @@ import { TodosHooks, } from '../data'; -export const useSaveSettings = () => { +export const useStoreManager = () => { const { withActivity } = CommonHooks.useBusyState(); const paymentStore = PaymentHooks.useStore(); From 274875ccdeac26b4afd64e3a41a43ad1ba0bdfa7 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:19:50 +0100 Subject: [PATCH 22/23] =?UTF-8?q?=E2=9C=A8=20Refresh=20Redux=20data=20afte?= =?UTF-8?q?r=20manual=20authentication?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/hooks/useHandleConnections.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js index e605b61ef..e98345149 100644 --- a/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js +++ b/modules/ppcp-settings/resources/js/hooks/useHandleConnections.js @@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from '@wordpress/element'; import { store as noticesStore } from '@wordpress/notices'; import { CommonHooks, OnboardingHooks } from '../data'; +import { useStoreManager } from './useStoreManager'; const PAYPAL_PARTNER_SDK_URL = 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js'; @@ -30,7 +31,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => { const { sandboxOnboardingUrl } = CommonHooks.useSandbox(); const { productionOnboardingUrl } = CommonHooks.useProduction(); const products = OnboardingHooks.useDetermineProducts(); - const { withActivity, startActivity } = CommonHooks.useBusyState(); + const { startActivity } = CommonHooks.useBusyState(); const { authenticateWithOAuth } = CommonHooks.useAuthentication(); const [ onboardingUrl, setOnboardingUrl ] = useState( '' ); const [ scriptLoaded, setScriptLoaded ] = useState( false ); @@ -134,7 +135,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => { // Ensure the onComplete handler is not removed by a PayPal init script. timerRef.current = setInterval( addHandler, 250 ); }, - [ authenticateWithOAuth, withActivity ] + [ authenticateWithOAuth, startActivity ] ); const removeCompleteHandler = useCallback( () => { @@ -161,6 +162,7 @@ const useConnectionBase = () => { useDispatch( noticesStore ); const { verifyLoginStatus } = CommonHooks.useMerchantInfo(); const { withActivity } = CommonHooks.useBusyState(); + const { refreshAll } = useStoreManager(); return { handleFailed: ( res, genericMessage ) => { @@ -178,6 +180,7 @@ const useConnectionBase = () => { if ( loginSuccessful ) { createSuccessNotice( MESSAGES.CONNECTED ); await setCompleted( true ); + refreshAll(); } else { createErrorNotice( MESSAGES.LOGIN_FAILED ); } From 845e6b876abf5b95a9de392f0baf2e75834caece Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 17 Feb 2025 14:37:49 +0100 Subject: [PATCH 23/23] =?UTF-8?q?=E2=9C=A8=20Load=20persistent=20data=20on?= =?UTF-8?q?=20first=20store=20access?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/Components/Screens/Settings/Tabs/TabOverview.js | 4 ---- modules/ppcp-settings/resources/js/data/_example/hooks.js | 7 ++++++- modules/ppcp-settings/resources/js/data/common/hooks.js | 7 ++++++- modules/ppcp-settings/resources/js/data/payment/hooks.js | 7 ++++++- modules/ppcp-settings/resources/js/data/settings/hooks.js | 7 ++++++- modules/ppcp-settings/resources/js/data/styling/hooks.js | 8 +++++++- modules/ppcp-settings/resources/js/data/todos/hooks.js | 7 ++++++- 7 files changed, 37 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js index 238cc1c68..929b55df4 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Tabs/TabOverview.js @@ -31,10 +31,6 @@ const TabOverview = () => { const { isReady: areTodosReady } = TodosHooks.useStore(); const { isReady: merchantIsReady } = CommonHooks.useStore(); - // TODO: Workaround before we implement the standard "persistentData" resolver. - // Calling this hook ensures that todos are loaded from the REST API at this point. - const {} = TodosHooks.useTodos(); - if ( ! areTodosReady || ! merchantIsReady ) { return ; } diff --git a/modules/ppcp-settings/resources/js/data/_example/hooks.js b/modules/ppcp-settings/resources/js/data/_example/hooks.js index 9df5d43d3..4c88523b5 100644 --- a/modules/ppcp-settings/resources/js/data/_example/hooks.js +++ b/modules/ppcp-settings/resources/js/data/_example/hooks.js @@ -38,10 +38,15 @@ const useStoreData = () => { }; export const useStore = () => { - const { dispatch, useTransient } = useStoreData(); + const { select, dispatch, useTransient } = useStoreData(); const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.persistentData(); + } + return { persist, refresh, isReady }; }; diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index 49e5736a1..fd6ba3157 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -96,10 +96,15 @@ const useHooks = () => { }; export const useStore = () => { - const { dispatch, useTransient } = useStoreData(); + const { select, dispatch, useTransient } = useStoreData(); const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.persistentData(); + } + return { persist, refresh, isReady }; }; diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 582fd86f6..6060041f4 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -38,10 +38,15 @@ const useStoreData = () => { }; export const useStore = () => { - const { useTransient, dispatch } = useStoreData(); + const { select, useTransient, dispatch } = useStoreData(); const { persist, refresh, setPersistent, changePaymentSettings } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.persistentData(); + } + return { persist, refresh, diff --git a/modules/ppcp-settings/resources/js/data/settings/hooks.js b/modules/ppcp-settings/resources/js/data/settings/hooks.js index a79f1fc6d..4a97afa9e 100644 --- a/modules/ppcp-settings/resources/js/data/settings/hooks.js +++ b/modules/ppcp-settings/resources/js/data/settings/hooks.js @@ -98,10 +98,15 @@ const useHooks = () => { }; export const useStore = () => { - const { dispatch, useTransient } = useStoreData(); + const { select, dispatch, useTransient } = useStoreData(); const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.persistentData(); + } + return { persist, refresh, isReady }; }; diff --git a/modules/ppcp-settings/resources/js/data/styling/hooks.js b/modules/ppcp-settings/resources/js/data/styling/hooks.js index a9d7bcbd6..ecf999eaf 100644 --- a/modules/ppcp-settings/resources/js/data/styling/hooks.js +++ b/modules/ppcp-settings/resources/js/data/styling/hooks.js @@ -20,6 +20,7 @@ import { STYLING_PAYMENT_METHODS, STYLING_SHAPES, } from './configuration'; +import { persistentData } from './selectors'; /** * Single source of truth for access Redux details. @@ -92,10 +93,15 @@ const useHooks = () => { }; export const useStore = () => { - const { dispatch, useTransient } = useStoreData(); + const { select, dispatch, useTransient } = useStoreData(); const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.persistentData(); + } + return { persist, refresh, isReady }; }; diff --git a/modules/ppcp-settings/resources/js/data/todos/hooks.js b/modules/ppcp-settings/resources/js/data/todos/hooks.js index f7bae751b..5ce97a636 100644 --- a/modules/ppcp-settings/resources/js/data/todos/hooks.js +++ b/modules/ppcp-settings/resources/js/data/todos/hooks.js @@ -81,10 +81,15 @@ const useHooks = () => { }; export const useStore = () => { - const { dispatch, useTransient } = useStoreData(); + const { select, dispatch, useTransient } = useStoreData(); const { persist, refresh } = dispatch; const [ isReady ] = useTransient( 'isReady' ); + // Load persistent data from REST if not done yet. + if ( ! isReady ) { + select.getTodos(); + } + return { persist, refresh, isReady }; };