mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Merge remote-tracking branch 'origin/PCP-4210-features-refactor-to-use-rest-endpoints' into PCP-4210-features-refactor-to-use-rest-endpoints
# Conflicts: # modules/ppcp-settings/resources/js/data/debug.js
This commit is contained in:
commit
c902f24010
26 changed files with 599 additions and 276 deletions
|
@ -11,8 +11,8 @@ import { getQuery } from '../utils/navigation';
|
||||||
const SettingsApp = () => {
|
const SettingsApp = () => {
|
||||||
const { isReady: onboardingIsReady, completed: onboardingCompleted } =
|
const { isReady: onboardingIsReady, completed: onboardingCompleted } =
|
||||||
OnboardingHooks.useSteps();
|
OnboardingHooks.useSteps();
|
||||||
|
const { isReady: merchantIsReady } = CommonHooks.useStore();
|
||||||
const {
|
const {
|
||||||
isReady: merchantIsReady,
|
|
||||||
merchant: { isSendOnlyCountry },
|
merchant: { isSendOnlyCountry },
|
||||||
} = CommonHooks.useMerchantInfo();
|
} = CommonHooks.useMerchantInfo();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { __ } from '@wordpress/i18n';
|
||||||
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||||
|
|
||||||
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
||||||
import { useSaveSettings } from '../../../../hooks/useSaveSettings';
|
import { useStoreManager } from '../../../../hooks/useStoreManager';
|
||||||
import { CommonHooks } from '../../../../data';
|
import { CommonHooks } from '../../../../data';
|
||||||
import TabBar from '../../../ReusableComponents/TabBar';
|
import TabBar from '../../../ReusableComponents/TabBar';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -20,7 +20,7 @@ const SettingsNavigation = ( {
|
||||||
activePanel,
|
activePanel,
|
||||||
setActivePanel,
|
setActivePanel,
|
||||||
} ) => {
|
} ) => {
|
||||||
const { persistAll } = useSaveSettings();
|
const { persistAll } = useStoreManager();
|
||||||
|
|
||||||
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,12 @@ import {
|
||||||
import { Content, ContentWrapper } from '../../../ReusableComponents/Elements';
|
import { Content, ContentWrapper } from '../../../ReusableComponents/Elements';
|
||||||
import SettingsCard from '../../../ReusableComponents/SettingsCard';
|
import SettingsCard from '../../../ReusableComponents/SettingsCard';
|
||||||
import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge';
|
import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge';
|
||||||
import { useTodos } from '../../../../data/todos/hooks';
|
import {
|
||||||
import { useMerchantInfo } from '../../../../data/common/hooks';
|
CommonStoreName,
|
||||||
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../data/common';
|
TodosStoreName,
|
||||||
import { STORE_NAME as TODOS_STORE_NAME } from '../../../../data/todos';
|
CommonHooks,
|
||||||
import { CommonHooks, TodosHooks } from '../../../../data';
|
TodosHooks,
|
||||||
|
} from '../../../../data';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NOTIFICATION_ERROR,
|
NOTIFICATION_ERROR,
|
||||||
|
@ -28,8 +29,8 @@ import { selectTab, TAB_IDS } from '../../../../utils/tabSelector';
|
||||||
import { setActiveModal } from '../../../../data/common/actions';
|
import { setActiveModal } from '../../../../data/common/actions';
|
||||||
|
|
||||||
const TabOverview = () => {
|
const TabOverview = () => {
|
||||||
const { isReady: areTodosReady } = TodosHooks.useTodos();
|
const { isReady: areTodosReady } = TodosHooks.useStore();
|
||||||
const { isReady: merchantIsReady } = CommonHooks.useMerchantInfo();
|
const { isReady: merchantIsReady } = CommonHooks.useStore();
|
||||||
|
|
||||||
if ( ! areTodosReady || ! merchantIsReady ) {
|
if ( ! areTodosReady || ! merchantIsReady ) {
|
||||||
return <SpinnerOverlay asModal={ true } />;
|
return <SpinnerOverlay asModal={ true } />;
|
||||||
|
@ -48,12 +49,12 @@ export default TabOverview;
|
||||||
|
|
||||||
const OverviewTodos = () => {
|
const OverviewTodos = () => {
|
||||||
const [ isResetting, setIsResetting ] = useState( false );
|
const [ isResetting, setIsResetting ] = useState( false );
|
||||||
const { todos, isReady: areTodosReady, dismissTodo } = useTodos();
|
const { todos, dismissTodo } = TodosHooks.useTodos();
|
||||||
// eslint-disable-next-line no-shadow
|
const { isReady: areTodosReady } = TodosHooks.useStore();
|
||||||
const { setActiveModal, setActiveHighlight } =
|
const { setActiveModal, setActiveHighlight } =
|
||||||
useDispatch( COMMON_STORE_NAME );
|
useDispatch( CommonStoreName );
|
||||||
const { resetDismissedTodos, setDismissedTodos } =
|
const { resetDismissedTodos, setDismissedTodos } =
|
||||||
useDispatch( TODOS_STORE_NAME );
|
useDispatch( TodosStoreName );
|
||||||
const { createSuccessNotice } = useDispatch( noticesStore );
|
const { createSuccessNotice } = useDispatch( noticesStore );
|
||||||
|
|
||||||
const showTodos = areTodosReady && todos.length > 0;
|
const showTodos = areTodosReady && todos.length > 0;
|
||||||
|
@ -120,8 +121,8 @@ const OverviewTodos = () => {
|
||||||
|
|
||||||
const OverviewFeatures = () => {
|
const OverviewFeatures = () => {
|
||||||
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
const [ isRefreshing, setIsRefreshing ] = useState( false );
|
||||||
const { merchant } = useMerchantInfo();
|
const { merchant } = CommonHooks.useMerchantInfo();
|
||||||
const { refreshFeatureStatuses } = useDispatch( COMMON_STORE_NAME );
|
const { refreshFeatureStatuses } = useDispatch( CommonStoreName );
|
||||||
const { createSuccessNotice, createErrorNotice } =
|
const { createSuccessNotice, createErrorNotice } =
|
||||||
useDispatch( noticesStore );
|
useDispatch( noticesStore );
|
||||||
const { features, fetchFeatures } = useFeatures();
|
const { features, fetchFeatures } = useFeatures();
|
||||||
|
|
|
@ -82,3 +82,16 @@ export function persist() {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -38,10 +38,16 @@ const useStoreData = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStore = () => {
|
export const useStore = () => {
|
||||||
const { dispatch, useTransient } = useStoreData();
|
const { select, dispatch, useTransient } = useStoreData();
|
||||||
|
const { persist, refresh } = dispatch;
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
return { persist: dispatch.persist, isReady };
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.persistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { persist, refresh, isReady };
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Replace with real hook.
|
// TODO: Replace with real hook.
|
||||||
|
|
|
@ -27,6 +27,19 @@ export function persist() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Side effect. Fetches the ISU-login URL for a sandbox account.
|
* Side effect. Fetches the ISU-login URL for a sandbox account.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,8 +13,32 @@ import { useCallback, useEffect, useMemo, useState } from '@wordpress/element';
|
||||||
import { createHooksForStore } from '../utils';
|
import { createHooksForStore } from '../utils';
|
||||||
import { STORE_NAME } from './constants';
|
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 { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ( {
|
||||||
|
select,
|
||||||
|
dispatch,
|
||||||
|
useTransient,
|
||||||
|
usePersistent,
|
||||||
|
} ),
|
||||||
|
[ select, dispatch, useTransient, usePersistent ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useHooks = () => {
|
||||||
|
const { useTransient, usePersistent, dispatch, select } = useStoreData();
|
||||||
const {
|
const {
|
||||||
persist,
|
persist,
|
||||||
sandboxOnboardingUrl,
|
sandboxOnboardingUrl,
|
||||||
|
@ -23,10 +47,9 @@ const useHooks = () => {
|
||||||
authenticateWithOAuth,
|
authenticateWithOAuth,
|
||||||
startWebhookSimulation,
|
startWebhookSimulation,
|
||||||
checkWebhookSimulationState,
|
checkWebhookSimulationState,
|
||||||
} = useDispatch( STORE_NAME );
|
} = dispatch;
|
||||||
|
|
||||||
// Transient accessors.
|
// Transient accessors.
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
|
||||||
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
|
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
|
||||||
const [ activeHighlight, setActiveHighlight ] =
|
const [ activeHighlight, setActiveHighlight ] =
|
||||||
useTransient( 'activeHighlight' );
|
useTransient( 'activeHighlight' );
|
||||||
|
@ -38,18 +61,9 @@ const useHooks = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Read-only properties.
|
// Read-only properties.
|
||||||
const wooSettings = useSelect(
|
const wooSettings = select.wooSettings();
|
||||||
( select ) => select( STORE_NAME ).wooSettings(),
|
const features = select.features();
|
||||||
[]
|
const webhooks = select.webhooks();
|
||||||
);
|
|
||||||
const features = useSelect(
|
|
||||||
( select ) => select( STORE_NAME ).features(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const webhooks = useSelect(
|
|
||||||
( select ) => select( STORE_NAME ).webhooks(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const savePersistent = async ( setter, value ) => {
|
const savePersistent = async ( setter, value ) => {
|
||||||
setter( value );
|
setter( value );
|
||||||
|
@ -57,7 +71,6 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isReady,
|
|
||||||
activeModal,
|
activeModal,
|
||||||
setActiveModal,
|
setActiveModal,
|
||||||
activeHighlight,
|
activeHighlight,
|
||||||
|
@ -82,6 +95,19 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useStore = () => {
|
||||||
|
const { select, dispatch, useTransient } = useStoreData();
|
||||||
|
const { persist, refresh } = dispatch;
|
||||||
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.persistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { persist, refresh, isReady };
|
||||||
|
};
|
||||||
|
|
||||||
export const useSandbox = () => {
|
export const useSandbox = () => {
|
||||||
const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks();
|
const { isSandboxMode, setSandboxMode, sandboxOnboardingUrl } = useHooks();
|
||||||
|
|
||||||
|
@ -139,7 +165,7 @@ export const useWebhooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useMerchantInfo = () => {
|
export const useMerchantInfo = () => {
|
||||||
const { isReady, features } = useHooks();
|
const { features } = useHooks();
|
||||||
const merchant = useMerchant();
|
const merchant = useMerchant();
|
||||||
const { refreshMerchantData, setMerchant } = useDispatch( STORE_NAME );
|
const { refreshMerchantData, setMerchant } = useDispatch( STORE_NAME );
|
||||||
|
|
||||||
|
@ -164,7 +190,6 @@ export const useMerchantInfo = () => {
|
||||||
}, [ refreshMerchantData, setMerchant ] );
|
}, [ refreshMerchantData, setMerchant ] );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isReady,
|
|
||||||
merchant, // Merchant details
|
merchant, // Merchant details
|
||||||
features, // Eligible merchant features
|
features, // Eligible merchant features
|
||||||
verifyLoginStatus, // Callback
|
verifyLoginStatus, // Callback
|
||||||
|
@ -204,7 +229,9 @@ export const useActiveHighlight = () => {
|
||||||
return { activeHighlight, setActiveHighlight };
|
return { activeHighlight, setActiveHighlight };
|
||||||
};
|
};
|
||||||
|
|
||||||
// -- Not using the `useHooks()` data provider --
|
/*
|
||||||
|
* Busy state management hooks
|
||||||
|
*/
|
||||||
|
|
||||||
export const useBusyState = () => {
|
export const useBusyState = () => {
|
||||||
const { startActivity, stopActivity } = useDispatch( STORE_NAME );
|
const { startActivity, stopActivity } = useDispatch( STORE_NAME );
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
SettingsStoreName,
|
SettingsStoreName,
|
||||||
StylingStoreName,
|
StylingStoreName,
|
||||||
TodosStoreName,
|
TodosStoreName,
|
||||||
|
FeaturesStoreName,
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
export const addDebugTools = ( context, modules ) => {
|
export const addDebugTools = ( context, modules ) => {
|
||||||
|
@ -18,10 +19,15 @@ export const addDebugTools = ( context, modules ) => {
|
||||||
if ( ! context.debug ) { return }
|
if ( ! context.debug ) { return }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const describe = ( fnName, fnInfo ) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log( `\n%c${ fnName }:`, 'font-weight:bold', fnInfo, '\n\n' );
|
||||||
|
};
|
||||||
|
|
||||||
const debugApi = ( window.ppcpDebugger = window.ppcpDebugger || {} );
|
const debugApi = ( window.ppcpDebugger = window.ppcpDebugger || {} );
|
||||||
|
|
||||||
// Dump the current state of all our Redux stores.
|
// Dump the current state of all our Redux stores.
|
||||||
debugApi.dumpStore = async () => {
|
debugApi.dumpStore = async ( cbFilter = null ) => {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
if ( ! console?.groupCollapsed ) {
|
if ( ! console?.groupCollapsed ) {
|
||||||
console.error( 'console.groupCollapsed is not supported.' );
|
console.error( 'console.groupCollapsed is not supported.' );
|
||||||
|
@ -34,11 +40,19 @@ export const addDebugTools = ( context, modules ) => {
|
||||||
console.group( `[STORE] ${ storeSelector }` );
|
console.group( `[STORE] ${ storeSelector }` );
|
||||||
|
|
||||||
const dumpStore = ( selector ) => {
|
const dumpStore = ( selector ) => {
|
||||||
const contents = wp.data.select( storeName )[ selector ]();
|
let contents = wp.data.select( storeName )[ selector ]();
|
||||||
|
|
||||||
console.groupCollapsed( `.${ selector }()` );
|
if ( cbFilter ) {
|
||||||
console.table( contents );
|
contents = cbFilter( contents, selector, storeName );
|
||||||
console.groupEnd();
|
|
||||||
|
if ( undefined !== contents && null !== contents ) {
|
||||||
|
console.log( `.${ selector }() [filtered]`, contents );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.groupCollapsed( `.${ selector }()` );
|
||||||
|
console.table( contents );
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys( module.selectors ).forEach( dumpStore );
|
Object.keys( module.selectors ).forEach( dumpStore );
|
||||||
|
@ -51,45 +65,90 @@ export const addDebugTools = ( context, modules ) => {
|
||||||
// Reset all Redux stores to their initial state.
|
// Reset all Redux stores to their initial state.
|
||||||
debugApi.resetStore = () => {
|
debugApi.resetStore = () => {
|
||||||
const stores = [];
|
const stores = [];
|
||||||
const { isConnected } = wp.data.select( CommonStoreName ).merchant();
|
|
||||||
|
|
||||||
if ( isConnected ) {
|
describe(
|
||||||
// Make sure the Onboarding wizard is "completed".
|
'resetStore',
|
||||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
'Reset all Redux stores to their DEFAULT state, without changing any server-side data. The default state is defined in the JS code.'
|
||||||
onboarding.setPersistent( 'completed', true );
|
);
|
||||||
onboarding.persist();
|
|
||||||
|
|
||||||
// Reset all stores, except for the onboarding store.
|
const { completed } = wp.data
|
||||||
stores.push( CommonStoreName );
|
.select( OnboardingStoreName )
|
||||||
stores.push( PaymentStoreName );
|
.persistentData();
|
||||||
stores.push( SettingsStoreName );
|
|
||||||
stores.push( StylingStoreName );
|
// Reset all stores, except for the onboarding store.
|
||||||
stores.push( TodosStoreName );
|
stores.push( CommonStoreName );
|
||||||
} else {
|
stores.push( PaymentStoreName );
|
||||||
// Only reset the common & onboarding stores to restart the onboarding wizard.
|
stores.push( SettingsStoreName );
|
||||||
stores.push( CommonStoreName );
|
stores.push( StylingStoreName );
|
||||||
|
stores.push( TodosStoreName );
|
||||||
|
|
||||||
|
// Only reset the onboarding store when the wizard is not completed.
|
||||||
|
if ( ! completed ) {
|
||||||
stores.push( OnboardingStoreName );
|
stores.push( OnboardingStoreName );
|
||||||
}
|
}
|
||||||
|
|
||||||
stores.forEach( ( storeName ) => {
|
stores.forEach( ( storeName ) => {
|
||||||
const store = wp.data.dispatch( storeName );
|
const store = wp.data.dispatch( storeName );
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log( `Reset store: ${ storeName }...` );
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
store.reset();
|
store.reset();
|
||||||
store.persist();
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log( `Done: Store '${ storeName }' reset` );
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
console.error( ' ... Reset failed, skipping this store' );
|
console.error(
|
||||||
|
`Failed: Could not reset store '${ storeName }'`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log( '---- Complete ----\n\n' );
|
||||||
|
};
|
||||||
|
|
||||||
|
debugApi.refreshStore = () => {
|
||||||
|
const stores = [];
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'refreshStore',
|
||||||
|
'Refreshes all Redux details with details provided by the server. This has a similar effect as reloading the page without saving'
|
||||||
|
);
|
||||||
|
|
||||||
|
stores.push( CommonStoreName );
|
||||||
|
stores.push( PaymentStoreName );
|
||||||
|
stores.push( SettingsStoreName );
|
||||||
|
stores.push( StylingStoreName );
|
||||||
|
stores.push( TodosStoreName );
|
||||||
|
stores.push( FeaturesStoreName );
|
||||||
|
stores.push( OnboardingStoreName );
|
||||||
|
|
||||||
|
stores.forEach( ( storeName ) => {
|
||||||
|
const store = wp.data.dispatch( storeName );
|
||||||
|
|
||||||
|
try {
|
||||||
|
store.refresh();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Done: Store '${ storeName }' refreshed from REST`
|
||||||
|
);
|
||||||
|
} catch ( error ) {
|
||||||
|
console.error(
|
||||||
|
`Failed: Could not refresh store '${ storeName }' from REST`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log( '---- Complete ----\n\n' );
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disconnect the merchant and display the onboarding wizard.
|
// Disconnect the merchant and display the onboarding wizard.
|
||||||
debugApi.disconnect = () => {
|
debugApi.disconnect = () => {
|
||||||
const common = wp.data.dispatch( CommonStoreName );
|
const common = wp.data.dispatch( CommonStoreName );
|
||||||
|
|
||||||
|
describe();
|
||||||
|
|
||||||
common.disconnectMerchant();
|
common.disconnectMerchant();
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -102,6 +161,11 @@ export const addDebugTools = ( context, modules ) => {
|
||||||
debugApi.onboardingMode = ( state ) => {
|
debugApi.onboardingMode = ( state ) => {
|
||||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'onboardingMode',
|
||||||
|
'Toggle between onboarding wizard and the settings screen.'
|
||||||
|
);
|
||||||
|
|
||||||
onboarding.setPersistent( 'completed', ! state );
|
onboarding.setPersistent( 'completed', ! state );
|
||||||
onboarding.persist();
|
onboarding.persist();
|
||||||
};
|
};
|
||||||
|
|
|
@ -87,3 +87,16 @@ export function persist() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -82,3 +82,16 @@ export function persist() {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -94,3 +94,16 @@ export function persist() {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -7,22 +7,58 @@
|
||||||
* @file
|
* @file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
|
||||||
import { STORE_NAME } from './constants';
|
import { STORE_NAME } from './constants';
|
||||||
import { createHooksForStore } from '../utils';
|
import { createHooksForStore } from '../utils';
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
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 { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||||
const { persist, setPersistent, changePaymentSettings } =
|
|
||||||
useDispatch( STORE_NAME );
|
|
||||||
|
|
||||||
// Read-only flags and derived state.
|
return useMemo(
|
||||||
// Nothing here yet.
|
() => ( {
|
||||||
|
select,
|
||||||
|
dispatch,
|
||||||
|
useTransient,
|
||||||
|
usePersistent,
|
||||||
|
} ),
|
||||||
|
[ select, dispatch, useTransient, usePersistent ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Transient accessors.
|
export const useStore = () => {
|
||||||
|
const { select, useTransient, dispatch } = useStoreData();
|
||||||
|
const { persist, refresh, setPersistent, changePaymentSettings } = dispatch;
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.persistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
persist,
|
||||||
|
refresh,
|
||||||
|
setPersistent,
|
||||||
|
changePaymentSettings,
|
||||||
|
isReady,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePaymentMethods = () => {
|
||||||
|
const { usePersistent } = useStoreData();
|
||||||
|
|
||||||
// PayPal checkout.
|
// PayPal checkout.
|
||||||
const [ paypal ] = usePersistent( 'ppcp-gateway' );
|
const [ paypal ] = usePersistent( 'ppcp-gateway' );
|
||||||
const [ venmo ] = usePersistent( 'venmo' );
|
const [ venmo ] = usePersistent( 'venmo' );
|
||||||
|
@ -47,79 +83,6 @@ const useHooks = () => {
|
||||||
const [ pui ] = usePersistent( 'ppcp-pay-upon-invoice-gateway' );
|
const [ pui ] = usePersistent( 'ppcp-pay-upon-invoice-gateway' );
|
||||||
const [ oxxo ] = usePersistent( 'ppcp-oxxo-gateway' );
|
const [ oxxo ] = usePersistent( 'ppcp-oxxo-gateway' );
|
||||||
|
|
||||||
// Custom modal data.
|
|
||||||
const [ paypalShowLogo ] = usePersistent( 'paypalShowLogo' );
|
|
||||||
const [ threeDSecure ] = usePersistent( 'threeDSecure' );
|
|
||||||
const [ fastlaneCardholderName ] = usePersistent(
|
|
||||||
'fastlaneCardholderName'
|
|
||||||
);
|
|
||||||
const [ fastlaneDisplayWatermark ] = usePersistent(
|
|
||||||
'fastlaneDisplayWatermark'
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
persist,
|
|
||||||
isReady,
|
|
||||||
setPersistent,
|
|
||||||
changePaymentSettings,
|
|
||||||
paypal,
|
|
||||||
venmo,
|
|
||||||
payLater,
|
|
||||||
creditCard,
|
|
||||||
advancedCreditCard,
|
|
||||||
fastlane,
|
|
||||||
applePay,
|
|
||||||
googlePay,
|
|
||||||
bancontact,
|
|
||||||
blik,
|
|
||||||
eps,
|
|
||||||
ideal,
|
|
||||||
mybank,
|
|
||||||
p24,
|
|
||||||
trustly,
|
|
||||||
multibanco,
|
|
||||||
pui,
|
|
||||||
oxxo,
|
|
||||||
paypalShowLogo,
|
|
||||||
threeDSecure,
|
|
||||||
fastlaneCardholderName,
|
|
||||||
fastlaneDisplayWatermark,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useStore = () => {
|
|
||||||
const { persist, isReady, setPersistent, changePaymentSettings } =
|
|
||||||
useHooks();
|
|
||||||
return { persist, isReady, setPersistent, changePaymentSettings };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usePaymentMethods = () => {
|
|
||||||
const {
|
|
||||||
// PayPal Checkout.
|
|
||||||
paypal,
|
|
||||||
venmo,
|
|
||||||
payLater,
|
|
||||||
creditCard,
|
|
||||||
|
|
||||||
// Online card payments.
|
|
||||||
advancedCreditCard,
|
|
||||||
fastlane,
|
|
||||||
applePay,
|
|
||||||
googlePay,
|
|
||||||
|
|
||||||
// Local APMs.
|
|
||||||
bancontact,
|
|
||||||
blik,
|
|
||||||
eps,
|
|
||||||
ideal,
|
|
||||||
mybank,
|
|
||||||
p24,
|
|
||||||
trustly,
|
|
||||||
multibanco,
|
|
||||||
pui,
|
|
||||||
oxxo,
|
|
||||||
} = useHooks();
|
|
||||||
|
|
||||||
const payPalCheckout = [ paypal, venmo, payLater, creditCard ];
|
const payPalCheckout = [ paypal, venmo, payLater, creditCard ];
|
||||||
const onlineCardPayments = [
|
const onlineCardPayments = [
|
||||||
advancedCreditCard,
|
advancedCreditCard,
|
||||||
|
@ -169,12 +132,16 @@ export const usePaymentMethods = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePaymentMethodsModal = () => {
|
export const usePaymentMethodsModal = () => {
|
||||||
const {
|
const { usePersistent } = useStoreData();
|
||||||
paypalShowLogo,
|
|
||||||
threeDSecure,
|
const [ paypalShowLogo ] = usePersistent( 'paypalShowLogo' );
|
||||||
fastlaneCardholderName,
|
const [ threeDSecure ] = usePersistent( 'threeDSecure' );
|
||||||
fastlaneDisplayWatermark,
|
const [ fastlaneCardholderName ] = usePersistent(
|
||||||
} = useHooks();
|
'fastlaneCardholderName'
|
||||||
|
);
|
||||||
|
const [ fastlaneDisplayWatermark ] = usePersistent(
|
||||||
|
'fastlaneDisplayWatermark'
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paypalShowLogo,
|
paypalShowLogo,
|
||||||
|
|
|
@ -19,6 +19,7 @@ const defaultTransient = Object.freeze( {
|
||||||
|
|
||||||
// Persistent: Values that are loaded from the DB.
|
// Persistent: Values that are loaded from the DB.
|
||||||
const defaultPersistent = Object.freeze( {
|
const defaultPersistent = Object.freeze( {
|
||||||
|
// Payment methods.
|
||||||
'ppcp-gateway': {},
|
'ppcp-gateway': {},
|
||||||
venmo: {},
|
venmo: {},
|
||||||
'pay-later': {},
|
'pay-later': {},
|
||||||
|
@ -37,6 +38,8 @@ const defaultPersistent = Object.freeze( {
|
||||||
'ppcp-multibanco': {},
|
'ppcp-multibanco': {},
|
||||||
'ppcp-pay-upon-invoice-gateway': {},
|
'ppcp-pay-upon-invoice-gateway': {},
|
||||||
'ppcp-oxxo-gateway': {},
|
'ppcp-oxxo-gateway': {},
|
||||||
|
|
||||||
|
// Custom payment method properties.
|
||||||
paypalShowLogo: false,
|
paypalShowLogo: false,
|
||||||
threeDSecure: 'no-3d-secure',
|
threeDSecure: 'no-3d-secure',
|
||||||
fastlaneCardholderName: false,
|
fastlaneCardholderName: false,
|
||||||
|
|
|
@ -84,3 +84,16 @@ export function persist() {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -6,17 +6,38 @@
|
||||||
*
|
*
|
||||||
* @file
|
* @file
|
||||||
*/
|
*/
|
||||||
import { useDispatch } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
|
||||||
import { STORE_NAME } from './constants';
|
import { STORE_NAME } from './constants';
|
||||||
import { createHooksForStore } from '../utils';
|
import { createHooksForStore } from '../utils';
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 );
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ( {
|
||||||
|
select,
|
||||||
|
dispatch,
|
||||||
|
useTransient,
|
||||||
|
usePersistent,
|
||||||
|
} ),
|
||||||
|
[ select, dispatch, useTransient, usePersistent ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const useHooks = () => {
|
const useHooks = () => {
|
||||||
const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
const { usePersistent } = useStoreData();
|
||||||
const { persist } = useDispatch( STORE_NAME );
|
|
||||||
|
|
||||||
// Read-only flags and derived state.
|
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
|
||||||
|
|
||||||
// Persistent accessors.
|
// Persistent accessors.
|
||||||
const [ invoicePrefix, setInvoicePrefix ] =
|
const [ invoicePrefix, setInvoicePrefix ] =
|
||||||
|
@ -47,8 +68,6 @@ const useHooks = () => {
|
||||||
usePersistent( 'disabledCards' );
|
usePersistent( 'disabledCards' );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
persist,
|
|
||||||
isReady,
|
|
||||||
invoicePrefix,
|
invoicePrefix,
|
||||||
setInvoicePrefix,
|
setInvoicePrefix,
|
||||||
authorizeOnly,
|
authorizeOnly,
|
||||||
|
@ -79,8 +98,16 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStore = () => {
|
export const useStore = () => {
|
||||||
const { persist, isReady } = useHooks();
|
const { select, dispatch, useTransient } = useStoreData();
|
||||||
return { persist, isReady };
|
const { persist, refresh } = dispatch;
|
||||||
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.persistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { persist, refresh, isReady };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSettings = () => {
|
export const useSettings = () => {
|
||||||
|
|
|
@ -82,3 +82,16 @@ export function persist() {
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* @file
|
* @file
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback } from '@wordpress/element';
|
import { useCallback, useMemo } from '@wordpress/element';
|
||||||
import { useDispatch, useSelect } from '@wordpress/data';
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
|
||||||
import { createHooksForStore } from '../utils';
|
import { createHooksForStore } from '../utils';
|
||||||
|
@ -20,13 +20,37 @@ import {
|
||||||
STYLING_PAYMENT_METHODS,
|
STYLING_PAYMENT_METHODS,
|
||||||
STYLING_SHAPES,
|
STYLING_SHAPES,
|
||||||
} from './configuration';
|
} from './configuration';
|
||||||
|
import { persistentData } from './selectors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 );
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ( {
|
||||||
|
select,
|
||||||
|
dispatch,
|
||||||
|
useTransient,
|
||||||
|
usePersistent,
|
||||||
|
} ),
|
||||||
|
[ select, dispatch, useTransient, usePersistent ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const useHooks = () => {
|
const useHooks = () => {
|
||||||
const { useTransient } = createHooksForStore( STORE_NAME );
|
const { useTransient, dispatch } = useStoreData();
|
||||||
const { persist, setPersistent } = useDispatch( STORE_NAME );
|
const { setPersistent } = dispatch;
|
||||||
|
|
||||||
// Transient accessors.
|
// Transient accessors.
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
|
||||||
const [ location, setLocation ] = useTransient( 'location' );
|
const [ location, setLocation ] = useTransient( 'location' );
|
||||||
|
|
||||||
// Persistent accessors.
|
// Persistent accessors.
|
||||||
|
@ -61,8 +85,6 @@ const useHooks = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
persist,
|
|
||||||
isReady,
|
|
||||||
location,
|
location,
|
||||||
setLocation,
|
setLocation,
|
||||||
getLocationProp,
|
getLocationProp,
|
||||||
|
@ -71,8 +93,16 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStore = () => {
|
export const useStore = () => {
|
||||||
const { persist, isReady } = useHooks();
|
const { select, dispatch, useTransient } = useStoreData();
|
||||||
return { persist, isReady };
|
const { persist, refresh } = dispatch;
|
||||||
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.persistentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { persist, refresh, isReady };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStylingLocation = () => {
|
export const useStylingLocation = () => {
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
/**
|
||||||
|
* Resets the store state to its initial values.
|
||||||
|
* Used when needing to clear all store data.
|
||||||
|
*/
|
||||||
|
RESET: 'ppcp/todos/RESET',
|
||||||
|
|
||||||
// Transient data
|
// Transient data
|
||||||
SET_TRANSIENT: 'ppcp/todos/SET_TRANSIENT',
|
SET_TRANSIENT: 'ppcp/todos/SET_TRANSIENT',
|
||||||
SET_COMPLETED_TODOS: 'ppcp/todos/SET_COMPLETED_TODOS',
|
SET_COMPLETED_TODOS: 'ppcp/todos/SET_COMPLETED_TODOS',
|
||||||
|
|
|
@ -17,11 +17,47 @@ import {
|
||||||
REST_RESET_DISMISSED_TODOS_PATH,
|
REST_RESET_DISMISSED_TODOS_PATH,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
export const setIsReady = ( isReady ) => ( {
|
/**
|
||||||
type: ACTION_TYPES.SET_TRANSIENT,
|
* Special. Resets all values in the store to initial defaults.
|
||||||
payload: { isReady },
|
*
|
||||||
|
* @return {Object} The action.
|
||||||
|
*/
|
||||||
|
export const reset = () => ( {
|
||||||
|
type: ACTION_TYPES.RESET,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic transient-data updater.
|
||||||
|
*
|
||||||
|
* @param {string} prop Name of the property to update.
|
||||||
|
* @param {any} value The new value of the property.
|
||||||
|
* @return {Object} 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 {Object} 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 Whether the store is ready
|
||||||
|
* @return {Object} The action.
|
||||||
|
*/
|
||||||
|
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||||
|
|
||||||
export const setTodos = ( todos ) => ( {
|
export const setTodos = ( todos ) => ( {
|
||||||
type: ACTION_TYPES.SET_TODOS,
|
type: ACTION_TYPES.SET_TODOS,
|
||||||
payload: todos,
|
payload: todos,
|
||||||
|
@ -39,6 +75,7 @@ export const setCompletedTodos = ( completedTodos ) => ( {
|
||||||
|
|
||||||
// Thunks
|
// Thunks
|
||||||
|
|
||||||
|
// TODO: Possibly, this should be a resolver?
|
||||||
export function fetchTodos() {
|
export function fetchTodos() {
|
||||||
return async () => {
|
return async () => {
|
||||||
const response = await apiFetch( { path: REST_PATH } );
|
const response = await apiFetch( { path: REST_PATH } );
|
||||||
|
@ -46,9 +83,14 @@ export function fetchTodos() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Triggers the persistence of store data to the server.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
export function persist() {
|
export function persist() {
|
||||||
return async ( { select } ) => {
|
return async ( { select } ) => {
|
||||||
return await apiFetch( {
|
await apiFetch( {
|
||||||
path: REST_PERSIST_PATH,
|
path: REST_PERSIST_PATH,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: select.persistentData(),
|
data: select.persistentData(),
|
||||||
|
@ -56,6 +98,19 @@ export function persist() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thunk action creator. Forces a data refresh from the REST API, replacing the current Redux values.
|
||||||
|
*
|
||||||
|
* @return {Function} The thunk function.
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
return ( { dispatch, select } ) => {
|
||||||
|
dispatch.invalidateResolutionForStore();
|
||||||
|
|
||||||
|
select.persistentData();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function resetDismissedTodos() {
|
export function resetDismissedTodos() {
|
||||||
return async ( { dispatch } ) => {
|
return async ( { dispatch } ) => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,31 +10,40 @@
|
||||||
import { useSelect, useDispatch } from '@wordpress/data';
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
import { STORE_NAME } from './constants';
|
import { STORE_NAME } from './constants';
|
||||||
import { createHooksForStore } from '../utils';
|
import { createHooksForStore } from '../utils';
|
||||||
|
import { useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
const ensureArray = ( value ) => {
|
/**
|
||||||
if ( ! value ) {
|
* Single source of truth for access Redux details.
|
||||||
return [];
|
*
|
||||||
}
|
* This hook returns a stable API to access actions, selectors and special hooks to generate
|
||||||
return Array.isArray( value ) ? value : Object.values( value );
|
* 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 );
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ( {
|
||||||
|
select,
|
||||||
|
dispatch,
|
||||||
|
useTransient,
|
||||||
|
usePersistent,
|
||||||
|
} ),
|
||||||
|
[ select, dispatch, useTransient, usePersistent ]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useHooks = () => {
|
const useHooks = () => {
|
||||||
const { useTransient } = createHooksForStore( STORE_NAME );
|
const { dispatch, select } = useStoreData();
|
||||||
const { fetchTodos, setDismissedTodos, setCompletedTodos, persist } =
|
const { fetchTodos, setDismissedTodos, setCompletedTodos } = dispatch;
|
||||||
useDispatch( STORE_NAME );
|
|
||||||
|
|
||||||
// Read-only flags and derived state.
|
|
||||||
const [ isReady ] = useTransient( 'isReady' );
|
|
||||||
|
|
||||||
// Get todos data from store
|
// Get todos data from store
|
||||||
const { todos, dismissedTodos, completedTodos } = useSelect( ( select ) => {
|
const todos = select.getTodos();
|
||||||
const store = select( STORE_NAME );
|
const dismissedTodos = select.getDismissedTodos();
|
||||||
return {
|
const completedTodos = select.getCompletedTodos();
|
||||||
todos: ensureArray( store.getTodos() ),
|
|
||||||
dismissedTodos: ensureArray( store.getDismissedTodos() ),
|
|
||||||
completedTodos: ensureArray( store.getCompletedTodos() ),
|
|
||||||
};
|
|
||||||
}, [] );
|
|
||||||
|
|
||||||
const dismissedSet = new Set( dismissedTodos );
|
const dismissedSet = new Set( dismissedTodos );
|
||||||
|
|
||||||
|
@ -62,8 +71,6 @@ const useHooks = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
persist,
|
|
||||||
isReady,
|
|
||||||
todos: filteredTodos,
|
todos: filteredTodos,
|
||||||
dismissedTodos,
|
dismissedTodos,
|
||||||
completedTodos,
|
completedTodos,
|
||||||
|
@ -74,14 +81,21 @@ const useHooks = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useStore = () => {
|
export const useStore = () => {
|
||||||
const { persist, isReady } = useHooks();
|
const { select, dispatch, useTransient } = useStoreData();
|
||||||
return { persist, isReady };
|
const { persist, refresh } = dispatch;
|
||||||
|
const [ isReady ] = useTransient( 'isReady' );
|
||||||
|
|
||||||
|
// Load persistent data from REST if not done yet.
|
||||||
|
if ( ! isReady ) {
|
||||||
|
select.getTodos();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { persist, refresh, isReady };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTodos = () => {
|
export const useTodos = () => {
|
||||||
const { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady } =
|
const { todos, fetchTodos, dismissTodo, setTodoCompleted } = useHooks();
|
||||||
useHooks();
|
return { todos, fetchTodos, dismissTodo, setTodoCompleted };
|
||||||
return { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDismissedTodos = () => {
|
export const useDismissedTodos = () => {
|
||||||
|
|
|
@ -52,6 +52,21 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||||
changeTransient( state, payload ),
|
changeTransient( 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 = changeTransient(
|
||||||
|
changePersistent( state, defaultPersistent ),
|
||||||
|
defaultTransient
|
||||||
|
);
|
||||||
|
cleanState.isReady = true; // Keep initialization flag
|
||||||
|
return cleanState;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates todos list
|
* Updates todos list
|
||||||
*
|
*
|
||||||
|
@ -99,6 +114,7 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: This is not used anywhere. Remove "SET_TODOS" and use this resolver instead.
|
||||||
* Initializes persistent state with data from the server
|
* Initializes persistent state with data from the server
|
||||||
*
|
*
|
||||||
* @param {Object} state Current state
|
* @param {Object} state Current state
|
||||||
|
|
|
@ -11,7 +11,17 @@ const EMPTY_OBJ = Object.freeze( {} );
|
||||||
const EMPTY_ARR = Object.freeze( [] );
|
const EMPTY_ARR = Object.freeze( [] );
|
||||||
|
|
||||||
const getState = ( state ) => state || EMPTY_OBJ;
|
const getState = ( state ) => state || EMPTY_OBJ;
|
||||||
|
const getArray = ( value ) => {
|
||||||
|
if ( Array.isArray( value ) ) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if ( value ) {
|
||||||
|
return Object.values( value );
|
||||||
|
}
|
||||||
|
return EMPTY_ARR;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Implement a persistentData resolver!
|
||||||
export const persistentData = ( state ) => {
|
export const persistentData = ( state ) => {
|
||||||
return getState( state ).data || EMPTY_OBJ;
|
return getState( state ).data || EMPTY_OBJ;
|
||||||
};
|
};
|
||||||
|
@ -23,15 +33,15 @@ export const transientData = ( state ) => {
|
||||||
|
|
||||||
export const getTodos = ( state ) => {
|
export const getTodos = ( state ) => {
|
||||||
const todos = state?.todos || persistentData( state ).todos;
|
const todos = state?.todos || persistentData( state ).todos;
|
||||||
return todos || EMPTY_ARR;
|
return getArray( todos );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDismissedTodos = ( state ) => {
|
export const getDismissedTodos = ( state ) => {
|
||||||
const dismissed =
|
const dismissed =
|
||||||
state?.dismissedTodos || persistentData( state ).dismissedTodos;
|
state?.dismissedTodos || persistentData( state ).dismissedTodos;
|
||||||
return dismissed || EMPTY_ARR;
|
return getArray( dismissed );
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCompletedTodos = ( state ) => {
|
export const getCompletedTodos = ( state ) => {
|
||||||
return state?.completedTodos || EMPTY_ARR; // Only look at root state, not persistent data
|
return getArray( state?.completedTodos ); // Only look at root state, not persistent data
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
|
||||||
import { store as noticesStore } from '@wordpress/notices';
|
import { store as noticesStore } from '@wordpress/notices';
|
||||||
|
|
||||||
import { CommonHooks, OnboardingHooks } from '../data';
|
import { CommonHooks, OnboardingHooks } from '../data';
|
||||||
|
import { useStoreManager } from './useStoreManager';
|
||||||
|
|
||||||
const PAYPAL_PARTNER_SDK_URL =
|
const PAYPAL_PARTNER_SDK_URL =
|
||||||
'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
|
'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js';
|
||||||
|
@ -30,7 +31,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
|
const { sandboxOnboardingUrl } = CommonHooks.useSandbox();
|
||||||
const { productionOnboardingUrl } = CommonHooks.useProduction();
|
const { productionOnboardingUrl } = CommonHooks.useProduction();
|
||||||
const products = OnboardingHooks.useDetermineProducts();
|
const products = OnboardingHooks.useDetermineProducts();
|
||||||
const { withActivity, startActivity } = CommonHooks.useBusyState();
|
const { startActivity } = CommonHooks.useBusyState();
|
||||||
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
|
||||||
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
|
const [ onboardingUrl, setOnboardingUrl ] = useState( '' );
|
||||||
const [ scriptLoaded, setScriptLoaded ] = useState( false );
|
const [ scriptLoaded, setScriptLoaded ] = useState( false );
|
||||||
|
@ -134,7 +135,7 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
|
||||||
// Ensure the onComplete handler is not removed by a PayPal init script.
|
// Ensure the onComplete handler is not removed by a PayPal init script.
|
||||||
timerRef.current = setInterval( addHandler, 250 );
|
timerRef.current = setInterval( addHandler, 250 );
|
||||||
},
|
},
|
||||||
[ authenticateWithOAuth, withActivity ]
|
[ authenticateWithOAuth, startActivity ]
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeCompleteHandler = useCallback( () => {
|
const removeCompleteHandler = useCallback( () => {
|
||||||
|
@ -161,6 +162,7 @@ const useConnectionBase = () => {
|
||||||
useDispatch( noticesStore );
|
useDispatch( noticesStore );
|
||||||
const { verifyLoginStatus } = CommonHooks.useMerchantInfo();
|
const { verifyLoginStatus } = CommonHooks.useMerchantInfo();
|
||||||
const { withActivity } = CommonHooks.useBusyState();
|
const { withActivity } = CommonHooks.useBusyState();
|
||||||
|
const { refreshAll } = useStoreManager();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleFailed: ( res, genericMessage ) => {
|
handleFailed: ( res, genericMessage ) => {
|
||||||
|
@ -178,6 +180,7 @@ const useConnectionBase = () => {
|
||||||
if ( loginSuccessful ) {
|
if ( loginSuccessful ) {
|
||||||
createSuccessNotice( MESSAGES.CONNECTED );
|
createSuccessNotice( MESSAGES.CONNECTED );
|
||||||
await setCompleted( true );
|
await setCompleted( true );
|
||||||
|
refreshAll();
|
||||||
} else {
|
} else {
|
||||||
createErrorNotice( MESSAGES.LOGIN_FAILED );
|
createErrorNotice( MESSAGES.LOGIN_FAILED );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { useCallback, useMemo } from '@wordpress/element';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CommonHooks,
|
|
||||||
PayLaterMessagingHooks,
|
|
||||||
PaymentHooks,
|
|
||||||
SettingsHooks,
|
|
||||||
StylingHooks,
|
|
||||||
TodosHooks,
|
|
||||||
} from '../data';
|
|
||||||
|
|
||||||
export const useSaveSettings = () => {
|
|
||||||
const { withActivity } = CommonHooks.useBusyState();
|
|
||||||
|
|
||||||
const { persist: persistPayment } = PaymentHooks.useStore();
|
|
||||||
const { persist: persistSettings } = SettingsHooks.useStore();
|
|
||||||
const { persist: persistStyling } = StylingHooks.useStore();
|
|
||||||
const { persist: persistTodos } = TodosHooks.useStore();
|
|
||||||
const { persist: persistPayLaterMessaging } =
|
|
||||||
PayLaterMessagingHooks.useStore();
|
|
||||||
|
|
||||||
const persistActions = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
key: 'persist-methods',
|
|
||||||
message: 'Save payment methods',
|
|
||||||
action: persistPayment,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'persist-settings',
|
|
||||||
message: 'Save the settings',
|
|
||||||
action: persistSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'persist-styling',
|
|
||||||
message: 'Save styling details',
|
|
||||||
action: persistStyling,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'persist-todos',
|
|
||||||
message: 'Save todos state',
|
|
||||||
action: persistTodos,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'persist-pay-later-messaging',
|
|
||||||
message: 'Save pay later messaging details',
|
|
||||||
action: persistPayLaterMessaging,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
persistPayLaterMessaging,
|
|
||||||
persistPayment,
|
|
||||||
persistSettings,
|
|
||||||
persistStyling,
|
|
||||||
persistTodos,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const persistAll = useCallback( () => {
|
|
||||||
/**
|
|
||||||
* Executes onSave on TabPayLaterMessaging component.
|
|
||||||
*
|
|
||||||
* Todo: find a better way for this, because it's highly unreliable
|
|
||||||
* (it only works when the user is still on the "Pay Later Messaging" tab)
|
|
||||||
*/
|
|
||||||
document.getElementById( 'configurator-publishButton' )?.click();
|
|
||||||
|
|
||||||
persistActions.forEach( ( { key, message, action } ) => {
|
|
||||||
withActivity( key, message, action );
|
|
||||||
} );
|
|
||||||
}, [ persistActions, withActivity ] );
|
|
||||||
|
|
||||||
return { persistAll };
|
|
||||||
};
|
|
76
modules/ppcp-settings/resources/js/hooks/useStoreManager.js
Normal file
76
modules/ppcp-settings/resources/js/hooks/useStoreManager.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { useCallback, useMemo } from '@wordpress/element';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CommonHooks,
|
||||||
|
PayLaterMessagingHooks,
|
||||||
|
PaymentHooks,
|
||||||
|
SettingsHooks,
|
||||||
|
StylingHooks,
|
||||||
|
TodosHooks,
|
||||||
|
} from '../data';
|
||||||
|
|
||||||
|
export const useStoreManager = () => {
|
||||||
|
const { withActivity } = CommonHooks.useBusyState();
|
||||||
|
|
||||||
|
const paymentStore = PaymentHooks.useStore();
|
||||||
|
const settingsStore = SettingsHooks.useStore();
|
||||||
|
const stylingStore = StylingHooks.useStore();
|
||||||
|
const todosStore = TodosHooks.useStore();
|
||||||
|
const payLaterStore = PayLaterMessagingHooks.useStore();
|
||||||
|
|
||||||
|
const storeActions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
key: 'methods',
|
||||||
|
message: 'Process payment methods',
|
||||||
|
store: paymentStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'settings',
|
||||||
|
message: 'Process the settings',
|
||||||
|
store: settingsStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'styling',
|
||||||
|
message: 'Process styling details',
|
||||||
|
store: stylingStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'todos',
|
||||||
|
message: 'Process todos state',
|
||||||
|
store: todosStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pay-later-messaging',
|
||||||
|
message: 'Process pay later messaging details',
|
||||||
|
store: payLaterStore,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[ payLaterStore, paymentStore, settingsStore, stylingStore, todosStore ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const persistAll = useCallback( () => {
|
||||||
|
/**
|
||||||
|
* Executes onSave on TabPayLaterMessaging component.
|
||||||
|
*
|
||||||
|
* Todo: find a better way for this, because it's highly unreliable
|
||||||
|
* (it only works when the user is still on the "Pay Later Messaging" tab)
|
||||||
|
*/
|
||||||
|
document.getElementById( 'configurator-publishButton' )?.click();
|
||||||
|
|
||||||
|
storeActions.forEach( ( { key, message, store } ) => {
|
||||||
|
withActivity( `persist-${ key }`, message, store.persist );
|
||||||
|
} );
|
||||||
|
}, [ storeActions, withActivity ] );
|
||||||
|
|
||||||
|
const refreshAll = useCallback( () => {
|
||||||
|
storeActions.forEach( ( { key, message, store } ) => {
|
||||||
|
withActivity( `refresh-${ key }`, message, store.refresh );
|
||||||
|
} );
|
||||||
|
}, [ storeActions, withActivity ] );
|
||||||
|
|
||||||
|
return {
|
||||||
|
persistAll,
|
||||||
|
refreshAll,
|
||||||
|
};
|
||||||
|
};
|
|
@ -211,7 +211,8 @@ class SettingsDataManager {
|
||||||
$this->payment_methods->toggle_method_state( $method['id'], false );
|
$this->payment_methods->toggle_method_state( $method['id'], false );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always enable Venmo and Pay Later.
|
// Always enable PayPal, Venmo and Pay Later.
|
||||||
|
$this->payment_methods->toggle_method_state( PayPalGateway::ID, true );
|
||||||
$this->payment_methods->toggle_method_state( 'venmo', true );
|
$this->payment_methods->toggle_method_state( 'venmo', true );
|
||||||
$this->payment_methods->toggle_method_state( 'pay-later', true );
|
$this->payment_methods->toggle_method_state( 'pay-later', true );
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue