diff --git a/changelog/add-1527-survey-modal-on-deactivation b/changelog/add-1527-survey-modal-on-deactivation
new file mode 100644
index 00000000000..d2f860f88ed
--- /dev/null
+++ b/changelog/add-1527-survey-modal-on-deactivation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a feedback survey modal upon deactivation.
diff --git a/client/plugins-page/deactivation-survey/index.js b/client/plugins-page/deactivation-survey/index.js
new file mode 100644
index 00000000000..faa4eaf2228
--- /dev/null
+++ b/client/plugins-page/deactivation-survey/index.js
@@ -0,0 +1,50 @@
+/**
+ * External dependencies
+ */
+import React, { useState } from 'react';
+import { __ } from '@wordpress/i18n';
+import { Modal } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+import Loadable from 'wcpay/components/loadable';
+import WooPaymentsIcon from 'assets/images/woopayments.svg?asset';
+
+const PluginDisableSurvey = ( { onRequestClose } ) => {
+ const [ isLoading, setIsLoading ] = useState( true );
+
+ return (
+
+ }
+ isDismissible={ true }
+ shouldCloseOnClickOutside={ false } // Should be false because of the iframe.
+ shouldCloseOnEsc={ true }
+ onRequestClose={ onRequestClose }
+ className="woopayments-disable-survey"
+ >
+
+
+
+ );
+};
+
+export default PluginDisableSurvey;
diff --git a/client/plugins-page/deactivation-survey/style.scss b/client/plugins-page/deactivation-survey/style.scss
new file mode 100644
index 00000000000..ba8a2e116e0
--- /dev/null
+++ b/client/plugins-page/deactivation-survey/style.scss
@@ -0,0 +1,38 @@
+.woopayments-disable-survey {
+ @media ( min-width: 960px ) {
+ max-height: calc( 100% - 120px );
+ }
+
+ .components-modal__content {
+ padding: 0;
+ overflow: hidden;
+ }
+
+ &-iframe {
+ width: 100%;
+ height: 100%;
+
+ @media ( min-width: 600px ) {
+ width: 600px;
+ height: 650px;
+ }
+ }
+
+ &-logo {
+ height: 40px;
+ }
+}
+
+/**
+ * There is a bug with the Modal component that when the close X is hovered or focused, a tooltip
+ * appears outside of the view of the modal causing scrollbars. This is a work around to hide the
+ * tooltip until the bug is fixed.
+ * TODO: remove rule ones bug is closed
+ * https://github.com/WordPress/gutenberg/issues/15434
+ */
+.components-modal__content
+ .components-modal__header
+ .components-button
+ .components-tooltip {
+ display: none;
+}
diff --git a/client/plugins-page/index.js b/client/plugins-page/index.js
new file mode 100644
index 00000000000..24d59e65fa5
--- /dev/null
+++ b/client/plugins-page/index.js
@@ -0,0 +1,108 @@
+/**
+ * External dependencies
+ */
+import React, { useState, useEffect, useCallback } from 'react';
+import { useDispatch } from '@wordpress/data';
+import ReactDOM from 'react-dom';
+import { OPTIONS_STORE_NAME } from '@woocommerce/data';
+
+/**
+ * Internal dependencies
+ */
+import PluginDisableSurvey from './deactivation-survey';
+
+const PluginsPage = () => {
+ const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
+ const [ modalOpen, setModalOpen ] = useState( false );
+ const surveyModalTimestamp =
+ window.wcpayPluginsSettings?.exitSurveyLastShown ?? null;
+
+ const deactivationLink = document.querySelector(
+ '#deactivate-woocommerce-payments, #deactivate-woocommerce-payments-dev'
+ ); // ID set by WP on the deactivation link.
+
+ const deactivatePlugin = useCallback( () => {
+ // Abort if the deactivation link is not present.
+ if ( deactivationLink === null ) {
+ return null;
+ }
+
+ // Deactivate plugin
+ window.location.href = deactivationLink.getAttribute( 'href' );
+ }, [ deactivationLink ] );
+
+ const showModal = useCallback( () => {
+ setModalOpen( true );
+ }, [ setModalOpen ] );
+
+ const closeModal = async () => {
+ setModalOpen( false );
+
+ const currentDate = new Date();
+
+ // Update modal dismissed option.
+ await updateOptions( {
+ wcpay_exit_survey_last_shown: currentDate,
+ } );
+
+ window.wcpayPluginsSettings.exitSurveyLastShown = currentDate;
+
+ // Deactivate plugin
+ deactivatePlugin();
+ };
+
+ const isModalDismissed = useCallback( () => {
+ if ( surveyModalTimestamp ) {
+ const date1 = new Date( surveyModalTimestamp );
+ const date2 = new Date();
+ const diffTime = Math.abs( date2 - date1 );
+ const diffDays = Math.ceil( diffTime / ( 1000 * 60 * 60 * 24 ) );
+
+ if ( diffDays < 7 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }, [ surveyModalTimestamp ] );
+
+ const handleLinkClick = useCallback(
+ ( e ) => {
+ e.preventDefault();
+ showModal();
+ },
+ [ showModal ]
+ );
+
+ useEffect( () => {
+ // If the survey is dismissed skip event listeners.
+ if ( isModalDismissed() ) {
+ return null;
+ }
+
+ // Abort if the deactivation link is not present.
+ if ( deactivationLink === null ) {
+ return null;
+ }
+
+ // Handle click event.
+ deactivationLink.addEventListener( 'click', handleLinkClick );
+
+ return () => {
+ deactivationLink.removeEventListener( 'click', handleLinkClick );
+ };
+ }, [ isModalDismissed, deactivationLink, handleLinkClick ] );
+
+ return (
+ <>
+ { ! isModalDismissed() && modalOpen && (
+
+ ) }
+ >
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.querySelector( '#woopayments-plugins-page-app' )
+);
diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php
index f302580b0b7..aeb97880bfa 100644
--- a/includes/admin/class-wc-payments-admin.php
+++ b/includes/admin/class-wc-payments-admin.php
@@ -581,6 +581,17 @@ public function register_payments_scripts() {
WC_Payments::get_file_version( 'dist/payment-gateways.css' ),
'all'
);
+
+ WC_Payments::register_script_with_dependencies( 'WCPAY_PLUGINS_PAGE', 'dist/plugins-page', [ 'wp-api-request' ] );
+ wp_set_script_translations( 'WCPAY_PLUGINS_PAGE', 'woocommerce-payments' );
+
+ WC_Payments_Utils::register_style(
+ 'WCPAY_PLUGINS_PAGE',
+ plugins_url( 'dist/plugins-page.css', WCPAY_PLUGIN_FILE ),
+ [ 'wp-components', 'wc-components' ],
+ WC_Payments::get_file_version( 'dist/plugins-page.css' ),
+ 'all'
+ );
}
/**
@@ -669,6 +680,23 @@ public function enqueue_payments_scripts() {
}
$screen = get_current_screen();
+
+ // Only enqueue the scripts on the plugins page.
+ if ( in_array( $screen->id, [ 'plugins' ], true ) ) {
+ // Localize before actually enqueuing to avoid unnecessary settings generation.
+ // Most importantly, the destructive error transient handling.
+ wp_localize_script(
+ 'WCPAY_PLUGINS_PAGE',
+ 'wcpayPluginsSettings',
+ $this->get_plugins_page_js_settings()
+ );
+
+ wp_enqueue_script( 'WCPAY_PLUGINS_PAGE' );
+ wp_enqueue_style( 'WCPAY_PLUGINS_PAGE' );
+
+ add_action( 'admin_footer', [ $this, 'load_plugins_page_wrapper' ] );
+ }
+
if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) {
$order = wc_get_order();
@@ -719,6 +747,22 @@ public function enqueue_payments_scripts() {
}
}
+ /**
+ * Outputs the wrapper for the plugin modal
+ * Contents are loaded by React script
+ *
+ * @return void
+ */
+ public function load_plugins_page_wrapper() {
+ wc_get_template(
+ 'plugins-page/plugins-page-wrapper.php',
+ [],
+ '',
+ WCPAY_ABSPATH . 'templates/'
+ );
+ }
+
+
/**
* Get the WCPay settings to be sent to JS.
*
@@ -872,6 +916,19 @@ private function get_js_settings(): array {
return apply_filters( 'wcpay_js_settings', $this->wcpay_js_settings );
}
+ /**
+ * Get the WCPay plugins page settings to be sent to JS.
+ *
+ * @return array
+ */
+ private function get_plugins_page_js_settings(): array {
+ $plugins_page_settings = [
+ 'exitSurveyLastShown' => get_option( 'wcpay_exit_survey_last_shown', null ),
+ ];
+
+ return apply_filters( 'wcpay_plugins_page_js_settings', $plugins_page_settings );
+ }
+
/**
* Helper function to retrieve enabled UPE payment methods.
*
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index d640cc30539..cf872d3a4f7 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -1833,6 +1833,7 @@ public static function add_wcpay_options_to_woocommerce_permissions_list( $permi
'wcpay_onboarding_eligibility_modal_dismissed',
'wcpay_next_deposit_notice_dismissed',
'wcpay_duplicate_payment_method_notices_dismissed',
+ 'wcpay_exit_survey_dismissed',
],
true
);
diff --git a/templates/plugins-page/plugins-page-wrapper.php b/templates/plugins-page/plugins-page-wrapper.php
new file mode 100644
index 00000000000..b89e5789c1a
--- /dev/null
+++ b/templates/plugins-page/plugins-page-wrapper.php
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/webpack/shared.js b/webpack/shared.js
index 60e6c3f333e..0a445ccf118 100644
--- a/webpack/shared.js
+++ b/webpack/shared.js
@@ -41,6 +41,7 @@ module.exports = {
'./client/subscription-product-onboarding/toast.js',
'product-details': './client/product-details/index.js',
'cart-block': './client/cart/blocks/index.js',
+ 'plugins-page': './client/plugins-page/index.js',
},
// Override webpack public path dynamically on every entry.
// Required for chunks loading to work on sites with JS concatenation.