Settings UI: Add Todos saving and dismissing

This commit is contained in:
Daniel Dudzic 2025-02-03 23:06:33 +01:00
parent 0daf56b2af
commit f6717e2e66
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
16 changed files with 433 additions and 89 deletions

View file

@ -1,5 +1,7 @@
import { selectTab, TAB_IDS } from '../../../utils/tabSelector';
import { useState } from '@wordpress/element';
import { useEffect, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { STORE_NAME as TODOS_STORE_NAME } from '../../../data/todos';
const TodoSettingsBlock = ( {
todosData,
@ -9,6 +11,21 @@ const TodoSettingsBlock = ( {
onDismissTodo,
} ) => {
const [ dismissingIds, setDismissingIds ] = useState( new Set() );
const { completedTodos, dismissedTodos } = useSelect(
( select ) => ( {
completedTodos:
select( TODOS_STORE_NAME ).getCompletedTodos() || [],
dismissedTodos:
select( TODOS_STORE_NAME ).getDismissedTodos() || [],
} ),
[]
);
useEffect( () => {
if ( dismissedTodos.length === 0 ) {
setDismissingIds( new Set() );
}
}, [ dismissedTodos ] );
if ( todosData.length === 0 ) {
return null;
@ -24,17 +41,22 @@ const TodoSettingsBlock = ( {
}, 300 );
};
// Filter out dismissed todos for display
const visibleTodos = todosData.filter(
( todo ) => ! dismissedTodos.includes( todo.id )
);
return (
<div
className={ `ppcp-r-settings-block__todo ppcp-r-todo-items ${ className }` }
>
{ todosData.map( ( todo ) => (
{ visibleTodos.map( ( todo ) => (
<TodoItem
key={ todo.id }
id={ todo.id }
title={ todo.title }
description={ todo.description }
isCompleted={ todo.isCompleted }
isCompleted={ completedTodos.includes( todo.id ) }
isDismissing={ dismissingIds.has( todo.id ) }
onDismiss={ ( e ) => handleDismiss( todo.id, e ) }
onClick={ async () => {

View file

@ -14,7 +14,9 @@ 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 { STORE_NAME as COMMON_STORE_NAME } from '../../../../data/common';
import { STORE_NAME as TODOS_STORE_NAME } from '../../../../data/todos';
import { getFeatures } from '../Components/Overview/features-config';
import {
@ -35,13 +37,34 @@ const TabOverview = () => {
export default TabOverview;
const OverviewTodos = () => {
const [ isResetting, setIsResetting ] = useState( false );
const { todos, isReady: areTodosReady, dismissTodo } = useTodos();
const { setActiveModal } = useDispatch( STORE_NAME );
const { setActiveHighlight } = useDispatch( STORE_NAME );
const { setActiveModal, setActiveHighlight } =
useDispatch( COMMON_STORE_NAME );
const { resetDismissedTodos, setDismissedTodos } =
useDispatch( TODOS_STORE_NAME );
const { createSuccessNotice } = useDispatch( noticesStore );
// Don't render todos section until data is ready
const showTodos = areTodosReady && todos.length > 0;
const resetHandler = async () => {
setIsResetting( true );
try {
await setDismissedTodos( [] );
await resetDismissedTodos();
createSuccessNotice(
__(
'Dismissed items restored successfully.',
'woocommerce-paypal-payments'
),
{ icon: NOTIFICATION_SUCCESS }
);
} finally {
setIsResetting( false );
}
};
if ( ! showTodos ) {
return null;
}
@ -50,10 +73,29 @@ const OverviewTodos = () => {
<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'
) }
description={
<>
<p>
{ __(
'Complete these tasks to keep your store updated with the latest products and services.',
'woocommerce-paypal-payments'
) }
</p>
<Button
variant="tertiary"
onClick={ resetHandler }
disabled={ isResetting }
>
<Icon icon={ reusableBlock } size={ 18 } />
{ isResetting
? __( 'Restoring…', 'woocommerce-paypal-payments' )
: __(
'Restore dismissed Things To Do',
'woocommerce-paypal-payments'
) }
</Button>
</>
}
>
<TodoSettingsBlock
todosData={ todos }
@ -69,7 +111,7 @@ const OverviewFeatures = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false );
const { merchant, features: merchantFeatures } = useMerchantInfo();
const { refreshFeatureStatuses, setActiveModal } =
useDispatch( STORE_NAME );
useDispatch( COMMON_STORE_NAME );
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );
@ -79,7 +121,7 @@ const OverviewFeatures = () => {
[ setActiveModal ]
);
// Map merchant features status to our config
// Map merchant features status to the config
const features = useMemo( () => {
return featuresData.map( ( feature ) => {
const merchantFeature = merchantFeatures?.[ feature.id ];

View file

@ -23,14 +23,19 @@ export const initTodoSync = () => {
try {
const paymentState = select( 'wc/paypal/payment' ).persistentData();
const todosState = select( 'wc/paypal/todos' ).getTodos();
const completedTodos =
select( 'wc/paypal/todos' ).getCompletedTodos();
// Skip if states haven't been initialized yet
if ( ! paymentState || ! todosState || ! previousPaymentState ) {
if ( ! paymentState || ! previousPaymentState ) {
previousPaymentState = paymentState;
isProcessing = false;
return;
}
// Track which todos should be marked as completed
let newCompletedTodos = [ ...( completedTodos || [] ) ];
Object.entries( TODO_TRIGGERS ).forEach(
( [ paymentMethod, todoId ] ) => {
const wasEnabled =
@ -38,26 +43,34 @@ export const initTodoSync = () => {
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
if ( isEnabled ) {
// Add to completed todos if not already there
if ( ! newCompletedTodos.includes( todoId ) ) {
newCompletedTodos.push( todoId );
}
} else {
// Remove from completed todos
newCompletedTodos = newCompletedTodos.filter(
( id ) => id !== todoId
);
}
}
}
);
previousPaymentState = paymentState;
// Only dispatch if there are changes
if (
newCompletedTodos.length !== completedTodos.length ||
newCompletedTodos.some(
( id ) => ! completedTodos.includes( id )
)
) {
dispatch( 'wc/paypal/todos' ).setCompletedTodos(
newCompletedTodos
);
}
previousPaymentState = { ...paymentState };
} catch ( error ) {
console.error( 'Error in todo sync:', error );
} finally {

View file

@ -7,6 +7,7 @@
export default {
// Transient data
SET_TRANSIENT: 'TODOS:SET_TRANSIENT',
SET_COMPLETED_TODOS: 'TODOS:SET_COMPLETED_TODOS',
// Persistent data
SET_TODOS: 'TODOS:SET_TODOS',
@ -14,4 +15,6 @@ export default {
// Controls
DO_FETCH_TODOS: 'TODOS:DO_FETCH_TODOS',
DO_PERSIST_DATA: 'TODOS:DO_PERSIST_DATA',
DO_RESET_DISMISSED_TODOS: 'TODOS:DO_RESET_DISMISSED_TODOS',
};

View file

@ -7,7 +7,9 @@
* @file
*/
import { select } from '@wordpress/data';
import ACTION_TYPES from './action-types';
import { STORE_NAME } from './constants';
export const setIsReady = ( isReady ) => ( {
type: ACTION_TYPES.SET_TRANSIENT,
@ -27,3 +29,24 @@ export const setDismissedTodos = ( dismissedTodos ) => ( {
export const fetchTodos = function* () {
yield { type: ACTION_TYPES.DO_FETCH_TODOS };
};
export const persist = function* () {
const data = yield select( STORE_NAME ).persistentData();
yield { type: ACTION_TYPES.DO_PERSIST_DATA, data };
};
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();
}
return result;
};
export const setCompletedTodos = ( completedTodos ) => ( {
type: ACTION_TYPES.SET_COMPLETED_TODOS,
payload: completedTodos,
} );

View file

@ -6,3 +6,6 @@
export const STORE_NAME = 'wc/paypal/todos';
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';

View file

@ -8,7 +8,11 @@
*/
import apiFetch from '@wordpress/api-fetch';
import { REST_PATH } from './constants';
import {
REST_PATH,
REST_PERSIST_PATH,
REST_RESET_DISMISSED_TODOS_PATH,
} from './constants';
import ACTION_TYPES from './action-types';
export const controls = {
@ -19,4 +23,25 @@ export const controls = {
} );
return response?.data || [];
},
async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) {
return await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
},
async [ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]() {
try {
return await apiFetch( {
path: REST_RESET_DISMISSED_TODOS_PATH,
method: 'POST',
} );
} catch ( e ) {
return {
success: false,
error: e,
message: e.message,
};
}
},
};

View file

@ -9,45 +9,87 @@
import { useSelect, useDispatch } from '@wordpress/data';
import { STORE_NAME } from './constants';
import { createHooksForStore } from '../utils';
const useTransient = ( key ) =>
useSelect(
( select ) => select( STORE_NAME ).transientData()?.[ key ],
[ key ]
);
const ensureArray = ( value ) => {
if ( ! value ) {
return [];
}
return Array.isArray( value ) ? value : Object.values( value );
};
export const useTodos = () => {
const todos = useSelect(
( 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 useHooks = () => {
const { useTransient } = createHooksForStore( STORE_NAME );
const { fetchTodos, setDismissedTodos, setCompletedTodos, persist } =
useDispatch( STORE_NAME );
// Read-only flags and derived state.
const [ isReady ] = useTransient( 'isReady' );
// Get todos data from store
const { todos, dismissedTodos, completedTodos } = useSelect( ( select ) => {
const store = select( STORE_NAME );
return {
todos: ensureArray( store.getTodos() ),
dismissedTodos: ensureArray( store.getDismissedTodos() ),
completedTodos: ensureArray( store.getCompletedTodos() ),
};
}, [] );
const isReady = useTransient( 'isReady' );
const { fetchTodos, setDismissedTodos } = useDispatch( STORE_NAME );
const dismissedSet = new Set( dismissedTodos );
const dismissTodo = ( todoId ) => {
const currentDismissed = dismissedTodos || [];
if ( ! currentDismissed.includes( todoId ) ) {
const newDismissedTodos = [ ...currentDismissed, todoId ];
setDismissedTodos( newDismissedTodos );
const dismissTodo = async ( todoId ) => {
if ( ! dismissedSet.has( todoId ) ) {
const newDismissedTodos = [ ...dismissedTodos, todoId ];
await setDismissedTodos( newDismissedTodos );
}
};
const setTodoCompleted = async ( todoId, isCompleted ) => {
let newCompletedTodos;
if ( isCompleted ) {
newCompletedTodos = [ ...completedTodos, todoId ];
} else {
newCompletedTodos = completedTodos.filter(
( id ) => id !== todoId
);
}
await setCompletedTodos( newCompletedTodos );
};
const filteredTodos = todos.filter(
( todo ) => ! dismissedTodos.includes( todo.id )
( todo ) => ! dismissedSet.has( todo.id )
);
return {
todos: filteredTodos,
persist,
isReady,
todos: filteredTodos,
dismissedTodos,
completedTodos,
fetchTodos,
dismissTodo,
setTodoCompleted,
};
};
export const useStore = () => {
const { persist, isReady } = useHooks();
return { persist, isReady };
};
export const useTodos = () => {
const { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady } =
useHooks();
return { todos, fetchTodos, dismissTodo, setTodoCompleted, isReady };
};
export const useDismissedTodos = () => {
const { dismissedTodos } = useHooks();
return { dismissedTodos };
};
export const useCompletedTodos = () => {
const { completedTodos } = useHooks();
return { completedTodos };
};

View file

@ -18,6 +18,7 @@ import ACTION_TYPES from './action-types';
*/
const defaultTransient = Object.freeze( {
isReady: false,
completedTodos: [],
} );
/**
@ -74,6 +75,28 @@ const reducer = createReducer( defaultTransient, defaultPersistent, {
} );
},
/**
* Updates completed todos list while preserving existing entries
*
* @param {Object} state Current state
* @param {Array} payload Array of todo IDs to mark as completed
* @return {Object} Updated state
*/
[ ACTION_TYPES.SET_COMPLETED_TODOS ]: ( state, payload ) => {
return changeTransient( state, {
completedTodos: Array.isArray( payload ) ? payload : [],
} );
},
/**
* Resets dismissed todos list to an empty array
* @param {Object} state Current state
* @return {Object} Updated state
*/
[ ACTION_TYPES.DO_RESET_DISMISSED_TODOS ]: ( state ) => {
return changePersistent( state, { dismissedTodos: [] } );
},
/**
* Resets state to defaults while maintaining initialization status
*

View file

@ -18,12 +18,11 @@ export const resolvers = {
try {
const response = yield apiFetch( { path: REST_PATH } );
// Make sure we're accessing the correct part of the response
const todos = response?.data || [];
const { todos = [], dismissedTodos = [] } = response?.data || {};
yield dispatch( STORE_NAME ).setTodos( todos );
yield dispatch( STORE_NAME ).setDismissedTodos( dismissedTodos );
yield dispatch( STORE_NAME ).setIsReady( true );
} catch ( e ) {
console.error( 'Resolver error:', e );
yield dispatch( STORE_NAME ).setIsReady( false );

View file

@ -22,16 +22,16 @@ export const transientData = ( state ) => {
};
export const getTodos = ( state ) => {
// Access todos directly from state first
const todos = state?.todos || persistentData( state ).todos || EMPTY_ARR;
return todos;
const todos = state?.todos || persistentData( state ).todos;
return todos || EMPTY_ARR;
};
export const getDismissedTodos = ( state ) => {
// Access dismissed todos directly from state first
const dismissedTodos =
state?.dismissedTodos ||
persistentData( state ).dismissedTodos ||
EMPTY_ARR;
return dismissedTodos;
const dismissed =
state?.dismissedTodos || persistentData( state ).dismissedTodos;
return dismissed || EMPTY_ARR;
};
export const getCompletedTodos = ( state ) => {
return state?.completedTodos || EMPTY_ARR; // Only look at root state, not persistent data
};

View file

@ -5,6 +5,7 @@ import {
PaymentHooks,
SettingsHooks,
StylingHooks,
TodosHooks,
} from '../data';
export const useSaveSettings = () => {
@ -13,6 +14,7 @@ export const useSaveSettings = () => {
const { persist: persistPayment } = PaymentHooks.useStore();
const { persist: persistSettings } = SettingsHooks.useStore();
const { persist: persistStyling } = StylingHooks.useStore();
const { persist: persistTodos } = TodosHooks.useStore();
const persistAll = useCallback( () => {
withActivity(
@ -30,7 +32,14 @@ export const useSaveSettings = () => {
'Save styling details',
persistStyling
);
}, [ persistPayment, persistSettings, persistStyling, withActivity ] );
withActivity( 'persist-todos', 'Save todos state', persistTodos );
}, [
persistPayment,
persistSettings,
persistStyling,
persistTodos,
withActivity,
] );
return { persistAll };
};

View file

@ -24,6 +24,7 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\PaymentRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\ResetDismissedTodosEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\SettingsRestEndpoint;
use WooCommerce\PayPalCommerce\Settings\Endpoint\StylingRestEndpoint;
@ -239,7 +240,8 @@ return array(
'settings.rest.todos' => static function ( ContainerInterface $container ) : TodosRestEndpoint {
return new TodosRestEndpoint(
$container->get( 'settings.data.todos' ),
$container->get( 'settings.data.definition.todos' )
$container->get( 'settings.data.definition.todos' ),
$container->get( 'settings.rest.settings' )
);
},
'settings.data.todos' => static function ( ContainerInterface $container ) : TodosModel {
@ -255,13 +257,25 @@ return array(
$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( 'wc-subscriptions.helper' ) && $container->get( 'wc-subscriptions.helper' )->plugin_is_active() && empty(
wc_get_products(
array(
'type' => 'subscription',
'limit' => 1,
)
)
),
$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( 'googlepay.eligible' ) &&
$container->get( 'googlepay.eligible' ) &&
( $container->get( 'googlepay.available' ) && ! $container->get( 'googlepay.is_referral' ) ),
$container->has( 'paylater-messaging.available' ) && $container->get( 'paylater-messaging.available' ),
$container->has( 'button.basic-checkout.enabled' ) && ! $container->get( 'button.basic-checkout.enabled' )
);
},
'settings.rest.reset_dismissed_todos' => static function( ContainerInterface $container ): ResetDismissedTodosEndpoint {
return new ResetDismissedTodosEndpoint();
},
);

View file

@ -0,0 +1,72 @@
<?php
/**
* REST endpoint to reset dismissed things to do items.
*
* Provides endpoint for resetting all dismissed todos
* via WP REST API route.
*
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
/**
* Class ResetDismissedTodosEndpoint
*
* Handles REST API endpoint for resetting dismissed things to do items.
*/
class ResetDismissedTodosEndpoint extends RestEndpoint {
/**
* The base path for this REST controller.
*
* @var string
*/
protected $rest_base = 'reset-dismissed-todos';
/**
* Registers the REST API route for resetting todos.
*/
public function register_routes(): void {
/**
* POST wc/v3/wc_paypal/reset-dismissed-todos
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'reset_dismissed_todos' ),
'permission_callback' => array( $this, 'check_permission' ),
)
);
}
/**
* Resets all dismissed todos.
*
* @param WP_REST_Request $request The request instance.
* @return WP_REST_Response The response containing reset status.
*/
public function reset_dismissed_todos( WP_REST_Request $request ): WP_REST_Response {
$settings = get_option( 'ppcp-settings', array() );
$settings['dismissedTodos'] = array();
$update_result = update_option( 'ppcp-settings', $settings );
if ( ! $update_result ) {
return $this->return_error( __( 'Failed to reset dismissed todos.', 'woocommerce-paypal-payments' ) );
}
return $this->return_success(
array(
'message' => __( 'Dismissed todos reset successfully.', 'woocommerce-paypal-payments' ),
)
);
}
}

View file

@ -2,6 +2,9 @@
/**
* REST endpoint to manage the things to do items.
*
* Provides endpoints for retrieving, updating, and resetting todos
* via WP REST API routes.
*
* @package WooCommerce\PayPalCommerce\Settings\Endpoint
*/
@ -9,8 +12,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use WP_REST_Response;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
use WooCommerce\PayPalCommerce\Settings\Data\TodosModel;
use WooCommerce\PayPalCommerce\Settings\Data\Definition\TodosDefinition;
@ -44,55 +48,104 @@ class TodosRestEndpoint extends RestEndpoint {
protected TodosDefinition $todos_definition;
/**
* Constructor.
* The settings endpoint instance.
*
* @param TodosModel $todos The todos model instance.
* @param TodosDefinition $todos_definition The todos definition instance.
* @var SettingsRestEndpoint
*/
protected SettingsRestEndpoint $settings;
/**
* TodosRestEndpoint constructor.
*
* @param TodosModel $todos The todos model instance.
* @param TodosDefinition $todos_definition The todos definition instance.
* @param SettingsRestEndpoint $settings The settings endpoint instance.
*/
public function __construct(
TodosModel $todos,
TodosDefinition $todos_definition
TodosDefinition $todos_definition,
SettingsRestEndpoint $settings
) {
$this->todos = $todos;
$this->todos_definition = $todos_definition;
$this->settings = $settings;
}
/**
* Configure REST API routes.
* Registers the REST API routes for todos management.
*/
public function register_routes(): void {
/**
* GET wc/v3/wc_paypal/todos
* POST wc/v3/wc_paypal/todos
*/
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_todos' ),
'permission_callback' => array( $this, 'check_permission' ),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_todos' ),
'permission_callback' => array( $this, 'check_permission' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_todos' ),
'permission_callback' => array( $this, 'check_permission' ),
),
)
);
}
/**
* Returns all eligible todo items.
* Retrieves the current todos.
*
* @return WP_REST_Response The response containing eligible todo items.
* @return WP_REST_Response The response containing todos data.
*/
public function get_todos(): WP_REST_Response {
$completion_states = $this->todos->get();
$todos = array();
$settings = get_option( 'ppcp-settings', array() );
$dismissed_ids = $settings['dismissedTodos'] ?? array();
$todos = array();
foreach ( $this->todos_definition->get() as $id => $todo ) {
if ( $todo['isEligible']() ) {
$todos[] = array_merge(
array(
'id' => $id,
'isCompleted' => $completion_states[ $id ] ?? false,
),
array( 'id' => $id ),
array_diff_key( $todo, array( 'isEligible' => true ) )
);
}
}
return $this->return_success( $todos );
return $this->return_success(
array(
'todos' => $todos,
'dismissedTodos' => $dismissed_ids,
)
);
}
/**
* Updates the todos with provided data.
*
* @param WP_REST_Request $request The request instance containing todo updates.
* @return WP_REST_Response The response containing updated todos or error details.
*/
public function update_todos( WP_REST_Request $request ): WP_REST_Response {
$data = $request->get_json_params();
$settings = get_option( 'ppcp-settings', array() );
if ( isset( $data['dismissedTodos'] ) ) {
$settings['dismissedTodos'] = array_unique(
is_array( $data['dismissedTodos'] ) ? $data['dismissedTodos'] : array()
);
$update_result = update_option( 'ppcp-settings', $settings );
if ( $update_result ) {
return $this->return_success( $settings );
}
}
return $this->return_success( $data );
}
}

View file

@ -238,6 +238,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
'settings' => $container->get( 'settings.rest.settings' ),
'styling' => $container->get( 'settings.rest.styling' ),
'todos' => $container->get( 'settings.rest.todos' ),
'reset_dismissed_todos' => $container->get( 'settings.rest.reset_dismissed_todos' ),
);
foreach ( $endpoints as $endpoint ) {