Skip to content

Commit 3161463

Browse files
committed
Add WooCommerce Shipping Migration modal and contextual banner with dismissal control
- Extend WC_Connect_Nux to support new service settings and schemas stores. - Add contextual after-connection banner for US users without WooCommerce Shipping plugin. - Enqueue and localize migration modal scripts and styles; render React modal container in admin notices. - Add dismissal logic with a shared option (`wcs_nux_any_banner_shown`) to prevent multiple simultaneous banners. - Enhance feature announcement React component to support forced open state. - Improve migration runner state handling for robustness. - Add safety checks in admin notice dismiss button event listeners. - Make `WC_Connect_Loader::get_wc_connect_base_url` public static for broader usage. - Instantiate `WC_Connect_Nux` with new dependencies in loader. - Add placeholder admin banner and modal manager classes.
1 parent 50e4745 commit 3161463

File tree

7 files changed

+161
-25
lines changed

7 files changed

+161
-25
lines changed

classes/class-wc-connect-nux.php

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,34 @@ class WC_Connect_Nux {
2929
*/
3030
private $shipping_label;
3131

32-
function __construct( WC_Connect_Tracks $tracks, WC_Connect_Shipping_Label $shipping_label ) {
33-
$this->tracks = $tracks;
34-
$this->shipping_label = $shipping_label;
32+
/**
33+
* @var WC_Connect_Service_Settings_Store
34+
*/
35+
protected $service_settings_store;
36+
37+
/**
38+
* @var WC_Connect_Payment_Methods_Store
39+
*/
40+
protected $payment_methods_store;
41+
42+
/**
43+
* @var WC_Connect_Service_Schemas_Store
44+
*/
45+
protected $service_schemas_store;
46+
47+
48+
function __construct(
49+
WC_Connect_Tracks $tracks,
50+
WC_Connect_Shipping_Label $shipping_label,
51+
WC_Connect_Service_Settings_Store $service_settings_store,
52+
WC_Connect_Payment_Methods_Store $payment_methods_store,
53+
WC_Connect_Service_Schemas_Store $service_schemas_store
54+
) {
55+
$this->tracks = $tracks;
56+
$this->shipping_label = $shipping_label;
57+
$this->service_settings_store = $service_settings_store;
58+
$this->payment_methods_store = $payment_methods_store;
59+
$this->service_schemas_store = $service_schemas_store;
3560

3661
$this->init_pointers();
3762
}
@@ -436,6 +461,68 @@ public function set_up_nux_notices() {
436461
add_action( 'admin_notices', array( $this, 'show_tos_banner' ) );
437462
break;
438463
case 'after_cxn_us_no_wcs_plugin':
464+
// Enqueue the migration modal assets specifically for this banner on the plugins page.
465+
$plugin_version = WC_Connect_Loader::get_wcs_version();
466+
// Use the public static method from WC_Connect_Loader
467+
$base_url = WC_Connect_Loader::get_wc_connect_base_url(); // Assuming get_wc_connect_base_url is static
468+
469+
wp_register_style( 'wcst_wcshipping_migration_admin_notice', $base_url . 'woocommerce-services-wcshipping-migration-admin-notice-' . $plugin_version . '.css', array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
470+
// Add 'wp-element' and 'wc_connect_admin' dependency for React and base script
471+
wp_register_script( 'wcst_wcshipping_migration_admin_notice', $base_url . 'woocommerce-services-wcshipping-migration-admin-notice-' . $plugin_version . '.js', array( 'wc_connect_admin', 'wp-element' ), null, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
472+
473+
// Localize script data - MATCHING the original register_wcshipping_migration_modal
474+
// Note: The modal primarily uses data-args, localization is minimal here.
475+
wp_localize_script(
476+
'wcst_wcshipping_migration_admin_notice',
477+
'wcsPluginData', // Ensure this matches the expected object name in the script
478+
array(
479+
'assetPath' => $base_url,
480+
'adminPluginPath' => admin_url( 'plugins.php' ),
481+
)
482+
);
483+
484+
// Enqueue scripts/styles needed for the banner and modal
485+
wp_enqueue_script( 'wc_connect_admin' ); // Ensure base script is loaded first
486+
wp_enqueue_script( 'wcst_wcshipping_migration_admin_notice' );
487+
wp_enqueue_style( 'wcst_wcshipping_migration_admin_notice' );
488+
wp_enqueue_style( 'wc_connect_banner' );
489+
490+
// Add the action to render the notice and container div
491+
add_action(
492+
'admin_notices',
493+
function () use ( $banner_to_display ) {
494+
// Instantiate settings classes HERE, inside the closure, using stored dependencies
495+
$account_settings = new WC_Connect_Account_Settings(
496+
$this->service_settings_store,
497+
$this->payment_methods_store
498+
);
499+
$packages_settings = new WC_Connect_Package_Settings(
500+
$this->service_settings_store,
501+
$this->service_schemas_store
502+
);
503+
504+
// Prepare the data for the data-args attribute by calling get()
505+
$container_data_args = array(
506+
'nonce' => wp_create_nonce( 'wp_rest' ),
507+
'baseURL' => get_rest_url(),
508+
'accountSettings' => $account_settings->get(), // Get REAL data
509+
'packagesSettings' => $packages_settings->get(), // Get REAL data
510+
);
511+
$encoded_container_args = wp_json_encode( $container_data_args );
512+
513+
// Echo the container div needed for the modal React component BEFORE the banner.
514+
// Add 'display: none;' initially; the script should manage visibility.
515+
printf(
516+
'<div id="wcst_wcshipping_migration_admin_notice_feature_announcement" data-args="%s" style="display: none;"></div>',
517+
esc_attr( $encoded_container_args ) // Use the REAL encoded data
518+
);
519+
520+
// Now show the banner itself
521+
$this->show_contextual_after_connection_banner( $banner_to_display );
522+
}
523+
);
524+
break; // End case 'after_cxn_us_no_wcs_plugin'
525+
439526
case 'after_cxn_us_with_wcs_plugin':
440527
case 'after_cxn_non_us':
441528
wp_enqueue_style( 'wc_connect_banner' );
@@ -453,6 +540,10 @@ function () use ( $banner_to_display ) {
453540
}
454541

455542
public function show_banner_before_connection() {
543+
if ( get_option( 'wcs_nux_any_banner_shown', false ) ) {
544+
return;
545+
}
546+
456547
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
457548
return;
458549
}
@@ -486,10 +577,16 @@ public function show_banner_before_connection() {
486577
'should_show_terms' => true,
487578
);
488579

580+
update_option( 'wcs_nux_any_banner_shown', true );
581+
489582
$this->show_nux_banner( $banner_content );
490583
}
491584

492585
public function show_banner_after_connection() {
586+
if ( get_option( 'wcs_nux_any_banner_shown', false ) ) {
587+
return;
588+
}
589+
493590
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
494591
return;
495592
}
@@ -504,6 +601,7 @@ public function show_banner_after_connection() {
504601
WC_Connect_Options::delete_option( self::SHOULD_SHOW_AFTER_CXN_BANNER );
505602
// Set the flag for the next contextual banner
506603
WC_Connect_Options::update_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER, true );
604+
delete_option( 'wcs_nux_any_banner_shown' );
507605
wp_safe_redirect( remove_query_arg( 'wcs-nux-notice' ) );
508606
exit;
509607
}
@@ -518,6 +616,8 @@ public function show_banner_after_connection() {
518616
$description_base = __( 'You can now enjoy %s.', 'woocommerce-services' );
519617
$feature_list = $this->get_feature_list_for_country( $country );
520618

619+
update_option( 'wcs_nux_any_banner_shown', true );
620+
521621
$this->show_nux_banner(
522622
array(
523623
'title' => __( 'Setup complete.', 'woocommerce-services' ),
@@ -538,6 +638,10 @@ public function show_banner_after_connection() {
538638
}
539639

540640
public function show_contextual_after_connection_banner( $banner_type ) {
641+
if ( get_option( 'wcs_nux_any_banner_shown', false ) ) {
642+
return;
643+
}
644+
541645
$screen = get_current_screen();
542646

543647
// This specific banner should only appear on the plugins page.
@@ -554,6 +658,7 @@ public function show_contextual_after_connection_banner( $banner_type ) {
554658
if ( isset( $_GET['wcs-nux-notice'] ) && 'dismiss' === $_GET['wcs-nux-notice'] ) {
555659
// Delete the flag for this contextual banner
556660
WC_Connect_Options::delete_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER );
661+
delete_option( 'wcs_nux_any_banner_shown' );
557662
wp_safe_redirect( remove_query_arg( 'wcs-nux-notice' ) );
558663
exit;
559664
}
@@ -564,17 +669,21 @@ public function show_contextual_after_connection_banner( $banner_type ) {
564669
// Using a generic tracks event, can be made more specific if needed.
565670
$this->tracks->opted_in( 'contextual_connection_banner_viewed' );
566671

672+
update_option( 'wcs_nux_any_banner_shown', true );
673+
567674
$banner_title = '';
568675
$banner_description = '';
569676
$banner_button_text = '';
570677
$banner_button_link = null;
571678

679+
update_option( 'wcshipping_migration_state', '0' );
572680
switch ( $banner_type ) {
573681
case 'after_cxn_us_no_wcs_plugin':
574682
$banner_title = __( 'WooCommerce Shipping & Tax has been renamed to WooCommerce Tax', 'woocommerce-services' );
575683
$banner_description = __( 'Your tax functionality will continue to work as expected. The shipping functionality in this plugin will be discontinued on September 1, 2025. Please migrate to the new WooCommerce Shipping extension to get discounted labels for UPS, USPS, DHL Express— and more coming soon!', 'woocommerce-services' );
576684
$banner_button_text = __( 'Try WooCommerce Shipping ', 'woocommerce-services' );
577-
$banner_button_link = '';
685+
// Ensure this line uses the special trigger value:
686+
$banner_button_link = '#trigger-migration-modal';
578687
break;
579688
case 'after_cxn_us_with_wcs_plugin':
580689
$banner_title = __( 'WooCommerce Shipping & Tax has been renamed to WooCommerce Tax', 'woocommerce-services' );
@@ -613,6 +722,10 @@ public function show_contextual_after_connection_banner( $banner_type ) {
613722
}
614723

615724
public function show_tos_banner() {
725+
if ( get_option( 'wcs_nux_any_banner_shown', false ) ) {
726+
return;
727+
}
728+
616729
if ( ! $this->should_display_nux_notice_for_current_store_locale() ) {
617730
return;
618731
}
@@ -628,6 +741,8 @@ public function show_tos_banner() {
628741

629742
$this->tracks->opted_in( 'tos_banner' );
630743

744+
delete_option( 'wcs_nux_any_banner_shown' );
745+
631746
wp_safe_redirect( remove_query_arg( 'wcs-nux-tos' ) );
632747
exit;
633748
}
@@ -637,6 +752,8 @@ public function show_tos_banner() {
637752
$description_base = __( "WooCommerce Tax is almost ready to go! Once you connect your site to WordPress.com you'll have access to %s.", 'woocommerce-services' );
638753
$feature_list = $this->get_feature_list_for_country( $country );
639754

755+
update_option( 'wcs_nux_any_banner_shown', true );
756+
640757
$this->show_nux_banner(
641758
array(
642759
'title' => __( 'Connect your site to activate WooCommerce Tax', 'woocommerce-services' ),
@@ -694,12 +811,23 @@ public function show_nux_banner( $content ) {
694811
</p>
695812
<?php endif; ?>
696813
<?php if ( isset( $content['button_link'] ) ) : ?>
697-
<a
698-
class="wcs-nux__notice-content-button button button-primary"
699-
href="<?php echo esc_url( $content['button_link'] ); ?>"
700-
>
701-
<?php echo esc_html( $content['button_text'] ); ?>
702-
</a>
814+
<?php // Check for the special modal trigger value ?>
815+
<?php if ( '#trigger-migration-modal' === $content['button_link'] ) : ?>
816+
<button
817+
type="button"
818+
id="wcst-wcshipping-migration-notice__click" <?php // Ensure this ID matches what the JS expects ?>
819+
class="wcs-nux__notice-content-button button button-primary"
820+
>
821+
<?php echo esc_html( $content['button_text'] ); ?>
822+
</button>
823+
<?php else : ?>
824+
<a
825+
class="wcs-nux__notice-content-button button button-primary"
826+
href="<?php echo esc_url( $content['button_link'] ); ?>"
827+
>
828+
<?php echo esc_html( $content['button_text'] ); ?>
829+
</a>
830+
<?php endif; ?>
703831
<?php else : ?>
704832
<form action="<?php echo esc_attr( admin_url( 'admin-post.php' ) ); ?>" method="post">
705833
<input type="hidden" name="action" value="register_woocommerce_services_jetpack"/>

client/components/migration/feature-announcement.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import {
2020
import { installAndActivatePlugins } from './migration-runner';
2121
import { TIME_TO_REMMEMBER_DISMISSAL_SECONDS } from './constants';
2222

23-
const FeatureAnnouncement = ( { translate, isEligable, previousMigrationState, onClose } ) => {
24-
const [isOpen, setIsOpen] = useState(isEligable);
23+
const FeatureAnnouncement = ( { translate, isEligable, previousMigrationState, onClose, forceOpen = false } ) => {
24+
// Open if either normally eligible OR if forced open by the prop.
25+
const [isOpen, setIsOpen] = useState( forceOpen || isEligable );
2526
const [isUpdating, setIsUpdating] = useState(false);
2627

2728
useEffect( () => {

client/components/migration/migration-runner.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,14 @@ const installAndActivatePlugins = async( previousMigrationState ) => {
228228
* @returns {string} The next state to run. The name of the state is the key in this object migrationStateTransitions.
229229
*/
230230
const getNextStateToRun = () => {
231-
if ( ! previousMigrationState ) {
232-
// stateInit
231+
// Check if previousMigrationState is a valid key in the map.
232+
// If not, or if it's falsy, default to the initial state (stateInit).
233+
if ( ! previousMigrationState || ! MIGRATION_ENUM_TO_STATE_NAME_MAP.hasOwnProperty( previousMigrationState ) ) {
234+
// stateInit (key 2)
233235
return MIGRATION_ENUM_TO_STATE_NAME_MAP[ 2 ];
234236
}
235237

238+
// If it's a valid key, find the corresponding state name and return its success transition.
236239
const currentStateName = MIGRATION_ENUM_TO_STATE_NAME_MAP[ previousMigrationState ];
237240
const nextMigrationState = getMigrationStateByName( currentStateName );
238241
return nextMigrationState.success; // The next state is "success".
@@ -258,4 +261,4 @@ const installAndActivatePlugins = async( previousMigrationState ) => {
258261

259262
export {
260263
installAndActivatePlugins
261-
}
264+
}

client/wcshipping-migration-admin-notice.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,24 @@ const wcstMigrationNoticeDimissButton = document.querySelector('.wcst-wcshipping
3333
* Prevent form submission when rendered in a form or alike that listens to button click
3434
*/
3535
evt.preventDefault();
36-
// Pop open feature announcement modal.
36+
// Pop open feature announcement modal, forcing it open regardless of eligibility state.
3737
ReactDOM.render(
3838
<Provider store={store}>
39-
<FeatureAnnouncement />
39+
<FeatureAnnouncement forceOpen={ true } />
4040
</Provider>,
4141
container
4242
);
4343
});
4444

4545
// Dismiss it for 3 days, then remove it from view.
46-
wcstMigrationNoticeDimissButton.addEventListener(eventName, () => {
47-
// window.wpCookies API: wordpress/wp-includes/js/utils.js
48-
window.wpCookies.set('wcst-wcshipping-migration-dismissed', 1, TIME_TO_REMMEMBER_DISMISSAL_SECONDS)
49-
const wcstMigrationAdminNotice = document.querySelector('.wcst-wcshipping-migration-notice');
50-
wcstMigrationAdminNotice.remove();
51-
});
46+
if (wcstMigrationNoticeDimissButton) {
47+
wcstMigrationNoticeDimissButton.addEventListener(eventName, () => {
48+
// window.wpCookies API: wordpress/wp-includes/js/utils.js
49+
window.wpCookies.set('wcst-wcshipping-migration-dismissed', 1, TIME_TO_REMMEMBER_DISMISSAL_SECONDS)
50+
const wcstMigrationAdminNotice = document.querySelector('.wcst-wcshipping-migration-notice');
51+
if (wcstMigrationAdminNotice) {
52+
wcstMigrationAdminNotice.remove();
53+
}
54+
});
55+
}
5256
});

includes/admin/class-wc-connect-banner.php

Whitespace-only changes.

includes/admin/class-wc-connect-modal-manager.php

Whitespace-only changes.

woocommerce-services.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ public static function get_wcs_version() {
337337
*
338338
* @return string
339339
*/
340-
private static function get_wc_connect_base_url() {
340+
public static function get_wc_connect_base_url() {
341341
return trailingslashit( defined( 'WOOCOMMERCE_CONNECT_DEV_SERVER_URL' ) ? WOOCOMMERCE_CONNECT_DEV_SERVER_URL : plugins_url( 'dist/', __FILE__ ) );
342342
}
343343

@@ -814,7 +814,7 @@ public function load_dependencies() {
814814
$payment_methods_store = new WC_Connect_Payment_Methods_Store( $settings_store, $api_client, $logger );
815815
$tracks = new WC_Connect_Tracks( $logger, __FILE__ );
816816
$shipping_label = new WC_Connect_Shipping_Label( $api_client, $settings_store, $schemas_store, $payment_methods_store );
817-
$nux = new WC_Connect_Nux( $tracks, $shipping_label );
817+
$nux = new WC_Connect_Nux( $tracks, $shipping_label, $settings_store, $payment_methods_store, $schemas_store );
818818
$taxjar = new WC_Connect_TaxJar_Integration( $api_client, $taxes_logger, $this->wc_connect_base_url );
819819
$paypal_ec = new WC_Connect_PayPal_EC( $api_client, $nux );
820820
$label_reports = new WC_Connect_Label_Reports( $settings_store );

0 commit comments

Comments
 (0)