mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
🔀 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:
commit
2f03dcb948
23 changed files with 661 additions and 107 deletions
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import './settings/input';
|
||||
@import './settings/tab-styling';
|
||||
@import './settings/tab-paylater-configurator';
|
||||
|
||||
// Container and Tab Settings
|
||||
.ppcp-r-tabs.settings,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
) }
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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'
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -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'
|
||||
) }
|
||||
/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ',
|
||||
},
|
||||
],
|
||||
} }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
} );
|
||||
};
|
||||
|
|
45
modules/ppcp-settings/resources/js/data/_example/README.md
Normal file
45
modules/ppcp-settings/resources/js/data/_example/README.md
Normal 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`
|
|
@ -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',
|
||||
};
|
71
modules/ppcp-settings/resources/js/data/_example/actions.js
Normal file
71
modules/ppcp-settings/resources/js/data/_example/actions.js
Normal 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 };
|
||||
};
|
|
@ -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>';
|
23
modules/ppcp-settings/resources/js/data/_example/controls.js
vendored
Normal file
23
modules/ppcp-settings/resources/js/data/_example/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,
|
||||
} );
|
||||
},
|
||||
};
|
65
modules/ppcp-settings/resources/js/data/_example/hooks.js
Normal file
65
modules/ppcp-settings/resources/js/data/_example/hooks.js
Normal 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,
|
||||
};
|
||||
};
|
24
modules/ppcp-settings/resources/js/data/_example/index.js
Normal file
24
modules/ppcp-settings/resources/js/data/_example/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 };
|
56
modules/ppcp-settings/resources/js/data/_example/reducer.js
Normal file
56
modules/ppcp-settings/resources/js/data/_example/reducer.js
Normal 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;
|
|
@ -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'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue