Settings UI: Move Things To Do config data to a separate definition file. Add settings highlighting. Add todos dismissing.

This commit is contained in:
Daniel Dudzic 2025-01-30 12:54:05 +01:00
parent 51b2582589
commit 0daf56b2af
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
20 changed files with 612 additions and 300 deletions

View file

@ -308,5 +308,4 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
);

View file

@ -15,12 +15,44 @@
}
// Todo List and Feature Items
.ppcp-r-todo-items {
display: flex;
flex-direction: column;
}
.ppcp-r-todo-item {
position: relative;
display: flex;
align-items: center;
gap: 18px;
width: 100%;
border-bottom: 1px solid $color-gray-300;
padding-bottom: 16px;
padding-top: 16px;
opacity: 1;
transform: translateY(0);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:first-child, &.is-dismissing:first-child + .ppcp-r-todo-item {
padding-top: 0;
}
&:last-child,
&:not(.is-dismissing):has(+ .is-dismissing:last-child) {
border-bottom: none;
padding-bottom: 0;
}
&.is-dismissing {
opacity: 0;
transform: translateY(-4px);
height: 0;
margin: 0;
padding: 0;
border: 0;
.ppcp-r-todo-item__inner {
height: 0;
padding: 0;
margin: 0;
}
}
&:hover {
cursor: pointer;
@ -32,15 +64,6 @@
}
}
&:not(:last-child) {
border-bottom: 1px solid $color-gray-400;
padding-bottom: 16px;
}
&:not(:first-child) {
padding-top: 16px;
}
&.is-completed {
.ppcp-r-todo-item__icon {
border-style: solid;
@ -68,17 +91,46 @@
&__inner {
position: relative;
height: auto;
overflow: hidden;
display: flex;
align-items: center;
gap: 18px;
width: 100%;
padding-right: 36px;
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
&__close {
margin-left: auto;
&__dismiss {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
background-color: transparent;
border: none;
padding: 4px;
cursor: pointer;
color: $color-gray-400;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
&:hover {
cursor: pointer;
color: $color-blueberry;
background-color: $color-gray-100;
color: $color-gray-700;
}
.dashicons {
font-size: 14px;
width: 14px;
height: 14px;
display: flex;
align-items: center;
justify-content: center;
}
}
@ -104,6 +156,7 @@
border-radius: 50%;
width: 24px;
height: 24px;
flex-shrink: 0;
}
}
@ -180,3 +233,23 @@
gap: 48px;
}
.ppcp-highlight {
animation: ppcp-highlight-fade 2s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid $color-blueberry;
border-radius: var(--container-border-radius);
position: relative;
z-index: 1;
}
@keyframes ppcp-highlight-fade {
0%, 20% {
background-color: rgba($color-blueberry, 0.08);
border-color: $color-blueberry;
border-width: 1px;
}
100% {
background-color: transparent;
border-color: $color-gray-300;
border-width: 1px;
}
}

View file

@ -2,6 +2,7 @@ import classNames from 'classnames';
import { Description, Header, Title, TitleExtra, Content } from './Elements';
const SettingsBlock = ( {
id,
className,
children,
title,
@ -15,14 +16,18 @@ const SettingsBlock = ( {
'ppcp--horizontal': horizontalLayout,
} );
const props = {
className: blockClassName,
...( id && { id } ),
};
return (
<div className={ blockClassName } id={ className }>
<div { ...props }>
<BlockTitle
blockTitle={ title }
blockSuffix={ titleSuffix }
blockDescription={ description }
/>
<Content asCard={ false }>{ children }</Content>
</div>
);

View file

@ -1,5 +1,7 @@
import { ToggleControl, Icon, Button } from '@wordpress/components';
import { cog } from '@wordpress/icons';
import { useEffect } from '@wordpress/element';
import { useActiveHighlight } from '../../../data/common/hooks';
import SettingsBlock from '../SettingsBlock';
import PaymentMethodIcon from '../PaymentMethodIcon';
@ -10,8 +12,28 @@ const PaymentMethodItemBlock = ( {
onSelect,
isSelected,
} ) => {
const { activeHighlight, setActiveHighlight } = useActiveHighlight();
const isHighlighted = activeHighlight === paymentMethod.id;
// Reset the active highlight after 2 seconds
useEffect( () => {
if ( isHighlighted ) {
const timer = setTimeout( () => {
setActiveHighlight( null );
}, 2000 );
return () => clearTimeout( timer );
}
}, [ isHighlighted, setActiveHighlight ] );
return (
<SettingsBlock className="ppcp--method-item" separatorAndGap={ false }>
<SettingsBlock
id={ paymentMethod.id }
className={ `ppcp--method-item ${
isHighlighted ? 'ppcp-highlight' : ''
}` }
separatorAndGap={ false }
>
<div className="ppcp--method-inner">
<div className="ppcp--method-title-wrapper">
{ paymentMethod?.icon && (

View file

@ -1,20 +1,42 @@
import { selectTab, TAB_IDS } from '../../../utils/tabSelector';
import { useState } from '@wordpress/element';
const TodoSettingsBlock = ( {
todosData,
className = '',
setActiveModal,
setActiveHighlight,
onDismissTodo,
} ) => {
const [ dismissingIds, setDismissingIds ] = useState( new Set() );
const TodoSettingsBlock = ( { todosData, className = '' } ) => {
if ( todosData.length === 0 ) {
return null;
}
const handleDismiss = ( todoId, e ) => {
e.preventDefault();
e.stopPropagation();
setDismissingIds( ( prev ) => new Set( [ ...prev, todoId ] ) );
setTimeout( () => {
onDismissTodo( todoId );
}, 300 );
};
return (
<div
className={ `ppcp-r-settings-block__todo ppcp-r-todo-items ${ className }` }
>
{ todosData.slice( 0, 5 ).map( ( todo ) => (
{ todosData.map( ( todo ) => (
<TodoItem
key={ todo.id }
id={ todo.id }
title={ todo.title }
description={ todo.description }
isCompleted={ todo.isCompleted }
isDismissing={ dismissingIds.has( todo.id ) }
onDismiss={ ( e ) => handleDismiss( todo.id, e ) }
onClick={ async () => {
if ( todo.action.type === 'tab' ) {
const tabId =
@ -23,6 +45,13 @@ const TodoSettingsBlock = ( { todosData, className = '' } ) => {
} else if ( todo.action.type === 'external' ) {
window.open( todo.action.url, '_blank' );
}
if ( todo.action.modal ) {
setActiveModal( todo.action.modal );
}
if ( todo.action.highlight ) {
setActiveHighlight( todo.action.highlight );
}
} }
/>
) ) }
@ -30,12 +59,19 @@ const TodoSettingsBlock = ( { todosData, className = '' } ) => {
);
};
const TodoItem = ( { title, description, isCompleted, onClick } ) => {
const TodoItem = ( {
title,
description,
isCompleted,
isDismissing,
onClick,
onDismiss,
} ) => {
return (
<div
className={ `ppcp-r-todo-item ${
isCompleted ? 'is-completed' : ''
}` }
} ${ isDismissing ? 'is-dismissing' : '' }` }
onClick={ onClick }
>
<div className="ppcp-r-todo-item__inner">
@ -54,6 +90,13 @@ const TodoItem = ( { title, description, isCompleted, onClick } ) => {
</div>
) }
</div>
<button
className="ppcp-r-todo-item__dismiss"
onClick={ onDismiss }
aria-label="Dismiss todo item"
>
<span className="dashicons dashicons-no-alt"></span>
</button>
</div>
</div>
);

View file

@ -23,29 +23,9 @@ 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">
{ showTodos && (
<SettingsCard
className="ppcp-r-tab-overview-todo"
title={ __(
'Things to do next',
'woocommerce-paypal-payments'
) }
description={ __(
'Complete these tasks to keep your store updated with the latest products and services.',
'woocommerce-paypal-payments'
) }
>
<TodoSettingsBlock todosData={ todos } />
</SettingsCard>
) }
<OverviewTodos />
<OverviewFeatures />
<OverviewHelp />
</div>
@ -54,6 +34,37 @@ const TabOverview = () => {
export default TabOverview;
const OverviewTodos = () => {
const { todos, isReady: areTodosReady, dismissTodo } = useTodos();
const { setActiveModal } = useDispatch( STORE_NAME );
const { setActiveHighlight } = useDispatch( STORE_NAME );
// Don't render todos section until data is ready
const showTodos = areTodosReady && todos.length > 0;
if ( ! showTodos ) {
return null;
}
return (
<SettingsCard
className="ppcp-r-tab-overview-todo"
title={ __( 'Things to do next', 'woocommerce-paypal-payments' ) }
description={ __(
'Complete these tasks to keep your store updated with the latest products and services.',
'woocommerce-paypal-payments'
) }
>
<TodoSettingsBlock
todosData={ todos }
setActiveModal={ setActiveModal }
setActiveHighlight={ setActiveHighlight }
onDismissTodo={ dismissTodo }
/>
</SettingsCard>
);
};
const OverviewFeatures = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false );
const { merchant, features: merchantFeatures } = useMerchantInfo();

View file

@ -1,160 +0,0 @@
import { __ } from '@wordpress/i18n';
import { selectTab, TAB_IDS } from '../../../utils/tabSelector';
export const todosData = [
{
id: 'enable_fastlane',
title: __( 'Enable Fastlane', 'woocommerce-paypal-payments' ),
description: __(
'Accelerate your guest checkout with Fastlane by PayPal.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () =>
selectTab( TAB_IDS.PAYMENT_METHODS, 'ppcp-card-payments-card' ),
},
{
id: 'enable_credit_debit_cards',
title: __(
'Enable Credit and Debit Cards on your checkout',
'woocommerce-paypal-payments'
),
description: __(
'Credit and Debit Cards is now available for Blocks checkout pages.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () =>
selectTab( TAB_IDS.PAYMENT_METHODS, 'ppcp-card-payments-card' ),
},
{
id: 'enable_pay_later_messaging',
title: __(
'Enable Pay Later messaging',
'woocommerce-paypal-payments'
),
description: __(
'Show Pay Later messaging to boost conversion rate and increase cart size.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => selectTab( TAB_IDS.OVERVIEW, 'pay_later_messaging' ),
},
{
id: 'configure_paypal_subscription',
title: __(
'Configure a PayPal Subscription',
'woocommerce-paypal-payments'
),
description: __(
'Connect a subscriptions-type product from WooCommerce with PayPal.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => {
console.log(
'Take merchant to product list, filtered with subscription-type products'
);
},
},
{
id: 'register_domain_apple_pay',
title: __(
'Register Domain for Apple Pay',
'woocommerce-paypal-payments'
),
description: __(
'To enable Apple Pay, you must register your domain with PayPal.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => selectTab( TAB_IDS.OVERVIEW, 'apple_pay' ),
},
{
id: 'add_digital_wallets_to_account',
title: __(
'Add digital wallets to your account',
'woocommerce-paypal-payments'
),
description: __(
'Add the ability to accept Apple Pay & Google Pay to your PayPal account.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => {
console.log(
'Take merchant to PayPal to enable Apple Pay & Google Pay'
);
},
},
{
id: 'add_apple_pay_to_account',
title: __(
'Add Apple Pay to your account',
'woocommerce-paypal-payments'
),
description: __(
'Add the ability to accept Apple Pay to your PayPal account.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => {
console.log( 'Take merchant to PayPal to enable Apple Pay' );
},
},
{
id: 'add_google_pay_to_account',
title: __(
'Add Google Pay to your account',
'woocommerce-paypal-payments'
),
description: __(
'Add the ability to accept Google Pay to your PayPal account.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => {
console.log( 'Take merchant to PayPal to enable Google Pay' );
},
},
{
id: 'enable_apple_pay',
title: __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ),
description: __(
'Allow your buyers to check out via Apple Pay.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => selectTab( TAB_IDS.OVERVIEW, 'apple_pay' ),
},
{
id: 'enable_google_pay',
title: __( 'Enable Google Pay', 'woocommerce-paypal-payments' ),
description: __(
'Allow your buyers to check out via Google Pay.',
'woocommerce-paypal-payments'
),
isCompleted: () => {
return false;
},
onClick: () => selectTab( TAB_IDS.OVERVIEW, 'google_pay' ),
},
];

View file

@ -77,6 +77,15 @@ export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
export const setActiveModal = ( activeModal ) =>
setTransient( 'activeModal', activeModal );
/**
* Transient. Sets the active settings highlight.
*
* @param {string} activeHighlight
* @return {Action} The action.
*/
export const setActiveHighlight = ( activeHighlight ) =>
setTransient( 'activeHighlight', activeHighlight );
/**
* Transient (Activity): Marks the start of an async activity
* Think of it as "setIsBusy(true)"

View file

@ -28,6 +28,8 @@ const useHooks = () => {
// Transient accessors.
const [ isReady ] = useTransient( 'isReady' );
const [ activeModal, setActiveModal ] = useTransient( 'activeModal' );
const [ activeHighlight, setActiveHighlight ] =
useTransient( 'activeHighlight' );
// Persistent accessors.
const [ isSandboxMode, setSandboxMode ] = usePersistent( 'useSandbox' );
@ -62,6 +64,8 @@ const useHooks = () => {
isReady,
activeModal,
setActiveModal,
activeHighlight,
setActiveHighlight,
isSandboxMode,
setSandboxMode: ( state ) => {
return savePersistent( setSandboxMode, state );
@ -162,6 +166,11 @@ export const useActiveModal = () => {
return { activeModal, setActiveModal };
};
export const useActiveHighlight = () => {
const { activeHighlight, setActiveHighlight } = useHooks();
return { activeHighlight, setActiveHighlight };
};
// -- Not using the `useHooks()` data provider --
export const useBusyState = () => {

View file

@ -16,6 +16,7 @@ const defaultTransient = Object.freeze( {
isReady: false,
activities: new Map(),
activeModal: '',
activeHighlight: '',
// Read only values, provided by the server via hydrate.
merchant: Object.freeze( {

View file

@ -10,6 +10,7 @@ export default {
// Persistent data
SET_TODOS: 'TODOS:SET_TODOS',
SET_DISMISSED_TODOS: 'TODOS:SET_DISMISSED_TODOS',
// Controls
DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS',

View file

@ -19,6 +19,11 @@ export const setTodos = ( todos ) => ( {
payload: todos,
} );
export const setDismissedTodos = ( dismissedTodos ) => ( {
type: ACTION_TYPES.SET_DISMISSED_TODOS,
payload: dismissedTodos,
} );
export const fetchTodos = function* () {
yield { type: ACTION_TYPES.DO_FETCH_TODOS };
};

View file

@ -21,13 +21,33 @@ export const useTodos = () => {
( select ) => select( STORE_NAME ).getTodos(),
[]
);
// Convert to array if we get an object
const dismissedTodos = useSelect( ( select ) => {
const dismissed = select( STORE_NAME ).getDismissedTodos() || [];
return Array.isArray( dismissed )
? dismissed
: Object.values( dismissed );
}, [] );
const isReady = useTransient( 'isReady' );
const { fetchTodos } = useDispatch( STORE_NAME );
const { fetchTodos, setDismissedTodos } = useDispatch( STORE_NAME );
const dismissTodo = ( todoId ) => {
const currentDismissed = dismissedTodos || [];
if ( ! currentDismissed.includes( todoId ) ) {
const newDismissedTodos = [ ...currentDismissed, todoId ];
setDismissedTodos( newDismissedTodos );
}
};
const filteredTodos = todos.filter(
( todo ) => ! dismissedTodos.includes( todo.id )
);
return {
todos,
todos: filteredTodos,
isReady,
fetchTodos,
dismissTodo,
};
};

View file

@ -26,6 +26,7 @@ const defaultTransient = Object.freeze( {
*/
const defaultPersistent = Object.freeze( {
todos: [],
dismissedTodos: [],
} );
// Reducer logic.
@ -60,6 +61,19 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
return changePersistent( state, { todos: payload } );
},
/**
* Updates dismissed todos list while preserving existing entries
*
* @param {Object} state Current state
* @param {Array} payload Array of todo IDs to mark as dismissed
* @return {Object} Updated state
*/
[ ACTION_TYPES.SET_DISMISSED_TODOS ]: ( state, payload ) => {
return changePersistent( state, {
dismissedTodos: Array.isArray( payload ) ? payload : [],
} );
},
/**
* Resets state to defaults while maintaining initialization status
*

View file

@ -26,3 +26,12 @@ export const getTodos = ( state ) => {
const todos = state?.todos || persistentData( state ).todos || EMPTY_ARR;
return todos;
};
export const getDismissedTodos = ( state ) => {
// Access dismissed todos directly from state first
const dismissedTodos =
state?.dismissedTodos ||
persistentData( state ).dismissedTodos ||
EMPTY_ARR;
return dismissedTodos;
};

View file

@ -50,10 +50,18 @@ export const selectTab = ( tabId, scrollToId ) => {
// Resolve after scroll animation
setTimeout( resolve, 300 );
} else {
console.error(
`Failed to scroll: Element with ID "${
scrollToId || 'ppcp-settings-container'
}" not found`
);
resolve();
}
}, 100 );
} else {
console.error(
`Failed to select tab: Tab with ID "${ tabId }" not found`
);
resolve();
}
} );

View file

@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
use WooCommerce\PayPalCommerce\Settings\Data\Definition\TodosDefinition;
use WooCommerce\PayPalCommerce\Settings\Endpoint\AuthenticationRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
@ -31,6 +32,7 @@ use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
use WooCommerce\PayPalCommerce\Settings\Service\AuthenticationManager;
use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Settings\Service\DataSanitizer;
@ -237,9 +239,29 @@ return array(
'settings.rest.todos' => static function ( ContainerInterface $container ) : TodosRestEndpoint {
return new TodosRestEndpoint(
$container->get( 'settings.data.todos' ),
$container->get( 'settings.data.definition.todos' )
);
},
'settings.data.todos' => static function ( ContainerInterface $container ) : TodosModel {
return new TodosModel();
},
'settings.data.definition.todos' => static function ( ContainerInterface $container ) : TodosDefinition {
return new TodosDefinition(
$container->get( 'settings.service.todos_eligibilities' )
);
},
'settings.service.todos_eligibilities' => static function( ContainerInterface $container ): TodosEligibilityService {
return new TodosEligibilityService(
$container->has( 'axo.eligible' ) && $container->get( 'axo.eligible' ),
$container->has( 'card-fields.eligible' ) && $container->get( 'card-fields.eligible' ),
$container->has( 'paylater-configurator.is-available' ) && $container->get( 'paylater-configurator.is-available' ),
$container->has( 'wc-subscriptions.helper' ) && $container->get( 'wc-subscriptions.helper' )->plugin_is_active(),
$container->has( 'apple-pay-domain.eligible' ) && $container->get( 'apple-pay-domain.eligible' ),
true,
$container->has( 'applepay.eligible' ) && $container->get( 'applepay.eligible' ),
$container->has( 'googlepay.eligible' ) && $container->get( 'googlepay.eligible' ),
$container->has( 'paylater-messaging.available' ) && $container->get( 'paylater-messaging.available' ),
$container->has( 'button.basic-checkout.enabled' ) && ! $container->get( 'button.basic-checkout.enabled' )
);
},
);

View file

@ -0,0 +1,149 @@
<?php
/**
* PayPal Commerce Todos Definitions
*
* @package WooCommerce\PayPalCommerce\Settings\Data\Definition
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings\Data\Definition;
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
/**
* Class TodosDefinition
*
* Provides the definitions for all available todos in the system.
* Each todo has a title, description, eligibility condition, and associated action.
*/
class TodosDefinition {
/**
* The todos eligibility service.
*
* @var TodosEligibilityService
*/
protected TodosEligibilityService $eligibilities;
/**
* Constructor.
*
* @param TodosEligibilityService $eligibilities The todos eligibility service.
*/
public function __construct( TodosEligibilityService $eligibilities ) {
$this->eligibilities = $eligibilities;
}
/**
* Returns the full list of todo definitions with their eligibility conditions.
*
* @return array The array of todo definitions.
*/
public function get(): array {
$eligibility_checks = $this->eligibilities->get_eligibility_checks();
return array(
'enable_fastlane' => array(
'title' => __( 'Enable Fastlane', 'woocommerce-paypal-payments' ),
'description' => __( 'Accelerate your guest checkout with Fastlane by PayPal', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_fastlane'],
'action' => array(
'type' => 'tab',
'tab' => 'payment_methods',
'section' => 'ppcp-fastlane',
'modal' => 'ppcp-fastlane-gateway',
),
),
'enable_credit_debit_cards' => array(
'title' => __( 'Enable Credit and Debit Cards on your checkout', 'woocommerce-paypal-payments' ),
'description' => __( 'Credit and Debit Cards is now available for Blocks checkout pages', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_credit_debit_cards'],
'action' => array(
'type' => 'tab',
'tab' => 'payment_methods',
'section' => 'ppcp-card-button-gateway',
'highlight' => 'ppcp-card-button-gateway',
),
),
'enable_pay_later_messaging' => array(
'title' => __( 'Enable Pay Later messaging', 'woocommerce-paypal-payments' ),
'description' => __( 'Show Pay Later messaging to boost conversion rate and increase cart size.', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_pay_later_messaging'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'pay_later_messaging',
),
),
'add_pay_later_messaging' => array(
'title' => __( 'Add Pay Later messaging to site', 'woocommerce-paypal-payments' ),
'description' => __( 'Present Pay Later messaging on your site to boost conversion rate and increase cart size.', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['add_pay_later_messaging'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'pay_later_messaging',
),
),
'configure_paypal_subscription' => array(
'title' => __( 'Configure a PayPal Subscription', 'woocommerce-paypal-payments' ),
'description' => __( 'Connect a subscriptions-type product from WooCommerce with PayPal', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['configure_paypal_subscription'],
'action' => array(
'type' => 'external',
'url' => admin_url( 'edit.php?post_type=product&product_type=subscription' ),
),
),
'register_domain_apple_pay' => array(
'title' => __( 'Register Domain for Apple Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'To enable Apple Pay, you must register your domain with PayPal', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['register_domain_apple_pay'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'apple_pay',
),
),
'add_digital_wallets_to_account' => array(
'title' => __( 'Add digital wallets to your account', 'woocommerce-paypal-payments' ),
'description' => __( 'Add the ability to accept Apple Pay & Google Pay to your PayPal account', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['add_digital_wallets_to_account'],
'action' => array(
'type' => 'external',
'url' => 'https://www.paypal.com/businessmanage/account/settings',
),
),
'enable_apple_pay' => array(
'title' => __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow your buyers to check out via Apple Pay', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_apple_pay'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'apple_pay',
),
),
'enable_google_pay' => array(
'title' => __( 'Enable Google Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow your buyers to check out via Google Pay', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_google_pay'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'google_pay',
),
),
'add_paypal_buttons' => array(
'title' => __( 'Add PayPal buttons to site', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow customers to check out quickly and securely. Customers save time and get through checkout in fewer clicks.', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['add_paypal_buttons'],
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'styling',
),
),
);
}
}

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WP_REST_Response;
use WP_REST_Server;
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
use WooCommerce\PayPalCommerce\Settings\Data\Definition\TodosDefinition;
/**
* REST controller for the "Things To Do" items in the Overview tab.
@ -35,13 +36,25 @@ class TodosRestEndpoint extends RestEndpoint {
*/
protected TodosModel $todos;
/**
* The todos definition instance.
*
* @var TodosDefinition
*/
protected TodosDefinition $todos_definition;
/**
* Constructor.
*
* @param TodosModel $todos The todos model instance.
* @param TodosModel $todos The todos model instance.
* @param TodosDefinition $todos_definition The todos definition instance.
*/
public function __construct( TodosModel $todos ) {
$this->todos = $todos;
public function __construct(
TodosModel $todos,
TodosDefinition $todos_definition
) {
$this->todos = $todos;
$this->todos_definition = $todos_definition;
}
/**
@ -59,94 +72,6 @@ class TodosRestEndpoint extends RestEndpoint {
);
}
/**
* Returns the full list of todo definitions with their eligibility conditions.
*
* @return array The array of todo definitions.
*/
protected function get_todo_definitions(): array {
return array(
'enable_fastlane' => array(
'title' => __( 'Enable Fastlane', 'woocommerce-paypal-payments' ),
'description' => __( 'Accelerate your guest checkout with Fastlane by PayPal.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'payment_methods',
'section' => 'ppcp-card-payments-card',
),
),
'enable_credit_debit_cards' => array(
'title' => __( 'Enable Credit and Debit Cards on your checkout', 'woocommerce-paypal-payments' ),
'description' => __( 'Credit and Debit Cards is now available for Blocks checkout pages.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'payment_methods',
'section' => 'ppcp-card-payments-card',
),
),
'enable_pay_later_messaging' => array(
'title' => __( 'Enable Pay Later messaging', 'woocommerce-paypal-payments' ),
'description' => __( 'Show Pay Later messaging to boost conversion rate and increase cart size.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'pay_later_messaging',
),
),
'configure_paypal_subscription' => array(
'title' => __( 'Configure a PayPal Subscription', 'woocommerce-paypal-payments' ),
'description' => __( 'Connect a subscriptions-type product from WooCommerce with PayPal.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'external',
'url' => admin_url( 'edit.php?post_type=product&product_type=subscription' ),
),
),
'register_domain_apple_pay' => array(
'title' => __( 'Register Domain for Apple Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'To enable Apple Pay, you must register your domain with PayPal.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'apple_pay',
),
),
'add_digital_wallets_to_account' => array(
'title' => __( 'Add digital wallets to your account', 'woocommerce-paypal-payments' ),
'description' => __( 'Add the ability to accept Apple Pay & Google Pay to your PayPal account.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'external',
'url' => 'https://www.paypal.com/businessmanage/account/settings',
),
),
'enable_apple_pay' => array(
'title' => __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow your buyers to check out via Apple Pay.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'apple_pay',
),
),
'enable_google_pay' => array(
'title' => __( 'Enable Google Pay', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow your buyers to check out via Google Pay.', 'woocommerce-paypal-payments' ),
'isEligible' => fn() => true,
'action' => array(
'type' => 'tab',
'tab' => 'overview',
'section' => 'google_pay',
),
),
);
}
/**
* Returns all eligible todo items.
*
@ -156,7 +81,7 @@ class TodosRestEndpoint extends RestEndpoint {
$completion_states = $this->todos->get();
$todos = array();
foreach ( $this->get_todo_definitions() as $id => $todo ) {
foreach ( $this->todos_definition->get() as $id => $todo ) {
if ( $todo['isEligible']() ) {
$todos[] = array_merge(
array(

View file

@ -0,0 +1,147 @@
<?php
/**
* PayPal Commerce eligibility service for WooCommerce.
*
* This file contains the TodosEligibilityService class which manages eligibility checks
* for various PayPal Commerce features including Fastlane, card payments, Pay Later messaging,
* subscriptions, Apple Pay, Google Pay, and other digital wallet features.
*
* @package WooCommerce\PayPalCommerce\Settings\Service
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings\Service;
/**
* Manages eligibility checks for various PayPal Commerce features.
*/
class TodosEligibilityService {
/**
* Whether Fastlane is eligible.
*
* @var bool
*/
private bool $is_fastlane_eligible;
/**
* Whether card payments are eligible.
*
* @var bool
*/
private bool $is_card_payment_eligible;
/**
* Whether Pay Later messaging is eligible.
*
* @var bool
*/
private bool $is_pay_later_messaging_eligible;
/**
* Whether subscriptions are eligible.
*
* @var bool
*/
private bool $is_subscription_eligible;
/**
* Whether Apple Pay domain registration is eligible.
*
* @var bool
*/
private bool $is_apple_pay_domain_eligible;
/**
* Whether digital wallet features are eligible.
*
* @var bool
*/
private bool $is_digital_wallet_eligible;
/**
* Whether Apple Pay is eligible.
*
* @var bool
*/
private bool $is_apple_pay_eligible;
/**
* Whether Google Pay is eligible.
*
* @var bool
*/
private bool $is_google_pay_eligible;
/**
* Whether Pay Later messaging for UI elements is eligible.
*
* @var bool
*/
private bool $is_pay_later_messaging_ui_eligible;
/**
* Whether PayPal buttons are eligible.
*
* @var bool
*/
private bool $is_paypal_buttons_eligible;
/**
* Constructor.
*
* @param bool $is_fastlane_eligible Whether Fastlane is eligible.
* @param bool $is_card_payment_eligible Whether card payments are eligible.
* @param bool $is_pay_later_messaging_eligible Whether Pay Later messaging is eligible.
* @param bool $is_subscription_eligible Whether subscriptions are eligible.
* @param bool $is_apple_pay_domain_eligible Whether Apple Pay domain registration is eligible.
* @param bool $is_digital_wallet_eligible Whether digital wallet features are eligible.
* @param bool $is_apple_pay_eligible Whether Apple Pay is eligible.
* @param bool $is_google_pay_eligible Whether Google Pay is eligible.
* @param bool $is_pay_later_messaging_ui_eligible Whether Pay Later messaging for UI is eligible.
* @param bool $is_paypal_buttons_eligible Whether PayPal buttons are eligible.
*/
public function __construct(
bool $is_fastlane_eligible,
bool $is_card_payment_eligible,
bool $is_pay_later_messaging_eligible,
bool $is_subscription_eligible,
bool $is_apple_pay_domain_eligible,
bool $is_digital_wallet_eligible,
bool $is_apple_pay_eligible,
bool $is_google_pay_eligible,
bool $is_pay_later_messaging_ui_eligible = false,
bool $is_paypal_buttons_eligible = false
) {
$this->is_fastlane_eligible = $is_fastlane_eligible;
$this->is_card_payment_eligible = $is_card_payment_eligible;
$this->is_pay_later_messaging_eligible = $is_pay_later_messaging_eligible;
$this->is_subscription_eligible = $is_subscription_eligible;
$this->is_apple_pay_domain_eligible = $is_apple_pay_domain_eligible;
$this->is_digital_wallet_eligible = $is_digital_wallet_eligible;
$this->is_apple_pay_eligible = $is_apple_pay_eligible;
$this->is_google_pay_eligible = $is_google_pay_eligible;
$this->is_pay_later_messaging_ui_eligible = $is_pay_later_messaging_ui_eligible;
$this->is_paypal_buttons_eligible = $is_paypal_buttons_eligible;
}
/**
* Returns all eligibility checks as callables.
*
* @return array<string, callable>
*/
public function get_eligibility_checks(): array {
return array(
'enable_fastlane' => fn() => ! $this->is_fastlane_eligible,
'enable_credit_debit_cards' => fn() => $this->is_card_payment_eligible,
'enable_pay_later_messaging' => fn() => ! $this->is_pay_later_messaging_eligible,
'add_pay_later_messaging' => fn() => $this->is_pay_later_messaging_eligible && ! $this->is_pay_later_messaging_ui_eligible,
'configure_paypal_subscription' => fn() => $this->is_subscription_eligible,
'register_domain_apple_pay' => fn() => $this->is_apple_pay_domain_eligible,
'add_digital_wallets_to_account' => fn() => $this->is_digital_wallet_eligible,
'enable_apple_pay' => fn() => $this->is_apple_pay_eligible && ! $this->is_apple_pay_domain_eligible,
'enable_google_pay' => fn() => $this->is_google_pay_eligible,
'add_paypal_buttons' => fn() => $this->is_paypal_buttons_eligible,
);
}
}