From b1b49b4e5c43271d39b81d79bde88f19c776b41f Mon Sep 17 00:00:00 2001 From: Luis Felipe Zaguini Date: Thu, 19 Mar 2026 20:50:47 -0300 Subject: [PATCH 01/13] Launch Site Flow Standardization: Add WP Admin masterbar variations --- pnpm-lock.yaml | 9 ++ projects/packages/explat/package.json | 3 +- .../explat/src/class-rest-controller.php | 11 ++- .../packages/explat/src/client/assignment.ts | 40 ++++----- projects/packages/explat/tsconfig.json | 7 +- projects/packages/explat/webpack.config.js | 11 ++- .../packages/jetpack-mu-wpcom/package.json | 2 + .../src/features/launch-button/index.js | 32 +++++-- .../src/features/launch-button/index.php | 6 +- .../features/launch-button/launch-button.js | 89 +++++++++++++++++++ 10 files changed, 170 insertions(+), 40 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77f872ef6b7..1c3615e4ca6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2218,6 +2218,9 @@ importers: debug: specifier: 4.4.3 version: 4.4.3 + wpcom-proxy-request: + specifier: ^7.0.7 + version: 7.0.7 devDependencies: '@automattic/jetpack-webpack-config': specifier: workspace:* @@ -2683,6 +2686,12 @@ importers: '@automattic/jetpack-components': specifier: workspace:* version: link:../../js-packages/components + '@automattic/jetpack-explat': + specifier: workspace:* + version: link:../explat + '@automattic/jetpack-script-data': + specifier: workspace:* + version: link:../../js-packages/script-data '@automattic/jetpack-shared-extension-utils': specifier: workspace:* version: link:../../js-packages/shared-extension-utils diff --git a/projects/packages/explat/package.json b/projects/packages/explat/package.json index 0868c7b48a1..daee45b14e5 100644 --- a/projects/packages/explat/package.json +++ b/projects/packages/explat/package.json @@ -36,7 +36,8 @@ "@wordpress/api-fetch": "7.42.0", "@wordpress/url": "4.42.0", "cookie": "1.0.2", - "debug": "4.4.3" + "debug": "4.4.3", + "wpcom-proxy-request": "^7.0.7" }, "devDependencies": { "@automattic/jetpack-webpack-config": "workspace:*", diff --git a/projects/packages/explat/src/class-rest-controller.php b/projects/packages/explat/src/class-rest-controller.php index 7ab2682f25a..ac9ed91856e 100644 --- a/projects/packages/explat/src/class-rest-controller.php +++ b/projects/packages/explat/src/class-rest-controller.php @@ -81,16 +81,15 @@ public function register_rest_routes() { * @return WP_REST_Response|WP_Error */ public function get_assignments( $request ) { - $response = null; - $is_user_connected = ( new Jetpack_Connection() )->is_user_connected(); - $platform = $request->get_param( 'platform' ); - $request_path = '/experiments/' . self::EXPLAT_API_VERSION . '/assignments/' . $platform; - $args = array( + $response = null; + $platform = $request->get_param( 'platform' ); + $request_path = '/experiments/' . self::EXPLAT_API_VERSION . '/assignments/' . $platform; + $args = array( 'experiment_name' => $request['experiment_name'], 'anon_id' => $request['anon_id'], ); - if ( $request['as_connected_user'] && $is_user_connected ) { + if ( $request['as_connected_user'] ) { $response = Client::wpcom_json_api_request_as_user( add_query_arg( $args, $request_path ), 'v2' diff --git a/projects/packages/explat/src/client/assignment.ts b/projects/packages/explat/src/client/assignment.ts index 2e35c082573..30090c497ff 100644 --- a/projects/packages/explat/src/client/assignment.ts +++ b/projects/packages/explat/src/client/assignment.ts @@ -1,8 +1,6 @@ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; -import debugFactory from 'debug'; - -const debug = debugFactory( 'jetpack-explat:client:assignment' ); +import wpcomRequest, { canAccessWpcomApis } from 'wpcom-proxy-request'; const fetchExperimentAssignment = ( asConnectedUser = false ) => @@ -13,24 +11,24 @@ const fetchExperimentAssignment = experimentName: string; anonId: string | null; } ): Promise< unknown > => { - if ( ! anonId ) { - debug( 'anonId is null' ); - throw new Error( `Tracking is disabled, can't fetch experimentAssignment` ); - } - - const params = { - experiment_name: experimentName, - anon_id: anonId ?? undefined, - as_connected_user: asConnectedUser, - }; - - debug( 'params', params ); - - const assignmentsRequestUrl = addQueryArgs( 'jetpack/v4/explat/assignments', params ); - - debug( 'assignmentsRequestUrl', assignmentsRequestUrl ); - - return await apiFetch( { path: assignmentsRequestUrl } ); + const platform = experimentName.split( '_' )[ 0 ]; + + return canAccessWpcomApis() + ? wpcomRequest( { + path: addQueryArgs( `/experiments/0.1.0/assignments/${ platform }`, { + experiment_name: experimentName, + anon_id: anonId ?? undefined, + } ), + apiNamespace: 'wpcom/v2', + } ) + : apiFetch( { + path: addQueryArgs( 'jetpack/v4/explat/assignments', { + experiment_name: experimentName, + anon_id: anonId ?? undefined, + as_connected_user: asConnectedUser, + platform, + } ), + } ); }; export const fetchExperimentAssignmentAnonymously = fetchExperimentAssignment( false ); diff --git a/projects/packages/explat/tsconfig.json b/projects/packages/explat/tsconfig.json index 9ba5162d9f6..9189d53015b 100644 --- a/projects/packages/explat/tsconfig.json +++ b/projects/packages/explat/tsconfig.json @@ -2,5 +2,10 @@ "extends": "jetpack-js-tools/tsconfig.base.json", // List all sources and source-containing subdirs. "include": [ "./src/client/**/*" ], - "files": [ "./global.d.ts" ] + "files": [ "./global.d.ts" ], + "compilerOptions": { + "typeRoots": [ "./node_modules/@types/", "src" ], + "sourceMap": true, + "outDir": "./build/" + } } diff --git a/projects/packages/explat/webpack.config.js b/projects/packages/explat/webpack.config.js index 5009637d7f9..34fd40b9721 100644 --- a/projects/packages/explat/webpack.config.js +++ b/projects/packages/explat/webpack.config.js @@ -3,9 +3,7 @@ const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpac module.exports = [ { - entry: { - index: './src/client/index.ts', - }, + entry: './src/client/index.ts', mode: jetpackWebpackConfig.mode, devtool: jetpackWebpackConfig.devtool, output: { @@ -19,7 +17,12 @@ module.exports = [ ...jetpackWebpackConfig.resolve, }, node: false, - plugins: [ ...jetpackWebpackConfig.StandardPlugins() ], + plugins: [ + ...jetpackWebpackConfig.StandardPlugins( { + // Generate `.d.ts` files per tsconfig settings. + ForkTSCheckerPlugin: {}, + } ), + ], module: { strictExportPresence: true, rules: [ diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index 89187d2a08c..a5ba535c32d 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -31,6 +31,8 @@ "@automattic/i18n-utils": "1.2.3", "@automattic/jetpack-base-styles": "workspace:*", "@automattic/jetpack-components": "workspace:*", + "@automattic/jetpack-explat": "workspace:*", + "@automattic/jetpack-script-data": "workspace:*", "@automattic/jetpack-shared-extension-utils": "workspace:*", "@automattic/launchpad": "1.1.5", "@automattic/typography": "1.0.0", diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js index 65a32f30268..c104402555a 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js @@ -1,11 +1,31 @@ -import { wpcomTrackEvent } from '../../common/tracks'; +import { createRoot } from 'react-dom/client'; +import { LaunchButton } from './launch-button'; -document.addEventListener( 'DOMContentLoaded', () => { +/** + * Renders the launch button. + * @return {Promise} + */ +async function renderLaunchButton() { const launchButton = document.querySelector( '#wpadminbar .launch-site' ); if ( ! launchButton ) { return; } - launchButton.addEventListener( 'click', () => { - wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); - } ); -} ); + + const root = createRoot( launchButton ); + root.render( + { + // We have two alternatives here... + + // Keep the user on the same page... + root.unmount(); + launchButton.remove(); + + // ...or reload it to reflect the new state. + // window.location.reload(); + } } + /> + ); +} + +renderLaunchButton(); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php index 8821ac15a24..e34a148e15d 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php @@ -81,9 +81,13 @@ function wpcom_add_launch_button_to_admin_bar( WP_Admin_Bar $admin_bar ) { * Enqueue the necessary styles for the admin bar button. */ function wpcom_enqueue_launch_button_styles() { - if ( ! current_user_can( 'manage_options' ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a context check, not a form submission. + $is_preview = isset( $_GET['preview'] ) && 'true' === sanitize_text_field( wp_unslash( $_GET['preview'] ) ); + + if ( ! current_user_can( 'manage_options' ) || $is_preview ) { return; } + $version = filemtime( __DIR__ . '/style.css' ); wp_enqueue_style( 'launch-banner', plugins_url( 'style.css', __FILE__ ), array(), $version ); $asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/adminbar-launch-button/adminbar-launch-button.asset.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js new file mode 100644 index 00000000000..6229da15a2b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -0,0 +1,89 @@ +import { useExperimentWithAuth } from '@automattic/jetpack-explat'; +import { getSiteData } from '@automattic/jetpack-script-data'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; +import { useState } from 'react'; +import { wpcomTrackEvent } from '../../common/tracks'; +import CelebrateLaunchModal from '../wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal'; + +const icon = ( + + + + +); + +const Content = () => ( + <> + { icon } + { __( 'Launch site', 'jetpack-mu-wpcom' ) } + +); + +/** + * The LaunchButton component. + * @param {object} props - Props + * @param {Function} props.onCelebrationModalClose - Callback on celebration modal close. + * @return {React.ReactNode} The LaunchButton component. + */ +export function LaunchButton( { onCelebrationModalClose } ) { + const [ , data ] = useExperimentWithAuth( 'calypso_standardized_site_launch_gating' ); + const [ showCelebrateLaunchModal, setShowCelebrateLaunchModal ] = useState( false ); + + const siteData = getSiteData(); + + // Default experience. Markup should match what's coming from the back-end. + if ( ! data || data.variationName !== 'ungated_site_launch' ) { + // Maybe this data can come from the server. + const launchUrl = addQueryArgs( 'https://wordpress.com/start/launch-site', { + siteSlug: siteData.suffix, + ref: 'wp-admin', + } ); + + return ( + wpcomTrackEvent( 'wpcom_adminbar_launch_site' ) } + > + + + ); + } + + const launchSite = e => { + e.preventDefault(); + + wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); + + /** + * TODO: Implement launch site mutation. + * Just like the experiment assignment, we'll need to check if + * we're on Simple or Jetpack, then dispatch the request appropriately. + */ + + setShowCelebrateLaunchModal( true ); + }; + + return ( + <> + + + + { showCelebrateLaunchModal && ( + /** + * Feed this component with data from enqueue_wpcom_dashboard_widgets. + * We'll need to refactor that hook to be rendered within launch-button/index.php + * Because not necessarily the user will be browsing the dashboard page, and that + * data is not available elsewhere. + */ + + ) } + + ); +} From c280d19657e667a99b7f68ffb9ec68cf22aa4e65 Mon Sep 17 00:00:00 2001 From: Luis Felipe Zaguini Date: Fri, 20 Mar 2026 23:09:32 -0300 Subject: [PATCH 02/13] Change user agent to assign the experiment in production --- .../explat/src/class-rest-controller.php | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/projects/packages/explat/src/class-rest-controller.php b/projects/packages/explat/src/class-rest-controller.php index ac9ed91856e..a9fef3045bf 100644 --- a/projects/packages/explat/src/class-rest-controller.php +++ b/projects/packages/explat/src/class-rest-controller.php @@ -81,18 +81,24 @@ public function register_rest_routes() { * @return WP_REST_Response|WP_Error */ public function get_assignments( $request ) { - $response = null; - $platform = $request->get_param( 'platform' ); - $request_path = '/experiments/' . self::EXPLAT_API_VERSION . '/assignments/' . $platform; - $args = array( - 'experiment_name' => $request['experiment_name'], - 'anon_id' => $request['anon_id'], + $response = null; + $is_user_connected = ( new Jetpack_Connection() )->is_user_connected(); + $platform = $request->get_param( 'platform' ); + $request_path = '/experiments/' . self::EXPLAT_API_VERSION . '/assignments/' . $platform; + $args = array( + 'experiment_names' => $request['experiment_name'], + 'anon_id' => $request['anon_id'], ); - if ( $request['as_connected_user'] ) { + if ( $request['as_connected_user'] && $is_user_connected ) { $response = Client::wpcom_json_api_request_as_user( add_query_arg( $args, $request_path ), - 'v2' + 'v2', + array( + 'headers' => array( + 'User-Agent' => 'Jetpack MU WPCOM Plugin Experiment Assignment', + ), + ) ); } else { $response = wp_remote_get( From 3a7d5e013ad05b4426ea7ac6acde5659891a93a2 Mon Sep 17 00:00:00 2001 From: Luis Felipe Zaguini Date: Fri, 20 Mar 2026 23:13:06 -0300 Subject: [PATCH 03/13] Address feedback --- projects/packages/explat/src/client/assignment.ts | 2 +- .../jetpack-mu-wpcom/src/features/launch-button/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/packages/explat/src/client/assignment.ts b/projects/packages/explat/src/client/assignment.ts index 30090c497ff..f07c31557af 100644 --- a/projects/packages/explat/src/client/assignment.ts +++ b/projects/packages/explat/src/client/assignment.ts @@ -16,7 +16,7 @@ const fetchExperimentAssignment = return canAccessWpcomApis() ? wpcomRequest( { path: addQueryArgs( `/experiments/0.1.0/assignments/${ platform }`, { - experiment_name: experimentName, + experiment_names: experimentName, anon_id: anonId ?? undefined, } ), apiNamespace: 'wpcom/v2', diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js index c104402555a..990109774a5 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js @@ -28,4 +28,4 @@ async function renderLaunchButton() { ); } -renderLaunchButton(); +document.addEventListener( 'DOMContentLoaded', renderLaunchButton, { once: true } ); From 6f2b32bae098bfde8643634f8a210a87fba5c680 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Fri, 27 Mar 2026 11:56:09 +0100 Subject: [PATCH 04/13] Launch Site Flow Standardization: Implement launch site mutation in WP Admin masterbar Adds the actual API call to the Launch site button for both Simple sites (via wpcom-proxy-request) and Atomic sites (via a new /wpcom/v2/launch-site proxy endpoint that tunnels through the Jetpack connection). Also populates JetpackScriptData.site.wpcom.blog_id for Atomic so the Simple site path has the correct blog ID available. Co-Authored-By: Claude Sonnet 4.6 --- .../src/class-jetpack-mu-wpcom.php | 17 ++++ .../features/launch-button/launch-button.js | 36 ++++++--- ...wpcom-rest-api-v2-endpoint-launch-site.php | 81 +++++++++++++++++++ 3 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php diff --git a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php index 30b28bc87f8..63385633987 100644 --- a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php +++ b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php @@ -87,6 +87,9 @@ public static function init() { // Filter to ensure JetpackScriptData.site.host and is_wpcom_platform is set, to ensure Jetpack blocks work as expected via P2. add_filter( 'jetpack_public_js_script_data', array( __CLASS__, 'add_jetpack_script_data_for_p2' ), 10, 1 ); + // Filter to populate JetpackScriptData.site.wpcom.blog_id with the actual WP.com blog ID. + add_filter( 'jetpack_admin_js_script_data', array( __CLASS__, 'set_wpcom_blog_id_script_data' ), 10, 1 ); + /** * Runs right after the Jetpack_Mu_Wpcom package is initialized. * @@ -747,6 +750,20 @@ public static function load_social_links() { } } + /** + * Populate JetpackScriptData.site.wpcom.blog_id with the actual WP.com blog ID. + * + * @param array $data The script data. + * @return array + */ + public static function set_wpcom_blog_id_script_data( $data ) { + $blog_id = get_wpcom_blog_id(); + if ( $blog_id ) { + $data['site']['wpcom']['blog_id'] = $blog_id; + } + return $data; + } + /** * Add Jetpack script data with host information on P2 * diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index 6229da15a2b..3c299cc2b61 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -1,8 +1,10 @@ import { useExperimentWithAuth } from '@automattic/jetpack-explat'; -import { getSiteData } from '@automattic/jetpack-script-data'; +import { getSiteData, isSimpleSite } from '@automattic/jetpack-script-data'; +import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { useState } from 'react'; +import wpcomRequest, { canAccessWpcomApis } from 'wpcom-proxy-request'; import { wpcomTrackEvent } from '../../common/tracks'; import CelebrateLaunchModal from '../wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal'; @@ -33,6 +35,7 @@ const Content = () => ( export function LaunchButton( { onCelebrationModalClose } ) { const [ , data ] = useExperimentWithAuth( 'calypso_standardized_site_launch_gating' ); const [ showCelebrateLaunchModal, setShowCelebrateLaunchModal ] = useState( false ); + const [ isLaunching, setIsLaunching ] = useState( false ); const siteData = getSiteData(); @@ -56,18 +59,33 @@ export function LaunchButton( { onCelebrationModalClose } ) { ); } - const launchSite = e => { + const launchSite = async e => { e.preventDefault(); - wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); + if ( isLaunching ) { + return; + } - /** - * TODO: Implement launch site mutation. - * Just like the experiment assignment, we'll need to check if - * we're on Simple or Jetpack, then dispatch the request appropriately. - */ + setIsLaunching( true ); + wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); - setShowCelebrateLaunchModal( true ); + try { + if ( canAccessWpcomApis() && isSimpleSite() ) { + await wpcomRequest( { + path: `/sites/${ siteData.wpcom.blog_id }/launch`, + apiVersion: '1.1', + method: 'POST', + } ); + } else { + await apiFetch( { + path: '/wpcom/v2/launch-site', + method: 'POST', + } ); + } + setShowCelebrateLaunchModal( true ); + } finally { + setIsLaunching( false ); + } }; return ( diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php new file mode 100644 index 00000000000..63646d98e6e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php @@ -0,0 +1,81 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'launch-site'; + + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + /** + * Register our routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'launch_site' ), + 'permission_callback' => array( $this, 'can_access' ), + ), + ) + ); + } + + /** + * Permission callback: only admins may launch the site. + * + * @return bool + */ + public function can_access() { + return current_user_can( 'manage_options' ); + } + + /** + * Proxies a launch request to the WPCOM REST API. + * + * @return WP_REST_Response|WP_Error + */ + public function launch_site() { + $blog_id = \Jetpack_Options::get_option( 'id' ); + $response = Client::wpcom_json_api_request_as_user( + '/sites/' . rawurlencode( $blog_id ) . '/launch', + '1.1', + array( 'method' => 'POST' ), + null, + 'rest' + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $body = wp_remote_retrieve_body( $response ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = json_decode( $body ); + + return new WP_REST_Response( $data, $status_code ); + } +} + +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launch_Site' ); From 0bda09469c22fa938d907e2ecbb3118f6a392fd5 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Fri, 27 Mar 2026 12:49:00 +0100 Subject: [PATCH 05/13] Fix fatal error caused by duplicate WPCOM_REST_API_V2_Endpoint_Launch_Site class declaration Guard the class with class_exists() to prevent a fatal redeclaration conflict when the same class is already loaded from WPCOM core's rest-api-plugins on Simple sites. --- ...wpcom-rest-api-v2-endpoint-launch-site.php | 116 +++++++++--------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php index 63646d98e6e..712c4cb722b 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php @@ -10,72 +10,74 @@ use Automattic\Jetpack\Connection\Client; -/** - * Launches the site by proxying to the WPCOM REST API. - */ -class WPCOM_REST_API_V2_Endpoint_Launch_Site extends WP_REST_Controller { - +if ( ! class_exists( 'WPCOM_REST_API_V2_Endpoint_Launch_Site' ) ) { /** - * Class constructor. + * Launches the site by proxying to the WPCOM REST API. */ - public function __construct() { - $this->namespace = 'wpcom/v2'; - $this->rest_base = 'launch-site'; + class WPCOM_REST_API_V2_Endpoint_Launch_Site extends WP_REST_Controller { - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } + /** + * Class constructor. + */ + public function __construct() { + $this->namespace = 'wpcom/v2'; + $this->rest_base = 'launch-site'; - /** - * Register our routes. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + /** + * Register our routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + $this->rest_base, array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'launch_site' ), - 'permission_callback' => array( $this, 'can_access' ), - ), - ) - ); - } + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'launch_site' ), + 'permission_callback' => array( $this, 'can_access' ), + ), + ) + ); + } - /** - * Permission callback: only admins may launch the site. - * - * @return bool - */ - public function can_access() { - return current_user_can( 'manage_options' ); - } + /** + * Permission callback: only admins may launch the site. + * + * @return bool + */ + public function can_access() { + return current_user_can( 'manage_options' ); + } - /** - * Proxies a launch request to the WPCOM REST API. - * - * @return WP_REST_Response|WP_Error - */ - public function launch_site() { - $blog_id = \Jetpack_Options::get_option( 'id' ); - $response = Client::wpcom_json_api_request_as_user( - '/sites/' . rawurlencode( $blog_id ) . '/launch', - '1.1', - array( 'method' => 'POST' ), - null, - 'rest' - ); + /** + * Proxies a launch request to the WPCOM REST API. + * + * @return WP_REST_Response|WP_Error + */ + public function launch_site() { + $blog_id = \Jetpack_Options::get_option( 'id' ); + $response = Client::wpcom_json_api_request_as_user( + '/sites/' . rawurlencode( $blog_id ) . '/launch', + '1.1', + array( 'method' => 'POST' ), + null, + 'rest' + ); - if ( is_wp_error( $response ) ) { - return $response; - } + if ( is_wp_error( $response ) ) { + return $response; + } - $body = wp_remote_retrieve_body( $response ); - $status_code = wp_remote_retrieve_response_code( $response ); - $data = json_decode( $body ); + $body = wp_remote_retrieve_body( $response ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = json_decode( $body ); - return new WP_REST_Response( $data, $status_code ); + return new WP_REST_Response( $data, $status_code ); + } } -} -wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launch_Site' ); + wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launch_Site' ); +} From 3f5c392e58a58d5ae63a1a0403961cfa02309a30 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Fri, 27 Mar 2026 13:54:47 +0100 Subject: [PATCH 06/13] Fix fatal class redeclaration by renaming endpoint class to Jetpack_Launch_Site Rename WPCOM_REST_API_V2_Endpoint_Launch_Site to WPCOM_REST_API_V2_Endpoint_Jetpack_Launch_Site to avoid a fatal conflict with the identically named class in WPCOM core's rest-api-plugins. --- ...st-api-v2-endpoint-jetpack-launch-site.php | 81 ++++++++++++++++++ ...wpcom-rest-api-v2-endpoint-launch-site.php | 83 ------------------- 2 files changed, 81 insertions(+), 83 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php new file mode 100644 index 00000000000..fd590f0f371 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php @@ -0,0 +1,81 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'launch-site'; + + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + /** + * Register our routes. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'launch_site' ), + 'permission_callback' => array( $this, 'can_access' ), + ), + ) + ); + } + + /** + * Permission callback: only admins may launch the site. + * + * @return bool + */ + public function can_access() { + return current_user_can( 'manage_options' ); + } + + /** + * Proxies a launch request to the WPCOM REST API. + * + * @return WP_REST_Response|WP_Error + */ + public function launch_site() { + $blog_id = \Jetpack_Options::get_option( 'id' ); + $response = Client::wpcom_json_api_request_as_user( + '/sites/' . rawurlencode( $blog_id ) . '/launch', + '1.1', + array( 'method' => 'POST' ), + null, + 'rest' + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $body = wp_remote_retrieve_body( $response ); + $status_code = wp_remote_retrieve_response_code( $response ); + $data = json_decode( $body ); + + return new WP_REST_Response( $data, $status_code ); + } +} + +wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Jetpack_Launch_Site' ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php deleted file mode 100644 index 712c4cb722b..00000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launch-site.php +++ /dev/null @@ -1,83 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'launch-site'; - - add_action( 'rest_api_init', array( $this, 'register_routes' ) ); - } - - /** - * Register our routes. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'launch_site' ), - 'permission_callback' => array( $this, 'can_access' ), - ), - ) - ); - } - - /** - * Permission callback: only admins may launch the site. - * - * @return bool - */ - public function can_access() { - return current_user_can( 'manage_options' ); - } - - /** - * Proxies a launch request to the WPCOM REST API. - * - * @return WP_REST_Response|WP_Error - */ - public function launch_site() { - $blog_id = \Jetpack_Options::get_option( 'id' ); - $response = Client::wpcom_json_api_request_as_user( - '/sites/' . rawurlencode( $blog_id ) . '/launch', - '1.1', - array( 'method' => 'POST' ), - null, - 'rest' - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $body = wp_remote_retrieve_body( $response ); - $status_code = wp_remote_retrieve_response_code( $response ); - $data = json_decode( $body ); - - return new WP_REST_Response( $data, $status_code ); - } - } - - wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_Launch_Site' ); -} From 0b1dc50c671c8d368940162643fc4056165b6126 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Fri, 27 Mar 2026 14:43:00 +0100 Subject: [PATCH 07/13] Launch Site Flow: Feed CelebrateLaunchModal with data from launch-button/index.php Enqueue site data (siteUrl, siteDomain, sitePlan, hasCustomDomain) as an inline script in the launch button enqueue hook, so CelebrateLaunchModal has the data it needs regardless of whether the user is on the dashboard page. --- .../src/features/launch-button/index.php | 20 +++++++++++++++++++ .../features/launch-button/launch-button.js | 10 +++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php index e34a148e15d..02b6f010064 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.php @@ -98,6 +98,26 @@ function wpcom_enqueue_launch_button_styles() { $asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . 'build/adminbar-launch-button/adminbar-launch-button.js' ), true ); + + $bundles = function_exists( 'wpcom_get_site_purchases' ) ? wp_list_filter( wpcom_get_site_purchases(), array( 'product_type' => 'bundle' ) ) : array(); + $current_plan = array_pop( $bundles ); + + $launch_button_data = wp_json_encode( + array( + 'siteUrl' => home_url(), + 'siteDomain' => wp_parse_url( home_url(), PHP_URL_HOST ), + 'sitePlan' => $current_plan, + 'hasCustomDomain' => function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'custom-domain' ), + ), + JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP + ); + + wp_add_inline_script( + 'adminbar-launch-button', + "var JETPACK_LAUNCH_BUTTON_DATA = $launch_button_data;", + 'before' + ); + Common\wpcom_enqueue_tracking_scripts( 'adminbar-launch-button' ); } diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index 3c299cc2b61..dd47cf2b8fe 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -26,6 +26,8 @@ const Content = () => ( ); +const launchButtonData = typeof window === 'object' ? window.JETPACK_LAUNCH_BUTTON_DATA : {}; + /** * The LaunchButton component. * @param {object} props - Props @@ -94,13 +96,7 @@ export function LaunchButton( { onCelebrationModalClose } ) { { showCelebrateLaunchModal && ( - /** - * Feed this component with data from enqueue_wpcom_dashboard_widgets. - * We'll need to refactor that hook to be rendered within launch-button/index.php - * Because not necessarily the user will be browsing the dashboard page, and that - * data is not available elsewhere. - */ - + ) } ); From 448d3847cc7bdd3019bcd5945c5effe3c2df78c4 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Fri, 27 Mar 2026 18:18:57 +0100 Subject: [PATCH 08/13] Fix launch site on Atomic: use wpcom_json_api_request_as_user with wpcom/v2 - Switch from as_blog to as_user to allow user-level launch action --- .../src/features/launch-button/launch-button.js | 4 ++-- ...wpcom-rest-api-v2-endpoint-jetpack-launch-site.php | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index dd47cf2b8fe..92710692325 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -1,5 +1,5 @@ import { useExperimentWithAuth } from '@automattic/jetpack-explat'; -import { getSiteData, isSimpleSite } from '@automattic/jetpack-script-data'; +import { getSiteData } from '@automattic/jetpack-script-data'; import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; @@ -72,7 +72,7 @@ export function LaunchButton( { onCelebrationModalClose } ) { wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); try { - if ( canAccessWpcomApis() && isSimpleSite() ) { + if ( canAccessWpcomApis() ) { await wpcomRequest( { path: `/sites/${ siteData.wpcom.blog_id }/launch`, apiVersion: '1.1', diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php index fd590f0f371..dc4bed6991b 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-jetpack-launch-site.php @@ -60,10 +60,15 @@ public function launch_site() { $blog_id = \Jetpack_Options::get_option( 'id' ); $response = Client::wpcom_json_api_request_as_user( '/sites/' . rawurlencode( $blog_id ) . '/launch', - '1.1', - array( 'method' => 'POST' ), + 'v2', + array( + 'method' => 'POST', + 'headers' => array( + 'content-type' => 'application/json', + ), + ), null, - 'rest' + 'wpcom' ); if ( is_wp_error( $response ) ) { From 7c871c73ed8f9dac395a71cfa22225250689f829 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Sat, 28 Mar 2026 00:03:00 +0100 Subject: [PATCH 09/13] Extract launch site API call into shared useLaunchSiteMutation hook Moves the mutation logic out of LaunchButton into a reusable useLaunchSiteMutation hook in common/hooks, so it can be shared with the upcoming Reading Settings launch flow. --- .../src/common/hooks/index.ts | 1 + .../common/hooks/use-launch-site-mutation.ts | 27 ++++++++++++++ .../src/features/launch-button/index.js | 25 +++++++------ .../features/launch-button/launch-button.js | 35 ++++--------------- 4 files changed, 49 insertions(+), 39 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/hooks/use-launch-site-mutation.ts diff --git a/projects/packages/jetpack-mu-wpcom/src/common/hooks/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/hooks/index.ts index f86dec94423..6eb55290520 100644 --- a/projects/packages/jetpack-mu-wpcom/src/common/hooks/index.ts +++ b/projects/packages/jetpack-mu-wpcom/src/common/hooks/index.ts @@ -1,2 +1,3 @@ export { default as useCanvasMode } from './use-canvas-mode'; +export { default as useLaunchSiteMutation } from './use-launch-site-mutation'; export { default as useLocation } from './use-location'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/hooks/use-launch-site-mutation.ts b/projects/packages/jetpack-mu-wpcom/src/common/hooks/use-launch-site-mutation.ts new file mode 100644 index 00000000000..06e11ec54d4 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/hooks/use-launch-site-mutation.ts @@ -0,0 +1,27 @@ +import { getSiteData } from '@automattic/jetpack-script-data'; +import { useMutation } from '@tanstack/react-query'; +import apiFetch from '@wordpress/api-fetch'; +import wpcomRequest, { canAccessWpcomApis } from 'wpcom-proxy-request'; + +const useLaunchSiteMutation = ( onSuccess: () => void ) => { + const siteData = getSiteData(); + + return useMutation( { + mutationFn: () => { + if ( canAccessWpcomApis() ) { + return wpcomRequest( { + path: `/sites/${ siteData.wpcom.blog_id }/launch`, + apiVersion: '1.1', + method: 'POST', + } ); + } + return apiFetch( { + path: '/wpcom/v2/launch-site', + method: 'POST', + } ); + }, + onSuccess, + } ); +}; + +export default useLaunchSiteMutation; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js index 990109774a5..f2bc7cd42e7 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js @@ -1,6 +1,9 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { createRoot } from 'react-dom/client'; import { LaunchButton } from './launch-button'; +const queryClient = new QueryClient(); + /** * Renders the launch button. * @return {Promise} @@ -13,18 +16,20 @@ async function renderLaunchButton() { const root = createRoot( launchButton ); root.render( - { - // We have two alternatives here... + + { + // We have two alternatives here... - // Keep the user on the same page... - root.unmount(); - launchButton.remove(); + // Keep the user on the same page... + root.unmount(); + launchButton.remove(); - // ...or reload it to reflect the new state. - // window.location.reload(); - } } - /> + // ...or reload it to reflect the new state. + // window.location.reload(); + } } + /> + ); } diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index 92710692325..25294db1f60 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -1,10 +1,9 @@ import { useExperimentWithAuth } from '@automattic/jetpack-explat'; import { getSiteData } from '@automattic/jetpack-script-data'; -import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { useState } from 'react'; -import wpcomRequest, { canAccessWpcomApis } from 'wpcom-proxy-request'; +import { useLaunchSiteMutation } from '../../common/hooks'; import { wpcomTrackEvent } from '../../common/tracks'; import CelebrateLaunchModal from '../wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal'; @@ -37,10 +36,11 @@ const launchButtonData = typeof window === 'object' ? window.JETPACK_LAUNCH_BUTT export function LaunchButton( { onCelebrationModalClose } ) { const [ , data ] = useExperimentWithAuth( 'calypso_standardized_site_launch_gating' ); const [ showCelebrateLaunchModal, setShowCelebrateLaunchModal ] = useState( false ); - const [ isLaunching, setIsLaunching ] = useState( false ); const siteData = getSiteData(); + const { mutate: launchSite } = useLaunchSiteMutation( () => setShowCelebrateLaunchModal( true ) ); + // Default experience. Markup should match what's coming from the back-end. if ( ! data || data.variationName !== 'ungated_site_launch' ) { // Maybe this data can come from the server. @@ -61,38 +61,15 @@ export function LaunchButton( { onCelebrationModalClose } ) { ); } - const launchSite = async e => { + const handleLaunchClick = e => { e.preventDefault(); - - if ( isLaunching ) { - return; - } - - setIsLaunching( true ); wpcomTrackEvent( 'wpcom_adminbar_launch_site' ); - - try { - if ( canAccessWpcomApis() ) { - await wpcomRequest( { - path: `/sites/${ siteData.wpcom.blog_id }/launch`, - apiVersion: '1.1', - method: 'POST', - } ); - } else { - await apiFetch( { - path: '/wpcom/v2/launch-site', - method: 'POST', - } ); - } - setShowCelebrateLaunchModal( true ); - } finally { - setIsLaunching( false ); - } + launchSite(); }; return ( <> - + { showCelebrateLaunchModal && ( From f86de5b931a1045ec36a25b5bbd1e601be51676a Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Sat, 28 Mar 2026 00:32:41 +0100 Subject: [PATCH 10/13] Move CelebrateLaunchModal to common/ for shared use The modal is now used by both the masterbar launch button and the dashboard widgets, so it belongs in common/ rather than scoped to wpcom-dashboard-widgets. --- .../celebrate-launch => common}/celebrate-launch-modal.js | 2 +- .../celebrate-launch => common}/celebrate-launch-modal.scss | 0 .../celebrate-launch => common}/confetti-animation.ts | 0 .../src/features/launch-button/launch-button.js | 2 +- .../features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename projects/packages/jetpack-mu-wpcom/src/{features/wpcom-dashboard-widgets/celebrate-launch => common}/celebrate-launch-modal.js (98%) rename projects/packages/jetpack-mu-wpcom/src/{features/wpcom-dashboard-widgets/celebrate-launch => common}/celebrate-launch-modal.scss (100%) rename projects/packages/jetpack-mu-wpcom/src/{features/wpcom-dashboard-widgets/celebrate-launch => common}/confetti-animation.ts (100%) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal.js b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js similarity index 98% rename from projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal.js rename to projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js index fc1ea47c9f5..1ddc3e716f5 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal.js +++ b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js @@ -4,8 +4,8 @@ import { useCopyToClipboard } from '@wordpress/compose'; import { useState, useEffect, createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, copy } from '@wordpress/icons'; -import { wpcomTrackEvent } from '../../../common/tracks'; import ConfettiAnimation from './confetti-animation'; +import { wpcomTrackEvent } from './tracks'; import './celebrate-launch-modal.scss'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal.scss b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.scss similarity index 100% rename from projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal.scss rename to projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.scss diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/confetti-animation.ts b/projects/packages/jetpack-mu-wpcom/src/common/confetti-animation.ts similarity index 100% rename from projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/celebrate-launch/confetti-animation.ts rename to projects/packages/jetpack-mu-wpcom/src/common/confetti-animation.ts diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index 25294db1f60..02b4fdbd579 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -3,9 +3,9 @@ import { getSiteData } from '@automattic/jetpack-script-data'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { useState } from 'react'; +import CelebrateLaunchModal from '../../common/celebrate-launch-modal'; import { useLaunchSiteMutation } from '../../common/hooks'; import { wpcomTrackEvent } from '../../common/tracks'; -import CelebrateLaunchModal from '../wpcom-dashboard-widgets/celebrate-launch/celebrate-launch-modal'; const icon = ( diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js index 11e7ea99488..81c45f0debf 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js @@ -1,6 +1,6 @@ import '../../common/public-path'; import { createRoot } from 'react-dom/client'; -import CelebrateLaunchModal from './celebrate-launch/celebrate-launch-modal'; +import CelebrateLaunchModal from '../../common/celebrate-launch-modal'; import WpcomDailyWritingPrompt from './wpcom-daily-writing-prompt'; import WpcomGeneralTasksWidget from './wpcom-general-tasks-widget'; import WpcomLaunchpadWidget from './wpcom-launchpad-widget'; From 45bc4e0ea73f30d41a2405bf12f75392ae83ae2e Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Sat, 28 Mar 2026 00:39:13 +0100 Subject: [PATCH 11/13] Changelog --- .../standardize-site-launch-wp-admin-masterbar-entry-point | 4 ++++ .../standardize-site-launch-wp-admin-masterbar-entry-point | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 projects/packages/explat/changelog/standardize-site-launch-wp-admin-masterbar-entry-point create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/standardize-site-launch-wp-admin-masterbar-entry-point diff --git a/projects/packages/explat/changelog/standardize-site-launch-wp-admin-masterbar-entry-point b/projects/packages/explat/changelog/standardize-site-launch-wp-admin-masterbar-entry-point new file mode 100644 index 00000000000..997603267dd --- /dev/null +++ b/projects/packages/explat/changelog/standardize-site-launch-wp-admin-masterbar-entry-point @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Launch Site: implement direct site launch from the WP Admin masterbar button via an ExPlat experiment, with a shared mutation hook and celebration modal for use across launch entry points. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/standardize-site-launch-wp-admin-masterbar-entry-point b/projects/packages/jetpack-mu-wpcom/changelog/standardize-site-launch-wp-admin-masterbar-entry-point new file mode 100644 index 00000000000..997603267dd --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/standardize-site-launch-wp-admin-masterbar-entry-point @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Launch Site: implement direct site launch from the WP Admin masterbar button via an ExPlat experiment, with a shared mutation hook and celebration modal for use across launch entry points. From ae53c8e1883548616d9d2bc4a7088539e9663b25 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Sat, 28 Mar 2026 01:08:14 +0100 Subject: [PATCH 12/13] Launch site: stay on page after modal close --- .../jetpack-mu-wpcom/src/features/launch-button/index.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js index f2bc7cd42e7..a7ca7bb03ec 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/index.js @@ -19,14 +19,8 @@ async function renderLaunchButton() { { - // We have two alternatives here... - - // Keep the user on the same page... root.unmount(); launchButton.remove(); - - // ...or reload it to reflect the new state. - // window.location.reload(); } } /> From 03c4401c1fc6fd31069b60eab30f98b42bcf5b09 Mon Sep 17 00:00:00 2001 From: Bogdan Nikolic Date: Mon, 30 Mar 2026 17:15:04 +0200 Subject: [PATCH 13/13] Move CelebrateLaunchModal to common/celebrate-launch/ subfolder --- .../src/common/{ => celebrate-launch}/celebrate-launch-modal.js | 2 +- .../common/{ => celebrate-launch}/celebrate-launch-modal.scss | 0 .../src/common/{ => celebrate-launch}/confetti-animation.ts | 0 .../src/features/launch-button/launch-button.js | 2 +- .../features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename projects/packages/jetpack-mu-wpcom/src/common/{ => celebrate-launch}/celebrate-launch-modal.js (99%) rename projects/packages/jetpack-mu-wpcom/src/common/{ => celebrate-launch}/celebrate-launch-modal.scss (100%) rename projects/packages/jetpack-mu-wpcom/src/common/{ => celebrate-launch}/confetti-animation.ts (100%) diff --git a/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/celebrate-launch-modal.js similarity index 99% rename from projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js rename to projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/celebrate-launch-modal.js index 1ddc3e716f5..70e837d7166 100644 --- a/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.js +++ b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/celebrate-launch-modal.js @@ -4,8 +4,8 @@ import { useCopyToClipboard } from '@wordpress/compose'; import { useState, useEffect, createInterpolateElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon, copy } from '@wordpress/icons'; +import { wpcomTrackEvent } from '../tracks'; import ConfettiAnimation from './confetti-animation'; -import { wpcomTrackEvent } from './tracks'; import './celebrate-launch-modal.scss'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.scss b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/celebrate-launch-modal.scss similarity index 100% rename from projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch-modal.scss rename to projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/celebrate-launch-modal.scss diff --git a/projects/packages/jetpack-mu-wpcom/src/common/confetti-animation.ts b/projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/confetti-animation.ts similarity index 100% rename from projects/packages/jetpack-mu-wpcom/src/common/confetti-animation.ts rename to projects/packages/jetpack-mu-wpcom/src/common/celebrate-launch/confetti-animation.ts diff --git a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js index 02b4fdbd579..8e88277be20 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/launch-button/launch-button.js @@ -3,7 +3,7 @@ import { getSiteData } from '@automattic/jetpack-script-data'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { useState } from 'react'; -import CelebrateLaunchModal from '../../common/celebrate-launch-modal'; +import CelebrateLaunchModal from '../../common/celebrate-launch/celebrate-launch-modal'; import { useLaunchSiteMutation } from '../../common/hooks'; import { wpcomTrackEvent } from '../../common/tracks'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js index 81c45f0debf..5d058994196 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.js @@ -1,6 +1,6 @@ import '../../common/public-path'; import { createRoot } from 'react-dom/client'; -import CelebrateLaunchModal from '../../common/celebrate-launch-modal'; +import CelebrateLaunchModal from '../../common/celebrate-launch/celebrate-launch-modal'; import WpcomDailyWritingPrompt from './wpcom-daily-writing-prompt'; import WpcomGeneralTasksWidget from './wpcom-general-tasks-widget'; import WpcomLaunchpadWidget from './wpcom-launchpad-widget';