From 0f03a636b23c2f935da2d9609d21afdd2d4618a7 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 18 Nov 2024 18:58:54 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Initial=20store-module=20for=20c?= =?UTF-8?q?ommon=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/data/common/action-types.js | 19 ++++ .../resources/js/data/common/actions.js | 100 ++++++++++++++++++ .../resources/js/data/common/constants.js | 26 +++++ .../resources/js/data/common/controls.js | 32 ++++++ .../resources/js/data/common/hooks.js | 9 ++ .../resources/js/data/common/index.js | 8 ++ .../resources/js/data/common/reducer.js | 43 ++++++++ .../resources/js/data/common/resolvers.js | 37 +++++++ .../resources/js/data/common/selectors.js | 34 ++++++ .../resources/js/data/onboarding/reducer.js | 1 - 10 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 modules/ppcp-settings/resources/js/data/common/action-types.js create mode 100644 modules/ppcp-settings/resources/js/data/common/actions.js create mode 100644 modules/ppcp-settings/resources/js/data/common/constants.js create mode 100644 modules/ppcp-settings/resources/js/data/common/controls.js create mode 100644 modules/ppcp-settings/resources/js/data/common/hooks.js create mode 100644 modules/ppcp-settings/resources/js/data/common/index.js create mode 100644 modules/ppcp-settings/resources/js/data/common/reducer.js create mode 100644 modules/ppcp-settings/resources/js/data/common/resolvers.js create mode 100644 modules/ppcp-settings/resources/js/data/common/selectors.js diff --git a/modules/ppcp-settings/resources/js/data/common/action-types.js b/modules/ppcp-settings/resources/js/data/common/action-types.js new file mode 100644 index 000000000..1645547d9 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/action-types.js @@ -0,0 +1,19 @@ +/** + * 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 + */ + +export default { + // Transient data. + SET_TRANSIENT: 'COMMON:SET_TRANSIENT', + + // Persistent data. + SET_PERSISTENT: 'COMMON:SET_PERSISTENT', + + // 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 new file mode 100644 index 000000000..3992e0ff5 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/actions.js @@ -0,0 +1,100 @@ +/** + * 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 + */ + +import ACTION_TYPES from './action-types'; + +/** + * Transient. Marks the onboarding details as "ready", i.e., fully initialized. + * + * @param {boolean} isReady + * @return {{type: string, isReady: boolean}} The action. + */ +export const setIsReady = ( isReady ) => { + return { + type: ACTION_TYPES.SET_TRANSIENT, + isReady, + }; +}; + +/** + * Transient. Changes the "saving" flag. + * + * @param {boolean} isSaving + * @return {{type: string, isSaving: boolean}} The action. + */ +export const setIsSaving = ( isSaving ) => { + return { + type: ACTION_TYPES.SET_TRANSIENT, + isSaving, + }; +}; + +/** + * Persistent. Sets the sandbox mode on or off. + * + * @param {boolean} useSandbox + * @return {{type: string, useSandbox: boolean}} An action. + */ +export const setSandboxMode = ( useSandbox ) => { + return { + type: ACTION_TYPES.SET_PERSISTENT, + useSandbox, + }; +}; + +/** + * Persistent. Toggles the "Manual Connection" mode on or off. + * + * @param {boolean} useManualConnection + * @return {{type: string, useManualConnection: boolean}} An action. + */ +export const setManualConnectionMode = ( useManualConnection ) => { + return { + type: ACTION_TYPES.SET_PERSISTENT, + useManualConnection, + }; +}; + +/** + * Persistent. Changes the "client ID" value. + * + * @param {string} clientId + * @return {{type: string, clientId: string}} The action. + */ +export const setClientId = ( clientId ) => { + return { + type: ACTION_TYPES.SET_PERSISTENT, + clientId, + }; +}; + +/** + * Persistent. Changes the "client secret" value. + * + * @param {string} clientSecret + * @return {{type: string, clientSecret: string}} The action. + */ +export const setClientSecret = ( clientSecret ) => { + return { + type: ACTION_TYPES.SET_PERSISTENT, + clientSecret, + }; +}; + +/** + * Side effect. Saves the persistent details to the WP database. + * + * @return {{type: string}} The action. + */ +export function* commonPersist() { + return { + type: ACTION_TYPES.DO_PERSIST_DATA, + }; +} diff --git a/modules/ppcp-settings/resources/js/data/common/constants.js b/modules/ppcp-settings/resources/js/data/common/constants.js new file mode 100644 index 000000000..365bfe305 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/constants.js @@ -0,0 +1,26 @@ +/** + * Name of the module-store in the main Redux store. + * + * Helps to isolate data, used by reducer and selectors. + * + * @type {string} + */ +export const STORE_KEY = 'common'; + +/** + * REST path to hydrate data of this module by loading data from the WP DB.. + * + * Used by resolvers. + * + * @type {string} + */ +export const REST_HYDRATE_PATH = 'common'; + +/** + * REST path to persist data of this module to the WP DB. + * + * Used by controls. + * + * @type {string} + */ +export const REST_PERSIST_PATH = 'common'; diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js new file mode 100644 index 000000000..45b3e6ea5 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/controls.js @@ -0,0 +1,32 @@ +/** + * Controls: Implement side effects, typically asynchronous operations. + * + * Controls use ACTION_TYPES keys as identifiers to ensure uniqueness. + * They are triggered by corresponding actions and handle external interactions. + * + * @file + */ + +import { select } from '@wordpress/data'; +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(); + + try { + return await apiFetch( { + path, + method: 'post', + data, + } ); + } catch ( error ) { + console.error( 'Error saving progress.', error ); + throw error; + } + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js new file mode 100644 index 000000000..a201c6d37 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -0,0 +1,9 @@ +/** + * Hooks: Provide the main API for components to interact with the store. + * + * 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 + */ diff --git a/modules/ppcp-settings/resources/js/data/common/index.js b/modules/ppcp-settings/resources/js/data/common/index.js new file mode 100644 index 000000000..a8c685d37 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/index.js @@ -0,0 +1,8 @@ +import { STORE_KEY } from './constants'; +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import * as resolvers from './resolvers'; +import { controls } from './controls'; + +export { reducer, selectors, actions, resolvers, controls, STORE_KEY }; diff --git a/modules/ppcp-settings/resources/js/data/common/reducer.js b/modules/ppcp-settings/resources/js/data/common/reducer.js new file mode 100644 index 000000000..583fd7540 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/reducer.js @@ -0,0 +1,43 @@ +/** + * 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 + */ + +import { createReducer, createSetters } from '../utils'; +import ACTION_TYPES from './action-types'; + +// Store structure. + +const defaultTransient = { + isReady: false, + isSaving: false, +}; + +const defaultPersistent = { + useSandbox: false, + useManualConnection: false, + clientId: '', + clientSecret: '', +}; + +// Reducer logic. + +const [ setTransient, setPersistent ] = createSetters( + defaultTransient, + defaultPersistent +); + +const commonReducer = createReducer( defaultTransient, defaultPersistent, { + [ ACTION_TYPES.SET_TRANSIENT ]: ( state, action ) => + setTransient( state, action ), + + [ ACTION_TYPES.SET_PERSISTENT ]: ( state, action ) => + setPersistent( state, action ), +} ); + +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 new file mode 100644 index 000000000..2358496be --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/resolvers.js @@ -0,0 +1,37 @@ +/** + * 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. + * Resolvers are called automatically when selectors request unavailable data. + * + * @file + */ + +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'; + +/** + * Retrieve settings from the site's REST API. + */ +export function* commonPersistentData() { + const path = `${ NAMESPACE }/${ 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' + ) + ); + } +} diff --git a/modules/ppcp-settings/resources/js/data/common/selectors.js b/modules/ppcp-settings/resources/js/data/common/selectors.js new file mode 100644 index 000000000..4ea6e86a7 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/common/selectors.js @@ -0,0 +1,34 @@ +/** + * Selectors: Extract specific pieces of state from the store. + * + * 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; + } + + return state[ STORE_KEY ] || EMPTY_OBJ; +}; + +export const commonPersistentData = ( state ) => { + return getState( state ).data || EMPTY_OBJ; +}; + +export const commonTransientData = ( state ) => { + const { data, flags, ...transientState } = getState( state ); + return transientState || EMPTY_OBJ; +}; + +export const commonFlags = ( state ) => { + return getState( state ).flags || EMPTY_OBJ; +}; diff --git a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js index 41a57c578..0f253fc02 100644 --- a/modules/ppcp-settings/resources/js/data/onboarding/reducer.js +++ b/modules/ppcp-settings/resources/js/data/onboarding/reducer.js @@ -4,7 +4,6 @@ * 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. - * Reducers are separated per module to avoid conflicts in state management. * * @file */