-
-
-
-
-
-
get_name() ); ?>
-
- get_available_variations( 'objects' );
- foreach ( $variations as $variation ) {
- $name = wc_get_formatted_variation( $variation, true );
- $price = $variation->get_price_html();
- $description = $variation->get_description();
- ?>
-
+
+
+
+
+
+ $variations = $product->get_available_variations( 'objects' );
+ foreach ( $variations as $variation ) {
+ $name = wc_get_formatted_variation( $variation, true );
+ $price = $variation->get_price_html();
+ $description = $variation->get_description();
+ ?>
+
+
+
+
[
+ 'billing_details' => __( 'Billing details', 'newspack-blocks' ),
+ 'shipping_details' => __( 'Shipping details', 'newspack-blocks' ),
+ ],
+ ]
+ );
wp_enqueue_style(
'newspack-blocks-modal-checkout',
plugins_url( 'dist/modalCheckout.css', \NEWSPACK_BLOCKS__PLUGIN_FILE ),
@@ -515,9 +549,9 @@ public static function wc_get_template( $located, $template_name ) {
}
$custom_templates = [
- 'checkout/form-checkout.php' => 'src/modal-checkout/templates/checkout-form.php',
- 'checkout/form-billing.php' => 'src/modal-checkout/templates/billing-form.php',
+ 'checkout/form-checkout.php' => 'src/modal-checkout/templates/form-checkout.php',
'checkout/thankyou.php' => 'src/modal-checkout/templates/thankyou.php',
+ 'checkout/form-coupon.php' => 'src/modal-checkout/templates/form-coupon.php',
// Replace the login form with the order summary if using the modal checkout. This is
// for the case where the reader used an existing email address.
'global/form-login.php' => 'src/modal-checkout/templates/thankyou.php',
@@ -528,13 +562,6 @@ public static function wc_get_template( $located, $template_name ) {
$located = NEWSPACK_BLOCKS__PLUGIN_DIR . $custom_template;
}
}
-
- // This is for the initial display – the markup will be refetched on cart updates (e.g. applying a coupon).
- // Then it'd be handled by the `woocommerce_update_order_review_fragments` filter.
- if ( 'checkout/review-order.php' === $template_name && ! self::should_show_order_details() ) {
- $located = NEWSPACK_BLOCKS__PLUGIN_DIR . 'src/modal-checkout/templates/empty-order-details.php';
- }
-
return $located;
}
@@ -552,43 +579,6 @@ public static function show_admin_bar( $show ) {
return false;
}
- /**
- * Check the nonce for the edit billing request.
- *
- * @return bool
- */
- private static function validate_edit_billing_request() {
- if ( ! isset( $_REQUEST['newspack_blocks_edit_billing_nonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- return false;
- }
- if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['newspack_blocks_edit_billing_nonce'] ), 'newspack_blocks_edit_billing' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- return false;
- }
- return true;
- }
-
- /**
- * Modify WC checkout field value.
- *
- * @param null $value Value.
- * @param string $input Input name.
- *
- * @return string|null Value or null if unaltered.
- */
- public static function woocommerce_checkout_get_value( $value, $input ) {
- if ( ! self::is_modal_checkout() ) {
- return null;
- }
- $valid_request = self::validate_edit_billing_request(); // This performs nonce verification.
- if ( ! $valid_request ) {
- return null;
- }
- if ( isset( $_REQUEST[ $input ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $value = sanitize_text_field( wp_unslash( $_REQUEST[ $input ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- }
- return $value;
- }
-
/**
* Modify fields for modal checkout.
*
@@ -600,6 +590,11 @@ public static function woocommerce_checkout_fields( $fields ) {
if ( ! self::is_modal_checkout() ) {
return $fields;
}
+ $cart = \WC()->cart;
+ // Don't modify fields if shipping is required.
+ if ( $cart->needs_shipping_address() ) {
+ return $fields;
+ }
/**
* Temporarily use the same fields as the donation checkout.
*
@@ -620,64 +615,6 @@ public static function woocommerce_checkout_fields( $fields ) {
return $fields;
}
- /**
- * Get the prefilled values for billing fields.
- *
- * @return array
- */
- public static function get_prefilled_fields() {
- $checkout = \WC()->checkout();
- $fields = $checkout->get_checkout_fields( 'billing' );
- $customer = new \WC_Customer( get_current_user_id() );
- $customer_fields = $customer->get_billing();
- // If the user is logged in and there's no billing email, use the user's email.
- if ( is_user_logged_in() && empty( $customer_fields['email'] ) ) {
- $customer_fields['email'] = $customer->get_email();
- }
- $valid_request = self::validate_edit_billing_request();
- $prefilled_fields = [];
- foreach ( $fields as $key => $field ) {
- $key = str_replace( 'billing_', '', $key );
- if ( $valid_request && isset( $_REQUEST[ 'billing_' . $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $value = sanitize_text_field( wp_unslash( $_REQUEST[ 'billing_' . $key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- } elseif ( isset( $customer_fields[ $key ] ) ) {
- $value = $customer_fields[ $key ];
- }
- $prefilled_fields[ $key ] = $value;
- }
- return $prefilled_fields;
- }
-
- /**
- * Whether the current checkout session has all required billing fields filled.
- *
- * @return bool
- */
- public static function has_filled_required_fields() {
- $checkout = \WC()->checkout();
- $fields = $checkout->get_checkout_fields( 'billing' );
- $required = array_filter(
- $fields,
- function( $field ) {
- return isset( $field['required'] ) && $field['required'];
- }
- );
- $required_keys = array_keys( $required );
- $customer_fields = self::get_prefilled_fields();
- $is_request = self::validate_edit_billing_request();
- foreach ( $required_keys as $key ) {
- $key = str_replace( 'billing_', '', $key );
- if ( empty( $customer_fields[ $key ] ) ) {
- if ( $is_request ) {
- /* translators: %s: field name */
- wc_add_notice( sprintf( __( '%s is a required field.', 'newspack-blocks' ), $fields[ 'billing_' . $key ]['label'] ), 'error' );
- }
- return false;
- }
- }
- return true;
- }
-
/**
* Whether to show order details table.
*
@@ -700,27 +637,6 @@ public static function should_show_order_details() {
return false;
}
- /**
- * Customize order review fragments on cart updates.
- *
- * @param array $fragments Fragments.
- *
- * @return array
- */
- public static function order_review_fragments( $fragments ) {
- if ( isset( $_POST['post_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
- parse_str( \sanitize_text_field( \wp_unslash( $_POST['post_data'] ) ), $post_data ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
- }
- if ( ! isset( $post_data['modal_checkout'] ) ) {
- return $fragments;
- }
- if ( ! self::should_show_order_details() ) {
- // Render an empty table so WC knows how to replace it on updates.
- $fragments['.woocommerce-checkout-review-order-table'] = '
';
- }
- return $fragments;
- }
-
/**
* Render markup at the end of the "thank you" view.
*
@@ -967,10 +883,113 @@ public static function is_modal_checkout() {
* @param string $text The button text.
*/
public static function order_button_text( $text ) {
- if ( self::is_modal_checkout() && method_exists( 'Newspack\Donations', 'is_donation_cart' ) && \Newspack\Donations::is_donation_cart() ) {
- return __( 'Donate now', 'newspack-blocks' );
+ if ( ! self::is_modal_checkout() ) {
+ return $text;
+ }
+ $cart = \WC()->cart;
+ if ( ! $cart || $cart->is_empty() ) {
+ return $text;
+ }
+ return sprintf(
+ // Translators: %s is the price.
+ __( 'Complete transaction: %s', 'newspack-blocks' ),
+ esc_html( wp_strip_all_tags( \WC()->cart->get_total() ) )
+ );
+ }
+
+ /**
+ * Render before the checkout form.
+ *
+ * This will render the order summary card.
+ */
+ public static function render_before_checkout_form() {
+ if ( ! self::is_modal_checkout() ) {
+ return;
+ }
+ $cart = \WC()->cart;
+ if ( 1 !== $cart->get_cart_contents_count() ) {
+ return;
+ }
+ ?>
+
+ get_cart() as $cart_item_key => $cart_item ) :
+ $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
+ if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_checkout_cart_item_visible', true, $cart_item, $cart_item_key ) ) :
+ ?>
+
+ get_name(), $cart_item, $cart_item_key ) . ': '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ ?>
+ get_product_subtotal( $_product, $cart_item['quantity'] ), $cart_item, $cart_item_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+
+
+
+
+
+
+
+
+ span {
- color: inherit;
- font-size: clamp( 1.75rem, 1.75rem + ( ( 1vw - 0.48rem ) * 0.962 ), 2.25rem );
- font-weight: bold;
- line-height: 1.434;
- }
- .subscription-details {
- font-size: 1rem;
- font-weight: normal;
- }
+ padding: 24px;
+ h3 {
+ font-size: 16px;
+ margin: 0 0 8px;
+ }
+ p {
+ margin: 0 0 16px;
+ }
+ .woocommerce-message,
+ .woocommerce-error,
+ .woocommerce-info {
+ border-radius: 6px;
+ svg {
+ display: none;
+ }
+ li {
+ font-size: 14px;
}
}
- #order-details-wrapper.hidden {
- display: none;
+ .woocommerce-info,
+ .woocommerce-message {
+ background-color: #f6f7f7;
+ color: #757575;
}
- .woocommerce-NoticeGroup {
- margin-top: 16px;
+ .wc-block-components-notice-banner__content {
+ font-size: 14px;
}
- .woocommerce-checkout-review-order-table {
- margin-top: 16px;
- th,
- td {
- font-size: 0.8rem;
+ // Avoid duplicate styling when block components notice is inside the legacy
+ // notice-group.
+ .woocommerce-NoticeGroup .wc-block-components-notice-banner {
+ background: transparent;
+ border: 0;
+ padding: 0 !important;
+ margin: 0;
+ color: inherit;
+ }
+ input[type='text'],
+ input[type='email'],
+ input[type='tel'],
+ input[type='number'],
+ input[type='password'],
+ input[type='submit'],
+ select,
+ textarea {
+ border-radius: 6px;
+ padding: 12px 16px;
+ font-size: 14px;
+ line-height: 24px;
+ border-color: #ddd;
+ &:focus {
+ box-shadow: 0 0 0 1px #36f inset;
}
- &.empty {
- display: none;
+ }
+ // Custom styles for Select2.
+ .select2-container .select2-selection--single .select2-selection__rendered {
+ padding-left: 16px;
+ padding-right: 16px;
+ font-size: 14px;
+ }
+ .select2-results {
+ font-size: 14px;
+ }
+ .select2-dropdown,
+ .woocommerce-checkout .select2-container--focus .select2-selection,
+ .select2-container--default .select2-selection--single {
+ border-color: #ddd;
+ }
+ input[type='checkbox'] {
+ border-radius: 6px;
+ }
+ input[type='checkbox']:checked {
+ background-color: var( --newspack-theme-color-primary );
+ border-color: var( --newspack-theme-color-primary );
+ }
+ // Inline input validation
+ .form-row .woocommerce-error {
+ padding: 0;
+ margin: 0.25rem 0 0;
+ color: #cc1818;
+ background: transparent;
+ font-size: 14px;
+ }
+ .button {
+ font-size: 14px;
+ background-color: transparent;
+ color: inherit;
+ border: 1px solid #ddd;
+ }
+ .order-details-summary {
+ background: #f6f7f7;
+ text-align: center;
+ padding: 24px;
+ border-radius: 6px;
+ margin: 0 0 24px;
+ h2 {
+ font-size: 16px;
+ line-height: 24px;
margin: 0;
- padding: 0;
}
}
- .woocommerce-billing-fields {
- margin-bottom: 16px;
- }
- .checkout-billing-summary {
- border-bottom: 1px solid colors.$color__border;
- padding-bottom: 32px;
- &__grid {
- align-items: baseline;
+ #ship-to-different-address {
+ label {
display: flex;
- flex-wrap: wrap;
- gap: 8px;
- justify-content: space-between;
- }
- p {
- font-size: clamp( 0.75rem, 0.75rem + ( ( 1vw - 0.48rem ) * 0.481 ), 1rem );
- margin: 16px 0 0;
- }
- .edit-billing-link {
- color: colors.$color__text-light;
- font-size: clamp( 0.75rem, 0.75rem + ( ( 1vw - 0.48rem ) * 0.481 ), 1rem );
- display: inline-block;
- text-decoration: underline;
- &:focus,
- &:hover {
- color: inherit;
+ align-items: center;
+ margin: 0 0 24px;
+ span::before,
+ span::after {
+ display: none;
+ }
+ input[type='checkbox'] {
+ display: inline-grid;
+ margin-right: 8px;
}
}
}
- .woocommerce-account-fields .create-account {
- border-bottom: 1px solid colors.$color__border;
- padding-bottom: 32px;
-
- .woocommerce-password-strength,
- .woocommerce-password-hint {
- color: colors.$color__error;
- margin-top: 16px;
+ #customer_details { /* stylelint-disable-line */
+ .woocommerce-billing-fields {
+ p {
+ margin: 8px 0 24px;
+ billing_address_1_field { /* stylelint-disable-line */
+ margin-bottom: 8px;
+ }
+ }
+ label {
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ }
}
}
-
- form {
- margin: 0;
- #wc-stripe-payment-request-button-separator {
- margin: 0;
- }
- .woocommerce-terms-and-conditions-wrapper {
- margin: 16px 0;
+ #payment {
+ .wc_payment_methods {
+ margin: 0 0 24px;
}
- h3 {
- font-size: 1.37rem;
- margin: 32px 0 0;
+ .wc_payment_method {
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ padding: 16px;
+ margin: 0 0 16px;
+ font-size: 14px;
+ line-height: 20px;
+ > label {
+ font-weight: 600;
+ }
+ &.selected {
+ background-color: #f5fdff;
+ border-color: #36f;
+ }
+ input.input-radio[name='payment_method'] + label::before {
+ box-shadow: 0 0 0 1px #ddd;
+ }
+ input.input-radio[name='payment_method']:checked + label::before {
+ background: #36f;
+ box-shadow: 0 0 0 1px #36f;
+ }
+ .payment_box {
+ font-size: 14px;
+ line-height: 20px;
+ padding: 0;
+ background: transparent;
+ margin: 16px 0 0;
+ }
+ > label:first-of-type {
+ margin: 0;
+ }
}
- p {
- margin: 16px 0;
+ // Hide radio button if there's only one payment method.
+ .wc_payment_method:first-child:nth-last-child( 1 ) {
+ label:first-of-type::before {
+ display: none;
+ }
}
- .select2-container .select2-selection--single {
+ .wc-saved-payment-methods {
margin: 0;
- }
- #payment button#place_order { /* stylelint-disable-line */
- margin-bottom: 0;
- }
- .checkout-billing button[type='submit'],
- button[name='woocommerce_checkout_place_order'],
- button.modal-continue {
- display: block;
- width: 100%;
- }
- }
- .form-row-first,
- .form-row-last {
- width: calc( 50% - 8px );
- }
- .form-row-wide + .form-row-first,
- .form-row-wide + .form-row-first + .form-row-last,
- .form-row-last + .form-row-first {
- margin-top: 0;
- }
- .select2-container--default {
- .select2-selection--single {
- border-color: colors.$color__border;
- border-radius: 0;
- height: 44px;
- .select2-selection__rendered {
- color: inherit;
- line-height: 44px;
- }
- .select2-selection__arrow {
- height: 42px;
+ padding: 0 0 0 8px;
+ li input[type='radio'] {
+ margin-right: 8px;
}
}
- }
- .wc_payment_method .payment_box {
- background: colors.$color__gray-lighter;
- border-radius: 3px;
- padding: 16px;
- p {
- margin-top: 0;
+ .wc-payment-form {
+ margin: 16px 0 0;
}
- p.woocommerce-SavedPaymentMethods-saveNew,
- p.newspack-cover-fees {
+ .newspack-cover-fees,
+ .woocommerce-SavedPaymentMethods-saveNew {
display: flex;
- margin: 8px 0;
- align-items: flex-start;
- input[type='checkbox'] {
+ margin: 16px 0 0;
+ input {
margin-right: 8px;
}
}
}
- .wc-saved-payment-methods {
- padding: 0;
- }
- .woocommerce-error {
- border-radius: 3px;
- margin: 0;
- }
- .woocommerce-notices-wrapper:not( :empty ) {
- margin-bottom: 32px;
- }
- .woocommerce-privacy-policy-text {
- color: colors.$color__text-light;
- p {
- font-size: clamp( 0.75rem, 0.75rem + ( ( 1vw - 0.48rem ) * 0.481 ), 1rem );
+ #order_review { /* stylelint-disable-line */
+ margin: 0 0 24px;
+ padding: 16px 24px;
+ border-radius: 6px;
+ background: #f6f7f7;
+ table {
+ font-size: 14px; // Prevent relative font size
+ line-height: 24px;
+ border: 0;
margin: 0;
+ th,
+ td {
+ font-size: 14px; // Prevent relative font size
+ padding: 4px 0;
+ vertical-align: top;
+ border-color: #ddd;
+ > * {
+ white-space: nowrap;
+ }
+ }
+ th {
+ background-color: inherit;
+ font-weight: 600;
+ text-align: left;
+ }
+ td:last-child {
+ width: 1%;
+ }
+ tr.cart-subtotal {
+ th {
+ font-weight: normal;
+ }
+ }
+ tr.recurring-totals {
+ th {
+ padding-top: 24px;
+ }
+ }
+ tfoot tr:last-child {
+ th,
+ td {
+ border-bottom: 0;
+ }
+ }
}
}
- .woocommerce-thankyou-order-received {
- align-items: center;
- border-bottom: 1px solid colors.$color__border;
- display: flex;
- flex-direction: column;
- justify-content: center;
- margin: 0 0 32px;
- padding: 0 0 32px;
- &::before {
- animation: bounce 250ms ease-in;
- animation-delay: 1s;
- animation-fill-mode: forwards;
- background-color: colors.$color__success;
- background-image: url( "data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.7 7.1l-6.3 8.5-3.3-2.5-.9 1.2 4.5 3.4L17.9 8z' fill='white'/%3E%3C/svg%3E" );
- background-position: 50% 50%;
- background-repeat: no-repeat;
- background-size: 24px;
- border-radius: 50%;
- content: '';
- display: block;
- height: 40px;
- margin-bottom: 16px;
- padding: 8px;
- transform: scale( 0 );
- width: 40px;
- }
- + .woocommerce-info {
- display: none; // Hide the "Please log in to view this order" message on the thank you page.
- }
- }
- .woocommerce-form-login-toggle {
- display: none; // Hide the "Returning customer? Click here to login" message.
- }
- .woocommerce-order-overview {
- color: colors.$color__text-light;
- list-style: none;
- margin: 16px 0 0;
- padding: 0;
- }
-
- .blockOverlay {
- align-items: center;
- display: flex;
- inset: 0;
- justify-content: center;
- position: fixed !important;
- &::after {
- animation: spin 1s infinite linear;
- border: 2px solid colors.$color__background-body;
- border-top-color: colors.$color__text-light;
- border-radius: 50%;
- content: '';
- display: block;
- height: 25px;
- width: 25px;
+ .woocommerce-terms-and-conditions-wrapper {
+ margin: 0;
+ .woocommerce-privacy-policy-text {
+ font-size: 14px;
+ line-height: 20px;
+ color: #757575;
}
}
-}
-
-.newspack-modal-newsletters {
- margin-top: 20px;
- padding-top: 22px;
- border-top: 1px solid colors.$color__border;
- &__info {
- margin-bottom: 35px;
- span {
- color: colors.$color__text-light;
+ .woocommerce-form-coupon {
+ margin: 0 0 24px;
+ p {
+ display: flex;
+ align-items: center;
+ input[type='text'] {
+ margin-right: 8px;
+ flex-grow: 1;
+ }
}
}
- &__list-item {
+ #checkout_details { /* stylelint-disable-line */
display: flex;
- margin-bottom: 18px;
- border: 1px solid colors.$color__border;
- border-radius: 6px;
- b {
- display: block;
- margin-bottom: 2px;
+ margin: 0 0 24px;
+ > * {
+ flex: 1 1 100%;
+ gap: 16px;
+ }
+ p {
+ font-size: 14px;
+ margin: 0;
}
}
- label {
- flex: 1;
- padding: 12px;
- cursor: pointer;
- }
- input[type='checkbox'] {
- padding: 10px;
- margin-top: 18px;
- margin-right: 19px;
- margin-left: 20px;
- border-radius: 5px;
- }
- input[type='submit'],
- &__button {
- margin-top: 25px;
+ #checkout_continue, #place_order { /* stylelint-disable-line */
+ background-color: var( --newspack-theme-color-primary );
+ color: #fff; /* TODO: Fix dynamic contrast */
+ font-size: 14px;
+ font-weight: 600;
+ display: block;
width: 100%;
- padding: 22px !important;
- font-size: 1em !important;
+ border: 0;
}
-}
-
-@keyframes bounce {
- 0% {
- transform: scale( 0 );
+ #checkout_back { /* stylelint-disable-line */
+ border: 0;
+ width: 100%;
+ background: transparent;
+ color: inherit;
}
- 90% {
- transform: scale( 1.4 );
+ /* Remove support for "Allow customers to log into an existing account during checkout" */
+ .woocommerce-form-login-toggle {
+ display: none;
}
- 100% {
- transform: scale( 1 );
+ /* Hide reCAPTCHA badge */
+ .grecaptcha-badge {
+ visibility: hidden !important;
}
}
diff --git a/src/modal-checkout/index.js b/src/modal-checkout/index.js
index 5ffcbae1c..8801fff74 100644
--- a/src/modal-checkout/index.js
+++ b/src/modal-checkout/index.js
@@ -1,36 +1,397 @@
+/* globals newspackBlocksModalCheckout, jQuery, wc_checkout_params */
/**
* Style dependencies
*/
import './checkout.scss';
-/**
- * Specify a function to execute when the DOM is fully loaded.
- *
- * @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dom-ready/
- *
- * @param {Function} callback A function to execute after the DOM is ready.
- * @return {void}
- */
-function domReady( callback ) {
- if ( typeof document === 'undefined' ) {
+( function ( $ ) {
+ if ( ! $ ) {
return;
}
- if (
- document.readyState === 'complete' || // DOMContentLoaded + Images/Styles/etc loaded, so we call directly.
- document.readyState === 'interactive' // DOMContentLoaded fires at this point, so we call directly.
- ) {
- return void callback();
+
+ const readyEvent = new CustomEvent( 'checkout-ready' );
+
+ function getEventHandlers( element, event ) {
+ const events = $._data( element, 'events' );
+ if ( ! events ) {
+ return [];
+ }
+ if ( ! event ) {
+ return events;
+ }
+ return $._data( element, 'events' )[ event ];
}
- // DOMContentLoaded has not fired yet, delay callback until then.
- document.addEventListener( 'DOMContentLoaded', callback );
-}
-
-domReady( () => {
- const continueButton = document.querySelector( '.modal-continue' );
- if ( continueButton ) {
- continueButton.addEventListener( 'click', () => {
- const form = document.querySelector( '.checkout' );
- form.submit();
- } );
+
+ function clearNotices() {
+ $(
+ '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message, .wc-block-components-notice-banner'
+ ).remove();
}
-} );
+
+ $( document.body ).on( 'init_checkout', function () {
+ let originalFormHandlers = [];
+
+ const $form = $( 'form.checkout' );
+
+ if ( ! $form.length ) {
+ return;
+ }
+
+ const $coupon = $( 'form.checkout_coupon' );
+ const $checkout_continue = $( '#checkout_continue' );
+ const $customer_details = $( '#customer_details' );
+ const $after_customer_details = $( '#after_customer_details' );
+ const $place_order_button = $( '#place_order' );
+
+ /**
+ * Ensure coupon form is shown after removing a coupon.
+ */
+ $( document.body ).on( 'removed_coupon_in_checkout', function () {
+ $coupon.show();
+ clearNotices();
+ } );
+
+ /**
+ * Handle styling update for selected payment method.
+ */
+ function handlePaymentMethodSelect() {
+ const selected = $( 'input[name="payment_method"]:checked' ).val();
+ $( '.wc_payment_method' ).removeClass( 'selected' );
+ $( '.wc_payment_method.payment_method_' + selected ).addClass( 'selected' );
+ }
+ $( 'input[name="payment_method"]' ).change( handlePaymentMethodSelect );
+ $( document ).on( 'payment_method_selected', handlePaymentMethodSelect );
+ $( document ).on( 'updated_checkout', handlePaymentMethodSelect );
+ handlePaymentMethodSelect();
+
+ /**
+ * Toggle "Payment info" title if there's no money transaction.
+ */
+ $( document ).on( 'updated_checkout', function () {
+ if ( $( '#payment .wc_payment_methods' ).length ) {
+ $( '#after_customer_details > h3' ).show();
+ } else {
+ $( '#after_customer_details > h3' ).hide();
+ }
+ } );
+
+ /**
+ * Initialize the 2-step checkout form.
+ */
+ if ( $checkout_continue.length ) {
+ setEditingDetails( true );
+ // Perform initial validation so it can skip 1st step if possible.
+ validateForm( true, () => {
+ // Attach handler to "Back" button.
+ $form.on( 'click', '#checkout_back', function ( ev ) {
+ ev.preventDefault();
+ setEditingDetails( true );
+ } );
+ setReady();
+ } );
+ } else {
+ setReady();
+ }
+
+ /**
+ * Handle form errors while editing billing/shiping fields.
+ *
+ * @param {string} error_message
+ */
+ function handleFormError( error_message ) {
+ clearNotices();
+
+ $form.removeClass( 'processing' ).unblock();
+ $form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' ).trigger( 'blur' );
+
+ let $fieldToFocus = false;
+
+ const genericErrors = [];
+
+ /**
+ * If a field is found, append the error to it. Otherwise, add it to the
+ * generic errors array.
+ *
+ * @param {jQuery} $error
+ */
+ const handleErrorItem = $error => {
+ const $field = $( '#' + $error.data( 'id' ) + '_field' );
+ if ( $field?.length ) {
+ if ( ! $fieldToFocus ) {
+ $fieldToFocus = $field;
+ }
+ $field.addClass( 'woocommerce-invalid' ).removeClass( 'woocommerce-valid' );
+ $field.append( '
' + $error.text() + '' );
+ $error.remove();
+ } else {
+ if ( ! $error.is( 'li' ) ) {
+ $error = $( '
' ).append( $error );
+ }
+ genericErrors.push( $error );
+ }
+ };
+
+ /**
+ * The new "wc-block-components-notice-banner" does not provide a
+ * of errors when only one field failed validation.
+ */
+ if ( ! error_message.includes( '
' ).append(
+ $( '
' ).append( genericErrors )
+ )
+ );
+ window.scroll( { top: 0, left: 0, behavior: 'smooth' } );
+ }
+
+ if ( $fieldToFocus?.length ) {
+ window.scroll( { top: $fieldToFocus.offset().top - 100, left: 0, behavior: 'smooth' } );
+ $fieldToFocus.find( 'input.input-text, select, input:checkbox' ).trigger( 'focus' );
+ }
+
+ $( document.body ).trigger( 'update_checkout' );
+ $( document.body ).trigger( 'checkout_error', [ error_message ] );
+ }
+
+ /**
+ * Set the checkout as ready so the modal can resolve the loading state.
+ */
+ function setReady() {
+ const container = document.querySelector( '#newspack_modal_checkout' );
+ container.checkoutReady = true;
+ container.dispatchEvent( readyEvent );
+ }
+
+ /**
+ * Handle form 1st step submission.
+ *
+ * @param {Event} ev
+ */
+ function handleFormSubmit( ev ) {
+ ev.preventDefault();
+ validateForm();
+ }
+
+ /**
+ * Set the checkout state as editing billing/shipping fields or not.
+ *
+ * @param {boolean} isEditingDetails
+ */
+ function setEditingDetails( isEditingDetails ) {
+ // Scroll to top.
+ window.scroll( { top: 0, left: 0, behavior: 'smooth' } );
+ // Update checkout.
+ $( document.body ).trigger( 'update_checkout' );
+ clearNotices();
+ // Clear checkout details.
+ $( '#checkout_details' ).remove();
+ if ( isEditingDetails ) {
+ if ( $coupon.length ) {
+ $coupon.hide();
+ }
+ $customer_details.show();
+ $after_customer_details.hide();
+ $place_order_button.attr( 'disabled', 'disabled' );
+ $customer_details.find( 'input' ).first().focus();
+ // Remove default form event handlers.
+ originalFormHandlers = getEventHandlers( $form[ 0 ], 'submit' ).slice( 0 );
+ originalFormHandlers.forEach( handler => {
+ $form.off( 'submit', handler.handler );
+ } );
+ $form.on( 'submit', handleFormSubmit );
+ } else {
+ if ( $coupon.length ) {
+ $coupon.show();
+ }
+ $customer_details.hide();
+ $after_customer_details.show();
+ $place_order_button.removeAttr( 'disabled' );
+ renderCheckoutDetails();
+ // Store event handlers.
+ $form.off( 'submit', handleFormSubmit );
+ originalFormHandlers.forEach( handler => {
+ $form.on( 'submit', handler.handler );
+ } );
+ }
+ $form.triggerHandler( 'editing_details', [ isEditingDetails ] );
+ }
+
+ /**
+ * Render the checkout billing/shipping details summary HTML.
+ */
+ function renderCheckoutDetails() {
+ $( '#checkout_details' ).remove();
+ const data = {};
+ $form.serializeArray().forEach( item => {
+ data[ item.name ] = item.value;
+ } );
+
+ const html = [];
+ html.push( '
' );
+ html.push( '
' + newspackBlocksModalCheckout.labels.billing_details + '
' );
+ if ( data.billing_first_name || data.billing_last_name ) {
+ html.push( '
' + data.billing_first_name + ' ' + data.billing_last_name + '
' );
+ }
+ if ( data.billing_company ) {
+ html.push( '
' + data.billing_company + '
' );
+ }
+ let billingAddress = '';
+ if ( data.billing_address_1 || data.billing_address_2 ) {
+ billingAddress = '
';
+ if ( data.billing_address_1 ) {
+ billingAddress += data.billing_address_1;
+ }
+ if ( data.billing_address_2 ) {
+ billingAddress += ' ' + data.billing_address_2;
+ }
+ billingAddress += '
';
+ if ( data.billing_city ) {
+ billingAddress += data.billing_city;
+ }
+ if ( data.billing_state ) {
+ billingAddress += ', ' + data.billing_state;
+ }
+ if ( data.billing_postcode ) {
+ billingAddress += ' ' + data.billing_postcode;
+ }
+ billingAddress += '
';
+ if ( data.billing_country ) {
+ billingAddress += data.billing_country;
+ }
+ }
+ html.push( billingAddress );
+ if ( data.billing_email ) {
+ html.push( '
' + data.billing_email + '
' );
+ }
+ html.push( '
' ); // Close billing-details.
+
+ // Shipping details.
+ if ( data.hasOwnProperty( 'shipping_address_1' ) ) {
+ html.push( '
' );
+ html.push( '
' + newspackBlocksModalCheckout.labels.shipping_details + '
' );
+ let shippingAddress = '';
+ if ( ! data.ship_to_different_address ) {
+ shippingAddress = billingAddress;
+ } else {
+ shippingAddress = '
';
+ if ( data.shipping_address_1 ) {
+ shippingAddress += data.shipping_address_1;
+ }
+ if ( data.shipping_address_2 ) {
+ shippingAddress += ' ' + data.shipping_address_2;
+ }
+ shippingAddress += '
';
+ if ( data.shipping_city ) {
+ shippingAddress += data.shipping_city;
+ }
+ if ( data.shipping_state ) {
+ shippingAddress += ', ' + data.shipping_state;
+ }
+ if ( data.shipping_postcode ) {
+ shippingAddress += ' ' + data.shipping_postcode;
+ }
+ shippingAddress += '
';
+ if ( data.shipping_country ) {
+ shippingAddress += data.shipping_country;
+ }
+ }
+ html.push( shippingAddress );
+ html.push( '
' ); // Close shipping-details.
+ }
+ $( '.order-details-summary' ).after(
+ '
' + html.join( '' ) + '
'
+ );
+ }
+
+ /**
+ * Validate the checkout form using Woo's "update_totals" ajax request.
+ *
+ * @param {boolean} silent Whether to show errors or not.
+ * @param {Function} cb Callback function.
+ */
+ function validateForm( silent = false, cb = () => {} ) {
+ if ( $form.is( '.processing' ) ) {
+ return false;
+ }
+ $form.addClass( 'processing' ).block( {
+ message: null,
+ overlayCSS: {
+ background: '#fff',
+ opacity: 0.6,
+ },
+ } );
+ const serializedForm = $form.serializeArray();
+ // Add 'update totals' parameter so it just performs validation.
+ serializedForm.push( { name: 'woocommerce_checkout_update_totals', value: '1' } );
+ // Ajax request.
+ $.ajax( {
+ type: 'POST',
+ url: wc_checkout_params.checkout_url,
+ data: serializedForm,
+ dataType: 'html',
+ success: response => {
+ let result;
+ try {
+ result = JSON.parse( response );
+ } catch ( e ) {
+ result = {
+ messages:
+ '
' +
+ wc_checkout_params.i18n_checkout_error +
+ '
',
+ };
+ }
+
+ // Reload page
+ if ( ! silent && true === result.reload ) {
+ window.location.reload();
+ return;
+ }
+
+ // Unblock form.
+ $form.removeClass( 'processing' ).unblock();
+
+ // Result will always be 'failure' from the server. We'll check for
+ // 'messages' in the response to see if it was successful.
+ const success = ! result.messages;
+ if ( success ) {
+ setEditingDetails( false );
+ } else if ( ! silent ) {
+ if ( result.messages ) {
+ handleFormError( result.messages );
+ } else {
+ handleFormError(
+ '
' +
+ wc_checkout_params.i18n_checkout_error +
+ '
'
+ );
+ }
+ }
+ cb( result );
+ },
+ error: ( jqXHR, textStatus, errorThrown ) => {
+ let messages = '';
+ if ( ! silent ) {
+ messages =
+ '
' +
+ ( errorThrown || wc_checkout_params.i18n_checkout_error ) +
+ '
';
+ handleFormError( messages );
+ }
+ cb( { messages } );
+ },
+ } );
+ }
+ } );
+} )( jQuery );
diff --git a/src/modal-checkout/modal.js b/src/modal-checkout/modal.js
index ed36dc108..45525cb98 100644
--- a/src/modal-checkout/modal.js
+++ b/src/modal-checkout/modal.js
@@ -40,7 +40,7 @@ function closeCheckout() {
iframeResizeObserver.disconnect();
}
Array.from( document.querySelectorAll( '.newspack-blocks-modal' ) ).forEach( el => {
- el.style.display = 'none';
+ el.classList.remove( 'open' );
if ( el.overlayId && window.newspackReaderActivation?.overlays ) {
window.newspackReaderActivation?.overlays.remove( el.overlayId );
}
@@ -129,7 +129,7 @@ domReady( () => {
form.addEventListener( 'submit', ev => {
const formData = new FormData( form );
// Clear any open variation modal.
- variationModals.forEach( variationModal => ( variationModal.style.display = 'none' ) );
+ variationModals.forEach( variationModal => variationModal.classList.remove( 'open' ) );
// Trigger variation modal if variation is not selected.
if ( formData.get( 'is_variable' ) && ! formData.get( 'variation_id' ) ) {
const variationModal = [ ...variationModals ].find(
@@ -155,18 +155,17 @@ domReady( () => {
// Open the variations modal.
ev.preventDefault();
document.body.classList.add( 'newspack-modal-checkout-open' );
- variationModal.style.display = 'block';
+ variationModal.classList.add( 'open' );
return;
}
}
// Continue with checkout modal.
spinner.style.display = 'flex';
- modalCheckout.style.display = 'block';
+ modalCheckout.classList.add( 'open' );
document.body.classList.add( 'newspack-modal-checkout-open' );
if ( window.newspackReaderActivation?.overlays ) {
modalCheckout.overlayId = window.newspackReaderActivation?.overlays.add();
}
-
iframeResizeObserver = new ResizeObserver( entries => {
if ( ! entries || ! entries.length ) {
return;
@@ -174,48 +173,36 @@ domReady( () => {
const contentRect = entries[ 0 ].contentRect;
if ( contentRect ) {
modalContent.style.height = contentRect.top + contentRect.bottom + 'px';
- spinner.style.display = 'none';
- }
- } );
- iframe.addEventListener( 'load', () => {
- const location = iframe.contentWindow.location;
- // If RAS is available, set the front-end authentication.
- if ( window.newspackReaderActivation && location.href.indexOf( 'order-received' ) > -1 ) {
- const ras = window.newspackReaderActivation;
- const params = new Proxy( new URLSearchParams( location.search ), {
- get: ( searchParams, prop ) => searchParams.get( prop ),
- } );
- if ( params.email ) {
- ras.setReaderEmail( params.email );
- ras.setAuthenticated( true );
- }
- }
- const container = iframe.contentDocument.querySelector( '#newspack_modal_checkout' );
- if ( container ) {
- iframeResizeObserver.observe( container );
- }
- const innerButtons = [
- ...iframe.contentDocument.querySelectorAll( '.modal-continue, .edit-billing-link' ),
- ];
- innerButtons.forEach( innerButton => {
- innerButton.addEventListener( 'click', () => ( spinner.style.display = 'flex' ) );
- } );
- const innerForm = iframe.contentDocument.querySelector( '.checkout' );
- if ( innerForm ) {
- const innerBillingFields = [
- ...innerForm.querySelectorAll( '.woocommerce-billing-fields input' ),
- ];
- innerBillingFields.forEach( innerField => {
- innerField.addEventListener( 'keyup', e => {
- if ( 'Enter' === e.key ) {
- spinner.style.display = 'flex';
- innerForm.submit();
- }
- } );
- } );
}
} );
} );
} );
} );
+ iframe.addEventListener( 'load', () => {
+ const location = iframe.contentWindow.location;
+ // If RAS is available, set the front-end authentication.
+ if ( window.newspackReaderActivation && location.href.indexOf( 'order-received' ) > -1 ) {
+ const ras = window.newspackReaderActivation;
+ const params = new Proxy( new URLSearchParams( location.search ), {
+ get: ( searchParams, prop ) => searchParams.get( prop ),
+ } );
+ if ( params.email ) {
+ ras.setReaderEmail( params.email );
+ ras.setAuthenticated( true );
+ }
+ }
+ const container = iframe.contentDocument.querySelector( '#newspack_modal_checkout' );
+ if ( container ) {
+ iframeResizeObserver.observe( container );
+ if ( container.checkoutReady ) {
+ spinner.style.display = 'none';
+ } else {
+ container.addEventListener( 'checkout-ready', () => {
+ spinner.style.display = 'none';
+ } );
+ }
+ } else {
+ spinner.style.display = 'none';
+ }
+ } );
} );
diff --git a/src/modal-checkout/modal.scss b/src/modal-checkout/modal.scss
index 2bc76b43c..c30f1f07c 100644
--- a/src/modal-checkout/modal.scss
+++ b/src/modal-checkout/modal.scss
@@ -20,23 +20,56 @@
width: 100%;
height: 100%;
background: rgba( 0, 0, 0, 0.75 );
- z-index: 99999;
- &__content {
+ visibility: hidden;
+ pointer-events: none;
+ z-index: -1;
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+ &__container {
+ border-radius: 6px;
+ background: colors.$color__background-body;
position: absolute;
top: 50%;
left: 50%;
- transform: translate( -50%, -50% );
width: calc( 100vw - 32px );
- max-width: 580px;
- min-height: 200px;
- max-height: calc( 100vh - 32px );
- background: colors.$color__background-body;
- border-radius: 5px;
- > *:not( .newspack-blocks-modal__close ) {
+ max-width: 600px;
+ min-height: 300px;
+ max-height: 90vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ transform: translate( -50%, -50% ) translateY( 50px );
+ transition: transform 0.2s ease-in-out;
+ }
+ &.open {
+ z-index: 99999;
+ visibility: visible;
+ pointer-events: auto;
+ opacity: 1;
+ .newspack-blocks-modal__container {
+ transform: translate( -50%, -50% );
+ }
+ }
+ &__header {
+ padding: 24px;
+ box-sizing: border-box;
+ height: 64px;
+ border-bottom: 1px solid #ddd;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ h2 {
+ font-size: 16px;
+ }
+ }
+ &__content {
+ position: relative;
+ flex-grow: 1;
+ > * {
width: 100%;
height: 100%;
border: 0;
- border-radius: 5px;
+ border-radius: 6px;
}
}
&__spinner {
@@ -44,13 +77,10 @@
background: #fff;
border-radius: 5px;
display: flex;
- height: 100%;
justify-content: center;
- left: 50%;
- opacity: 0.5;
position: absolute;
- top: 50%;
- transform: translate( -50%, -50% );
+ top: 0;
+ bottom: 0;
width: 100%;
> span {
animation: spin 1s infinite linear;
@@ -59,12 +89,11 @@
border-radius: 50%;
height: 25px;
width: 25px;
+ margin-top: -12.5px;
+ margin-left: -12.5px;
}
}
&__close {
- position: absolute;
- top: 0;
- right: 0;
padding: 8px;
border: 0;
background: transparent;
@@ -147,17 +176,14 @@
@media ( max-width: 600px ) {
.newspack-blocks-modal {
- &__content {
+ &__container {
max-width: 100%;
width: 100%;
border-radius: 0;
top: auto;
bottom: 0;
left: 0;
- transform: none;
- > *:not( .newspack-blocks-modal__close ) {
- border-radius: 0;
- }
+ transform: none !important;
}
}
}
diff --git a/src/modal-checkout/templates/billing-form.php b/src/modal-checkout/templates/billing-form.php
deleted file mode 100644
index 61c68f6c4..000000000
--- a/src/modal-checkout/templates/billing-form.php
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
- cart->needs_shipping() ) : ?>
-
-
-
-
-
-
-
-
-
-
-
-
- get_checkout_fields( 'billing' );
-
- foreach ( $fields as $key => $field ) {
- woocommerce_form_field( $key, $field, $checkout->get_value( $key ) );
- }
- ?>
-
-
-
-
diff --git a/src/modal-checkout/templates/checkout-form.php b/src/modal-checkout/templates/checkout-form.php
deleted file mode 100644
index 437eb6bba..000000000
--- a/src/modal-checkout/templates/checkout-form.php
+++ /dev/null
@@ -1,169 +0,0 @@
-cart;
-
-$has_filled_billing = \Newspack_Blocks\Modal_Checkout::has_filled_required_fields( 'billing' );
-$edit_billing = ! $has_filled_billing || isset( $_REQUEST['edit_billing'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
-
-$form_action = $edit_billing ? '#checkout' : wc_get_checkout_url();
-$form_class = 'checkout woocommerce-checkout';
-$form_method = $edit_billing ? 'get' : 'post';
-$form_billing_fields = \Newspack_Blocks\Modal_Checkout::get_prefilled_fields();
-
-$after_success_behavior = filter_input( INPUT_GET, 'after_success_behavior', FILTER_SANITIZE_SPECIAL_CHARS );
-$after_success_url = filter_input( INPUT_GET, 'after_success_url', FILTER_SANITIZE_SPECIAL_CHARS );
-$after_success_button_label = filter_input( INPUT_GET, 'after_success_button_label', FILTER_SANITIZE_SPECIAL_CHARS );
-
-if ( $edit_billing ) {
- $form_class .= ' edit-billing';
-}
-
-do_action( 'woocommerce_before_checkout_form', $checkout );
-
-// If checkout registration is disabled and not logged in, the user cannot checkout.
-if ( ! $checkout->is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
- echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'newspack-blocks' ) ) ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
- return;
-}
-?>
-
-get_cart_contents_count() ) : ?>
-
- get_cart() as $cart_item_key => $cart_item ) {
- $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key );
-
- if ( $_product && $_product->exists() && $cart_item['quantity'] > 0 && apply_filters( 'woocommerce_checkout_cart_item_visible', true, $cart_item, $cart_item_key ) ) {
- ?>
-
-
-
- get_name(), $cart_item, $cart_item_key ) . ' '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- ?>
-
-
- get_product_subtotal( $_product, $cart_item['quantity'] ), $cart_item, $cart_item_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- ?>
-
-
-
-
-
-
-
-
-
diff --git a/src/modal-checkout/templates/form-checkout.php b/src/modal-checkout/templates/form-checkout.php
new file mode 100644
index 000000000..821b091d4
--- /dev/null
+++ b/src/modal-checkout/templates/form-checkout.php
@@ -0,0 +1,40 @@
+is_registration_enabled() && $checkout->is_registration_required() && ! is_user_logged_in() ) {
+ echo esc_html( apply_filters( 'woocommerce_checkout_must_be_logged_in_message', __( 'You must be logged in to checkout.', 'newspack-blocks' ) ) );
+ return;
+}
+?>
+
+
+
+
diff --git a/src/modal-checkout/templates/form-coupon.php b/src/modal-checkout/templates/form-coupon.php
new file mode 100644
index 000000000..00e3fd334
--- /dev/null
+++ b/src/modal-checkout/templates/form-coupon.php
@@ -0,0 +1,23 @@
+
+