diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/TodoSettingsBlock.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/TodoSettingsBlock.js index 42f7ab3c8..8ede31393 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/TodoSettingsBlock.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/TodoSettingsBlock.js @@ -1,6 +1,6 @@ import { selectTab, TAB_IDS } from '../../../utils/tabSelector'; import { useEffect, useState } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { STORE_NAME as TODOS_STORE_NAME } from '../../../data/todos'; const TodoSettingsBlock = ( { @@ -21,6 +21,8 @@ const TodoSettingsBlock = ( { [] ); + const { completeOnClick } = useDispatch( TODOS_STORE_NAME ); + useEffect( () => { if ( dismissedTodos.length === 0 ) { setDismissingIds( new Set() ); @@ -41,6 +43,26 @@ const TodoSettingsBlock = ( { }, 300 ); }; + const handleClick = async ( todo ) => { + 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' ); + // If it has completeOnClick flag, trigger the action + if ( todo.action.completeOnClick === true ) { + await completeOnClick( todo.id ); + } + } + + if ( todo.action.modal ) { + setActiveModal( todo.action.modal ); + } + if ( todo.action.highlight ) { + setActiveHighlight( todo.action.highlight ); + } + }; + // Filter out dismissed todos for display const visibleTodos = todosData.filter( ( todo ) => ! dismissedTodos.includes( todo.id ) @@ -59,22 +81,7 @@ const TodoSettingsBlock = ( { isCompleted={ completedTodos.includes( todo.id ) } isDismissing={ dismissingIds.has( todo.id ) } onDismiss={ ( e ) => handleDismiss( todo.id, e ) } - 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' ); - } - - if ( todo.action.modal ) { - setActiveModal( todo.action.modal ); - } - if ( todo.action.highlight ) { - setActiveHighlight( todo.action.highlight ); - } - } } + onClick={ () => handleClick( todo ) } /> ) ) } diff --git a/modules/ppcp-settings/resources/js/data/todos/action-types.js b/modules/ppcp-settings/resources/js/data/todos/action-types.js index 7d928fabc..73b9ff48a 100644 --- a/modules/ppcp-settings/resources/js/data/todos/action-types.js +++ b/modules/ppcp-settings/resources/js/data/todos/action-types.js @@ -17,4 +17,5 @@ export default { DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS', DO_PERSIST_DATA: 'TODOS:DO_PERSIST_DATA', DO_RESET_DISMISSED_TODOS: 'TODOS:DO_RESET_DISMISSED_TODOS', + DO_COMPLETE_ONCLICK: 'TODOS:DO_COMPLETE_ONCLICK', }; diff --git a/modules/ppcp-settings/resources/js/data/todos/actions.js b/modules/ppcp-settings/resources/js/data/todos/actions.js index 1c21181b4..86864590c 100644 --- a/modules/ppcp-settings/resources/js/data/todos/actions.js +++ b/modules/ppcp-settings/resources/js/data/todos/actions.js @@ -39,8 +39,7 @@ export const resetDismissedTodos = function* () { const result = yield { type: ACTION_TYPES.DO_RESET_DISMISSED_TODOS }; if ( result && result.success ) { - // After successful reset, fetch fresh todos - yield fetchTodos(); + yield setDismissedTodos( [] ); } return result; @@ -50,3 +49,19 @@ export const setCompletedTodos = ( completedTodos ) => ( { type: ACTION_TYPES.SET_COMPLETED_TODOS, payload: completedTodos, } ); + +export const completeOnClick = function* ( todoId ) { + const result = yield { + type: ACTION_TYPES.DO_COMPLETE_ONCLICK, + todoId, + }; + + if ( result && result.success ) { + // Set transient completed state for visual feedback + const currentTransientCompleted = + yield select( STORE_NAME ).getCompletedTodos(); + yield setCompletedTodos( [ ...currentTransientCompleted, todoId ] ); + } + + return result; +}; diff --git a/modules/ppcp-settings/resources/js/data/todos/constants.js b/modules/ppcp-settings/resources/js/data/todos/constants.js index 80ed826a2..c84ea37a8 100644 --- a/modules/ppcp-settings/resources/js/data/todos/constants.js +++ b/modules/ppcp-settings/resources/js/data/todos/constants.js @@ -9,3 +9,4 @@ export const REST_PATH = '/wc/v3/wc_paypal/todos'; export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/todos'; export const REST_RESET_DISMISSED_TODOS_PATH = '/wc/v3/wc_paypal/reset-dismissed-todos'; +export const REST_COMPLETE_ONCLICK_PATH = '/wc/v3/wc_paypal/complete-onclick'; diff --git a/modules/ppcp-settings/resources/js/data/todos/controls.js b/modules/ppcp-settings/resources/js/data/todos/controls.js index 1a6ef6a31..6a6579a4b 100644 --- a/modules/ppcp-settings/resources/js/data/todos/controls.js +++ b/modules/ppcp-settings/resources/js/data/todos/controls.js @@ -12,6 +12,7 @@ import { REST_PATH, REST_PERSIST_PATH, REST_RESET_DISMISSED_TODOS_PATH, + REST_COMPLETE_ONCLICK_PATH, } from './constants'; import ACTION_TYPES from './action-types'; @@ -44,4 +45,21 @@ export const controls = { }; } }, + async [ ACTION_TYPES.DO_COMPLETE_ONCLICK ]( { todoId } ) { + try { + const response = await apiFetch( { + path: REST_COMPLETE_ONCLICK_PATH, + method: 'POST', + data: { todoId }, + } ); + + return response; + } catch ( e ) { + return { + success: false, + error: e, + message: e.message, + }; + } + }, }; diff --git a/modules/ppcp-settings/resources/js/data/todos/reducer.js b/modules/ppcp-settings/resources/js/data/todos/reducer.js index 9509bba82..c037b8162 100644 --- a/modules/ppcp-settings/resources/js/data/todos/reducer.js +++ b/modules/ppcp-settings/resources/js/data/todos/reducer.js @@ -28,6 +28,7 @@ const defaultTransient = Object.freeze( { const defaultPersistent = Object.freeze( { todos: [], dismissedTodos: [], + completedOnClickTodos: [], } ); // Reducer logic. diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index b5c4153e7..a72e5fafb 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\Settings\Data\Definition\TodosDefinition; use WooCommerce\PayPalCommerce\Settings\Endpoint\AuthenticationRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint; +use WooCommerce\PayPalCommerce\Settings\Endpoint\CompleteOnClickEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\PayLaterMessagingEndpoint; @@ -265,7 +266,8 @@ }, 'settings.data.definition.todos' => static function ( ContainerInterface $container ) : TodosDefinition { return new TodosDefinition( - $container->get( 'settings.service.todos_eligibilities' ) + $container->get( 'settings.service.todos_eligibilities' ), + $container->get( 'settings.data.general' ) ); }, 'settings.service.todos_eligibilities' => static function( ContainerInterface $container ): TodosEligibilityService { @@ -314,4 +316,7 @@ 'settings.rest.reset_dismissed_todos' => static function( ContainerInterface $container ): ResetDismissedTodosEndpoint { return new ResetDismissedTodosEndpoint(); }, + 'settings.rest.complete_onclick' => static function( ContainerInterface $container ): CompleteOnClickEndpoint { + return new CompleteOnClickEndpoint(); + }, ); diff --git a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php index 40748b5f9..ef65aca7b 100644 --- a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php @@ -10,6 +10,7 @@ namespace WooCommerce\PayPalCommerce\Settings\Data\Definition; use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService; +use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings; /** * Class TodosDefinition @@ -26,13 +27,25 @@ class TodosDefinition { */ protected TodosEligibilityService $eligibilities; + /** + * The general settings service. + * + * @var GeneralSettings + */ + protected GeneralSettings $settings; + /** * Constructor. * * @param TodosEligibilityService $eligibilities The todos eligibility service. + * @param GeneralSettings $settings The general settings service. */ - public function __construct( TodosEligibilityService $eligibilities ) { + public function __construct( + TodosEligibilityService $eligibilities, + GeneralSettings $settings + ) { $this->eligibilities = $eligibilities; + $this->settings = $settings; } /** @@ -110,9 +123,11 @@ public function get(): array { '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', + 'type' => 'external', + 'url' => $this->settings->is_sandbox_merchant() + ? 'https://www.sandbox.paypal.com/uccservicing/apm/applepay' + : 'https://www.paypal.com/uccservicing/apm/applepay', + 'completeOnClick' => true, ), ), 'add_digital_wallets' => array( diff --git a/modules/ppcp-settings/src/Endpoint/CompleteOnClickEndpoint.php b/modules/ppcp-settings/src/Endpoint/CompleteOnClickEndpoint.php new file mode 100644 index 000000000..063fe0d1f --- /dev/null +++ b/modules/ppcp-settings/src/Endpoint/CompleteOnClickEndpoint.php @@ -0,0 +1,54 @@ +namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'complete_onclick' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + } + + public function complete_onclick( WP_REST_Request $request ): WP_REST_Response { + $todo_id = $request->get_param( 'todoId' ); + + if ( ! $todo_id ) { + return $this->return_error( __( 'Todo ID is required.', 'woocommerce-paypal-payments' ) ); + } + + $settings = get_option( 'ppcp-settings', array() ); + + if ( ! isset( $settings['completedOnClickTodos'] ) ) { + $settings['completedOnClickTodos'] = array(); + } + + if ( ! in_array( $todo_id, $settings['completedOnClickTodos'] ) ) { + $settings['completedOnClickTodos'][] = $todo_id; + $update_result = update_option( 'ppcp-settings', $settings ); + + if ( ! $update_result ) { + return $this->return_error( __( 'Failed to mark todo as completed on click.', 'woocommerce-paypal-payments' ) ); + } + } + + return $this->return_success( + array( + 'message' => __( 'Todo marked as completed on click successfully.', 'woocommerce-paypal-payments' ), + 'todoId' => $todo_id, + ) + ); + } +} diff --git a/modules/ppcp-settings/src/Endpoint/ResetDismissedTodosEndpoint.php b/modules/ppcp-settings/src/Endpoint/ResetDismissedTodosEndpoint.php index 39edc2355..faeae9e10 100644 --- a/modules/ppcp-settings/src/Endpoint/ResetDismissedTodosEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/ResetDismissedTodosEndpoint.php @@ -57,7 +57,11 @@ public function reset_dismissed_todos( WP_REST_Request $request ): WP_REST_Respo $settings = get_option( 'ppcp-settings', array() ); $settings['dismissedTodos'] = array(); - $update_result = update_option( 'ppcp-settings', $settings ); + + // Clear the completedOnClickTodos for testing purposes. + // $settings['completedOnClickTodos'] = array(); + + $update_result = update_option( 'ppcp-settings', $settings ); if ( ! $update_result ) { return $this->return_error( __( 'Failed to reset dismissed todos.', 'woocommerce-paypal-payments' ) ); diff --git a/modules/ppcp-settings/src/Endpoint/TodosRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/TodosRestEndpoint.php index 09018b591..926d93680 100644 --- a/modules/ppcp-settings/src/Endpoint/TodosRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/TodosRestEndpoint.php @@ -103,11 +103,22 @@ public function register_routes(): void { * @return WP_REST_Response The response containing todos data. */ public function get_todos(): WP_REST_Response { - $settings = get_option( 'ppcp-settings', array() ); - $dismissed_ids = $settings['dismissedTodos'] ?? array(); + $settings = get_option( 'ppcp-settings', array() ); + $dismissed_ids = $settings['dismissedTodos'] ?? array(); + $completed_onclick_ids = $settings['completedOnClickTodos'] ?? array(); $todos = array(); foreach ( $this->todos_definition->get() as $id => $todo ) { + // Skip if todo has completeOnClick flag and is in completed list + if ( + in_array( $id, $completed_onclick_ids, true ) && + isset( $todo['action']['completeOnClick'] ) && + $todo['action']['completeOnClick'] === true + ) { + continue; + } + + // Check eligibility and add to todos if eligible if ( $todo['isEligible']() ) { $todos[] = array_merge( array( 'id' => $id ), @@ -118,8 +129,9 @@ public function get_todos(): WP_REST_Response { return $this->return_success( array( - 'todos' => $todos, - 'dismissedTodos' => $dismissed_ids, + 'todos' => $todos, + 'dismissedTodos' => $dismissed_ids, + 'completedOnClickTodos' => $completed_onclick_ids, ) ); } diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 3b1a435b0..a72a8be46 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -240,6 +240,7 @@ static function () use ( $container ) : void { 'todos' => $container->get( 'settings.rest.todos' ), 'reset_dismissed_todos' => $container->get( 'settings.rest.reset_dismissed_todos' ), 'pay_later_messaging' => $container->get( 'settings.rest.pay_later_messaging' ), + 'complete_onclick' => $container->get( 'settings.rest.complete_onclick' ), ); foreach ( $endpoints as $endpoint ) {