@automattic/newspack-blocks 4.28.0 → 4.29.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/blocks/checkout-button/block.json +3 -0
  3. package/dist/editor-rtl.css +16 -13
  4. package/dist/editor.asset.php +1 -1
  5. package/dist/editor.css +16 -13
  6. package/dist/editor.js +30 -25
  7. package/dist/modal.asset.php +1 -1
  8. package/dist/modal.js +1 -1
  9. package/dist/modalCheckout-rtl.css +1 -1
  10. package/dist/modalCheckout.asset.php +1 -1
  11. package/dist/modalCheckout.css +1 -1
  12. package/dist/modalCheckout.js +1 -1
  13. package/includes/class-modal-checkout.php +131 -168
  14. package/includes/class-newspack-blocks.php +1 -0
  15. package/includes/modal-checkout/class-checkout-data.php +23 -0
  16. package/newspack-blocks.php +2 -2
  17. package/package.json +5 -5
  18. package/src/blocks/checkout-button/block.json +3 -0
  19. package/src/blocks/checkout-button/coupon-control.js +150 -0
  20. package/src/blocks/checkout-button/edit.js +35 -2
  21. package/src/blocks/checkout-button/edit.scss +3 -2
  22. package/src/blocks/checkout-button/view.php +41 -2
  23. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-frequency-based.php +2 -2
  24. package/src/blocks/donate/frontend/class-newspack-blocks-donate-renderer-tiers-based.php +1 -1
  25. package/src/modal-checkout/checkout-button-trigger.js +91 -6
  26. package/src/modal-checkout/checkout-button-trigger.test.js +143 -1
  27. package/src/modal-checkout/checkout.scss +1 -53
  28. package/src/modal-checkout/index.js +7 -89
  29. package/src/modal-checkout/modal.js +5 -27
  30. package/src/modal-checkout/templates/form-checkout.php +10 -20
  31. package/src/modal-checkout/templates/form-coupon.php +9 -0
  32. package/src/modal-checkout/templates/thankyou.php +1 -1
  33. package/vendor/composer/installed.php +2 -2
  34. package/webpack.config.js +6 -12
@@ -95,56 +95,12 @@
95
95
  }
96
96
 
97
97
  .order-review-wrapper {
98
- // Collapse and expand Transaction Details.
99
- // stylelint-disable-next-line selector-id-pattern
100
- #order_review_heading {
101
- border-radius: 0;
102
- min-height: 0;
103
- padding: 0;
104
-
105
- &:hover {
106
- background: transparent;
107
- color: var(--newspack-ui-color-neutral-60);
108
- }
109
-
110
- &:focus-visible {
111
- outline: 0;
112
- text-decoration: underline;
113
- }
114
-
115
- svg {
116
- margin: 0 -8px;
117
- transition: rotate 125ms ease-in-out;
118
- }
119
- }
98
+ margin-bottom: var(--newspack-ui-spacer-5, 24px);
120
99
 
121
100
  // stylelint-disable-next-line selector-id-pattern
122
101
  #order_review {
123
102
  margin: 0;
124
103
  }
125
-
126
- .transaction-details-content {
127
- display: grid;
128
- grid-template-rows: 0fr;
129
- margin-bottom: var(--newspack-ui-spacer-2, 12px);
130
- transition: grid-template-rows 250ms ease-in-out;
131
- }
132
-
133
- .transaction-details-content-inner {
134
- overflow: hidden;
135
- }
136
- }
137
-
138
- &.transaction-details-expanded {
139
- // stylelint-disable-next-line selector-id-pattern
140
- #order_review_heading svg {
141
- rotate: 180deg;
142
- }
143
-
144
- .transaction-details-content {
145
- grid-template-rows: 1fr;
146
- margin-bottom: var(--newspack-ui-spacer-5, 24px);
147
- }
148
104
  }
149
105
  }
150
106
 
@@ -410,14 +366,6 @@
410
366
  }
411
367
  }
412
368
 
413
- // Checkout form heading.
414
- .woocommerce .order-details-summary {
415
- // Top margin not needed in newspack ui modal.
416
- h2 {
417
- margin-top: 0;
418
- }
419
- }
420
-
421
369
  // Override checkout payment method box.
422
370
 
