From af7d4c75241dd4c23ca15099d4e195790c61dacb Mon Sep 17 00:00:00 2001 From: Himad M Date: Tue, 15 Jul 2025 11:31:33 -0400 Subject: [PATCH 1/4] Block UI during onApprove callback on classic components --- .../ppcp-button/resources/js/modules/Helper/Spinner.js | 5 ++++- .../js/modules/OnApproveHandler/onApproveForContinue.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js index 30d0830f9..0d6640531 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js @@ -20,6 +20,9 @@ class Spinner { unblock() { jQuery( this.target ).unblock(); } -} + static fullPage() { + return new Spinner( window ); + } +} export default Spinner; diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js index 13b914335..d5c5ab6dc 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js @@ -1,3 +1,5 @@ +import Spinner from '../Helper/Spinner'; + const initiateRedirect = ( successUrl ) => { /** * Notice how this step initiates a redirect to a new page using a plain @@ -15,6 +17,10 @@ const initiateRedirect = ( successUrl ) => { const onApprove = ( context, errorHandler ) => { return ( data, actions ) => { + // Block the entire page during approval process + const spinner = Spinner.fullPage(); + spinner.block(); + const canCreateOrder = ! context.config.vaultingEnabled || data.paymentSource !== 'venmo'; @@ -50,6 +56,9 @@ const onApprove = ( context, errorHandler ) => { const orderReceivedUrl = approveData.data?.order_received_url; initiateRedirect( orderReceivedUrl || context.config.redirect ); + } ) + .finally( () => { + spinner.unblock(); } ); }; }; From 8ce9e94bbbe40a69ccf8c0bbf9516f9b1c56c129 Mon Sep 17 00:00:00 2001 From: Himad M Date: Wed, 16 Jul 2025 16:53:21 -0400 Subject: [PATCH 2/4] Block UI during onApprove callback on block components --- modules/ppcp-blocks/resources/js/paypal-config.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/ppcp-blocks/resources/js/paypal-config.js b/modules/ppcp-blocks/resources/js/paypal-config.js index 4aacab318..b743cd901 100644 --- a/modules/ppcp-blocks/resources/js/paypal-config.js +++ b/modules/ppcp-blocks/resources/js/paypal-config.js @@ -3,6 +3,7 @@ import { paypalSubscriptionToWcAddresses, } from './Helper/Address'; import ResumeFlowHelper from '../../../ppcp-button/resources/js/modules/Helper/ResumeFlowHelper'; +import Spinner from '../../../ppcp-button/resources/js/modules/Helper/Spinner'; export const createOrder = async ( data, config, onError, onClose ) => { try { @@ -66,6 +67,10 @@ export const handleApprove = async ( onError, onClose ) => { + // Block the entire page during approval process + const spinner = Spinner.fullPage(); + spinner.block(); + try { let order; @@ -173,6 +178,8 @@ export const handleApprove = async ( onClose(); throw err; + } finally { + spinner.unblock(); } }; From 41d02e80842ed89acaecfafef4c7153fe7d52d16 Mon Sep 17 00:00:00 2001 From: Himad M Date: Tue, 12 Aug 2025 17:49:17 -0400 Subject: [PATCH 3/4] Delegate spinner unblock to WooCommerce events --- .../resources/js/Components/paypal.js | 31 +++++++++++++++++-- .../ppcp-blocks/resources/js/paypal-config.js | 12 +++---- .../resources/js/modules/Helper/Spinner.js | 1 + 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/Components/paypal.js b/modules/ppcp-blocks/resources/js/Components/paypal.js index 92287381f..989a56558 100644 --- a/modules/ppcp-blocks/resources/js/Components/paypal.js +++ b/modules/ppcp-blocks/resources/js/Components/paypal.js @@ -21,6 +21,7 @@ import { onApproveSavePayment, } from '../paypal-config'; import { useRef } from 'react'; +import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner'; const PAYPAL_GATEWAY_ID = 'ppcp-gateway'; @@ -52,8 +53,11 @@ export const PayPalComponent = ( { useState( false ); const [ paypalScriptLoaded, setPaypalScriptLoaded ] = useState( false ); + const [ isFullPageSpinnerActive, setIsFullPageSpinnerActive ] = + useState( false ); const paypalButtonRef = useRef( null ); + const spinnerRef = useRef( null ); if ( ! paypalScriptLoaded ) { if ( ! paypalScriptPromise ) { @@ -70,6 +74,18 @@ export const PayPalComponent = ( { ? `${ config.id }-${ fundingSource }` : config.id; + // Full-page spinner used to block UI interactions during flows like AppSwitch. + useEffect( () => { + if ( isFullPageSpinnerActive ) { + if ( ! spinnerRef.current ) { + spinnerRef.current = Spinner.fullPage(); + } + spinnerRef.current.block(); + } else if ( spinnerRef.current ) { + spinnerRef.current.unblock(); + } + }, [ isFullPageSpinnerActive ] ); + useEffect( () => { // fill the form if in continuation (for product or mini-cart buttons) if ( continuationFilled || ! config.scriptData.continuation?.order ) { @@ -286,12 +302,21 @@ export const PayPalComponent = ( { }; }, [ onPaymentSetup, paypalOrder, activePaymentMethod ] ); + useEffect( () => { + const unsubscribe = onCheckoutFail( () => { + setIsFullPageSpinnerActive( false ); + } ); + + return unsubscribe; + }, [ onCheckoutFail ] ); + useEffect( () => { if ( activePaymentMethod !== methodId ) { return; } const unsubscribe = onCheckoutFail( ( { processingResponse } ) => { console.error( processingResponse ); + setIsFullPageSpinnerActive( false ); if ( onClose ) { onClose(); } @@ -336,7 +361,8 @@ export const PayPalComponent = ( { setGotoContinuationOnError, onSubmit, onError, - onClose + onClose, + setIsFullPageSpinnerActive ); }, } @@ -491,7 +517,8 @@ export const PayPalComponent = ( { setGotoContinuationOnError, onSubmit, onError, - onClose + onClose, + setIsFullPageSpinnerActive ) } onShippingOptionsChange={ getOnShippingOptionsChange( diff --git a/modules/ppcp-blocks/resources/js/paypal-config.js b/modules/ppcp-blocks/resources/js/paypal-config.js index b743cd901..93105786f 100644 --- a/modules/ppcp-blocks/resources/js/paypal-config.js +++ b/modules/ppcp-blocks/resources/js/paypal-config.js @@ -3,7 +3,6 @@ import { paypalSubscriptionToWcAddresses, } from './Helper/Address'; import ResumeFlowHelper from '../../../ppcp-button/resources/js/modules/Helper/ResumeFlowHelper'; -import Spinner from '../../../ppcp-button/resources/js/modules/Helper/Spinner'; export const createOrder = async ( data, config, onError, onClose ) => { try { @@ -65,11 +64,10 @@ export const handleApprove = async ( setGotoContinuationOnError, onSubmit, onError, - onClose + onClose, + setIsFullPageSpinnerActive ) => { - // Block the entire page during approval process - const spinner = Spinner.fullPage(); - spinner.block(); + setIsFullPageSpinnerActive( true ); try { let order; @@ -173,13 +171,13 @@ export const handleApprove = async ( } catch ( err ) { console.error( err ); + setIsFullPageSpinnerActive( false ); + onError( err.message ); onClose(); throw err; - } finally { - spinner.unblock(); } }; diff --git a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js index 0d6640531..63f941279 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/Spinner.js +++ b/modules/ppcp-button/resources/js/modules/Helper/Spinner.js @@ -14,6 +14,7 @@ class Spinner { background: '#fff', opacity: 0.6, }, + baseZ: 10000, } ); } From 02d05dc579be49db446c38bfce3400a676920480 Mon Sep 17 00:00:00 2001 From: Himad M Date: Tue, 12 Aug 2025 17:51:10 -0400 Subject: [PATCH 4/4] Prevent button re-rendering during resume flow in single product context --- modules/ppcp-blocks/resources/js/Components/paypal.js | 1 - .../js/modules/ContextBootstrap/SingleProductBootstrap.js | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/Components/paypal.js b/modules/ppcp-blocks/resources/js/Components/paypal.js index 989a56558..f2185f0b7 100644 --- a/modules/ppcp-blocks/resources/js/Components/paypal.js +++ b/modules/ppcp-blocks/resources/js/Components/paypal.js @@ -316,7 +316,6 @@ export const PayPalComponent = ( { } const unsubscribe = onCheckoutFail( ( { processingResponse } ) => { console.error( processingResponse ); - setIsFullPageSpinnerActive( false ); if ( onClose ) { onClose(); } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstrap.js index ef51b32d5..87dadb468 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstrap.js @@ -8,6 +8,7 @@ import SimulateCart from '../Helper/SimulateCart'; import { strRemoveWord, strAddWord, throttle } from '../Helper/Utils'; import merge from 'deepmerge'; import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce'; +import ResumeFlowHelper from '../Helper/ResumeFlowHelper'; class SingleProductBootstrap { constructor( gateway, renderer, errorHandler ) { @@ -53,7 +54,10 @@ class SingleProductBootstrap { return; } - this.render(); + // Avoid re-rendering during the resume flow to prevent duplicate onApprove callbacks. + if ( ! ResumeFlowHelper.isResumeFlow() ) { + this.render(); + } this.renderer.enableSmartButtons( this.gateway.button.wrapper ); show( this.gateway.button.wrapper );