From 3d367cb7c664e3b3550b59a8619f88183b8318b4 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 3 Jun 2026 12:49:17 -0300 Subject: [PATCH 1/2] fix(modal-checkout): tiered donate URL trigger handles non-default frequency The tiered donate form renders a single hidden donation_frequency input locked to the block's default frequency, so triggerDonationForm's early guard rejected any URL whose frequency differed from the default and the modal never opened. Move the tiered branch ahead of the guard (only the non-tiered branches need that input) and scope the frequency-tab lookup to the form. Fixes NPPM-2815 --- .../src/modal-checkout/modal.js | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/plugins/newspack-blocks/src/modal-checkout/modal.js b/plugins/newspack-blocks/src/modal-checkout/modal.js index 9334215824..b8f40de474 100644 --- a/plugins/newspack-blocks/src/modal-checkout/modal.js +++ b/plugins/newspack-blocks/src/modal-checkout/modal.js @@ -756,41 +756,48 @@ domReady( () => { const triggerDonationForm = ( layout, frequency, amount, other = null ) => { let form; document.querySelectorAll( '.wpbnbd.wpbnbd--platform-wc form' ).forEach( donationForm => { - const frequencyInput = donationForm.querySelector( `input[name="donation_frequency"][value="${ frequency }"]` ); - if ( ! frequencyInput ) { - return; - } if ( layout === 'tiered' ) { - const frequencyButton = document.querySelector( `button[data-frequency-slug="${ frequency }"]` ); + // Tiered forms render a single hidden `donation_frequency` input whose value + // is locked to the block's default frequency, so we can't gate on that input + // matching the URL `frequency`. Instead, look for the tiered frequency tab, + // which is unique to the tiered layout (frequency-based uses `data-tab-id`). + const frequencyButton = donationForm.querySelector( `button[data-frequency-slug="${ frequency }"]` ); if ( ! frequencyButton ) { return; } + // Clicking the tab synchronously updates each tier submit button's `name` + // and `value` attributes (see src/blocks/donate/tiers-based/view.ts), so we + // can query for the matching submit button immediately afterwards. frequencyButton.click(); const submitButton = donationForm.querySelector( `button[type="submit"][name="donation_value_${ frequency }"][value="${ amount }"]` ); if ( ! submitButton ) { return; } submitButton.click(); - } else { - const amountInput = - layout === 'untiered' - ? donationForm.querySelector( `input[name="donation_value_${ frequency }_untiered"]` ) - : donationForm.querySelector( `input[name="donation_value_${ frequency }"][value="${ amount }"]` ); - if ( frequencyInput && amountInput ) { - frequencyInput.checked = true; - if ( layout === 'untiered' ) { - amountInput.value = amount; - } else if ( amount === 'other' ) { - amountInput.click(); - const otherInput = donationForm.querySelector( `input[name="donation_value_${ frequency }_other"]` ); - if ( otherInput && other ) { - otherInput.value = other; - } - } else { - amountInput.checked = true; + return; + } + const frequencyInput = donationForm.querySelector( `input[name="donation_frequency"][value="${ frequency }"]` ); + if ( ! frequencyInput ) { + return; + } + const amountInput = + layout === 'untiered' + ? donationForm.querySelector( `input[name="donation_value_${ frequency }_untiered"]` ) + : donationForm.querySelector( `input[name="donation_value_${ frequency }"][value="${ amount }"]` ); + if ( frequencyInput && amountInput ) { + frequencyInput.checked = true; + if ( layout === 'untiered' ) { + amountInput.value = amount; + } else if ( amount === 'other' ) { + amountInput.click(); + const otherInput = donationForm.querySelector( `input[name="donation_value_${ frequency }_other"]` ); + if ( otherInput && other ) { + otherInput.value = other; } - form = donationForm; + } else { + amountInput.checked = true; } + form = donationForm; } } ); if ( form ) { From e35fc32152435b0e3dae943c6973615b75d312f7 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 3 Jun 2026 13:01:48 -0300 Subject: [PATCH 2/2] fix(modal-checkout): stop iterating after first successful donate trigger Switch from .forEach() to for...of so the URL-trigger handler returns after the first form that matches, preventing duplicate submits on pages with multiple .wpbnbd--platform-wc forms. Addresses Copilot review feedback on #202. --- .../src/modal-checkout/modal.js | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/plugins/newspack-blocks/src/modal-checkout/modal.js b/plugins/newspack-blocks/src/modal-checkout/modal.js index b8f40de474..51b4dcf9d9 100644 --- a/plugins/newspack-blocks/src/modal-checkout/modal.js +++ b/plugins/newspack-blocks/src/modal-checkout/modal.js @@ -754,8 +754,10 @@ domReady( () => { * @param {string|null} other Optional. The custom amount when other is selected. */ const triggerDonationForm = ( layout, frequency, amount, other = null ) => { - let form; - document.querySelectorAll( '.wpbnbd.wpbnbd--platform-wc form' ).forEach( donationForm => { + // Iterate with `for...of` so we can `return` after the first successful trigger. + // Without this, multiple matching forms on the same page (e.g. two tiered Donate + // blocks) would each get submitted in succession. + for ( const donationForm of document.querySelectorAll( '.wpbnbd.wpbnbd--platform-wc form' ) ) { if ( layout === 'tiered' ) { // Tiered forms render a single hidden `donation_frequency` input whose value // is locked to the block's default frequency, so we can't gate on that input @@ -763,7 +765,7 @@ domReady( () => { // which is unique to the tiered layout (frequency-based uses `data-tab-id`). const frequencyButton = donationForm.querySelector( `button[data-frequency-slug="${ frequency }"]` ); if ( ! frequencyButton ) { - return; + continue; } // Clicking the tab synchronously updates each tier submit button's `name` // and `value` attributes (see src/blocks/donate/tiers-based/view.ts), so we @@ -771,37 +773,36 @@ domReady( () => { frequencyButton.click(); const submitButton = donationForm.querySelector( `button[type="submit"][name="donation_value_${ frequency }"][value="${ amount }"]` ); if ( ! submitButton ) { - return; + continue; } submitButton.click(); return; } const frequencyInput = donationForm.querySelector( `input[name="donation_frequency"][value="${ frequency }"]` ); if ( ! frequencyInput ) { - return; + continue; } const amountInput = layout === 'untiered' ? donationForm.querySelector( `input[name="donation_value_${ frequency }_untiered"]` ) : donationForm.querySelector( `input[name="donation_value_${ frequency }"][value="${ amount }"]` ); - if ( frequencyInput && amountInput ) { - frequencyInput.checked = true; - if ( layout === 'untiered' ) { - amountInput.value = amount; - } else if ( amount === 'other' ) { - amountInput.click(); - const otherInput = donationForm.querySelector( `input[name="donation_value_${ frequency }_other"]` ); - if ( otherInput && other ) { - otherInput.value = other; - } - } else { - amountInput.checked = true; + if ( ! amountInput ) { + continue; + } + frequencyInput.checked = true; + if ( layout === 'untiered' ) { + amountInput.value = amount; + } else if ( amount === 'other' ) { + amountInput.click(); + const otherInput = donationForm.querySelector( `input[name="donation_value_${ frequency }_other"]` ); + if ( otherInput && other ) { + otherInput.value = other; } - form = donationForm; + } else { + amountInput.checked = true; } - } ); - if ( form ) { - triggerFormSubmit( form ); + triggerFormSubmit( donationForm ); + return; } };