From 9054f397bbbc24b2a8ed199a3cf278b7a4feb966 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Mon, 13 Jan 2025 21:48:42 -0500 Subject: [PATCH 01/18] Add redbubble and notice when paid plan is missing plugin(s). --- ...use-install-multiple-standalone-plugins.ts | 31 ++++++++ .../my-jetpack/src/class-products.php | 14 ++++ .../my-jetpack/src/class-rest-products.php | 79 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts diff --git a/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts b/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts new file mode 100644 index 0000000000000..30702ee0e97eb --- /dev/null +++ b/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts @@ -0,0 +1,31 @@ +import { __, sprintf } from '@wordpress/i18n'; +import { REST_API_SITE_PRODUCTS_ENDPOINT, QUERY_INSTALL_PRODUCT_KEY } from '../constants'; +import useSimpleMutation from '../use-simple-mutation'; +import useProducts from './use-products'; + +const useInstallMultipleStandalonePlugins = ( productIds: string[] ) => { + const { products, refetch } = useProducts( productIds ); + + const { mutate: install, isPending } = useSimpleMutation( { + name: QUERY_INSTALL_PRODUCT_KEY, + query: { + path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/install-products-plugins`, + method: 'POST', + data: { products: productIds }, + }, + options: { + onSuccess: refetch, + }, + errorMessage: __( + 'Failed to install standalone plugins. Please try again', + 'jetpack-my-jetpack' + ), + } ); + + return { + install, + isPending, + }; +}; + +export default useInstallMultipleStandalonePlugins; diff --git a/projects/packages/my-jetpack/src/class-products.php b/projects/packages/my-jetpack/src/class-products.php index 5fa9af2db4c22..c5d1fdd04722b 100644 --- a/projects/packages/my-jetpack/src/class-products.php +++ b/projects/packages/my-jetpack/src/class-products.php @@ -115,6 +115,20 @@ class Products { self::STATUS_NEEDS_ATTENTION__ERROR, ); + /** + * List of product slugs that are Not displayed on the main My Jetpack page + * + * @var array + */ + public static $not_shown_products = array( + 'scan', + 'extras', + 'ai', // 'ai' is a duplicate class of 'jetpack-ai', and therefore not needed. + 'newsletter', + 'site-accelerator', + 'related-posts', + ); + /** * Get the list of Products classes * diff --git a/projects/packages/my-jetpack/src/class-rest-products.php b/projects/packages/my-jetpack/src/class-rest-products.php index c997c2280a510..52ebd49aa358d 100644 --- a/projects/packages/my-jetpack/src/class-rest-products.php +++ b/projects/packages/my-jetpack/src/class-rest-products.php @@ -49,6 +49,32 @@ public function __construct() { 'validate_callback' => __CLASS__ . '::check_products_argument', ); + $products_arg = array( + 'description' => __( 'Array of Product slugs', 'jetpack-my-jetpack' ), + 'type' => 'array', + 'items' => array( + 'enum' => Products::get_products_slugs(), + 'type' => 'string', + ), + 'required' => true, + 'validate_callback' => __CLASS__ . '::check_products_argument', + ); + + register_rest_route( + 'my-jetpack/v1', + 'site/products/install-products-plugins', + array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::install_products_plugins', + 'permission_callback' => __CLASS__ . '::edit_permissions_callback', + 'args' => array( + 'products' => $products_arg, + ), + ), + ) + ); + register_rest_route( 'my-jetpack/v1', 'site/products/install', @@ -193,6 +219,26 @@ public static function check_products_argument( $value ) { return true; } + /** + * Check Products argument. + * + * @access public + * @static + * + * @param array $products_array - An array of product slug strings. + * @return true|WP_Error True if the value is valid, WP_Error otherwise. + */ + public static function check_products_argument( $products_array ) { + if ( ! is_array( $products_array ) ) { + return new WP_Error( + 'rest_invalid_param', + esc_html__( 'The product argument must be an array.', 'jetpack-my-jetpack' ), + array( 'status' => 400 ) + ); + } + + return true; + } /** * Site products endpoint. @@ -335,4 +381,37 @@ public static function install_plugins( $request ) { return rest_ensure_response( Products::get_products( $products_array ) ); } + + /** + * Callback for installing (and activating) multiple product plugins. + * + * @param \WP_REST_Request $request The request object. + * @return \WP_REST_Response + */ + public static function install_products_plugins( $request ) { + $products_array = $request->get_param( 'products' ); + + foreach ( $products_array as $product_slug ) { + $product = Products::get_product( $product_slug ); + if ( ! isset( $product['class'] ) ) { + return new \WP_Error( + 'product_class_handler_not_found', + sprintf( + /* translators: %s is the product_slug */ + __( 'The product slug %s does not have an associated class handler.', 'jetpack-my-jetpack' ), + $product_slug + ), + array( 'status' => 501 ) + ); + } + + $install_product_result = call_user_func( array( $product['class'], 'install_and_activate_standalone' ) ); + if ( is_wp_error( $install_product_result ) ) { + $install_product_result->add_data( array( 'status' => 400 ) ); + return $install_product_result; + } + } + + return rest_ensure_response( Products::get_products( $products_array ), 200 ); + } } From eea41d5a5d66a5122be04cc26d8bcaa96faba9d8 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Tue, 14 Jan 2025 21:01:09 -0500 Subject: [PATCH 02/18] Add activate-multiple-plugins endpoint & logic. --- .../data/products/use-activate-multiple.ts | 77 +++++++++++++++++++ ...use-install-multiple-standalone-plugins.ts | 9 ++- .../my-jetpack/src/class-rest-products.php | 48 ++++-------- 3 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts diff --git a/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts b/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts new file mode 100644 index 0000000000000..d050ff8fe0d80 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts @@ -0,0 +1,77 @@ +import { __, sprintf } from '@wordpress/i18n'; +import useAnalytics from '../../hooks/use-analytics'; +import { REST_API_SITE_PRODUCTS_ENDPOINT, QUERY_ACTIVATE_PRODUCT_KEY } from '../constants'; +import useSimpleMutation from '../use-simple-mutation'; +import { getMyJetpackWindowInitialState } from '../utils/get-my-jetpack-window-state'; +import useProducts from './use-products'; +import type { ProductCamelCase } from '../types'; + +const setPluginActiveState = ( productId: string ) => { + const { items } = getMyJetpackWindowInitialState( 'products' ); + if ( items[ productId ]?.standalone_plugin_info.has_standalone_plugin ) { + window.myJetpackInitialState.products.items[ + productId + ].standalone_plugin_info.is_standalone_active = true; + window.myJetpackInitialState.products.items[ + productId + ].standalone_plugin_info.is_standalone_installed = true; + } +}; + +const getIsPluginAlreadyActive = ( detail: ProductCamelCase ) => { + const { standalonePluginInfo, isPluginActive } = detail; + + if ( standalonePluginInfo?.hasStandalonePlugin ) { + return standalonePluginInfo?.isStandaloneActive; + } + + return isPluginActive; +}; + +const useActivateMultiple = ( productIds: string[] ) => { + const { products, refetch } = useProducts( productIds ); + const { recordEvent } = useAnalytics(); + + const { + mutate: activate, + isPending, + isSuccess, + } = useSimpleMutation( { + name: QUERY_ACTIVATE_PRODUCT_KEY, + query: { + path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/activate-multiple-plugins`, + method: 'POST', + data: { products: productIds }, + }, + options: { + onSuccess: () => { + products.forEach( product => { + if ( ! getIsPluginAlreadyActive( product ) ) { + recordEvent( 'jetpack_myjetpack_product_activated', { + product: product.slug, + } ); + + // This is to handle an edge case where a user is redirected somewhere after activation + // and goes back in the browser and "activates" the product again. This will manually update + // the window state so that the tracking is not recorded twice for one activation. + setPluginActiveState( product.slug ); + } + } ); + refetch(); + }, + }, + errorMessage: sprintf( + // translators: %s is the Jetpack product name or comma seperated list of multiple Jetpack product names. + __( 'There was a problem activating %s.', 'jetpack-my-jetpack' ), + products?.map( product => product.name ).join( ', ' ) + ), + } ); + + return { + activate, + isPending, + isSuccess, + }; +}; + +export default useActivateMultiple; diff --git a/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts b/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts index 30702ee0e97eb..c24f8fe2827ce 100644 --- a/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts +++ b/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts @@ -9,16 +9,17 @@ const useInstallMultipleStandalonePlugins = ( productIds: string[] ) => { const { mutate: install, isPending } = useSimpleMutation( { name: QUERY_INSTALL_PRODUCT_KEY, query: { - path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/install-products-plugins`, + path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/install-multiple-plugins`, method: 'POST', data: { products: productIds }, }, options: { onSuccess: refetch, }, - errorMessage: __( - 'Failed to install standalone plugins. Please try again', - 'jetpack-my-jetpack' + errorMessage: sprintf( + // translators: %s is the Jetpack product name or comma seperated list of multiple Jetpack product names. + __( 'There was a problem installing and activating %s.', 'jetpack-my-jetpack' ), + products?.map( product => product.name ).join( ', ' ) ), } ); diff --git a/projects/packages/my-jetpack/src/class-rest-products.php b/projects/packages/my-jetpack/src/class-rest-products.php index 52ebd49aa358d..46dd57aefbf36 100644 --- a/projects/packages/my-jetpack/src/class-rest-products.php +++ b/projects/packages/my-jetpack/src/class-rest-products.php @@ -49,24 +49,28 @@ public function __construct() { 'validate_callback' => __CLASS__ . '::check_products_argument', ); - $products_arg = array( - 'description' => __( 'Array of Product slugs', 'jetpack-my-jetpack' ), - 'type' => 'array', - 'items' => array( - 'enum' => Products::get_products_slugs(), - 'type' => 'string', - ), - 'required' => true, - 'validate_callback' => __CLASS__ . '::check_products_argument', + register_rest_route( + 'my-jetpack/v1', + 'site/products/install-multiple-plugins', + array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => __CLASS__ . '::install_multiple_plugins', + 'permission_callback' => __CLASS__ . '::edit_permissions_callback', + 'args' => array( + 'products' => $products_arg, + ), + ), + ) ); register_rest_route( 'my-jetpack/v1', - 'site/products/install-products-plugins', + 'site/products/activate-multiple-plugins', array( array( 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::install_products_plugins', + 'callback' => __CLASS__ . '::activate_multiple_products', 'permission_callback' => __CLASS__ . '::edit_permissions_callback', 'args' => array( 'products' => $products_arg, @@ -199,26 +203,6 @@ public static function check_products_string( $value ) { return true; } - /** - * Check Products argument. - * - * @access public - * @static - * - * @param mixed $value - Value of the 'product' argument. - * @return true|WP_Error True if the value is valid, WP_Error otherwise. - */ - public static function check_products_argument( $value ) { - if ( ! is_array( $value ) ) { - return new WP_Error( - 'rest_invalid_param', - esc_html__( 'The product argument must be an array.', 'jetpack-my-jetpack' ), - array( 'status' => 400 ) - ); - } - - return true; - } /** * Check Products argument. * @@ -388,7 +372,7 @@ public static function install_plugins( $request ) { * @param \WP_REST_Request $request The request object. * @return \WP_REST_Response */ - public static function install_products_plugins( $request ) { + public static function install_multiple_plugins( $request ) { $products_array = $request->get_param( 'products' ); foreach ( $products_array as $product_slug ) { From c88a8878e7b30c51fe7d1a8658e79ad3d0f75fdb Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 16 Jan 2025 16:56:16 -0500 Subject: [PATCH 03/18] Delay last-backup-failed red-bubble until 30 min after purchase. Also fix $not_shown_products. --- projects/packages/my-jetpack/src/class-products.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/src/class-products.php b/projects/packages/my-jetpack/src/class-products.php index c5d1fdd04722b..3619c9d0c44c9 100644 --- a/projects/packages/my-jetpack/src/class-products.php +++ b/projects/packages/my-jetpack/src/class-products.php @@ -121,9 +121,13 @@ class Products { * @var array */ public static $not_shown_products = array( - 'scan', + 'creator', 'extras', 'ai', // 'ai' is a duplicate class of 'jetpack-ai', and therefore not needed. + 'scan', + 'security', + 'growth', + 'complete', 'newsletter', 'site-accelerator', 'related-posts', From e47896957fa275b4035ba5963e1cc827c16af624 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Fri, 17 Jan 2025 05:59:47 -0500 Subject: [PATCH 04/18] Refactor how we derive shown_products & not_shown_products. --- .../packages/my-jetpack/src/class-products.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/projects/packages/my-jetpack/src/class-products.php b/projects/packages/my-jetpack/src/class-products.php index 3619c9d0c44c9..5fa9af2db4c22 100644 --- a/projects/packages/my-jetpack/src/class-products.php +++ b/projects/packages/my-jetpack/src/class-products.php @@ -115,24 +115,6 @@ class Products { self::STATUS_NEEDS_ATTENTION__ERROR, ); - /** - * List of product slugs that are Not displayed on the main My Jetpack page - * - * @var array - */ - public static $not_shown_products = array( - 'creator', - 'extras', - 'ai', // 'ai' is a duplicate class of 'jetpack-ai', and therefore not needed. - 'scan', - 'security', - 'growth', - 'complete', - 'newsletter', - 'site-accelerator', - 'related-posts', - ); - /** * Get the list of Products classes * From 175fad3c7bd741dc6a467d39bd5dccc207eecf87 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 23 Jan 2025 10:08:03 -0500 Subject: [PATCH 05/18] Cleanup & add GlobalNotice after successfull activation/installation. --- .../data/products/use-activate-multiple.ts | 77 ------------------- ...use-install-multiple-standalone-plugins.ts | 32 -------- .../my-jetpack/src/class-rest-products.php | 63 --------------- 3 files changed, 172 deletions(-) delete mode 100644 projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts delete mode 100644 projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts diff --git a/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts b/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts deleted file mode 100644 index d050ff8fe0d80..0000000000000 --- a/projects/packages/my-jetpack/_inc/data/products/use-activate-multiple.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { __, sprintf } from '@wordpress/i18n'; -import useAnalytics from '../../hooks/use-analytics'; -import { REST_API_SITE_PRODUCTS_ENDPOINT, QUERY_ACTIVATE_PRODUCT_KEY } from '../constants'; -import useSimpleMutation from '../use-simple-mutation'; -import { getMyJetpackWindowInitialState } from '../utils/get-my-jetpack-window-state'; -import useProducts from './use-products'; -import type { ProductCamelCase } from '../types'; - -const setPluginActiveState = ( productId: string ) => { - const { items } = getMyJetpackWindowInitialState( 'products' ); - if ( items[ productId ]?.standalone_plugin_info.has_standalone_plugin ) { - window.myJetpackInitialState.products.items[ - productId - ].standalone_plugin_info.is_standalone_active = true; - window.myJetpackInitialState.products.items[ - productId - ].standalone_plugin_info.is_standalone_installed = true; - } -}; - -const getIsPluginAlreadyActive = ( detail: ProductCamelCase ) => { - const { standalonePluginInfo, isPluginActive } = detail; - - if ( standalonePluginInfo?.hasStandalonePlugin ) { - return standalonePluginInfo?.isStandaloneActive; - } - - return isPluginActive; -}; - -const useActivateMultiple = ( productIds: string[] ) => { - const { products, refetch } = useProducts( productIds ); - const { recordEvent } = useAnalytics(); - - const { - mutate: activate, - isPending, - isSuccess, - } = useSimpleMutation( { - name: QUERY_ACTIVATE_PRODUCT_KEY, - query: { - path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/activate-multiple-plugins`, - method: 'POST', - data: { products: productIds }, - }, - options: { - onSuccess: () => { - products.forEach( product => { - if ( ! getIsPluginAlreadyActive( product ) ) { - recordEvent( 'jetpack_myjetpack_product_activated', { - product: product.slug, - } ); - - // This is to handle an edge case where a user is redirected somewhere after activation - // and goes back in the browser and "activates" the product again. This will manually update - // the window state so that the tracking is not recorded twice for one activation. - setPluginActiveState( product.slug ); - } - } ); - refetch(); - }, - }, - errorMessage: sprintf( - // translators: %s is the Jetpack product name or comma seperated list of multiple Jetpack product names. - __( 'There was a problem activating %s.', 'jetpack-my-jetpack' ), - products?.map( product => product.name ).join( ', ' ) - ), - } ); - - return { - activate, - isPending, - isSuccess, - }; -}; - -export default useActivateMultiple; diff --git a/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts b/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts deleted file mode 100644 index c24f8fe2827ce..0000000000000 --- a/projects/packages/my-jetpack/_inc/data/products/use-install-multiple-standalone-plugins.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { __, sprintf } from '@wordpress/i18n'; -import { REST_API_SITE_PRODUCTS_ENDPOINT, QUERY_INSTALL_PRODUCT_KEY } from '../constants'; -import useSimpleMutation from '../use-simple-mutation'; -import useProducts from './use-products'; - -const useInstallMultipleStandalonePlugins = ( productIds: string[] ) => { - const { products, refetch } = useProducts( productIds ); - - const { mutate: install, isPending } = useSimpleMutation( { - name: QUERY_INSTALL_PRODUCT_KEY, - query: { - path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/install-multiple-plugins`, - method: 'POST', - data: { products: productIds }, - }, - options: { - onSuccess: refetch, - }, - errorMessage: sprintf( - // translators: %s is the Jetpack product name or comma seperated list of multiple Jetpack product names. - __( 'There was a problem installing and activating %s.', 'jetpack-my-jetpack' ), - products?.map( product => product.name ).join( ', ' ) - ), - } ); - - return { - install, - isPending, - }; -}; - -export default useInstallMultipleStandalonePlugins; diff --git a/projects/packages/my-jetpack/src/class-rest-products.php b/projects/packages/my-jetpack/src/class-rest-products.php index 46dd57aefbf36..470b9cd7a9bf2 100644 --- a/projects/packages/my-jetpack/src/class-rest-products.php +++ b/projects/packages/my-jetpack/src/class-rest-products.php @@ -49,36 +49,6 @@ public function __construct() { 'validate_callback' => __CLASS__ . '::check_products_argument', ); - register_rest_route( - 'my-jetpack/v1', - 'site/products/install-multiple-plugins', - array( - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::install_multiple_plugins', - 'permission_callback' => __CLASS__ . '::edit_permissions_callback', - 'args' => array( - 'products' => $products_arg, - ), - ), - ) - ); - - register_rest_route( - 'my-jetpack/v1', - 'site/products/activate-multiple-plugins', - array( - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::activate_multiple_products', - 'permission_callback' => __CLASS__ . '::edit_permissions_callback', - 'args' => array( - 'products' => $products_arg, - ), - ), - ) - ); - register_rest_route( 'my-jetpack/v1', 'site/products/install', @@ -365,37 +335,4 @@ public static function install_plugins( $request ) { return rest_ensure_response( Products::get_products( $products_array ) ); } - - /** - * Callback for installing (and activating) multiple product plugins. - * - * @param \WP_REST_Request $request The request object. - * @return \WP_REST_Response - */ - public static function install_multiple_plugins( $request ) { - $products_array = $request->get_param( 'products' ); - - foreach ( $products_array as $product_slug ) { - $product = Products::get_product( $product_slug ); - if ( ! isset( $product['class'] ) ) { - return new \WP_Error( - 'product_class_handler_not_found', - sprintf( - /* translators: %s is the product_slug */ - __( 'The product slug %s does not have an associated class handler.', 'jetpack-my-jetpack' ), - $product_slug - ), - array( 'status' => 501 ) - ); - } - - $install_product_result = call_user_func( array( $product['class'], 'install_and_activate_standalone' ) ); - if ( is_wp_error( $install_product_result ) ) { - $install_product_result->add_data( array( 'status' => 400 ) ); - return $install_product_result; - } - } - - return rest_ensure_response( Products::get_products( $products_array ), 200 ); - } } From c89b7d511bfeed938ad8d54726533ff87aba2e7a Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Fri, 24 Jan 2025 11:50:36 -0500 Subject: [PATCH 06/18] Remove single product endpoints from mj REST_Products. --- projects/packages/my-jetpack/src/class-rest-products.php | 6 +++--- .../packages/my-jetpack/tests/php/test-products-rest.php | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/packages/my-jetpack/src/class-rest-products.php b/projects/packages/my-jetpack/src/class-rest-products.php index 470b9cd7a9bf2..c997c2280a510 100644 --- a/projects/packages/my-jetpack/src/class-rest-products.php +++ b/projects/packages/my-jetpack/src/class-rest-products.php @@ -179,11 +179,11 @@ public static function check_products_string( $value ) { * @access public * @static * - * @param array $products_array - An array of product slug strings. + * @param mixed $value - Value of the 'product' argument. * @return true|WP_Error True if the value is valid, WP_Error otherwise. */ - public static function check_products_argument( $products_array ) { - if ( ! is_array( $products_array ) ) { + public static function check_products_argument( $value ) { + if ( ! is_array( $value ) ) { return new WP_Error( 'rest_invalid_param', esc_html__( 'The product argument must be an array.', 'jetpack-my-jetpack' ), diff --git a/projects/packages/my-jetpack/tests/php/test-products-rest.php b/projects/packages/my-jetpack/tests/php/test-products-rest.php index 6c302c669a3b5..490183ab6e63d 100644 --- a/projects/packages/my-jetpack/tests/php/test-products-rest.php +++ b/projects/packages/my-jetpack/tests/php/test-products-rest.php @@ -176,6 +176,7 @@ public function test_get_product() { $response = $this->server->dispatch( $request ); $data = $response->get_data(); + print_r( $data ); $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( $product, $data['boost'] ); From 6b9a030a15000f7b2882eb97861185097ba943f4 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:36:33 -0700 Subject: [PATCH 07/18] Update unowned section to list --- .../products-table-view/ListButton.tsx | 57 +++++++++ .../category-filter-control.tsx | 108 ++++++++++++++++++ .../components/products-table-view/style.scss | 2 +- 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx new file mode 100644 index 0000000000000..25288956a0fbb --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx @@ -0,0 +1,57 @@ +import { useCallback } from 'react'; +import { MyJetpackRoutes } from '../../constants'; +import useActivate from '../../data/products/use-activate'; +import useInstallStandalonePlugin from '../../data/products/use-install-standalone-plugin'; +import useProduct from '../../data/products/use-product'; +import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; +import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; +import useMyJetpackNavigate from '../../hooks/use-my-jetpack-navigate'; +import ActionButton from '../product-card/action-button'; +import type { ListButtonProps } from './types'; +import type { FC } from 'react'; + +const ListButton: FC< ListButtonProps > = ( { slug } ) => { + const { isRegistered, isUserConnected } = useMyJetpackConnection(); + const { install: installStandalonePlugin, isPending: isInstalling } = + useInstallStandalonePlugin( slug ); + const { detail, isLoading: isProductDataLoading } = useProduct( slug ); + const { activate, isPending: isActivating } = useActivate( slug ); + + const { userIsAdmin: admin } = getMyJetpackWindowInitialState(); + const { name, description, status, requiresUserConnection } = detail; + + const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); + + /* + * Redirect only if connected + */ + const handleActivate = useCallback( () => { + if ( ( ! isRegistered || ! isUserConnected ) && requiresUserConnection ) { + navigateToConnectionPage(); + return; + } + + activate(); + }, [ + activate, + isRegistered, + isUserConnected, + requiresUserConnection, + navigateToConnectionPage, + ] ); + + return ( + + ); +}; + +export default ListButton; diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx new file mode 100644 index 0000000000000..9c0a648be5c80 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx @@ -0,0 +1,108 @@ +import { Button } from '@automattic/jetpack-components'; +import { + __experimentalToggleGroupControl as ToggleGroupControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis + __experimentalToggleGroupControlOption as ToggleGroupControlOption, // eslint-disable-line @wordpress/no-unsafe-wp-apis +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useCallback, useState } from 'react'; +import { PRODUCT_TABLE_CATEGORY } from './constants'; +import type { ProductData } from './types'; +import type { View } from '@wordpress/dataviews'; + +type Category = Window[ 'myJetpackInitialState' ][ 'products' ][ 'items' ][ 0 ][ 'category' ] | ''; + +const CategoryFilterControl = ( { + data, + view, + onChangeView, +}: { + data: ProductData[]; + view: View; + onChangeView: ( newView: View ) => void; +} ) => { + const [ selectedValue, setSelectedValue ] = useState< Category >( '' ); + const [ isOptionsVisible, setIsOptionsVisible ] = useState( false ); + + const allUniqueCategories = data.reduce( ( categories, { product } ) => { + const { category } = product; + if ( ! categories.includes( category ) ) { + categories.push( category ); + } + return categories; + }, [] as Category[] ); + + const onCategoryFilterChange = useCallback( + ( value: Category ) => { + const newFilters = view.filters.filter( filter => filter.field !== PRODUCT_TABLE_CATEGORY ); + + if ( value ) { + newFilters.push( { + field: PRODUCT_TABLE_CATEGORY, + operator: 'is', + value, + } ); + } + + onChangeView( { + ...view, + filters: newFilters, + } ); + setSelectedValue( value ); + setIsOptionsVisible( false ); + }, + [ onChangeView, view ] + ); + + const toggleOptionsVisibility = useCallback( () => { + setIsOptionsVisible( ! isOptionsVisible ); + }, [ isOptionsVisible ] ); + + const capitalizeLabel = ( label: string ) => { + return label.charAt( 0 ).toUpperCase() + label.slice( 1 ); + }; + + const clearFilter = useCallback( () => { + onCategoryFilterChange( '' ); + }, [ onCategoryFilterChange ] ); + + return ( + <> + { selectedValue && ( +
+ + { __( 'Filter:', 'jetpack-my-jetpack' ) } { capitalizeLabel( selectedValue ) } + + +
+ ) } + { ! selectedValue && ( + + ) } + { isOptionsVisible && ( +
+ + { allUniqueCategories.map( category => { + return ( + + ); + } ) } + +
+ ) } + + ); +}; + +export default CategoryFilterControl; diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss index 85916a4eeecc5..88d68bc560aba 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss @@ -97,4 +97,4 @@ div.dataviews-filters__summary-chip-container div.dataviews-filters__summary-chi div.dataviews-filters__summary-chip-container div.dataviews-filters__summary-chip.has-values:hover, div.dataviews-filters__summary-chip-container button.dataviews-filters__summary-chip-remove.has-values:hover { background-color: var(--tag-color); -} \ No newline at end of file +} From b1e7b72994f2b86d7bf711342184e74e64d405d1 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:38:40 -0700 Subject: [PATCH 08/18] changelog --- .../products-table-view/ListButton.tsx | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx deleted file mode 100644 index 25288956a0fbb..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/ListButton.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useCallback } from 'react'; -import { MyJetpackRoutes } from '../../constants'; -import useActivate from '../../data/products/use-activate'; -import useInstallStandalonePlugin from '../../data/products/use-install-standalone-plugin'; -import useProduct from '../../data/products/use-product'; -import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; -import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; -import useMyJetpackNavigate from '../../hooks/use-my-jetpack-navigate'; -import ActionButton from '../product-card/action-button'; -import type { ListButtonProps } from './types'; -import type { FC } from 'react'; - -const ListButton: FC< ListButtonProps > = ( { slug } ) => { - const { isRegistered, isUserConnected } = useMyJetpackConnection(); - const { install: installStandalonePlugin, isPending: isInstalling } = - useInstallStandalonePlugin( slug ); - const { detail, isLoading: isProductDataLoading } = useProduct( slug ); - const { activate, isPending: isActivating } = useActivate( slug ); - - const { userIsAdmin: admin } = getMyJetpackWindowInitialState(); - const { name, description, status, requiresUserConnection } = detail; - - const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); - - /* - * Redirect only if connected - */ - const handleActivate = useCallback( () => { - if ( ( ! isRegistered || ! isUserConnected ) && requiresUserConnection ) { - navigateToConnectionPage(); - return; - } - - activate(); - }, [ - activate, - isRegistered, - isUserConnected, - requiresUserConnection, - navigateToConnectionPage, - ] ); - - return ( - - ); -}; - -export default ListButton; From 4c16515639889fab663a3ce1ccf8581e0ce20c74 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Thu, 30 Jan 2025 08:45:49 -0700 Subject: [PATCH 09/18] Remove filter : --- .../category-filter-control.tsx | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx deleted file mode 100644 index 9c0a648be5c80..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/category-filter-control.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { Button } from '@automattic/jetpack-components'; -import { - __experimentalToggleGroupControl as ToggleGroupControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis - __experimentalToggleGroupControlOption as ToggleGroupControlOption, // eslint-disable-line @wordpress/no-unsafe-wp-apis -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useCallback, useState } from 'react'; -import { PRODUCT_TABLE_CATEGORY } from './constants'; -import type { ProductData } from './types'; -import type { View } from '@wordpress/dataviews'; - -type Category = Window[ 'myJetpackInitialState' ][ 'products' ][ 'items' ][ 0 ][ 'category' ] | ''; - -const CategoryFilterControl = ( { - data, - view, - onChangeView, -}: { - data: ProductData[]; - view: View; - onChangeView: ( newView: View ) => void; -} ) => { - const [ selectedValue, setSelectedValue ] = useState< Category >( '' ); - const [ isOptionsVisible, setIsOptionsVisible ] = useState( false ); - - const allUniqueCategories = data.reduce( ( categories, { product } ) => { - const { category } = product; - if ( ! categories.includes( category ) ) { - categories.push( category ); - } - return categories; - }, [] as Category[] ); - - const onCategoryFilterChange = useCallback( - ( value: Category ) => { - const newFilters = view.filters.filter( filter => filter.field !== PRODUCT_TABLE_CATEGORY ); - - if ( value ) { - newFilters.push( { - field: PRODUCT_TABLE_CATEGORY, - operator: 'is', - value, - } ); - } - - onChangeView( { - ...view, - filters: newFilters, - } ); - setSelectedValue( value ); - setIsOptionsVisible( false ); - }, - [ onChangeView, view ] - ); - - const toggleOptionsVisibility = useCallback( () => { - setIsOptionsVisible( ! isOptionsVisible ); - }, [ isOptionsVisible ] ); - - const capitalizeLabel = ( label: string ) => { - return label.charAt( 0 ).toUpperCase() + label.slice( 1 ); - }; - - const clearFilter = useCallback( () => { - onCategoryFilterChange( '' ); - }, [ onCategoryFilterChange ] ); - - return ( - <> - { selectedValue && ( -
- - { __( 'Filter:', 'jetpack-my-jetpack' ) } { capitalizeLabel( selectedValue ) } - - -
- ) } - { ! selectedValue && ( - - ) } - { isOptionsVisible && ( -
- - { allUniqueCategories.map( category => { - return ( - - ); - } ) } - -
- ) } - - ); -}; - -export default CategoryFilterControl; From 7e368e1a1859c3462ab0a4867784c902d3abd7d9 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:28:21 -0700 Subject: [PATCH 10/18] Remove print statement --- projects/packages/my-jetpack/tests/php/test-products-rest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/packages/my-jetpack/tests/php/test-products-rest.php b/projects/packages/my-jetpack/tests/php/test-products-rest.php index 490183ab6e63d..6c302c669a3b5 100644 --- a/projects/packages/my-jetpack/tests/php/test-products-rest.php +++ b/projects/packages/my-jetpack/tests/php/test-products-rest.php @@ -176,7 +176,6 @@ public function test_get_product() { $response = $this->server->dispatch( $request ); $data = $response->get_data(); - print_r( $data ); $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( $product, $data['boost'] ); From 8758c9c807c93ef794981acddd8a3e6017ca7071 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:38:51 -0700 Subject: [PATCH 11/18] Add missing text domain package" --- pnpm-lock.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 108e5ab1b09b7..83fdd16d366e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5321,6 +5321,9 @@ packages: '@automattic/calypso-analytics@1.1.3': resolution: {integrity: sha512-7DiQZLC2wzs5GW4PDXnSngYFNkMC8aB8FXgD5Xqgzo9cQmqo7Ka4sKITsp7b8yBHXrpEtMwCK265UAEQs8wSMg==} + '@automattic/babel-plugin-replace-textdomain@1.0.42': + resolution: {integrity: sha512-LAaahL8khr8BbGft004hum+5lLQ2MiAB1AdfZEiVZkMtiFTJeuCQnzWqHSJ3cSXFUfQVCO5PH0uODU9fWprnhw==} + '@automattic/calypso-color-schemes@3.1.3': resolution: {integrity: sha512-nzs36yfxUOcsD3HvB72IHgdUfIzTRnT7QmF78CBXEREawTEs0uDyELdx/rAOtW/PauxRYRGQ4zeK5c67FWqLxw==} @@ -15476,6 +15479,9 @@ snapshots: hash.js: 1.1.7 tslib: 2.5.0 uuid: 9.0.1 + '@automattic/babel-plugin-replace-textdomain@1.0.42': + dependencies: + debug: 4.4.0 transitivePeerDependencies: - supports-color From 6ac2de29147360fa760c379c214e8ea73ee80bde Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:48:48 -0700 Subject: [PATCH 12/18] Update lockfile --- pnpm-lock.yaml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83fdd16d366e8..67210125f0576 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5318,12 +5318,12 @@ packages: '@automattic/babel-plugin-preserve-i18n@1.0.0': resolution: {integrity: sha512-dRmLP0Ytf2oDNbUbO8MXLKYnPZfqhtFQ8v1hgDo2Fde1Y0bUz2Ll1UmUOHdyZudnrN/8Zt95cG/fIOJ0dxHi8Q==} - '@automattic/calypso-analytics@1.1.3': - resolution: {integrity: sha512-7DiQZLC2wzs5GW4PDXnSngYFNkMC8aB8FXgD5Xqgzo9cQmqo7Ka4sKITsp7b8yBHXrpEtMwCK265UAEQs8wSMg==} - '@automattic/babel-plugin-replace-textdomain@1.0.42': resolution: {integrity: sha512-LAaahL8khr8BbGft004hum+5lLQ2MiAB1AdfZEiVZkMtiFTJeuCQnzWqHSJ3cSXFUfQVCO5PH0uODU9fWprnhw==} + '@automattic/calypso-analytics@1.1.3': + resolution: {integrity: sha512-7DiQZLC2wzs5GW4PDXnSngYFNkMC8aB8FXgD5Xqgzo9cQmqo7Ka4sKITsp7b8yBHXrpEtMwCK265UAEQs8wSMg==} + '@automattic/calypso-color-schemes@3.1.3': resolution: {integrity: sha512-nzs36yfxUOcsD3HvB72IHgdUfIzTRnT7QmF78CBXEREawTEs0uDyELdx/rAOtW/PauxRYRGQ4zeK5c67FWqLxw==} @@ -15471,6 +15471,12 @@ snapshots: '@automattic/babel-plugin-preserve-i18n@1.0.0': {} + '@automattic/babel-plugin-replace-textdomain@1.0.42': + dependencies: + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + '@automattic/calypso-analytics@1.1.3': dependencies: '@automattic/load-script': 1.0.0 @@ -15479,9 +15485,6 @@ snapshots: hash.js: 1.1.7 tslib: 2.5.0 uuid: 9.0.1 - '@automattic/babel-plugin-replace-textdomain@1.0.42': - dependencies: - debug: 4.4.0 transitivePeerDependencies: - supports-color From c0d9bb4af4ffb1053f8b2fc56785e252938285f0 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:20:19 -0700 Subject: [PATCH 13/18] Fix package versions --- pnpm-lock.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67210125f0576..108e5ab1b09b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5318,9 +5318,6 @@ packages: '@automattic/babel-plugin-preserve-i18n@1.0.0': resolution: {integrity: sha512-dRmLP0Ytf2oDNbUbO8MXLKYnPZfqhtFQ8v1hgDo2Fde1Y0bUz2Ll1UmUOHdyZudnrN/8Zt95cG/fIOJ0dxHi8Q==} - '@automattic/babel-plugin-replace-textdomain@1.0.42': - resolution: {integrity: sha512-LAaahL8khr8BbGft004hum+5lLQ2MiAB1AdfZEiVZkMtiFTJeuCQnzWqHSJ3cSXFUfQVCO5PH0uODU9fWprnhw==} - '@automattic/calypso-analytics@1.1.3': resolution: {integrity: sha512-7DiQZLC2wzs5GW4PDXnSngYFNkMC8aB8FXgD5Xqgzo9cQmqo7Ka4sKITsp7b8yBHXrpEtMwCK265UAEQs8wSMg==} @@ -15471,12 +15468,6 @@ snapshots: '@automattic/babel-plugin-preserve-i18n@1.0.0': {} - '@automattic/babel-plugin-replace-textdomain@1.0.42': - dependencies: - debug: 4.4.0 - transitivePeerDependencies: - - supports-color - '@automattic/calypso-analytics@1.1.3': dependencies: '@automattic/load-script': 1.0.0 From 7d547d980a494a50ae7e5c0b521146eae9c7e508 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:00:55 -0700 Subject: [PATCH 14/18] Add chevron to interstitial for mobile view --- .../components/products-table-view/index.tsx | 28 +++++++++++++++++-- .../components/products-table-view/style.scss | 11 ++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx index 820e84ab516d7..72651ad3c12f5 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx @@ -1,6 +1,9 @@ +import { useViewportMatch } from '@wordpress/compose'; import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { __ } from '@wordpress/i18n'; +import { Icon, chevronRight } from '@wordpress/icons'; import { useCallback, useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useAllProducts } from '../../data/products/use-all-products'; import ActionButton from '../action-button'; import { @@ -100,6 +103,8 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { }, [] ); const isItemClickable = useCallback( () => false, [] ); const allProductData = useAllProducts(); + const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); + const navigate = useNavigate(); const baseView: ViewList = { sort: { @@ -177,8 +182,8 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { enableHiding: false, render( { item }: { item: ProductData } ) { const { product } = item; - const Icon = PRODUCT_ICONS[ product.slug ]; - return ; + const ProductIcon = PRODUCT_ICONS[ product.slug ]; + return ; }, }, { @@ -193,7 +198,24 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { const { product } = item; const { slug } = product; - return ; + if ( isMobileViewport ) { + return ( + + ); + } + + return ( + + ); }, }, ]; diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss index 88d68bc560aba..a8db6e272d35d 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss @@ -98,3 +98,14 @@ div.dataviews-filters__summary-chip-container div.dataviews-filters__summary-chi div.dataviews-filters__summary-chip-container button.dataviews-filters__summary-chip-remove.has-values:hover { background-color: var(--tag-color); } + +button.product-list-item-chevron { + display: flex; + align-items: center; + justify-content: center; + + cursor: pointer; + background: transparent; + border: none; + outline-color: var(--jp-green-40); +} From ca080098dbd952f1d8dd8fd347dc614b2f11bfd3 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:13:45 -0700 Subject: [PATCH 15/18] Add mobile chevron tracks event click --- .../my-jetpack/_inc/components/products-table-view/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx index 72651ad3c12f5..5c24d50d4e7e6 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx @@ -5,6 +5,7 @@ import { Icon, chevronRight } from '@wordpress/icons'; import { useCallback, useState, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAllProducts } from '../../data/products/use-all-products'; +import useAnalytics from '../../hooks/use-analytics'; import ActionButton from '../action-button'; import { PRODUCT_TABLE_TITLE, @@ -105,6 +106,7 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { const allProductData = useAllProducts(); const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); const navigate = useNavigate(); + const { recordEvent } = useAnalytics(); const baseView: ViewList = { sort: { From c85ab68014391e247d341304ac172ea064639f30 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:30:46 -0700 Subject: [PATCH 16/18] Add changelog --- .../add-action-dropdown-to-mobile-view-of-unowned-list | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/my-jetpack/changelog/add-action-dropdown-to-mobile-view-of-unowned-list diff --git a/projects/packages/my-jetpack/changelog/add-action-dropdown-to-mobile-view-of-unowned-list b/projects/packages/my-jetpack/changelog/add-action-dropdown-to-mobile-view-of-unowned-list new file mode 100644 index 0000000000000..2eeb497a5ed33 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-action-dropdown-to-mobile-view-of-unowned-list @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Add mobile CTA to DataViews table From 32266ff092bc2791a8f07e39d04d6f7f8261420f Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:33:28 -0700 Subject: [PATCH 17/18] Add accidentally removed function --- .../_inc/components/products-table-view/index.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx index 5c24d50d4e7e6..cae7fb393675d 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx @@ -34,7 +34,7 @@ import type { Operator, Option, } from '@wordpress/dataviews'; -import type { FC } from 'react'; +import type { FC, MouseEvent } from 'react'; import './style.scss'; @@ -134,6 +134,15 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { [ products, allProductData ] ); + const navigateToInterstitial = useCallback( + ( slug: string ) => ( event: MouseEvent< HTMLButtonElement > ) => { + event.preventDefault(); + recordEvent( `jetpack_myjetpack_product_list_item_${ slug }_learnmore_mobile_click` ); + navigate( `add-${ slug }` ); + }, + [ navigate, recordEvent ] + ); + const fields = useMemo( () => { return [ { From 2e4a61ab0a1da4ffa61a384113e42206297d3ed8 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:48:48 -0700 Subject: [PATCH 18/18] Remove chevron padding and fix rebase issue --- .../my-jetpack/_inc/components/products-table-view/index.tsx | 2 +- .../my-jetpack/_inc/components/products-table-view/style.scss | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx index cae7fb393675d..33975137425b2 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/index.tsx @@ -234,7 +234,7 @@ const ProductsTableView: FC< ProductsTableViewProps > = ( { products } ) => { // and a 'jumping' of the CTA buttons. Having categories as a dependency here is unnecessary // and leaving it out doesn't cause the values to be incorrect. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); + }, [ isMobileViewport, navigateToInterstitial ] ); const [ view, setView ] = useState< View >( { type: 'list', diff --git a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss index a8db6e272d35d..6b64a9bb190e3 100644 --- a/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss +++ b/projects/packages/my-jetpack/_inc/components/products-table-view/style.scss @@ -58,6 +58,7 @@ div.dataviews-wrapper { .dataviews-view-list__fields { justify-content: space-between; + flex-wrap: nowrap; } &:not(.is-selected).is-hovered, @@ -103,6 +104,7 @@ button.product-list-item-chevron { display: flex; align-items: center; justify-content: center; + padding: 0; cursor: pointer; background: transparent;