mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Add data store boilerplate
This commit is contained in:
parent
442a33778f
commit
0e89cde688
12 changed files with 411 additions and 2 deletions
|
@ -5,8 +5,17 @@ import * as Payment from './payment';
|
||||||
import * as Settings from './settings';
|
import * as Settings from './settings';
|
||||||
import * as Styling from './styling';
|
import * as Styling from './styling';
|
||||||
import * as Todos from './todos';
|
import * as Todos from './todos';
|
||||||
|
import * as PayLaterMessaging from './pay-later-messaging';
|
||||||
|
|
||||||
const stores = [ Onboarding, Common, Payment, Settings, Styling, Todos ];
|
const stores = [
|
||||||
|
Onboarding,
|
||||||
|
Common,
|
||||||
|
Payment,
|
||||||
|
Settings,
|
||||||
|
Styling,
|
||||||
|
Todos,
|
||||||
|
PayLaterMessaging,
|
||||||
|
];
|
||||||
|
|
||||||
stores.forEach( ( store ) => {
|
stores.forEach( ( store ) => {
|
||||||
try {
|
try {
|
||||||
|
@ -30,6 +39,7 @@ export const PaymentHooks = Payment.hooks;
|
||||||
export const SettingsHooks = Settings.hooks;
|
export const SettingsHooks = Settings.hooks;
|
||||||
export const StylingHooks = Styling.hooks;
|
export const StylingHooks = Styling.hooks;
|
||||||
export const TodosHooks = Todos.hooks;
|
export const TodosHooks = Todos.hooks;
|
||||||
|
export const PayLaterMessagingHooks = PayLaterMessaging.hooks;
|
||||||
|
|
||||||
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
||||||
export const CommonStoreName = Common.STORE_NAME;
|
export const CommonStoreName = Common.STORE_NAME;
|
||||||
|
@ -37,6 +47,7 @@ export const PaymentStoreName = Payment.STORE_NAME;
|
||||||
export const SettingsStoreName = Settings.STORE_NAME;
|
export const SettingsStoreName = Settings.STORE_NAME;
|
||||||
export const StylingStoreName = Styling.STORE_NAME;
|
export const StylingStoreName = Styling.STORE_NAME;
|
||||||
export const TodosStoreName = Todos.STORE_NAME;
|
export const TodosStoreName = Todos.STORE_NAME;
|
||||||
|
export const PayLaterMessagingStoreName = PayLaterMessaging.STORE_NAME;
|
||||||
|
|
||||||
export * from './configuration';
|
export * from './configuration';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Store template
|
||||||
|
|
||||||
|
This template contains all files for a Redux store.
|
||||||
|
|
||||||
|
## New Store: Redux integration
|
||||||
|
|
||||||
|
1. Copy this folder, give it a correct name.
|
||||||
|
2. Check each file for `<UNKNOWN>` placeholders and `TODO` remarks.
|
||||||
|
3. Edit the main store-index file and add the relevant store integration there.
|
||||||
|
4. Check the debug-module, and add relevant debug code.
|
||||||
|
- Register the store in the `reset()` method.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Main store-index:
|
||||||
|
`modules/ppcp-settings/resources/js/data/index.js`
|
||||||
|
|
||||||
|
Sample store integration:
|
||||||
|
```js
|
||||||
|
import * as YourStore from './yourStore';
|
||||||
|
// ...
|
||||||
|
YourStore.initStore();
|
||||||
|
// ...
|
||||||
|
export const YourStoreHooks = YourStore.hooks;
|
||||||
|
// ...
|
||||||
|
export const YourStoreName = YourStore.STORE_NAME;
|
||||||
|
// ...
|
||||||
|
addDebugTools( window.ppcpSettings, [ ..., YourStoreName ] );
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### New Store: PHP integration
|
||||||
|
|
||||||
|
1. Create the **REST endpoint** for hydrating and persisting data.
|
||||||
|
- `modules/ppcp-settings/src/Endpoint/YourStoreRestEndpoint.php`
|
||||||
|
- Extend from base class `RestEndpoint`
|
||||||
|
2. Create the **data model** class to manage the DB interaction.
|
||||||
|
- `modules/ppcp-settings/src/Data/YourStoreSettings.php`
|
||||||
|
- Extend from base class `AbstractDataModel`
|
||||||
|
3. Create relevant **DI services** for both files.
|
||||||
|
- `modules/ppcp-settings/services.php`
|
||||||
|
4. Register the REST endpoint in the **service module**.
|
||||||
|
- `modules/ppcp-settings/src/SettingsModule.php`
|
||||||
|
- Find the action `rest_api_init`
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Action Types: Define unique identifiers for actions across all store modules.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Transient data.
|
||||||
|
SET_TRANSIENT: 'PAY_LATER_MESSAGING:SET_TRANSIENT',
|
||||||
|
|
||||||
|
// Persistent data.
|
||||||
|
SET_PERSISTENT: 'PAY_LATER_MESSAGING:SET_PERSISTENT',
|
||||||
|
RESET: 'PAY_LATER_MESSAGING:RESET',
|
||||||
|
HYDRATE: 'PAY_LATER_MESSAGING:HYDRATE',
|
||||||
|
|
||||||
|
// Controls - always start with "DO_".
|
||||||
|
DO_PERSIST_DATA: 'PAY_LATER_MESSAGING:DO_PERSIST_DATA',
|
||||||
|
};
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* Action Creators: Define functions to create action objects.
|
||||||
|
*
|
||||||
|
* These functions update state or trigger side effects (e.g., async operations).
|
||||||
|
* Actions are categorized as Transient, Persistent, or Side effect.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { select } from '@wordpress/data';
|
||||||
|
|
||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
import { STORE_NAME } from './constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special. Resets all values in the store to initial defaults.
|
||||||
|
*
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const reset = () => ( { type: ACTION_TYPES.RESET } );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistent. Set the full store details during app initialization.
|
||||||
|
*
|
||||||
|
* @param {{data: {}, flags?: {}}} payload
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const hydrate = ( payload ) => ( {
|
||||||
|
type: ACTION_TYPES.HYDRATE,
|
||||||
|
payload,
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic transient-data updater.
|
||||||
|
*
|
||||||
|
* @param {string} prop Name of the property to update.
|
||||||
|
* @param {any} value The new value of the property.
|
||||||
|
* @return {Action} 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 {Action} 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
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effect. Triggers the persistence of store data to the server.
|
||||||
|
*
|
||||||
|
* @return {Action} The action.
|
||||||
|
*/
|
||||||
|
export const persist = function* () {
|
||||||
|
const data = yield select( STORE_NAME ).persistentData();
|
||||||
|
|
||||||
|
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Name of the Redux store module.
|
||||||
|
*
|
||||||
|
* Used by: Reducer, Selector, Index
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const STORE_NAME = 'wc/paypal/pay_later_messaging';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST path to hydrate data of this module by loading data from the WP DB.
|
||||||
|
*
|
||||||
|
* Used by: Resolvers
|
||||||
|
* See: PayLaterMessagingEndpoint.php
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/pay_later_messaging';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST path to persist data of this module to the WP DB.
|
||||||
|
*
|
||||||
|
* Used by: Controls
|
||||||
|
* See: PayLaterMessagingEndpoint.php
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/pay_later_messaging';
|
23
modules/ppcp-settings/resources/js/data/pay-later-messaging/controls.js
vendored
Normal file
23
modules/ppcp-settings/resources/js/data/pay-later-messaging/controls.js
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Controls: Implement side effects, typically asynchronous operations.
|
||||||
|
*
|
||||||
|
* Controls use ACTION_TYPES keys as identifiers.
|
||||||
|
* They are triggered by corresponding actions and handle external interactions.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
import apiFetch from '@wordpress/api-fetch';
|
||||||
|
|
||||||
|
import { REST_PERSIST_PATH } from './constants';
|
||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
export const controls = {
|
||||||
|
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||||
|
return await apiFetch( {
|
||||||
|
path: REST_PERSIST_PATH,
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useDispatch } from '@wordpress/data';
|
||||||
|
|
||||||
|
import { createHooksForStore } from '../utils';
|
||||||
|
import { STORE_NAME } from './constants';
|
||||||
|
|
||||||
|
const useHooks = () => {
|
||||||
|
const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||||
|
const { persist } = useDispatch( STORE_NAME );
|
||||||
|
|
||||||
|
// Read-only flags and derived state.
|
||||||
|
// Nothing here yet.
|
||||||
|
|
||||||
|
// Transient accessors.
|
||||||
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Persistent accessors.
|
||||||
|
// TODO: Replace with real property.
|
||||||
|
const [ sampleValue, setSampleValue ] = usePersistent( 'sampleValue' );
|
||||||
|
|
||||||
|
return {
|
||||||
|
persist,
|
||||||
|
isReady,
|
||||||
|
sampleValue,
|
||||||
|
setSampleValue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useState = () => {
|
||||||
|
const { persist, isReady } = useHooks();
|
||||||
|
return { persist, isReady };
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Replace with real hook.
|
||||||
|
export const useSampleValue = () => {
|
||||||
|
const { sampleValue, setSampleValue } = useHooks();
|
||||||
|
|
||||||
|
return {
|
||||||
|
sampleValue,
|
||||||
|
setSampleValue,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,32 @@
|
||||||
|
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 hooks from './hooks';
|
||||||
|
import { resolvers } from './resolvers';
|
||||||
|
import { controls } from './controls';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes and registers the settings store with WordPress data layer.
|
||||||
|
* Combines custom controls with WordPress data controls.
|
||||||
|
*
|
||||||
|
* @return {boolean} True if initialization succeeded, false otherwise.
|
||||||
|
*/
|
||||||
|
export const initStore = () => {
|
||||||
|
const store = createReduxStore( STORE_NAME, {
|
||||||
|
reducer,
|
||||||
|
controls: { ...wpControls, ...controls },
|
||||||
|
actions,
|
||||||
|
selectors,
|
||||||
|
resolvers,
|
||||||
|
} );
|
||||||
|
|
||||||
|
register( store );
|
||||||
|
|
||||||
|
return Boolean( wp.data.select( STORE_NAME ) );
|
||||||
|
};
|
||||||
|
|
||||||
|
export { hooks, selectors, STORE_NAME };
|
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Reducer: Defines store structure and state updates for this module.
|
||||||
|
*
|
||||||
|
* Manages both transient (temporary) and persistent (saved) state.
|
||||||
|
* The initial state must define all properties, as dynamic additions are not supported.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createReducer, createReducerSetters } from '../utils';
|
||||||
|
import ACTION_TYPES from './action-types';
|
||||||
|
|
||||||
|
// Store structure.
|
||||||
|
|
||||||
|
// Transient: Values that are _not_ saved to the DB (like app lifecycle-flags).
|
||||||
|
const defaultTransient = Object.freeze( {
|
||||||
|
isReady: false,
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Persistent: Values that are loaded from the DB.
|
||||||
|
const defaultPersistent = Object.freeze( {
|
||||||
|
// TODO: Add real DB properties here.
|
||||||
|
sampleValue: 'foo',
|
||||||
|
cart: {},
|
||||||
|
checkout: {},
|
||||||
|
product: {},
|
||||||
|
shop: {},
|
||||||
|
home: {},
|
||||||
|
custom_placement: [],
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Reducer logic.
|
||||||
|
|
||||||
|
const [ changeTransient, changePersistent ] = createReducerSetters(
|
||||||
|
defaultTransient,
|
||||||
|
defaultPersistent
|
||||||
|
);
|
||||||
|
|
||||||
|
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||||
|
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||||
|
changeTransient( state, payload ),
|
||||||
|
|
||||||
|
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
|
||||||
|
changePersistent( state, payload ),
|
||||||
|
|
||||||
|
[ ACTION_TYPES.RESET ]: ( state ) => {
|
||||||
|
const cleanState = changeTransient(
|
||||||
|
changePersistent( state, defaultPersistent ),
|
||||||
|
defaultTransient
|
||||||
|
);
|
||||||
|
|
||||||
|
// Keep "read-only" details and initialization flags.
|
||||||
|
cleanState.isReady = true;
|
||||||
|
|
||||||
|
return cleanState;
|
||||||
|
},
|
||||||
|
|
||||||
|
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||||
|
changePersistent( state, payload.data ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
export default reducer;
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* 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 (selector with same name must exist).
|
||||||
|
* 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 { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||||
|
|
||||||
|
export const resolvers = {
|
||||||
|
/**
|
||||||
|
* Retrieve settings from the site's REST API.
|
||||||
|
*/
|
||||||
|
*persistentData() {
|
||||||
|
try {
|
||||||
|
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||||
|
|
||||||
|
console.log( result );
|
||||||
|
|
||||||
|
yield dispatch( STORE_NAME ).hydrate( result );
|
||||||
|
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||||
|
} catch ( e ) {
|
||||||
|
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||||
|
// TODO: Add the module name to the error message.
|
||||||
|
__(
|
||||||
|
'Error retrieving Pay Later Messaging config details.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
const EMPTY_OBJ = Object.freeze( {} );
|
||||||
|
|
||||||
|
const getState = ( state ) => state || EMPTY_OBJ;
|
||||||
|
|
||||||
|
export const persistentData = ( state ) => {
|
||||||
|
return getState( state ).data || EMPTY_OBJ;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transientData = ( state ) => {
|
||||||
|
const { data, ...transientState } = getState( state );
|
||||||
|
return transientState || EMPTY_OBJ;
|
||||||
|
};
|
|
@ -100,7 +100,7 @@ class PayLaterMessagingEndpoint extends RestEndpoint {
|
||||||
*
|
*
|
||||||
* @param WP_REST_Request $request Full data about the request.
|
* @param WP_REST_Request $request Full data about the request.
|
||||||
*
|
*
|
||||||
* @return WP_REST_Response The updated payment methods details.
|
* @return WP_REST_Response The updated Pay Later Messaging configuration details.
|
||||||
*/
|
*/
|
||||||
public function update_details( WP_REST_Request $request ) : WP_REST_Response {
|
public function update_details( WP_REST_Request $request ) : WP_REST_Response {
|
||||||
$this->save_config->save_config( $request->get_params() );
|
$this->save_config->save_config( $request->get_params() );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue