mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
Merge pull request #3070 from woocommerce/PCP-4128-redux-refactor-controls-to-thunks
Redux: Refactor controls to thunks (4128)
This commit is contained in:
commit
78c2208f2a
46 changed files with 738 additions and 966 deletions
|
@ -6,13 +6,10 @@
|
|||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: '<UNKNOWN>:SET_TRANSIENT',
|
||||
SET_TRANSIENT: 'ppcp/<UNKNOWN>/SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: '<UNKNOWN>:SET_PERSISTENT',
|
||||
RESET: '<UNKNOWN>:RESET',
|
||||
HYDRATE: '<UNKNOWN>:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: '<UNKNOWN>:DO_PERSIST_DATA',
|
||||
SET_PERSISTENT: 'ppcp/<UNKNOWN>/SET_PERSISTENT',
|
||||
RESET: 'ppcp/<UNKNOWN>/RESET',
|
||||
HYDRATE: 'ppcp/<UNKNOWN>/HYDRATE',
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -69,12 +69,22 @@ export const setPersistent = ( prop, value ) => ( {
|
|||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -7,44 +7,51 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
|
||||
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 );
|
||||
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,
|
||||
};
|
||||
return useMemo(
|
||||
() => ( {
|
||||
select,
|
||||
dispatch,
|
||||
useTransient,
|
||||
usePersistent,
|
||||
} ),
|
||||
[ select, dispatch, useTransient, usePersistent ]
|
||||
);
|
||||
};
|
||||
|
||||
export const useStore = () => {
|
||||
const { persist, isReady } = useHooks();
|
||||
return { persist, isReady };
|
||||
const { dispatch, useTransient } = useStoreData();
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
|
||||
return { persist: dispatch.persist, isReady };
|
||||
};
|
||||
|
||||
// TODO: Replace with real hook.
|
||||
export const useSampleValue = () => {
|
||||
const { sampleValue, setSampleValue } = useHooks();
|
||||
const { usePersistent, select } = useStoreData();
|
||||
const [ sampleValue, setSampleValue ] = usePersistent( 'sampleValue' );
|
||||
|
||||
return {
|
||||
sampleValue,
|
||||
setSampleValue,
|
||||
flags: select.flags(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -18,7 +16,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,30 +8,31 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
// TODO: Add the module name to the error message.
|
||||
__(
|
||||
'Error retrieving <UNKNOWN> details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
// TODO: Add the module name to the error message.
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving <UNKNOWN> details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,26 +6,15 @@
|
|||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'COMMON:SET_TRANSIENT',
|
||||
SET_TRANSIENT: 'ppcp/common/SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: 'COMMON:SET_PERSISTENT',
|
||||
RESET: 'COMMON:RESET',
|
||||
HYDRATE: 'COMMON:HYDRATE',
|
||||
SET_PERSISTENT: 'ppcp/common/SET_PERSISTENT',
|
||||
RESET: 'ppcp/common/RESET',
|
||||
HYDRATE: 'ppcp/common/HYDRATE',
|
||||
RESET_MERCHANT: 'ppcp/common/RESET_MERCHANT',
|
||||
|
||||
// Activity management (advanced solution that replaces the isBusy state).
|
||||
START_ACTIVITY: 'COMMON:START_ACTIVITY',
|
||||
STOP_ACTIVITY: 'COMMON:STOP_ACTIVITY',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'COMMON:DO_PERSIST_DATA',
|
||||
DO_DIRECT_API_AUTHENTICATION: 'COMMON:DO_DIRECT_API_AUTHENTICATION',
|
||||
DO_OAUTH_AUTHENTICATION: 'COMMON:DO_OAUTH_AUTHENTICATION',
|
||||
DO_DISCONNECT_MERCHANT: 'COMMON:DO_DISCONNECT_MERCHANT',
|
||||
DO_GENERATE_ONBOARDING_URL: 'COMMON:DO_GENERATE_ONBOARDING_URL',
|
||||
DO_REFRESH_MERCHANT: 'COMMON:DO_REFRESH_MERCHANT',
|
||||
DO_REFRESH_FEATURES: 'COMMON:DO_REFRESH_FEATURES',
|
||||
DO_RESUBSCRIBE_WEBHOOKS: 'COMMON:DO_RESUBSCRIBE_WEBHOOKS',
|
||||
DO_START_WEBHOOK_SIMULATION: 'COMMON:DO_START_WEBHOOK_SIMULATION',
|
||||
DO_CHECK_WEBHOOK_SIMULATION: 'COMMON:DO_CHECK_WEBHOOK_SIMULATION',
|
||||
START_ACTIVITY: 'ppcp/common/START_ACTIVITY',
|
||||
STOP_ACTIVITY: 'ppcp/common/STOP_ACTIVITY',
|
||||
};
|
||||
|
|
281
modules/ppcp-settings/resources/js/data/common/actions-thunk.js
Normal file
281
modules/ppcp-settings/resources/js/data/common/actions-thunk.js
Normal file
|
@ -0,0 +1,281 @@
|
|||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import {
|
||||
REST_CONNECTION_URL_PATH,
|
||||
REST_DIRECT_AUTHENTICATION_PATH,
|
||||
REST_DISCONNECT_MERCHANT_PATH,
|
||||
REST_HYDRATE_MERCHANT_PATH,
|
||||
REST_OAUTH_AUTHENTICATION_PATH,
|
||||
REST_PERSIST_PATH,
|
||||
REST_REFRESH_FEATURES_PATH,
|
||||
REST_WEBHOOKS,
|
||||
REST_WEBHOOKS_SIMULATE,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
* Side effect. Saves the persistent details to the WP database.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Fetches the ISU-login URL for a sandbox account.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function sandboxOnboardingUrl() {
|
||||
return async () => {
|
||||
try {
|
||||
return apiFetch( {
|
||||
path: REST_CONNECTION_URL_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
useSandbox: true,
|
||||
products: [ 'EXPRESS_CHECKOUT' ],
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Fetches the ISU-login URL for a production account.
|
||||
*
|
||||
* @param {string[]} products Which products/features to display in the ISU popup.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function productionOnboardingUrl( products = [] ) {
|
||||
return async () => {
|
||||
try {
|
||||
return apiFetch( {
|
||||
path: REST_CONNECTION_URL_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
useSandbox: false,
|
||||
products,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Initiates a direct connection attempt using the provided client ID and secret.
|
||||
*
|
||||
* This action accepts parameters instead of fetching data from the Redux state because the
|
||||
* values (ID and secret) are not managed by a central redux store, but might come from private
|
||||
* component state.
|
||||
*
|
||||
* @param {string} clientId - AP client ID (always 80-characters, starting with "A").
|
||||
* @param {string} clientSecret - API client secret.
|
||||
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function authenticateWithCredentials(
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox
|
||||
) {
|
||||
return async () => {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_DIRECT_AUTHENTICATION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Completes the ISU login by authenticating the user via the one time sharedId and
|
||||
* authCode provided by PayPal.
|
||||
*
|
||||
* This action accepts parameters instead of fetching data from the Redux state because all
|
||||
* parameters are dynamically generated during the authentication process, and not managed by our
|
||||
* Redux store.
|
||||
*
|
||||
* @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the
|
||||
* API client ID.
|
||||
* @param {string} authCode - OAuth authorization code provided during onboarding.
|
||||
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function authenticateWithOAuth( sharedId, authCode, useSandbox ) {
|
||||
return async () => {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_OAUTH_AUTHENTICATION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
sharedId,
|
||||
authCode,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Checks webhook simulation.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function disconnectMerchant() {
|
||||
return async () => {
|
||||
return await apiFetch( {
|
||||
path: REST_DISCONNECT_MERCHANT_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Clears and refreshes the merchant data via a REST request.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function refreshMerchantData() {
|
||||
return async ( { dispatch } ) => {
|
||||
try {
|
||||
await dispatch.resetMerchant();
|
||||
const result = await apiFetch( {
|
||||
path: REST_HYDRATE_MERCHANT_PATH,
|
||||
} );
|
||||
|
||||
if ( result.success && result.merchant ) {
|
||||
dispatch.hydrate( result );
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect.
|
||||
* Purges all feature status data via a REST request.
|
||||
* Refreshes the merchant data via a REST request.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function refreshFeatureStatuses() {
|
||||
return async ( { dispatch } ) => {
|
||||
try {
|
||||
const result = await apiFetch( {
|
||||
path: REST_REFRESH_FEATURES_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
|
||||
if ( result && result.success ) {
|
||||
// TODO: Review if we can get the updated feature details in the result.data
|
||||
// instead of doing a second refreshMerchantData() request.
|
||||
await dispatch.refreshMerchantData();
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect
|
||||
* Refreshes subscribed webhooks via a REST request
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function resubscribeWebhooks() {
|
||||
return async ( { dispatch } ) => {
|
||||
try {
|
||||
const result = await apiFetch( {
|
||||
method: 'POST',
|
||||
path: REST_WEBHOOKS,
|
||||
} );
|
||||
|
||||
if ( result.success && result.merchant ) {
|
||||
dispatch.hydrate( result );
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Starts webhook simulation.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function startWebhookSimulation() {
|
||||
return async () => {
|
||||
return await apiFetch( {
|
||||
method: 'POST',
|
||||
path: REST_WEBHOOKS_SIMULATE,
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Side effect. Checks webhook simulation.
|
||||
*
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export function checkWebhookSimulationState() {
|
||||
return async () => {
|
||||
return await apiFetch( {
|
||||
path: REST_WEBHOOKS_SIMULATE,
|
||||
} );
|
||||
};
|
||||
}
|
|
@ -7,10 +7,7 @@
|
|||
* @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.
|
||||
|
@ -86,6 +83,42 @@ export const setActiveModal = ( activeModal ) =>
|
|||
export const setActiveHighlight = ( activeHighlight ) =>
|
||||
setTransient( 'activeHighlight', activeHighlight );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the sandbox mode on or off.
|
||||
*
|
||||
* @param {boolean} useSandbox
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSandboxMode = ( useSandbox ) =>
|
||||
setPersistent( 'useSandbox', useSandbox );
|
||||
|
||||
/**
|
||||
* Persistent. Toggles the "Manual Connection" mode on or off.
|
||||
*
|
||||
* @param {boolean} useManualConnection
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualConnectionMode = ( useManualConnection ) =>
|
||||
setPersistent( 'useManualConnection', useManualConnection );
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "webhooks" value.
|
||||
*
|
||||
* @param {string} webhooks
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setWebhooks = ( webhooks ) =>
|
||||
setPersistent( 'webhooks', webhooks );
|
||||
|
||||
/**
|
||||
* Reset merchant details in the store.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const resetMerchant = () => ( { type: ACTION_TYPES.RESET_MERCHANT } );
|
||||
|
||||
// Activity control - see useBusyState() hook.
|
||||
|
||||
/**
|
||||
* Transient (Activity): Marks the start of an async activity
|
||||
* Think of it as "setIsBusy(true)"
|
||||
|
@ -117,198 +150,3 @@ export const stopActivity = ( id ) => ( {
|
|||
type: ACTION_TYPES.STOP_ACTIVITY,
|
||||
payload: { id },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the sandbox mode on or off.
|
||||
*
|
||||
* @param {boolean} useSandbox
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSandboxMode = ( useSandbox ) =>
|
||||
setPersistent( 'useSandbox', useSandbox );
|
||||
|
||||
/**
|
||||
* Persistent. Toggles the "Manual Connection" mode on or off.
|
||||
*
|
||||
* @param {boolean} useManualConnection
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualConnectionMode = ( useManualConnection ) =>
|
||||
setPersistent( 'useManualConnection', useManualConnection );
|
||||
|
||||
/**
|
||||
* Side effect. Saves the persistent details to the WP database.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Fetches the ISU-login URL for a sandbox account.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const sandboxOnboardingUrl = function* () {
|
||||
return yield {
|
||||
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
|
||||
useSandbox: true,
|
||||
products: [ 'EXPRESS_CHECKOUT' ],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Fetches the ISU-login URL for a production account.
|
||||
*
|
||||
* @param {string[]} products Which products/features to display in the ISU popup.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const productionOnboardingUrl = function* ( products = [] ) {
|
||||
return yield {
|
||||
type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
|
||||
useSandbox: false,
|
||||
products,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Initiates a direct connection attempt using the provided client ID and secret.
|
||||
*
|
||||
* This action accepts parameters instead of fetching data from the Redux state because the
|
||||
* values (ID and secret) are not managed by a central redux store, but might come from private
|
||||
* component state.
|
||||
*
|
||||
* @param {string} clientId - AP client ID (always 80-characters, starting with "A").
|
||||
* @param {string} clientSecret - API client secret.
|
||||
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const authenticateWithCredentials = function* (
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox
|
||||
) {
|
||||
return yield {
|
||||
type: ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION,
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Completes the ISU login by authenticating the user via the one time sharedId and
|
||||
* authCode provided by PayPal.
|
||||
*
|
||||
* This action accepts parameters instead of fetching data from the Redux state because all
|
||||
* parameters are dynamically generated during the authentication process, and not managed by our
|
||||
* Redux store.
|
||||
*
|
||||
* @param {string} sharedId - OAuth client ID; called "sharedId" to prevent confusion with the API client ID.
|
||||
* @param {string} authCode - OAuth authorization code provided during onboarding.
|
||||
* @param {boolean} useSandbox - Whether the credentials are for a sandbox account.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const authenticateWithOAuth = function* (
|
||||
sharedId,
|
||||
authCode,
|
||||
useSandbox
|
||||
) {
|
||||
return yield {
|
||||
type: ACTION_TYPES.DO_OAUTH_AUTHENTICATION,
|
||||
sharedId,
|
||||
authCode,
|
||||
useSandbox,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Checks webhook simulation.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const disconnectMerchant = function* () {
|
||||
return yield { type: ACTION_TYPES.DO_DISCONNECT_MERCHANT };
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Clears and refreshes the merchant data via a REST request.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const refreshMerchantData = function* () {
|
||||
const result = yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT };
|
||||
|
||||
if ( result.success && result.merchant ) {
|
||||
yield hydrate( result );
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect.
|
||||
* Purges all feature status data via a REST request.
|
||||
* Refreshes the merchant data via a REST request.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const refreshFeatureStatuses = function* () {
|
||||
const result = yield { type: ACTION_TYPES.DO_REFRESH_FEATURES };
|
||||
|
||||
if ( result && result.success ) {
|
||||
// TODO: Review if we can get the updated feature details in the result.data instead of
|
||||
// doing a second refreshMerchantData() request.
|
||||
yield refreshMerchantData();
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Persistent. Changes the "webhooks" value.
|
||||
*
|
||||
* @param {string} webhooks
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setWebhooks = ( webhooks ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { webhooks },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Side effect
|
||||
* Refreshes subscribed webhooks via a REST request
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const resubscribeWebhooks = function* () {
|
||||
const result = yield { type: ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS };
|
||||
|
||||
if ( result && result.success ) {
|
||||
yield hydrate( result );
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Starts webhook simulation.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const startWebhookSimulation = function* () {
|
||||
return yield { type: ACTION_TYPES.DO_START_WEBHOOK_SIMULATION };
|
||||
};
|
||||
|
||||
/**
|
||||
* Side effect. Checks webhook simulation.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const checkWebhookSimulationState = function* () {
|
||||
return yield { type: ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION };
|
||||
};
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
REST_CONNECTION_URL_PATH,
|
||||
REST_HYDRATE_MERCHANT_PATH,
|
||||
REST_REFRESH_FEATURES_PATH,
|
||||
REST_DIRECT_AUTHENTICATION_PATH,
|
||||
REST_OAUTH_AUTHENTICATION_PATH,
|
||||
REST_DISCONNECT_MERCHANT_PATH,
|
||||
REST_WEBHOOKS,
|
||||
REST_WEBHOOKS_SIMULATE,
|
||||
} from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
console.error( 'Error saving data.', error );
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_GENERATE_ONBOARDING_URL ]( {
|
||||
products,
|
||||
useSandbox,
|
||||
} ) {
|
||||
try {
|
||||
return apiFetch( {
|
||||
path: REST_CONNECTION_URL_PATH,
|
||||
method: 'POST',
|
||||
data: { useSandbox, products },
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION ]( {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
} ) {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_DIRECT_AUTHENTICATION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
clientId,
|
||||
clientSecret,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_OAUTH_AUTHENTICATION ]( {
|
||||
sharedId,
|
||||
authCode,
|
||||
useSandbox,
|
||||
} ) {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_OAUTH_AUTHENTICATION_PATH,
|
||||
method: 'POST',
|
||||
data: {
|
||||
sharedId,
|
||||
authCode,
|
||||
useSandbox,
|
||||
},
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_DISCONNECT_MERCHANT ]() {
|
||||
return await apiFetch( {
|
||||
path: REST_DISCONNECT_MERCHANT_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_REFRESH_MERCHANT ]() {
|
||||
try {
|
||||
return await apiFetch( { path: REST_HYDRATE_MERCHANT_PATH } );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_REFRESH_FEATURES ]() {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_REFRESH_FEATURES_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS ]() {
|
||||
return await apiFetch( {
|
||||
method: 'POST',
|
||||
path: REST_WEBHOOKS,
|
||||
} );
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_START_WEBHOOK_SIMULATION ]() {
|
||||
return await apiFetch( {
|
||||
method: 'POST',
|
||||
path: REST_WEBHOOKS_SIMULATE,
|
||||
} );
|
||||
},
|
||||
|
||||
async [ ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION ]() {
|
||||
return await apiFetch( {
|
||||
path: REST_WEBHOOKS_SIMULATE,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -1,13 +1,12 @@
|
|||
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 thunkActions from './actions-thunk';
|
||||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -18,8 +17,7 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
actions: { ...actions, ...thunkActions },
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
|
|
@ -106,7 +106,8 @@ const commonReducer = createReducer( defaultTransient, defaultPersistent, {
|
|||
return changeTransient( state, { activities: newActivities } );
|
||||
},
|
||||
|
||||
[ ACTION_TYPES.DO_REFRESH_MERCHANT ]: ( state ) => ( {
|
||||
// Instantly reset the merchant data and features before refreshing the details.
|
||||
[ ACTION_TYPES.RESET_MERCHANT ]: ( state ) => ( {
|
||||
...state,
|
||||
merchant: Object.freeze( { ...defaultTransient.merchant } ),
|
||||
features: Object.freeze( { ...defaultTransient.features } ),
|
||||
|
|
|
@ -8,34 +8,37 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH, REST_WEBHOOKS } from './constants';
|
||||
import { REST_HYDRATE_PATH, REST_WEBHOOKS } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const webhooks = yield apiFetch( { path: REST_WEBHOOKS } );
|
||||
const [ result, webhooks ] = await Promise.all( [
|
||||
apiFetch( { path: REST_HYDRATE_PATH } ),
|
||||
apiFetch( { path: REST_WEBHOOKS } ),
|
||||
] );
|
||||
|
||||
if ( webhooks.success && webhooks.data ) {
|
||||
if ( result?.success && webhooks?.success && webhooks.data ) {
|
||||
result.webhooks = webhooks.data;
|
||||
}
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving plugin details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving plugin details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export const addDebugTools = ( context, modules ) => {
|
|||
if ( isConnected ) {
|
||||
// Make sure the Onboarding wizard is "completed".
|
||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||
onboarding.setCompleted( true );
|
||||
onboarding.setPersistent( 'completed', true );
|
||||
onboarding.persist();
|
||||
|
||||
// Reset all stores, except for the onboarding store.
|
||||
|
@ -102,7 +102,7 @@ export const addDebugTools = ( context, modules ) => {
|
|||
debugApi.onboardingMode = ( state ) => {
|
||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||
|
||||
onboarding.setCompleted( ! state );
|
||||
onboarding.setPersistent( 'completed', ! state );
|
||||
onboarding.persist();
|
||||
};
|
||||
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'ONBOARDING:SET_TRANSIENT',
|
||||
SET_TRANSIENT: 'ppcp/onboarding/SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: 'ONBOARDING:SET_PERSISTENT',
|
||||
RESET: 'ONBOARDING:RESET',
|
||||
HYDRATE: 'ONBOARDING:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'ONBOARDING:DO_PERSIST_DATA',
|
||||
SET_PERSISTENT: 'ppcp/onboarding/SET_PERSISTENT',
|
||||
RESET: 'ppcp/onboarding/RESET',
|
||||
HYDRATE: 'ppcp/onboarding/HYDRATE',
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -69,12 +69,22 @@ export const setPersistent = ( prop, value ) => ( {
|
|||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of onboarding data to the server.
|
||||
* Thunk action creator. Triggers the persistence of onboarding data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/**
|
||||
* 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 } ) {
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -18,7 +16,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,29 +8,27 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving onboarding details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving onboarding details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'PAY_LATER_MESSAGING:SET_TRANSIENT',
|
||||
SET_TRANSIENT: 'ppcp/paylater/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',
|
||||
SET_PERSISTENT: 'ppcp/paylater/SET_PERSISTENT',
|
||||
RESET: 'ppcp/paylater/RESET',
|
||||
HYDRATE: 'ppcp/paylater/HYDRATE',
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -69,12 +69,22 @@ export const setPersistent = ( prop, value ) => ( {
|
|||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -18,7 +16,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,30 +8,30 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.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'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving Pay Later Messaging details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,4 @@ export default {
|
|||
RESET: 'PAYMENT:RESET',
|
||||
HYDRATE: 'PAYMENT:HYDRATE',
|
||||
CHANGE_PAYMENT_SETTING: 'PAYMENT:CHANGE_PAYMENT_SETTING',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'PAYMENT:DO_PERSIST_DATA',
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -81,12 +81,22 @@ export const changePaymentSettings = ( id, props ) => ( {
|
|||
} );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
import { initTodoSync } from '../sync/todo-state-sync';
|
||||
|
||||
/**
|
||||
|
@ -19,7 +17,6 @@ import { initTodoSync } from '../sync/todo-state-sync';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,29 +8,30 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving payment details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving payment details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,10 +31,4 @@ export default {
|
|||
* to set up the initial state with data from the server.
|
||||
*/
|
||||
HYDRATE: 'ppcp/settings/HYDRATE',
|
||||
|
||||
/**
|
||||
* Triggers the persistence of store data to the server.
|
||||
* Used when changes need to be saved to the backend.
|
||||
*/
|
||||
DO_PERSIST_DATA: 'ppcp/settings/DO_PERSIST_DATA',
|
||||
};
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -70,12 +71,22 @@ export const setPersistent = ( prop, value ) => ( {
|
|||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Yields an action with the current persistent data to be saved.
|
||||
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Control handlers for settings store actions.
|
||||
* Each handler maps to an ACTION_TYPE and performs the corresponding async operation.
|
||||
*/
|
||||
export const controls = {
|
||||
/**
|
||||
* Persists settings data to the server via REST API.
|
||||
* Triggered by the DO_PERSIST_DATA action to save settings changes.
|
||||
*
|
||||
* @param {Object} action The action object
|
||||
* @param {Object} action.data The settings data to persist
|
||||
* @return {Promise<Object>} The API response
|
||||
*/
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
return await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -8,15 +8,13 @@
|
|||
*/
|
||||
|
||||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -27,7 +25,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,50 +8,30 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve PayPal settings from the site's REST API.
|
||||
* Hydrates the store with the retrieved data and marks it as ready.
|
||||
*
|
||||
* @generator
|
||||
* @yield {Object} API fetch and dispatch actions
|
||||
*/
|
||||
*persistentData() {
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
// Fetch settings data from REST API
|
||||
const result = yield apiFetch( {
|
||||
path: REST_HYDRATE_PATH,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
} );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
// Update store with retrieved data
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
// Mark store as ready for use
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
// Log detailed error information for debugging
|
||||
console.error( 'Full error details:', {
|
||||
error: e,
|
||||
path: REST_HYDRATE_PATH,
|
||||
store: STORE_NAME,
|
||||
} );
|
||||
|
||||
// Display user-friendly error notice
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving PayPal settings details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving PayPal Settings details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'STYLE:SET_TRANSIENT',
|
||||
SET_TRANSIENT: 'ppcp/style/SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: 'STYLE:SET_PERSISTENT',
|
||||
RESET: 'STYLE:RESET',
|
||||
HYDRATE: 'STYLE:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'STYLE:DO_PERSIST_DATA',
|
||||
SET_PERSISTENT: 'ppcp/style/SET_PERSISTENT',
|
||||
RESET: 'ppcp/style/RESET',
|
||||
HYDRATE: 'ppcp/style/HYDRATE',
|
||||
};
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import { REST_PERSIST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
|
@ -69,12 +69,22 @@ export const setPersistent = ( prop, value ) => ( {
|
|||
export const setIsReady = ( state ) => setTransient( 'isReady', state );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
* @return {Function} The thunk function.
|
||||
*/
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = select.persistentData();
|
||||
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
try {
|
||||
await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
} catch ( e ) {
|
||||
console.error( 'Error saving progress.', e );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -18,7 +16,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -8,29 +8,30 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import { STORE_NAME, REST_HYDRATE_PATH } from './constants';
|
||||
import { REST_HYDRATE_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
*persistentData() {
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function persistentData() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const result = yield apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
const result = await apiFetch( { path: REST_HYDRATE_PATH } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.hydrate( result );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving style-details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving Styling details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,16 +6,11 @@
|
|||
|
||||
export default {
|
||||
// Transient data
|
||||
SET_TRANSIENT: 'TODOS:SET_TRANSIENT',
|
||||
SET_COMPLETED_TODOS: 'TODOS:SET_COMPLETED_TODOS',
|
||||
SET_TRANSIENT: 'ppcp/todos/SET_TRANSIENT',
|
||||
SET_COMPLETED_TODOS: 'ppcp/todos/SET_COMPLETED_TODOS',
|
||||
|
||||
// Persistent data
|
||||
SET_TODOS: 'TODOS:SET_TODOS',
|
||||
SET_DISMISSED_TODOS: 'TODOS:SET_DISMISSED_TODOS',
|
||||
|
||||
// Controls
|
||||
DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS',
|
||||
DO_PERSIST_DATA: 'TODOS:DO_PERSIST_DATA',
|
||||
DO_RESET_DISMISSED_TODOS: 'TODOS:DO_RESET_DISMISSED_TODOS',
|
||||
DO_COMPLETE_ONCLICK: 'TODOS:DO_COMPLETE_ONCLICK',
|
||||
SET_TODOS: 'ppcp/todos/SET_TODOS',
|
||||
SET_DISMISSED_TODOS: 'ppcp/todos/SET_DISMISSED_TODOS',
|
||||
RESET_DISMISSED_TODOS: 'ppcp/todos/RESET_DISMISSED_TODOS',
|
||||
};
|
||||
|
|
|
@ -7,9 +7,15 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
import {
|
||||
REST_COMPLETE_ONCLICK_PATH,
|
||||
REST_PATH,
|
||||
REST_PERSIST_PATH,
|
||||
REST_RESET_DISMISSED_TODOS_PATH,
|
||||
} from './constants';
|
||||
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
|
@ -26,42 +32,76 @@ export const setDismissedTodos = ( dismissedTodos ) => ( {
|
|||
payload: dismissedTodos,
|
||||
} );
|
||||
|
||||
export const fetchTodos = function* () {
|
||||
yield { type: ACTION_TYPES.DO_FETCH_TODOS };
|
||||
};
|
||||
|
||||
export const persist = function* () {
|
||||
const data = yield select( STORE_NAME ).persistentData();
|
||||
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
|
||||
};
|
||||
|
||||
export const resetDismissedTodos = function* () {
|
||||
const result = yield { type: ACTION_TYPES.DO_RESET_DISMISSED_TODOS };
|
||||
|
||||
if ( result && result.success ) {
|
||||
yield setDismissedTodos( [] );
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const setCompletedTodos = ( completedTodos ) => ( {
|
||||
type: ACTION_TYPES.SET_COMPLETED_TODOS,
|
||||
payload: completedTodos,
|
||||
} );
|
||||
|
||||
export const completeOnClick = function* ( todoId ) {
|
||||
const result = yield {
|
||||
type: ACTION_TYPES.DO_COMPLETE_ONCLICK,
|
||||
todoId,
|
||||
// Thunks
|
||||
|
||||
export function fetchTodos() {
|
||||
return async () => {
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
return response?.data || [];
|
||||
};
|
||||
}
|
||||
|
||||
if ( result && result.success ) {
|
||||
// Set transient completed state for visual feedback
|
||||
const currentTransientCompleted =
|
||||
yield select( STORE_NAME ).getCompletedTodos();
|
||||
yield setCompletedTodos( [ ...currentTransientCompleted, todoId ] );
|
||||
}
|
||||
export function persist() {
|
||||
return async ( { select } ) => {
|
||||
const data = await select.persistentData();
|
||||
return await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
export function resetDismissedTodos() {
|
||||
return async ( { dispatch } ) => {
|
||||
try {
|
||||
const result = await apiFetch( {
|
||||
path: REST_RESET_DISMISSED_TODOS_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
|
||||
if ( result && result.success ) {
|
||||
await dispatch.setDismissedTodos( [] );
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function completeOnClick( todoId ) {
|
||||
return async ( { select, dispatch } ) => {
|
||||
try {
|
||||
const result = await apiFetch( {
|
||||
path: REST_COMPLETE_ONCLICK_PATH,
|
||||
method: 'POST',
|
||||
data: { todoId },
|
||||
} );
|
||||
|
||||
if ( result?.success ) {
|
||||
// Set transient completed state for visual feedback
|
||||
const completed = await select.getCompletedTodos();
|
||||
await dispatch.setCompletedTodos( [ ...completed, todoId ] );
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* 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_PATH,
|
||||
REST_PERSIST_PATH,
|
||||
REST_RESET_DISMISSED_TODOS_PATH,
|
||||
REST_COMPLETE_ONCLICK_PATH,
|
||||
} from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_FETCH_TODOS ]() {
|
||||
const response = await apiFetch( {
|
||||
path: REST_PATH,
|
||||
method: 'GET',
|
||||
} );
|
||||
return response?.data || [];
|
||||
},
|
||||
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
|
||||
return await apiFetch( {
|
||||
path: REST_PERSIST_PATH,
|
||||
method: 'POST',
|
||||
data,
|
||||
} );
|
||||
},
|
||||
async [ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]() {
|
||||
try {
|
||||
return await apiFetch( {
|
||||
path: REST_RESET_DISMISSED_TODOS_PATH,
|
||||
method: 'POST',
|
||||
} );
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
async [ ACTION_TYPES.DO_COMPLETE_ONCLICK ]( { todoId } ) {
|
||||
try {
|
||||
const response = await apiFetch( {
|
||||
path: REST_COMPLETE_ONCLICK_PATH,
|
||||
method: 'POST',
|
||||
data: { todoId },
|
||||
} );
|
||||
|
||||
return response;
|
||||
} catch ( e ) {
|
||||
return {
|
||||
success: false,
|
||||
error: e,
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
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';
|
||||
import * as resolvers from './resolvers';
|
||||
|
||||
/**
|
||||
* Initializes and registers the todos store with WordPress data layer.
|
||||
|
@ -18,7 +16,6 @@ import { controls } from './controls';
|
|||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
|
|
|
@ -94,25 +94,10 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
|
|||
* @param {Object} state Current state
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]: ( state ) => {
|
||||
[ ACTION_TYPES.RESET_DISMISSED_TODOS ]: ( state ) => {
|
||||
return changePersistent( state, { dismissedTodos: [] } );
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes persistent state with data from the server
|
||||
*
|
||||
|
|
|
@ -8,27 +8,33 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { apiFetch } from '@wordpress/data-controls';
|
||||
import { STORE_NAME, REST_PATH } from './constants';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
export const resolvers = {
|
||||
*getTodos() {
|
||||
import { REST_PATH } from './constants';
|
||||
|
||||
/**
|
||||
* Retrieve settings from the site's REST API.
|
||||
*/
|
||||
export function getTodos() {
|
||||
return async ( { dispatch, registry } ) => {
|
||||
try {
|
||||
const response = yield apiFetch( { path: REST_PATH } );
|
||||
const response = await apiFetch( { path: REST_PATH } );
|
||||
|
||||
const { todos = [], dismissedTodos = [] } = response?.data || {};
|
||||
|
||||
yield dispatch( STORE_NAME ).setTodos( todos );
|
||||
yield dispatch( STORE_NAME ).setDismissedTodos( dismissedTodos );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
await dispatch.setTodos( todos );
|
||||
await dispatch.setDismissedTodos( dismissedTodos );
|
||||
await dispatch.setIsReady( true );
|
||||
} catch ( e ) {
|
||||
console.error( 'Resolver error:', e );
|
||||
yield dispatch( STORE_NAME ).setIsReady( false );
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__( 'Error retrieving todos.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
await registry
|
||||
.dispatch( 'core/notices' )
|
||||
.createErrorNotice(
|
||||
__(
|
||||
'Error retrieving todos.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ class SettingsModel extends AbstractDataModel {
|
|||
public function set_soft_descriptor( string $descriptor ) : void {
|
||||
$descriptor = $this->sanitizer->sanitize_text( $descriptor );
|
||||
$descriptor = preg_replace( '/[^a-zA-Z0-9\-*. ]/', '', $descriptor ) ?? '';
|
||||
|
||||
$this->data['soft_descriptor'] = substr( $descriptor, 0, 22 );
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue