From d90a893a3897bbc59f232503e47a048cda8e6848 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Thu, 6 Feb 2025 14:20:22 +0100
Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Common=20store:=20Convert?=
=?UTF-8?q?=20to=20thunks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../resources/js/data/common/action-types.js | 32 +-
.../resources/js/data/common/actions-thunk.js | 274 ++++++++++++------
.../resources/js/data/common/controls.js | 154 ----------
.../resources/js/data/common/index.js | 5 +-
.../resources/js/data/common/resolvers.js | 45 +--
5 files changed, 232 insertions(+), 278 deletions(-)
delete mode 100644 modules/ppcp-settings/resources/js/data/common/controls.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
index f33e8a9ee..990ea8759 100644
--- a/modules/ppcp-settings/resources/js/data/common/action-types.js
+++ b/modules/ppcp-settings/resources/js/data/common/action-types.js
@@ -6,26 +6,26 @@
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',
// Activity management (advanced solution that replaces the isBusy state).
- START_ACTIVITY: 'COMMON:START_ACTIVITY',
- STOP_ACTIVITY: 'COMMON:STOP_ACTIVITY',
+ START_ACTIVITY: 'ppcp/common/START_ACTIVITY',
+ STOP_ACTIVITY: 'ppcp/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',
+ DO_PERSIST_DATA: 'ppcp/common/DO_PERSIST_DATA',
+ DO_DIRECT_API_AUTHENTICATION: 'ppcp/common/DO_DIRECT_API_AUTHENTICATION',
+ DO_OAUTH_AUTHENTICATION: 'ppcp/common/DO_OAUTH_AUTHENTICATION',
+ DO_DISCONNECT_MERCHANT: 'ppcp/common/DO_DISCONNECT_MERCHANT',
+ DO_GENERATE_ONBOARDING_URL: 'ppcp/common/DO_GENERATE_ONBOARDING_URL',
+ DO_REFRESH_MERCHANT: 'ppcp/common/DO_REFRESH_MERCHANT',
+ DO_REFRESH_FEATURES: 'ppcp/common/DO_REFRESH_FEATURES',
+ DO_RESUBSCRIBE_WEBHOOKS: 'ppcp/common/DO_RESUBSCRIBE_WEBHOOKS',
+ DO_START_WEBHOOK_SIMULATION: 'ppcp/common/DO_START_WEBHOOK_SIMULATION',
+ DO_CHECK_WEBHOOK_SIMULATION: 'ppcp/common/DO_CHECK_WEBHOOK_SIMULATION',
};
diff --git a/modules/ppcp-settings/resources/js/data/common/actions-thunk.js b/modules/ppcp-settings/resources/js/data/common/actions-thunk.js
index 240f78f21..6a8a9fc26 100644
--- a/modules/ppcp-settings/resources/js/data/common/actions-thunk.js
+++ b/modules/ppcp-settings/resources/js/data/common/actions-thunk.js
@@ -1,46 +1,83 @@
-import { select } from '@wordpress/data';
-
-import ACTION_TYPES from './action-types';
-import { hydrate } from './actions';
-import { STORE_NAME } from './constants';
+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';
+import apiFetch from '@wordpress/api-fetch';
/**
* Side effect. Saves the persistent details to the WP database.
*
- * @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 };
-};
+ await apiFetch( {
+ path: REST_PERSIST_PATH,
+ method: 'POST',
+ data,
+ } );
+ };
+}
/**
* Side effect. Fetches the ISU-login URL for a sandbox account.
*
- * @return {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const sandboxOnboardingUrl = function* () {
- return yield {
- type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
- useSandbox: true,
- products: [ 'EXPRESS_CHECKOUT' ],
+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 {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const productionOnboardingUrl = function* ( products = [] ) {
- return yield {
- type: ACTION_TYPES.DO_GENERATE_ONBOARDING_URL,
- useSandbox: false,
- products,
+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.
@@ -52,20 +89,32 @@ export const productionOnboardingUrl = function* ( products = [] ) {
* @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.
+ * @return {Function} The thunk function.
*/
-export const authenticateWithCredentials = function* (
+export function authenticateWithCredentials(
clientId,
clientSecret,
useSandbox
) {
- return yield {
- type: ACTION_TYPES.DO_DIRECT_API_AUTHENTICATION,
- 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
@@ -75,97 +124,156 @@ export const authenticateWithCredentials = function* (
* 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} 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.
+ * @return {Function} The thunk function.
*/
-export const authenticateWithOAuth = function* (
- sharedId,
- authCode,
- useSandbox
-) {
- return yield {
- type: ACTION_TYPES.DO_OAUTH_AUTHENTICATION,
- sharedId,
- authCode,
- useSandbox,
+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 {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const disconnectMerchant = function* () {
- return yield { type: ACTION_TYPES.DO_DISCONNECT_MERCHANT };
-};
+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 {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const refreshMerchantData = function* () {
- const result = yield { type: ACTION_TYPES.DO_REFRESH_MERCHANT };
+export function refreshMerchantData() {
+ return async ( { dispatch } ) => {
+ try {
+ const result = await apiFetch( {
+ path: REST_HYDRATE_MERCHANT_PATH,
+ } );
- if ( result.success && result.merchant ) {
- yield hydrate( result );
- }
+ if ( result.success && result.merchant ) {
+ dispatch.hydrate( result );
+ }
- return 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 {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const refreshFeatureStatuses = function* () {
- const result = yield { type: ACTION_TYPES.DO_REFRESH_FEATURES };
+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.
- yield refreshMerchantData();
- }
+ 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;
-};
+ return result;
+ } catch ( e ) {
+ return {
+ success: false,
+ error: e,
+ message: e.message,
+ };
+ }
+ };
+}
/**
* Side effect
* Refreshes subscribed webhooks via a REST request
*
- * @return {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const resubscribeWebhooks = function* () {
- const result = yield { type: ACTION_TYPES.DO_RESUBSCRIBE_WEBHOOKS };
+export function resubscribeWebhooks() {
+ return async ( { dispatch } ) => {
+ try {
+ const result = await apiFetch( {
+ method: 'POST',
+ path: REST_WEBHOOKS,
+ } );
- if ( result && result.success ) {
- yield hydrate( result );
- }
+ if ( result.success && result.merchant ) {
+ dispatch.hydrate( result );
+ }
- return result;
-};
+ return result;
+ } catch ( e ) {
+ return {
+ success: false,
+ error: e,
+ };
+ }
+ };
+}
/**
* Side effect. Starts webhook simulation.
*
- * @return {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const startWebhookSimulation = function* () {
- return yield { type: ACTION_TYPES.DO_START_WEBHOOK_SIMULATION };
-};
+export function startWebhookSimulation() {
+ return async () => {
+ return await apiFetch( {
+ method: 'POST',
+ path: REST_WEBHOOKS_SIMULATE,
+ } );
+ };
+}
/**
* Side effect. Checks webhook simulation.
*
- * @return {Action} The action.
+ * @return {Function} The thunk function.
*/
-export const checkWebhookSimulationState = function* () {
- return yield { type: ACTION_TYPES.DO_CHECK_WEBHOOK_SIMULATION };
-};
+export function checkWebhookSimulationState() {
+ return async () => {
+ return await apiFetch( {
+ path: REST_WEBHOOKS_SIMULATE,
+ } );
+ };
+}
diff --git a/modules/ppcp-settings/resources/js/data/common/controls.js b/modules/ppcp-settings/resources/js/data/common/controls.js
deleted file mode 100644
index a318e2116..000000000
--- a/modules/ppcp-settings/resources/js/data/common/controls.js
+++ /dev/null
@@ -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,
- } );
- },
-};
diff --git a/modules/ppcp-settings/resources/js/data/common/index.js b/modules/ppcp-settings/resources/js/data/common/index.js
index 9e4f22782..89a554b0e 100644
--- a/modules/ppcp-settings/resources/js/data/common/index.js
+++ b/modules/ppcp-settings/resources/js/data/common/index.js
@@ -1,5 +1,4 @@
import { createReduxStore, register } from '@wordpress/data';
-import { controls as wpControls } from '@wordpress/data-controls';
import { STORE_NAME } from './constants';
import reducer from './reducer';
@@ -7,8 +6,7 @@ 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.
@@ -19,7 +17,6 @@ import { controls } from './controls';
export const initStore = () => {
const store = createReduxStore( STORE_NAME, {
reducer,
- controls: { ...wpControls, ...controls },
actions: { ...actions, ...thunkActions },
selectors,
resolvers,
diff --git a/modules/ppcp-settings/resources/js/data/common/resolvers.js b/modules/ppcp-settings/resources/js/data/common/resolvers.js
index 2c1f0d3c2..9fc8428c0 100644
--- a/modules/ppcp-settings/resources/js/data/common/resolvers.js
+++ b/modules/ppcp-settings/resources/js/data/common/resolvers.js
@@ -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'
+ )
+ );
}
- },
-};
+ };
+}