mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 12:25:15 +08:00
Merge trunk
This commit is contained in:
commit
6ec624d37b
77 changed files with 3278 additions and 1090 deletions
|
@ -36,28 +36,37 @@ export const hydrate = ( payload ) => ( {
|
|||
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 ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Sets a sample value.
|
||||
* TODO: Replace with a real action/property.
|
||||
*
|
||||
* @param {string} value
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSampleValue = ( value ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { sampleValue: value },
|
||||
} );
|
||||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
|
|
|
@ -7,39 +7,24 @@
|
|||
* @file
|
||||
*/
|
||||
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
import { createHooksForStore } from '../utils';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const useHooks = () => {
|
||||
const {
|
||||
persist,
|
||||
|
||||
// TODO: Replace with real property.
|
||||
setSampleValue,
|
||||
} = 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' );
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
|
||||
// Persistent accessors.
|
||||
// TODO: Replace with real property.
|
||||
const sampleValue = usePersistent( 'sampleValue' );
|
||||
const [ sampleValue, setSampleValue ] = usePersistent( 'sampleValue' );
|
||||
|
||||
return {
|
||||
persist,
|
||||
|
|
|
@ -36,16 +36,37 @@ export const hydrate = ( payload ) => ( {
|
|||
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 onboarding details as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Transient. Sets the active settings tab.
|
||||
|
@ -53,21 +74,8 @@ export const setIsReady = ( isReady ) => ( {
|
|||
* @param {string} activeModal
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setActiveModal = ( activeModal ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { activeModal },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Changes the "saving" flag.
|
||||
*
|
||||
* @param {boolean} isSaving
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsSaving = ( isSaving ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isSaving },
|
||||
} );
|
||||
export const setActiveModal = ( activeModal ) =>
|
||||
setTransient( 'activeModal', activeModal );
|
||||
|
||||
/**
|
||||
* Transient (Activity): Marks the start of an async activity
|
||||
|
@ -107,10 +115,8 @@ export const stopActivity = ( id ) => ( {
|
|||
* @param {boolean} useSandbox
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSandboxMode = ( useSandbox ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { useSandbox },
|
||||
} );
|
||||
export const setSandboxMode = ( useSandbox ) =>
|
||||
setPersistent( 'useSandbox', useSandbox );
|
||||
|
||||
/**
|
||||
* Persistent. Toggles the "Manual Connection" mode on or off.
|
||||
|
@ -118,10 +124,8 @@ export const setSandboxMode = ( useSandbox ) => ( {
|
|||
* @param {boolean} useManualConnection
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualConnectionMode = ( useManualConnection ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { useManualConnection },
|
||||
} );
|
||||
export const setManualConnectionMode = ( useManualConnection ) =>
|
||||
setPersistent( 'useManualConnection', useManualConnection );
|
||||
|
||||
/**
|
||||
* Side effect. Saves the persistent details to the WP database.
|
||||
|
|
|
@ -9,41 +9,31 @@
|
|||
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
|
||||
import { createHooksForStore } from '../utils';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const useHooks = () => {
|
||||
const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||
const {
|
||||
persist,
|
||||
setSandboxMode,
|
||||
setManualConnectionMode,
|
||||
sandboxOnboardingUrl,
|
||||
productionOnboardingUrl,
|
||||
authenticateWithCredentials,
|
||||
authenticateWithOAuth,
|
||||
setActiveModal,
|
||||
startWebhookSimulation,
|
||||
checkWebhookSimulationState,
|
||||
} = useDispatch( STORE_NAME );
|
||||
|
||||
// Transient accessors.
|
||||
const isReady = useTransient( 'isReady' );
|
||||
const activeModal = useTransient( 'activeModal' );
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
|
||||
|
||||
// Persistent accessors.
|
||||
const isSandboxMode = usePersistent( 'useSandbox' );
|
||||
const isManualConnectionMode = usePersistent( 'useManualConnection' );
|
||||
const [ isSandboxMode, setSandboxMode ] = usePersistent( 'useSandbox' );
|
||||
const [ isManualConnectionMode, setManualConnectionMode ] = usePersistent(
|
||||
'useManualConnection'
|
||||
);
|
||||
const merchant = useSelect(
|
||||
( select ) => select( STORE_NAME ).merchant(),
|
||||
[]
|
||||
|
|
10
modules/ppcp-settings/resources/js/data/configuration.js
Normal file
10
modules/ppcp-settings/resources/js/data/configuration.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export { BUSINESS_TYPES, PRODUCT_TYPES } from './onboarding/configuration';
|
||||
|
||||
export {
|
||||
STYLING_LOCATIONS,
|
||||
STYLING_PAYMENT_METHODS,
|
||||
STYLING_LABELS,
|
||||
STYLING_COLORS,
|
||||
STYLING_LAYOUTS,
|
||||
STYLING_SHAPES,
|
||||
} from './styling/configuration';
|
|
@ -1,20 +1,24 @@
|
|||
import { addDebugTools } from './debug';
|
||||
import * as Onboarding from './onboarding';
|
||||
import * as Common from './common';
|
||||
import * as Styling from './styling';
|
||||
import * as Payment from './payment';
|
||||
|
||||
Onboarding.initStore();
|
||||
Common.initStore();
|
||||
Payment.initStore();
|
||||
Styling.initStore();
|
||||
|
||||
export const OnboardingHooks = Onboarding.hooks;
|
||||
export const CommonHooks = Common.hooks;
|
||||
export const PaymentHooks = Payment.hooks;
|
||||
export const StylingHooks = Styling.hooks;
|
||||
|
||||
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
||||
export const CommonStoreName = Common.STORE_NAME;
|
||||
export const PaymentStoreName = Payment.STORE_NAME;
|
||||
export const StylingStoreName = Styling.STORE_NAME;
|
||||
|
||||
export * from './constants';
|
||||
export * from './configuration';
|
||||
|
||||
addDebugTools( window.ppcpSettings, [ Onboarding, Common, Payment ] );
|
||||
addDebugTools( window.ppcpSettings, [ Onboarding, Common, Payment, Styling ] );
|
||||
|
|
|
@ -36,16 +36,37 @@ export const hydrate = ( payload ) => ( {
|
|||
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 onboarding details as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* Transient. Sets the "manualClientId" value.
|
||||
|
@ -53,10 +74,8 @@ export const setIsReady = ( isReady ) => ( {
|
|||
* @param {string} manualClientId
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualClientId = ( manualClientId ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { manualClientId },
|
||||
} );
|
||||
export const setManualClientId = ( manualClientId ) =>
|
||||
setTransient( 'manualClientId', manualClientId );
|
||||
|
||||
/**
|
||||
* Transient. Sets the "manualClientSecret" value.
|
||||
|
@ -64,10 +83,8 @@ export const setManualClientId = ( manualClientId ) => ( {
|
|||
* @param {string} manualClientSecret
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setManualClientSecret = ( manualClientSecret ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { manualClientSecret },
|
||||
} );
|
||||
export const setManualClientSecret = ( manualClientSecret ) =>
|
||||
setTransient( 'manualClientSecret', manualClientSecret );
|
||||
|
||||
/**
|
||||
* Persistent.Set the "onboarding completed" flag which shows or hides the wizard.
|
||||
|
@ -75,10 +92,8 @@ export const setManualClientSecret = ( manualClientSecret ) => ( {
|
|||
* @param {boolean} completed
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setCompleted = ( completed ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { completed },
|
||||
} );
|
||||
export const setCompleted = ( completed ) =>
|
||||
setPersistent( 'completed', completed );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the onboarding wizard to a new step.
|
||||
|
@ -86,10 +101,7 @@ export const setCompleted = ( completed ) => ( {
|
|||
* @param {number} step
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setStep = ( step ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { step },
|
||||
} );
|
||||
export const setStep = ( step ) => setPersistent( 'step', step );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the "isCasualSeller" value.
|
||||
|
@ -97,10 +109,8 @@ export const setStep = ( step ) => ( {
|
|||
* @param {boolean} isCasualSeller
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsCasualSeller = ( isCasualSeller ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { isCasualSeller },
|
||||
} );
|
||||
export const setIsCasualSeller = ( isCasualSeller ) =>
|
||||
setPersistent( 'isCasualSeller', isCasualSeller );
|
||||
|
||||
/**
|
||||
* Persistent. Sets the "areOptionalPaymentMethodsEnabled" value.
|
||||
|
@ -110,10 +120,11 @@ export const setIsCasualSeller = ( isCasualSeller ) => ( {
|
|||
*/
|
||||
export const setAreOptionalPaymentMethodsEnabled = (
|
||||
areOptionalPaymentMethodsEnabled
|
||||
) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { areOptionalPaymentMethodsEnabled },
|
||||
} );
|
||||
) =>
|
||||
setPersistent(
|
||||
'areOptionalPaymentMethodsEnabled',
|
||||
areOptionalPaymentMethodsEnabled
|
||||
);
|
||||
|
||||
/**
|
||||
* Persistent. Sets the "products" array.
|
||||
|
@ -121,10 +132,8 @@ export const setAreOptionalPaymentMethodsEnabled = (
|
|||
* @param {string[]} products
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setProducts = ( products ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { products },
|
||||
} );
|
||||
export const setProducts = ( products ) =>
|
||||
setPersistent( 'products', products );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of onboarding data to the server.
|
||||
|
|
|
@ -1,8 +1,24 @@
|
|||
/**
|
||||
* Configuration for UI components.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Onboarding options for StepBusiness
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const BUSINESS_TYPES = {
|
||||
CASUAL_SELLER: 'casual_seller',
|
||||
BUSINESS: 'business',
|
||||
};
|
||||
|
||||
/**
|
||||
* Onboarding options for StepProducts
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
export const PRODUCT_TYPES = {
|
||||
VIRTUAL: 'virtual',
|
||||
PHYSICAL: 'physical',
|
|
@ -8,7 +8,7 @@
|
|||
export const STORE_NAME = 'wc/paypal/onboarding';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB..
|
||||
* REST path to hydrate data of this module by loading data from the WP DB.
|
||||
*
|
||||
* Used by: Resolvers
|
||||
* See: OnboardingRestEndpoint.php
|
||||
|
|
|
@ -9,32 +9,14 @@
|
|||
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
import { PRODUCT_TYPES } from '../constants';
|
||||
import { createHooksForStore } from '../utils';
|
||||
import { PRODUCT_TYPES } from './configuration';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const useHooks = () => {
|
||||
const {
|
||||
persist,
|
||||
setStep,
|
||||
setCompleted,
|
||||
setIsCasualSeller,
|
||||
setManualClientId,
|
||||
setManualClientSecret,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
setProducts,
|
||||
} = useDispatch( STORE_NAME );
|
||||
const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||
|
||||
const { persist } = useDispatch( STORE_NAME );
|
||||
|
||||
// Read-only flags and derived state.
|
||||
const flags = useSelect( ( select ) => select( STORE_NAME ).flags(), [] );
|
||||
|
@ -44,18 +26,22 @@ const useHooks = () => {
|
|||
);
|
||||
|
||||
// Transient accessors.
|
||||
const isReady = useTransient( 'isReady' );
|
||||
const manualClientId = useTransient( 'manualClientId' );
|
||||
const manualClientSecret = useTransient( 'manualClientSecret' );
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
const [ manualClientId, setManualClientId ] =
|
||||
useTransient( 'manualClientId' );
|
||||
const [ manualClientSecret, setManualClientSecret ] =
|
||||
useTransient( 'manualClientSecret' );
|
||||
|
||||
// Persistent accessors.
|
||||
const step = usePersistent( 'step' );
|
||||
const completed = usePersistent( 'completed' );
|
||||
const isCasualSeller = usePersistent( 'isCasualSeller' );
|
||||
const areOptionalPaymentMethodsEnabled = usePersistent(
|
||||
'areOptionalPaymentMethodsEnabled'
|
||||
);
|
||||
const products = usePersistent( 'products' );
|
||||
const [ step, setStep ] = usePersistent( 'step' );
|
||||
const [ completed, setCompleted ] = usePersistent( 'completed' );
|
||||
const [ isCasualSeller, setIsCasualSeller ] =
|
||||
usePersistent( 'isCasualSeller' );
|
||||
const [
|
||||
areOptionalPaymentMethodsEnabled,
|
||||
setAreOptionalPaymentMethodsEnabled,
|
||||
] = usePersistent( 'areOptionalPaymentMethodsEnabled' );
|
||||
const [ products, setProducts ] = usePersistent( 'products' );
|
||||
|
||||
const savePersistent = async ( setter, value ) => {
|
||||
setter( value );
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Settings action types
|
||||
*
|
||||
* Defines the constants used for dispatching actions in the settings store.
|
||||
* Each constant represents a unique action type that can be handled by reducers.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Represents setting transient (temporary) state data.
|
||||
* These values are not persisted and will reset on page reload.
|
||||
*/
|
||||
SET_TRANSIENT: 'ppcp/settings/SET_TRANSIENT',
|
||||
|
||||
/**
|
||||
* Represents setting persistent state data.
|
||||
* These values are meant to be saved to the server and persist between page loads.
|
||||
*/
|
||||
SET_PERSISTENT: 'ppcp/settings/SET_PERSISTENT',
|
||||
|
||||
/**
|
||||
* Resets the store state to its initial values.
|
||||
* Used when needing to clear all settings data.
|
||||
*/
|
||||
RESET: 'ppcp/settings/RESET',
|
||||
|
||||
/**
|
||||
* Initializes the store with data, typically used during store initialization
|
||||
* 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',
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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. Sets the full store details during app initialization.
|
||||
*
|
||||
* @param {Object} payload Initial store data
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const hydrate = ( payload ) => ( {
|
||||
type: ACTION_TYPES.HYDRATE,
|
||||
payload,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Marks the store as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady Whether the store is ready
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent. Updates the settings data in the store.
|
||||
*
|
||||
* @param {Object} settings The settings object to store
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setSettings = ( settings ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: settings,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Side effect. Triggers the persistence of store data to the server.
|
||||
* Yields an action with the current persistent data to be saved.
|
||||
*
|
||||
* @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/settings';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB.
|
||||
*
|
||||
* Used by: Resolvers
|
||||
* See: SettingsRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/settings';
|
||||
|
||||
/**
|
||||
* REST path to persist data of this module to the WP DB.
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: SettingsRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/settings';
|
34
modules/ppcp-settings/resources/js/data/settings-tab/controls.js
vendored
Normal file
34
modules/ppcp-settings/resources/js/data/settings-tab/controls.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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 { useSelect, useDispatch } from '@wordpress/data';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useTransient = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).transientData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const usePersistent = ( key ) =>
|
||||
useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData()?.[ key ],
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const useHooks = () => {
|
||||
const { persist, setSettings } = useDispatch( STORE_NAME );
|
||||
|
||||
// Read-only flags and derived state.
|
||||
const isReady = useTransient( 'isReady' );
|
||||
|
||||
// Persistent accessors.
|
||||
const settings = useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData(),
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
persist,
|
||||
isReady,
|
||||
settings,
|
||||
setSettings,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSettingsState = () => {
|
||||
const { persist, isReady } = useHooks();
|
||||
return { persist, isReady };
|
||||
};
|
||||
|
||||
export const useSettings = () => {
|
||||
const { settings, setSettings } = useHooks();
|
||||
return {
|
||||
settings,
|
||||
setSettings,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Store Configuration: Defines and registers the settings data store.
|
||||
*
|
||||
* Creates a Redux-style store with WordPress data layer integration.
|
||||
* Combines reducers, actions, selectors and controls into a unified store.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
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 = () => {
|
||||
try {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
register( store );
|
||||
|
||||
// Verify store registration
|
||||
const isStoreRegistered = Boolean( wp.data.select( STORE_NAME ) );
|
||||
if ( ! isStoreRegistered ) {
|
||||
console.error( 'Store registration verification failed' );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to initialize settings store:', error );
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
107
modules/ppcp-settings/resources/js/data/settings-tab/reducer.js
Normal file
107
modules/ppcp-settings/resources/js/data/settings-tab/reducer.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* 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, createSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
// Store structure.
|
||||
|
||||
/**
|
||||
* Transient: Values that are _not_ saved to the DB (like app lifecycle-flags).
|
||||
* These reset on page reload.
|
||||
*/
|
||||
const defaultTransient = Object.freeze( {
|
||||
isReady: false,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent: Values that are loaded from and saved to the DB.
|
||||
* These represent the core PayPal payment settings configuration.
|
||||
*/
|
||||
const defaultPersistent = Object.freeze( {
|
||||
invoicePrefix: '', // Prefix for PayPal invoice IDs
|
||||
authorizeOnly: false, // Whether to only authorize payments initially
|
||||
captureVirtualOnlyOrders: false, // Auto-capture virtual-only orders
|
||||
savePaypalAndVenmo: false, // Enable PayPal & Venmo vaulting
|
||||
saveCreditCardAndDebitCard: false, // Enable card vaulting
|
||||
payNowExperience: false, // Enable Pay Now experience
|
||||
sandboxAccountCredentials: false, // Use sandbox credentials
|
||||
sandboxMode: null, // Sandbox mode configuration
|
||||
sandboxEnabled: false, // Whether sandbox mode is active
|
||||
sandboxClientId: '', // Sandbox API client ID
|
||||
sandboxSecretKey: '', // Sandbox API secret key
|
||||
sandboxConnected: false, // Sandbox connection status
|
||||
logging: false, // Enable debug logging
|
||||
subtotalMismatchFallback: null, // Handling for subtotal mismatches
|
||||
brandName: '', // Merchant brand name for PayPal
|
||||
softDescriptor: '', // Payment descriptor on statements
|
||||
paypalLandingPage: null, // PayPal checkout landing page
|
||||
buttonLanguage: '', // Language for PayPal buttons
|
||||
} );
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ setTransient, setPersistent ] = createSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
/**
|
||||
* Reducer implementation mapping actions to state updates.
|
||||
*/
|
||||
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
/**
|
||||
* Updates temporary state values
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) => {
|
||||
return setTransient( state, payload );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates persistent configuration values
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
|
||||
setPersistent( state, payload ),
|
||||
|
||||
/**
|
||||
* Resets state to defaults while maintaining initialization status
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @return {Object} Reset state
|
||||
*/
|
||||
[ ACTION_TYPES.RESET ]: ( state ) => {
|
||||
const cleanState = setTransient(
|
||||
setPersistent( state, defaultPersistent ),
|
||||
defaultTransient
|
||||
);
|
||||
cleanState.isReady = true; // Keep initialization flag
|
||||
return cleanState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes persistent state with data from the server
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Hydration payload containing server data
|
||||
* @param {Object} payload.data The settings data to hydrate
|
||||
* @return {Object} Hydrated state
|
||||
*/
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||
setPersistent( state, payload.data ),
|
||||
} );
|
||||
|
||||
export default reducer;
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 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() {
|
||||
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',
|
||||
},
|
||||
} );
|
||||
|
||||
// Update store with retrieved data
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
// Mark store as ready for use
|
||||
yield dispatch( STORE_NAME ).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'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Empty frozen object used as fallback when state is undefined.
|
||||
*
|
||||
* @constant
|
||||
* @type {Object}
|
||||
*/
|
||||
const EMPTY_OBJ = Object.freeze( {} );
|
||||
|
||||
/**
|
||||
* Base selector that ensures a valid state object.
|
||||
*
|
||||
* @param {Object|undefined} state The current state
|
||||
* @return {Object} The state or empty object if undefined
|
||||
*/
|
||||
export const getState = ( state ) => state || EMPTY_OBJ;
|
||||
|
||||
/**
|
||||
* Retrieves persistent (saved) data from the store.
|
||||
*
|
||||
* @param {Object} state The current state
|
||||
* @return {Object} The persistent data or empty object if undefined
|
||||
*/
|
||||
export const persistentData = ( state ) => {
|
||||
return getState( state ).data || EMPTY_OBJ;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves transient (temporary) data from the store.
|
||||
* Excludes persistent data stored in the 'data' property.
|
||||
*
|
||||
* @param {Object} state The current state
|
||||
* @return {Object} The transient state or empty object if undefined
|
||||
*/
|
||||
export const transientData = ( state ) => {
|
||||
const { data, ...transientState } = getState( state );
|
||||
return transientState || EMPTY_OBJ;
|
||||
};
|
|
@ -1,162 +0,0 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
const cartAndExpressCheckoutSettings = {
|
||||
paymentMethods: [],
|
||||
style: {
|
||||
shape: 'pill',
|
||||
label: 'paypal',
|
||||
color: 'gold',
|
||||
},
|
||||
};
|
||||
|
||||
const settings = {
|
||||
paymentMethods: [],
|
||||
style: {
|
||||
layout: 'vertical',
|
||||
shape: cartAndExpressCheckoutSettings.style.shape,
|
||||
label: cartAndExpressCheckoutSettings.style.label,
|
||||
color: cartAndExpressCheckoutSettings.style.color,
|
||||
tagline: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultLocationSettings = {
|
||||
cart: {
|
||||
value: 'cart',
|
||||
label: __( 'Cart', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...cartAndExpressCheckoutSettings },
|
||||
// translators: %s: Link to Cart page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Cart page</a> and select which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'classic-checkout': {
|
||||
value: 'classic-checkout',
|
||||
label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Classic Checkout page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Classic Checkout page</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'express-checkout': {
|
||||
value: 'express-checkout',
|
||||
label: __( 'Express Checkout', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...cartAndExpressCheckoutSettings },
|
||||
// translators: %s: Link to Express Checkout location
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Express Checkout location</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'mini-cart': {
|
||||
value: 'mini-cart',
|
||||
label: __( 'Mini Cart', 'woocommerce-paypel-payements' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Mini Cart
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Mini Cart</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
'product-page': {
|
||||
value: 'product-page',
|
||||
label: __( 'Product Page', 'woocommerce-paypal-payments' ),
|
||||
settings: { ...settings },
|
||||
// translators: %s: Link to Product Page
|
||||
description: __(
|
||||
'Customize the appearance of the PayPal smart buttons on the <a href="%s">[MISSING LINK]Product Page</a> and choose which additional payment buttons to display in this location.',
|
||||
'wooocommerce-paypal-payments'
|
||||
),
|
||||
descriptionLink: '#',
|
||||
},
|
||||
};
|
||||
|
||||
export const paymentMethodOptions = [
|
||||
{
|
||||
value: 'venmo',
|
||||
label: __( 'Venmo', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'paylater',
|
||||
label: __( 'Pay Later', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'googlepay',
|
||||
label: __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'applepay',
|
||||
label: __( 'Apple Pay', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
];
|
||||
|
||||
export const buttonLabelOptions = [
|
||||
{
|
||||
value: 'paypal',
|
||||
label: __( 'PayPal', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'checkout',
|
||||
label: __( 'Checkout', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'buynow',
|
||||
label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'pay',
|
||||
label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
];
|
||||
|
||||
export const colorOptions = [
|
||||
{
|
||||
value: 'gold',
|
||||
label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'blue',
|
||||
label: __( 'Blue', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'silver',
|
||||
label: __( 'Silver', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'black',
|
||||
label: __( 'Black', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'white',
|
||||
label: __( 'White', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
];
|
||||
|
||||
export const buttonLayoutOptions = [
|
||||
{
|
||||
label: __( 'Vertical', 'woocommerce-paypal-payments' ),
|
||||
value: 'vertical',
|
||||
},
|
||||
{
|
||||
label: __( 'Horizontal', 'woocommerce-paypal-payments' ),
|
||||
value: 'horizontal',
|
||||
},
|
||||
];
|
||||
|
||||
export const shapeOptions = [
|
||||
{
|
||||
value: 'pill',
|
||||
label: __( 'Pill', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
{
|
||||
value: 'rect',
|
||||
label: __( 'Rectangle', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
];
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: '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',
|
||||
};
|
80
modules/ppcp-settings/resources/js/data/styling/actions.js
Normal file
80
modules/ppcp-settings/resources/js/data/styling/actions.js
Normal file
|
@ -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. Changes the "ready-state" of the module.
|
||||
*
|
||||
* @param {boolean} state Whether the store is ready to be used.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( state ) => setTransient( 'isReady', state );
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
};
|
131
modules/ppcp-settings/resources/js/data/styling/configuration.js
Normal file
131
modules/ppcp-settings/resources/js/data/styling/configuration.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* Configuration for UI components.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
export const STYLING_LOCATIONS = {
|
||||
cart: {
|
||||
value: 'cart',
|
||||
label: __( 'Cart', 'woocommerce-paypal-payments' ),
|
||||
link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-cart',
|
||||
props: { layout: false, tagline: false },
|
||||
},
|
||||
classicCheckout: {
|
||||
value: 'classicCheckout',
|
||||
label: __( 'Classic Checkout', 'woocommerce-paypal-payments' ),
|
||||
link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-checkout',
|
||||
props: { layout: true, tagline: true },
|
||||
},
|
||||
expressCheckout: {
|
||||
value: 'expressCheckout',
|
||||
label: __( 'Express Checkout', 'woocommerce-paypal-payments' ),
|
||||
link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-block-express-checkout',
|
||||
props: { layout: false, tagline: false },
|
||||
},
|
||||
miniCart: {
|
||||
value: 'miniCart',
|
||||
label: __( 'Mini Cart', 'woocommerce-paypel-payements' ),
|
||||
link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-mini-cart',
|
||||
props: { layout: true, tagline: true },
|
||||
},
|
||||
product: {
|
||||
value: 'product',
|
||||
label: __( 'Product Page', 'woocommerce-paypal-payments' ),
|
||||
link: 'https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-single-product',
|
||||
props: { layout: true, tagline: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const STYLING_LABELS = {
|
||||
paypal: {
|
||||
value: 'paypal',
|
||||
label: __( 'PayPal', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
checkout: {
|
||||
value: 'checkout',
|
||||
label: __( 'Checkout', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
buynow: {
|
||||
value: 'buynow',
|
||||
label: __( 'PayPal Buy Now', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
pay: {
|
||||
value: 'pay',
|
||||
label: __( 'Pay with PayPal', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
};
|
||||
|
||||
export const STYLING_COLORS = {
|
||||
gold: {
|
||||
value: 'gold',
|
||||
label: __( 'Gold (Recommended)', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
blue: {
|
||||
value: 'blue',
|
||||
label: __( 'Blue', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
silver: {
|
||||
value: 'silver',
|
||||
label: __( 'Silver', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
black: {
|
||||
value: 'black',
|
||||
label: __( 'Black', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
white: {
|
||||
value: 'white',
|
||||
label: __( 'White', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
};
|
||||
|
||||
export const STYLING_LAYOUTS = {
|
||||
vertical: {
|
||||
value: 'vertical',
|
||||
label: __( 'Vertical', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
horizontal: {
|
||||
value: 'horizontal',
|
||||
label: __( 'Horizontal', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
};
|
||||
|
||||
export const STYLING_SHAPES = {
|
||||
rect: {
|
||||
value: 'rect',
|
||||
label: __( 'Rectangle', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
pill: {
|
||||
value: 'pill',
|
||||
label: __( 'Pill', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
};
|
||||
|
||||
export const STYLING_PAYMENT_METHODS = {
|
||||
paypal: {
|
||||
value: '',
|
||||
label: __( 'PayPal', 'woocommerce-paypal-payments' ),
|
||||
checked: true,
|
||||
disabled: true,
|
||||
},
|
||||
venmo: {
|
||||
value: 'venmo',
|
||||
label: __( 'Venmo', 'woocommerce-paypal-payments' ),
|
||||
isFunding: true,
|
||||
},
|
||||
paylater: {
|
||||
value: 'paylater',
|
||||
label: __( 'Pay Later', 'woocommerce-paypal-payments' ),
|
||||
isFunding: true,
|
||||
},
|
||||
googlepay: {
|
||||
value: 'googlepay',
|
||||
label: __( 'Google Pay', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
applepay: {
|
||||
value: 'applepay',
|
||||
label: __( 'Apple Pay', 'woocommerce-paypal-payments' ),
|
||||
},
|
||||
};
|
28
modules/ppcp-settings/resources/js/data/styling/constants.js
Normal file
28
modules/ppcp-settings/resources/js/data/styling/constants.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Name of the Redux store module.
|
||||
*
|
||||
* Used by: Reducer, Selector, Index
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const STORE_NAME = 'wc/paypal/style';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB.
|
||||
*
|
||||
* Used by: Resolvers
|
||||
* See: StylingRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/styling';
|
||||
|
||||
/**
|
||||
* REST path to persist data of this module to the WP DB.
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: StylingRestEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/styling';
|
23
modules/ppcp-settings/resources/js/data/styling/controls.js
vendored
Normal file
23
modules/ppcp-settings/resources/js/data/styling/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,
|
||||
} );
|
||||
},
|
||||
};
|
211
modules/ppcp-settings/resources/js/data/styling/hooks.js
Normal file
211
modules/ppcp-settings/resources/js/data/styling/hooks.js
Normal file
|
@ -0,0 +1,211 @@
|
|||
/**
|
||||
* 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 { useCallback } from '@wordpress/element';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
|
||||
import { createHooksForStore } from '../utils';
|
||||
import { STORE_NAME } from './constants';
|
||||
import {
|
||||
STYLING_COLORS,
|
||||
STYLING_LABELS,
|
||||
STYLING_LAYOUTS,
|
||||
STYLING_LOCATIONS,
|
||||
STYLING_PAYMENT_METHODS,
|
||||
STYLING_SHAPES,
|
||||
} from './configuration';
|
||||
|
||||
const useHooks = () => {
|
||||
const { useTransient } = createHooksForStore( STORE_NAME );
|
||||
const { persist, setPersistent } = useDispatch( STORE_NAME );
|
||||
|
||||
// Transient accessors.
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
const [ location, setLocation ] = useTransient( 'location' );
|
||||
|
||||
// Persistent accessors.
|
||||
const persistentData = useSelect(
|
||||
( select ) => select( STORE_NAME ).persistentData(),
|
||||
[]
|
||||
);
|
||||
|
||||
const getLocationProp = useCallback(
|
||||
( locationId, prop ) => {
|
||||
if ( undefined === persistentData[ locationId ]?.[ prop ] ) {
|
||||
console.error(
|
||||
`Trying to access non-existent style property: ${ locationId }.${ prop }. Possibly wrong style name - review the reducer.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return persistentData[ locationId ][ prop ];
|
||||
},
|
||||
[ persistentData ]
|
||||
);
|
||||
|
||||
const setLocationProp = useCallback(
|
||||
( locationId, prop, value ) => {
|
||||
const updatedStyles = {
|
||||
...persistentData[ locationId ],
|
||||
[ prop ]: value,
|
||||
};
|
||||
|
||||
setPersistent( locationId, updatedStyles );
|
||||
},
|
||||
[ persistentData, setPersistent ]
|
||||
);
|
||||
|
||||
return {
|
||||
persist,
|
||||
isReady,
|
||||
location,
|
||||
setLocation,
|
||||
getLocationProp,
|
||||
setLocationProp,
|
||||
};
|
||||
};
|
||||
|
||||
export const useStore = () => {
|
||||
const { persist, isReady } = useHooks();
|
||||
return { persist, isReady };
|
||||
};
|
||||
|
||||
export const useStylingLocation = () => {
|
||||
const { location, setLocation } = useHooks();
|
||||
return { location, setLocation };
|
||||
};
|
||||
|
||||
export const useLocationProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
const details = STYLING_LOCATIONS[ location ] ?? {};
|
||||
|
||||
const sanitize = ( value ) => ( undefined === value ? true : !! value );
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_LOCATIONS ),
|
||||
details,
|
||||
isActive: sanitize( getLocationProp( location, 'enabled' ) ),
|
||||
setActive: ( state ) =>
|
||||
setLocationProp( location, 'enabled', sanitize( state ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const usePaymentMethodProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
|
||||
const sanitize = ( value ) => {
|
||||
if ( Array.isArray( value ) ) {
|
||||
return value;
|
||||
}
|
||||
return value ? [ value ] : [];
|
||||
};
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_PAYMENT_METHODS ),
|
||||
paymentMethods: sanitize( getLocationProp( location, 'methods' ) ),
|
||||
setPaymentMethods: ( methods ) =>
|
||||
setLocationProp( location, 'methods', sanitize( methods ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useColorProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
|
||||
const sanitize = ( value ) => {
|
||||
const isValidColor = Object.values( STYLING_COLORS ).some(
|
||||
( color ) => color.value === value
|
||||
);
|
||||
return isValidColor ? value : STYLING_COLORS.gold.value;
|
||||
};
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_COLORS ),
|
||||
color: sanitize( getLocationProp( location, 'color' ) ),
|
||||
setColor: ( color ) =>
|
||||
setLocationProp( location, 'color', sanitize( color ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useShapeProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
|
||||
const sanitize = ( value ) => {
|
||||
const isValidColor = Object.values( STYLING_SHAPES ).some(
|
||||
( color ) => color.value === value
|
||||
);
|
||||
return isValidColor ? value : STYLING_SHAPES.rect.value;
|
||||
};
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_SHAPES ),
|
||||
shape: sanitize( getLocationProp( location, 'shape' ) ),
|
||||
setShape: ( shape ) =>
|
||||
setLocationProp( location, 'shape', sanitize( shape ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useLabelProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
|
||||
const sanitize = ( value ) => {
|
||||
const isValidColor = Object.values( STYLING_LABELS ).some(
|
||||
( color ) => color.value === value
|
||||
);
|
||||
return isValidColor ? value : STYLING_LABELS.paypal.value;
|
||||
};
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_LABELS ),
|
||||
label: sanitize( getLocationProp( location, 'label' ) ),
|
||||
setLabel: ( label ) =>
|
||||
setLocationProp( location, 'label', sanitize( label ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useLayoutProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
const { details } = useLocationProps( location );
|
||||
const isAvailable = false !== details.props.layout;
|
||||
|
||||
const sanitize = ( value ) => {
|
||||
const isValidColor = Object.values( STYLING_LAYOUTS ).some(
|
||||
( color ) => color.value === value
|
||||
);
|
||||
return isValidColor ? value : STYLING_LAYOUTS.vertical.value;
|
||||
};
|
||||
|
||||
return {
|
||||
choices: Object.values( STYLING_LAYOUTS ),
|
||||
isAvailable,
|
||||
layout: sanitize( getLocationProp( location, 'layout' ) ),
|
||||
setLayout: ( layout ) =>
|
||||
setLocationProp( location, 'layout', sanitize( layout ) ),
|
||||
};
|
||||
};
|
||||
|
||||
export const useTaglineProps = ( location ) => {
|
||||
const { getLocationProp, setLocationProp } = useHooks();
|
||||
const { details } = useLocationProps( location );
|
||||
|
||||
// Tagline is only available for horizontal layouts.
|
||||
const isAvailable =
|
||||
false !== details.props.tagline &&
|
||||
STYLING_LAYOUTS.horizontal.value ===
|
||||
getLocationProp( location, 'layout' );
|
||||
|
||||
const sanitize = ( value ) => !! value;
|
||||
|
||||
return {
|
||||
isAvailable,
|
||||
tagline: isAvailable
|
||||
? sanitize( getLocationProp( location, 'tagline' ) )
|
||||
: false,
|
||||
setTagline: ( tagline ) =>
|
||||
setLocationProp( location, 'tagline', sanitize( tagline ) ),
|
||||
};
|
||||
};
|
24
modules/ppcp-settings/resources/js/data/styling/index.js
Normal file
24
modules/ppcp-settings/resources/js/data/styling/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
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';
|
||||
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
128
modules/ppcp-settings/resources/js/data/styling/reducer.js
Normal file
128
modules/ppcp-settings/resources/js/data/styling/reducer.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* 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, createSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
import {
|
||||
STYLING_COLORS,
|
||||
STYLING_LABELS,
|
||||
STYLING_LAYOUTS,
|
||||
STYLING_LOCATIONS,
|
||||
STYLING_SHAPES,
|
||||
} from './configuration';
|
||||
|
||||
// Store structure.
|
||||
|
||||
// Transient: Values that are _not_ saved to the DB (like app lifecycle-flags).
|
||||
const defaultTransient = Object.freeze( {
|
||||
isReady: false,
|
||||
location: STYLING_LOCATIONS.cart.value, // Which location is selected in the Styling tab.
|
||||
} );
|
||||
|
||||
// Persistent: Values that are loaded from the DB.
|
||||
const defaultPersistent = Object.freeze( {
|
||||
[ STYLING_LOCATIONS.cart.value ]: Object.freeze( {
|
||||
enabled: true,
|
||||
methods: [],
|
||||
label: STYLING_LABELS.pay.value,
|
||||
shape: STYLING_SHAPES.rect.value,
|
||||
color: STYLING_COLORS.gold.value,
|
||||
} ),
|
||||
[ STYLING_LOCATIONS.classicCheckout.value ]: Object.freeze( {
|
||||
enabled: true,
|
||||
methods: [],
|
||||
label: STYLING_LABELS.checkout.value,
|
||||
shape: STYLING_SHAPES.rect.value,
|
||||
color: STYLING_COLORS.gold.value,
|
||||
layout: STYLING_LAYOUTS.vertical.value,
|
||||
tagline: false,
|
||||
} ),
|
||||
[ STYLING_LOCATIONS.expressCheckout.value ]: Object.freeze( {
|
||||
enabled: true,
|
||||
methods: [],
|
||||
label: STYLING_LABELS.checkout.value,
|
||||
shape: STYLING_SHAPES.rect.value,
|
||||
color: STYLING_COLORS.gold.value,
|
||||
} ),
|
||||
[ STYLING_LOCATIONS.miniCart.value ]: Object.freeze( {
|
||||
enabled: true,
|
||||
methods: [],
|
||||
label: STYLING_LABELS.pay.value,
|
||||
shape: STYLING_SHAPES.rect.value,
|
||||
color: STYLING_COLORS.gold.value,
|
||||
layout: STYLING_LAYOUTS.vertical.value,
|
||||
tagline: false,
|
||||
} ),
|
||||
[ STYLING_LOCATIONS.product.value ]: Object.freeze( {
|
||||
enabled: true,
|
||||
methods: [],
|
||||
label: STYLING_LABELS.buynow.value,
|
||||
shape: STYLING_SHAPES.rect.value,
|
||||
color: STYLING_COLORS.gold.value,
|
||||
layout: STYLING_LAYOUTS.vertical.value,
|
||||
tagline: false,
|
||||
} ),
|
||||
} );
|
||||
|
||||
const sanitizeLocation = ( oldDetails, newDetails ) => {
|
||||
// Skip if provided details are not a plain object.
|
||||
if (
|
||||
! newDetails ||
|
||||
'object' !== typeof newDetails ||
|
||||
Array.isArray( newDetails )
|
||||
) {
|
||||
return oldDetails;
|
||||
}
|
||||
|
||||
return { ...oldDetails, ...newDetails };
|
||||
};
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ setTransient, setPersistent ] = createSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||
setTransient( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
|
||||
setPersistent( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.RESET ]: ( state ) => {
|
||||
const cleanState = setTransient(
|
||||
setPersistent( state, defaultPersistent ),
|
||||
defaultTransient
|
||||
);
|
||||
|
||||
// Keep "read-only" details and initialization flags.
|
||||
cleanState.isReady = true;
|
||||
|
||||
return cleanState;
|
||||
},
|
||||
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) => {
|
||||
const validData = Object.keys( defaultPersistent ).reduce(
|
||||
( data, location ) => {
|
||||
data[ location ] = sanitizeLocation(
|
||||
state.data[ location ],
|
||||
payload.data[ location ]
|
||||
);
|
||||
return data;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return setPersistent( state, validData );
|
||||
},
|
||||
} );
|
||||
|
||||
export default reducer;
|
36
modules/ppcp-settings/resources/js/data/styling/resolvers.js
Normal file
36
modules/ppcp-settings/resources/js/data/styling/resolvers.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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 } );
|
||||
|
||||
yield dispatch( STORE_NAME ).hydrate( result );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
} catch ( e ) {
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__(
|
||||
'Error retrieving style-details.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
21
modules/ppcp-settings/resources/js/data/styling/selectors.js
Normal file
21
modules/ppcp-settings/resources/js/data/styling/selectors.js
Normal file
|
@ -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;
|
||||
};
|
|
@ -1,3 +1,6 @@
|
|||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Updates an object with new values, filtering based on allowed keys.
|
||||
*
|
||||
|
@ -13,6 +16,10 @@ const updateObject = ( oldObject, newValues, allowedKeys = {} ) => ( {
|
|||
...Object.keys( newValues ).reduce( ( acc, key ) => {
|
||||
if ( key in allowedKeys ) {
|
||||
acc[ key ] = newValues[ key ];
|
||||
} else {
|
||||
console.warn(
|
||||
`Ignoring unknown key "${ key }" - to use it, add it to the initial store properties in the reducer.`
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, {} ),
|
||||
|
@ -73,3 +80,63 @@ export const createReducer = (
|
|||
return state;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object with two hooks:
|
||||
* - useTransient( prop )
|
||||
* - usePersistent( prop )
|
||||
*
|
||||
* Both hooks have a similar syntax to the native "useState( prop )" hook, but provide access to
|
||||
* a transient or persistent property in the relevant Redux store.
|
||||
*
|
||||
* Sample:
|
||||
*
|
||||
* const { useTransient } = createHooksForStore( STORE_NAME );
|
||||
* const [ isReady, setIsReady ] = useTransient( 'isReady' );
|
||||
*
|
||||
* @param {string} storeName Store name.
|
||||
* @return {{useTransient, usePersistent}} Store hooks.
|
||||
*/
|
||||
export const createHooksForStore = ( storeName ) => {
|
||||
const createHook = ( selector, dispatcher ) => ( key ) => {
|
||||
const value = useSelect(
|
||||
( select ) => {
|
||||
const store = select( storeName );
|
||||
if ( ! store?.[ selector ] ) {
|
||||
throw new Error(
|
||||
`Please create the selector "${ selector }" for store "${ storeName }"`
|
||||
);
|
||||
}
|
||||
const selectorResult = store[ selector ]();
|
||||
if ( undefined === selectorResult?.[ key ] ) {
|
||||
console.error(
|
||||
`Warning: ${ selector }()[${ key }] is undefined in store "${ storeName }". This may indicate a bug.`
|
||||
);
|
||||
}
|
||||
return selectorResult?.[ key ];
|
||||
},
|
||||
[ key ]
|
||||
);
|
||||
|
||||
const actions = useDispatch( storeName );
|
||||
|
||||
const setValue = useCallback(
|
||||
( newValue ) => {
|
||||
if ( ! actions?.[ dispatcher ] ) {
|
||||
throw new Error(
|
||||
`Please create the action "${ dispatcher }" for store "${ storeName }"`
|
||||
);
|
||||
}
|
||||
actions[ dispatcher ]( key, newValue );
|
||||
},
|
||||
[ actions, key ]
|
||||
);
|
||||
|
||||
return [ value, setValue ];
|
||||
};
|
||||
|
||||
return {
|
||||
useTransient: createHook( 'transientData', 'setTransient' ),
|
||||
usePersistent: createHook( 'persistentData', 'setPersistent' ),
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue