🔀 Merge branch 'trunk'

# Conflicts:
#	modules/ppcp-settings/resources/css/components/screens/settings/_tab-paylater-configurator.scss
#	modules/ppcp-settings/resources/css/style.scss
#	modules/ppcp-settings/resources/js/Components/Screens/tabs.js
This commit is contained in:
Philipp Stracker 2025-01-14 12:29:39 +01:00
commit 2f03dcb948
No known key found for this signature in database
23 changed files with 661 additions and 107 deletions

View file

@ -9,11 +9,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayLaterConfigurator;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\GetConfig;
use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig;
use WooCommerce\PayPalCommerce\PayLaterConfigurator\Factory\ConfigFactory;
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
@ -70,9 +68,12 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec
$current_page_id = $c->get( 'wcgateway.current-ppcp-settings-page-id' );
$is_wc_settings_page = $c->get( 'wcgateway.is-wc-settings-page' );
$messaging_locations = $c->get( 'paylater-configurator.messaging-locations' );
$onboarding_profile = $c->get( 'settings.data.onboarding' );
self::add_paylater_update_notice( $messaging_locations, $is_wc_settings_page, $current_page_id, $onboarding_profile );
self::add_paylater_update_notice(
$messaging_locations,
$is_wc_settings_page,
$current_page_id
);
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
@ -162,19 +163,13 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec
* The notice appears on any PayPal-Settings page, except for the Pay-Later settings page,
* when no Pay-Later messaging is used yet.
*
* @param array $message_locations PayLater messaging locations.
* @param bool $is_settings_page Whether the current page is a WC settings page.
* @param string $current_page_id ID of current settings page tab.
* @param OnboardingProfile $onboarding_profile Onboarding profile.
* @param array $message_locations PayLater messaging locations.
* @param bool $is_settings_page Whether the current page is a WC settings page.
* @param string $current_page_id ID of current settings page tab.
*
* @return void
*/
private static function add_paylater_update_notice( array $message_locations, bool $is_settings_page, string $current_page_id, OnboardingProfile $onboarding_profile ) : void {
// Don't display the notice if the user has not completed the onboarding process.
if ( $onboarding_profile->get_completed() !== true ) {
return;
}
private static function add_paylater_update_notice( array $message_locations, bool $is_settings_page, string $current_page_id ) : void {
// The message must be registered on any WC-Settings page, except for the Pay Later page.
if ( ! $is_settings_page || Settings::PAY_LATER_TAB_ID === $current_page_id ) {
return;

View file

@ -1,5 +1,6 @@
@import './settings/input';
@import './settings/tab-styling';
@import './settings/tab-paylater-configurator';
// Container and Tab Settings
.ppcp-r-tabs.settings,

View file

@ -0,0 +1,89 @@
.ppcp-r-paylater-configurator {
display: flex;
border: 1px solid $color-gray-200;
border-radius: 8px;
overflow: hidden;
.css-1snxoyf.eolpigi0 {
margin: 0;
}
#configurator-eligibleContainer.css-4nclxm.e1vy3g880 {
width: 100%;
max-width: 100%;
padding: 48px 0px 48px 48px;
#configurator-controlPanelContainer.css-5urmrq.e1vy3g880 {
width: 374px;
padding-right: 48px;
}
#configurator-previewSectionContainer.css-vojyxx.e1vy3g880 {
width: calc(100% - 374px);
.css-7xkxom, .css-8tvj6u {
height: auto;
}
.css-10nkerk.ej6n7t60 {
align-items: flex-start;
}
.css-1sgwra0-svg-size_sm {
height: 1.2rem;
width: 1.2rem;
}
.css-1vc34jy-handler {
height: 1.6rem;
width: 1.6rem;
}
.css-8vwtr6-state {
height: 1.6rem;
}
}
}
&__subheader, #configurator-controlPanelSubHeader {
@include font(13, 20, 400);
color: $color-gray-800;
margin: 0 0 18px 0;
}
.css-1caaugt-links_base-text_body_strong, .css-dpyjrq-text_body {
@include font(13, 20, 400);
}
&__header, #configurator-controlPanelHeader, #configurator-previewSectionSubHeaderText.css-14ujlqd-text_body, .css-16jt5za-text_body {
@include font(14, 20, 600);
color: $color-gray-800;
margin: 0 0 8px 0;
display: block;
}
.css-1yo2lxy-text_body_strong {
@include font(13, 16, 600);
color: $color-black;
margin: 0;
text-transform: none;
}
.css-rok10q {
margin-top: 0;
}
&__publish-button {
display: none;
}
.css-1oxdnb3-dropdown_menu_button-text_field_value_sm-active, .css-1wvwydd-dropdown_menu_button-text_field_value_sm-active-active, .css-16jt5za-text_body {
font-size: 13px;
line-height: 1.5384615385;
font-weight: 400;
}
.css-udzaps {
padding: 0px;
}
}

View file

@ -1,40 +1,62 @@
import data from '../../utils/data';
const BadgeBox = ( props ) => {
const titleSize =
props.titleType && props.titleType === BADGE_BOX_TITLE_BIG
? BADGE_BOX_TITLE_BIG
: BADGE_BOX_TITLE_SMALL;
const ImageBadge = ( { images } ) => {
if ( ! images || ! images.length ) {
return null;
}
return (
<BadgeContent>
<span className="ppcp-r-badge-box__title-image-badge">
{ images.map( ( badge ) => data().getImage( badge ) ) }
</span>
</BadgeContent>
);
};
// If `children` is not empty, it's output and wrapped in spaces.
const BadgeContent = ( { children } ) => {
if ( ! children ) {
return null;
}
return <> { children } </>;
};
const BadgeBox = ( {
title,
textBadge,
imageBadge = [],
titleType = BADGE_BOX_TITLE_BIG,
description = '',
} ) => {
let titleSize = BADGE_BOX_TITLE_SMALL;
if ( BADGE_BOX_TITLE_BIG === titleType ) {
titleSize = BADGE_BOX_TITLE_BIG;
}
const titleTextClassName =
'ppcp-r-badge-box__title-text ' +
`ppcp-r-badge-box__title-text--${ titleSize }`;
const titleBaseClassName = 'ppcp-r-badge-box__title';
const titleClassName = props.imageBadge
const titleClassName = imageBadge.length
? `${ titleBaseClassName } ppcp-r-badge-box__title--has-image-badge`
: titleBaseClassName;
return (
<div className="ppcp-r-badge-box">
<span className={ titleClassName }>
<span className={ titleTextClassName }>{ props.title }</span>
<span className={ titleTextClassName }>{ title }</span>
{ props.imageBadge && (
<span className="ppcp-r-badge-box__title-image-badge">
{ props.imageBadge.map( ( badge ) =>
data().getImage( badge )
) }
</span>
) }
{ props.textBadge }
<ImageBadge images={ imageBadge } />
<BadgeContent>{ textBadge }</BadgeContent>
</span>
<div className="ppcp-r-badge-box__description">
{ props?.description && (
{ description && (
<p
className="ppcp-r-badge-box__description"
dangerouslySetInnerHTML={ {
__html: props.description,
__html: description,
} }
></p>
) }

View file

@ -1,54 +1,35 @@
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { CommonHooks } from '../../data';
const ConnectionInfo = ( { connectionStatusDataDefault } ) => {
const [ connectionData, setConnectionData ] = useState( {
...connectionStatusDataDefault,
} );
const toggleStatusClassName = [ 'ppcp-r-connection-status__status-toggle' ];
if ( connectionData.showAllData ) {
toggleStatusClassName.push(
'ppcp-r-connection-status__status-toggle--toggled'
);
}
const ConnectionInfo = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<div className="ppcp-r-connection-status__data">
<div className="ppcp-r-connection-status__status-row ppcp-r-connection-status__status-row--first">
<span className="ppcp-r-connection-status__status-label">
{ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
</span>
<span className="ppcp-r-connection-status__status-value">
{ connectionData.merchantId }
</span>
</div>
<div className="ppcp-r-connection-status__status-row">
<span className="ppcp-r-connection-status__status-label">
{ __( 'Email address', 'woocommerce-paypal-payments' ) }
</span>
<span className="ppcp-r-connection-status__status-value">
{ connectionData.email }
</span>
</div>
<div className="ppcp-r-connection-status__status-row">
<span className="ppcp-r-connection-status__status-label">
{ __( 'Client ID', 'woocommerce-paypal-payments' ) }
</span>
<span className="ppcp-r-connection-status__status-value">
{ connectionData.clientId }
</span>
</div>
<StatusRow
label={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
value={ merchant.id }
/>
<StatusRow
label={ __( 'Email address', 'woocommerce-paypal-payments' ) }
value={ merchant.email }
/>
<StatusRow
label={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
value={ merchant.clientId }
/>
</div>
);
};
export default ConnectionInfo;
export const connectionStatusDataDefault = {
connectionStatus: true,
showAllData: false,
email: 'bt_us@woocommerce.com',
merchantId: 'AT45V2DGMKLRY',
clientId: 'BAARTJLxtUNN4d2GMB6Eut3suMDYad72xQA-FntdIFuJ6FmFJITxAY8',
};
const StatusRow = ( { label, value } ) => (
<div className="ppcp-r-connection-status__status-row">
<span className="ppcp-r-connection-status__status-label">
{ label }
</span>
<span className="ppcp-r-connection-status__status-value">
{ value }
</span>
</div>
);

View file

@ -95,12 +95,12 @@ const AcdcFlow = ( { isFastlane, isPayLater, storeCountry } ) => {
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Optional payment methods',
'Expanded Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'Accept debit/credit cards, PayPal, Apple Pay, Google Pay, and more. Note: Additional application required for more methods',
'woocommerce-paypal-payments'
) }
/>

View file

@ -94,12 +94,12 @@ const BcdcFlow = ( { isPayLater, storeCountry } ) => {
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Optional payment methods',
'Expanded Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'Accept debit/credit cards, PayPal, Apple Pay, Google Pay, and more. Note: Additional application required for more methods',
'woocommerce-paypal-payments'
) }
/>

View file

@ -17,14 +17,21 @@ const StepPaymentMethods = ( {} ) => {
const { storeCountry, storeCurrency } = CommonHooks.useWooSettings();
let screenTitle = __(
'Add optional payment methods to your Checkout',
'woocommerce-paypal-payments'
);
if ( 'US' === storeCountry ) {
screenTitle = __(
'Add Expanded Checkout for More Ways to Pay',
'woocommerce-paypal-payments'
);
}
return (
<div className="ppcp-r-page-optional-payment-methods">
<OnboardingHeader
title={ __(
'Add optional payment methods to your Checkout',
'woocommerce-paypal-payments'
) }
/>
<OnboardingHeader title={ screenTitle } />
<div className="ppcp-r-inner-container">
<SelectBoxWrapper>
<SelectBox

View file

@ -205,7 +205,7 @@ const TabOverview = () => {
'View full documentation',
'woocommerce-paypal-payments'
),
url: '#',
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/ ',
},
],
} }
@ -225,7 +225,7 @@ const TabOverview = () => {
'View support options',
'woocommerce-paypal-payments'
),
url: '#',
url: 'https://woocommerce.com/document/woocommerce-paypal-payments/#get-help ',
},
],
} }

View file

@ -1,5 +1,50 @@
import React, { useEffect } from 'react';
const TabPayLaterMessaging = () => {
return <div className="ppcp-r-pay-later-messaging"></div>;
const config = {}; // Replace with the appropriate/saved configuration.
const PcpPayLaterConfigurator =
window.ppcpSettings?.PcpPayLaterConfigurator;
useEffect( () => {
if ( window.merchantConfigurators && PcpPayLaterConfigurator ) {
window.merchantConfigurators.Messaging( {
config,
merchantClientId: PcpPayLaterConfigurator.merchantClientId,
partnerClientId: PcpPayLaterConfigurator.partnerClientId,
partnerName: 'WooCommerce',
bnCode: PcpPayLaterConfigurator.bnCode,
placements: [
'cart',
'checkout',
'product',
'shop',
'home',
'custom_placement',
],
styleOverrides: {
button: 'ppcp-r-paylater-configurator__publish-button',
header: 'ppcp-r-paylater-configurator__header',
subheader: 'ppcp-r-paylater-configurator__subheader',
},
onSave: ( data ) => {
/*
TODO:
- The saving will be handled in a separate PR.
- One option could be:
- When saving the settings, programmatically click on the configurator's
"Save Changes" button and send the request to PHP.
*/
},
} );
}
}, [ PcpPayLaterConfigurator ] );
return (
<div
id="messaging-configurator"
className="ppcp-r-paylater-configurator"
></div>
);
};
export default TabPayLaterMessaging;

