Settings UI: Add functionality to mark todos as complete on click

This commit is contained in:
Daniel Dudzic 2025-02-05 11:19:44 +01:00
parent 05c73303af
commit 04f0f45233
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
12 changed files with 163 additions and 29 deletions

View file

@ -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 ) }
/>
) ) }
</div>

View file

@ -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',
};

View file

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

View file

@ -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';

View file

@ -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,
};
}
},
};

View file

@ -28,6 +28,7 @@ const defaultTransient = Object.freeze( {
const defaultPersistent = Object.freeze( {
todos: [],
dismissedTodos: [],
completedOnClickTodos: [],
} );
// Reducer logic.

View file

@ -20,6 +20,7 @@ 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\CompleteOnClickEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\PayLaterMessagingEndpoint;
@ -265,7 +266,8 @@ return array(
},
'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 @@ return array(
'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();
},
);

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
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 @@ class TodosDefinition {
'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(

View file

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
class CompleteOnClickEndpoint extends RestEndpoint {
protected $rest_base = 'complete-onclick';
public function register_routes(): void {
register_rest_route(
$this->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,
)
);
}
}

View file

@ -57,7 +57,11 @@ class ResetDismissedTodosEndpoint extends RestEndpoint {
$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' ) );

View file

@ -103,11 +103,22 @@ class TodosRestEndpoint extends RestEndpoint {
* @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 @@ class TodosRestEndpoint extends RestEndpoint {
return $this->return_success(
array(
'todos' => $todos,
'dismissedTodos' => $dismissed_ids,
'todos' => $todos,
'dismissedTodos' => $dismissed_ids,
'completedOnClickTodos' => $completed_onclick_ids,
)
);
}

View file

@ -240,6 +240,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
'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 ) {