423
371
  .wc_payment_methods {
@@ -189,110 +189,23 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils';
189
189
  $wrapper.removeClass( 'hidden' );
190
190
  }
191
191
 
192
- const $details = $( '#after_customer_details' );
193
- const expanded = $details.hasClass( 'transaction-details-expanded' );
194
-
195
192
  // Move new order review table to the payment methods.
196
193
  const $payment_methods = $( '.payment_methods' );
197
194
  if ( $payment_methods.length ) {
198
195
  const $el = $wrapper.clone();
199
- // Make sure Transaction Details toggle's aria-expanded value is correct in cloned version.
200
- if ( expanded ) {
201
- $( '[id="order_review_heading"]', $el ).attr( 'aria-expanded', 'true' );
202
- }
203
196
  $( '.order-review-wrapper' ).remove();
204
197
  $payment_methods.after( $el );
205
- } else if ( ! expanded ) {
206
- // If there's no payment method, make sure to expand the Transaction Details on load.
207
- $wrapper.find( '#order_review_heading' ).trigger( 'click' );
208
198
  }
209
199
  } );
210
200
 
211
201
  /**
212
- * Keep the static product summary aligned with recalculated order-review totals.
202
+ * Serialize the checkout form for cart-recalculation AJAX requests.
213
203
  */
214
- function setProductSummary( productDetails, allowHtml = true ) {
215
- const $productDetails = $( '#modal-checkout-product-details strong' );
216
- if ( ! $productDetails.length || ! productDetails ) {
217
- return false;
218
- }
219
- if ( allowHtml && $productDetails.html() !== productDetails ) {
220
- $productDetails.html( productDetails );
221
- } else if ( ! allowHtml && $productDetails.text() !== productDetails ) {
222
- $productDetails.text( productDetails );
223
- }
224
- return true;
225
- }
226
- let productSummaryRequest = false;
227
204
  function getCheckoutPostData() {
228
205
  const $checkoutForm = $( 'form.checkout' );
229
206
  // Repeat-trial checks can only resolve once the checkout form includes a billing email.
230
207
  return $checkoutForm.length ? $checkoutForm.serialize() : '';
231
208
  }
232
- function requestUpdatedProductSummary() {
233
- if ( productSummaryRequest ) {
234
- productSummaryRequest.abort();
235
- }
236
- const request = $.ajax( {
237
- url: newspackBlocksModalCheckout.ajax_url,
238
- method: 'POST',
239
- data: {
240
- action: 'get_cart_product_summary',
241
- modal_checkout: 1,
242
- post_data: getCheckoutPostData(),
243
- },
244
- success: response => {
245
- if ( productSummaryRequest === request ) {
246
- setProductSummary( response );
247
- }
248
- },
249
- complete: () => {
250
- if ( productSummaryRequest === request ) {
251
- productSummaryRequest = false;
252
- }
253
- },
254
- } );
255
- productSummaryRequest = request;
256
- }
257
- function updateProductSummaryFromOrderReview() {
258
- const $cartItem = $( '.order-review-wrapper .woocommerce-checkout-review-order-table tr.cart_item' ).first();
259
- if ( ! $cartItem.length ) {
260
- return false;
261
- }
262
-
263
- const $productName = $cartItem.find( '.product-name' ).clone();
264
- $productName.find( '.product-quantity' ).remove();
265
- // Keep the DOM path text-only; the AJAX fallback is the HTML path because PHP runs it through wp_kses.
266
- const productName = $productName.text().replace( /\s+/g, ' ' ).trim();
267
- const productTotal = $cartItem.find( '.product-total' ).text().replace( /\s+/g, ' ' ).trim();
268
- if ( productName && productTotal ) {
269
- return setProductSummary( `${ productName }: ${ productTotal }`, false );
270
- }
271
- return false;
272
- }
273
- function syncProductSummary() {
274
- if ( updateProductSummaryFromOrderReview() ) {
275
- if ( productSummaryRequest ) {
276
- productSummaryRequest.abort();
277
- productSummaryRequest = false;
278
- }
279
- return;
280
- }
281
- requestUpdatedProductSummary();
282
- }
283
- $( document ).on( 'updated_checkout', syncProductSummary );
284
-
285
- /**
286
- * Toggle Transaction Details
287
- */
288
- $( document ).on( 'click', '#order_review_heading', function () {
289
- // Toggle the aria-expanded attribute.
290
- $( this ).attr( 'aria-expanded', function ( index, attr ) {
291
- return attr === 'false' ? 'true' : 'false';
292
- } );
293
- // Toggle the CSS class to show/hide the Transaction Details.
294
- $( '#after_customer_details' ).toggleClass( 'transaction-details-expanded' );
295
- } );
296
209
 
297
210
  /**
298
211
  * Get updated cart total to update the "Place Order" button.
@@ -748,7 +661,12 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils';
748
661
  }
749
662
  }
750
663
 
751
- $( '.order-details-summary' ).after( '<div id="checkout_details">' + html.join( '' ) + '</div>' );
664
+ // Anchor the summary to the hidden product-details carrier, falling back to
665
+ // #after_customer_details when the carrier isn't present (e.g. multi-item carts).
666
+ const $anchor = $( '#modal-checkout-product-details' ).length
667
+ ? $( '#modal-checkout-product-details' )
668
+ : $( '#after_customer_details' );
669
+ $anchor.after( '<div id="checkout_details">' + html.join( '' ) + '</div>' );
752
670
  }
753
671
 
754
672
  /**
@@ -24,7 +24,7 @@ import {
24
24
  getCheckoutData,
25
25
  getFormattedAmount,
26
26
  } from './utils';
27
- import { resolveCheckoutButtonForm, readCheckoutData } from './checkout-button-trigger';
27
+ import { resolveCheckoutButtonForm, readCheckoutData, applyContextFields } from './checkout-button-trigger';
28
28
  import { applyCtaAttribution } from '../shared/js/cta-attribution';
29
29
 
30
30
  const CLASS_PREFIX = newspackBlocksModal.newspack_class_prefix;
@@ -165,12 +165,6 @@ domReady( () => {
165
165
  onCheckoutPlaceOrderError( container, hideProcessingPaymentScreen );
166
166
 
167
167
  onCheckoutReady( container, () => {
168
- // Make sure the order summary renders the correct text.
169
- const summaryTextNode = productDetails?.querySelector( 'strong' );
170
- if ( summaryTextNode ) {
171
- summaryTextNode.textContent = checkoutData.price_summary;
172
- }
173
-
174
168
  // Display initial errors if any.
175
169
  if ( modalCheckout.initialErrors ) {
176
170
  const errorContainer = document.createElement( 'div' );
@@ -369,20 +363,10 @@ domReady( () => {
369
363
  const variationModal = [ ...variationModals ].find( modal => modal.dataset.productId === checkoutData.product_id );
370
364
  if ( variationModal ) {
371
365
  variationModal.querySelectorAll( `form[target="${ IFRAME_NAME }"]` ).forEach( singleVariationForm => {
372
- // Fill in the hidden params in the variation modal.
373
- [
374
- 'after_success_behavior',
375
- 'after_success_url',
376
- 'after_success_button_label',
377
- 'gate_post_id',
378
- 'newspack_popup_id',
379
- 'prompt_title',
380
- ].forEach( hiddenParam => {
381
- const existingInputs = singleVariationForm.querySelectorAll( 'input[name="' + hiddenParam + '"]' );
382
- if ( 0 === existingInputs.length ) {
383
- singleVariationForm.prepend( createHiddenInput( hiddenParam, checkoutData[ hiddenParam ] ) );
384
- }
385
- } );
366
+ // Fill in the hidden params in the variation modal. The picker is
367
+ // shared by every button for this product and is never reset, so
368
+ // this overwrites the previous open's context rather than adding to it.
369
+ applyContextFields( singleVariationForm, checkoutData );
386
370
 
387
371
  // Append the product data hidden inputs.
388
372
  const data = readCheckoutData( singleVariationForm );
@@ -444,11 +428,6 @@ domReady( () => {
444
428
  if ( shouldPromptRegistration() ) {
445
429
  ev.preventDefault();
446
430
 
447
- const priceSummary = checkoutData.price_summary;
448
- const content = priceSummary
449
- ? `<div class="order-details-summary ${ CLASS_PREFIX }__box ${ CLASS_PREFIX }__box--text-center"><p><strong>${ priceSummary }</strong></p></div>`
450
- : '';
451
-
452
431
  // Generate cart asynchroneously.
453
432
  const cartReq = generateCart( checkoutData );
454
433
 
@@ -499,7 +478,6 @@ domReady( () => {
499
478
  title: newspackBlocksModal.labels.register_modal_title,
500
479
  },
501
480
  },
502
- content,
503
481
  trigger: ev.submitter,
504
482
  closeOnSuccess: isModalCheckout,
505
483
  } );
@@ -42,27 +42,17 @@ if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_requir
42
42
  ?>
43
43
  <div class="order-review-wrapper hidden">
44
44
  <?php do_action( 'woocommerce_checkout_before_order_review_heading' ); ?>
45
- <button id="order_review_heading" aria-expanded="false" aria-controls="order_review_content" class="newspack-ui__button newspack-ui__button--ghost" type="button">
46
- <?php esc_html_e( 'Transaction details', 'newspack-blocks' ); ?>
47
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
48
- <path d="M15.9899 10.8888L12.0018 14.3071L8.01368 10.8888L8.98986 9.74988L12.0018 12.3315L15.0137 9.74988L15.9899 10.8888Z"/>
49
- </svg>
50
- </button>
51
- <div class="transaction-details-content">
52
- <div class="transaction-details-content-inner" id="order_review_content">
53
- <?php do_action( 'woocommerce_checkout_before_order_review' ); ?>
54
- <div id="order_review" class="woocommerce-checkout-review-order newspack-ui__box">
55
- <?php
56
- if ( wp_is_block_theme() && function_exists( 'woocommerce_order_review' ) ) {
57
- woocommerce_order_review();
58
- } else {
59
- do_action( 'woocommerce_checkout_order_review' );
60
- }
61
- ?>
62
- </div>
63
- <?php do_action( 'woocommerce_checkout_after_order_review' ); ?>
64
- </div>
45
+ <?php do_action( 'woocommerce_checkout_before_order_review' ); ?>
46
+ <div id="order_review" class="woocommerce-checkout-review-order newspack-ui__box">
47
+ <?php
48
+ if ( wp_is_block_theme() && function_exists( 'woocommerce_order_review' ) ) {
49
+ woocommerce_order_review();
50
+ } else {
51
+ do_action( 'woocommerce_checkout_order_review' );
52
+ }
53
+ ?>
65
54
  </div>
55
+ <?php do_action( 'woocommerce_checkout_after_order_review' ); ?>
66
56
  </div>
67
57
  <?php do_action( 'woocommerce_checkout_after_customer_details' ); ?>
68
58
  <button class="newspack-ui__button newspack-ui__button--secondary newspack-ui__button--wide" id="checkout_edit_billing" type="button"><?php echo esc_html( Modal_Checkout::get_modal_checkout_labels( 'checkout_edit_billing' ) ); ?></button>
@@ -12,6 +12,15 @@ if ( ! wc_coupons_enabled() ) { // @codingStandardsIgnoreLine.
12
12
  return;
13
13
  }
14
14
 
15
+ // Hide the coupon form when a coupon was auto-applied from a Checkout Button
16
+ // block — the reader didn't enter the code, so don't prompt them for one.
17
+ if (
18
+ class_exists( '\Newspack_Blocks\Modal_Checkout' )
19
+ && \Newspack_Blocks\Modal_Checkout::should_hide_coupon_form()
20
+ ) {
21
+ return;
22
+ }
23
+
15
24
  ?>
16
25
  <form class="modal_checkout_coupon woocommerce-form-coupon" method="post">
17
26
  <h3><?php esc_html_e( 'Apply a coupon code', 'newspack-blocks' ); ?></h3>
@@ -60,7 +60,7 @@ $checkout_data = Checkout_Data::get_checkout_data( $order );
60
60
  <path d="M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z"></path>
61
61
  </svg>
62
62
  </span>
63
- <p id="modal-checkout-product-details" data-checkout='<?php echo wp_json_encode( $checkout_data ); ?>'>
63
+ <p id="modal-checkout-product-details" <?php Checkout_Data::print_data_checkout_attr( $checkout_data ); ?>>
64
64
  <strong>
65
65
  <?php
66
66
  echo esc_html( Modal_Checkout::get_post_checkout_success_text() );
@@ -3,7 +3,7 @@
3
3
  'name' => 'automattic/newspack-blocks',
4
4
  'pretty_version' => 'dev-main',
5
5
  'version' => 'dev-main',
6
- 'reference' => 'f937e9f691dbdb938f7478a05bc22d51b771311f',
6
+ 'reference' => 'ab60934d72746a28196f59a043877a76f1168fa5',
7
7
  'type' => 'wordpress-plugin',
8
8
  'install_path' => __DIR__ . '/../../',
9
9
  'aliases' => array(),
@@ -13,7 +13,7 @@
13
13
  'automattic/newspack-blocks' => array(
14
14
  'pretty_version' => 'dev-main',
15
15
  'version' => 'dev-main',
16
- 'reference' => 'f937e9f691dbdb938f7478a05bc22d51b771311f',
16
+ 'reference' => 'ab60934d72746a28196f59a043877a76f1168fa5',
17
17
  'type' => 'wordpress-plugin',
18
18
  'install_path' => __DIR__ . '/../../',
19
19
  'aliases' => array(),
package/webpack.config.js CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
  const fs = require( 'fs' );
11
11
  const getBaseWebpackConfig = require( 'newspack-scripts/config/getWebpackConfig' );
12
+ const { findSourceFile, resolveSourceFile } = require( 'newspack-scripts/config/resolveSource' );
12
13
  const path = require( 'path' );
13
14
  const isDevelopment = process.env.NODE_ENV !== 'production';
14
15
  const blockListFile = process.env.npm_config_block_list || 'block-list.json';
@@ -20,26 +21,19 @@ const blockList = JSON.parse( fs.readFileSync( blockListFile ) );
20
21
  const editorSetup = path.join( __dirname, 'src', 'setup', 'editor' );
21
22
 
22
23
  function blockScripts( type, inputDir, blocks ) {
23
- return blocks.map( block => path.join( inputDir, 'blocks', block, `${ type }.js` ) ).filter( fs.existsSync );
24
+ return blocks.map( block => findSourceFile( path.join( inputDir, 'blocks', block, type ) ) ).filter( Boolean );
24
25
  }
25
26
 
26
27
  const blocksDir = path.join( __dirname, 'src', 'blocks' );
27
28
  const blocks = fs
28
29
  .readdirSync( blocksDir )
29
30
  .filter( block => isDevelopment || blockList.production.includes( block ) )
30
- .filter( block => fs.existsSync( path.join( __dirname, 'src', 'blocks', block, 'editor.js' ) ) );
31
+ .filter( block => findSourceFile( path.join( blocksDir, block, 'editor' ) ) );
31
32
 
32
33
  // Helps split up each block into its own folder view script
33
34
  const viewBlocksScripts = blocks.reduce( ( viewBlocks, block ) => {
34
- const pathToBlock = [ __dirname, 'src', 'blocks', block ];
35
- let viewScriptPath = path.join( ...pathToBlock, 'view.js' );
36
- let fileExists = fs.existsSync( viewScriptPath );
37
- if ( ! fileExists ) {
38
- // Try TS.
39
- viewScriptPath = path.join( ...pathToBlock, 'view.ts' );
40
- fileExists = fs.existsSync( viewScriptPath );
41
- }
42
- if ( fileExists ) {
35
+ const viewScriptPath = findSourceFile( path.join( blocksDir, block, 'view' ) );
36
+ if ( viewScriptPath ) {
43
37
  viewBlocks[ block + '/view' ] = viewScriptPath;
44
38
  }
45
39
  return viewBlocks;
@@ -56,7 +50,7 @@ const entry = {
56
50
  placeholder_blocks: placeholderBlocksScript,
57
51
  editor: editorScript,
58
52
  block_styles: blockStylesScript,
59
- modal: path.join( __dirname, 'src/modal-checkout/modal.js' ),
53
+ modal: resolveSourceFile( path.join( __dirname, 'src/modal-checkout/modal' ) ),
60
54
  modalCheckout: path.join( __dirname, 'src/modal-checkout' ),
61
55
  frequencyBased: path.join( __dirname, 'src/blocks/donate/frequency-based' ),
62
56
  tiersBased: path.join( __dirname, 'src/blocks/donate/tiers-based' ),