View file

@ -1,13 +1,13 @@
import { __ } from '@wordpress/i18n';
import SettingsCard from '../../../ReusableComponents/SettingsCard';
import ConnectionInfo, {
connectionStatusDataDefault,
} from '../../../ReusableComponents/ConnectionInfo';
import { CommonHooks } from '../../../../data';
import TitleBadge, {
TITLE_BADGE_NEGATIVE,
TITLE_BADGE_POSITIVE,
} from '../../../ReusableComponents/TitleBadge';
import ConnectionInfo from '../../../ReusableComponents/ConnectionInfo';
const ConnectionStatus = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<SettingsCard
className="ppcp-r-tab-overview-support"
@ -20,7 +20,7 @@ const ConnectionStatus = () => {
<div className="ppcp-r-connection-status">
<div className="ppcp-r-connection-status__status">
<div className="ppcp-r-connection-status__status-status">
{ connectionStatusDataDefault.connectionStatus ? (
{ merchant.isConnected ? (
<TitleBadge
type={ TITLE_BADGE_POSITIVE }
text={ __(
@ -39,12 +39,8 @@ const ConnectionStatus = () => {
) }
</div>
</div>
{ connectionStatusDataDefault.connectionStatus && (
<ConnectionInfo
connectionStatusDataDefault={
connectionStatusDataDefault
}
/>
{ merchant.isConnected && (
<ConnectionInfo connectionStatusDataDefault={ merchant } />
) }
</div>
</SettingsCard>

View file

@ -38,9 +38,12 @@ const DEFAULT_TABS = [
name: 'pay-later-messaging',
title: __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ),
Component: <TabPayLaterMessaging />,
showIf: () => !! window.ppcpSettings?.isPayLaterConfiguratorAvailable,
},
];
export const getSettingsTabs = () => {
return DEFAULT_TABS;
return DEFAULT_TABS.filter( ( tab ) => {
return ! tab.showIf || tab.showIf();
} );
};

View file

@ -0,0 +1,45 @@
# Store template
This template contains all files for a Redux store.
## New Store: Redux integration
1. Copy this folder, give it a correct name.
2. Check each file for `<UNKNOWN>` placeholders and `TODO` remarks.
3. Edit the main store-index file and add the relevant store integration there.
4. Check the debug-module, and add relevant debug code.
- Register the store in the `reset()` method.
---
Main store-index:
`modules/ppcp-settings/resources/js/data/index.js`
Sample store integration:
```js
import * as YourStore from './yourStore';
// ...
YourStore.initStore();
// ...
export const YourStoreHooks = YourStore.hooks;
// ...
export const YourStoreName = YourStore.STORE_NAME;
// ...
addDebugTools( window.ppcpSettings, [ ..., YourStoreName ] );
```
---
### New Store: PHP integration
1. Create the **REST endpoint** for hydrating and persisting data.
- `modules/ppcp-settings/src/Endpoint/YourStoreRestEndpoint.php`
- Extend from base class `RestEndpoint`
2. Create the **data model** class to manage the DB interaction.
- `modules/ppcp-settings/src/Data/YourStoreSettings.php`
- Extend from base class `AbstractDataModel`
3. Create relevant **DI services** for both files.
- `modules/ppcp-settings/services.php`
4. Register the REST endpoint in the **service module**.
- `modules/ppcp-settings/src/SettingsModule.php`
- Find the action `rest_api_init`

View file

@ -0,0 +1,18 @@
/**
* Action Types: Define unique identifiers for actions across all store modules.
*
* @file
*/
export default {
// Transient data.
SET_TRANSIENT: '<UNKNOWN>:SET_TRANSIENT',
// Persistent data.
SET_PERSISTENT: '<UNKNOWN>:SET_PERSISTENT',
RESET: '<UNKNOWN>:RESET',
HYDRATE: '<UNKNOWN>:HYDRATE',
// Controls - always start with "DO_".
DO_PERSIST_DATA: '<UNKNOWN>:DO_PERSIST_DATA',
};

View file

@ -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. 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,
} );
/**
* 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 },
} );
/**
* 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 };
};

View file

@ -0,0 +1,28 @@
/**
* Name of the Redux store module.
*
* Used by: Reducer, Selector, Index
*
* @type {string}
*/
export const STORE_NAME = 'wc/paypal/<UNKNOWN>';
/**
* REST path to hydrate data of this module by loading data from the WP DB.
*
* Used by: Resolvers
* See: <UNKNOWN>.php
*
* @type {string}
*/
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/<UNKNOWN>';
/**
* REST path to persist data of this module to the WP DB.
*
* Used by: Controls
* See: <UNKNOWN>.php
*
* @type {string}
*/
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/<UNKNOWN>';

View 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,
} );
},
};

