mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-07 19:54:15 +08:00
🔀 Merge branch 'trunk'
This commit is contained in:
commit
7cdeab40fe
120 changed files with 2741 additions and 1126 deletions
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo } from '@wordpress/element';
|
||||
import { useEffect, useMemo, useState } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { OnboardingHooks, CommonHooks } from '../data';
|
||||
|
@ -31,6 +31,8 @@ const SettingsApp = () => {
|
|||
loading: ! onboardingIsReady,
|
||||
} );
|
||||
|
||||
const [ activePanel, setActivePanel ] = useState( 'overview' );
|
||||
|
||||
const Content = useMemo( () => {
|
||||
if ( ! onboardingIsReady || ! merchantIsReady ) {
|
||||
return <SpinnerOverlay />;
|
||||
|
@ -44,12 +46,18 @@ const SettingsApp = () => {
|
|||
return <OnboardingScreen />;
|
||||
}
|
||||
|
||||
return <SettingsScreen />;
|
||||
return (
|
||||
<SettingsScreen
|
||||
activePanel={ activePanel }
|
||||
setActivePanel={ setActivePanel }
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
isSendOnlyCountry,
|
||||
merchantIsReady,
|
||||
onboardingCompleted,
|
||||
onboardingIsReady,
|
||||
activePanel,
|
||||
] );
|
||||
|
||||
return <div className={ wrapperClass }>{ Content }</div>;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import data from '../../utils/data';
|
||||
import { PPIcon } from './Icons';
|
||||
|
||||
const ImageBadge = ( { images } ) => {
|
||||
if ( ! images || ! images.length ) {
|
||||
|
@ -8,7 +8,13 @@ const ImageBadge = ( { images } ) => {
|
|||
return (
|
||||
<BadgeContent>
|
||||
<span className="ppcp-r-badge-box__title-image-badge">
|
||||
{ images.map( ( badge ) => data().getImage( badge ) ) }
|
||||
{ images.map( ( badge, index ) => (
|
||||
<PPIcon
|
||||
key={ `badge-${ index }` }
|
||||
imageName={ badge }
|
||||
className="ppcp-r-badge-box__image"
|
||||
/>
|
||||
) ) }
|
||||
</span>
|
||||
</BadgeContent>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ const ControlToggleButton = ( { label, description, value, onChange } ) => (
|
|||
<Action>
|
||||
<ToggleControl
|
||||
className="ppcp--control-toggle"
|
||||
__nextHasNoMarginBottom={ true }
|
||||
__nextHasNoMarginBottom
|
||||
checked={ value }
|
||||
onChange={ onChange }
|
||||
label={ label }
|
||||
|
|
|
@ -65,7 +65,9 @@ const OptionItem = ( {
|
|||
<div className="ppcp--box-content">
|
||||
<div className="ppcp--box-content-inner">
|
||||
<span className="ppcp--box-title">{ itemTitle }</span>
|
||||
<p className="ppcp--box-description">{ itemDescription }</p>
|
||||
<div className="ppcp--box-description">
|
||||
{ itemDescription }
|
||||
</div>
|
||||
{ children && (
|
||||
<div className="ppcp--box-details">{ children }</div>
|
||||
) }
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
const GenericIcon = ( { imageName, className = '', alt = '' } ) => {
|
||||
const pathToImages = global.ppcpSettings.assets.imagesUrl;
|
||||
|
||||
return (
|
||||
<img
|
||||
className={ className }
|
||||
alt={ alt }
|
||||
src={ `${ pathToImages }${ imageName }` }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GenericIcon;
|
|
@ -1,6 +1,6 @@
|
|||
import { SVG, Path } from '@wordpress/primitives';
|
||||
|
||||
const logoPayPal = (
|
||||
const LogoPayPal = (
|
||||
<SVG fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 38">
|
||||
<Path
|
||||
d="M109.583.683v27.359h-6.225V.683h6.225Zm-8.516 9.234v18.175h-5.534v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.259 7.259 0 0 1-2.892.583c-1.3 0-2.508-.242-3.616-.725a9.216 9.216 0 0 1-2.892-2.067 10.021 10.021 0 0 1-1.958-3.05c-.459-1.183-.684-2.458-.684-3.816 0-1.359.225-2.617.684-3.775.483-1.184 1.133-2.217 1.958-3.092a8.708 8.708 0 0 1 2.892-2.033c1.108-.509 2.316-.767 3.616-.767 1.034 0 2 .192 2.892.583a7.312 7.312 0 0 1 2.383 1.567V9.933h5.534v-.016Zm-9.809 13.225c1.134 0 2.059-.384 2.784-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.784-1.167-1.125 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.367 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM72.225.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925a9.439 9.439 0 0 1 1.917 2.908c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3h-6.333V.683h9.333Zm-.908 12.467c.85 0 1.491-.083 1.958-.258a3.853 3.853 0 0 0 1.192-.725c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.329 3.329 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1ZM39.558 9.917h6.875l4.667 8.716h.075l4.158-8.716H61.7l-13.642 27.4h-6.333l6.225-12.534-8.392-14.866Zm-1.225 0v18.175H32.8v-1.567c-.7.683-1.5 1.2-2.383 1.567a7.258 7.258 0 0 1-2.892.583c-1.3 0-2.508-.242-3.617-.725a9.218 9.218 0 0 1-2.891-2.067 10.18 10.18 0 0 1-1.959-3.05c-.458-1.183-.683-2.458-.683-3.816 0-1.359.225-2.617.683-3.775.484-1.184 1.134-2.217 1.959-3.092a8.626 8.626 0 0 1 2.891-2.033c1.109-.509 2.317-.767 3.617-.767 1.033 0 2 .192 2.892.583A7.312 7.312 0 0 1 32.8 11.5V9.933h5.533v-.016Zm-9.808 13.225c1.133 0 2.058-.384 2.792-1.167.75-.775 1.125-1.767 1.125-2.975 0-1.208-.375-2.208-1.125-2.975-.725-.775-1.659-1.167-2.792-1.167-1.133 0-2.075.384-2.825 1.167-.725.775-1.083 1.767-1.083 2.975 0 1.208.366 2.208 1.083 2.975.75.775 1.692 1.167 2.825 1.167ZM9.75.683c1.642 0 3.042.234 4.2.692 1.158.458 2.133 1.1 2.933 1.925A9.439 9.439 0 0 1 18.8 6.208c.458 1.092.683 2.267.683 3.525 0 1.259-.225 2.434-.683 3.525a9.293 9.293 0 0 1-1.917 2.909c-.791.825-1.775 1.466-2.933 1.925-1.158.458-2.558.691-4.2.691h-3v9.3H.417V.683H9.75Zm-.9 12.467c.85 0 1.492-.083 1.958-.258A3.855 3.855 0 0 0 12 12.167c.65-.609.975-1.417.975-2.434 0-1.016-.325-1.825-.975-2.433a3.33 3.33 0 0 0-1.192-.692c-.458-.191-1.108-.291-1.958-.291h-2.1v6.833h2.1Z"
|
||||
|
@ -9,4 +9,4 @@ const logoPayPal = (
|
|||
</SVG>
|
||||
);
|
||||
|
||||
export default logoPayPal;
|
||||
export default LogoPayPal;
|
|
@ -1,9 +1,9 @@
|
|||
import { SVG, Path } from '@wordpress/primitives';
|
||||
|
||||
const openSignup = (
|
||||
const OpenSignup = (
|
||||
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24">
|
||||
<Path d="M12.4999 12.75V18.75C12.4999 18.9489 12.4209 19.1397 12.2803 19.2803C12.1396 19.421 11.9488 19.5 11.7499 19.5C11.551 19.5 11.3603 19.421 11.2196 19.2803C11.0789 19.1397 10.9999 18.9489 10.9999 18.75V14.5613L4.78055 20.7806C4.71087 20.8503 4.62815 20.9056 4.5371 20.9433C4.44606 20.981 4.34847 21.0004 4.24993 21.0004C4.15138 21.0004 4.0538 20.981 3.96276 20.9433C3.87171 20.9056 3.78899 20.8503 3.7193 20.7806C3.64962 20.7109 3.59435 20.6282 3.55663 20.5372C3.51892 20.4461 3.49951 20.3485 3.49951 20.25C3.49951 20.1515 3.51892 20.0539 3.55663 19.9628C3.59435 19.8718 3.64962 19.7891 3.7193 19.7194L9.93868 13.5H5.74993C5.55102 13.5 5.36025 13.421 5.2196 13.2803C5.07895 13.1397 4.99993 12.9489 4.99993 12.75C4.99993 12.5511 5.07895 12.3603 5.2196 12.2197C5.36025 12.079 5.55102 12 5.74993 12H11.7499C11.9488 12 12.1396 12.079 12.2803 12.2197C12.4209 12.3603 12.4999 12.5511 12.4999 12.75ZM19.9999 3H7.99993C7.6021 3 7.22057 3.15804 6.93927 3.43934C6.65796 3.72064 6.49993 4.10218 6.49993 4.5V9C6.49993 9.19891 6.57895 9.38968 6.7196 9.53033C6.86025 9.67098 7.05102 9.75 7.24993 9.75C7.44884 9.75 7.63961 9.67098 7.78026 9.53033C7.92091 9.38968 7.99993 9.19891 7.99993 9V4.5H19.9999V16.5H15.4999C15.301 16.5 15.1103 16.579 14.9696 16.7197C14.8289 16.8603 14.7499 17.0511 14.7499 17.25C14.7499 17.4489 14.8289 17.6397 14.9696 17.7803C15.1103 17.921 15.301 18 15.4999 18H19.9999C20.3978 18 20.7793 17.842 21.0606 17.5607C21.3419 17.2794 21.4999 16.8978 21.4999 16.5V4.5C21.4999 4.10218 21.3419 3.72064 21.0606 3.43934C20.7793 3.15804 20.3978 3 19.9999 3Z" />
|
||||
</SVG>
|
||||
);
|
||||
|
||||
export default openSignup;
|
||||
export default OpenSignup;
|
|
@ -1,5 +1,6 @@
|
|||
export { default as openSignup } from './open-signup';
|
||||
export { default as logoPayPal } from './logo-paypal';
|
||||
export { default as PPIcon } from './GenericIcon';
|
||||
export { default as OpenSignup } from './OpenSignup';
|
||||
export { default as LogoPayPal } from './LogoPayPal';
|
||||
|
||||
export const NOTIFICATION_SUCCESS = '✔️';
|
||||
export const NOTIFICATION_ERROR = '❌';
|
||||
|
|
|
@ -16,7 +16,7 @@ const SettingsBlock = ( {
|
|||
} );
|
||||
|
||||
return (
|
||||
<div className={ blockClassName }>
|
||||
<div className={ blockClassName } id={ className }>
|
||||
<BlockTitle
|
||||
blockTitle={ title }
|
||||
blockSuffix={ titleSuffix }
|
||||
|
|
|
@ -14,10 +14,12 @@ const PaymentMethodItemBlock = ( {
|
|||
<SettingsBlock className="ppcp--method-item" separatorAndGap={ false }>
|
||||
<div className="ppcp--method-inner">
|
||||
<div className="ppcp--method-title-wrapper">
|
||||
<PaymentMethodIcon
|
||||
icons={ [ paymentMethod.icon ] }
|
||||
type={ paymentMethod.icon }
|
||||
/>
|
||||
{ paymentMethod?.icon && (
|
||||
<PaymentMethodIcon
|
||||
icons={ [ paymentMethod.icon ] }
|
||||
type={ paymentMethod.icon }
|
||||
/>
|
||||
) }
|
||||
<span className="ppcp--method-title">
|
||||
{ paymentMethod.itemTitle }
|
||||
</span>
|
||||
|
@ -27,7 +29,7 @@ const PaymentMethodItemBlock = ( {
|
|||
</p>
|
||||
<div className="ppcp--method-footer">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom={ true }
|
||||
__nextHasNoMarginBottom
|
||||
checked={ isSelected }
|
||||
onChange={ onSelect }
|
||||
/>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { selectTab, TAB_IDS } from '../../../utils/tabSelector';
|
||||
|
||||
const TodoSettingsBlock = ( { todosData, className = '' } ) => {
|
||||
if ( todosData.length === 0 ) {
|
||||
return null;
|
||||
|
@ -7,29 +9,50 @@ const TodoSettingsBlock = ( { todosData, className = '' } ) => {
|
|||
<div
|
||||
className={ `ppcp-r-settings-block__todo ppcp-r-todo-items ${ className }` }
|
||||
>
|
||||
{ todosData
|
||||
.slice( 0, 5 )
|
||||
.filter( ( todo ) => {
|
||||
return ! todo.isCompleted();
|
||||
} )
|
||||
.map( ( todo ) => (
|
||||
<TodoItem
|
||||
key={ todo.id }
|
||||
title={ todo.title }
|
||||
onClick={ todo.onClick }
|
||||
/>
|
||||
) ) }
|
||||
{ todosData.slice( 0, 5 ).map( ( todo ) => (
|
||||
<TodoItem
|
||||
key={ todo.id }
|
||||
title={ todo.title }
|
||||
description={ todo.description }
|
||||
isCompleted={ todo.isCompleted }
|
||||
onClick={ async () => {
|
||||
if ( todo.action.type === 'tab' ) {
|
||||
const tabId =
|
||||
TAB_IDS[ todo.action.tab.toUpperCase() ];
|
||||
await selectTab( tabId, todo.action.section );
|
||||
} else if ( todo.action.type === 'external' ) {
|
||||
window.open( todo.action.url, '_blank' );
|
||||
}
|
||||
} }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TodoItem = ( props ) => {
|
||||
const TodoItem = ( { title, description, isCompleted, onClick } ) => {
|
||||
return (
|
||||
<div className="ppcp-r-todo-item" onClick={ props.onClick }>
|
||||
<div
|
||||
className={ `ppcp-r-todo-item ${
|
||||
isCompleted ? 'is-completed' : ''
|
||||
}` }
|
||||
onClick={ onClick }
|
||||
>
|
||||
<div className="ppcp-r-todo-item__inner">
|
||||
<div className="ppcp-r-todo-item__icon"></div>
|
||||
<div className="ppcp-r-todo-item__description">
|
||||
{ props.title }
|
||||
<div className="ppcp-r-todo-item__icon">
|
||||
{ isCompleted && (
|
||||
<span className="dashicons dashicons-yes"></span>
|
||||
) }
|
||||
</div>
|
||||
<div className="ppcp-r-todo-item__content">
|
||||
<div className="ppcp-r-todo-item__description">
|
||||
{ title }
|
||||
</div>
|
||||
{ description && (
|
||||
<div className="ppcp-r-todo-item__secondary-description">
|
||||
{ description }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -43,6 +43,7 @@ const SettingsToggleBlock = ( {
|
|||
</div>
|
||||
<div className="ppcp-r-toggle-block__switch">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom
|
||||
ref={ toggleRef }
|
||||
checked={ isToggled }
|
||||
onChange={ ( newState ) => setToggled( newState ) }
|
||||
|
|
|
@ -1,26 +1,14 @@
|
|||
import { useCallback, useEffect, useState } from '@wordpress/element';
|
||||
import { useCallback, useEffect } from '@wordpress/element';
|
||||
|
||||
// TODO: Migrate to Tabs (TabPanel v2) once its API is publicly available, as it provides programmatic tab switching support: https://github.com/WordPress/gutenberg/issues/52997
|
||||
import { TabPanel } from '@wordpress/components';
|
||||
|
||||
import { getQuery, updateQueryString } from '../../utils/navigation';
|
||||
|
||||
const TabNavigation = ( { tabs } ) => {
|
||||
const { panel } = getQuery();
|
||||
import { updateQueryString } from '../../utils/navigation';
|
||||
|
||||
const TabBar = ( { tabs, activePanel, setActivePanel } ) => {
|
||||
const isValidTab = ( tabsList, checkTab ) => {
|
||||
return tabsList.some( ( tab ) => tab.name === checkTab );
|
||||
};
|
||||
|
||||
const getValidInitialPanel = () => {
|
||||
if ( ! panel || ! isValidTab( tabs, panel ) ) {
|
||||
return tabs[ 0 ].name;
|
||||
}
|
||||
return panel;
|
||||
};
|
||||
|
||||
const [ activePanel, setActivePanel ] = useState( getValidInitialPanel );
|
||||
|
||||
const updateActivePanel = useCallback(
|
||||
( tabName ) => {
|
||||
if ( isValidTab( tabs, tabName ) ) {
|
||||
|
@ -29,7 +17,7 @@ const TabNavigation = ( { tabs } ) => {
|
|||
console.warn( `Invalid tab name: ${ tabName }` );
|
||||
}
|
||||
},
|
||||
[ tabs ]
|
||||
[ tabs, setActivePanel ]
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
|
@ -43,9 +31,9 @@ const TabNavigation = ( { tabs } ) => {
|
|||
onSelect={ updateActivePanel }
|
||||
tabs={ tabs }
|
||||
>
|
||||
{ ( { Component } ) => Component }
|
||||
{ () => '' }
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabNavigation;
|
||||
export default TabBar;
|
|
@ -15,6 +15,7 @@ const TopNavigation = ( {
|
|||
onTitleClick = null,
|
||||
showProgressBar = false,
|
||||
progressBarPercent = 0,
|
||||
subNavigation = null,
|
||||
} ) => {
|
||||
const { goToWooCommercePaymentsTab } = useNavigation();
|
||||
const { isScrolled } = useIsScrolled();
|
||||
|
@ -40,35 +41,43 @@ const TopNavigation = ( {
|
|||
}, [] );
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
<div className="ppcp-r-navigation">
|
||||
<BusyStateWrapper
|
||||
className="ppcp-r-navigation--left"
|
||||
busySpinner={ false }
|
||||
enabled={ ! exitOnTitleClick }
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={ handleTitleClick }
|
||||
className="is-title"
|
||||
<>
|
||||
<nav className={ className }>
|
||||
<div className="ppcp-r-navigation">
|
||||
<BusyStateWrapper
|
||||
className="ppcp-r-navigation--left"
|
||||
busySpinner={ false }
|
||||
enabled={ ! exitOnTitleClick }
|
||||
>
|
||||
<Icon icon={ chevronLeft } />
|
||||
<span className={ titleClassName }>{ title }</span>
|
||||
</Button>
|
||||
</BusyStateWrapper>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={ handleTitleClick }
|
||||
className="is-title"
|
||||
>
|
||||
<Icon icon={ chevronLeft } />
|
||||
<span className={ titleClassName }>{ title }</span>
|
||||
</Button>
|
||||
</BusyStateWrapper>
|
||||
|
||||
<BusyStateWrapper
|
||||
className="ppcp-r-navigation--right"
|
||||
busySpinner={ false }
|
||||
>
|
||||
{ children }
|
||||
</BusyStateWrapper>
|
||||
<BusyStateWrapper
|
||||
className="ppcp-r-navigation--right"
|
||||
busySpinner={ false }
|
||||
>
|
||||
{ children }
|
||||
</BusyStateWrapper>
|
||||
</div>
|
||||
|
||||
{ subNavigation && (
|
||||
<section className="ppcp--top-sub-navigation">
|
||||
{ subNavigation }
|
||||
</section>
|
||||
) }
|
||||
|
||||
{ showProgressBar && (
|
||||
<ProgressBar percent={ progressBarPercent } />
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ const AcdcOptionalPaymentMethods = ( {
|
|||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-sepa.svg',
|
||||
// 'icon-button-sepa.svg',
|
||||
'icon-button-ideal.svg',
|
||||
'icon-button-blik.svg',
|
||||
'icon-button-bancontact.svg',
|
||||
|
@ -211,7 +211,7 @@ const AcdcOptionalPaymentMethods = ( {
|
|||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
imageBadge={ [
|
||||
'icon-button-sepa.svg',
|
||||
// 'icon-button-sepa.svg',
|
||||
'icon-button-ideal.svg',
|
||||
'icon-button-blik.svg',
|
||||
'icon-button-bancontact.svg',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@wordpress/components';
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
import { openSignup } from '../../../ReusableComponents/Icons';
|
||||
import { OpenSignup } from '../../../ReusableComponents/Icons';
|
||||
import { useHandleOnboardingButton } from '../../../../hooks/useHandleConnections';
|
||||
import BusyStateWrapper from '../../../ReusableComponents/BusyStateWrapper';
|
||||
|
||||
|
@ -27,7 +27,7 @@ const ButtonOrPlaceholder = ( {
|
|||
const buttonProps = {
|
||||
className,
|
||||
variant,
|
||||
icon: showIcon ? openSignup : null,
|
||||
icon: showIcon ? OpenSignup : null,
|
||||
};
|
||||
|
||||
if ( href ) {
|
||||
|
|
|
@ -21,9 +21,37 @@ const OnboardingNavigation = ( { stepDetails, onNext, onPrev } ) => {
|
|||
showProgressBar={ true }
|
||||
progressBarPercent={ percentage * 0.9 }
|
||||
>
|
||||
<Button variant="link" onClick={ goToWooCommercePaymentsTab }>
|
||||
<OnboardingNavigationActions
|
||||
onExit={ goToWooCommercePaymentsTab }
|
||||
isFirst={ isFirst }
|
||||
isDisabled={ isDisabled }
|
||||
showNext={ showNext }
|
||||
onNext={ onNext }
|
||||
/>
|
||||
</TopNavigation>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnboardingNavigation;
|
||||
|
||||
const OnboardingNavigationActions = ( {
|
||||
isFirst,
|
||||
showNext,
|
||||
isDisabled,
|
||||
onExit,
|
||||
onNext,
|
||||
} ) => {
|
||||
// On first page we don't have any actions.
|
||||
if ( isFirst ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="link" onClick={ onExit }>
|
||||
{ __( 'Save and exit', 'woocommerce-paypal-payments' ) }
|
||||
</Button>
|
||||
|
||||
{ showNext && (
|
||||
<Button
|
||||
variant="primary"
|
||||
|
@ -33,8 +61,6 @@ const OnboardingNavigation = ( { stepDetails, onNext, onPrev } ) => {
|
|||
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
|
||||
</Button>
|
||||
) }
|
||||
</TopNavigation>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OnboardingNavigation;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Icon } from '@wordpress/components';
|
||||
|
||||
import { logoPayPal } from '../../../ReusableComponents/Icons';
|
||||
import { LogoPayPal } from '../../../ReusableComponents/Icons';
|
||||
|
||||
const OnboardingHeader = ( props ) => {
|
||||
return (
|
||||
<section className="ppcp-r-onboarding-header">
|
||||
<div className="ppcp-r-onboarding-header__logo">
|
||||
<div className="ppcp-r-onboarding-header__logo-wrapper">
|
||||
<Icon icon={ logoPayPal } width="auto" height={ 38 } />
|
||||
<Icon icon={ LogoPayPal } width={ 110 } height={ 38 } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ppcp-r-onboarding-header__content">
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { PayLaterMessagingHooks } from '../../../data';
|
||||
|
||||
const TabPayLaterMessaging = () => {
|
||||
const config = {}; // Replace with the appropriate/saved configuration.
|
||||
const {
|
||||
config,
|
||||
setCart,
|
||||
setCheckout,
|
||||
setProduct,
|
||||
setShop,
|
||||
setHome,
|
||||
setCustom_placement,
|
||||
} = PayLaterMessagingHooks.usePayLaterMessaging();
|
||||
const PcpPayLaterConfigurator =
|
||||
window.ppcpSettings?.PcpPayLaterConfigurator;
|
||||
|
||||
|
@ -27,17 +36,16 @@ const TabPayLaterMessaging = () => {
|
|||
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.
|
||||
*/
|
||||
setCart( data.config.cart );
|
||||
setCheckout( data.config.checkout );
|
||||
setProduct( data.config.product );
|
||||
setShop( data.config.shop );
|
||||
setHome( data.config.home );
|
||||
setCustom_placement( data.config.custom_placement );
|
||||
},
|
||||
} );
|
||||
}
|
||||
}, [ PcpPayLaterConfigurator ] );
|
||||
}, [ PcpPayLaterConfigurator, config ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -3,14 +3,30 @@ import { __ } from '@wordpress/i18n';
|
|||
|
||||
import TopNavigation from '../../../ReusableComponents/TopNavigation';
|
||||
import { useSaveSettings } from '../../../../hooks/useSaveSettings';
|
||||
import TabBar from '../../../ReusableComponents/TabBar';
|
||||
|
||||
const SettingsNavigation = ( { canSave = true } ) => {
|
||||
const SettingsNavigation = ( {
|
||||
canSave = true,
|
||||
tabs,
|
||||
activePanel,
|
||||
setActivePanel,
|
||||
} ) => {
|
||||
const { persistAll } = useSaveSettings();
|
||||
|
||||
const title = __( 'PayPal Payments', 'woocommerce-paypal-payments' );
|
||||
|
||||
return (
|
||||
<TopNavigation title={ title } exitOnTitleClick={ true }>
|
||||
<TopNavigation
|
||||
title={ title }
|
||||
exitOnTitleClick={ true }
|
||||
subNavigation={
|
||||
<TabBar
|
||||
tabs={ tabs }
|
||||
activePanel={ activePanel }
|
||||
setActivePanel={ setActivePanel }
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ canSave && (
|
||||
<Button variant="primary" onClick={ persistAll }>
|
||||
{ __( 'Save', 'woocommerce-paypal-payments' ) }
|
||||
|
|
|
@ -18,11 +18,9 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-paypal-checkout-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'paypal' );
|
||||
} );
|
||||
TAB_IDS.SETTINGS,
|
||||
'ppcp--save-payment-methods'
|
||||
);
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
|
@ -68,9 +66,7 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal(
|
||||
'advanced_credit_and_debit_card_payments'
|
||||
);
|
||||
setActiveModal( 'ppcp-credit-card-gateway' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
|
@ -149,7 +145,7 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'google_pay' );
|
||||
setActiveModal( 'ppcp-googlepay' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
|
@ -196,7 +192,7 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-card-payments-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'apple_pay' );
|
||||
setActiveModal( 'ppcp-applepay' );
|
||||
} );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
|
@ -239,7 +235,6 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
|
||||
const countryData = payLaterMessaging[ storeCountry ] || {};
|
||||
|
||||
// Add "Pay Later Messaging" to the feature list, if it's available.
|
||||
if (
|
||||
!! window.ppcpSettings?.isPayLaterConfiguratorAvailable &&
|
||||
countryData
|
||||
|
@ -256,12 +251,7 @@ export const getFeatures = ( setActiveModal ) => {
|
|||
type: 'secondary',
|
||||
text: __( 'Configure', 'woocommerce-paypal-payments' ),
|
||||
onClick: () => {
|
||||
selectTab(
|
||||
TAB_IDS.PAYMENT_METHODS,
|
||||
'ppcp-paypal-checkout-card'
|
||||
).then( () => {
|
||||
setActiveModal( 'paypal' );
|
||||
} );
|
||||
selectTab( TAB_IDS.PAY_LATER_MESSAGING );
|
||||
},
|
||||
showWhen: 'enabled',
|
||||
class: 'small-button',
|
||||
|
|
|
@ -11,7 +11,7 @@ import PaymentMethodModal from '../../../../ReusableComponents/PaymentMethodModa
|
|||
import { PaymentHooks } from '../../../../../data';
|
||||
|
||||
const Modal = ( { method, setModalIsVisible, onSave } ) => {
|
||||
const { paymentMethods } = PaymentHooks.usePaymentMethods();
|
||||
const { all: paymentMethods } = PaymentHooks.usePaymentMethods();
|
||||
const {
|
||||
paypalShowLogo,
|
||||
threeDSecure,
|
||||
|
@ -64,9 +64,9 @@ const Modal = ( { method, setModalIsVisible, onSave } ) => {
|
|||
switch ( field.type ) {
|
||||
case 'text':
|
||||
return (
|
||||
<div className="ppcp-r-modal__field-row">
|
||||
<div key={ key } className="ppcp-r-modal__field-row">
|
||||
<TextControl
|
||||
__nextHasNoMarginBottom={ true }
|
||||
__nextHasNoMarginBottom
|
||||
className="ppcp-r-vertical-text-control"
|
||||
label={ field.label }
|
||||
value={ settings[ key ] }
|
||||
|
@ -82,8 +82,9 @@ const Modal = ( { method, setModalIsVisible, onSave } ) => {
|
|||
|
||||
case 'toggle':
|
||||
return (
|
||||
<div className="ppcp-r-modal__field-row">
|
||||
<div key={ key } className="ppcp-r-modal__field-row">
|
||||
<ToggleControl
|
||||
__nextHasNoMarginBottom
|
||||
label={ field.label }
|
||||
checked={ settings[ key ] }
|
||||
onChange={ ( value ) =>
|
||||
|
|
|
@ -12,10 +12,10 @@ import {
|
|||
import { Content, ContentWrapper } from '../../../ReusableComponents/Elements';
|
||||
import SettingsCard from '../../../ReusableComponents/SettingsCard';
|
||||
import { TITLE_BADGE_POSITIVE } from '../../../ReusableComponents/TitleBadge';
|
||||
import { useTodos } from '../../../../data/todos/hooks';
|
||||
import { useMerchantInfo } from '../../../../data/common/hooks';
|
||||
import { STORE_NAME } from '../../../../data/common';
|
||||
import { getFeatures } from '../Components/Overview/features-config';
|
||||
import { todosData } from '../todo-items';
|
||||
|
||||
import {
|
||||
NOTIFICATION_ERROR,
|
||||
|
@ -23,9 +23,14 @@ import {
|
|||
} from '../../../ReusableComponents/Icons';
|
||||
|
||||
const TabOverview = () => {
|
||||
const { todos, isReady: areTodosReady } = useTodos();
|
||||
|
||||
// Don't render todos section until data is ready
|
||||
const showTodos = areTodosReady && todos.length > 0;
|
||||
|
||||
return (
|
||||
<div className="ppcp-r-tab-overview">
|
||||
{ todosData.length > 0 && (
|
||||
{ showTodos && (
|
||||
<SettingsCard
|
||||
className="ppcp-r-tab-overview-todo"
|
||||
title={ __(
|
||||
|
@ -37,7 +42,7 @@ const TabOverview = () => {
|
|||
'woocommerce-paypal-payments'
|
||||
) }
|
||||
>
|
||||
<TodoSettingsBlock todosData={ todosData } />
|
||||
<TodoSettingsBlock todosData={ todos } />
|
||||
</SettingsCard>
|
||||
) }
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import Container from '../../ReusableComponents/Container';
|
||||
import TabNavigation from '../../ReusableComponents/TabNavigation';
|
||||
import { getSettingsTabs } from './Tabs';
|
||||
import SettingsNavigation from './Components/Navigation';
|
||||
import { getSettingsTabs } from './Tabs';
|
||||
|
||||
const SettingsScreen = () => {
|
||||
const SettingsScreen = ( { activePanel, setActivePanel } ) => {
|
||||
const tabs = getSettingsTabs();
|
||||
|
||||
const { Component } = tabs.find( ( tab ) => tab.name === activePanel );
|
||||
return (
|
||||
<>
|
||||
<SettingsNavigation />
|
||||
<Container page="settings">
|
||||
<TabNavigation tabs={ tabs }></TabNavigation>
|
||||
</Container>
|
||||
<SettingsNavigation
|
||||
tabs={ tabs }
|
||||
activePanel={ activePanel }
|
||||
setActivePanel={ setActivePanel }
|
||||
/>
|
||||
<Container page="settings">{ Component }</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ const useHooks = () => {
|
|||
};
|
||||
};
|
||||
|
||||
export const useState = () => {
|
||||
export const useStore = () => {
|
||||
const { persist, isReady } = useHooks();
|
||||
return { persist, isReady };
|
||||
};
|
||||
|
|
|
@ -4,16 +4,24 @@ import {
|
|||
PaymentStoreName,
|
||||
SettingsStoreName,
|
||||
StylingStoreName,
|
||||
TodosStoreName,
|
||||
} from './index';
|
||||
import { setCompleted } from './onboarding/actions';
|
||||
|
||||
export const addDebugTools = ( context, modules ) => {
|
||||
if ( ! context || ! context?.debug ) {
|
||||
if ( ! context ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO - enable this condition for version 3.0.1
|
||||
// In version 3.0.0 we want to have the debug tools available on every installation
|
||||
if ( ! context.debug ) { return }
|
||||
*/
|
||||
|
||||
const debugApi = ( window.ppcpDebugger = window.ppcpDebugger || {} );
|
||||
|
||||
// Dump the current state of all our Redux stores.
|
||||
context.dumpStore = async () => {
|
||||
debugApi.dumpStore = async () => {
|
||||
/* eslint-disable no-console */
|
||||
if ( ! console?.groupCollapsed ) {
|
||||
console.error( 'console.groupCollapsed is not supported.' );
|
||||
|
@ -41,7 +49,7 @@ export const addDebugTools = ( context, modules ) => {
|
|||
};
|
||||
|
||||
// Reset all Redux stores to their initial state.
|
||||
context.resetStore = () => {
|
||||
debugApi.resetStore = () => {
|
||||
const stores = [];
|
||||
const { isConnected } = wp.data.select( CommonStoreName ).merchant();
|
||||
|
||||
|
@ -56,6 +64,7 @@ export const addDebugTools = ( context, modules ) => {
|
|||
stores.push( PaymentStoreName );
|
||||
stores.push( SettingsStoreName );
|
||||
stores.push( StylingStoreName );
|
||||
stores.push( TodosStoreName );
|
||||
} else {
|
||||
// Only reset the common & onboarding stores to restart the onboarding wizard.
|
||||
stores.push( CommonStoreName );
|
||||
|
@ -68,13 +77,17 @@ export const addDebugTools = ( context, modules ) => {
|
|||
// eslint-disable-next-line no-console
|
||||
console.log( `Reset store: ${ storeName }...` );
|
||||
|
||||
store.reset();
|
||||
store.persist();
|
||||
try {
|
||||
store.reset();
|
||||
store.persist();
|
||||
} catch ( error ) {
|
||||
console.error( ' ... Reset failed, skipping this store' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
// Disconnect the merchant and display the onboarding wizard.
|
||||
context.disconnect = () => {
|
||||
debugApi.disconnect = () => {
|
||||
const common = wp.data.dispatch( CommonStoreName );
|
||||
|
||||
common.disconnectMerchant();
|
||||
|
@ -86,10 +99,13 @@ export const addDebugTools = ( context, modules ) => {
|
|||
};
|
||||
|
||||
// Enters or completes the onboarding wizard without changing anything else.
|
||||
context.onboardingMode = ( state ) => {
|
||||
debugApi.onboardingMode = ( state ) => {
|
||||
const onboarding = wp.data.dispatch( OnboardingStoreName );
|
||||
|
||||
onboarding.setCompleted( ! state );
|
||||
onboarding.persist();
|
||||
};
|
||||
|
||||
// Expose original debug API.
|
||||
Object.assign( context, debugApi );
|
||||
};
|
||||
|
|
|
@ -4,8 +4,18 @@ import * as Common from './common';
|
|||
import * as Payment from './payment';
|
||||
import * as Settings from './settings';
|
||||
import * as Styling from './styling';
|
||||
import * as Todos from './todos';
|
||||
import * as PayLaterMessaging from './pay-later-messaging';
|
||||
|
||||
const stores = [ Onboarding, Common, Payment, Settings, Styling ];
|
||||
const stores = [
|
||||
Onboarding,
|
||||
Common,
|
||||
Payment,
|
||||
Settings,
|
||||
Styling,
|
||||
Todos,
|
||||
PayLaterMessaging,
|
||||
];
|
||||
|
||||
stores.forEach( ( store ) => {
|
||||
try {
|
||||
|
@ -28,12 +38,16 @@ export const CommonHooks = Common.hooks;
|
|||
export const PaymentHooks = Payment.hooks;
|
||||
export const SettingsHooks = Settings.hooks;
|
||||
export const StylingHooks = Styling.hooks;
|
||||
export const TodosHooks = Todos.hooks;
|
||||
export const PayLaterMessagingHooks = PayLaterMessaging.hooks;
|
||||
|
||||
export const OnboardingStoreName = Onboarding.STORE_NAME;
|
||||
export const CommonStoreName = Common.STORE_NAME;
|
||||
export const PaymentStoreName = Payment.STORE_NAME;
|
||||
export const SettingsStoreName = Settings.STORE_NAME;
|
||||
export const StylingStoreName = Styling.STORE_NAME;
|
||||
export const TodosStoreName = Todos.STORE_NAME;
|
||||
export const PayLaterMessagingStoreName = PayLaterMessaging.STORE_NAME;
|
||||
|
||||
export * from './configuration';
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data.
|
||||
SET_TRANSIENT: 'PAY_LATER_MESSAGING:SET_TRANSIENT',
|
||||
|
||||
// Persistent data.
|
||||
SET_PERSISTENT: 'PAY_LATER_MESSAGING:SET_PERSISTENT',
|
||||
RESET: 'PAY_LATER_MESSAGING:RESET',
|
||||
HYDRATE: 'PAY_LATER_MESSAGING:HYDRATE',
|
||||
|
||||
// Controls - always start with "DO_".
|
||||
DO_PERSIST_DATA: 'PAY_LATER_MESSAGING:DO_PERSIST_DATA',
|
||||
};
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Action Creators: Define functions to create action objects.
|
||||
*
|
||||
* These functions update state or trigger side effects (e.g., async operations).
|
||||
* Actions are categorized as Transient, Persistent, or Side effect.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { select } from '@wordpress/data';
|
||||
|
||||
import ACTION_TYPES from './action-types';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
/**
|
||||
* @typedef {Object} Action An action object that is handled by a reducer or control.
|
||||
* @property {string} type - The action type.
|
||||
* @property {Object?} payload - Optional payload for the action.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special. Resets all values in the store to initial defaults.
|
||||
*
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const reset = () => ( { type: ACTION_TYPES.RESET } );
|
||||
|
||||
/**
|
||||
* Persistent. Set the full store details during app initialization.
|
||||
*
|
||||
* @param {{data: {}, flags?: {}}} payload
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const hydrate = ( payload ) => ( {
|
||||
type: ACTION_TYPES.HYDRATE,
|
||||
payload,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Generic transient-data updater.
|
||||
*
|
||||
* @param {string} prop Name of the property to update.
|
||||
* @param {any} value The new value of the property.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setTransient = ( prop, value ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { [ prop ]: value },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Generic persistent-data updater.
|
||||
*
|
||||
* @param {string} prop Name of the property to update.
|
||||
* @param {any} value The new value of the property.
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setPersistent = ( prop, value ) => ( {
|
||||
type: ACTION_TYPES.SET_PERSISTENT,
|
||||
payload: { [ prop ]: value },
|
||||
} );
|
||||
|
||||
/**
|
||||
* Transient. Marks the store as "ready", i.e., fully initialized.
|
||||
*
|
||||
* @param {boolean} isReady
|
||||
* @return {Action} The action.
|
||||
*/
|
||||
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
|
||||
|
||||
/**
|
||||
* 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/pay_later_messaging';
|
||||
|
||||
/**
|
||||
* REST path to hydrate data of this module by loading data from the WP DB.
|
||||
*
|
||||
* Used by: Resolvers
|
||||
* See: PayLaterMessagingEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/pay_later_messaging';
|
||||
|
||||
/**
|
||||
* REST path to persist data of this module to the WP DB.
|
||||
*
|
||||
* Used by: Controls
|
||||
* See: PayLaterMessagingEndpoint.php
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/pay_later_messaging';
|
23
modules/ppcp-settings/resources/js/data/pay-later-messaging/controls.js
vendored
Normal file
23
modules/ppcp-settings/resources/js/data/pay-later-messaging/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,
|
||||
} );
|
||||
},
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* 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 { useDispatch } from '@wordpress/data';
|
||||
|
||||
import { createHooksForStore } from '../utils';
|
||||
import { STORE_NAME } from './constants';
|
||||
|
||||
const useHooks = () => {
|
||||
const { useTransient, usePersistent } = createHooksForStore( STORE_NAME );
|
||||
const { persist } = useDispatch( STORE_NAME );
|
||||
|
||||
// Read-only flags and derived state.
|
||||
// Nothing here yet.
|
||||
|
||||
// Transient accessors.
|
||||
const [ isReady ] = useTransient( 'isReady' );
|
||||
|
||||
// Persistent accessors.
|
||||
const [ cart, setCart ] = usePersistent( 'cart' );
|
||||
const [ checkout, setCheckout ] = usePersistent( 'checkout' );
|
||||
const [ product, setProduct ] = usePersistent( 'product' );
|
||||
const [ shop, setShop ] = usePersistent( 'shop' );
|
||||
const [ home, setHome ] = usePersistent( 'home' );
|
||||
const [ custom_placement, setCustom_placement ] =
|
||||
usePersistent( 'custom_placement' );
|
||||
|
||||
return {
|
||||
persist,
|
||||
isReady,
|
||||
cart,
|
||||
setCart,
|
||||
checkout,
|
||||
setCheckout,
|
||||
product,
|
||||
setProduct,
|
||||
shop,
|
||||
setShop,
|
||||
home,
|
||||
setHome,
|
||||
custom_placement,
|
||||
setCustom_placement,
|
||||
};
|
||||
};
|
||||
|
||||
export const useStore = () => {
|
||||
const { persist, isReady } = useHooks();
|
||||
return { persist, isReady };
|
||||
};
|
||||
|
||||
export const usePayLaterMessaging = () => {
|
||||
const {
|
||||
cart,
|
||||
setCart,
|
||||
checkout,
|
||||
setCheckout,
|
||||
product,
|
||||
setProduct,
|
||||
shop,
|
||||
setShop,
|
||||
home,
|
||||
setHome,
|
||||
custom_placement,
|
||||
setCustom_placement,
|
||||
} = useHooks();
|
||||
|
||||
return {
|
||||
config: {
|
||||
cart,
|
||||
checkout,
|
||||
product,
|
||||
shop,
|
||||
home,
|
||||
custom_placement,
|
||||
},
|
||||
setCart,
|
||||
setCheckout,
|
||||
setProduct,
|
||||
setShop,
|
||||
setHome,
|
||||
setCustom_placement,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
import { createReduxStore, register } from '@wordpress/data';
|
||||
import { controls as wpControls } from '@wordpress/data-controls';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
* Combines custom controls with WordPress data controls.
|
||||
*
|
||||
* @return {boolean} True if initialization succeeded, false otherwise.
|
||||
*/
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
|
||||
return Boolean( wp.data.select( STORE_NAME ) );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* 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, createReducerSetters } 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( {
|
||||
cart: {},
|
||||
checkout: {},
|
||||
product: {},
|
||||
shop: {},
|
||||
home: {},
|
||||
custom_placement: [],
|
||||
} );
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ changeTransient, changePersistent ] = createReducerSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||
changeTransient( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) =>
|
||||
changePersistent( state, payload ),
|
||||
|
||||
[ ACTION_TYPES.RESET ]: ( state ) => {
|
||||
const cleanState = changeTransient(
|
||||
changePersistent( state, defaultPersistent ),
|
||||
defaultTransient
|
||||
);
|
||||
|
||||
// Keep "read-only" details and initialization flags.
|
||||
cleanState.isReady = true;
|
||||
|
||||
return cleanState;
|
||||
},
|
||||
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||
changePersistent( 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 Pay Later Messaging config 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;
|
||||
};
|
|
@ -8,6 +8,7 @@ import * as actions from './actions';
|
|||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
import { initTodoSync } from '../sync/todo-state-sync';
|
||||
|
||||
/**
|
||||
* Initializes and registers the settings store with WordPress data layer.
|
||||
|
@ -26,6 +27,9 @@ export const initStore = () => {
|
|||
|
||||
register( store );
|
||||
|
||||
// Initialize todo sync after store registration. Potentially should be moved elsewhere.
|
||||
initTodoSync();
|
||||
|
||||
return Boolean( wp.data.select( STORE_NAME ) );
|
||||
};
|
||||
|
||||
|
|
|
@ -21,25 +21,28 @@ const useHooks = () => {
|
|||
// Persistent accessors.
|
||||
const [ invoicePrefix, setInvoicePrefix ] =
|
||||
usePersistent( 'invoicePrefix' );
|
||||
const [ brandName, setBrandName ] = usePersistent( 'brandName' );
|
||||
const [ softDescriptor, setSoftDescriptor ] =
|
||||
usePersistent( 'softDescriptor' );
|
||||
|
||||
const [ subtotalAdjustment, setSubtotalAdjustment ] =
|
||||
usePersistent( 'subtotalAdjustment' );
|
||||
const [ landingPage, setLandingPage ] = usePersistent( 'landingPage' );
|
||||
const [ buttonLanguage, setButtonLanguage ] =
|
||||
usePersistent( 'buttonLanguage' );
|
||||
|
||||
const [ authorizeOnly, setAuthorizeOnly ] =
|
||||
usePersistent( 'authorizeOnly' );
|
||||
const [ captureVirtualOnlyOrders, setCaptureVirtualOnlyOrders ] =
|
||||
usePersistent( 'captureVirtualOnlyOrders' );
|
||||
usePersistent( 'captureVirtualOrders' );
|
||||
const [ savePaypalAndVenmo, setSavePaypalAndVenmo ] =
|
||||
usePersistent( 'savePaypalAndVenmo' );
|
||||
const [ saveCardDetails, setSaveCardDetails ] =
|
||||
usePersistent( 'saveCardDetails' );
|
||||
const [ payNowExperience, setPayNowExperience ] =
|
||||
usePersistent( 'payNowExperience' );
|
||||
const [ logging, setLogging ] = usePersistent( 'logging' );
|
||||
const [ subtotalAdjustment, setSubtotalAdjustment ] =
|
||||
usePersistent( 'subtotalAdjustment' );
|
||||
const [ brandName, setBrandName ] = usePersistent( 'brandName' );
|
||||
const [ softDescriptor, setSoftDescriptor ] =
|
||||
usePersistent( 'softDescriptor' );
|
||||
const [ landingPage, setLandingPage ] = usePersistent( 'landingPage' );
|
||||
const [ buttonLanguage, setButtonLanguage ] =
|
||||
usePersistent( 'buttonLanguage' );
|
||||
usePersistent( 'enablePayNow' );
|
||||
const [ logging, setLogging ] = usePersistent( 'enableLogging' );
|
||||
|
||||
const [ disabledCards, setDisabledCards ] =
|
||||
usePersistent( 'disabledCards' );
|
||||
|
||||
|
|
|
@ -25,18 +25,25 @@ const defaultTransient = Object.freeze( {
|
|||
* These represent the core PayPal payment settings configuration.
|
||||
*/
|
||||
const defaultPersistent = Object.freeze( {
|
||||
// String values.
|
||||
invoicePrefix: '', // Prefix for PayPal invoice IDs
|
||||
authorizeOnly: false, // Whether to only authorize payments initially
|
||||
captureVirtualOnlyOrders: false, // Auto-capture virtual-only orders
|
||||
savePaypalAndVenmo: false, // Enable PayPal & Venmo vaulting
|
||||
saveCardDetails: false, // Enable card vaulting
|
||||
payNowExperience: false, // Enable Pay Now experience
|
||||
logging: false, // Enable debug logging
|
||||
subtotalAdjustment: 'skip_details', // Handling for subtotal mismatches
|
||||
brandName: '', // Merchant brand name for PayPal
|
||||
softDescriptor: '', // Payment descriptor on statements
|
||||
landingPage: 'any', // PayPal checkout landing page
|
||||
|
||||
// Limited value strings.
|
||||
subtotalAdjustment: 'no_details', // [correction|no_details] Handling for subtotal mismatches
|
||||
landingPage: 'any', // [any|login|guest_checkout] PayPal checkout landing page
|
||||
buttonLanguage: '', // Language for PayPal buttons
|
||||
|
||||
// Boolean flags.
|
||||
authorizeOnly: false, // Whether to only authorize payments initially
|
||||
captureVirtualOrders: false, // Auto-capture virtual-only orders
|
||||
savePaypalAndVenmo: false, // Enable PayPal & Venmo vaulting
|
||||
saveCardDetails: false, // Enable card vaulting
|
||||
enablePayNow: false, // Enable Pay Now experience
|
||||
enableLogging: false, // Enable debug logging
|
||||
|
||||
// String arrays.
|
||||
disabledCards: [], // Disabled credit card types
|
||||
} );
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { subscribe, select, dispatch } from '@wordpress/data';
|
||||
|
||||
const TODO_TRIGGERS = {
|
||||
'ppcp-applepay': 'enable_apple_pay',
|
||||
'ppcp-googlepay': 'enable_google_pay',
|
||||
'ppcp-axo-gateway': 'enable_fastlane',
|
||||
'ppcp-card-button-gateway': 'enable_credit_debit_cards',
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize todo synchronization
|
||||
*/
|
||||
export const initTodoSync = () => {
|
||||
let previousPaymentState = null;
|
||||
let isProcessing = false;
|
||||
|
||||
subscribe( () => {
|
||||
if ( isProcessing ) {
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
try {
|
||||
const paymentState = select( 'wc/paypal/payment' ).persistentData();
|
||||
const todosState = select( 'wc/paypal/todos' ).getTodos();
|
||||
|
||||
// Skip if states haven't been initialized yet
|
||||
if ( ! paymentState || ! todosState || ! previousPaymentState ) {
|
||||
previousPaymentState = paymentState;
|
||||
return;
|
||||
}
|
||||
|
||||
Object.entries( TODO_TRIGGERS ).forEach(
|
||||
( [ paymentMethod, todoId ] ) => {
|
||||
const wasEnabled =
|
||||
previousPaymentState[ paymentMethod ]?.enabled;
|
||||
const isEnabled = paymentState[ paymentMethod ]?.enabled;
|
||||
|
||||
if ( wasEnabled !== isEnabled ) {
|
||||
const todoToUpdate = todosState.find(
|
||||
( todo ) => todo.id === todoId
|
||||
);
|
||||
|
||||
if ( todoToUpdate ) {
|
||||
const updatedTodos = todosState.map( ( todo ) =>
|
||||
todo.id === todoId
|
||||
? { ...todo, isCompleted: isEnabled }
|
||||
: todo
|
||||
);
|
||||
|
||||
dispatch( 'wc/paypal/todos' ).setTodos(
|
||||
updatedTodos
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
previousPaymentState = paymentState;
|
||||
} catch ( error ) {
|
||||
console.error( 'Error in todo sync:', error );
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
} );
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Action Types: Define unique identifiers for actions across all store modules.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Transient data
|
||||
SET_TRANSIENT: 'TODOS:SET_TRANSIENT',
|
||||
|
||||
// Persistent data
|
||||
SET_TODOS: 'TODOS:SET_TODOS',
|
||||
|
||||
// Controls
|
||||
DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS',
|
||||
};
|
24
modules/ppcp-settings/resources/js/data/todos/actions.js
Normal file
24
modules/ppcp-settings/resources/js/data/todos/actions.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 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 ACTION_TYPES from './action-types';
|
||||
|
||||
export const setIsReady = ( isReady ) => ( {
|
||||
type: ACTION_TYPES.SET_TRANSIENT,
|
||||
payload: { isReady },
|
||||
} );
|
||||
|
||||
export const setTodos = ( todos ) => ( {
|
||||
type: ACTION_TYPES.SET_TODOS,
|
||||
payload: todos,
|
||||
} );
|
||||
|
||||
export const fetchTodos = function* () {
|
||||
yield { type: ACTION_TYPES.DO_FETCH_TODOS };
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Constants: Define store configuration values.
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
export const STORE_NAME = 'wc/paypal/todos';
|
||||
export const REST_PATH = '/wc/v3/wc_paypal/todos';
|
22
modules/ppcp-settings/resources/js/data/todos/controls.js
vendored
Normal file
22
modules/ppcp-settings/resources/js/data/todos/controls.js
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 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_PATH } from './constants';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
export const controls = {
|
||||
async [ ACTION_TYPES.DO_FETCH_TODOS ]() {
|
||||
const response = await apiFetch( {
|
||||
path: REST_PATH,
|
||||
method: 'GET',
|
||||
} );
|
||||
return response?.data || [];
|
||||
},
|
||||
};
|
33
modules/ppcp-settings/resources/js/data/todos/hooks.js
Normal file
33
modules/ppcp-settings/resources/js/data/todos/hooks.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 ]
|
||||
);
|
||||
|
||||
export const useTodos = () => {
|
||||
const todos = useSelect(
|
||||
( select ) => select( STORE_NAME ).getTodos(),
|
||||
[]
|
||||
);
|
||||
const isReady = useTransient( 'isReady' );
|
||||
|
||||
const { fetchTodos } = useDispatch( STORE_NAME );
|
||||
|
||||
return {
|
||||
todos,
|
||||
isReady,
|
||||
fetchTodos,
|
||||
};
|
||||
};
|
32
modules/ppcp-settings/resources/js/data/todos/index.js
Normal file
32
modules/ppcp-settings/resources/js/data/todos/index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { createReduxStore, register } from '@wordpress/data';
|
||||
import { controls as wpControls } from '@wordpress/data-controls';
|
||||
|
||||
import { STORE_NAME } from './constants';
|
||||
import reducer from './reducer';
|
||||
import * as selectors from './selectors';
|
||||
import * as actions from './actions';
|
||||
import * as hooks from './hooks';
|
||||
import { resolvers } from './resolvers';
|
||||
import { controls } from './controls';
|
||||
|
||||
/**
|
||||
* Initializes and registers the todos store with WordPress data layer.
|
||||
* Combines custom controls with WordPress data controls.
|
||||
*
|
||||
* @return {boolean} True if initialization succeeded, false otherwise.
|
||||
*/
|
||||
export const initStore = () => {
|
||||
const store = createReduxStore( STORE_NAME, {
|
||||
reducer,
|
||||
controls: { ...wpControls, ...controls },
|
||||
actions,
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
||||
register( store );
|
||||
|
||||
return Boolean( wp.data.select( STORE_NAME ) );
|
||||
};
|
||||
|
||||
export { hooks, selectors, STORE_NAME };
|
90
modules/ppcp-settings/resources/js/data/todos/reducer.js
Normal file
90
modules/ppcp-settings/resources/js/data/todos/reducer.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Reducer: Defines store structure and state updates for todos 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, createReducerSetters } from '../utils';
|
||||
import ACTION_TYPES from './action-types';
|
||||
|
||||
// Store structure.
|
||||
|
||||
/**
|
||||
* Transient: Values that are _not_ saved to the DB (like app lifecycle-flags).
|
||||
* These reset on page reload.
|
||||
*/
|
||||
const defaultTransient = Object.freeze( {
|
||||
isReady: false,
|
||||
} );
|
||||
|
||||
/**
|
||||
* Persistent: Values that are loaded from and saved to the DB.
|
||||
* These represent the core todos configuration.
|
||||
*/
|
||||
const defaultPersistent = Object.freeze( {
|
||||
todos: [],
|
||||
} );
|
||||
|
||||
// Reducer logic.
|
||||
|
||||
const [ changeTransient, changePersistent ] = createReducerSetters(
|
||||
defaultTransient,
|
||||
defaultPersistent
|
||||
);
|
||||
|
||||
/**
|
||||
* Reducer implementation mapping actions to state updates.
|
||||
*/
|
||||
const reducer = createReducer( defaultTransient, defaultPersistent, {
|
||||
/**
|
||||
* Updates temporary state values
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) =>
|
||||
changeTransient( state, payload ),
|
||||
|
||||
/**
|
||||
* Updates todos list
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Update payload
|
||||
* @return {Object} Updated state
|
||||
*/
|
||||
[ ACTION_TYPES.SET_TODOS ]: ( state, payload ) => {
|
||||
return changePersistent( state, { todos: 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes persistent state with data from the server
|
||||
*
|
||||
* @param {Object} state Current state
|
||||
* @param {Object} payload Hydration payload containing server data
|
||||
* @param {Object} payload.data The todos data to hydrate
|
||||
* @return {Object} Hydrated state
|
||||
*/
|
||||
[ ACTION_TYPES.HYDRATE ]: ( state, payload ) =>
|
||||
changePersistent( state, payload.data ),
|
||||
} );
|
||||
|
||||
export default reducer;
|
35
modules/ppcp-settings/resources/js/data/todos/resolvers.js
Normal file
35
modules/ppcp-settings/resources/js/data/todos/resolvers.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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_PATH } from './constants';
|
||||
|
||||
export const resolvers = {
|
||||
*getTodos() {
|
||||
try {
|
||||
const response = yield apiFetch( { path: REST_PATH } );
|
||||
|
||||
// Make sure we're accessing the correct part of the response
|
||||
const todos = response?.data || [];
|
||||
|
||||
yield dispatch( STORE_NAME ).setTodos( todos );
|
||||
yield dispatch( STORE_NAME ).setIsReady( true );
|
||||
|
||||
} catch ( e ) {
|
||||
console.error( 'Resolver error:', e );
|
||||
yield dispatch( STORE_NAME ).setIsReady( false );
|
||||
yield dispatch( 'core/notices' ).createErrorNotice(
|
||||
__( 'Error retrieving todos.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
28
modules/ppcp-settings/resources/js/data/todos/selectors.js
Normal file
28
modules/ppcp-settings/resources/js/data/todos/selectors.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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 EMPTY_ARR = 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;
|
||||
};
|
||||
|
||||
export const getTodos = ( state ) => {
|
||||
// Access todos directly from state first
|
||||
const todos = state?.todos || persistentData( state ).todos || EMPTY_ARR;
|
||||
return todos;
|
||||
};
|
|
@ -2,6 +2,7 @@ import { useCallback } from '@wordpress/element';
|
|||
|
||||
import {
|
||||
CommonHooks,
|
||||
PayLaterMessagingHooks,
|
||||
PaymentHooks,
|
||||
SettingsHooks,
|
||||
StylingHooks,
|
||||
|
@ -13,8 +14,13 @@ export const useSaveSettings = () => {
|
|||
const { persist: persistPayment } = PaymentHooks.useStore();
|
||||
const { persist: persistSettings } = SettingsHooks.useStore();
|
||||
const { persist: persistStyling } = StylingHooks.useStore();
|
||||
const { persist: persistPayLaterMessaging } =
|
||||
PayLaterMessagingHooks.useStore();
|
||||
|
||||
const persistAll = useCallback( () => {
|
||||
// Executes onSave on TabPayLaterMessaging component.
|
||||
document.getElementById( 'configurator-publishButton' )?.click();
|
||||
|
||||
withActivity(
|
||||
'persist-methods',
|
||||
'Save payment methods',
|
||||
|
@ -30,7 +36,18 @@ export const useSaveSettings = () => {
|
|||
'Save styling details',
|
||||
persistStyling
|
||||
);
|
||||
}, [ persistPayment, persistSettings, persistStyling, withActivity ] );
|
||||
withActivity(
|
||||
'persist-pay-later-messaging',
|
||||
'Save pay later messaging details',
|
||||
persistPayLaterMessaging
|
||||
);
|
||||
}, [
|
||||
persistPayment,
|
||||
persistSettings,
|
||||
persistStyling,
|
||||
persistPayLaterMessaging,
|
||||
withActivity,
|
||||
] );
|
||||
|
||||
return { persistAll };
|
||||
};
|
||||
|
|
|
@ -2,139 +2,139 @@ export const countryPriceInfo = {
|
|||
US: {
|
||||
fixedFee: {
|
||||
USD: 0.49,
|
||||
GBP: 0.39,
|
||||
CAD: 0.59,
|
||||
AUD: 0.59,
|
||||
EUR: 0.39,
|
||||
GBP: 0.39,
|
||||
CAD: 0.59,
|
||||
AUD: 0.59,
|
||||
EUR: 0.39,
|
||||
},
|
||||
checkout: 3.49,
|
||||
plater: 4.99,
|
||||
ccf: {
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
plater: 4.99,
|
||||
ccf: {
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
dw: {
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
apm: {
|
||||
percentage: 2.89,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
fast: {
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
percentage: 2.89,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
fast: {
|
||||
percentage: 2.59,
|
||||
fixedFee: 0.29,
|
||||
},
|
||||
standardCardFields: 2.99,
|
||||
},
|
||||
UK: {
|
||||
GB: {
|
||||
fixedFee: {
|
||||
GPB: 0.3,
|
||||
USD: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
},
|
||||
checkout: 2.9,
|
||||
plater: 2.9,
|
||||
plater: 2.9,
|
||||
ccf: 1.2,
|
||||
dw: 1.2,
|
||||
fast: 1.2,
|
||||
fast: 1.2,
|
||||
apm: 1.2,
|
||||
standardCardFields: 1.2,
|
||||
},
|
||||
CA: {
|
||||
fixedFee: {
|
||||
CAD: 0.3,
|
||||
USD: 0.3,
|
||||
GBP: 0.2,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.2,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
},
|
||||
checkout: 2.9,
|
||||
ccf: 2.7,
|
||||
dw: 2.7,
|
||||
fast: 2.7,
|
||||
fast: 2.7,
|
||||
apm: 2.9,
|
||||
standardCardFields: 2.9,
|
||||
},
|
||||
AU: {
|
||||
fixedFee: {
|
||||
AUD: 0.3,
|
||||
USD: 0.3,
|
||||
GBP: 0.2,
|
||||
CAD: 0.3,
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.2,
|
||||
CAD: 0.3,
|
||||
EUR: 0.35,
|
||||
},
|
||||
checkout: 2.6,
|
||||
plater: 2.6,
|
||||
plater: 2.6,
|
||||
ccf: 1.75,
|
||||
dw: 1.75,
|
||||
fast: 1.75,
|
||||
fast: 1.75,
|
||||
apm: 2.6,
|
||||
standardCardFields: 2.6,
|
||||
},
|
||||
FR: {
|
||||
fixedFee: {
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
},
|
||||
checkout: 2.9,
|
||||
plater: 2.9,
|
||||
plater: 2.9,
|
||||
ccf: 1.2,
|
||||
dw: 1.2,
|
||||
fast: 1.2,
|
||||
fast: 1.2,
|
||||
apm: 1.2,
|
||||
standardCardFields: 1.2,
|
||||
},
|
||||
IT: {
|
||||
fixedFee: {
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
},
|
||||
checkout: 3.4,
|
||||
plater: 3.4,
|
||||
plater: 3.4,
|
||||
ccf: 1.2,
|
||||
dw: 1.2,
|
||||
fast: 1.2,
|
||||
fast: 1.2,
|
||||
apm: 1.2,
|
||||
standardCardFields: 1.2,
|
||||
},
|
||||
DE: {
|
||||
fixedFee: {
|
||||
EUR: 0.39,
|
||||
USD: 0.49,
|
||||
GBP: 0.29,
|
||||
CAD: 0.59,
|
||||
AUD: 0.59,
|
||||
USD: 0.49,
|
||||
GBP: 0.29,
|
||||
CAD: 0.59,
|
||||
AUD: 0.59,
|
||||
},
|
||||
checkout: 2.99,
|
||||
plater: 2.99,
|
||||
plater: 2.99,
|
||||
ccf: 2.99,
|
||||
dw: 2.99,
|
||||
fast: 2.99,
|
||||
fast: 2.99,
|
||||
apm: 2.99,
|
||||
standardCardFields: 2.99,
|
||||
},
|
||||
ES: {
|
||||
fixedFee: {
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
EUR: 0.35,
|
||||
USD: 0.3,
|
||||
GBP: 0.3,
|
||||
CAD: 0.3,
|
||||
AUD: 0.3,
|
||||
},
|
||||
checkout: 2.9,
|
||||
plater: 2.9,
|
||||
plater: 2.9,
|
||||
ccf: 1.2,
|
||||
dw: 1.2,
|
||||
fast: 1.2,
|
||||
fast: 1.2,
|
||||
apm: 1.2,
|
||||
standardCardFields: 1.2,
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue