diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js index 1645547d9..5d5968591 100644 --- a/modules/ppcp-settings/resources/js/data/common/action-types.js +++ b/modules/ppcp-settings/resources/js/data/common/action-types.js @@ -1,9 +1,6 @@ /** * Action Types: Define unique identifiers for actions across all store modules. * - * Keys are module-internal and can have any value. - * Values must be unique across all store modules to avoid collisions. - * * @file */ @@ -13,6 +10,7 @@ export default { // Persistent data. SET_PERSISTENT: 'COMMON:SET_PERSISTENT', + HYDRATE: 'COMMON:HYDRATE', // Controls - always start with "DO_". DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA', diff --git a/modules/ppcp-settings/resources/js/data/common/actions.js b/modules/ppcp-settings/resources/js/data/common/actions.js index 3992e0ff5..4586b8923 100644 --- a/modules/ppcp-settings/resources/js/data/common/actions.js +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -2,7 +2,6 @@ * Action Creators: Define functions to create action objects. * * These functions update state or trigger side effects (e.g., async operations). - * Exported functions must have unique names across all store modules. * Actions are categorized as Transient, Persistent, or Side effect. * * @file @@ -10,91 +9,98 @@ import ACTION_TYPES from './action-types'; +/** + * @typedef {Object} Action An action object that is handled by a reducer or control. + * @property {string} type - The action type. + * @property {Object?} payload - Optional payload for the action. + */ + /** * Transient. Marks the onboarding details as "ready", i.e., fully initialized. * * @param {boolean} isReady - * @return {{type: string, isReady: boolean}} The action. + * @return {Action} The action. */ -export const setIsReady = ( isReady ) => { - return { - type: ACTION_TYPES.SET_TRANSIENT, - isReady, - }; -}; +export const setIsReady = ( isReady ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { isReady }, +} ); /** * Transient. Changes the "saving" flag. * * @param {boolean} isSaving - * @return {{type: string, isSaving: boolean}} The action. + * @return {Action} The action. */ -export const setIsSaving = ( isSaving ) => { - return { - type: ACTION_TYPES.SET_TRANSIENT, - isSaving, - }; -}; +export const setIsSaving = ( isSaving ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { isSaving }, +} ); + +/** + * Transient. Changes the "manual connection is busy" flag. + * + * @param {boolean} isBusy + * @return {Action} The action. + */ +export const setIsBusy = ( isBusy ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { isBusy }, +} ); /** * Persistent. Sets the sandbox mode on or off. * * @param {boolean} useSandbox - * @return {{type: string, useSandbox: boolean}} An action. + * @return {Action} The action. */ -export const setSandboxMode = ( useSandbox ) => { - return { - type: ACTION_TYPES.SET_PERSISTENT, - useSandbox, - }; -}; +export const setSandboxMode = ( useSandbox ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { useSandbox }, +} ); /** * Persistent. Toggles the "Manual Connection" mode on or off. * * @param {boolean} useManualConnection - * @return {{type: string, useManualConnection: boolean}} An action. + * @return {Action} The action. */ -export const setManualConnectionMode = ( useManualConnection ) => { - return { - type: ACTION_TYPES.SET_PERSISTENT, - useManualConnection, - }; -}; +export const setManualConnectionMode = ( useManualConnection ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { useManualConnection }, +} ); /** * Persistent. Changes the "client ID" value. * * @param {string} clientId - * @return {{type: string, clientId: string}} The action. + * @return {Action} The action. */ -export const setClientId = ( clientId ) => { - return { - type: ACTION_TYPES.SET_PERSISTENT, - clientId, - }; -}; +export const setClientId = ( clientId ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { clientId }, +} ); /** * Persistent. Changes the "client secret" value. * * @param {string} clientSecret - * @return {{type: string, clientSecret: string}} The action. + * @return {Action} The action. */ -export const setClientSecret = ( clientSecret ) => { - return { - type: ACTION_TYPES.SET_PERSISTENT, - clientSecret, - }; -}; +export const setClientSecret = ( clientSecret ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { clientSecret }, +} ); /** * Side effect. Saves the persistent details to the WP database. * - * @return {{type: string}} The action. + * @return {Action} The action. */ -export function* commonPersist() { - return { +export const persist = function* () { + yield setIsBusy( true ); + yield { type: ACTION_TYPES.DO_PERSIST_DATA, }; -} + yield setIsBusy( false ); +}; diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js index 365bfe305..881cac2f5 100644 --- a/modules/ppcp-settings/resources/js/data/common/constants.js +++ b/modules/ppcp-settings/resources/js/data/common/constants.js @@ -5,7 +5,7 @@ * * @type {string} */ -export const STORE_KEY = 'common'; +export const STORE_NAME = 'wc/paypal/common'; /** * REST path to hydrate data of this module by loading data from the WP DB.. @@ -14,7 +14,7 @@ export const STORE_KEY = 'common'; * * @type {string} */ -export const REST_HYDRATE_PATH = 'common'; +export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/common'; /** * REST path to persist data of this module to the WP DB. @@ -23,4 +23,4 @@ export const REST_HYDRATE_PATH = 'common'; * * @type {string} */ -export const REST_PERSIST_PATH = 'common'; +export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/common'; diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js index 45b3e6ea5..e18bb48ec 100644 --- a/modules/ppcp-settings/resources/js/data/common/controls.js +++ b/modules/ppcp-settings/resources/js/data/common/controls.js @@ -1,32 +1,27 @@ /** * Controls: Implement side effects, typically asynchronous operations. * - * Controls use ACTION_TYPES keys as identifiers to ensure uniqueness. + * Controls use ACTION_TYPES keys as identifiers. * They are triggered by corresponding actions and handle external interactions. * * @file */ -import { select } from '@wordpress/data'; -import { apiFetch } from '@wordpress/api-fetch'; +import apiFetch from '@wordpress/api-fetch'; -import { NAMESPACE, STORE_NAME } from '../constants'; import { REST_PERSIST_PATH } from './constants'; import ACTION_TYPES from './action-types'; -export const controls = { - [ ACTION_TYPES.DO_PERSIST_DATA ]: async () => { - const path = `${ NAMESPACE }/${ REST_PERSIST_PATH }`; - const data = select( STORE_NAME ).getPersistentData(); +export const controls = { + async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) { try { return await apiFetch( { - path, - method: 'post', + path: REST_PERSIST_PATH, + method: 'POST', data, } ); } catch ( error ) { - console.error( 'Error saving progress.', error ); - throw error; + console.error( 'Error saving data.', error ); } }, }; diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index a201c6d37..907ee00f3 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -3,7 +3,22 @@ * * These encapsulate store interactions, offering a consistent interface. * Hooks simplify data access and manipulation for components. - * Exported hooks must have unique names across all store modules. * * @file */ + +import { useSelect } from '@wordpress/data'; + +import { STORE_NAME } from './constants'; + +const useTransient = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).transientData()?.[ key ], + [ key ] + ); + +const usePersistent = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).persistentData()?.[ key ], + [ key ] + ); diff --git a/modules/ppcp-settings/resources/js/data/common/index.js b/modules/ppcp-settings/resources/js/data/common/index.js index a8c685d37..60f81da60 100644 --- a/modules/ppcp-settings/resources/js/data/common/index.js +++ b/modules/ppcp-settings/resources/js/data/common/index.js @@ -1,8 +1,26 @@ -import { STORE_KEY } from './constants'; +import { createReduxStore, register } from '@wordpress/data'; +import { controls as wpControls } from '@wordpress/data-controls'; + +import { STORE_NAME } from './constants'; import reducer from './reducer'; import * as selectors from './selectors'; import * as actions from './actions'; -import * as resolvers from './resolvers'; +import * as hooks from './hooks'; +import { resolvers } from './resolvers'; import { controls } from './controls'; -export { reducer, selectors, actions, resolvers, controls, STORE_KEY }; +export const initStore = () => { + const store = createReduxStore( STORE_NAME, { + reducer, + controls: { ...wpControls, ...controls }, + actions, + selectors, + resolvers, + } ); + + register( store ); +}; + +export { hooks }; + +export { STORE_NAME }; diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js index 583fd7540..d16106379 100644 --- a/modules/ppcp-settings/resources/js/data/common/reducer.js +++ b/modules/ppcp-settings/resources/js/data/common/reducer.js @@ -2,7 +2,6 @@ * Reducer: Defines store structure and state updates for this module. * * Manages both transient (temporary) and persistent (saved) state. - * Each module uses isolated memory objects to prevent conflicts. * The initial state must define all properties, as dynamic additions are not supported. * * @file @@ -16,6 +15,7 @@ import ACTION_TYPES from './action-types'; const defaultTransient = { isReady: false, isSaving: false, + isBusy: false, }; const defaultPersistent = { @@ -38,6 +38,9 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, { [ ACTION_TYPES.SET_PERSISTENT ]: ( state, action ) => setPersistent( state, action ), + + [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => + setPersistent( state, payload.data ), } ); export default commonReducer; diff --git a/modules/ppcp-settings/resources/js/data/common/resolvers.js b/modules/ppcp-settings/resources/js/data/common/resolvers.js index 2358496be..ceebca53f 100644 --- a/modules/ppcp-settings/resources/js/data/common/resolvers.js +++ b/modules/ppcp-settings/resources/js/data/common/resolvers.js @@ -2,7 +2,7 @@ * Resolvers: Handle asynchronous data fetching for the store. * * These functions update store state with data from external sources. - * Each resolver corresponds to a specific selector but must have a unique name. + * Each resolver corresponds to a specific selector (selector with same name must exist). * Resolvers are called automatically when selectors request unavailable data. * * @file @@ -12,26 +12,25 @@ import { dispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { apiFetch } from '@wordpress/data-controls'; -import { NAMESPACE } from '../constants'; -import { setIsReady, setCommonDetails } from './actions'; -import { REST_HYDRATE_PATH } from './constants'; +import { STORE_NAME, REST_HYDRATE_PATH } from './constants'; -/** - * Retrieve settings from the site's REST API. - */ -export function* commonPersistentData() { - const path = `${ NAMESPACE }/${ REST_HYDRATE_PATH }`; +export const resolvers = { + /** + * Retrieve settings from the site's REST API. + */ + *persistentData() { + try { + const result = yield apiFetch( { path: REST_HYDRATE_PATH } ); - try { - const result = yield apiFetch( { path } ); - yield setCommonDetails( result ); - yield setIsReady( true ); - } catch ( e ) { - yield dispatch( 'core/notices' ).createErrorNotice( - __( - 'Error retrieving plugin details.', - 'woocommerce-paypal-payments' - ) - ); - } -} + yield dispatch( STORE_NAME ).hydrate( result ); + yield dispatch( STORE_NAME ).setIsReady( true ); + } catch ( e ) { + yield dispatch( 'core/notices' ).createErrorNotice( + __( + 'Error retrieving plugin details.', + 'woocommerce-paypal-payments' + ) + ); + } + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/common/selectors.js b/modules/ppcp-settings/resources/js/data/common/selectors.js index 4ea6e86a7..14334fcf3 100644 --- a/modules/ppcp-settings/resources/js/data/common/selectors.js +++ b/modules/ppcp-settings/resources/js/data/common/selectors.js @@ -3,32 +3,19 @@ * * These functions provide a consistent interface for accessing store data. * They allow components to retrieve data without knowing the store structure. - * Exported functions must have unique names across all store modules. * * @file */ -import { STORE_KEY } from './constants'; - const EMPTY_OBJ = Object.freeze( {} ); -const getState = ( state ) => { - if ( ! state ) { - return EMPTY_OBJ; - } +const getState = ( state ) => state || EMPTY_OBJ; - return state[ STORE_KEY ] || EMPTY_OBJ; -}; - -export const commonPersistentData = ( state ) => { +export const persistentData = ( state ) => { return getState( state ).data || EMPTY_OBJ; }; -export const commonTransientData = ( state ) => { - const { data, flags, ...transientState } = getState( state ); +export const transientData = ( state ) => { + const { data, ...transientState } = getState( state ); return transientState || EMPTY_OBJ; }; - -export const commonFlags = ( state ) => { - return getState( state ).flags || EMPTY_OBJ; -};