View file

@ -0,0 +1,65 @@
/**
* 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,
// TODO: Replace with real property.
setSampleValue,
} = useDispatch( STORE_NAME );
// Read-only flags and derived state.
// Nothing here yet.
// Transient accessors.
const isReady = useTransient( 'isReady' );
// Persistent accessors.
// TODO: Replace with real property.
const sampleValue = usePersistent( 'sampleValue' );
return {
persist,
isReady,
sampleValue,
setSampleValue,
};
};
export const useState = () => {
const { persist, isReady } = useHooks();
return { persist, isReady };
};
// TODO: Replace with real hook.
export const useSampleValue = () => {
const { sampleValue, setSampleValue } = useHooks();
return {
sampleValue,
setSampleValue,
};
};

View 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 };

View file

@ -0,0 +1,56 @@
/**
* 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).
const defaultTransient = Object.freeze( {
isReady: false,
} );
// Persistent: Values that are loaded from the DB.
const defaultPersistent = Object.freeze( {
// TODO: Add real DB properties here.
sampleValue: 'foo',
} );
// 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 ) =>
setPersistent( state, payload.data ),
} );
export default reducer;

View file

@ -0,0 +1,37 @@
/**
* 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(
// TODO: Add the module name to the error message.
__(
'Error retrieving <UNKNOWN> details.',
'woocommerce-paypal-payments'
)
);
}
},
};

View 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;
};

View file

@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class SettingsModule
@ -146,19 +147,45 @@ class SettingsModule implements ServiceModule, ExecutableModule {
$style_asset_file['version']
);
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
wp_enqueue_style( 'ppcp-admin-settings' );
wp_enqueue_style( 'ppcp-admin-settings-font', 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', array(), $style_asset_file['version'] );
$is_pay_later_configurator_available = $container->get( 'paylater-configurator.is-available' );
$script_data = array(
'assets' => array(
'imagesUrl' => $module_url . '/images/',
),
'wcPaymentsTabUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout' ),
'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'isPayLaterConfiguratorAvailable' => $is_pay_later_configurator_available,
);
if ( $is_pay_later_configurator_available ) {
wp_enqueue_script(
'ppcp-paylater-configurator-lib',
'https://www.paypalobjects.com/merchant-library/merchant-configurator.js',
array(),
$script_asset_file['version'],
true
);
$script_data['PcpPayLaterConfigurator'] = array(
'config' => array(),
'merchantClientId' => $settings->get( 'client_id' ),
'partnerClientId' => $container->get( 'api.partner_merchant_id' ),
'bnCode' => PPCP_PAYPAL_BN_CODE,
);
}
wp_localize_script(
'ppcp-admin-settings',
'ppcpSettings',
array(
'assets' => array(
'imagesUrl' => $module_url . '/images/',
),
'wcPaymentsTabUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout' ),
'debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
)
$script_data
);
}
);