From d59d127085ecbf515999421a7edfe2120171245c Mon Sep 17 00:00:00 2001 From: Dusty Reagan Date: Thu, 1 Aug 2024 01:06:35 -0500 Subject: [PATCH 01/33] Masterbar: Remove dead Help icon CSS code (#38659) * Remove dead css code * changelog --- .../packages/jetpack-mu-wpcom/changelog/remove-dead-css-code | 4 ++++ .../src/features/wpcom-admin-bar/wpcom-admin-bar.scss | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/remove-dead-css-code diff --git a/projects/packages/jetpack-mu-wpcom/changelog/remove-dead-css-code b/projects/packages/jetpack-mu-wpcom/changelog/remove-dead-css-code new file mode 100644 index 0000000000000..d9686a1d86f25 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/remove-dead-css-code @@ -0,0 +1,4 @@ +Significance: patch +Type: removed + +Removed dead CSS code diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss index 30e7cb5942849..354b07f96df31 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss @@ -116,11 +116,6 @@ margin-right: 0 !important; .ab-item { width: 52px; - svg { - width: 36px; - height: 36px; - padding: 4px 8px; - } } } From 760d6c3b146b6b1c0c184ea99c9efd65cfbdeccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20M=C3=A1rk=20Juh=C3=A1sz?= <36671565+gmjuhasz@users.noreply.github.com> Date: Thu, 1 Aug 2024 08:30:33 +0200 Subject: [PATCH 02/33] Social: Add feature flag management (#38375) * Add feature flag management for social and useEditorPreview flag * changelog * Move check inside publicize active check * Fixup versions * Fixup versions * Put modal below notice * update naming --- .../changelog/add-feature-flag-management | 4 ++++ .../src/components/form/index.tsx | 4 +++- .../src/social-store/reducer/index.js | 1 + .../src/social-store/selectors/index.js | 1 + .../src/social-store/types.ts | 1 + .../changelog/add-feature-flag-management | 4 ++++ projects/packages/publicize/composer.json | 2 +- projects/packages/publicize/package.json | 2 +- .../publicize/src/class-publicize-base.php | 20 +++++++++++++++++++ .../class-settings.php | 20 +++++++++++++++++++ .../changelog/add-feature-flag-management | 4 ++++ .../jetpack/class.jetpack-gutenberg.php | 2 ++ projects/plugins/jetpack/composer.lock | 4 ++-- .../changelog/add-feature-flag-management | 4 ++++ projects/plugins/social/composer.lock | 4 ++-- .../social/src/class-jetpack-social.php | 2 ++ 16 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 projects/js-packages/publicize-components/changelog/add-feature-flag-management create mode 100644 projects/packages/publicize/changelog/add-feature-flag-management create mode 100644 projects/plugins/jetpack/changelog/add-feature-flag-management create mode 100644 projects/plugins/social/changelog/add-feature-flag-management diff --git a/projects/js-packages/publicize-components/changelog/add-feature-flag-management b/projects/js-packages/publicize-components/changelog/add-feature-flag-management new file mode 100644 index 0000000000000..99b94073d5131 --- /dev/null +++ b/projects/js-packages/publicize-components/changelog/add-feature-flag-management @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added feature flag management for Social diff --git a/projects/js-packages/publicize-components/src/components/form/index.tsx b/projects/js-packages/publicize-components/src/components/form/index.tsx index ecaa3f8f2a29b..c5b6f60d2e212 100644 --- a/projects/js-packages/publicize-components/src/components/form/index.tsx +++ b/projects/js-packages/publicize-components/src/components/form/index.tsx @@ -38,10 +38,11 @@ export default function PublicizeForm() { userConnectionUrl, } = usePublicizeConfig(); - const { useAdminUiV1 } = useSelect( select => { + const { useAdminUiV1, featureFlags } = useSelect( select => { const store = select( socialStore ); return { useAdminUiV1: store.useAdminUiV1(), + featureFlags: store.featureFlags(), }; }, [] ); @@ -62,6 +63,7 @@ export default function PublicizeForm() { + { featureFlags.useEditorPreview ?

New modal trigger goes here

: null } ) : null } diff --git a/projects/js-packages/publicize-components/src/social-store/reducer/index.js b/projects/js-packages/publicize-components/src/social-store/reducer/index.js index abd4003d17c65..e330313125616 100644 --- a/projects/js-packages/publicize-components/src/social-store/reducer/index.js +++ b/projects/js-packages/publicize-components/src/social-store/reducer/index.js @@ -14,6 +14,7 @@ const reducer = combineReducers( { hasPaidPlan: ( state = false ) => state, userConnectionUrl: ( state = '' ) => state, useAdminUiV1: ( state = false ) => state, + featureFlags: ( state = false ) => state, hasPaidFeatures: ( state = false ) => state, connectionRefreshPath: ( state = '' ) => state, } ); diff --git a/projects/js-packages/publicize-components/src/social-store/selectors/index.js b/projects/js-packages/publicize-components/src/social-store/selectors/index.js index 6b196c1b4bc68..ad6608112a2fe 100644 --- a/projects/js-packages/publicize-components/src/social-store/selectors/index.js +++ b/projects/js-packages/publicize-components/src/social-store/selectors/index.js @@ -12,6 +12,7 @@ const selectors = { ...socialImageGeneratorSettingsSelectors, userConnectionUrl: state => state.userConnectionUrl, useAdminUiV1: state => state.useAdminUiV1, + featureFlags: state => state.featureFlags, hasPaidFeatures: state => state.hasPaidFeatures, connectionRefreshPath: state => state.connectionRefreshPath, }; diff --git a/projects/js-packages/publicize-components/src/social-store/types.ts b/projects/js-packages/publicize-components/src/social-store/types.ts index 88344bb91c09b..79ecd3ae19b2d 100644 --- a/projects/js-packages/publicize-components/src/social-store/types.ts +++ b/projects/js-packages/publicize-components/src/social-store/types.ts @@ -60,6 +60,7 @@ export type SocialStoreState = { // on Jetack Social admin page jetpackSettings?: JetpackSettings; useAdminUiV1?: boolean; + featureFlags?: Record< string, boolean >; }; export interface KeyringAdditionalUser { diff --git a/projects/packages/publicize/changelog/add-feature-flag-management b/projects/packages/publicize/changelog/add-feature-flag-management new file mode 100644 index 0000000000000..99b94073d5131 --- /dev/null +++ b/projects/packages/publicize/changelog/add-feature-flag-management @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added feature flag management for Social diff --git a/projects/packages/publicize/composer.json b/projects/packages/publicize/composer.json index 77d85828d891b..c8531e0d9ad1d 100644 --- a/projects/packages/publicize/composer.json +++ b/projects/packages/publicize/composer.json @@ -67,7 +67,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.47.x-dev" + "dev-trunk": "0.48.x-dev" } }, "config": { diff --git a/projects/packages/publicize/package.json b/projects/packages/publicize/package.json index 384d69583cf41..6427de0f6546c 100644 --- a/projects/packages/publicize/package.json +++ b/projects/packages/publicize/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize", - "version": "0.47.4-alpha", + "version": "0.48.0-alpha", "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/publicize/#readme", "bugs": { diff --git a/projects/packages/publicize/src/class-publicize-base.php b/projects/packages/publicize/src/class-publicize-base.php index e4195b728af71..ecd58ce0523db 100644 --- a/projects/packages/publicize/src/class-publicize-base.php +++ b/projects/packages/publicize/src/class-publicize-base.php @@ -289,6 +289,26 @@ public function use_admin_ui_v1(): bool { || $this->has_connections_management_feature(); } + /** + * Whether the site has the feature flag enabled. + * + * @param string $flag_name The feature flag to check. Will be prefixed with 'jetpack_social_has_' for the option. + * @param string $plan_name The plan name to check for for the Current_Plan check, without the social- prefix. + * @return bool + */ + public function has_feature_flag( $flag_name, $plan_name ): bool { + // If the option is set, use it. + if ( get_option( 'jetpack_social_has_' . $flag_name, false ) ) { + return true; + } + // If the constant is set, use it. + if ( defined( 'JETPACK_SOCIAL_HAS_' . strtoupper( $flag_name ) ) && constant( 'JETPACK_SOCIAL_HAS_' . strtoupper( $flag_name ) ) ) { + return true; + } + + return Current_Plan::supports( 'social-' . $plan_name ); + } + /** * Does the given user have a connection to the service on the given blog? * diff --git a/projects/packages/publicize/src/jetpack-social-settings/class-settings.php b/projects/packages/publicize/src/jetpack-social-settings/class-settings.php index fb35561fa55b8..ebf92075473ab 100644 --- a/projects/packages/publicize/src/jetpack-social-settings/class-settings.php +++ b/projects/packages/publicize/src/jetpack-social-settings/class-settings.php @@ -38,6 +38,22 @@ class Settings { 'enabled' => true, ); + /** + * Feature flags. Each item has 3 keys because of the naming conventions: + * - flag_name: The name of the feature flag for the option check. + * - plan_name: The name of the plan that enables the feature. Will be checked with Current_Plan. + * - variable_name: The name of the variable that will be used in the front-end. + * + * @var array + */ + const FEATURE_FLAGS = array( + array( + 'flag_name' => 'editor_preview', + 'plan_name' => 'editor-preview', + 'variable_name' => 'useEditorPreview', + ), + ); + /** * Migrate old options to the new settings. Previously SIG settings were stored in the * jetpack_social_image_generator_settings option. Now they are stored in the jetpack_social_settings @@ -199,6 +215,10 @@ public function get_initial_state() { $settings['is_publicize_enabled'] = true; $settings['hasPaidFeatures'] = $publicize->has_paid_features(); + + foreach ( self::FEATURE_FLAGS as $feature_flag ) { + $settings['featureFlags'][ $feature_flag['variable_name'] ] = $publicize->has_feature_flag( $feature_flag['flag_name'], $feature_flag['plan_name'] ); + } } else { $settings['connectionData'] = array( 'connections' => array(), diff --git a/projects/plugins/jetpack/changelog/add-feature-flag-management b/projects/plugins/jetpack/changelog/add-feature-flag-management new file mode 100644 index 0000000000000..82e1d667e93d8 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-feature-flag-management @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Social: Added feature flag to initial state diff --git a/projects/plugins/jetpack/class.jetpack-gutenberg.php b/projects/plugins/jetpack/class.jetpack-gutenberg.php index 6067fb8a629d1..496778d9c229f 100644 --- a/projects/plugins/jetpack/class.jetpack-gutenberg.php +++ b/projects/plugins/jetpack/class.jetpack-gutenberg.php @@ -771,6 +771,8 @@ public static function enqueue_block_editor_assets() { $initial_state['social']['connectionRefreshPath'] = $social_initial_state['connectionRefreshPath']; } + + $initial_state['social']['featureFlags'] = $social_initial_state['featureFlags']; } wp_localize_script( diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index 1df2073d5d6c4..6fdc7c986ee24 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -2223,7 +2223,7 @@ "dist": { "type": "path", "url": "../../packages/publicize", - "reference": "6494d867225a31d465c41e86117becbe7a35c11e" + "reference": "af5dd80bea134427f6b4d5bc8c0a95452afb17c1" }, "require": { "automattic/jetpack-assets": "@dev", @@ -2251,7 +2251,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.47.x-dev" + "dev-trunk": "0.48.x-dev" } }, "autoload": { diff --git a/projects/plugins/social/changelog/add-feature-flag-management b/projects/plugins/social/changelog/add-feature-flag-management new file mode 100644 index 0000000000000..99b94073d5131 --- /dev/null +++ b/projects/plugins/social/changelog/add-feature-flag-management @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added feature flag management for Social diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index f1b4256fc3297..deaa2e7adc22f 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -1495,7 +1495,7 @@ "dist": { "type": "path", "url": "../../packages/publicize", - "reference": "6494d867225a31d465c41e86117becbe7a35c11e" + "reference": "af5dd80bea134427f6b4d5bc8c0a95452afb17c1" }, "require": { "automattic/jetpack-assets": "@dev", @@ -1523,7 +1523,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.47.x-dev" + "dev-trunk": "0.48.x-dev" } }, "autoload": { diff --git a/projects/plugins/social/src/class-jetpack-social.php b/projects/plugins/social/src/class-jetpack-social.php index a347f7c8bf74a..60c80835cdced 100644 --- a/projects/plugins/social/src/class-jetpack-social.php +++ b/projects/plugins/social/src/class-jetpack-social.php @@ -352,6 +352,8 @@ class_exists( 'Jetpack' ) || $initial_state['connectionRefreshPath'] = $social_state['connectionRefreshPath']; } + $initial_state['featureFlags'] = $social_state['featureFlags']; + wp_localize_script( 'jetpack-social-editor', 'Jetpack_Editor_Initial_State', From 1723da10dda28538032429ac8f314ce56dbd867d Mon Sep 17 00:00:00 2001 From: Louis Laugesen Date: Thu, 1 Aug 2024 17:21:17 +1000 Subject: [PATCH 03/33] MU WPCOM: Port the wpcom block editor nux feature from the ETK (#38446) * Add wpcom-block-editor-nux from wp-calypso's ETK * Add feature entrypoints * Move loader to load_etk_features * Js != jsx * Package updates * changelog * Update namespace * Use wpcomTrackEvent to track events * Port @automattic/tour-kit package and fix build * Fix blogging prompts api path * Fix lint * Fix getPrimarySiteDomain is undefined * Fix inline script is not printed * Remove @automattic/calypso-analytics deps * Fix lint * Fix Optimization seems to have broken the following translation strings --------- Co-authored-by: arthur --- pnpm-lock.yaml | 47 ++ .../changelog/wpcom-block-editor-nux | 4 + .../packages/jetpack-mu-wpcom/package.json | 7 +- .../src/class-jetpack-mu-wpcom.php | 1 + .../components/keyboard-navigation.tsx | 61 +++ .../tour-kit/components/tour-kit-frame.tsx | 286 +++++++++++ .../components/tour-kit-minimized.tsx | 30 ++ .../tour-kit/components/tour-kit-overlay.tsx | 20 + .../tour-kit-spotlight-interactivity.tsx | 38 ++ .../components/tour-kit-spotlight.tsx | 122 +++++ .../tour-kit/components/tour-kit-step.tsx | 52 ++ .../common/tour-kit/components/tour-kit.tsx | 40 ++ .../src/common/tour-kit/constants.ts | 3 + ...-seen-seller-celebration-modal-context.tsx | 68 +++ ...s-seen-video-celebration-modal-context.tsx | 68 +++ .../src/common/tour-kit/contexts/index.ts | 4 + ...how-first-post-published-modal-context.jsx | 31 ++ .../tour-kit/contexts/tour-kit-context.tsx | 16 + .../src/common/tour-kit/error-boundary.tsx | 33 ++ .../src/common/tour-kit/hooks/index.ts | 9 + .../tour-kit/hooks/use-focus-handler.ts | 60 +++ .../common/tour-kit/hooks/use-focus-trap.ts | 56 +++ .../use-has-selected-payment-block-once.js | 62 +++ .../tour-kit/hooks/use-keydown-handler.ts | 52 ++ ...se-should-show-seller-celebration-modal.js | 67 +++ ...use-should-show-video-celebration-modal.ts | 51 ++ .../common/tour-kit/hooks/use-site-intent.js | 14 + .../common/tour-kit/hooks/use-site-plan.js | 24 + .../tour-kit/hooks/use-step-tracking.ts | 26 + .../src/common/tour-kit/index.ts | 6 + .../src/common/tour-kit/styles.scss | 91 ++++ .../src/common/tour-kit/types.ts | 164 +++++++ .../src/common/tour-kit/utils/index.ts | 16 + .../tour-kit/utils/live-resize-modifier.tsx | 111 +++++ .../components/wpcom-tour-kit-minimized.tsx | 50 ++ .../wpcom-tour-kit-pagination-control.scss | 44 ++ .../wpcom-tour-kit-pagination-control.tsx | 49 ++ .../components/wpcom-tour-kit-rating.tsx | 73 +++ .../wpcom-tour-kit-step-card-navigation.tsx | 62 +++ ...om-tour-kit-step-card-overlay-controls.tsx | 41 ++ .../components/wpcom-tour-kit-step-card.tsx | 109 +++++ .../wpcom/components/wpcom-tour-kit-step.tsx | 28 ++ .../wpcom/components/wpcom-tour-kit.tsx | 29 ++ .../wpcom/hooks/use-prefetch-tour-assets.tsx | 16 + .../variants/wpcom/icons/maximize.tsx | 13 + .../variants/wpcom/icons/minimize.tsx | 14 + .../variants/wpcom/icons/thumbs_down.tsx | 14 + .../variants/wpcom/icons/thumbs_up.tsx | 14 + .../common/tour-kit/variants/wpcom/index.ts | 2 + .../tour-kit/variants/wpcom/styles.scss | 232 +++++++++ .../features/wpcom-block-editor-nux/README.md | 7 + ...-first-post-published-modal-controller.php | 94 ++++ ...com-block-editor-nux-status-controller.php | 126 +++++ ...or-seller-celebration-modal-controller.php | 101 ++++ ...-block-editor-sharing-modal-controller.php | 89 ++++ ...tor-video-celebration-modal-controller.php | 101 ++++ .../class-wpcom-block-editor-nux.php | 78 +++ .../features/wpcom-block-editor-nux/index.js | 6 + .../src/block-editor-nux.js | 137 ++++++ .../src/blogging-prompts-modal/icons.js | 13 + .../src/blogging-prompts-modal/index.js | 106 +++++ .../src/blogging-prompts-modal/style.scss | 53 +++ .../src/disable-core-nux.js | 43 ++ .../draft-post-modal/images/draft-post.svg | 20 + .../src/draft-post-modal/index.js | 50 ++ .../src/draft-post-modal/style.scss | 17 + .../images/post-published.svg | 15 + .../src/first-post-published-modal/index.tsx | 117 +++++ .../src/first-post-published-modal/style.scss | 23 + .../src/nux-modal/index.tsx | 58 +++ .../src/nux-modal/style.scss | 96 ++++ .../src/purchase-notice/index.jsx | 34 ++ .../src/purchase-notice/style.scss | 15 + .../images/product-published.svg | 37 ++ .../src/seller-celebration-modal/index.jsx | 139 ++++++ .../src/seller-celebration-modal/style.scss | 27 ++ .../src/sharing-modal/clipboard-button.tsx | 47 ++ .../src/sharing-modal/form-checkbox.tsx | 19 + .../src/sharing-modal/form-label.tsx | 37 ++ .../src/sharing-modal/images/illo-share.svg | 24 + .../src/sharing-modal/index.tsx | 291 ++++++++++++ .../src/sharing-modal/inline-social-logo.tsx | 48 ++ .../inline-social-logos-sprite.tsx | 286 +++++++++++ .../src/sharing-modal/style.scss | 173 +++++++ .../src/sharing-modal/suggested-tags.tsx | 132 ++++++ .../src/sharing-modal/use-add-tags-to-post.ts | 31 ++ .../use-sharing-modal-dismissed.ts | 24 + .../wpcom-block-editor-nux/src/store.js | 150 ++++++ .../src/test/store.test.js | 127 +++++ .../src/video-celebration-modal/index.jsx | 113 +++++ .../src/video-celebration-modal/style.scss | 37 ++ .../video-celebration-modal/video-success.svg | 56 +++ .../src/welcome-modal/images/block-picker.svg | 39 ++ .../src/welcome-modal/images/editor.svg | 21 + .../src/welcome-modal/images/preview.svg | 16 + .../src/welcome-modal/images/private.svg | 26 + .../src/welcome-modal/style.scss | 208 ++++++++ .../src/welcome-modal/wpcom-nux.js | 153 ++++++ .../src/welcome-tour/get-editor-type.ts | 41 ++ .../src/welcome-tour/style-tour.scss | 51 ++ .../src/welcome-tour/test/tour-steps.test.ts | 110 +++++ .../src/welcome-tour/tour-launch.jsx | 243 ++++++++++ .../src/welcome-tour/use-tour-steps.tsx | 445 ++++++++++++++++++ .../jetpack-mu-wpcom/webpack.config.js | 1 + 104 files changed, 7080 insertions(+), 1 deletion(-) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/form-checkbox.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/form-label.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/images/illo-share.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/index.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx create mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d163c92cb441..9591209e2002f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2194,6 +2194,9 @@ importers: projects/packages/jetpack-mu-wpcom: dependencies: + '@automattic/calypso-color-schemes': + specifier: 3.1.3 + version: 3.1.3 '@automattic/color-studio': specifier: 2.6.0 version: 2.6.0 @@ -2212,6 +2215,9 @@ importers: '@automattic/typography': specifier: 1.0.0 version: 1.0.0 + '@popperjs/core': + specifier: ^2.11.8 + version: 2.11.8 '@preact/signals': specifier: ^1.2.2 version: 1.3.0(preact@10.22.1) @@ -2254,15 +2260,24 @@ importers: '@wordpress/plugins': specifier: 7.2.0 version: 7.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/private-apis': + specifier: ^1.2.0 + version: 1.2.0 '@wordpress/url': specifier: 4.2.0 version: 4.2.0 clsx: specifier: 2.1.1 version: 2.1.1 + debug: + specifier: 4.3.4 + version: 4.3.4 preact: specifier: ^10.13.1 version: 10.22.1 + react-popper: + specifier: ^2.3.0 + version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) redux: specifier: ^4.2.1 version: 4.2.1 @@ -6369,6 +6384,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@preact/signals-core@1.7.0': resolution: {integrity: sha512-bEZLgmJGSBVP5PUPDowhPW3bVdMmp9Tr5OEl+SQK+8Tv9T7UsIfyN905cfkmmeqw8z4xp8T6zrl4M1uj9+HAfg==} @@ -12851,6 +12869,9 @@ packages: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -12869,6 +12890,13 @@ packages: peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-popper@2.3.0: + resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} + peerDependencies: + '@popperjs/core': ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + react-redux@7.2.8: resolution: {integrity: sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==} peerDependencies: @@ -14379,6 +14407,9 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -16483,6 +16514,8 @@ snapshots: dependencies: playwright: 1.45.1 + '@popperjs/core@2.11.8': {} + '@preact/signals-core@1.7.0': {} '@preact/signals@1.3.0(preact@10.22.1)': @@ -26084,6 +26117,8 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 + react-fast-compare@3.2.2: {} + react-is@16.13.1: {} react-is@17.0.2: {} @@ -26097,6 +26132,14 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 + react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@popperjs/core': 2.11.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + warning: 4.0.3 + react-redux@7.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.7 @@ -27811,6 +27854,10 @@ snapshots: dependencies: makeerror: 1.0.12 + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + watchpack@2.4.1: dependencies: glob-to-regexp: 0.4.1 diff --git a/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux b/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux new file mode 100644 index 0000000000000..44b012c3bcd33 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Added wpcom-block-editor-nux feature from calypso's ETK package. diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index 40a68e6784eee..e4e684ca3ad91 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -48,12 +48,14 @@ "webpack-cli": "4.9.1" }, "dependencies": { + "@automattic/calypso-color-schemes": "3.1.3", "@automattic/color-studio": "2.6.0", "@automattic/i18n-utils": "1.2.3", "@automattic/jetpack-base-styles": "workspace:*", "@automattic/jetpack-shared-extension-utils": "workspace:*", "@automattic/typography": "1.0.0", "@automattic/page-pattern-modal": "1.1.5", + "@popperjs/core": "^2.11.8", "@preact/signals": "^1.2.2", "@sentry/browser": "7.80.1", "@tanstack/react-query": "^5.15.5", @@ -66,13 +68,16 @@ "@wordpress/element": "6.2.0", "@wordpress/hooks": "4.2.0", "@wordpress/i18n": "5.2.0", + "@wordpress/icons": "10.2.0", "@wordpress/plugins": "7.2.0", + "@wordpress/private-apis": "^1.2.0", "@wordpress/url": "4.2.0", - "@wordpress/icons": "10.2.0", "clsx": "2.1.1", + "debug": "4.3.4", "preact": "^10.13.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-popper": "^2.3.0", "redux": "^4.2.1", "redux-saga": "^1.3.0", "swiper": "^8.4.5", 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 aa7d9f2f259da..a2749e1cca762 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 @@ -160,6 +160,7 @@ public static function load_etk_features() { require_once __DIR__ . '/features/paragraph-block-placeholder/paragraph-block-placeholder.php'; require_once __DIR__ . '/features/tags-education/tags-education.php'; require_once __DIR__ . '/features/wpcom-block-description-links/wpcom-block-description-links.php'; + require_once __DIR__ . '/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php'; require_once __DIR__ . '/features/wpcom-blocks/a8c-posts-list/a8c-posts-list.php'; require_once __DIR__ . '/features/wpcom-blocks/event-countdown/event-countdown.php'; require_once __DIR__ . '/features/wpcom-blocks/timeline/timeline.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx new file mode 100644 index 0000000000000..7d913d40d3a7f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx @@ -0,0 +1,61 @@ +/** + * External dependencies + */ +import * as React from 'react'; +/** + * Internal dependencies + */ +import useFocusHandler from '../hooks/use-focus-handler'; +import useFocusTrap from '../hooks/use-focus-trap'; +import useKeydownHandler from '../hooks/use-keydown-handler'; + +interface Props { + onMinimize: () => void; + onDismiss: ( target: string ) => () => void; + onNextStepProgression: () => void; + onPreviousStepProgression: () => void; + tourContainerRef: React.MutableRefObject< null | HTMLElement >; + isMinimized: boolean; +} + +const KeyboardNavigation: React.FunctionComponent< Props > = ( { + onMinimize, + onDismiss, + onNextStepProgression, + onPreviousStepProgression, + tourContainerRef, + isMinimized, +} ) => { + /** + * Expand Tour Nav + */ + function ExpandedTourNav() { + useKeydownHandler( { + onEscape: onMinimize, + onArrowRight: onNextStepProgression, + onArrowLeft: onPreviousStepProgression, + } ); + useFocusTrap( tourContainerRef ); + + return null; + } + + /** + * Minimize Tour Nav + */ + function MinimizedTourNav() { + useKeydownHandler( { onEscape: onDismiss( 'esc-key-minimized' ) } ); + + return null; + } + + const isTourFocused = useFocusHandler( tourContainerRef ); + + if ( ! isTourFocused ) { + return null; + } + + return isMinimized ? : ; +}; + +export default KeyboardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx new file mode 100644 index 0000000000000..e030a37cdda26 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx @@ -0,0 +1,286 @@ +/** + * External Dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; +import { useEffect, useState, useCallback, useMemo, useRef } from '@wordpress/element'; +import clsx from 'clsx'; +import { usePopper } from 'react-popper'; +/** + * Internal Dependencies + */ +import useStepTracking from '../hooks/use-step-tracking'; +import { classParser } from '../utils'; +import { liveResizeModifier } from '../utils/live-resize-modifier'; +import KeyboardNavigation from './keyboard-navigation'; +import TourKitMinimized from './tour-kit-minimized'; +import Overlay from './tour-kit-overlay'; +import Spotlight from './tour-kit-spotlight'; +import TourKitStep from './tour-kit-step'; +import type { Callback, Config } from '../types'; + +const handleCallback = ( currentStepIndex: number, callback?: Callback ) => { + typeof callback === 'function' && callback( currentStepIndex ); +}; + +interface Props { + config: Config; +} + +const TourKitFrame: React.FunctionComponent< Props > = ( { config } ) => { + const [ currentStepIndex, setCurrentStepIndex ] = useState( 0 ); + const [ initialFocusedElement, setInitialFocusedElement ] = useState< HTMLElement | null >( + null + ); + const [ isMinimized, setIsMinimized ] = useState( config.isMinimized ?? false ); + + const [ popperElement, setPopperElement ] = useState< HTMLElement | null >( null ); + const [ tourReady, setTourReady ] = useState( false ); + const tourContainerRef = useRef( null ); + const isMobile = useViewportMatch( 'mobile', '<' ); + const lastStepIndex = config.steps.length - 1; + const referenceElements = config.steps[ currentStepIndex ].referenceElements; + const referenceElementSelector = + referenceElements?.[ isMobile ? 'mobile' : 'desktop' ] || referenceElements?.desktop; + const referenceElement = referenceElementSelector + ? document.querySelector< HTMLElement >( referenceElementSelector ) + : null; + + useEffect( () => { + if ( config.isMinimized ) { + setIsMinimized( true ); + } + }, [ config.isMinimized ] ); + + const showArrowIndicator = useCallback( () => { + if ( config.options?.effects?.arrowIndicator === false ) { + return false; + } + + return !! ( referenceElement && ! isMinimized && tourReady ); + }, [ config.options?.effects?.arrowIndicator, isMinimized, referenceElement, tourReady ] ); + + const showSpotlight = useCallback( () => { + if ( ! config.options?.effects?.spotlight ) { + return false; + } + + return ! isMinimized; + }, [ config.options?.effects?.spotlight, isMinimized ] ); + + const showOverlay = useCallback( () => { + if ( showSpotlight() || ! config.options?.effects?.overlay ) { + return false; + } + + return ! isMinimized; + }, [ config.options?.effects?.overlay, isMinimized, showSpotlight ] ); + + const handleDismiss = useCallback( + ( source: string ) => { + return () => { + config.closeHandler( config.steps, currentStepIndex, source ); + }; + }, + [ config, currentStepIndex ] + ); + + const handleNextStepProgression = useCallback( () => { + let newStepIndex = currentStepIndex; + if ( lastStepIndex > currentStepIndex ) { + newStepIndex = currentStepIndex + 1; + setCurrentStepIndex( newStepIndex ); + } + handleCallback( newStepIndex, config.options?.callbacks?.onNextStep ); + }, [ config.options?.callbacks?.onNextStep, currentStepIndex, lastStepIndex ] ); + + const handlePreviousStepProgression = useCallback( () => { + let newStepIndex = currentStepIndex; + if ( currentStepIndex > 0 ) { + newStepIndex = currentStepIndex - 1; + setCurrentStepIndex( newStepIndex ); + } + handleCallback( newStepIndex, config.options?.callbacks?.onPreviousStep ); + }, [ config.options?.callbacks?.onPreviousStep, currentStepIndex ] ); + + const handleGoToStep = useCallback( + ( stepIndex: number ) => { + setCurrentStepIndex( stepIndex ); + handleCallback( stepIndex, config.options?.callbacks?.onGoToStep ); + }, + [ config.options?.callbacks?.onGoToStep ] + ); + + const handleMinimize = useCallback( () => { + setIsMinimized( true ); + handleCallback( currentStepIndex, config.options?.callbacks?.onMinimize ); + }, [ config.options?.callbacks?.onMinimize, currentStepIndex ] ); + + const handleMaximize = useCallback( () => { + setIsMinimized( false ); + handleCallback( currentStepIndex, config.options?.callbacks?.onMaximize ); + }, [ config.options?.callbacks?.onMaximize, currentStepIndex ] ); + + const { + styles: popperStyles, + attributes: popperAttributes, + update: popperUpdate, + } = usePopper( referenceElement, popperElement, { + strategy: 'fixed', + placement: config?.placement ?? 'bottom', + modifiers: [ + { + name: 'preventOverflow', + options: { + rootBoundary: 'document', + padding: 16, // same as the left/margin of the tour frame + }, + }, + { + name: 'arrow', + options: { + padding: 12, + }, + }, + { + name: 'offset', + options: { + offset: [ 0, showArrowIndicator() ? 12 : 10 ], + }, + }, + { + name: 'flip', + options: { + fallbackPlacements: [ 'top', 'left', 'right' ], + }, + }, + useMemo( + () => liveResizeModifier( config.options?.effects?.liveResize ), + [ config.options?.effects?.liveResize ] + ), + ...( config.options?.popperModifiers || [] ), + ], + } ); + + const stepRepositionProps = + ! isMinimized && referenceElement && tourReady + ? { + style: popperStyles?.popper, + ...popperAttributes?.popper, + } + : null; + + const arrowPositionProps = + ! isMinimized && referenceElement && tourReady + ? { + style: popperStyles?.arrow, + ...popperAttributes?.arrow, + } + : null; + + /* + * Focus first interactive element when step renders. + */ + useEffect( () => { + setTimeout( () => initialFocusedElement?.focus() ); + }, [ initialFocusedElement ] ); + + /* + * Fixes issue with Popper misplacing the instance on mount + * See: https://stackoverflow.com/questions/65585859/react-popper-incorrect-position-on-mount + */ + useEffect( () => { + // If no reference element to position step near + if ( ! referenceElement ) { + setTourReady( true ); + return; + } + + setTourReady( false ); + + if ( popperUpdate ) { + popperUpdate() + .then( () => setTourReady( true ) ) + .catch( () => setTourReady( true ) ); + } + }, [ popperUpdate, referenceElement ] ); + + useEffect( () => { + if ( referenceElement && config.options?.effects?.autoScroll ) { + referenceElement.scrollIntoView( config.options.effects.autoScroll ); + } + }, [ config.options?.effects?.autoScroll, referenceElement ] ); + + const classes = clsx( + 'tour-kit-frame', + isMobile ? 'is-mobile' : 'is-desktop', + { 'is-visible': tourReady }, + classParser( config.options?.classNames ) + ); + + useStepTracking( currentStepIndex, config.options?.callbacks?.onStepViewOnce ); + + useEffect( () => { + if ( config.options?.callbacks?.onStepView ) { + handleCallback( currentStepIndex, config.options?.callbacks?.onStepView ); + } + }, [ config.options?.callbacks?.onStepView, currentStepIndex ] ); + + return ( + <> + +
+ { showOverlay() && } + { showSpotlight() && ( + + ) } +
) } + > + { showArrowIndicator() && ( +
) } + /> + ) } + { ! isMinimized ? ( + + ) : ( + + ) } +
+
+ + ); +}; + +export default TourKitFrame; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx new file mode 100644 index 0000000000000..6831fbf8f6051 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx @@ -0,0 +1,30 @@ +/** + * Internal Dependencies + */ +import { MinimizedTourRendererProps } from '../types'; +import type { Config } from '../types'; + +interface Props extends MinimizedTourRendererProps { + config: Config; +} + +const TourKitMinimized: React.FunctionComponent< Props > = ( { + config, + steps, + currentStepIndex, + onMaximize, + onDismiss, +} ) => { + return ( +
+ +
+ ); +}; + +export default TourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx new file mode 100644 index 0000000000000..3c440bcdc2de7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx @@ -0,0 +1,20 @@ +/** + * External Dependencies + */ +import clsx from 'clsx'; + +interface Props { + visible: boolean; +} + +const TourKitOverlay: React.FunctionComponent< Props > = ( { visible } ) => { + return ( +
+ ); +}; + +export default TourKitOverlay; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx new file mode 100644 index 0000000000000..61b7e94bca203 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx @@ -0,0 +1,38 @@ +import { SPOTLIT_ELEMENT_CLASS } from './tour-kit-spotlight'; + +export interface SpotlightInteractivityConfiguration { + /** If true, the user will be allowed to interact with the spotlit element. Defaults to false. */ + enabled?: boolean; + /** + * This element is the root element within which all children will have + * pointer-events disabled during the tour. Defaults to '#wpwrap' + */ + rootElementSelector?: string; +} + +export const SpotlightInteractivity: React.VFC< SpotlightInteractivityConfiguration > = ( { + enabled = false, + rootElementSelector = '#wpwrap', +}: SpotlightInteractivityConfiguration ) => { + if ( ! enabled ) { + return null; + } + return ( + + ); +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx new file mode 100644 index 0000000000000..81e9ed80ff131 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx @@ -0,0 +1,122 @@ +import { useMemo, useState, useEffect } from '@wordpress/element'; +import clsx from 'clsx'; +import { usePopper } from 'react-popper'; +import { LiveResizeConfiguration, liveResizeModifier } from '../utils/live-resize-modifier'; +import Overlay from './tour-kit-overlay'; +import { + SpotlightInteractivity, + SpotlightInteractivityConfiguration, +} from './tour-kit-spotlight-interactivity'; +import type { Rect, Placement } from '@popperjs/core'; + +export const SPOTLIT_ELEMENT_CLASS = 'wp-tour-kit-spotlit'; +interface Props { + referenceElement: HTMLElement | null; + styles?: React.CSSProperties; + interactivity?: SpotlightInteractivityConfiguration; + liveResize?: LiveResizeConfiguration; +} + +const TourKitSpotlight: React.FunctionComponent< Props > = ( { + referenceElement, + styles, + interactivity, + liveResize, +} ) => { + const [ popperElement, sePopperElement ] = useState< HTMLElement | null >( null ); + const referenceRect = referenceElement?.getBoundingClientRect(); + + const modifiers = [ + { + name: 'flip', + enabled: false, + }, + { + name: 'preventOverflow', + options: { + mainAxis: false, // true by default + }, + }, + useMemo( + () => ( { + name: 'offset', + options: { + offset: ( { + placement, + reference, + popper, + }: { + placement: Placement; + reference: Rect; + popper: Rect; + } ): [ number, number ] => { + if ( placement === 'bottom' ) { + return [ 0, -( reference.height + ( popper.height - reference.height ) / 2 ) ]; + } + return [ 0, 0 ]; + }, + }, + } ), + [] + ), + // useMemo because https://popper.js.org/react-popper/v2/faq/#why-i-get-render-loop-whenever-i-put-a-function-inside-the-popper-configuration + useMemo( () => { + return liveResizeModifier( liveResize ); + }, [ liveResize ] ), + ]; + + const { styles: popperStyles, attributes: popperAttributes } = usePopper( + referenceElement, + popperElement, + { + strategy: 'fixed', + placement: 'bottom', + modifiers, + } + ); + + const clipDimensions = referenceRect + ? { + width: `${ referenceRect.width }px`, + height: `${ referenceRect.height }px`, + } + : null; + + const clipRepositionProps = referenceElement + ? { + style: { + ...( clipDimensions && clipDimensions ), + ...popperStyles?.popper, + ...( styles && styles ), + }, + ...popperAttributes?.popper, + } + : null; + + /** + * Add a .wp-spotlit class to the referenced element so that we can + * apply CSS styles to it, for whatever purposes such as interactivity + */ + useEffect( () => { + referenceElement?.classList.add( SPOTLIT_ELEMENT_CLASS ); + return () => { + referenceElement?.classList.remove( SPOTLIT_ELEMENT_CLASS ); + }; + }, [ referenceElement ] ); + + return ( + <> + + +
) } + /> + + ); +}; + +export default TourKitSpotlight; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx new file mode 100644 index 0000000000000..ded21b229ed03 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx @@ -0,0 +1,52 @@ +/** + * External Dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; +import clsx from 'clsx'; +/** + * Internal Dependencies + */ +import { classParser } from '../utils'; +import type { Config, TourStepRendererProps } from '../types'; + +interface Props extends TourStepRendererProps { + config: Config; +} + +const TourKitStep: React.FunctionComponent< Props > = ( { + config, + steps, + currentStepIndex, + onMinimize, + onDismiss, + onNextStep, + onPreviousStep, + setInitialFocusedElement, + onGoToStep, +} ) => { + const isMobile = useViewportMatch( 'mobile', '<' ); + const classes = clsx( + 'tour-kit-step', + `is-step-${ currentStepIndex }`, + classParser( + config.steps[ currentStepIndex ].options?.classNames?.[ isMobile ? 'mobile' : 'desktop' ] + ) + ); + + return ( +
+ +
+ ); +}; + +export default TourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx new file mode 100644 index 0000000000000..97d0fea0f31ec --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx @@ -0,0 +1,40 @@ +import { createPortal, useEffect, useRef } from '@wordpress/element'; +import React from 'react'; +import { TourKitContextProvider } from '../contexts'; +import ErrorBoundary from '../error-boundary'; +import TourKitFrame from './tour-kit-frame'; +import type { Config } from '../types'; + +import '../styles.scss'; + +interface Props { + config: Config; + __temp__className?: string; +} + +const TourKit: React.FunctionComponent< Props > = ( { config, __temp__className } ) => { + const portalParent = useRef( document.createElement( 'div' ) ).current; + + useEffect( () => { + const classes = [ 'tour-kit', ...( __temp__className ? [ __temp__className ] : [] ) ]; + + portalParent.classList.add( ...classes ); + + const portalParentElement = config.options?.portalParentElement || document.body; + portalParentElement.appendChild( portalParent ); + + return () => { + portalParentElement.removeChild( portalParent ); + }; + }, [ __temp__className, portalParent, config.options?.portalParentElement ] ); + + return ( + + +
{ createPortal( , portalParent ) }
+
+
+ ); +}; + +export default TourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts new file mode 100644 index 0000000000000..b8522601535a0 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts @@ -0,0 +1,3 @@ +// Copied from https://github.com/Automattic/wp-calypso/blob/ce1a376af4bcc8987855ced4c961efacda6e1d32/packages/onboarding/src/utils/flows.ts#L32-L33 +export const START_WRITING_FLOW = 'start-writing'; +export const DESIGN_FIRST_FLOW = 'design-first'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx new file mode 100644 index 0000000000000..94bc6c49e7e13 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx @@ -0,0 +1,68 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +type HasSeenSCModalResult = { + has_seen_seller_celebration_modal: boolean; +}; + +type HasSeenSellerCelebrationModalContextType = { + hasSeenSellerCelebrationModal: boolean; + updateHasSeenSellerCelebrationModal: ( value: boolean ) => void; +}; + +const HasSeenSCModalContext = React.createContext< HasSeenSellerCelebrationModalContextType >( { + hasSeenSellerCelebrationModal: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + updateHasSeenSellerCelebrationModal: () => {}, +} ); + +export const useHasSeenSellerCelebrationModal = () => { + return useContext( HasSeenSCModalContext ); +}; + +export const HasSeenSellerCelebrationModalProvider: React.FC< { children: JSX.Element } > = + function ( { children } ) { + const [ hasSeenSellerCelebrationModal, setHasSeenSellerCelebrationModal ] = useState( false ); + + /** + * Fetch the value that whether the video celebration modal has been seen. + */ + function fetchHasSeenSellerCelebrationModal() { + apiFetch< HasSeenSCModalResult >( { + path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', + } ) + .then( ( result: HasSeenSCModalResult ) => + setHasSeenSellerCelebrationModal( result.has_seen_seller_celebration_modal ) + ) + .catch( () => setHasSeenSellerCelebrationModal( false ) ); + } + + /** + * Update the value that whether the video celebration modal has been seen. + * + * @param value - The value to update. + */ + function updateHasSeenSellerCelebrationModal( value: boolean ) { + apiFetch( { + method: 'PUT', + path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', + data: { has_seen_seller_celebration_modal: value }, + } ).finally( () => { + setHasSeenSellerCelebrationModal( true ); + } ); + } + + useEffect( () => { + fetchHasSeenSellerCelebrationModal(); + }, [] ); + + return ( + + { children } + + ); + }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx new file mode 100644 index 0000000000000..18b9345434ec6 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx @@ -0,0 +1,68 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +type HasSeenVCModalResult = { + has_seen_video_celebration_modal: boolean; +}; + +type HasSeenVideoCelebrationModalContextType = { + hasSeenVideoCelebrationModal: boolean; + updateHasSeenVideoCelebrationModal: ( value: boolean ) => void; +}; + +const HasSeenVCModalContext = React.createContext< HasSeenVideoCelebrationModalContextType >( { + hasSeenVideoCelebrationModal: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + updateHasSeenVideoCelebrationModal: () => {}, +} ); + +export const useHasSeenVideoCelebrationModal = () => { + return useContext( HasSeenVCModalContext ); +}; + +export const HasSeenVideoCelebrationModalProvider: React.FC< { children: JSX.Element } > = + function ( { children } ) { + const [ hasSeenVideoCelebrationModal, setHasSeenVideoCelebrationModal ] = useState( false ); + + useEffect( () => { + fetchHasSeenVideoCelebrationModal(); + }, [] ); + + /** + * Fetch the value that whether the video celebration modal has been seen. + */ + function fetchHasSeenVideoCelebrationModal() { + apiFetch< HasSeenVCModalResult >( { + path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', + } ) + .then( ( result: HasSeenVCModalResult ) => + setHasSeenVideoCelebrationModal( result.has_seen_video_celebration_modal ) + ) + .catch( () => setHasSeenVideoCelebrationModal( false ) ); + } + + /** + * Update the value that whether the video celebration modal has been seen. + * + * @param value - The value to update. + */ + function updateHasSeenVideoCelebrationModal( value: boolean ) { + apiFetch( { + method: 'PUT', + path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', + data: { has_seen_video_celebration_modal: value }, + } ).finally( () => { + setHasSeenVideoCelebrationModal( value ); + } ); + } + + return ( + + { children } + + ); + }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts new file mode 100644 index 0000000000000..21d21c340259d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts @@ -0,0 +1,4 @@ +export * from './has-seen-seller-celebration-modal-context'; +export * from './has-seen-video-celebration-modal-context'; +export * from './should-show-first-post-published-modal-context'; +export * from './tour-kit-context'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx new file mode 100644 index 0000000000000..d9b5b30e8ee0f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx @@ -0,0 +1,31 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import * as React from 'react'; +import { useContext } from 'react'; + +const ShouldShowFPPModalContext = React.createContext( false ); + +export const useShouldShowFirstPostPublishedModal = () => { + return useContext( ShouldShowFPPModalContext ); +}; + +export const ShouldShowFirstPostPublishedModalProvider = ( { children } ) => { + const value = useSelect( + select => select( 'automattic/wpcom-welcome-guide' ).getShouldShowFirstPostPublishedModal(), + [] + ); + + const { fetchShouldShowFirstPostPublishedModal } = useDispatch( + 'automattic/wpcom-welcome-guide' + ); + + useEffect( () => { + fetchShouldShowFirstPostPublishedModal(); + }, [ fetchShouldShowFirstPostPublishedModal ] ); + + return ( + + { children } + + ); +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx new file mode 100644 index 0000000000000..392911e574750 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx @@ -0,0 +1,16 @@ +import { createContext, useContext } from '@wordpress/element'; +import type { Config } from '../types'; + +interface TourKitContext { + config: Config; +} + +const TourKitContext = createContext< TourKitContext >( {} as TourKitContext ); + +export const TourKitContextProvider: React.FunctionComponent< + TourKitContext & { children?: React.ReactNode } +> = ( { config, children } ) => { + return { children }; +}; + +export const useTourKitContext = (): TourKitContext => useContext( TourKitContext ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx new file mode 100644 index 0000000000000..250ce916b1496 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx @@ -0,0 +1,33 @@ +import React, { ErrorInfo } from 'react'; + +type State = { + hasError: boolean; +}; + +class ErrorBoundary extends React.Component< { children: React.ReactNode }, State > { + state = { + hasError: false, + }; + + static getDerivedStateFromError() { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch( error: Error, errorInfo: ErrorInfo ) { + // You can also log the error to an error reporting service + // eslint-disable-next-line no-console + console.error( error, errorInfo ); + } + + render() { + if ( this.state.hasError ) { + // You can render any custom fallback UI + return

Something went wrong.

; + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts new file mode 100644 index 0000000000000..7c2fc8e6c2b2b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts @@ -0,0 +1,9 @@ +export { default as useFocusHandle } from './use-focus-handler'; +export { default as useFocusTrap } from './use-focus-trap'; +export { default as useHasSelectedPaymentBlockOnce } from './use-has-selected-payment-block-once'; +export { default as useKeydownHandler } from './use-keydown-handler'; +export { default as useShouldShowSellerCelebrationModal } from './use-should-show-seller-celebration-modal'; +export { default as useShouldShowVideoCelebrationModal } from './use-should-show-video-celebration-modal'; +export { default as useSiteIntent } from './use-site-intent'; +export { default as useSitePlan } from './use-site-plan'; +export { default as useStepTracking } from './use-step-tracking'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts new file mode 100644 index 0000000000000..7e54ac45673dc --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts @@ -0,0 +1,60 @@ +/** + * External Dependencies + */ +import { useEffect, useCallback, useState } from '@wordpress/element'; + +/** + * A hook that returns true/false if ref node receives focus by either tabbing or clicking into any of its children. + * @param ref - React.MutableRefObject< null | HTMLElement > + */ +const useFocusHandler = ( ref: React.MutableRefObject< null | HTMLElement > ): boolean => { + const [ hasFocus, setHasFocus ] = useState( false ); + + const handleFocus = useCallback( () => { + if ( document.hasFocus() && ref.current?.contains( document.activeElement ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + }, [ ref ] ); + + const handleMousedown = useCallback( + ( event: MouseEvent ) => { + if ( ref.current?.contains( event.target as Node ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + }, + [ ref ] + ); + + const handleKeyup = useCallback( + ( event: KeyboardEvent ) => { + if ( event.key === 'Tab' ) { + if ( ref.current?.contains( event.target as Node ) ) { + setHasFocus( true ); + } else { + setHasFocus( false ); + } + } + }, + [ ref ] + ); + + useEffect( () => { + document.addEventListener( 'focusin', handleFocus ); + document.addEventListener( 'mousedown', handleMousedown ); + document.addEventListener( 'keyup', handleKeyup ); + + return () => { + document.removeEventListener( 'focusin', handleFocus ); + document.removeEventListener( 'mousedown', handleMousedown ); + document.removeEventListener( 'keyup', handleKeyup ); + }; + }, [ ref, handleFocus, handleKeyup, handleMousedown ] ); + + return hasFocus; +}; + +export default useFocusHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts new file mode 100644 index 0000000000000..b8e0bc7b313a9 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts @@ -0,0 +1,56 @@ +/** + * External Dependencies + */ +import { focus } from '@wordpress/dom'; +import { useEffect, useCallback, useState } from '@wordpress/element'; +/** + * A hook that constraints tabbing/focus on focuable elements in the given element ref. + * @param ref - React.MutableRefObject< null | HTMLElement > + */ +const useFocusTrap = ( ref: React.MutableRefObject< null | HTMLElement > ): void => { + const [ firstFocusableElement, setFirstFocusableElement ] = useState< HTMLElement | undefined >(); + const [ lastFocusableElement, setLastFocusableElement ] = useState< HTMLElement | undefined >(); + + const handleTrapFocus = useCallback( + ( event: KeyboardEvent ) => { + let handled = false; + + if ( event.key === 'Tab' ) { + if ( event.shiftKey ) { + // Shift + Tab + if ( document.activeElement === firstFocusableElement ) { + lastFocusableElement?.focus(); + handled = true; + } + } else if ( document.activeElement === lastFocusableElement ) { + // Tab + firstFocusableElement?.focus(); + handled = true; + } + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + [ firstFocusableElement, lastFocusableElement ] + ); + + useEffect( () => { + const focusableElements = ref.current ? focus.focusable.find( ref.current as HTMLElement ) : []; + + if ( focusableElements && focusableElements.length ) { + setFirstFocusableElement( focusableElements[ 0 ] as HTMLElement ); + setLastFocusableElement( focusableElements[ focusableElements.length - 1 ] as HTMLElement ); + } + + document.addEventListener( 'keydown', handleTrapFocus ); + + return () => { + document.removeEventListener( 'keydown', handleTrapFocus ); + }; + }, [ ref, handleTrapFocus ] ); +}; + +export default useFocusTrap; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js new file mode 100644 index 0000000000000..f8060b9a2d232 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js @@ -0,0 +1,62 @@ +import { useSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; + +/** + * Watch the user's block selection and keep a note if they ever select a payments + * block. + * A payments block is any block with 'payments' in the name, like jetpack/simple-payments + * or jetpack/recurring-payments. + * Selecting a block whose direct parent has 'payments' in the name also counts. + * This is to account for clicking inside the button in a payments block, for example. + * @returns {boolean} Has the user selected a payments block (or a direct descendant) at least once? + */ +const useHasSelectedPaymentBlockOnce = () => { + const [ hasSelectedPaymentsOnce, setHasSelectedPaymentsOnce ] = useState( false ); + + // Get the name of the currently selected block + const selectedBlockName = useSelect( select => { + // Special case: We know we're returning true, so we don't need to find block names. + if ( hasSelectedPaymentsOnce ) { + return ''; + } + + const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); + return selectedBlock?.name ?? ''; + } ); + + // Get the name of the currently selected block's direct parent, if one exists + const parentSelectedBlockName = useSelect( select => { + // Special case: We know we're returning true, so we don't need to find block names. + if ( hasSelectedPaymentsOnce ) { + return ''; + } + + const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); + if ( selectedBlock?.clientId ) { + const parentIds = select( 'core/block-editor' ).getBlockParents( selectedBlock?.clientId ); + if ( parentIds && parentIds.length ) { + const parent = select( 'core/block-editor' ).getBlock( parentIds[ parentIds.length - 1 ] ); + return parent?.name ?? ''; + } + } + return ''; + } ); + + // On selection change, set hasSelectedPaymentsOnce=true if block name or parent's block name contains 'payments' + useEffect( () => { + if ( + ! hasSelectedPaymentsOnce && + ( selectedBlockName.includes( 'payments' ) || parentSelectedBlockName.includes( 'payments' ) ) + ) { + setHasSelectedPaymentsOnce( true ); + } + }, [ + selectedBlockName, + parentSelectedBlockName, + hasSelectedPaymentsOnce, + setHasSelectedPaymentsOnce, + ] ); + + return hasSelectedPaymentsOnce; +}; +export default useHasSelectedPaymentBlockOnce; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts new file mode 100644 index 0000000000000..6ea129fe08f2e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts @@ -0,0 +1,52 @@ +/* eslint-disable jsdoc/require-param */ +/** + * External Dependencies + */ +import { useEffect, useCallback } from '@wordpress/element'; + +interface Props { + onEscape?: () => void; + onArrowRight?: () => void; + onArrowLeft?: () => void; +} + +/** + * A hook the applies the respective callbacks in response to keydown events. + */ +const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): void => { + const handleKeydown = useCallback( + ( event: KeyboardEvent ) => { + let handled = false; + + switch ( event.key ) { + case 'Escape': + onEscape && ( onEscape(), ( handled = true ) ); + break; + case 'ArrowRight': + onArrowRight && ( onArrowRight(), ( handled = true ) ); + break; + case 'ArrowLeft': + onArrowLeft && ( onArrowLeft(), ( handled = true ) ); + break; + default: + break; + } + + if ( handled ) { + event.preventDefault(); + event.stopPropagation(); + } + }, + [ onEscape, onArrowRight, onArrowLeft ] + ); + + useEffect( () => { + document.addEventListener( 'keydown', handleKeydown ); + + return () => { + document.removeEventListener( 'keydown', handleKeydown ); + }; + }, [ handleKeydown ] ); +}; + +export default useKeydownHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js new file mode 100644 index 0000000000000..80f37b3b530cf --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js @@ -0,0 +1,67 @@ +import { useSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; +import { useHasSeenSellerCelebrationModal } from '../contexts/has-seen-seller-celebration-modal-context'; +import useHasSelectedPaymentBlockOnce from './use-has-selected-payment-block-once'; +import useSiteIntent from './use-site-intent'; + +const useShouldShowSellerCelebrationModal = () => { + const [ shouldShowSellerCelebrationModal, setShouldShowSellerCelebrationModal ] = + useState( false ); + + const { siteIntent: intent } = useSiteIntent(); + const hasSelectedPaymentsOnce = useHasSelectedPaymentBlockOnce(); + + const { hasSeenSellerCelebrationModal } = useHasSeenSellerCelebrationModal(); + + const hasPaymentsBlock = useSelect( select => { + const isSiteEditor = !! select( 'core/edit-site' ); + + if ( isSiteEditor ) { + const page = select( 'core/edit-site' ).getPage(); + const pageId = parseInt( page?.context?.postId ); + const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); + + let paymentsBlock = false; + // Only check for payment blocks if we haven't seen the celebration modal text yet + if ( ! hasSeenSellerCelebrationModal ) { + const didCountRecurringPayments = + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/recurring-payments' ) > 0; + const didCountSimplePayments = + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ) > 0; + paymentsBlock = + ( pageEntity?.content?.raw?.includes( '' ) || + pageEntity?.content?.raw?.includes( '' ) || + didCountRecurringPayments || + didCountSimplePayments ) ?? + false; + } + + return paymentsBlock; + } + + let paymentBlockCount = 0; + // Only check for payment blocks if we haven't seen the celebration modal yet + if ( ! hasSeenSellerCelebrationModal ) { + paymentBlockCount += select( 'core/block-editor' ).getGlobalBlockCount( + 'jetpack/recurring-payments' + ); + paymentBlockCount += + select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ); + } + + return paymentBlockCount > 0; + } ); + + useEffect( () => { + if ( + intent === 'sell' && + hasPaymentsBlock && + hasSelectedPaymentsOnce && + ! hasSeenSellerCelebrationModal + ) { + setShouldShowSellerCelebrationModal( true ); + } + }, [ intent, hasPaymentsBlock, hasSelectedPaymentsOnce, hasSeenSellerCelebrationModal ] ); + return shouldShowSellerCelebrationModal; +}; +export default useShouldShowSellerCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts new file mode 100644 index 0000000000000..ebbc5ce2ce1d2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts @@ -0,0 +1,51 @@ +import { useState, useEffect } from '@wordpress/element'; +import request from 'wpcom-proxy-request'; +import { useHasSeenVideoCelebrationModal } from '../contexts/has-seen-video-celebration-modal-context'; +import useSiteIntent from './use-site-intent'; + +declare global { + interface Window { + _currentSiteId: number; + _currentSiteType: string; + } +} + +interface Site { + options?: { + launchpad_checklist_tasks_statuses?: { + video_uploaded: boolean; + }; + }; +} +const useShouldShowVideoCelebrationModal = ( isEditorSaving: boolean ) => { + const { siteIntent: intent } = useSiteIntent(); + + const [ shouldShowVideoCelebrationModal, setShouldShowVideoCelebrationModal ] = useState( false ); + const { hasSeenVideoCelebrationModal } = useHasSeenVideoCelebrationModal(); + + useEffect( () => { + const maybeRenderVideoCelebrationModal = async () => { + // Get latest site options since the video may have just been uploaded. + const siteObj = ( await request( { + path: `/sites/${ window._currentSiteId }?http_envelope=1`, + apiVersion: '1.1', + } ) ) as Site; + + if ( siteObj?.options?.launchpad_checklist_tasks_statuses?.video_uploaded ) { + setShouldShowVideoCelebrationModal( true ); + } + }; + + if ( 'videopress' === intent && ! hasSeenVideoCelebrationModal ) { + maybeRenderVideoCelebrationModal(); + } else if ( hasSeenVideoCelebrationModal ) { + setShouldShowVideoCelebrationModal( false ); + } + }, [ + isEditorSaving, // included so that we check whether the video has been uploaded on save. + intent, + hasSeenVideoCelebrationModal, + ] ); + return shouldShowVideoCelebrationModal; +}; +export default useShouldShowVideoCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js new file mode 100644 index 0000000000000..496ee2bc23bbd --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js @@ -0,0 +1,14 @@ +// FIXME: We can use `useSiteIntent` from `@automattic/data-stores` and remove this. +// https://github.com/Automattic/wp-calypso/pull/73565#discussion_r1113839120 +const useSiteIntent = () => { + // We can skip the request altogether since this information is already added to the window in + // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/plugins/jetpack/extensions/plugins/launchpad-save-modal/launchpad-save-modal.php#LL31C8-L31C34 + // We could update this to use the launchpad endpoint in jetpack-mu-wpcom, but that may require + // permissions changes as it requires 'manage_options' to read + // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php#L121. + return { + siteIntent: window.Jetpack_LaunchpadSaveModal?.siteIntentOption, + siteIntentFetched: true, + }; +}; +export default useSiteIntent; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js new file mode 100644 index 0000000000000..d3c64400da453 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js @@ -0,0 +1,24 @@ +import { useState, useEffect } from '@wordpress/element'; +import { useCallback } from 'react'; +import request from 'wpcom-proxy-request'; + +const useSitePlan = siteIdOrSlug => { + const [ sitePlan, setSitePlan ] = useState( {} ); + + const fetchSite = useCallback( async () => { + const siteObj = await request( { + path: `/sites/${ siteIdOrSlug }?http_envelope=1`, + apiVersion: '1.1', + } ); + if ( siteObj?.plan ) { + setSitePlan( siteObj.plan ); + } + }, [ siteIdOrSlug ] ); + + useEffect( () => { + fetchSite(); + }, [ fetchSite ] ); + + return sitePlan; +}; +export default useSitePlan; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts new file mode 100644 index 0000000000000..a1e231ace668c --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts @@ -0,0 +1,26 @@ +/** + * External Dependencies + */ +import { useState, useEffect } from '@wordpress/element'; +/** + * Internal Dependencies + */ +import { Callback } from '../types'; + +const useStepTracking = ( + currentStepIndex: number, + onStepViewOnce: Callback | undefined +): void => { + const [ stepsViewed, setStepsViewed ] = useState< number[] >( [] ); + + useEffect( () => { + if ( stepsViewed.includes( currentStepIndex ) ) { + return; + } + + setStepsViewed( prev => [ ...prev, currentStepIndex ] ); + onStepViewOnce?.( currentStepIndex ); + }, [ currentStepIndex, onStepViewOnce, stepsViewed ] ); +}; + +export default useStepTracking; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts new file mode 100644 index 0000000000000..2cf54b34efa8a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts @@ -0,0 +1,6 @@ +export { default } from './components/tour-kit'; +export * from './constants'; +export { default as WpcomTourKit, usePrefetchTourAssets } from './variants/wpcom'; +export * from './contexts'; +export * from './hooks'; +export * from './types'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss new file mode 100644 index 0000000000000..ef38a0c05b1e1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss @@ -0,0 +1,91 @@ +.tour-kit-frame { + visibility: hidden; + + &.is-visible { + visibility: visible; + } +} + +.tour-kit-frame__container { + border-radius: 2px; + bottom: 44px; + display: inline; + left: 16px; + position: fixed; + z-index: 9999; + // Avoid the text cursor when the text is not selectable + cursor: default; + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.25); + background: var(--studio-white); +} + +.tour-kit-overlay { + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + background: rgba(0, 0, 0); + opacity: 0; + + &.is-visible { + opacity: 0.5; + } +} + +.tour-kit-spotlight { + &.is-visible { + position: fixed; + overflow: hidden; + // box-shadow: 0 0 0 9999px rgba(0, 0, 255, 0.2); + outline: 99999px solid rgba(0, 0, 0, 0.5); + z-index: 1; + } +} + +.tour-kit-frame__arrow { + visibility: hidden; +} + +.tour-kit-frame__arrow, +.tour-kit-frame__arrow::before { + position: absolute; + width: 12px; + height: 12px; + background: inherit; + z-index: -1; +} + +.tour-kit-frame__arrow::before { + visibility: visible; + content: ""; + transform: rotate(45deg); +} + +.tour-kit-frame__container[data-popper-placement^="top"] > .tour-kit-frame__arrow { + bottom: -6px; + &::before { + box-shadow: 1px 1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="bottom"] > .tour-kit-frame__arrow { + top: -6px; + &::before { + box-shadow: -1px -1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="left"] > .tour-kit-frame__arrow { + right: -6px; + &::before { + box-shadow: 1px -1px 2px -1px rgb(0 0 0 / 25%); + } +} + +.tour-kit-frame__container[data-popper-placement^="right"] > .tour-kit-frame__arrow { + left: -6px; + &::before { + box-shadow: -1px 1px 2px -1px rgb(0 0 0 / 25%); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts new file mode 100644 index 0000000000000..3f4ec5c080a13 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts @@ -0,0 +1,164 @@ +import * as PopperJS from '@popperjs/core'; +import React from 'react'; +import { SpotlightInteractivityConfiguration } from './components/tour-kit-spotlight-interactivity'; +import { LiveResizeConfiguration } from './utils/live-resize-modifier'; +import type { Modifier } from 'react-popper'; + +export interface Step { + slug?: string; + referenceElements?: { + desktop?: string; + mobile?: string; + }; + meta: { + [ key: string ]: unknown; + // | React.FunctionComponent< Record< string, unknown > > + // | HTMLElement + // | string + // | ... + }; + options?: { + classNames?: { + /** + * `desktop` classes are applied when min-width is larger or equal to 480px. + */ + desktop?: string | string[]; + /** + * `mobile` classes are applied when max-width is smaller than 480px. + */ + mobile?: string | string[]; + }; + }; +} + +export interface TourStepRendererProps { + steps: Step[]; + currentStepIndex: number; + onDismiss: ( source: string ) => () => void; + onNextStep: () => void; + onPreviousStep: () => void; + onMinimize: () => void; + setInitialFocusedElement: React.Dispatch< React.SetStateAction< HTMLElement | null > >; + onGoToStep: ( stepIndex: number ) => void; +} + +export interface MinimizedTourRendererProps { + steps: Step[]; + currentStepIndex: number; + onMaximize: () => void; + onDismiss: ( source: string ) => () => void; +} + +export type TourStepRenderer = React.FunctionComponent< TourStepRendererProps >; +export type MinimizedTourRenderer = React.FunctionComponent< MinimizedTourRendererProps >; +export type Callback = ( currentStepIndex: number ) => void; +export type CloseHandler = ( steps: Step[], currentStepIndex: number, source: string ) => void; +export type PopperModifier = Partial< Modifier< unknown, Record< string, unknown > > >; + +export interface Callbacks { + onMinimize?: Callback; + onMaximize?: Callback; + onGoToStep?: Callback; + onNextStep?: Callback; + onPreviousStep?: Callback; + onStepViewOnce?: Callback; + onStepView?: Callback; +} + +export interface Options { + classNames?: string | string[]; + callbacks?: Callbacks; + /** An object to enable/disable/combine various tour effects, such as spotlight, overlay, and autoscroll */ + effects?: { + /** + * Adds a semi-transparent overlay and highlights the reference element + * when provided with a transparent box over it. The existence of this configuration + * key implies enabling the spotlight effect. + */ + spotlight?: { + /** An object that configures whether the user is allowed to interact with the referenced element during the tour */ + interactivity?: SpotlightInteractivityConfiguration; + /** CSS properties that configures the styles applied to the spotlight overlay */ + styles?: React.CSSProperties; + }; + /** Shows a little triangle that points to the referenced element. Defaults to true */ + arrowIndicator?: boolean; + /** + * Includes the semi-transparent overlay for all the steps Also blocks interactions for everything except the tour dialogues, + * including the referenced elements. Refer to spotlight interactivity configuration to affect this. + * + * Defaults to false, but if spotlight is enabled it implies this is enabled as well. + */ + overlay?: boolean; + /** Configures the autoscroll behaviour. Defaults to False. More information about the configuration at: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView */ + autoScroll?: ScrollIntoViewOptions | boolean; + /** Configures the behaviour for automatically resizing the tour kit elements (TourKitFrame and Spotlight). Defaults to disabled. */ + liveResize?: LiveResizeConfiguration; + }; + popperModifiers?: PopperModifier[]; + portalParentElement?: HTMLElement | null; +} + +export interface Config { + steps: Step[]; + renderers: { + tourStep: TourStepRenderer; + tourMinimized: MinimizedTourRenderer; + }; + closeHandler: CloseHandler; + isMinimized?: boolean; + options?: Options; + placement?: PopperJS.Placement; +} + +export type Tour = React.FunctionComponent< { config: Config } >; + +/************************ + * WPCOM variant types: * + ************************/ + +export type OnTourRateCallback = ( currentStepIndex: number, liked: boolean ) => void; + +export interface WpcomStep extends Step { + meta: { + heading: string | null; + descriptions: { + desktop: string | React.ReactElement | null; + mobile: string | React.ReactElement | null; + }; + imgSrc?: { + desktop?: { + src: string; + type: string; + }; + mobile?: { + src: string; + type: string; + }; + }; + imgLink?: { + href: string; + playable?: boolean; + onClick?: () => void; + }; + }; +} + +export interface WpcomTourStepRendererProps extends TourStepRendererProps { + steps: WpcomStep[]; +} + +export interface WpcomOptions extends Options { + tourRating?: { + enabled: boolean; + useTourRating?: () => 'thumbs-up' | 'thumbs-down' | undefined; + onTourRate?: ( rating: 'thumbs-up' | 'thumbs-down' ) => void; + }; +} + +export interface WpcomConfig extends Omit< Config, 'renderers' > { + steps: WpcomStep[]; + options?: WpcomOptions; +} + +export type WpcomTour = React.FunctionComponent< { config: WpcomConfig } >; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts new file mode 100644 index 0000000000000..3f00a820d1703 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts @@ -0,0 +1,16 @@ +import debugFactory from 'debug'; + +/** + * Helper to convert CSV of `classes` to an array. + * @param classes - String or array of classes to format. + * @returns Array of classes + */ +export function classParser( classes?: string | string[] ): string[] | null { + if ( classes?.length ) { + return classes.toString().split( ',' ); + } + + return null; +} + +export const debug = debugFactory( 'tour-kit' ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx new file mode 100644 index 0000000000000..ab1a14cf8568b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx @@ -0,0 +1,111 @@ +import { debug } from '../utils'; +import type { ModifierArguments, Options, State } from '@popperjs/core'; +import type { Modifier } from 'react-popper'; + +// Adds the resizeObserver and mutationObserver properties to the popper effect function argument +type ModifierArgumentsWithObserversProp = ModifierArguments< Options > & { + state: State & { + elements: State[ 'elements' ] & { + reference: State[ 'elements' ][ 'reference' ] & { + [ key: symbol ]: { + resizeObserver: ResizeObserver; + mutationObserver: MutationObserver; + }; + }; + }; + }; +}; + +export interface LiveResizeConfiguration { + /** CSS Selector for the the DOM node (and children) to observe for mutations */ + rootElementSelector?: string; + /** True to enable update on reference element resize, defaults to false */ + resize?: boolean; + /** True to enable update on node and subtree mutation, defaults to false. May be performance intensive */ + mutation?: boolean; +} + +type liveResizeModifierFactory = ( + params: LiveResizeConfiguration | undefined +) => Modifier< 'liveResizeModifier', Record< string, unknown > >; + +/** + * Function that returns a Popper modifier that observes the specified root element as well as + * reference element for any changes. The reason for being a currying function is so that + * we can customise the root element selector, otherwise observing at a higher than necessary + * level might cause unnecessary performance penalties. + * + * The Popper modifier queues an asynchronous update on the Popper instance whenever either of the + * Observers trigger its callback. + * + * @param config - The config. + * @param config.rootElementSelector - The selector of the root element. + * @param config.mutation - Whether to mutate. + * @param config.resize - Whether to resize. + * @returns custom Popper modifier. + */ +export const liveResizeModifier: liveResizeModifierFactory = ( + { rootElementSelector, mutation = false, resize = false }: LiveResizeConfiguration = { + mutation: false, + resize: false, + } +) => ( { + name: 'liveResizeModifier', + enabled: true, + phase: 'main', + fn: () => { + return; + }, + effect: arg0 => { + try { + const { state, instance } = arg0 as ModifierArgumentsWithObserversProp; // augment types here because we are mutating the properties on the argument that is passed in + + const ObserversProp = Symbol(); // use a symbol here so that we don't clash with multiple poppers using this modifier on the same reference node + const { reference } = state.elements; + + reference[ ObserversProp ] = { + resizeObserver: new ResizeObserver( () => { + instance.update(); + } ), + + mutationObserver: new MutationObserver( () => { + instance.update(); + } ), + }; + + if ( resize ) { + if ( reference instanceof Element ) { + reference[ ObserversProp ].resizeObserver.observe( reference ); + } else { + debug( + 'Error: ResizeObserver does not work with virtual elements, Tour Kit will not resize automatically if the size of the referenced element changes.' + ); + } + } + + if ( mutation ) { + const rootElementNode = document.querySelector( rootElementSelector || '#wpwrap' ); + if ( rootElementNode instanceof Element ) { + reference[ ObserversProp ].mutationObserver.observe( rootElementNode, { + attributes: true, + characterData: true, + childList: true, + subtree: true, + } ); + } else { + debug( + `Error: ${ rootElementSelector } selector did not find a valid DOM element, Tour Kit will not update automatically if the DOM layout changes.` + ); + } + } + + return () => { + reference[ ObserversProp ].resizeObserver.disconnect(); + reference[ ObserversProp ].mutationObserver.disconnect(); + delete reference[ ObserversProp ]; + }; + } catch ( error ) { + debug( 'Error: Tour Kit live resize modifier failed unexpectedly:', error ); + } + }, +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx new file mode 100644 index 0000000000000..1afd5ce37fca4 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx @@ -0,0 +1,50 @@ +import { Button, Flex } from '@wordpress/components'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; +import { Icon, close } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import maximize from '../icons/maximize'; +import type { MinimizedTourRendererProps } from '../../../types'; + +const WpcomTourKitMinimized: React.FunctionComponent< MinimizedTourRendererProps > = ( { + steps, + onMaximize, + onDismiss, + currentStepIndex, +} ) => { + const { __ } = useI18n(); + const lastStepIndex = steps.length - 1; + const page = currentStepIndex + 1; + const numberOfPages = lastStepIndex + 1; + + return ( + + + + + ); +}; + +export default WpcomTourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss new file mode 100644 index 0000000000000..fe9a19fc4b508 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss @@ -0,0 +1,44 @@ +@import "@automattic/calypso-color-schemes"; + +.wpcom-tour-kit-pagination-control { + margin: 0; + display: flex; + justify-content: center; + width: 100%; + + li { + display: inline-flex; + margin: auto 4px; + height: 18px; + align-items: center; + border: none; + } + + li.pagination-control__last-item { + margin-left: auto; + height: 32px; + } + + button.pagination-control__page { + border: none; + padding: 0; + width: 6px; + height: 6px; + border-radius: 50%; + cursor: pointer; + transition: all 0.2s ease-in-out; + background-color: var(--color-neutral-10); + + &:hover { + background-color: var(--color-neutral-40); + } + + &:disabled { + cursor: default; + } + + &.is-current { + background-color: var(--wp-admin-theme-color); + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx new file mode 100644 index 0000000000000..a37ce82c23b98 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx @@ -0,0 +1,49 @@ +import { __, sprintf } from '@wordpress/i18n'; +import clsx from 'clsx'; +import './wpcom-tour-kit-pagination-control.scss'; + +interface Props { + onChange: ( page: number ) => void; + activePageIndex: number; + numberOfPages: number; + classNames?: string | string[]; + children?: React.ReactNode; +} + +const WpcomTourKitPaginationControl: React.FunctionComponent< Props > = ( { + activePageIndex, + numberOfPages, + onChange, + classNames, + children, +} ) => { + const classes = clsx( 'wpcom-tour-kit-pagination-control', classNames ); + + return ( +
    + { Array.from( { length: numberOfPages } ).map( ( value, index ) => ( +
  • +
  • + ) ) } + { children &&
  • { children }
  • } +
+ ); +}; + +export default WpcomTourKitPaginationControl; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx new file mode 100644 index 0000000000000..82c20f1410019 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx @@ -0,0 +1,73 @@ +import { Button } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { useI18n } from '@wordpress/react-i18n'; +import clsx from 'clsx'; +import { useTourKitContext } from '../../../index'; +import thumbsDown from '../icons/thumbs_down'; +import thumbsUp from '../icons/thumbs_up'; +import type { WpcomConfig } from '../../../index'; + +const WpcomTourKitRating: React.FunctionComponent = () => { + const [ tempRating, setTempRating ] = useState< 'thumbs-up' | 'thumbs-down' >(); + const context = useTourKitContext(); + const config = context.config as unknown as WpcomConfig; + const tourRating = config.options?.tourRating?.useTourRating?.() ?? tempRating; + const { __ } = useI18n(); + + let isDisabled = false; + + if ( ! config.options?.tourRating?.enabled ) { + return null; + } + + // check is on tempRating to allow rerating in a restarted tour + if ( ! isDisabled && tempRating !== undefined ) { + isDisabled = true; + } + + const rateTour = ( isThumbsUp: boolean ) => { + if ( isDisabled ) { + return; + } + + const rating = isThumbsUp ? 'thumbs-up' : 'thumbs-down'; + + if ( rating !== tourRating ) { + isDisabled = true; + setTempRating( rating ); + config.options?.tourRating?.onTourRate?.( rating ); + } + }; + + return ( + <> +

+ { __( 'Did you find this guide helpful?', 'jetpack-mu-wpcom' ) } +

+
+
+ + ); +}; + +export default WpcomTourKitRating; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx new file mode 100644 index 0000000000000..e324f0b2a4380 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx @@ -0,0 +1,62 @@ +import { Button } from '@wordpress/components'; +import { useI18n } from '@wordpress/react-i18n'; +import WpcomTourKitPaginationControl from './wpcom-tour-kit-pagination-control'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +type Props = Omit< WpcomTourStepRendererProps, 'onMinimize' >; + +const WpcomTourKitStepCardNavigation: React.FunctionComponent< Props > = ( { + currentStepIndex, + onDismiss, + onGoToStep, + onNextStep, + onPreviousStep, + setInitialFocusedElement, + steps, +} ) => { + const { __ } = useI18n(); + const isFirstStep = currentStepIndex === 0; + const lastStepIndex = steps.length - 1; + + return ( + <> + + { isFirstStep ? ( +
+ + +
+ ) : ( +
+ + +
+ ) } +
+ + ); +}; + +export default WpcomTourKitStepCardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx new file mode 100644 index 0000000000000..bfad1e0204dc7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx @@ -0,0 +1,41 @@ +import { Button, Flex } from '@wordpress/components'; +import { close } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import minimize from '../icons/minimize'; +import type { TourStepRendererProps } from '../../../types'; + +interface Props { + onMinimize: TourStepRendererProps[ 'onMinimize' ]; + onDismiss: TourStepRendererProps[ 'onDismiss' ]; +} + +const WpcomTourKitStepCardOverlayControls: React.FunctionComponent< Props > = ( { + onMinimize, + onDismiss, +} ) => { + const { __ } = useI18n(); + + return ( +
+ + + + +
+ ); +}; + +export default WpcomTourKitStepCardOverlayControls; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx new file mode 100644 index 0000000000000..743d4f1cce9ac --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx @@ -0,0 +1,109 @@ +import { Button, Card, CardBody, CardFooter, CardMedia } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; +import { Icon } from '@wordpress/icons'; +import { useI18n } from '@wordpress/react-i18n'; +import clsx from 'clsx'; +import WpcomTourKitRating from './wpcom-tour-kit-rating'; +import WpcomTourKitStepCardNavigation from './wpcom-tour-kit-step-card-navigation'; +import WpcomTourKitStepCardOverlayControls from './wpcom-tour-kit-step-card-overlay-controls'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +const WpcomTourKitStepCard: React.FunctionComponent< WpcomTourStepRendererProps > = ( { + steps, + currentStepIndex, + onMinimize, + onDismiss, + onGoToStep, + onNextStep, + onPreviousStep, + setInitialFocusedElement, +} ) => { + const { __ } = useI18n(); + const lastStepIndex = steps.length - 1; + const { descriptions, heading, imgSrc, imgLink } = steps[ currentStepIndex ].meta; + const isLastStep = currentStepIndex === lastStepIndex; + const isMobile = useViewportMatch( 'mobile', '<' ); + const description = descriptions[ isMobile ? 'mobile' : 'desktop' ] ?? descriptions.desktop; + + return ( + + + { imgSrc && ( + + + { imgSrc.mobile && ( + + ) } + { + + { imgLink && ( + + + + + } + size={ 27 } + /> + + ) } + + ) } + +

{ heading }

+

+ { description } + { isLastStep ? ( + + ) : null } +

+
+ + { isLastStep ? ( + + ) : ( + + ) } + +
+ ); +}; + +export default WpcomTourKitStepCard; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx new file mode 100644 index 0000000000000..24064230211d2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx @@ -0,0 +1,28 @@ +import WpcomTourKitStepCard from './wpcom-tour-kit-step-card'; +import type { WpcomTourStepRendererProps } from '../../../types'; + +const WpcomTourKitStep: React.FunctionComponent< WpcomTourStepRendererProps > = ( { + steps, + currentStepIndex, + onDismiss, + onNextStep, + onPreviousStep, + onMinimize, + setInitialFocusedElement, + onGoToStep, +} ) => { + return ( + + ); +}; + +export default WpcomTourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx new file mode 100644 index 0000000000000..863d20ac6745d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx @@ -0,0 +1,29 @@ +import TourKit from '../../../components/tour-kit'; +import usePrefetchTourAssets from '../hooks/use-prefetch-tour-assets'; +import WpcomTourKitMinimized from './wpcom-tour-kit-minimized'; +import WpcomTourKitStep from './wpcom-tour-kit-step'; +import '../styles.scss'; +import type { WpcomConfig, TourStepRenderer } from '../../../types'; + +interface Props { + config: WpcomConfig; +} + +const WpcomTourKit: React.FunctionComponent< Props > = ( { config } ) => { + usePrefetchTourAssets( config.steps ); + + return ( + + ); +}; + +export default WpcomTourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx new file mode 100644 index 0000000000000..76db7335bf3eb --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx @@ -0,0 +1,16 @@ +import { useEffect } from '@wordpress/element'; +import type { WpcomStep } from '../../../types'; + +/** + * The hook to prefetch the assets of the tour + * + * @param steps - The steps that require assets. + */ +export default function usePrefetchTourAssets( steps: WpcomStep[] ): void { + useEffect( () => { + steps.forEach( step => { + step.meta.imgSrc?.mobile && ( new window.Image().src = step.meta.imgSrc.mobile.src ); + step.meta.imgSrc?.desktop && ( new window.Image().src = step.meta.imgSrc.desktop.src ); + } ); + }, [ steps ] ); +} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx new file mode 100644 index 0000000000000..0d0ee00282e4c --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx @@ -0,0 +1,13 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const minimize = ( + + + +); + +export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx new file mode 100644 index 0000000000000..e844c2cd1fc64 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const minimize = ( + + + +); + +export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx new file mode 100644 index 0000000000000..784e6668db965 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const thumbsDown = ( + + + +); + +export default thumbsDown; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx new file mode 100644 index 0000000000000..d57b147512eac --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx @@ -0,0 +1,14 @@ +import { SVG, Path } from '@wordpress/primitives'; + +const thumbsUp = ( + + + +); + +export default thumbsUp; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts new file mode 100644 index 0000000000000..f092f4ed3fa54 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts @@ -0,0 +1,2 @@ +export { default } from './components/wpcom-tour-kit'; +export { default as usePrefetchTourAssets } from './hooks/use-prefetch-tour-assets'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss new file mode 100644 index 0000000000000..56483b6839201 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss @@ -0,0 +1,232 @@ +@use "sass:math"; +@import "@wordpress/base-styles/colors"; +@import "@wordpress/base-styles/mixins"; +@import "@wordpress/base-styles/variables"; +@import "@wordpress/base-styles/z-index"; + +$wpcom-tour-kit-step-card-overlay-controls-button-bg-color: #32373c; // former $dark-gray-700. TODO: replace with standard color + +.wpcom-tour-kit-minimized { + border-radius: 2px; + box-shadow: + 0 2px 6px rgba(60, 66, 87, 0.08), + 0 0 0 1px rgba(60, 66, 87, 0.16), + 0 1px 1px rgba(0, 0, 0, 0.08); + background-color: $white; + color: $black; + + .components-button { + height: 44px; + + .wpcom-tour-kit-minimized__tour-index { + color: $gray-600; + } + + svg { + color: #50575e; + } + + &:hover { + .wpcom-tour-kit-minimized__tour-index, + svg { + color: inherit; + } + } + } +} + +.wpcom-tour-kit-step-card__heading { + font-size: 1.125rem; /* stylelint-disable-line scales/font-sizes */ + margin: 0.5rem 0; +} + +.wpcom-tour-kit-step-card__description { + font-size: 0.875rem; + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + line-height: 1.5rem; + margin: 0; + + .components-button { + height: auto; + line-height: 1; + text-decoration: underline; + padding: 0 0 0 4px; + } +} + +// @todo clk - update? +.wpcom-tour-kit .tour-kit-frame__container { + box-shadow: none; +} + +.wpcom-tour-kit-step-card { + width: 416px; + max-width: 92vw; + + &.wpcom-tour-kit-step-card.is-elevated { + box-shadow: rgba(0, 0, 0, 0.1) 0 0 0 1px, rgba(0, 0, 0, 0.1) 0 2px 4px 0; + } + + &.components-card { + border: none; + border-radius: 4px; + box-shadow: none; + } + + .components-card__body { + min-height: 114px; + } + + .components-card__body, + .components-card__footer { + border-top: none; + padding: $grid-unit-20 !important; + } + + .components-card__footer { + .wpcom-tour-kit-rating__end-text { + color: $gray-600; + font-size: 0.875rem; + font-style: italic; + } + + .wpcom-tour-kit-rating__end-icon.components-button.has-icon { + background-color: #f6f7f7; + border-radius: 50%; + color: $gray-600; + margin-left: 8px; + + path { + fill: $gray-600; + } + + &.active { + background-color: $black; + opacity: 1; + + path { + fill: $white; + } + } + } + } + + .components-card__media { + height: 0; + padding-top: math.percentage(math.div(math.ceil(math.div(1, 1.53) * 100), 100)); // img width:height ratio (1:1.53) + position: relative; + width: 100%; + + img { + left: 0; + position: absolute; + top: 0; + width: 100%; + } + } + + .components-guide__page-control { + margin: 0; + + .components-button { + min-width: auto; + &.has-icon { + padding: 3px; + } + } + + li { + margin-bottom: 0; + } + } +} + +.wpcom-tour-kit-step-card-overlay-controls__minimize-icon svg { + position: relative; + left: -2px; +} + +.wpcom-tour-kit-step-card-overlay-controls { + left: 0; + padding: $grid-unit-15; + right: 0; + z-index: 1; // z-index is needed because overlay controls are written before components-card__media, and so ends up under the image + + .components-button { + width: 32px; + min-width: 32px; + height: 32px; + background: $wpcom-tour-kit-step-card-overlay-controls-button-bg-color; + transition: opacity 200ms; + opacity: 0.7; + + &:active { + opacity: 0.9; + } + } + + @media (hover: hover) and (pointer: fine) { + // styles only applicable for hoverable viewports with precision pointing devices connected (eg: mouse) + .components-button { + opacity: 0; + } + + .tour-kit-frame__container:hover &, + .tour-kit-frame__container:focus-within & { + .components-button { + opacity: 0.7; + + &:hover, + &:focus { + opacity: 0.9; + } + } + } + } +} + +.wpcom-tour-kit-step-card-navigation__next-btn { + margin-left: $grid-unit-15; + justify-content: center; + min-width: 85px; +} + +.wpcom-tour-kit-step-card__media { + position: relative; +} + +// TODO: Remove once @wordpress/components/src/card/styles/card-styles.js is updated +.wpcom-tour-kit-step-card__media img { + display: block; + height: auto; + max-width: 100%; + width: 100%; +} + +.wpcom-tour-kit-step-card__media-link { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + + svg { + display: none; + transition: transform 0.15s ease-in-out; + + &:hover { + transform: scale(1.05); + } + } + + &--playable { + background-color: rgba(0, 0, 0, 0.5); + + svg { + display: block; + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md new file mode 100644 index 0000000000000..4c7e0026936e7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md @@ -0,0 +1,7 @@ +# Nux Welcome Tour Modal + +A help tour to show new users some of the basics of using the editor. + +## Testing Instructions + +Instructions for testing the modal and its variants, and for resetting the state of `nux-status` so that the modal is shown again can be found on the PR [#47779](https://github.com/Automattic/wp-calypso/pull/47779) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php new file mode 100644 index 0000000000000..5c9370e9d539d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php @@ -0,0 +1,94 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'block-editor/should-show-first-post-published-modal'; + + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_script' ), 100 ); + } + + /** + * Enqueue Launchpad options. + */ + public function enqueue_script() { + $launchpad_options = array( + 'launchpadScreenOption' => get_option( 'launchpad_screen' ), + 'siteUrlOption' => get_option( 'siteurl' ), + 'siteIntentOption' => get_option( 'site_intent' ), + ); + + wp_add_inline_script( + 'wpcom-block-editor-nux', + 'var launchpadOptions = ' . wp_json_encode( $launchpad_options, JSON_HEX_TAG | JSON_HEX_AMP ) . ';', + 'before' + ); + } + + /** + * Register available routes. + */ + public function register_rest_route() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'should_show_first_post_published_modal' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + ) + ); + } + + /** + * Callback to determine whether the request can proceed. + * + * @return boolean + */ + public function permission_callback() { + return current_user_can( 'read' ); + } + + /** + * Should we show the first post published modal + * + * @return \WP_REST_Response + */ + public function should_show_first_post_published_modal() { + // As we has synced the `has_never_published_post` option to part of atomic sites but we cannot + // update the value now, always return false to avoid showing the modal at every publishing until + // we can update the value on atomic sites. See D69932-code. + if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { + return rest_ensure_response( + array( + 'should_show_first_post_published_modal' => false, + ) + ); + } + + $has_never_published_post = (bool) get_option( 'has_never_published_post', false ); + $intent = get_option( 'site_intent', '' ); + $should_show_first_post_published_modal = $has_never_published_post && 'write' === $intent; + + return rest_ensure_response( + array( + 'should_show_first_post_published_modal' => $should_show_first_post_published_modal, + ) + ); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php new file mode 100644 index 0000000000000..9f8930d7392dd --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php @@ -0,0 +1,126 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'block-editor/nux'; + } + + /** + * Register available routes. + */ + public function register_rest_route() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_nux_status' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_nux_status' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + ) + ); + } + + /** + * Callback to determine whether the request can proceed. + * + * @return boolean + */ + public function permission_callback() { + return is_user_logged_in(); + } + + /** + * Should we show the wpcom welcome guide (i.e. welcome tour or nux modal) + * + * @param mixed $nux_status Can be "enabled", "dismissed", or undefined. + * @return boolean + */ + public function show_wpcom_welcome_guide( $nux_status ) { + return 'enabled' === $nux_status; + } + + /** + * Return the WPCOM NUX status + * + * This is only called for sites where the user hasn't already dismissed the tour. + * Once the tour has been dismissed, the closed state is saved in local storage (for the current site) + * see src/block-editor-nux.js + * + * @return \WP_REST_Response + */ + public function get_nux_status() { + + $should_open_patterns_panel = (bool) get_option( 'was_created_with_blank_canvas_design' ); + + if ( $should_open_patterns_panel ) { + $variant = 'blank-canvas-tour'; + } else { + $variant = 'tour'; + } + + if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { + $is_p2 = false; + } else { + $blog_id = get_current_blog_id(); + $is_p2 = \WPForTeams\is_wpforteams_site( $blog_id ); + } + + if ( $is_p2 ) { + // disable welcome tour for authoring P2s. + // see: https://github.com/Automattic/wp-calypso/issues/62973. + $nux_status = 'disabled'; + } elseif ( has_filter( 'wpcom_block_editor_nux_get_status' ) ) { + $nux_status = apply_filters( 'wpcom_block_editor_nux_get_status', false ); + } elseif ( ! metadata_exists( 'user', get_current_user_id(), 'wpcom_block_editor_nux_status' ) ) { + $nux_status = 'enabled'; + } else { + $nux_status = get_user_meta( get_current_user_id(), 'wpcom_block_editor_nux_status', true ); + } + + $show_welcome_guide = $this->show_wpcom_welcome_guide( $nux_status ); + + return rest_ensure_response( + array( + 'show_welcome_guide' => $show_welcome_guide, + 'variant' => $variant, + ) + ); + } + + /** + * Update the WPCOM NUX status + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response + */ + public function update_nux_status( $request ) { + $params = $request->get_json_params(); + $nux_status = $params['show_welcome_guide'] ? 'enabled' : 'dismissed'; + if ( has_action( 'wpcom_block_editor_nux_update_status' ) ) { + do_action( 'wpcom_block_editor_nux_update_status', $nux_status ); + } + update_user_meta( get_current_user_id(), 'wpcom_block_editor_nux_status', $nux_status ); + return rest_ensure_response( array( 'show_welcome_guide' => $this->show_wpcom_welcome_guide( $nux_status ) ) ); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php new file mode 100644 index 0000000000000..6e4045465bd4a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php @@ -0,0 +1,101 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'block-editor/has-seen-seller-celebration-modal'; + $this->wpcom_is_site_specific_endpoint = true; + $this->wpcom_is_wpcom_only_endpoint = true; + } + + /** + * Register available routes. + */ + public function register_rest_route() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'has_seen_seller_celebration_modal' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + ) + ); + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'set_has_seen_seller_celebration_modal' ), + 'permission_callback' => array( $this, 'permission_callback' ), + 'args' => array( + 'has_seen_seller_celebration_modal' => array( + 'required' => true, + 'type' => 'boolean', + ), + ), + ), + ) + ); + } + + /** + * Callback to determine whether the request can proceed. + * + * @return boolean + */ + public function permission_callback() { + return current_user_can( 'read' ); + } + + /** + * Whether the user has seen the seller celebration modal + * + * @return \WP_REST_Response + */ + public function has_seen_seller_celebration_modal() { + // See D69932-code and apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php. + if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { + return rest_ensure_response( + array( + 'has_seen_seller_celebration_modal' => false, + ) + ); + } + $has_seen_seller_celebration_modal = (bool) get_option( 'has_seen_seller_celebration_modal', false ); + + return rest_ensure_response( + array( + 'has_seen_seller_celebration_modal' => $has_seen_seller_celebration_modal, + ) + ); + } + + /** + * Update the option for whether the user has seen the seller celebration modal. + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response + */ + public function set_has_seen_seller_celebration_modal( $request ) { + $params = $request->get_json_params(); + update_option( 'has_seen_seller_celebration_modal', $params['has_seen_seller_celebration_modal'] ); + return rest_ensure_response( array( 'has_seen_seller_celebration_modal' => $params['has_seen_seller_celebration_modal'] ) ); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php new file mode 100644 index 0000000000000..5365109e39b84 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php @@ -0,0 +1,89 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'block-editor/sharing-modal-dismissed'; + + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_script' ), 100 ); + } + + /** + * Enqueue sharing modal options. + */ + public function enqueue_script() { + $modal_options = array( + 'isDismissed' => $this->get_wpcom_sharing_modal_dismissed(), + ); + + wp_add_inline_script( + 'wpcom-block-editor-nux', + 'var sharingModalOptions = ' . wp_json_encode( $modal_options, JSON_HEX_TAG | JSON_HEX_AMP ) . ';', + 'before' + ); + } + + /** + * Register available routes. + */ + public function register_rest_route() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'set_wpcom_sharing_modal_dismissed' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + ) + ); + } + + /** + * Callback to determine whether the request can proceed. + * + * @return boolean + */ + public function permission_callback() { + return current_user_can( 'read' ); + } + + /** + * Get the sharing modal dismissed status + * + * @return boolean + */ + public function get_wpcom_sharing_modal_dismissed() { + $old_sharing_modal_dismissed = (bool) get_option( 'sharing_modal_dismissed', false ); + if ( $old_sharing_modal_dismissed ) { + return true; + } + return (bool) get_option( 'wpcom_sharing_modal_dismissed', false ); + } + + /** + * Dismiss the sharing modal + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response + */ + public function set_wpcom_sharing_modal_dismissed( $request ) { + $params = $request->get_json_params(); + update_option( 'wpcom_sharing_modal_dismissed', $params['wpcom_sharing_modal_dismissed'] ); + return rest_ensure_response( array( 'wpcom_sharing_modal_dismissed' => $this->get_wpcom_sharing_modal_dismissed() ) ); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php new file mode 100644 index 0000000000000..c74f8dcdf51ed --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php @@ -0,0 +1,101 @@ +namespace = 'wpcom/v2'; + $this->rest_base = 'block-editor/has-seen-video-celebration-modal'; + $this->wpcom_is_site_specific_endpoint = true; + $this->wpcom_is_wpcom_only_endpoint = true; + } + + /** + * Register available routes. + */ + public function register_rest_route() { + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'has_seen_video_celebration_modal' ), + 'permission_callback' => array( $this, 'permission_callback' ), + ), + ) + ); + register_rest_route( + $this->namespace, + $this->rest_base, + array( + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'set_has_seen_video_celebration_modal' ), + 'permission_callback' => array( $this, 'permission_callback' ), + 'args' => array( + 'has_seen_video_celebration_modal' => array( + 'required' => true, + 'type' => 'boolean', + ), + ), + ), + ) + ); + } + + /** + * Callback to determine whether the request can proceed. + * + * @return boolean + */ + public function permission_callback() { + return current_user_can( 'read' ); + } + + /** + * Whether the site has displayed the video upload celebration modal. + * + * @return \WP_REST_Response + */ + public function has_seen_video_celebration_modal() { + // See D69932-code and apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php. + if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { + return rest_ensure_response( + array( + 'has_seen_video_celebration_modal' => true, + ) + ); + } + $has_seen_video_celebration_modal = (bool) get_option( 'has_seen_video_celebration_modal', false ); + + return rest_ensure_response( + array( + 'has_seen_video_celebration_modal' => $has_seen_video_celebration_modal, + ) + ); + } + + /** + * Update the option for whether the user has seen the video upload celebration modal. + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response + */ + public function set_has_seen_video_celebration_modal( $request ) { + $params = $request->get_json_params(); + update_option( 'has_seen_video_celebration_modal', $params['has_seen_video_celebration_modal'] ); + return rest_ensure_response( array( 'has_seen_video_celebration_modal' => $params['has_seen_video_celebration_modal'] ) ); + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php new file mode 100644 index 0000000000000..d720a6a32b52e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php @@ -0,0 +1,78 @@ +register_rest_route(); + + require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php'; + $first_post_published_modal_controller = new WP_REST_WPCOM_Block_Editor_First_Post_Published_Modal_Controller(); + $first_post_published_modal_controller->register_rest_route(); + + require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php'; + $seller_celebration_modal_controller = new WP_REST_WPCOM_Block_Editor_Seller_Celebration_Modal_Controller(); + $seller_celebration_modal_controller->register_rest_route(); + + require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php'; + $video_celebration_modal_controller = new WP_REST_WPCOM_Block_Editor_Video_Celebration_Modal_Controller(); + $video_celebration_modal_controller->register_rest_route(); + + require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php'; + $sharing_modal_controller = new WP_REST_WPCOM_Block_Editor_Sharing_Modal_Controller(); + $sharing_modal_controller->register_rest_route(); + } +} +add_action( 'init', array( __NAMESPACE__ . '\WPCOM_Block_Editor_NUX', 'init' ) ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js new file mode 100644 index 0000000000000..67979daf701d1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js @@ -0,0 +1,6 @@ +import { register } from './src/store'; + +import './src/disable-core-nux'; +import './src/block-editor-nux'; + +register(); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js new file mode 100644 index 0000000000000..1749ed15908da --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js @@ -0,0 +1,137 @@ +import { LocaleProvider } from '@automattic/i18n-utils'; +import { Guide, GuidePage } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect, useState } from '@wordpress/element'; +import { applyFilters } from '@wordpress/hooks'; +import { registerPlugin } from '@wordpress/plugins'; +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; +import { getQueryArg } from '@wordpress/url'; +import { + HasSeenSellerCelebrationModalProvider, + HasSeenVideoCelebrationModalProvider, + ShouldShowFirstPostPublishedModalProvider, +} from '../../../common/tour-kit'; +import { BloggingPromptsModal } from './blogging-prompts-modal'; +import DraftPostModal from './draft-post-modal'; +import FirstPostPublishedModal from './first-post-published-modal'; +import PurchaseNotice from './purchase-notice'; +import SellerCelebrationModal from './seller-celebration-modal'; +import PostPublishedSharingModal from './sharing-modal'; +import { DEFAULT_VARIANT, BLANK_CANVAS_VARIANT } from './store'; +import VideoPressCelebrationModal from './video-celebration-modal'; +import WpcomNux from './welcome-modal/wpcom-nux'; +import LaunchWpcomWelcomeTour from './welcome-tour/tour-launch'; + +/** + * Sometimes Gutenberg doesn't allow you to re-register the module and throws an error. + * FIXME: The new version allow it by default, but we might need to ensure that all the site has the new version. + * @see https://github.com/Automattic/wp-calypso/pull/79663 + */ +let unlock; +try { + unlock = __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', + '@wordpress/edit-site' + ).unlock; +} catch ( error ) { + // eslint-disable-next-line no-console + console.error( 'Error: Unable to get the unlock api. Reason: %s', error ); +} + +/** + * The WelcomeTour component + */ +function WelcomeTour() { + const [ showDraftPostModal ] = useState( + getQueryArg( window.location.href, 'showDraftPostModal' ) + ); + + const { + show, + isLoaded, + variant, + isManuallyOpened, + isNewPageLayoutModalOpen, + siteEditorCanvasMode, + } = useSelect( select => { + const welcomeGuideStoreSelect = select( 'automattic/wpcom-welcome-guide' ); + const starterPageLayoutsStoreSelect = select( 'automattic/starter-page-layouts' ); + let canvasMode; + if ( unlock && select( 'core/edit-site' ) ) { + canvasMode = + select( 'core/edit-site' ) && unlock( select( 'core/edit-site' ) ).getCanvasMode(); + } + + return { + show: welcomeGuideStoreSelect.isWelcomeGuideShown(), + isLoaded: welcomeGuideStoreSelect.isWelcomeGuideStatusLoaded(), + variant: welcomeGuideStoreSelect.getWelcomeGuideVariant(), + isManuallyOpened: welcomeGuideStoreSelect.isWelcomeGuideManuallyOpened(), + isNewPageLayoutModalOpen: starterPageLayoutsStoreSelect?.isOpen(), // Handle the case where SPT is not initalized. + siteEditorCanvasMode: canvasMode, + }; + }, [] ); + + const setOpenState = useDispatch( 'automattic/starter-page-layouts' )?.setOpenState; + + const { fetchWelcomeGuideStatus } = useDispatch( 'automattic/wpcom-welcome-guide' ); + + // On mount check if the WPCOM welcome guide status exists in state (from local storage), otherwise fetch it from the API. + useEffect( () => { + if ( ! isLoaded ) { + fetchWelcomeGuideStatus(); + } + }, [ fetchWelcomeGuideStatus, isLoaded ] ); + + const filteredShow = applyFilters( 'a8c.WpcomBlockEditorWelcomeTour.show', show ); + + if ( ! filteredShow || isNewPageLayoutModalOpen ) { + return null; + } + + // Hide the Welcome Tour when not in the edit mode. Note that canvas mode is available only in the site editor + if ( siteEditorCanvasMode && siteEditorCanvasMode !== 'edit' ) { + return null; + } + + // Open patterns panel before Welcome Tour if necessary (e.g. when using Blank Canvas theme) + // Do this only when Welcome Tour is not manually opened. + // NOTE: at the moment, 'starter-page-templates' assets are not loaded on /site-editor/ page so 'setOpenState' may be undefined + if ( variant === BLANK_CANVAS_VARIANT && ! isManuallyOpened && setOpenState ) { + setOpenState( 'OPEN_FOR_BLANK_CANVAS' ); + return null; + } + + if ( variant === DEFAULT_VARIANT ) { + return ( + + { showDraftPostModal ? : } + + ); + } + + // This case is redundant now and it will be cleaned up in a follow-up PR + if ( variant === 'modal' && Guide && GuidePage ) { + return ; + } + + return null; +} + +registerPlugin( 'wpcom-block-editor-nux', { + render: () => ( + + + + + + + + + + + + + + ), +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js new file mode 100644 index 0000000000000..1b968dbc81d19 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js @@ -0,0 +1,13 @@ +import { Path, SVG } from '@wordpress/components'; + +export const ArrowRightIcon = () => ( + + + +); + +export const ArrowLeftIcon = () => ( + + + +); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js new file mode 100644 index 0000000000000..74ff6b5be19c2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js @@ -0,0 +1,106 @@ +import apiFetch from '@wordpress/api-fetch'; +import { createBlock } from '@wordpress/blocks'; +import { Button, Modal } from '@wordpress/components'; +import { dispatch, select } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { addQueryArgs, getQueryArg } from '@wordpress/url'; +import moment from 'moment'; +import { useEffect, useState } from 'react'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import { ArrowLeftIcon, ArrowRightIcon } from './icons'; + +import './style.scss'; + +export const BloggingPromptsModalInner = () => { + const [ isOpen, setIsOpen ] = useState( true ); + const [ prompts, setPrompts ] = useState( [] ); + const [ promptIndex, setPromptIndex ] = useState( 0 ); + + useEffect( () => { + const path = addQueryArgs( `/wpcom/v3/blogging-prompts`, { + per_page: 10, + after: moment().format( '--MM-DD' ), + order: 'desc', + force_year: new Date().getFullYear(), + } ); + apiFetch( { + path, + } ) + .then( result => { + wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_viewed' ); + return setPrompts( result ); + } ) + // eslint-disable-next-line no-console + .catch( () => console.error( 'Unable to fetch writing prompts' ) ); + }, [] ); + + if ( ! isOpen || ! prompts.length ) { + return null; + } + + const selectPrompt = () => { + const promptId = prompts[ promptIndex ]?.id; + dispatch( 'core/editor' ).resetEditorBlocks( [ + createBlock( 'jetpack/blogging-prompt', { promptId } ), + ] ); + wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_prompt_selected', { + prompt_id: promptId, + } ); + setIsOpen( false ); + }; + + const closeModal = () => { + wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_closed' ); + setIsOpen( false ); + }; + + return ( + +
+
+ +

{ prompts[ promptIndex ]?.text }

+ +
+ +
+
+ ); +}; + +export const BloggingPromptsModal = () => { + const hasQueryArg = getQueryArg( window.location.href, 'new_prompt' ); + const editorType = select( 'core/editor' ).getCurrentPostType(); + + const shouldOpen = hasQueryArg && editorType === 'post'; + + if ( ! shouldOpen ) { + return null; + } + return ; +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss new file mode 100644 index 0000000000000..67969ac4d706b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss @@ -0,0 +1,53 @@ +@import "@automattic/typography/styles/variables"; +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.blogging-prompts-modal { + @include break-small { + width: 80%; + } + @include break-large { + width: 60%; + } + max-width: 800px; + margin: auto; + + .components-modal__header-heading { + font-size: $font-body; + font-weight: 400; + } +} + +.blogging-prompts-modal__prompt { + display: flex; + flex-direction: column; + align-items: flex-end; + + .blogging-prompts-modal__prompt-navigation { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; + gap: 24px; + width: 100%; + } + + .blogging-prompts-modal__prompt-navigation-button { + border-radius: 50%; + width: 44px; + height: 44px; + &.components-button:hover:not(:disabled,[aria-disabled="true"]) { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); + } + } + + .blogging-prompts-modal__prompt-text { + font-size: 1.25rem; + font-weight: 500; + line-height: 26px; + text-align: left; + width: 100%; + text-wrap: pretty; + } +} + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js new file mode 100644 index 0000000000000..6591d4e0b5c2d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js @@ -0,0 +1,43 @@ +import { select, dispatch, subscribe } from '@wordpress/data'; + +import '@wordpress/nux'; //ensure nux store loads + +// Disable nux and welcome guide features from core. +const unsubscribe = subscribe( () => { + dispatch( 'core/nux' ).disableTips(); + if ( select( 'core/edit-post' )?.isFeatureActive( 'welcomeGuide' ) ) { + dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); + unsubscribe(); + } + if ( select( 'core/edit-site' )?.isFeatureActive( 'welcomeGuide' ) ) { + dispatch( 'core/edit-site' ).toggleFeature( 'welcomeGuide' ); + unsubscribe(); + } +} ); + +// Listen for these features being triggered to call dotcom welcome guide instead. +// Note migration of areTipsEnabled: https://github.com/WordPress/gutenberg/blob/5c3a32dabe4393c45f7fe6ac5e4d78aebd5ee274/packages/data/src/plugins/persistence/index.js#L269 +subscribe( () => { + if ( select( 'core/nux' ).areTipsEnabled() ) { + dispatch( 'core/nux' ).disableTips(); + dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true ); + } + if ( select( 'core/edit-post' )?.isFeatureActive( 'welcomeGuide' ) ) { + dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); + // On mounting, the welcomeGuide feature is turned on by default. This opens the welcome guide despite `welcomeGuideStatus` value. + // This check ensures that we only listen to `welcomeGuide` changes if the welcomeGuideStatus value is loaded and respected + if ( select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideStatusLoaded() ) { + dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true, { + openedManually: true, + } ); + } + } + if ( select( 'core/edit-site' )?.isFeatureActive( 'welcomeGuide' ) ) { + dispatch( 'core/edit-site' ).toggleFeature( 'welcomeGuide' ); + if ( select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideStatusLoaded() ) { + dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true, { + openedManually: true, + } ); + } + } +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg new file mode 100644 index 0000000000000..cf24d89ced98a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js new file mode 100644 index 0000000000000..ada982e317304 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js @@ -0,0 +1,50 @@ +import { Button } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { doAction, hasAction } from '@wordpress/hooks'; +import { __ } from '@wordpress/i18n'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import NuxModal from '../nux-modal'; +import draftPostImage from './images/draft-post.svg'; +import './style.scss'; + +const CLOSE_EDITOR_ACTION = 'a8c.wpcom-block-editor.closeEditor'; + +const DraftPostModal = () => { + const homeUrl = `/home/${ window.location.hostname }`; + const [ isOpen, setIsOpen ] = useState( true ); + const closeModal = () => setIsOpen( false ); + const closeEditor = () => { + if ( hasAction( CLOSE_EDITOR_ACTION ) ) { + doAction( CLOSE_EDITOR_ACTION, homeUrl ); + } else { + window.location.href = `https://wordpress.com${ homeUrl }`; + } + }; + + return ( + + + + + } + onRequestClose={ closeModal } + onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_draft_post_modal_show' ) } + /> + ); +}; + +export default DraftPostModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss new file mode 100644 index 0000000000000..2317634ed12b7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss @@ -0,0 +1,17 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-block-editor-draft-post-modal { + .components-modal__content { + @include break-small { + padding: 48px 128px; + } + } + + .wpcom-block-editor-nux-modal__image-container { + img { + width: 209px; + height: 95px; + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg new file mode 100644 index 0000000000000..3b55263b209af --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx new file mode 100644 index 0000000000000..3d27771c6d50a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx @@ -0,0 +1,117 @@ +import { Button } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useEffect, useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { isURL } from '@wordpress/url'; +import React from 'react'; +import { useSiteIntent, useShouldShowFirstPostPublishedModal } from '../../../../common/tour-kit'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import NuxModal from '../nux-modal'; +import postPublishedImage from './images/post-published.svg'; + +import './style.scss'; + +type CoreEditorPlaceholder = { + getCurrentPost: ( ...args: unknown[] ) => { link: string }; + getCurrentPostType: ( ...args: unknown[] ) => string; + isCurrentPostPublished: ( ...args: unknown[] ) => boolean; +}; + +/** + * Show the first post publish modal + */ +const FirstPostPublishedModalInner: React.FC = () => { + const { link } = useSelect( + select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPost(), + [] + ); + const postType = useSelect( + select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPostType(), + [] + ); + + const isCurrentPostPublished = useSelect( + select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).isCurrentPostPublished(), + [] + ); + const previousIsCurrentPostPublished = useRef( isCurrentPostPublished ); + const shouldShowFirstPostPublishedModal = useShouldShowFirstPostPublishedModal(); + const [ isOpen, setIsOpen ] = useState( false ); + const closeModal = () => setIsOpen( false ); + + const { siteUrlOption, launchpadScreenOption, siteIntentOption } = window?.launchpadOptions || {}; + + let siteUrl = ''; + if ( isURL( siteUrlOption ) ) { + // https://mysite.wordpress.com/path becomes mysite.wordpress.com + siteUrl = new URL( siteUrlOption ).hostname; + } + + useEffect( () => { + // If the user is set to see the first post modal and current post status changes to publish, + // open the post publish modal + if ( + shouldShowFirstPostPublishedModal && + ! previousIsCurrentPostPublished.current && + isCurrentPostPublished && + postType === 'post' + ) { + previousIsCurrentPostPublished.current = isCurrentPostPublished; + + // When the post published panel shows, it is focused automatically. + // Thus, we need to delay open the modal so that the modal would not be close immediately + // because the outside of modal is focused + window.setTimeout( () => { + setIsOpen( true ); + } ); + } + }, [ postType, shouldShowFirstPostPublishedModal, isCurrentPostPublished ] ); + + const handleViewPostClick = ( event: React.MouseEvent ) => { + event.preventDefault(); + ( window.top as Window ).location.href = link; + }; + + const handleNextStepsClick = ( event: React.MouseEvent ) => { + event.preventDefault(); + ( + window.top as Window + ).location.href = `https://wordpress.com/setup/write/launchpad?siteSlug=${ siteUrl }`; + }; + return ( + + + { launchpadScreenOption === 'full' && siteIntentOption === 'write' && ( + + ) } + + } + onRequestClose={ closeModal } + onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_first_post_published_modal_show' ) } + /> + ); +}; + +const FirstPostPublishedModal = () => { + const { siteIntent: intent } = useSiteIntent(); + if ( intent === 'write' ) { + return ; + } + return null; +}; + +export default FirstPostPublishedModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss new file mode 100644 index 0000000000000..ebbe84e61dfc8 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss @@ -0,0 +1,23 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-block-editor-post-published-modal { + .components-modal__content { + @include break-small { + padding: 48px 90px; + } + } + + .wpcom-block-editor-nux-modal__image-container { + img { + width: 158px; + height: 85px; + } + } + + .wpcom-block-editor-nux-modal__buttons { + .components-button { + min-width: 113px; + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx new file mode 100644 index 0000000000000..e46656c0ce2e3 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx @@ -0,0 +1,58 @@ +import { Modal } from '@wordpress/components'; +import { useEffect, useRef } from '@wordpress/element'; +import clsx from 'clsx'; +import React from 'react'; +import './style.scss'; + +interface Props { + isOpen: boolean; + className?: string; + title: string; + description: string; + imageSrc: string; + actionButtons: React.ReactElement; + onRequestClose: () => void; + onOpen?: () => void; +} + +const NuxModal: React.FC< Props > = ( { + isOpen, + className, + title, + description, + imageSrc, + actionButtons, + onRequestClose, + onOpen, +} ) => { + const prevIsOpen = useRef< boolean | null >( null ); + + useEffect( () => { + if ( ! prevIsOpen.current && isOpen ) { + onOpen?.(); + } + + prevIsOpen.current = isOpen; + }, [ prevIsOpen, isOpen, onOpen ] ); + + if ( ! isOpen ) { + return null; + } + + return ( + +
+ { +
+

{ title }

+

{ description }

+
{ actionButtons }
+
+ ); +}; + +export default NuxModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss new file mode 100644 index 0000000000000..ee7ee1b10caef --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss @@ -0,0 +1,96 @@ +@import "@automattic/typography/styles/variables"; +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-block-editor-nux-modal { + .components-modal__header { + height: auto; + padding: 10px; + border-bottom: 0; + + // Fix styles when Gutenberg is deactivated + position: absolute; + left: 0; + right: 0; + margin: 0; + background-color: transparent; + + button { + left: unset; + + svg path { + transform: scale(1.4); + transform-origin: center; + } + } + } + + .components-modal__content { + padding: 84px 20px 20px; + margin-top: 0; + + &::before { + margin: 0; + } + + @include break-mobile { + text-align: center; + } + } + + .wpcom-block-editor-nux-modal__image-container { + display: flex; + justify-content: center; + } + + .wpcom-block-editor-nux-modal__title { + margin: 34px 0 0; + font-size: $font-headline-small; + font-weight: 500; + line-height: 1.2; + + @include break-mobile { + margin-top: 24px; + } + } + + .wpcom-block-editor-nux-modal__description { + max-width: 352px; + margin: 16px 0 0; + font-size: $font-body; + + @include break-mobile { + margin: 20px auto 0; + font-size: $font-body-large; + } + } + + .wpcom-block-editor-nux-modal__buttons { + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 24px; + + .components-button { + min-width: 130px; + height: 40px; + justify-content: center; + border-radius: 3px; + font-size: $font-body-small; + } + + .components-button + .components-button { + margin-top: 12px; + } + + @include break-mobile { + flex-direction: row; + margin-top: 28px; + + .components-button + .components-button { + margin-top: 0; + margin-left: 16px; + } + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx new file mode 100644 index 0000000000000..b6ffafa4ab99d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx @@ -0,0 +1,34 @@ +import { useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { store as noticesStore } from '@wordpress/notices'; +import { useEffect, useRef } from 'react'; +import './style.scss'; + +/** + * Display the purchase notice snackbar + */ +function PurchaseNotice() { + const hasPaymentNotice = useRef( false ); + const { createNotice } = useDispatch( noticesStore ); + + useEffect( () => { + const noticePattern = /[&?]notice=([\w_-]+)/; + const match = noticePattern.exec( document.location.search ); + const notice = match && match[ 1 ]; + if ( 'purchase-success' === notice && hasPaymentNotice.current === false ) { + hasPaymentNotice.current = true; + createNotice( + 'info', + __( 'Congrats! Premium blocks are now available to use.', 'jetpack-mu-wpcom' ), + { + isDismissible: true, + type: 'snackbar', + } + ); + } + }, [ createNotice ] ); + + return null; +} + +export default PurchaseNotice; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss new file mode 100644 index 0000000000000..f9802fcb34e2b --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss @@ -0,0 +1,15 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-block-editor-purchase-notice { + position: fixed; + overflow: visible; + bottom: 0; + left: 0; + width: 100%; + z-index: 10000; + justify-content: center; + display: flex; + pointer-events: none; + padding-bottom: 1rem; +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg new file mode 100644 index 0000000000000..258f49c3780c7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx new file mode 100644 index 0000000000000..070eea11fb0e2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx @@ -0,0 +1,139 @@ +import { Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState, useRef, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + useSiteIntent, + useShouldShowSellerCelebrationModal, + useHasSeenSellerCelebrationModal, +} from '../../../../common/tour-kit'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import NuxModal from '../nux-modal'; +import contentSubmittedImage from './images/product-published.svg'; +import './style.scss'; + +/** + * Show the seller celebration modal + */ +const SellerCelebrationModalInner = () => { + const { addEntities } = useDispatch( 'core' ); + + useEffect( () => { + // @TODO - not sure if I actually need this; need to test with it removed. + // Teach core data about the status entity so we can use selectors like `getEntityRecords()` + addEntities( [ + { + baseURL: '/wp/v2/statuses', + key: 'slug', + kind: 'root', + name: 'status', + plural: 'statuses', + }, + ] ); + // Only register entity once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + // conditions to show: + // - user just finished saving (check) + // - editor has not yet displayed modal once (check) + // - user is a seller (check) + // - user has not saved site before + // - content includes product block, and a user has selected it at least once (check) + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const [ hasDisplayedModal, setHasDisplayedModal ] = useState( false ); + + const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ) ); + const previousIsEditorSaving = useRef( false ); + + const { updateHasSeenSellerCelebrationModal } = useHasSeenSellerCelebrationModal(); + + const linkUrl = useSelect( select => { + if ( isSiteEditor ) { + const page = select( 'core/edit-site' ).getPage(); + const pageId = parseInt( page?.context?.postId ); + const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); + return pageEntity?.link; + } + const currentPost = select( 'core/editor' ).getCurrentPost(); + return currentPost.link; + } ); + + const shouldShowSellerCelebrationModal = useShouldShowSellerCelebrationModal(); + + const isEditorSaving = useSelect( select => { + if ( isSiteEditor ) { + const page = select( 'core/edit-site' ).getPage(); + const pageId = parseInt( page?.context?.postId ); + const isSavingSite = + select( 'core' ).isSavingEntityRecord( 'root', 'site' ) && + ! select( 'core' ).isAutosavingEntityRecord( 'root', 'site' ); + const isSavingEntity = + select( 'core' ).isSavingEntityRecord( 'postType', 'page', pageId ) && + ! select( 'core' ).isAutosavingEntityRecord( 'postType', 'page', pageId ); + + return isSavingSite || isSavingEntity; + } + const currentPost = select( 'core/editor' ).getCurrentPost(); + const isSavingEntity = + select( 'core' ).isSavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ) && + ! select( 'core' ).isAutosavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ); + return isSavingEntity; + } ); + + useEffect( () => { + if ( + ! isEditorSaving && + previousIsEditorSaving.current && + ! hasDisplayedModal && + shouldShowSellerCelebrationModal + ) { + setIsModalOpen( true ); + setHasDisplayedModal( true ); + updateHasSeenSellerCelebrationModal( true ); + } + previousIsEditorSaving.current = isEditorSaving; + }, [ + isEditorSaving, + hasDisplayedModal, + shouldShowSellerCelebrationModal, + updateHasSeenSellerCelebrationModal, + ] ); + + // if save state has changed and was saving on last render + // then it has finished saving + // open modal if content has sell block, + + const closeModal = () => setIsModalOpen( false ); + return ( + + + + + } + onRequestClose={ closeModal } + onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_seller_celebration_modal_show' ) } + /> + ); +}; + +const SellerCelebrationModal = () => { + const { siteIntent: intent } = useSiteIntent(); + if ( intent === 'sell' ) { + return ; + } + return null; +}; + +export default SellerCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss new file mode 100644 index 0000000000000..c1576d9f74db5 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss @@ -0,0 +1,27 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-site-editor-seller-celebration-modal { + .components-modal__content { + @include break-small { + padding: 48px 90px; + } + } + + .wpcom-block-editor-nux-modal__image-container { + img { + width: 158px; + height: 85px; + } + } + + .wpcom-block-editor-nux-modal__buttons { + .components-button { + min-width: 113px; + &:not(.is-primary) { + border: 1px solid #c3c4c7; + border-radius: 4px; + } + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx new file mode 100644 index 0000000000000..bd077bf7625cc --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx @@ -0,0 +1,47 @@ +import { Button } from '@wordpress/components'; +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +interface ClipboardButtonProps { + className?: string; + compact?: boolean; + disabled?: boolean; + primary?: boolean; + scary?: boolean; + busy?: boolean; + borderless?: boolean; + plain?: boolean; + transparent?: boolean; + text: string | null; + onCopy?: () => void; + onMouseLeave?: () => void; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = () => {}; + +const ClipboardButton = forwardRef< + HTMLButtonElement, + React.PropsWithChildren< ClipboardButtonProps > +>( ( { className, text, onCopy = noop, ...rest }, ref ) => { + /** + * The copy handler + */ + function onCopyHandler() { + if ( text ) { + navigator.clipboard.writeText( text ); + onCopy(); + } + } + + return ( + + + + + +
+ + { + updateIsDismissed( ! isDismissed ); + } } + /> + { __( "Don't show again", 'jetpack-mu-wpcom' ) } + +
+
+
+ { shouldShowSuggestedTags ? ( + + ) : ( + { + ) } +
+
+ + ); +}; + +const SharingModal = () => { + const { siteIntent: intent } = useSiteIntent(); + if ( intent === START_WRITING_FLOW || intent === DESIGN_FIRST_FLOW ) { + return null; + } + return ; +}; +export default SharingModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx new file mode 100644 index 0000000000000..b3d72b5eae882 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx @@ -0,0 +1,48 @@ +import clsx from 'clsx'; +import * as React from 'react'; +import { Assign } from 'utility-types'; + +interface Props { + icon: string; + size?: number; +} + +/** + * InlineSocialLogo is a copy of client/components/social-logo that references an inline SVG sprite. + * This componenet is needed because: + * + * The XML element does not work with SVGs loaded from external domains. + * In the editor, images are loaded from the CDN (s0.wp.com) in production. + * useInline allows us to reference an svg sprite from the current page instead. + * see https://github.com/w3c/svgwg/issues/707 + * + * InlineSocialLogosSprite must be included on the page where this is used + * @param props - The props of the component, + * @returns A Social Logo SVG + */ +function InlineSocialLogo( props: Assign< React.SVGProps< SVGSVGElement >, Props > ) { + const { size = 24, icon, className, ...otherProps } = props; + + // Using a missing icon doesn't produce any errors, just a blank icon, which is the exact intended behaviour. + // This means we don't need to perform any checks on the icon name. + const iconName = `social-logo-${ icon }`; + // The current CSS expects individual icon classes in the form of e.g. `.twitter`, but the social-logos build + // appears to generate them in the form of `.social-logo-twitter` instead. + // We add both here, to ensure compatibility. + const iconClass = clsx( 'social-logo', iconName, icon, className ); + + return ( + + + + ); +} + +export default React.memo( InlineSocialLogo ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx new file mode 100644 index 0000000000000..c0c5e9388cdad --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx @@ -0,0 +1,286 @@ +/** + * A hidden inline svg sprite of social logos. + * + * Sprite was coppied from https://wordpress.com/calypso/images/social-logos-d55401f99bb02ebd6cf4.svg + * @returns see above. + */ +const InlineSocialLogosSprite = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +export default InlineSocialLogosSprite; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss new file mode 100644 index 0000000000000..c7738fc6fc0d5 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss @@ -0,0 +1,173 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-block-editor-post-published-sharing-modal { + .components-modal__content { + margin-top: 0; + padding-bottom: 0; + .wpcom-block-editor-post-published-sharing-modal__inner { + display: flex; + .wpcom-block-editor-post-published-sharing-modal__left { + width: 50%; + padding: 52px 40px 52px 20px; + + p a { + color: var(--color-text); + text-decoration: underline; + white-space: nowrap; + + &:hover { + color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); + text-decoration: none; + } + + .components-external-link__icon { + margin-left: 4px; + } + } + + .wpcom-block-editor-post-published-buttons { + display: flex; + } + } + .wpcom-block-editor-post-published-sharing-modal__right { + width: 50%; + padding: 52px 20px 52px 40px; + border-left: 1px solid var(--studio-gray-5); + display: flex; + justify-content: center; + min-width: 300px; + } + + @media only screen and (max-width: 600px) { + flex-direction: column-reverse; + + .wpcom-block-editor-post-published-sharing-modal__left { + padding: 44px 0 20px; + width: 100%; + } + .wpcom-block-editor-post-published-sharing-modal__right { + border-left: none; + padding: 52px 0 0; + width: 100%; + } + .wpcom-block-editor-post-published-sharing-modal__image { + height: 140px; + } + } + } + h1 { + margin-top: 0; + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 26px; + @media only screen and (max-width: 600px) { + font-size: 2.25rem; + font-weight: 500; + line-height: 1; + } + } + p { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 18px; + @media only screen and (max-width: 600px) { + font-size: 1rem; + } + } + .link-button { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 14px; + font-weight: 500; + display: inline-flex; + border: none; + background: transparent; + color: var(--color-text); + padding: 0 14px 0 0; + line-height: 2.71428571; + min-height: 40px; + margin-bottom: 4px; + align-items: center; + text-decoration: underline; + white-space: nowrap; + + &:hover { + background: transparent; + color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); + text-decoration: none; + } + + svg { + fill: currentColor; + } + } + hr { + margin-top: 20px; + border-top: 1px solid var(--studio-gray-5); + border-bottom: none; + } + h2 { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 18px; + font-weight: 600px; + } + .wpcom-block-editor-post-published-sharing-modal__checkbox-section { + margin-top: 40px; + color: var(--studio-gray-60); + } + .form-checkbox { + margin-top: 1px; + height: 1rem; + width: 1rem; + &::before { + width: 1.3125rem; + height: 1.3125rem; + } + } + .wpcom-block-editor-post-published-sharing-modal__suggest-tags { + width: 250px; + flex: fit-content; + } + } +} + +.wpcom-block-editor-post-published-buttons .link-button { + gap: 4px; + padding-left: 0; +} + +.wpcom-block-editor-post-published-sharing-modal__sharing-button { + display: inline-block; + position: relative; + width: 32px; + height: 32px; + top: -2px; + margin: 8px 8px 0 0; + border-radius: 50%; + color: var(--color-neutral-80); + background: var(--studio-white); + padding: 7px; + + &:hover { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22), 0 0 0 1px rgba(0, 0, 0, 0.22); + } + + .social-logo { + top: 0; + color: var(--color-text-inverted); + } +} + +@mixin sharing-button-service( $name, $color ) { + .wpcom-block-editor-post-published-sharing-modal__sharing-button.share-#{ $name } { + background: $color; + + &:hover { + background: $color; + } + } +} + +@include sharing-button-service( "facebook", var( --color-facebook ) ); +@include sharing-button-service( "twitter", var( --color-twitter ) ); +@include sharing-button-service( "linkedin", var( --color-linkedin ) ); +@include sharing-button-service( "tumblr", var( --color-tumblr ) ); +@include sharing-button-service( "pinterest", var( --color-pinterest ) ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx new file mode 100644 index 0000000000000..8f47b85adb592 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx @@ -0,0 +1,132 @@ +import { useLocale } from '@automattic/i18n-utils'; +import { Button, FormTokenField } from '@wordpress/components'; +import { TokenItem } from '@wordpress/components/build-types/form-token-field/types'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; +import { useI18n } from '@wordpress/react-i18n'; +import * as React from 'react'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import useAddTagsToPost from './use-add-tags-to-post'; + +type PostMeta = { + reader_suggested_tags: string; +}; + +type CoreEditorPlaceholder = { + getCurrentPost: ( ...args: unknown[] ) => { + id: number; + meta: PostMeta; + }; +}; + +type SuggestedTagsEventProps = { + number_of_original_suggested_tags: number; + number_of_selected_tags: number; + number_of_suggested_tags_selected: number; + number_of_added_tags: number; +}; + +type SuggestedTagsProps = { + setShouldShowSuggestedTags: ( shouldShow: boolean ) => void; +}; + +/** + * Display the suggested tags. + * + * @param props - The props of the component. + */ +function SuggestedTags( props: SuggestedTagsProps ) { + const { __, _n } = useI18n(); + const localeSlug = useLocale(); + const { id: postId, meta: postMeta } = useSelect( + select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPost(), + [] + ); + const { createNotice } = useDispatch( noticesStore ); + const origSuggestedTags = postMeta?.reader_suggested_tags + ? JSON.parse( postMeta.reader_suggested_tags ) + : []; + const [ selectedTags, setSelectedTags ] = React.useState( origSuggestedTags ); + const onAddTagsButtonClick = ( numAddedTags: number ) => { + // Compare origSuggestedTags and selectedTags and determine the number of tags that are different + const numSuggestedTags = origSuggestedTags.length; + const numSelectedTags = selectedTags.length; + const numSameTags = origSuggestedTags.filter( ( tag: string ) => + selectedTags.includes( tag ) + ).length; + const eventProps: SuggestedTagsEventProps = { + number_of_original_suggested_tags: numSuggestedTags, + number_of_selected_tags: numSelectedTags, + number_of_suggested_tags_selected: numSameTags, + number_of_added_tags: numAddedTags, + }; + wpcomTrackEvent( 'calypso_reader_post_publish_add_tags', eventProps ); + if ( numAddedTags > 0 ) { + createNotice( + 'success', + _n( 'Tag Added.', 'Tags Added.', numAddedTags, 'jetpack-mu-wpcom' ), + { + type: 'snackbar', + } + ); + } else { + createNotice( 'warning', __( 'No Tags Added.', 'jetpack-mu-wpcom' ), { + type: 'snackbar', + } ); + } + props.setShouldShowSuggestedTags( false ); + }; + const { saveTags } = useAddTagsToPost( postId, selectedTags, onAddTagsButtonClick ); + + useEffect( () => { + if ( origSuggestedTags?.length === 0 ) { + // Check if localeSlug begins with 'en' + if ( localeSlug && localeSlug.startsWith( 'en' ) ) { + wpcomTrackEvent( 'calypso_reader_post_publish_no_suggested_tags' ); + } + props.setShouldShowSuggestedTags( false ); + } else { + wpcomTrackEvent( 'calypso_reader_post_publish_show_suggested_tags', { + number_of_original_suggested_tags: origSuggestedTags.length, + } ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + + const onChangeSelectedTags = ( newTags: ( string | TokenItem )[] ) => { + setSelectedTags( newTags ); + wpcomTrackEvent( 'calypso_reader_post_publish_update_suggested_tags' ); + }; + + const tokenField = ( + + ); + + return ( +
+

{ __( 'Recommended tags:', 'jetpack-mu-wpcom' ) }

+

+ { __( + 'Based on the topics and themes in your post, here are some suggested tags to consider:', + 'jetpack-mu-wpcom' + ) } +

+ { tokenField } +

{ __( 'Adding tags can help drive more traffic to your post.', 'jetpack-mu-wpcom' ) }

+ +
+ ); +} + +export default React.memo( SuggestedTags ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts new file mode 100644 index 0000000000000..afa6e8fe4f435 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts @@ -0,0 +1,31 @@ +import apiFetch from '@wordpress/api-fetch'; + +type HasAddedTagsResult = { + added_tags: number; + success: boolean; +}; + +type OnSaveTagsCallback = ( addedTags: number ) => void; +const useAddTagsToPost = ( postId: number, tags: string[], onSaveTags: OnSaveTagsCallback ) => { + /** + * Save tags + */ + async function saveTags() { + let addedTags = 0; + try { + const result: HasAddedTagsResult = await apiFetch( { + method: 'POST', + path: `/wpcom/v2/read/posts/${ postId }/tags/add`, + data: { tags }, + } ); + addedTags = result.added_tags ?? 0; + } catch ( error ) { + // eslint-disable-next-line no-console + console.error( 'Error: Unable to add tags. Reason: %s', JSON.stringify( error ) ); + } + onSaveTags( addedTags ); + } + return { saveTags }; +}; + +export default useAddTagsToPost; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts new file mode 100644 index 0000000000000..ef136077fb30d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts @@ -0,0 +1,24 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState } from '@wordpress/element'; + +const useSharingModalDismissed = ( initial: boolean ) => { + const [ isDismissed, setSharingModalDismissed ] = useState( initial ); + + /** + * Update the value to dismiss the sharing modal + * + * @param value - The value to update. + */ + function updateIsDismissed( value: boolean ) { + apiFetch( { + method: 'PUT', + path: '/wpcom/v2/block-editor/sharing-modal-dismissed', + data: { wpcom_sharing_modal_dismissed: value }, + } ).finally( () => { + setSharingModalDismissed( value ); + } ); + } + return { isDismissed, updateIsDismissed }; +}; + +export default useSharingModalDismissed; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js new file mode 100644 index 0000000000000..6cc4da7c641eb --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js @@ -0,0 +1,150 @@ +import apiFetch from '@wordpress/api-fetch'; +import { combineReducers, registerStore } from '@wordpress/data'; +import { apiFetch as apiFetchControls, controls } from '@wordpress/data-controls'; + +export const DEFAULT_VARIANT = 'tour'; +export const BLANK_CANVAS_VARIANT = 'blank-canvas-tour'; + +const showWelcomeGuideReducer = ( state = undefined, action ) => { + switch ( action.type ) { + case 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS': + return action.response.show_welcome_guide; + case 'WPCOM_WELCOME_GUIDE_SHOW_SET': + return action.show; + case 'WPCOM_WELCOME_GUIDE_RESET_STORE': + return undefined; + default: + return state; + } +}; + +const welcomeGuideManuallyOpenedReducer = ( state = false, action ) => { + switch ( action.type ) { + case 'WPCOM_WELCOME_GUIDE_SHOW_SET': + if ( typeof action.openedManually !== 'undefined' ) { + return action.openedManually; + } + return state; + + case 'WPCOM_WELCOME_GUIDE_RESET_STORE': + return false; + + default: + return state; + } +}; + +// TODO: next PR convert file to Typescript to ensure control of tourRating values: null, 'thumbs-up' 'thumbs-down' +const tourRatingReducer = ( state = undefined, action ) => { + switch ( action.type ) { + case 'WPCOM_WELCOME_GUIDE_TOUR_RATING_SET': + return action.tourRating; + case 'WPCOM_WELCOME_GUIDE_RESET_STORE': + return undefined; + default: + return state; + } +}; + +const welcomeGuideVariantReducer = ( state = DEFAULT_VARIANT, action ) => { + switch ( action.type ) { + case 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS': + return action.response.variant; + case 'WPCOM_HAS_USED_PATTERNS_MODAL': + return state === BLANK_CANVAS_VARIANT ? DEFAULT_VARIANT : state; + case 'WPCOM_WELCOME_GUIDE_RESET_STORE': + return DEFAULT_VARIANT; + default: + return state; + } +}; + +const shouldShowFirstPostPublishedModalReducer = ( state = false, action ) => { + switch ( action.type ) { + case 'WPCOM_SET_SHOULD_SHOW_FIRST_POST_PUBLISHED_MODAL': + return action.value; + case 'WPCOM_WELCOME_GUIDE_RESET_STORE': + return false; + default: + return state; + } +}; + +const reducer = combineReducers( { + welcomeGuideManuallyOpened: welcomeGuideManuallyOpenedReducer, + showWelcomeGuide: showWelcomeGuideReducer, + tourRating: tourRatingReducer, + welcomeGuideVariant: welcomeGuideVariantReducer, + shouldShowFirstPostPublishedModal: shouldShowFirstPostPublishedModalReducer, +} ); + +export const actions = { + *fetchWelcomeGuideStatus() { + const response = yield apiFetchControls( { path: '/wpcom/v2/block-editor/nux' } ); + + return { + type: 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS', + response, + }; + }, + *fetchShouldShowFirstPostPublishedModal() { + const response = yield apiFetchControls( { + path: '/wpcom/v2/block-editor/should-show-first-post-published-modal', + } ); + + return { + type: 'WPCOM_SET_SHOULD_SHOW_FIRST_POST_PUBLISHED_MODAL', + value: response.should_show_first_post_published_modal, + }; + }, + setShowWelcomeGuide: ( show, { openedManually, onlyLocal } = {} ) => { + if ( ! onlyLocal ) { + apiFetch( { + path: '/wpcom/v2/block-editor/nux', + method: 'POST', + data: { show_welcome_guide: show }, + } ); + } + + return { + type: 'WPCOM_WELCOME_GUIDE_SHOW_SET', + show, + openedManually, + }; + }, + setTourRating: tourRating => { + return { type: 'WPCOM_WELCOME_GUIDE_TOUR_RATING_SET', tourRating }; + }, + setUsedPageOrPatternsModal: () => { + return { type: 'WPCOM_HAS_USED_PATTERNS_MODAL' }; + }, + // The `resetStore` action is only used for testing to reset the + // store inbetween tests. + resetStore: () => ( { + type: 'WPCOM_WELCOME_GUIDE_RESET_STORE', + } ), +}; + +export const selectors = { + isWelcomeGuideManuallyOpened: state => state.welcomeGuideManuallyOpened, + isWelcomeGuideShown: state => !! state.showWelcomeGuide, + isWelcomeGuideStatusLoaded: state => typeof state.showWelcomeGuide !== 'undefined', + getTourRating: state => state.tourRating, + // the 'modal' variant previously used for mobile has been removed but its slug may still be persisted in local storage + getWelcomeGuideVariant: state => + state.welcomeGuideVariant === 'modal' ? DEFAULT_VARIANT : state.welcomeGuideVariant, + getShouldShowFirstPostPublishedModal: state => state.shouldShowFirstPostPublishedModal, +}; + +/** + * Register the wpcom-welcome-guide store + */ +export function register() { + return registerStore( 'automattic/wpcom-welcome-guide', { + reducer, + actions, + selectors, + controls, + persist: true, + } ); +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js new file mode 100644 index 0000000000000..352fbd939e698 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js @@ -0,0 +1,127 @@ +import { dispatch, select } from '@wordpress/data'; +import waitForExpect from 'wait-for-expect'; +import { register, DEFAULT_VARIANT } from '../store'; + +const STORE_KEY = 'automattic/wpcom-welcome-guide'; + +beforeAll( () => { + register(); + jest.useRealTimers(); // Required for wait-for-expect to work. +} ); + +let originalFetch; +beforeEach( () => { + dispatch( STORE_KEY ).resetStore(); + originalFetch = window.fetch; + jest.spyOn( window, 'fetch' ).mockImplementation(); +} ); + +afterEach( () => { + window.fetch = originalFetch; +} ); + +test( 'resetting the store', async () => { + window.fetch.mockResolvedValue( { + status: 200, + json: () => Promise.resolve( { show_welcome_guide: true, variant: 'modal' } ), + } ); + + dispatch( STORE_KEY ).fetchWelcomeGuideStatus(); + await waitForExpect( () => + expect( select( STORE_KEY ).isWelcomeGuideStatusLoaded() ).toBe( true ) + ); + dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); + dispatch( STORE_KEY ).setTourRating( 'thumbs-up' ); + + dispatch( STORE_KEY ).resetStore(); + + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); + expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( false ); + expect( select( STORE_KEY ).isWelcomeGuideStatusLoaded() ).toBe( false ); + expect( select( STORE_KEY ).getTourRating() ).toBeUndefined(); + expect( select( STORE_KEY ).getWelcomeGuideVariant() ).toBe( DEFAULT_VARIANT ); +} ); + +test( "by default the store isn't loaded", () => { + const isLoaded = select( STORE_KEY ).isWelcomeGuideStatusLoaded(); + expect( isLoaded ).toBe( false ); +} ); + +test( 'after fetching the guide status the store is loaded', async () => { + window.fetch.mockResolvedValue( { + status: 200, + json: () => Promise.resolve( { show_welcome_guide: true, variant: DEFAULT_VARIANT } ), + } ); + + dispatch( STORE_KEY ).fetchWelcomeGuideStatus(); + + await waitForExpect( () => { + const isLoaded = select( STORE_KEY ).isWelcomeGuideStatusLoaded(); + expect( isLoaded ).toBe( true ); + } ); + + expect( window.fetch ).toHaveBeenCalledWith( + '/wpcom/v2/block-editor/nux?_locale=user', + expect.anything() + ); + + // Check the store is loaded with the state that came from the server + const isWelcomeGuideShown = select( STORE_KEY ).isWelcomeGuideShown(); + expect( isWelcomeGuideShown ).toBe( true ); + const welcomeGuideVariant = select( STORE_KEY ).getWelcomeGuideVariant(); + expect( welcomeGuideVariant ).toBe( DEFAULT_VARIANT ); +} ); + +test( 'toggle welcome guide visibility', () => { + // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise + // rejection errors that appear in CLI output + window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( true ); + expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( true ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( false ); + expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( false ); +} ); + +test( 'guide manually opened flag is false by default', () => { + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); +} ); + +test( '"manually opened" flag can be set when opening welcome guide', () => { + // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise + // rejection errors that appear in CLI output + window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: false } ); + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); +} ); + +test( 'leaving `openedManually` unspecified leaves the flag unchanged', () => { + // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise + // rejection errors that appear in CLI output + window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); + + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); + + dispatch( STORE_KEY ).setShowWelcomeGuide( false ); + expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); +} ); + +test( 'tour rating is "undefined" by default', () => { + expect( select( STORE_KEY ).getTourRating() ).toBeUndefined(); +} ); + +test( 'tour rating can be set to "thumbs-up" or "thumbs-down"', () => { + dispatch( STORE_KEY ).setTourRating( 'thumbs-up' ); + expect( select( STORE_KEY ).getTourRating() ).toBe( 'thumbs-up' ); + + dispatch( STORE_KEY ).setTourRating( 'thumbs-down' ); + expect( select( STORE_KEY ).getTourRating() ).toBe( 'thumbs-down' ); +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx new file mode 100644 index 0000000000000..733e36bdad499 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx @@ -0,0 +1,113 @@ +import { Button } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useState, useRef, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + useShouldShowVideoCelebrationModal, + useSiteIntent, + useHasSeenVideoCelebrationModal, +} from '../../../../common/tour-kit'; +import NuxModal from '../nux-modal'; +import videoSuccessImage from './video-success.svg'; +import './style.scss'; + +// Shows a celebration modal after a video is first uploaded to a site and the editor is saved. +const VideoCelebrationModalInner = () => { + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const [ hasDisplayedModal, setHasDisplayedModal ] = useState( false ); + const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ) ); + const previousIsEditorSaving = useRef( false ); + const { updateHasSeenVideoCelebrationModal } = useHasSeenVideoCelebrationModal(); + + const { isEditorSaving } = useSelect( select => { + if ( isSiteEditor ) { + const isSavingSite = + select( 'core' ).isSavingEntityRecord( 'root', 'site' ) && + ! select( 'core' ).isAutosavingEntityRecord( 'root', 'site' ); + + const page = select( 'core/edit-site' ).getPage(); + const pageId = parseInt( page?.context?.postId ); + const isSavingEntity = + select( 'core' ).isSavingEntityRecord( 'postType', 'page', pageId ) && + ! select( 'core' ).isAutosavingEntityRecord( 'postType', 'page', pageId ); + const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); + + return { + isEditorSaving: isSavingSite || isSavingEntity, + linkUrl: pageEntity?.link, + }; + } + + const currentPost = select( 'core/editor' ).getCurrentPost(); + const isSavingEntity = + select( 'core' ).isSavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ) && + ! select( 'core' ).isAutosavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ); + + return { + isEditorSaving: isSavingEntity, + }; + } ); + const shouldShowVideoCelebrationModal = useShouldShowVideoCelebrationModal( isEditorSaving ); + + useEffect( () => { + // Conditions to show modal: + // - user just finished saving + // - celebration modal hasn't been viewed/isn't visible + // - site intent is 'videopress' + // - site has uploaded a video + if ( + ! isEditorSaving && + previousIsEditorSaving.current && + ! hasDisplayedModal && + shouldShowVideoCelebrationModal + ) { + setIsModalOpen( true ); + setHasDisplayedModal( true ); + updateHasSeenVideoCelebrationModal( true ); + } + previousIsEditorSaving.current = isEditorSaving; + }, [ + isEditorSaving, + hasDisplayedModal, + shouldShowVideoCelebrationModal, + updateHasSeenVideoCelebrationModal, + ] ); + + const closeModal = () => setIsModalOpen( false ); + return ( + + + + + } + onRequestClose={ closeModal } + /> + ); +}; + +const VideoCelebrationModal = () => { + const { siteIntent: intent } = useSiteIntent(); + if ( 'videopress' === intent ) { + return ; + } + return null; +}; + +export default VideoCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss new file mode 100644 index 0000000000000..eca641aefdaa7 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss @@ -0,0 +1,37 @@ +@import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; + +.wpcom-site-editor-video-celebration-modal { + .components-modal__content { + @include break-small { + padding: 48px 90px; + } + } + + .wpcom-block-editor-nux-modal__image-container { + img { + width: 158px; + height: 85px; + } + } + + .wpcom-block-editor-nux-modal__buttons { + .components-button { + min-width: 113px; + &:not(.is-primary) { + border: 1px solid #c3c4c7; + border-radius: 4px; + } + } + } + + .components-modal__header { + button { + svg { + path { + transform: scale(1); + } + } + } + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg new file mode 100644 index 0000000000000..038f9f732ade2 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg new file mode 100644 index 0000000000000..a7fe75f9d393e --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg new file mode 100644 index 0000000000000..b3f080ffd46fd --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg new file mode 100644 index 0000000000000..120e144993c70 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg new file mode 100644 index 0000000000000..6603602512258 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss new file mode 100644 index 0000000000000..5d16245ab222f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss @@ -0,0 +1,208 @@ +@import "@automattic/typography/styles/fonts"; + +$wpcom-modal-breakpoint: 660px; + +$wpcom-modal-padding-v: 40px; +$wpcom-modal-padding-h: 50px; +$wpcom-modal-content-min-height: 350px; +$wpcom-modal-footer-padding-v: 20px; +$wpcom-modal-footer-height: 30px + ( $wpcom-modal-footer-padding-v * 2 ); + +// Core modal style overrides +.wpcom-block-editor-nux { + &.components-modal__frame { + overflow: visible; + height: 65vh; + top: calc(17.5vh - #{$wpcom-modal-footer-height * 0.5}); + + @media (max-width: $wpcom-modal-breakpoint) { + width: 90vw; + min-width: 90vw; + left: 5vw; + right: 5vw; + } + + @media (min-width: $wpcom-modal-breakpoint) { + width: 720px; + height: $wpcom-modal-content-min-height; + top: calc(50% - #{$wpcom-modal-footer-height * 0.5}); + } + } + + .components-modal__header { + position: absolute; + max-width: 90%; + left: 5%; + @media (min-width: $wpcom-modal-breakpoint) { + display: none; + } + } + + .components-guide__container { + margin-top: 0; + } + + .components-guide__footer { + position: absolute; + width: 100%; + height: $wpcom-modal-footer-height; + bottom: $wpcom-modal-footer-height * -1; + left: 0; + padding: $wpcom-modal-footer-padding-v 0; + margin: 0; + display: flex; + justify-content: center; + background: var(--studio-white); + border-top: 1px solid #dcdcde; + + @media (min-width: $wpcom-modal-breakpoint) { + border-top: none; + } + } + + .components-guide__page { + position: absolute; + width: 100%; + max-width: 90vw; + height: 100%; + justify-content: start; + + @media (min-width: $wpcom-modal-breakpoint) { + max-width: 100%; + } + } + + .components-guide__page-control { + position: relative; + height: 0; + top: 100%; + overflow: visible; + margin: 0 auto; + z-index: 2; + + &::before { + display: inline-block; + content: ""; + height: $wpcom-modal-footer-height; + vertical-align: middle; + } + + li { + vertical-align: middle; + margin-bottom: 0; + } + + // Temporarily disable dots on mobile as alignment is wonky. + display: none; + @media (min-width: $wpcom-modal-breakpoint) { + display: block; + } + } +} + +.wpcom-block-editor-nux__page { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + background: var(--studio-white); + width: 100%; + height: 90%; + max-width: 90vw; + + @media (min-width: $wpcom-modal-breakpoint) { + flex-direction: row; + justify-content: flex-start; + position: absolute; + max-width: 100%; + min-height: $wpcom-modal-content-min-height; + bottom: 0; + } +} + +.wpcom-block-editor-nux__text, +.wpcom-block-editor-nux__visual { + @media (min-width: $wpcom-modal-breakpoint) { + flex: 1 0 50%; + min-width: 290px; + } +} + +.wpcom-block-editor-nux__text { + padding: 0 25px 25px; + height: 60%; + + @media (min-width: $wpcom-modal-breakpoint) { + height: auto; + padding: $wpcom-modal-padding-v $wpcom-modal-padding-h; + } +} +.wpcom-block-editor-nux__visual { + height: 40%; + background: #1381d8; + text-align: center; + + @media (min-width: $wpcom-modal-breakpoint) { + height: auto; + } +} + +.wpcom-block-editor-nux__heading { + /* Gray / Gray 90 */ + color: #1d2327; + + font-family: $brand-serif; + font-weight: 400; + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 32px; + line-height: 1.19; + letter-spacing: -0.4px; + + @media (min-width: $wpcom-modal-breakpoint) { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 42px; + } + + // TODO: remove this hack once the welcome editor deals better with + // overflowing text + body.locale-de & { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 24px; + + @media (min-width: $wpcom-modal-breakpoint) { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 28px; + } + } +} + +.wpcom-block-editor-nux__description { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 15px; + line-height: 22px; + + /* Gray / Gray 60 */ + color: #50575e; + + @media (min-width: $wpcom-modal-breakpoint) { + /* stylelint-disable-next-line declaration-property-unit-allowed-list */ + font-size: 17px; + line-height: 26px; + } +} + +.wpcom-block-editor-nux__image { + max-width: 100%; + height: auto; + flex: 1; + align-self: center; + + &.align-bottom { + align-self: flex-end; + } + + max-height: 100%; + + @media (min-width: $wpcom-modal-breakpoint) { + max-height: none; + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js new file mode 100644 index 0000000000000..020c21390770a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js @@ -0,0 +1,153 @@ +import { Guide, GuidePage } from '@wordpress/components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import blockPickerImage from './images/block-picker.svg'; +import editorImage from './images/editor.svg'; +import previewImage from './images/preview.svg'; +import privateImage from './images/private.svg'; + +import './style.scss'; + +/** + * The nux component. + */ +function WpcomNux() { + const { show, isNewPageLayoutModalOpen, isManuallyOpened } = useSelect( select => ( { + show: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideShown(), + isNewPageLayoutModalOpen: + select( 'automattic/starter-page-layouts' ) && // Handle the case where SPT is not initalized. + select( 'automattic/starter-page-layouts' ).isOpen(), + isManuallyOpened: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideManuallyOpened(), + } ) ); + + const { setShowWelcomeGuide } = useDispatch( 'automattic/wpcom-welcome-guide' ); + + // Track opening of the welcome guide + useEffect( () => { + if ( show && ! isNewPageLayoutModalOpen ) { + wpcomTrackEvent( 'calypso_editor_wpcom_nux_open', { + is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, + is_manually_opened: isManuallyOpened, + } ); + } + }, [ isManuallyOpened, isNewPageLayoutModalOpen, show ] ); + + if ( ! show || isNewPageLayoutModalOpen ) { + return null; + } + + const dismissWpcomNux = () => { + wpcomTrackEvent( 'calypso_editor_wpcom_nux_dismiss', { + is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, + } ); + setShowWelcomeGuide( false, { openedManually: false } ); + }; + + const nuxPages = getWpcomNuxPages(); + + return ( + + { nuxPages.map( ( nuxPage, index ) => ( + + ) ) } + + ); +} + +/** + * This function returns a collection of NUX slide data + * @returns { Array } a collection of props + */ +function getWpcomNuxPages() { + return [ + { + heading: __( 'Welcome to your website', 'jetpack-mu-wpcom' ), + description: __( + 'Edit your homepage, add the pages you need, and change your site’s look and feel.', + 'jetpack-mu-wpcom' + ), + imgSrc: editorImage, + alignBottom: true, + }, + { + heading: __( 'Add or edit your content', 'jetpack-mu-wpcom' ), + description: __( + 'Edit the placeholder content we’ve started you off with, or click the plus sign to add more content.', + 'jetpack-mu-wpcom' + ), + imgSrc: blockPickerImage, + }, + { + heading: __( 'Preview your site as you go', 'jetpack-mu-wpcom' ), + description: __( + 'As you edit your site content, click “Preview” to see your site the way your visitors will.', + 'jetpack-mu-wpcom' + ), + imgSrc: previewImage, + alignBottom: true, + }, + { + heading: __( 'Hidden until you’re ready', 'jetpack-mu-wpcom' ), + description: __( + 'Your site will remain hidden until launched. Click “Launch” in the toolbar to share it with the world.', + 'jetpack-mu-wpcom' + ), + imgSrc: privateImage, + alignBottom: true, + }, + ]; +} + +/** + * Display the Nux page + * + * @param props - The props of the component. + * @param props.pageNumber - The number of page. + * @param props.isLastPage - Whether the current page is the last one. + * @param props.alignBottom - Whether to align bottom. + * @param props.heading - The text of heading. + * @param props.description - The text of description. + * @param props.imgSrc - The src of image. + */ +function NuxPage( { pageNumber, isLastPage, alignBottom = false, heading, description, imgSrc } ) { + useEffect( () => { + wpcomTrackEvent( 'calypso_editor_wpcom_nux_slide_view', { + slide_number: pageNumber, + is_last_slide: isLastPage, + is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, + } ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [] ); + return ( + +
+

{ heading }

+
{ description }
+
+
+ +
+
+ ); +} + +export default WpcomNux; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts new file mode 100644 index 0000000000000..876bd2e34a5d3 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts @@ -0,0 +1,41 @@ +import { select } from '@wordpress/data'; + +/** + * Post (Post Type: ‘post’) + * Page (Post Type: ‘page’) + * Attachment (Post Type: ‘attachment’) + * Revision (Post Type: ‘revision’) + * Navigation menu (Post Type: ‘nav_menu_item’) + * Block templates (Post Type: ‘wp_template’) + * Template parts (Post Type: ‘wp_template_part’) + * @see https://developer.wordpress.org/themes/basics/post-types/#default-post-types + */ + +type PostType = + | 'post' + | 'page' + | 'attachment' + | 'revision' + | 'nav_menu_item' + | 'wp_template' + | 'wp_template_part' + | null; + +type EditorType = 'site' | PostType; + +export const getEditorType = (): EditorType | undefined => { + /** + * Beware when using this method to figure out if we are in the site editor. + * @see https://github.com/WordPress/gutenberg/issues/46616#issuecomment-1355301090 + * @see https://github.com/Automattic/jetpack/blob/2e56d0d/projects/plugins/jetpack/extensions/shared/get-editor-type.js + */ + if ( select( 'core/edit-site' ) ) { + return 'site'; + } + + if ( select( 'core/editor' ) ) { + return select( 'core/editor' ).getCurrentPostType() as PostType; + } + + return undefined; +}; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss new file mode 100644 index 0000000000000..51a9eaaf2407f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss @@ -0,0 +1,51 @@ +@use "sass:math"; +@import "@wordpress/base-styles/colors"; +@import "@wordpress/base-styles/mixins"; +@import "@wordpress/base-styles/variables"; +@import "@wordpress/base-styles/z-index"; + +$welcome-tour-card-media-extra-padding: 14%; // temporary value, to match the padding of the desktop instructional graphics + +.wpcom-editor-welcome-tour { + .wpcom-editor-welcome-tour__step { + &.is-with-extra-padding { + .components-card__media { + background-color: #e7eaeb; // the color of the background used in desktop graphics + + img { + left: $welcome-tour-card-media-extra-padding; + top: $welcome-tour-card-media-extra-padding; + width: 100% - $welcome-tour-card-media-extra-padding; + } + } + } + } + + .wpcom-tour-kit-step-card-overlay-controls { + position: absolute; + } +} + +// @todo clk - it this used? +.wpcom-editor-welcome-tour-card-frame { + position: relative; + + .components-guide__page-control { + bottom: 0; + left: $grid-unit-20; + margin: 0; + position: absolute; + + li { + margin-bottom: 0; + } + } +} + +// Adding it to hide the WelcomeTour when the W-icon is pressed on mobile +#wpwrap.wp-responsive-open { + + .tour-kit.wpcom-tour-kit { + display: none; + } +} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts new file mode 100644 index 0000000000000..fac4d7e1547a1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts @@ -0,0 +1,110 @@ +import '../get-editor-type'; +import getTourSteps from '../tour-steps'; + +jest.mock( '../get-editor-type', () => { + return { getEditorType: () => 'post' }; +} ); + +describe( 'Welcome Tour', () => { + describe( 'Tour Steps', () => { + it( 'should retrieve the "Welcome to WordPress!" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Welcome to WordPress!' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Everything is a block" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Everything is a block' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Adding a new block" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Adding a new block' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Click a block to change it" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Click a block to change it' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "More Options" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'More Options' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Find your way" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Find your way' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Undo any mistake" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Undo any mistake' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Drag & drop" slide', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Undo any mistake' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Edit your site" slide, when in site editor', () => { + expect( getTourSteps( 'en', true, true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Edit your site' } ), + } ), + ] ) + ); + } ); + it( 'should not retrieve the "Edit your site" slide, when not in site editor', () => { + expect( getTourSteps( 'en', true, false ) ).not.toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Edit your site' } ), + } ), + ] ) + ); + } ); + it( 'should retrieve the "Congratulations!" slide, with correct url', () => { + expect( getTourSteps( 'en', true ) ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + meta: expect.objectContaining( { heading: 'Congratulations!' } ), + } ), + ] ) + ); + } ); + } ); +} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx new file mode 100644 index 0000000000000..1df746ef992b5 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx @@ -0,0 +1,243 @@ +import { useLocale } from '@automattic/i18n-utils'; +import { useDispatch, useSelect, dispatch } from '@wordpress/data'; +import { useEffect, useMemo } from '@wordpress/element'; +import { + WpcomTourKit, + usePrefetchTourAssets, + START_WRITING_FLOW, + DESIGN_FIRST_FLOW, + useSiteIntent, + useSitePlan, +} from '../../../../common/tour-kit'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import { getEditorType } from './get-editor-type'; +import useTourSteps from './use-tour-steps'; +import './style-tour.scss'; + +/** + * The Welcome Tour of the Launch. + */ +function LaunchWpcomWelcomeTour() { + const { show, isNewPageLayoutModalOpen, isManuallyOpened } = useSelect( + select => ( { + show: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideShown(), + // Handle the case where the new page pattern modal is initialized and open + isNewPageLayoutModalOpen: + select( 'automattic/starter-page-layouts' ) && + select( 'automattic/starter-page-layouts' ).isOpen(), + isManuallyOpened: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideManuallyOpened(), + } ), + [] + ); + const { siteIntent, siteIntentFetched } = useSiteIntent(); + const localeSlug = useLocale(); + const editorType = getEditorType(); + const { siteIntent: intent } = useSiteIntent(); + // We check the URL param along with site intent because the param loads faster and prevents element flashing. + const isBlogOnboardingFlow = intent === START_WRITING_FLOW || intent === DESIGN_FIRST_FLOW; + + const tourSteps = useTourSteps( localeSlug, false, false, null, siteIntent ); + + // Preload first card image (others preloaded after open state confirmed) + usePrefetchTourAssets( [ tourSteps[ 0 ] ] ); + + useEffect( () => { + if ( isBlogOnboardingFlow ) { + return; + } + if ( ! show && ! isNewPageLayoutModalOpen ) { + return; + } + + if ( ! siteIntentFetched ) { + return; + } + + // Track opening of the Welcome Guide + wpcomTrackEvent( 'calypso_editor_wpcom_tour_open', { + is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, + is_manually_opened: isManuallyOpened, + intent: siteIntent, + editor_type: editorType, + } ); + }, [ + isNewPageLayoutModalOpen, + isManuallyOpened, + show, + siteIntent, + siteIntentFetched, + editorType, + isBlogOnboardingFlow, + ] ); + + if ( ! show || isNewPageLayoutModalOpen || isBlogOnboardingFlow ) { + return null; + } + + return ; +} + +/** + * Display the welcome tour. + * + * @param props - The props of the component. + * @param props.siteIntent - The intent of the site. + */ +function WelcomeTour( { siteIntent } ) { + const sitePlan = useSitePlan( window._currentSiteId ); + const localeSlug = useLocale(); + const { setShowWelcomeGuide } = useDispatch( 'automattic/wpcom-welcome-guide' ); + const isGutenboarding = window.calypsoifyGutenberg?.isGutenboarding; + const isWelcomeTourNext = () => { + return new URLSearchParams( document.location.search ).has( 'welcome-tour-next' ); + }; + const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ), [] ); + const currentTheme = useSelect( select => select( 'core' ).getCurrentTheme() ); + const themeName = currentTheme?.name?.raw?.toLowerCase() ?? null; + + const tourSteps = useTourSteps( + localeSlug, + isWelcomeTourNext(), + isSiteEditor, + themeName, + siteIntent + ); + + // Only keep Payment block step if user comes from seller simple flow + if ( ! ( 'sell' === siteIntent && sitePlan && 'ecommerce-bundle' !== sitePlan.product_slug ) ) { + const paymentBlockIndex = tourSteps.findIndex( step => step.slug === 'payment-block' ); + tourSteps.splice( paymentBlockIndex, 1 ); + } + const { isInserterOpened, isSidebarOpened, isSettingsOpened } = useSelect( + select => ( { + isInserterOpened: select( 'core/edit-post' ).isInserterOpened(), + isSidebarOpened: select( 'automattic/block-editor-nav-sidebar' )?.isSidebarOpened() ?? false, // The sidebar store may not always be loaded. + isSettingsOpened: + select( 'core/interface' ).getActiveComplementaryArea( 'core/edit-post' ) === + 'edit-post/document', + } ), + [] + ); + + const isTourMinimized = + isSidebarOpened || + ( window.matchMedia( `(max-width: 782px)` ).matches && + ( isInserterOpened || isSettingsOpened ) ); + + const editorType = getEditorType(); + + const tourConfig = { + steps: tourSteps, + closeHandler: ( _steps, currentStepIndex, source ) => { + wpcomTrackEvent( 'calypso_editor_wpcom_tour_dismiss', { + is_gutenboarding: isGutenboarding, + slide_number: currentStepIndex + 1, + action: source, + intent: siteIntent, + editor_type: editorType, + } ); + setShowWelcomeGuide( false, { openedManually: false } ); + }, + isMinimized: isTourMinimized, + options: { + tourRating: { + enabled: true, + useTourRating: () => { + return useSelect( + select => select( 'automattic/wpcom-welcome-guide' ).getTourRating(), + [] + ); + }, + onTourRate: rating => { + dispatch( 'automattic/wpcom-welcome-guide' ).setTourRating( rating ); + wpcomTrackEvent( 'calypso_editor_wpcom_tour_rate', { + thumbs_up: rating === 'thumbs-up', + is_gutenboarding: false, + intent: siteIntent, + editor_type: editorType, + } ); + }, + }, + callbacks: { + onMinimize: currentStepIndex => { + wpcomTrackEvent( 'calypso_editor_wpcom_tour_minimize', { + is_gutenboarding: isGutenboarding, + slide_number: currentStepIndex + 1, + intent: siteIntent, + editor_type: editorType, + } ); + }, + onMaximize: currentStepIndex => { + wpcomTrackEvent( 'calypso_editor_wpcom_tour_maximize', { + is_gutenboarding: isGutenboarding, + slide_number: currentStepIndex + 1, + intent: siteIntent, + editor_type: editorType, + } ); + }, + onStepViewOnce: currentStepIndex => { + const lastStepIndex = tourSteps.length - 1; + const { heading } = tourSteps[ currentStepIndex ].meta; + + wpcomTrackEvent( 'calypso_editor_wpcom_tour_slide_view', { + slide_number: currentStepIndex + 1, + is_last_slide: currentStepIndex === lastStepIndex, + slide_heading: heading, + is_gutenboarding: isGutenboarding, + intent: siteIntent, + editor_type: editorType, + } ); + }, + }, + effects: { + spotlight: isWelcomeTourNext() + ? { + styles: { + minWidth: '50px', + minHeight: '50px', + borderRadius: '2px', + }, + } + : undefined, + arrowIndicator: false, + }, + popperModifiers: [ + useMemo( + () => ( { + name: 'offset', + options: { + offset: ( { placement, reference } ) => { + if ( placement === 'bottom' ) { + const boundary = document.querySelector( '.edit-post-header' ); + + if ( ! boundary ) { + return; + } + + const boundaryRect = boundary.getBoundingClientRect(); + const boundaryBottomY = boundaryRect.height + boundaryRect.y; + const referenceBottomY = reference.height + reference.y; + + return [ 0, boundaryBottomY - referenceBottomY + 16 ]; + } + return [ 0, 0 ]; + }, + }, + } ), + [] + ), + ], + classNames: 'wpcom-editor-welcome-tour', + portalParentElement: document.getElementById( 'wpwrap' ), + }, + }; + + // Theme isn't immediately available, so we prevent rendering so the content doesn't switch after it is presented, since some content is based on theme + if ( null === themeName ) { + return null; + } + + return ; +} + +export default LaunchWpcomWelcomeTour; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx new file mode 100644 index 0000000000000..6d0ad267c70c1 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx @@ -0,0 +1,445 @@ +import { localizeUrl } from '@automattic/i18n-utils'; +import { ExternalLink } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; +import { createInterpolateElement } from '@wordpress/element'; +import { __, _x } from '@wordpress/i18n'; +import { getQueryArg } from '@wordpress/url'; +import { wpcomTrackEvent } from '../../../../common/tracks'; +import { getEditorType } from './get-editor-type'; +import type { WpcomStep } from '../../../../common/tour-kit'; + +interface TourAsset { + desktop?: { src: string; type: string }; + mobile?: { src: string; type: string }; +} + +/** + * Get the tour asset by the key. + * + * @param key - The key of the tour asset. + */ +function getTourAssets( key: string ): TourAsset | undefined { + const CDN_PREFIX = 'https://s0.wp.com/i/editor-welcome-tour'; + const tourAssets = { + addBlock: { + desktop: { src: `${ CDN_PREFIX }/slide-add-block.gif`, type: 'image/gif' }, + mobile: { src: `${ CDN_PREFIX }/slide-add-block_mobile.gif`, type: 'image/gif' }, + }, + allBlocks: { desktop: { src: `${ CDN_PREFIX }/slide-all-blocks.gif`, type: 'image/gif' } }, + finish: { desktop: { src: `${ CDN_PREFIX }/slide-finish.png`, type: 'image/gif' } }, + makeBold: { desktop: { src: `${ CDN_PREFIX }/slide-make-bold.gif`, type: 'image/gif' } }, + moreOptions: { + desktop: { src: `${ CDN_PREFIX }/slide-more-options.gif`, type: 'image/gif' }, + mobile: { src: `${ CDN_PREFIX }/slide-more-options_mobile.gif`, type: 'image/gif' }, + }, + moveBlock: { + desktop: { src: `${ CDN_PREFIX }/slide-move-block.gif`, type: 'image/gif' }, + mobile: { src: `${ CDN_PREFIX }/slide-move-block_mobile.gif`, type: 'image/gif' }, + }, + findYourWay: { + desktop: { src: `${ CDN_PREFIX }/slide-find-your-way.gif`, type: 'image/gif' }, + }, + undo: { desktop: { src: `${ CDN_PREFIX }/slide-undo.gif`, type: 'image/gif' } }, + welcome: { + desktop: { src: `${ CDN_PREFIX }/slide-welcome.png`, type: 'image/png' }, + mobile: { src: `${ CDN_PREFIX }/slide-welcome_mobile.jpg`, type: 'image/jpeg' }, + }, + editYourSite: { + desktop: { + src: `https://s.w.org/images/block-editor/edit-your-site.gif?1`, + type: 'image/gif', + }, + mobile: { + src: `https://s.w.org/images/block-editor/edit-your-site.gif?1`, + type: 'image/gif', + }, + }, + videomakerWelcome: { + desktop: { src: `${ CDN_PREFIX }/slide-videomaker-welcome.png`, type: 'image/png' }, + }, + videomakerEdit: { + desktop: { src: `${ CDN_PREFIX }/slide-videomaker-edit.png`, type: 'image/png' }, + }, + } as { [ key: string ]: TourAsset }; + + return tourAssets[ key ]; +} + +/** + * Get the steps of the tour + * + * @param localeSlug - The slug of the locale. + * @param referencePositioning - The reference positioning. + * @param isSiteEditor - Whether is the site editor. + * @param themeName - The name of the theme. + * @param siteIntent - The intent of the current site. + */ +function useTourSteps( + localeSlug: string, + referencePositioning = false, + isSiteEditor = false, + themeName: string | null = null, + siteIntent: string | undefined = undefined +): WpcomStep[] { + const isVideoMaker = 'videomaker' === ( themeName ?? '' ); + const isPatternAssembler = !! getQueryArg( window.location.href, 'assembler' ); + const isMobile = useViewportMatch( 'mobile', '<' ); + const siteEditorCourseUrl = `https://wordpress.com/home/${ window.location.hostname }?courseSlug=site-editor-quick-start`; + const editorType = getEditorType(); + const onSiteEditorCourseLinkClick = () => { + wpcomTrackEvent( 'calypso_editor_wpcom_tour_site_editor_course_link_click', { + is_pattern_assembler: isPatternAssembler, + intent: siteIntent, + editor_type: editorType, + } ); + }; + + return [ + { + slug: 'welcome', + meta: { + heading: isPatternAssembler + ? __( 'Nice job! Your new page is set up.', 'jetpack-mu-wpcom' ) + : _x( 'Welcome to WordPress!', 'jetpack-mu-wpcom', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: ( () => { + if ( isPatternAssembler ) { + return createInterpolateElement( + __( + 'This is the Site Editor, where you can change everything about your site, including adding content to your homepage. Watch these short videos and take this tour to get started.', + 'jetpack-mu-wpcom' + ), + { + link_to_site_editor_course: ( + + ), + } + ); + } + + return isSiteEditor + ? __( + 'Take this short, interactive tour to learn the fundamentals of the WordPress Site Editor.', + 'jetpack-mu-wpcom' + ) + : _x( + 'Take this short, interactive tour to learn the fundamentals of the WordPress editor.', + 'jetpack-mu-wpcom', + 'jetpack-mu-wpcom' + ); + } )(), + mobile: null, + }, + imgSrc: getTourAssets( isVideoMaker ? 'videomakerWelcome' : 'welcome' ), + imgLink: isPatternAssembler + ? { + href: siteEditorCourseUrl, + playable: true, + onClick: onSiteEditorCourseLinkClick, + } + : undefined, + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: [ 'is-with-extra-padding', 'calypso_editor_wpcom_draft_post_modal_show' ], + }, + }, + }, + { + slug: 'everything-is-a-block', + meta: { + heading: __( 'Everything is a block', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( + 'In the WordPress Editor, paragraphs, images, and videos are all blocks.', + 'jetpack-mu-wpcom' + ), + mobile: null, + }, + imgSrc: getTourAssets( 'allBlocks' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + { + slug: 'add-block', + ...( referencePositioning && { + referenceElements: { + mobile: + '.edit-post-header .edit-post-header__toolbar .components-button.edit-post-header-toolbar__inserter-toggle', + desktop: + '.edit-post-header .edit-post-header__toolbar .components-button.edit-post-header-toolbar__inserter-toggle', + }, + } ), + meta: { + heading: __( 'Adding a new block', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( + 'Click + to open the inserter. Then click the block you want to add.', + 'jetpack-mu-wpcom' + ), + mobile: __( + 'Tap + to open the inserter. Then tap the block you want to add.', + 'jetpack-mu-wpcom' + ), + }, + imgSrc: getTourAssets( 'addBlock' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], + }, + }, + }, + { + slug: 'edit-block', + meta: { + heading: __( 'Click a block to change it', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: isVideoMaker + ? __( + 'Use the toolbar to change the appearance of a selected block. Try replacing a video!', + 'jetpack-mu-wpcom' + ) + : _x( + 'Use the toolbar to change the appearance of a selected block. Try making it bold.', + 'jetpack-mu-wpcom', + 'jetpack-mu-wpcom' + ), + mobile: null, + }, + imgSrc: getTourAssets( isVideoMaker ? 'videomakerEdit' : 'makeBold' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + { + slug: 'settings', + ...( referencePositioning && { + referenceElements: { + mobile: + '.edit-post-header .edit-post-header__settings .interface-pinned-items > button:nth-child(1)', + desktop: + '.edit-post-header .edit-post-header__settings .interface-pinned-items > button:nth-child(1)', + }, + } ), + meta: { + heading: __( 'More Options', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( 'Click the settings icon to see even more options.', 'jetpack-mu-wpcom' ), + mobile: __( 'Tap the settings icon to see even more options.', 'jetpack-mu-wpcom' ), + }, + imgSrc: getTourAssets( 'moreOptions' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], + }, + }, + }, + ...( ! isMobile + ? [ + { + slug: 'find-your-way', + meta: { + heading: __( 'Find your way', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( + "Use List View to see all the blocks you've added. Click and drag any block to move it around.", + 'jetpack-mu-wpcom' + ), + mobile: null, + }, + imgSrc: getTourAssets( 'findYourWay' ), + }, + options: { + classNames: { + desktop: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + ] + : [] ), + ...( ! isMobile + ? [ + { + slug: 'undo', + ...( referencePositioning && { + referenceElements: { + desktop: + '.edit-post-header .edit-post-header__toolbar .components-button.editor-history__undo', + }, + } ), + meta: { + heading: __( 'Undo any mistake', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( + "Click the Undo button if you've made a mistake.", + 'jetpack-mu-wpcom' + ), + mobile: null, + }, + imgSrc: getTourAssets( 'undo' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + ] + : [] ), + { + slug: 'drag-drop', + meta: { + heading: __( 'Drag & drop', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: __( 'To move blocks around, click and drag the handle.', 'jetpack-mu-wpcom' ), + mobile: __( 'To move blocks around, tap the up and down arrows.', 'jetpack-mu-wpcom' ), + }, + imgSrc: getTourAssets( 'moveBlock' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], + }, + }, + }, + { + slug: 'payment-block', + meta: { + heading: __( 'The Payments block', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: ( + <> + { __( + 'The Payments block allows you to accept payments for one-time, monthly recurring, or annual payments on your website', + 'jetpack-mu-wpcom' + ) } +
+ + { __( 'Learn more', 'jetpack-mu-wpcom' ) } + + + ), + mobile: null, + }, + imgSrc: getTourAssets( 'welcome' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + ...( isSiteEditor + ? [ + { + slug: 'edit-your-site', + meta: { + heading: __( 'Edit your site', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: createInterpolateElement( + __( + 'Design everything on your site - from the header right down to the footer - in the Site Editor. Learn more', + 'jetpack-mu-wpcom' + ), + { + link_to_fse_docs: ( + + ), + } + ), + mobile: __( + 'Design everything on your site - from the header right down to the footer - in the Site Editor.', + 'jetpack-mu-wpcom' + ), + }, + imgSrc: getTourAssets( 'editYourSite' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], + }, + }, + }, + ] + : [] ), + { + slug: 'congratulations', + meta: { + heading: __( 'Congratulations!', 'jetpack-mu-wpcom' ), + descriptions: { + desktop: createInterpolateElement( + __( + "You've learned the basics. Remember, your site is private until you decide to launch. View the block editing docs to learn more.", + 'jetpack-mu-wpcom' + ), + { + link_to_launch_site_docs: ( + + ), + link_to_editor_docs: ( + + ), + } + ), + mobile: null, + }, + imgSrc: getTourAssets( 'finish' ), + }, + options: { + classNames: { + desktop: 'wpcom-editor-welcome-tour__step', + mobile: 'wpcom-editor-welcome-tour__step', + }, + }, + }, + ]; +} + +export default useTourSteps; diff --git a/projects/packages/jetpack-mu-wpcom/webpack.config.js b/projects/packages/jetpack-mu-wpcom/webpack.config.js index ea855ad9da300..cfdb59d970f19 100644 --- a/projects/packages/jetpack-mu-wpcom/webpack.config.js +++ b/projects/packages/jetpack-mu-wpcom/webpack.config.js @@ -37,6 +37,7 @@ module.exports = [ 'wpcom-blocks-timeline-editor': './src/features/wpcom-blocks/timeline/editor.js', 'wpcom-blocks-timeline-view': './src/features/wpcom-blocks/timeline/view.js', 'wpcom-block-description-links': './src/features/wpcom-block-description-links/index.tsx', + 'wpcom-block-editor-nux': './src/features/wpcom-block-editor-nux/index.js', 'wpcom-global-styles-editor': './src/features/wpcom-global-styles/index.js', 'wpcom-global-styles-frontend': './src/features/wpcom-global-styles/wpcom-global-styles-view.js', From d6c1bb6463182086d2c8864827f823f8f9d130ce Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Thu, 1 Aug 2024 08:24:10 +0100 Subject: [PATCH 04/33] Classic Theme Helper: Copy Social Links code to Classic Theme Helper package (#38593) --- .../classic-theme-helper/.phan/baseline.php | 5 +- ...cial-links-to-classic-theme-helper-package | 4 + .../classic-theme-helper/package.json | 2 +- .../classic-theme-helper/src/class-main.php | 2 +- .../classic-theme-helper/src/social-links.php | 279 ++++++++++++++++++ ...cial-links-to-classic-theme-helper-package | 4 + .../modules/theme-tools/social-links.php | 16 +- 7 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 projects/packages/classic-theme-helper/changelog/add-social-links-to-classic-theme-helper-package create mode 100644 projects/packages/classic-theme-helper/src/social-links.php create mode 100644 projects/plugins/jetpack/changelog/add-social-links-to-classic-theme-helper-package diff --git a/projects/packages/classic-theme-helper/.phan/baseline.php b/projects/packages/classic-theme-helper/.phan/baseline.php index 0ddcfaf434b6c..82a2e7525ac13 100644 --- a/projects/packages/classic-theme-helper/.phan/baseline.php +++ b/projects/packages/classic-theme-helper/.phan/baseline.php @@ -10,18 +10,21 @@ return [ // # Issue statistics: // PhanTypeMismatchArgumentInternal : 10+ occurrences + // PhanUndeclaredClassMethod : 7 occurrences + // PhanUndeclaredClassReference : 4 occurrences // PhanTypeInvalidDimOffset : 2 occurrences // PhanTypeMismatchArgument : 2 occurrences // PhanTypeComparisonToArray : 1 occurrence // PhanTypeMismatchArgumentProbablyReal : 1 occurrence // PhanTypeMismatchProperty : 1 occurrence // PhanTypePossiblyInvalidDimOffset : 1 occurrence - // PhanUndeclaredFunction : 1 occurrence + // PhanUndeclaredTypeProperty : 1 occurrence // Currently, file_suppressions and directory_suppressions are the only supported suppressions 'file_suppressions' => [ '_inc/lib/tonesque.php' => ['PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentInternal', 'PhanTypeMismatchArgumentProbablyReal'], 'src/class-featured-content.php' => ['PhanTypeComparisonToArray', 'PhanTypeInvalidDimOffset', 'PhanTypeMismatchArgument', 'PhanTypeMismatchProperty', 'PhanTypePossiblyInvalidDimOffset'], + 'src/social-links.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredClassReference', 'PhanUndeclaredTypeProperty'], ], // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed. // (directory_suppressions will currently be ignored by subsequent calls to --save-baseline, but may be preserved in future Phan releases) diff --git a/projects/packages/classic-theme-helper/changelog/add-social-links-to-classic-theme-helper-package b/projects/packages/classic-theme-helper/changelog/add-social-links-to-classic-theme-helper-package new file mode 100644 index 0000000000000..c314b1446fbd5 --- /dev/null +++ b/projects/packages/classic-theme-helper/changelog/add-social-links-to-classic-theme-helper-package @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Social Links: Added feature to Classic Theme Helper package. diff --git a/projects/packages/classic-theme-helper/package.json b/projects/packages/classic-theme-helper/package.json index bd863220da3ce..3a6afb51dd960 100644 --- a/projects/packages/classic-theme-helper/package.json +++ b/projects/packages/classic-theme-helper/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-classic-theme-helper", - "version": "0.4.3", + "version": "0.4.4-alpha", "description": "Features used with classic themes", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/classic-theme-helper/#readme", "bugs": { diff --git a/projects/packages/classic-theme-helper/src/class-main.php b/projects/packages/classic-theme-helper/src/class-main.php index 4445f2f760f27..d608530e70557 100644 --- a/projects/packages/classic-theme-helper/src/class-main.php +++ b/projects/packages/classic-theme-helper/src/class-main.php @@ -14,7 +14,7 @@ */ class Main { - const PACKAGE_VERSION = '0.4.3'; + const PACKAGE_VERSION = '0.4.4-alpha'; /** * Modules to include. diff --git a/projects/packages/classic-theme-helper/src/social-links.php b/projects/packages/classic-theme-helper/src/social-links.php new file mode 100644 index 0000000000000..f09024595d1f5 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/social-links.php @@ -0,0 +1,279 @@ +theme_supported_services = $theme_support[0]; + $this->links = class_exists( \Jetpack_Options::class ) ? \Jetpack_Options::get_option( 'social_links', array() ) : ''; + + $this->admin_setup(); + + add_filter( 'jetpack_has_social_links', array( $this, 'has_social_links' ) ); + add_filter( 'jetpack_get_social_links', array( $this, 'get_social_links' ) ); + + foreach ( $theme_support[0] as $service ) { + add_filter( "pre_option_jetpack-$service", array( $this, 'get_social_link_filter' ) ); // - `get_option( 'jetpack-service' );` + add_filter( "theme_mod_jetpack-$service", array( $this, 'get_social_link_filter' ) ); // - `get_theme_mod( 'jetpack-service' );` + } + } + + /** + * Init the admin setup. + */ + public function admin_setup() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + if ( ! is_admin() && ! $this->is_customize_preview() ) { + return; + } + + // @phan-suppress-next-line PhanUndeclaredFunction -- Function checked with function_exists - see https://github.com/phan/phan/issues/1204. + $this->publicize = function_exists( 'publicize_init' ) ? publicize_init() : null; + $publicize_services = $this->publicize->get_services( 'connected' ); + $this->services = array_intersect( array_keys( $publicize_services ), $this->theme_supported_services ); + + add_action( 'publicize_connected', array( $this, 'check_links' ), 20 ); + add_action( 'publicize_disconnected', array( $this, 'check_links' ), 20 ); + add_action( 'customize_register', array( $this, 'customize_register' ) ); + add_filter( 'sanitize_option_jetpack_options', array( $this, 'sanitize_link' ) ); + } + + /** + * Compares the currently saved links with the connected services and removes + * links from services that are no longer connected. + * + * @return void + */ + public function check_links() { + $active_links = array_intersect_key( $this->links, array_flip( $this->services ) ); + + if ( $active_links !== $this->links ) { + $this->links = $active_links; + if ( class_exists( \Jetpack_Options::class ) ) { + \Jetpack_Options::update_option( 'social_links', $active_links ); + } + } + } + + /** + * Add social link dropdown to the Customizer. + * + * @param \WP_Customize_Manager $wp_customize Theme Customizer object. + */ + public function customize_register( $wp_customize ) { + $wp_customize->add_section( + 'jetpack_social_links', + array( + 'title' => esc_html__( 'Connect', 'jetpack-classic-theme-helper' ), + 'priority' => 35, + ) + ); + + if ( class_exists( \Publicize::class ) ) { + foreach ( array_keys( $this->publicize->get_services( 'all' ) ) as $service ) { + $choices = $this->get_customize_select( $service ); + + if ( empty( $choices ) ) { + continue; + } + + $wp_customize->add_setting( + "jetpack_options[social_links][$service]", + array( + 'type' => 'option', + 'default' => '', + ) + ); + + $wp_customize->add_control( + "jetpack-$service", + array( + 'label' => $this->publicize->get_service_label( $service ), + 'section' => 'jetpack_social_links', + 'settings' => "jetpack_options[social_links][$service]", + 'type' => 'select', + 'choices' => $choices, + ) + ); + } + } + } + + /** + * Sanitizes social links. + * + * @param array $option The incoming values to be sanitized. + * @return array + */ + public function sanitize_link( $option ) { + foreach ( $this->services as $service ) { + if ( ! empty( $option['social_links'][ $service ] ) ) { + $option['social_links'][ $service ] = esc_url_raw( $option['social_links'][ $service ] ); + } else { + unset( $option['social_links'][ $service ] ); + } + } + + return $option; + } + + /** + * Returns whether there are any social links set. + * + * @return bool + */ + public function has_social_links() { + return ! empty( $this->links ); + } + + /** + * Return available social links. + * + * @return array + */ + public function get_social_links() { + return $this->links; + } + + /** + * Short-circuits get_option and get_theme_mod calls. + * + * @param string $link The incoming value to be replaced. + * @return string $link The social link that we've got. + */ + public function get_social_link_filter( $link ) { + if ( preg_match( '/_jetpack-(.+)$/i', current_filter(), $matches ) && ! empty( $this->links[ $matches[1] ] ) ) { + return $this->links[ $matches[1] ]; + } + + return $link; + } + + /** + * Puts together an array of choices for a specific service. + * + * @param string $service The social service. + * @return array An associative array with profile links and display names. + */ + private function get_customize_select( $service ) { + $choices = array( + '' => __( '— Select —', 'jetpack-classic-theme-helper' ), + ); + + if ( isset( $this->links[ $service ] ) ) { + $choices[ $this->links[ $service ] ] = $this->links[ $service ]; + } + + if ( class_exists( \Publicize::class ) ) { + $connected_services = $this->publicize->get_services( 'connected' ); + if ( isset( $connected_services[ $service ] ) ) { + foreach ( $connected_services[ $service ] as $c ) { + $profile_link = $this->publicize->get_profile_link( $service, $c ); + + if ( false === $profile_link ) { + continue; + } + + $choices[ $profile_link ] = $this->publicize->get_display_name( $service, $c ); + } + } + } + + if ( 1 === count( $choices ) ) { + return array(); + } + + return $choices; + } + + /** + * Back-compat function for versions prior to 4.0. + */ + private function is_customize_preview() { + global $wp_customize; + return is_a( $wp_customize, 'WP_Customize_Manager' ) && $wp_customize->is_preview(); + } + } + +} // - end if ( ! class_exists( 'Social_Links' ) diff --git a/projects/plugins/jetpack/changelog/add-social-links-to-classic-theme-helper-package b/projects/plugins/jetpack/changelog/add-social-links-to-classic-theme-helper-package new file mode 100644 index 0000000000000..c7320ff0ac6ee --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-social-links-to-classic-theme-helper-package @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Social Links: Adding a function_exists check within the social-links.php file, to preventconflicts with package version. diff --git a/projects/plugins/jetpack/modules/theme-tools/social-links.php b/projects/plugins/jetpack/modules/theme-tools/social-links.php index 46ffd1583d088..75c1fb0e04a86 100644 --- a/projects/plugins/jetpack/modules/theme-tools/social-links.php +++ b/projects/plugins/jetpack/modules/theme-tools/social-links.php @@ -15,15 +15,17 @@ // phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files. -/** - * Init Social_Links if the theme declares support. - */ -function jetpack_theme_supports_social_links() { - if ( ! wp_is_block_theme() && current_theme_supports( 'social-links' ) && function_exists( 'publicize_init' ) ) { - new Social_Links(); +if ( ! function_exists( 'jetpack_theme_supports_social_links' ) ) { + /** + * Init Social_Links if the theme declares support. + */ + function jetpack_theme_supports_social_links() { + if ( ! wp_is_block_theme() && current_theme_supports( 'social-links' ) && function_exists( 'publicize_init' ) ) { + new Social_Links(); + } } + add_action( 'init', 'jetpack_theme_supports_social_links', 30 ); } -add_action( 'init', 'jetpack_theme_supports_social_links', 30 ); if ( ! class_exists( 'Social_Links' ) ) { From cce7cc817b6be70ec0d999858f03d571b90b3a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20M=C3=A1rk=20Juh=C3=A1sz?= <36671565+gmjuhasz@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:03:41 +0200 Subject: [PATCH 05/33] Revert "Social: Add feature flag management (#38375)" (#38667) This reverts commit 760d6c3b146b6b1c0c184ea99c9efd65cfbdeccf. --- .../changelog/add-feature-flag-management | 4 ---- .../src/components/form/index.tsx | 4 +--- .../src/social-store/reducer/index.js | 1 - .../src/social-store/selectors/index.js | 1 - .../src/social-store/types.ts | 1 - .../changelog/add-feature-flag-management | 4 ---- projects/packages/publicize/composer.json | 2 +- projects/packages/publicize/package.json | 2 +- .../publicize/src/class-publicize-base.php | 20 ------------------- .../class-settings.php | 20 ------------------- .../changelog/add-feature-flag-management | 4 ---- .../jetpack/class.jetpack-gutenberg.php | 2 -- projects/plugins/jetpack/composer.lock | 4 ++-- .../changelog/add-feature-flag-management | 4 ---- projects/plugins/social/composer.lock | 4 ++-- .../social/src/class-jetpack-social.php | 2 -- 16 files changed, 7 insertions(+), 72 deletions(-) delete mode 100644 projects/js-packages/publicize-components/changelog/add-feature-flag-management delete mode 100644 projects/packages/publicize/changelog/add-feature-flag-management delete mode 100644 projects/plugins/jetpack/changelog/add-feature-flag-management delete mode 100644 projects/plugins/social/changelog/add-feature-flag-management diff --git a/projects/js-packages/publicize-components/changelog/add-feature-flag-management b/projects/js-packages/publicize-components/changelog/add-feature-flag-management deleted file mode 100644 index 99b94073d5131..0000000000000 --- a/projects/js-packages/publicize-components/changelog/add-feature-flag-management +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Added feature flag management for Social diff --git a/projects/js-packages/publicize-components/src/components/form/index.tsx b/projects/js-packages/publicize-components/src/components/form/index.tsx index c5b6f60d2e212..ecaa3f8f2a29b 100644 --- a/projects/js-packages/publicize-components/src/components/form/index.tsx +++ b/projects/js-packages/publicize-components/src/components/form/index.tsx @@ -38,11 +38,10 @@ export default function PublicizeForm() { userConnectionUrl, } = usePublicizeConfig(); - const { useAdminUiV1, featureFlags } = useSelect( select => { + const { useAdminUiV1 } = useSelect( select => { const store = select( socialStore ); return { useAdminUiV1: store.useAdminUiV1(), - featureFlags: store.featureFlags(), }; }, [] ); @@ -63,7 +62,6 @@ export default function PublicizeForm() { - { featureFlags.useEditorPreview ?

New modal trigger goes here

: null } ) : null } diff --git a/projects/js-packages/publicize-components/src/social-store/reducer/index.js b/projects/js-packages/publicize-components/src/social-store/reducer/index.js index e330313125616..abd4003d17c65 100644 --- a/projects/js-packages/publicize-components/src/social-store/reducer/index.js +++ b/projects/js-packages/publicize-components/src/social-store/reducer/index.js @@ -14,7 +14,6 @@ const reducer = combineReducers( { hasPaidPlan: ( state = false ) => state, userConnectionUrl: ( state = '' ) => state, useAdminUiV1: ( state = false ) => state, - featureFlags: ( state = false ) => state, hasPaidFeatures: ( state = false ) => state, connectionRefreshPath: ( state = '' ) => state, } ); diff --git a/projects/js-packages/publicize-components/src/social-store/selectors/index.js b/projects/js-packages/publicize-components/src/social-store/selectors/index.js index ad6608112a2fe..6b196c1b4bc68 100644 --- a/projects/js-packages/publicize-components/src/social-store/selectors/index.js +++ b/projects/js-packages/publicize-components/src/social-store/selectors/index.js @@ -12,7 +12,6 @@ const selectors = { ...socialImageGeneratorSettingsSelectors, userConnectionUrl: state => state.userConnectionUrl, useAdminUiV1: state => state.useAdminUiV1, - featureFlags: state => state.featureFlags, hasPaidFeatures: state => state.hasPaidFeatures, connectionRefreshPath: state => state.connectionRefreshPath, }; diff --git a/projects/js-packages/publicize-components/src/social-store/types.ts b/projects/js-packages/publicize-components/src/social-store/types.ts index 79ecd3ae19b2d..88344bb91c09b 100644 --- a/projects/js-packages/publicize-components/src/social-store/types.ts +++ b/projects/js-packages/publicize-components/src/social-store/types.ts @@ -60,7 +60,6 @@ export type SocialStoreState = { // on Jetack Social admin page jetpackSettings?: JetpackSettings; useAdminUiV1?: boolean; - featureFlags?: Record< string, boolean >; }; export interface KeyringAdditionalUser { diff --git a/projects/packages/publicize/changelog/add-feature-flag-management b/projects/packages/publicize/changelog/add-feature-flag-management deleted file mode 100644 index 99b94073d5131..0000000000000 --- a/projects/packages/publicize/changelog/add-feature-flag-management +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Added feature flag management for Social diff --git a/projects/packages/publicize/composer.json b/projects/packages/publicize/composer.json index c8531e0d9ad1d..77d85828d891b 100644 --- a/projects/packages/publicize/composer.json +++ b/projects/packages/publicize/composer.json @@ -67,7 +67,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.48.x-dev" + "dev-trunk": "0.47.x-dev" } }, "config": { diff --git a/projects/packages/publicize/package.json b/projects/packages/publicize/package.json index 6427de0f6546c..384d69583cf41 100644 --- a/projects/packages/publicize/package.json +++ b/projects/packages/publicize/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize", - "version": "0.48.0-alpha", + "version": "0.47.4-alpha", "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/publicize/#readme", "bugs": { diff --git a/projects/packages/publicize/src/class-publicize-base.php b/projects/packages/publicize/src/class-publicize-base.php index ecd58ce0523db..e4195b728af71 100644 --- a/projects/packages/publicize/src/class-publicize-base.php +++ b/projects/packages/publicize/src/class-publicize-base.php @@ -289,26 +289,6 @@ public function use_admin_ui_v1(): bool { || $this->has_connections_management_feature(); } - /** - * Whether the site has the feature flag enabled. - * - * @param string $flag_name The feature flag to check. Will be prefixed with 'jetpack_social_has_' for the option. - * @param string $plan_name The plan name to check for for the Current_Plan check, without the social- prefix. - * @return bool - */ - public function has_feature_flag( $flag_name, $plan_name ): bool { - // If the option is set, use it. - if ( get_option( 'jetpack_social_has_' . $flag_name, false ) ) { - return true; - } - // If the constant is set, use it. - if ( defined( 'JETPACK_SOCIAL_HAS_' . strtoupper( $flag_name ) ) && constant( 'JETPACK_SOCIAL_HAS_' . strtoupper( $flag_name ) ) ) { - return true; - } - - return Current_Plan::supports( 'social-' . $plan_name ); - } - /** * Does the given user have a connection to the service on the given blog? * diff --git a/projects/packages/publicize/src/jetpack-social-settings/class-settings.php b/projects/packages/publicize/src/jetpack-social-settings/class-settings.php index ebf92075473ab..fb35561fa55b8 100644 --- a/projects/packages/publicize/src/jetpack-social-settings/class-settings.php +++ b/projects/packages/publicize/src/jetpack-social-settings/class-settings.php @@ -38,22 +38,6 @@ class Settings { 'enabled' => true, ); - /** - * Feature flags. Each item has 3 keys because of the naming conventions: - * - flag_name: The name of the feature flag for the option check. - * - plan_name: The name of the plan that enables the feature. Will be checked with Current_Plan. - * - variable_name: The name of the variable that will be used in the front-end. - * - * @var array - */ - const FEATURE_FLAGS = array( - array( - 'flag_name' => 'editor_preview', - 'plan_name' => 'editor-preview', - 'variable_name' => 'useEditorPreview', - ), - ); - /** * Migrate old options to the new settings. Previously SIG settings were stored in the * jetpack_social_image_generator_settings option. Now they are stored in the jetpack_social_settings @@ -215,10 +199,6 @@ public function get_initial_state() { $settings['is_publicize_enabled'] = true; $settings['hasPaidFeatures'] = $publicize->has_paid_features(); - - foreach ( self::FEATURE_FLAGS as $feature_flag ) { - $settings['featureFlags'][ $feature_flag['variable_name'] ] = $publicize->has_feature_flag( $feature_flag['flag_name'], $feature_flag['plan_name'] ); - } } else { $settings['connectionData'] = array( 'connections' => array(), diff --git a/projects/plugins/jetpack/changelog/add-feature-flag-management b/projects/plugins/jetpack/changelog/add-feature-flag-management deleted file mode 100644 index 82e1d667e93d8..0000000000000 --- a/projects/plugins/jetpack/changelog/add-feature-flag-management +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: other - -Social: Added feature flag to initial state diff --git a/projects/plugins/jetpack/class.jetpack-gutenberg.php b/projects/plugins/jetpack/class.jetpack-gutenberg.php index 496778d9c229f..6067fb8a629d1 100644 --- a/projects/plugins/jetpack/class.jetpack-gutenberg.php +++ b/projects/plugins/jetpack/class.jetpack-gutenberg.php @@ -771,8 +771,6 @@ public static function enqueue_block_editor_assets() { $initial_state['social']['connectionRefreshPath'] = $social_initial_state['connectionRefreshPath']; } - - $initial_state['social']['featureFlags'] = $social_initial_state['featureFlags']; } wp_localize_script( diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index 6fdc7c986ee24..1df2073d5d6c4 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -2223,7 +2223,7 @@ "dist": { "type": "path", "url": "../../packages/publicize", - "reference": "af5dd80bea134427f6b4d5bc8c0a95452afb17c1" + "reference": "6494d867225a31d465c41e86117becbe7a35c11e" }, "require": { "automattic/jetpack-assets": "@dev", @@ -2251,7 +2251,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.48.x-dev" + "dev-trunk": "0.47.x-dev" } }, "autoload": { diff --git a/projects/plugins/social/changelog/add-feature-flag-management b/projects/plugins/social/changelog/add-feature-flag-management deleted file mode 100644 index 99b94073d5131..0000000000000 --- a/projects/plugins/social/changelog/add-feature-flag-management +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Added feature flag management for Social diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index deaa2e7adc22f..f1b4256fc3297 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -1495,7 +1495,7 @@ "dist": { "type": "path", "url": "../../packages/publicize", - "reference": "af5dd80bea134427f6b4d5bc8c0a95452afb17c1" + "reference": "6494d867225a31d465c41e86117becbe7a35c11e" }, "require": { "automattic/jetpack-assets": "@dev", @@ -1523,7 +1523,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.48.x-dev" + "dev-trunk": "0.47.x-dev" } }, "autoload": { diff --git a/projects/plugins/social/src/class-jetpack-social.php b/projects/plugins/social/src/class-jetpack-social.php index 60c80835cdced..a347f7c8bf74a 100644 --- a/projects/plugins/social/src/class-jetpack-social.php +++ b/projects/plugins/social/src/class-jetpack-social.php @@ -352,8 +352,6 @@ class_exists( 'Jetpack' ) || $initial_state['connectionRefreshPath'] = $social_state['connectionRefreshPath']; } - $initial_state['featureFlags'] = $social_state['featureFlags']; - wp_localize_script( 'jetpack-social-editor', 'Jetpack_Editor_Initial_State', From 9767fc7963cd8973ab8a7493a6fefb6ad104716d Mon Sep 17 00:00:00 2001 From: Gabriel Demichelis Date: Thu, 1 Aug 2024 08:21:34 -0300 Subject: [PATCH 06/33] Masterbar: Fix icon overlap issue at smaller resolutions (#38551) Co-authored-by: Ashar Fuadi --- .../fix-masterbar-icons-overlap-in-mobile | 4 ++ .../wpcom-admin-bar/wpcom-admin-bar.scss | 68 ++++++++++++++++--- 2 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 projects/packages/jetpack-mu-wpcom/changelog/fix-masterbar-icons-overlap-in-mobile diff --git a/projects/packages/jetpack-mu-wpcom/changelog/fix-masterbar-icons-overlap-in-mobile b/projects/packages/jetpack-mu-wpcom/changelog/fix-masterbar-icons-overlap-in-mobile new file mode 100644 index 0000000000000..800805205f65a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/fix-masterbar-icons-overlap-in-mobile @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Masterbar: Fix icon overlap issue at smaller resolutions diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss index 354b07f96df31..34501dd1ce390 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-admin-bar/wpcom-admin-bar.scss @@ -14,6 +14,20 @@ display: none !important; } +/** + * Hides certain icons on smaller viewports. + */ +@media (max-width: 480px) { + #wpadminbar #wp-admin-bar-new-content { + display: none !important; + } +} +@media (max-width: 360px) { + #wpadminbar .wp-admin-bar-reader { + display: none !important; + } +} + /** * WP logo menu */ @@ -42,6 +56,15 @@ mask-size: cover; top: 7px; } + +} +#wpadminbar #wp-admin-bar-wpcom-logo:not(:first-child) { + // Site admin on mobile viewport (the first child is the hamburger menu) + @media (max-width: 480px) { + >.ab-item .ab-icon:before { + margin: 0 7px 0 1px; + } + } } /** @@ -73,6 +96,7 @@ justify-content: center; text-indent: 0; font-size: 0; + width: 52px; } & a.ab-item:before { @@ -82,6 +106,25 @@ mask-size: contain; } } + @media (max-width: 480px) { + & a.ab-item { + width: 46px; + } + & a.ab-item:before { + margin: 0 5px; + } + } +} + +#wpadminbar #wp-admin-bar-site-name>.ab-item:before { + /** + * Always show the House icon by the site name. + */ + content: "\f102" !important; + + @media (max-width: 480px) { + max-width: 46px; + } } /** @@ -110,6 +153,8 @@ } @media (max-width: 782px) { + height: 31px; + #wp-admin-bar-help-center { display: block !important; width: 52px !important; @@ -136,7 +181,7 @@ right: 0; width: 36px !important; height: 40px !important; - background-size: contain; + background-size: contain; } } } @@ -146,12 +191,19 @@ } } } - } -} + @media (max-width: 480px) { + #wp-admin-bar-notes { + width: 46px !important; + } + #wp-admin-bar-my-account { + .ab-item { + width: 46px; -/** - * Always show the House icon by the site name. - */ -#wpadminbar #wp-admin-bar-site-name>.ab-item:before { - content: "\f102" !important; + img { + right: 7px; + } + } + } + } + } } From 68a36152b2bff940c6d94a209627ffc5936f5ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20M=C3=A1rk=20Juh=C3=A1sz?= <36671565+gmjuhasz@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:20:59 +0200 Subject: [PATCH 07/33] Changelog and readme.txt edits. (#38670) --- projects/js-packages/components/CHANGELOG.md | 5 +++++ .../changelog/update-welcome-banner-connect | 4 ---- projects/js-packages/components/package.json | 2 +- .../js-packages/publicize-components/CHANGELOG.md | 13 +++++++++++++ .../changelog/fix-eslint-no-empty-catch-blocks | 5 ----- .../fix-mastodon-fediverse-creator-og-fatals | 4 ---- ...ove-social-auto-conversion-logic-from-data-store | 4 ---- ...date-social-Remove-the-auto-conversion-toggle-UI | 4 ---- .../changelog/update-social-auto-conversion-notice | 4 ---- ...improve-broken-connection-message-for-non-admins | 4 ---- .../js-packages/publicize-components/package.json | 2 +- projects/packages/connection/CHANGELOG.md | 5 +++++ .../changelog/add-evaluation-recommendations-logic | 4 ---- .../connection/src/class-package-version.php | 2 +- projects/packages/explat/CHANGELOG.md | 6 ++++++ ...chore-update-explat-client-react-helpers-version | 5 ----- projects/packages/explat/package.json | 2 +- projects/packages/explat/src/class-explat.php | 2 +- projects/packages/my-jetpack/CHANGELOG.md | 10 ++++++++++ .../my-jetpack/changelog/change-my-jetpack-ai-card | 4 ---- .../changelog/fix-eslint-no-empty-catch-blocks | 5 ----- .../fix-mastodon-fediverse-creator-og-fatals | 4 ---- .../my-jetpack/changelog/update-jsx-namespace-usage | 4 ---- .../changelog/update-welcome-banner-connect | 4 ---- .../my-jetpack/changelog/update-welcome-flow-copy | 5 ----- projects/packages/my-jetpack/package.json | 2 +- .../packages/my-jetpack/src/class-initializer.php | 2 +- projects/packages/publicize/CHANGELOG.md | 8 ++++++++ .../fix-mastodon-fediverse-creator-og-fatals | 4 ---- .../changelog/fix-social-threads-profile-url | 4 ---- projects/packages/publicize/package.json | 2 +- projects/packages/waf/CHANGELOG.md | 8 ++++++++ .../waf/changelog/add-protect-global-waf-stats | 4 ---- .../packages/waf/changelog/add-sync-waf-options | 5 ----- .../changelog/fix-protect-global-stats-type-check | 4 ---- projects/plugins/social/CHANGELOG.md | 10 ++++++++++ .../add-mj-protect-card-auto-firewall-status | 5 ----- .../changelog/add-mj-protect-card-last-scan-time | 5 ----- .../changelog/add-mj-protect-card-last-scan-time#2 | 5 ----- .../changelog/add-mj-protect-card-last-scan-time#3 | 5 ----- .../social/changelog/add-my-jetpack-aa-experiment | 5 ----- .../social/changelog/add-my-jetpack-aa-experiment#2 | 5 ----- .../social/changelog/add-protect-global-waf-stats | 5 ----- .../add-segmentation-to-jetpack-products-my-jetpack | 5 ----- .../plugins/social/changelog/add-sync-waf-options | 5 ----- .../social/changelog/change-my-jetpack-ai-card | 5 ----- .../changelog/fix-jsx-runtime-react-19-polyfill | 5 ----- .../plugins/social/changelog/fix-mu-wpcom-scssphp | 5 ----- .../social/changelog/fix-sync-hpos-checksum-support | 5 ----- projects/plugins/social/changelog/prerelease | 4 ---- ...ove-social-auto-conversion-logic-from-data-store | 4 ---- .../social/changelog/renovate-lock-file-maintenance | 4 ---- .../social/changelog/renovate-playwright-monorepo | 4 ---- .../social/changelog/update-minimum-wp-to-6.5 | 4 ---- .../update-remove-social-connection-feature-checks | 4 ---- ...update-remove-stats-from-connection-interstitial | 5 ----- ...date-social-Remove-the-auto-conversion-toggle-UI | 4 ---- .../changelog/update-sync-must-sync-data-settings | 5 ----- projects/plugins/social/composer.json | 2 +- projects/plugins/social/jetpack-social.php | 2 +- projects/plugins/social/readme.txt | 12 ++++++------ 61 files changed, 81 insertions(+), 204 deletions(-) delete mode 100644 projects/js-packages/components/changelog/update-welcome-banner-connect delete mode 100644 projects/js-packages/publicize-components/changelog/fix-eslint-no-empty-catch-blocks delete mode 100644 projects/js-packages/publicize-components/changelog/fix-mastodon-fediverse-creator-og-fatals delete mode 100644 projects/js-packages/publicize-components/changelog/remove-social-auto-conversion-logic-from-data-store delete mode 100644 projects/js-packages/publicize-components/changelog/update-social-Remove-the-auto-conversion-toggle-UI delete mode 100644 projects/js-packages/publicize-components/changelog/update-social-auto-conversion-notice delete mode 100644 projects/js-packages/publicize-components/changelog/update-social-improve-broken-connection-message-for-non-admins delete mode 100644 projects/packages/connection/changelog/add-evaluation-recommendations-logic delete mode 100644 projects/packages/explat/changelog/chore-update-explat-client-react-helpers-version delete mode 100644 projects/packages/my-jetpack/changelog/change-my-jetpack-ai-card delete mode 100644 projects/packages/my-jetpack/changelog/fix-eslint-no-empty-catch-blocks delete mode 100644 projects/packages/my-jetpack/changelog/fix-mastodon-fediverse-creator-og-fatals delete mode 100644 projects/packages/my-jetpack/changelog/update-jsx-namespace-usage delete mode 100644 projects/packages/my-jetpack/changelog/update-welcome-banner-connect delete mode 100644 projects/packages/my-jetpack/changelog/update-welcome-flow-copy delete mode 100644 projects/packages/publicize/changelog/fix-mastodon-fediverse-creator-og-fatals delete mode 100644 projects/packages/publicize/changelog/fix-social-threads-profile-url delete mode 100644 projects/packages/waf/changelog/add-protect-global-waf-stats delete mode 100644 projects/packages/waf/changelog/add-sync-waf-options delete mode 100644 projects/packages/waf/changelog/fix-protect-global-stats-type-check delete mode 100644 projects/plugins/social/changelog/add-mj-protect-card-auto-firewall-status delete mode 100644 projects/plugins/social/changelog/add-mj-protect-card-last-scan-time delete mode 100644 projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#2 delete mode 100644 projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#3 delete mode 100644 projects/plugins/social/changelog/add-my-jetpack-aa-experiment delete mode 100644 projects/plugins/social/changelog/add-my-jetpack-aa-experiment#2 delete mode 100644 projects/plugins/social/changelog/add-protect-global-waf-stats delete mode 100644 projects/plugins/social/changelog/add-segmentation-to-jetpack-products-my-jetpack delete mode 100644 projects/plugins/social/changelog/add-sync-waf-options delete mode 100644 projects/plugins/social/changelog/change-my-jetpack-ai-card delete mode 100644 projects/plugins/social/changelog/fix-jsx-runtime-react-19-polyfill delete mode 100644 projects/plugins/social/changelog/fix-mu-wpcom-scssphp delete mode 100644 projects/plugins/social/changelog/fix-sync-hpos-checksum-support delete mode 100644 projects/plugins/social/changelog/prerelease delete mode 100644 projects/plugins/social/changelog/remove-social-auto-conversion-logic-from-data-store delete mode 100644 projects/plugins/social/changelog/renovate-lock-file-maintenance delete mode 100644 projects/plugins/social/changelog/renovate-playwright-monorepo delete mode 100644 projects/plugins/social/changelog/update-minimum-wp-to-6.5 delete mode 100644 projects/plugins/social/changelog/update-remove-social-connection-feature-checks delete mode 100644 projects/plugins/social/changelog/update-remove-stats-from-connection-interstitial delete mode 100644 projects/plugins/social/changelog/update-social-Remove-the-auto-conversion-toggle-UI delete mode 100644 projects/plugins/social/changelog/update-sync-must-sync-data-settings diff --git a/projects/js-packages/components/CHANGELOG.md b/projects/js-packages/components/CHANGELOG.md index 33a97c8a4345c..a5e898450353e 100644 --- a/projects/js-packages/components/CHANGELOG.md +++ b/projects/js-packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## [0.55.4] - 2024-08-01 +### Added +- Update Welcome Banner and set async site-only connection [#38534] + ## [0.55.3] - 2024-07-30 ### Changed - React: Changing global JSX namespace to React.JSX [#38585] @@ -1106,6 +1110,7 @@ ### Changed - Update node version requirement to 14.16.1 +[0.55.4]: https://github.com/Automattic/jetpack-components/compare/0.55.3...0.55.4 [0.55.3]: https://github.com/Automattic/jetpack-components/compare/0.55.2...0.55.3 [0.55.2]: https://github.com/Automattic/jetpack-components/compare/0.55.1...0.55.2 [0.55.1]: https://github.com/Automattic/jetpack-components/compare/0.55.0...0.55.1 diff --git a/projects/js-packages/components/changelog/update-welcome-banner-connect b/projects/js-packages/components/changelog/update-welcome-banner-connect deleted file mode 100644 index 2f5c0598037fb..0000000000000 --- a/projects/js-packages/components/changelog/update-welcome-banner-connect +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Update Welcome Banner and set async site-only connection diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 58f4376d1134f..751da1227e0dc 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-components", - "version": "0.55.4-alpha", + "version": "0.55.4", "description": "Jetpack Components Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/publicize-components/CHANGELOG.md b/projects/js-packages/publicize-components/CHANGELOG.md index bdc1dfac7da57..f1b1fe47f591a 100644 --- a/projects/js-packages/publicize-components/CHANGELOG.md +++ b/projects/js-packages/publicize-components/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.58.0] - 2024-08-01 +### Changed +- Fixup versions [#38612] + +### Removed +- Removed the unused code for image auto-conversion from social store [#38609] +- Social | Removed the media auto-conversion UI [#38497] + +### Fixed +- Improved broken connection messaging for non-admins [#38639] +- Social | Fixed and improved media auto conversion notices [#38499] + ## [0.57.0] - 2024-07-29 ### Added - Added mentioning of Manual Sharing [#38411] @@ -795,6 +807,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated package dependencies. [#24470] +[0.58.0]: https://github.com/Automattic/jetpack-publicize-components/compare/v0.57.0...v0.58.0 [0.57.0]: https://github.com/Automattic/jetpack-publicize-components/compare/v0.56.2...v0.57.0 [0.56.2]: https://github.com/Automattic/jetpack-publicize-components/compare/v0.56.1...v0.56.2 [0.56.1]: https://github.com/Automattic/jetpack-publicize-components/compare/v0.56.0...v0.56.1 diff --git a/projects/js-packages/publicize-components/changelog/fix-eslint-no-empty-catch-blocks b/projects/js-packages/publicize-components/changelog/fix-eslint-no-empty-catch-blocks deleted file mode 100644 index fbb57fc4b2c1b..0000000000000 --- a/projects/js-packages/publicize-components/changelog/fix-eslint-no-empty-catch-blocks +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Added version bumps. - - diff --git a/projects/js-packages/publicize-components/changelog/fix-mastodon-fediverse-creator-og-fatals b/projects/js-packages/publicize-components/changelog/fix-mastodon-fediverse-creator-og-fatals deleted file mode 100644 index f379a9a899dab..0000000000000 --- a/projects/js-packages/publicize-components/changelog/fix-mastodon-fediverse-creator-og-fatals +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Fixup versions diff --git a/projects/js-packages/publicize-components/changelog/remove-social-auto-conversion-logic-from-data-store b/projects/js-packages/publicize-components/changelog/remove-social-auto-conversion-logic-from-data-store deleted file mode 100644 index 8f024ecc67e92..0000000000000 --- a/projects/js-packages/publicize-components/changelog/remove-social-auto-conversion-logic-from-data-store +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: removed - -Removed the unused code for image auto-conversion from social store diff --git a/projects/js-packages/publicize-components/changelog/update-social-Remove-the-auto-conversion-toggle-UI b/projects/js-packages/publicize-components/changelog/update-social-Remove-the-auto-conversion-toggle-UI deleted file mode 100644 index 4de390bcd2d2b..0000000000000 --- a/projects/js-packages/publicize-components/changelog/update-social-Remove-the-auto-conversion-toggle-UI +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: removed - -Social | Removed the media auto-conversion UI diff --git a/projects/js-packages/publicize-components/changelog/update-social-auto-conversion-notice b/projects/js-packages/publicize-components/changelog/update-social-auto-conversion-notice deleted file mode 100644 index 26e73d34f4167..0000000000000 --- a/projects/js-packages/publicize-components/changelog/update-social-auto-conversion-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Social | Fixed and improved media auto conversion notices diff --git a/projects/js-packages/publicize-components/changelog/update-social-improve-broken-connection-message-for-non-admins b/projects/js-packages/publicize-components/changelog/update-social-improve-broken-connection-message-for-non-admins deleted file mode 100644 index af35a00a9ee93..0000000000000 --- a/projects/js-packages/publicize-components/changelog/update-social-improve-broken-connection-message-for-non-admins +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Improved broken connection messaging for non-admins diff --git a/projects/js-packages/publicize-components/package.json b/projects/js-packages/publicize-components/package.json index 1897a5481b46d..6e057ee7beb0a 100644 --- a/projects/js-packages/publicize-components/package.json +++ b/projects/js-packages/publicize-components/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize-components", - "version": "0.58.0-alpha", + "version": "0.58.0", "description": "A library of JS components required by the Publicize editor plugin", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/publicize-components/#readme", "bugs": { diff --git a/projects/packages/connection/CHANGELOG.md b/projects/packages/connection/CHANGELOG.md index bc52731f97123..9eb380c3dda9a 100644 --- a/projects/packages/connection/CHANGELOG.md +++ b/projects/packages/connection/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.11.3] - 2024-08-01 +### Added +- Added support for 'recommendations_evaluation' Jetpack option" [#38534] + ## [2.11.2] - 2024-07-22 ### Fixed - Fixed textdomain on i18n messages imported from the IDC package. [#38412] @@ -1131,6 +1135,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Separate the connection library into its own package. +[2.11.3]: https://github.com/Automattic/jetpack-connection/compare/v2.11.2...v2.11.3 [2.11.2]: https://github.com/Automattic/jetpack-connection/compare/v2.11.1...v2.11.2 [2.11.1]: https://github.com/Automattic/jetpack-connection/compare/v2.11.0...v2.11.1 [2.11.0]: https://github.com/Automattic/jetpack-connection/compare/v2.10.2...v2.11.0 diff --git a/projects/packages/connection/changelog/add-evaluation-recommendations-logic b/projects/packages/connection/changelog/add-evaluation-recommendations-logic deleted file mode 100644 index a9e1d32b91432..0000000000000 --- a/projects/packages/connection/changelog/add-evaluation-recommendations-logic +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Added support for 'recommendations_evaluation' Jetpack option" diff --git a/projects/packages/connection/src/class-package-version.php b/projects/packages/connection/src/class-package-version.php index 7b8c21c1276ed..09ff7311fdea3 100644 --- a/projects/packages/connection/src/class-package-version.php +++ b/projects/packages/connection/src/class-package-version.php @@ -12,7 +12,7 @@ */ class Package_Version { - const PACKAGE_VERSION = '2.11.3-alpha'; + const PACKAGE_VERSION = '2.11.3'; const PACKAGE_SLUG = 'connection'; diff --git a/projects/packages/explat/CHANGELOG.md b/projects/packages/explat/CHANGELOG.md index ec40ad3d8fa44..0759c69ee4159 100644 --- a/projects/packages/explat/CHANGELOG.md +++ b/projects/packages/explat/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.1] - 2024-08-01 +### Changed +- Internal updates. + ## 0.1.0 - 2024-07-29 ### Added - Adds a new component to fetch experiments specifically for authenticated users [#37999] @@ -14,3 +18,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - ExPlat: add condition to prevent fetching the experiment assignment if there's not anon id (meaning that Tracks is likely disabled) [#38327] - Updated package dependencies. [#38132] + +[0.1.1]: https://github.com/Automattic/jetpack-explat/compare/v0.1.0...v0.1.1 diff --git a/projects/packages/explat/changelog/chore-update-explat-client-react-helpers-version b/projects/packages/explat/changelog/chore-update-explat-client-react-helpers-version deleted file mode 100644 index e903d6095bb18..0000000000000 --- a/projects/packages/explat/changelog/chore-update-explat-client-react-helpers-version +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: This change updates one of the dependencies to remove the noise added by the previous version. It does not affect the end user. - - diff --git a/projects/packages/explat/package.json b/projects/packages/explat/package.json index c2faeabd0f31c..8601d22a73dd5 100644 --- a/projects/packages/explat/package.json +++ b/projects/packages/explat/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-explat", - "version": "0.1.1-alpha", + "version": "0.1.1", "description": "A package for running A/B tests on the Experimentation Platform (ExPlat) in the plugin.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/explat/#readme", "bugs": { diff --git a/projects/packages/explat/src/class-explat.php b/projects/packages/explat/src/class-explat.php index 1b8c025ff3575..5c6db2b653c1d 100644 --- a/projects/packages/explat/src/class-explat.php +++ b/projects/packages/explat/src/class-explat.php @@ -20,7 +20,7 @@ class ExPlat { * * @var string */ - const PACKAGE_VERSION = '0.1.1-alpha'; + const PACKAGE_VERSION = '0.1.1'; /** * Initializer. diff --git a/projects/packages/my-jetpack/CHANGELOG.md b/projects/packages/my-jetpack/CHANGELOG.md index e8e2ccf731929..711c57072b06e 100644 --- a/projects/packages/my-jetpack/CHANGELOG.md +++ b/projects/packages/my-jetpack/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.31.0] - 2024-08-01 +### Added +- Update Welcome Banner and set async site-only connection [#38534] + +### Changed +- Fixup versions [#38612] +- My Jetpack: modify Jetpack AI product class and interstitial links [#38602] +- React: Changing global JSX namespace to React.JSX [#38585] + ## [4.30.0] - 2024-07-29 ### Added - Async card update after async site connection [#38549] @@ -1598,6 +1607,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Created package +[4.31.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.30.0...4.31.0 [4.30.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.29.0...4.30.0 [4.29.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.28.0...4.29.0 [4.28.0]: https://github.com/Automattic/jetpack-my-jetpack/compare/4.27.2...4.28.0 diff --git a/projects/packages/my-jetpack/changelog/change-my-jetpack-ai-card b/projects/packages/my-jetpack/changelog/change-my-jetpack-ai-card deleted file mode 100644 index b21ad58866eeb..0000000000000 --- a/projects/packages/my-jetpack/changelog/change-my-jetpack-ai-card +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -My Jetpack: modify Jetpack AI product class and interstitial links diff --git a/projects/packages/my-jetpack/changelog/fix-eslint-no-empty-catch-blocks b/projects/packages/my-jetpack/changelog/fix-eslint-no-empty-catch-blocks deleted file mode 100644 index fbb57fc4b2c1b..0000000000000 --- a/projects/packages/my-jetpack/changelog/fix-eslint-no-empty-catch-blocks +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Added version bumps. - - diff --git a/projects/packages/my-jetpack/changelog/fix-mastodon-fediverse-creator-og-fatals b/projects/packages/my-jetpack/changelog/fix-mastodon-fediverse-creator-og-fatals deleted file mode 100644 index f379a9a899dab..0000000000000 --- a/projects/packages/my-jetpack/changelog/fix-mastodon-fediverse-creator-og-fatals +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Fixup versions diff --git a/projects/packages/my-jetpack/changelog/update-jsx-namespace-usage b/projects/packages/my-jetpack/changelog/update-jsx-namespace-usage deleted file mode 100644 index b8ccf72e8ff1b..0000000000000 --- a/projects/packages/my-jetpack/changelog/update-jsx-namespace-usage +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -React: Changing global JSX namespace to React.JSX diff --git a/projects/packages/my-jetpack/changelog/update-welcome-banner-connect b/projects/packages/my-jetpack/changelog/update-welcome-banner-connect deleted file mode 100644 index 2f5c0598037fb..0000000000000 --- a/projects/packages/my-jetpack/changelog/update-welcome-banner-connect +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Update Welcome Banner and set async site-only connection diff --git a/projects/packages/my-jetpack/changelog/update-welcome-flow-copy b/projects/packages/my-jetpack/changelog/update-welcome-flow-copy deleted file mode 100644 index 31b24e2127661..0000000000000 --- a/projects/packages/my-jetpack/changelog/update-welcome-flow-copy +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: copy changes - - diff --git a/projects/packages/my-jetpack/package.json b/projects/packages/my-jetpack/package.json index 93d66ab9e69c5..97c2df61a15f5 100644 --- a/projects/packages/my-jetpack/package.json +++ b/projects/packages/my-jetpack/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-my-jetpack", - "version": "4.31.0-alpha", + "version": "4.31.0", "description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/my-jetpack/#readme", "bugs": { diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index e48e23f32e1be..3b69506686bfb 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -41,7 +41,7 @@ class Initializer { * * @var string */ - const PACKAGE_VERSION = '4.31.0-alpha'; + const PACKAGE_VERSION = '4.31.0'; /** * HTML container ID for the IDC screen on My Jetpack page. diff --git a/projects/packages/publicize/CHANGELOG.md b/projects/packages/publicize/CHANGELOG.md index f2c83afe441f4..0dbe2889c4922 100644 --- a/projects/packages/publicize/CHANGELOG.md +++ b/projects/packages/publicize/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.47.4] - 2024-08-01 +### Removed +- Removed Fediverse og filters to fix fatals [#38612] + +### Fixed +- Fixed Threads connections not having a profile_url [#38611] + ## [0.47.3] - 2024-07-15 ### Added - Mastodon: display a Fediverse Creator tag when the post author has connected their account to a Mastodon account. [#38198] @@ -625,6 +632,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Update package.json metadata. +[0.47.4]: https://github.com/Automattic/jetpack-publicize/compare/v0.47.3...v0.47.4 [0.47.3]: https://github.com/Automattic/jetpack-publicize/compare/v0.47.2...v0.47.3 [0.47.2]: https://github.com/Automattic/jetpack-publicize/compare/v0.47.1...v0.47.2 [0.47.1]: https://github.com/Automattic/jetpack-publicize/compare/v0.47.0...v0.47.1 diff --git a/projects/packages/publicize/changelog/fix-mastodon-fediverse-creator-og-fatals b/projects/packages/publicize/changelog/fix-mastodon-fediverse-creator-og-fatals deleted file mode 100644 index b96a56198a3c4..0000000000000 --- a/projects/packages/publicize/changelog/fix-mastodon-fediverse-creator-og-fatals +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: removed - -Removed Fediverse og filters to fix fatals diff --git a/projects/packages/publicize/changelog/fix-social-threads-profile-url b/projects/packages/publicize/changelog/fix-social-threads-profile-url deleted file mode 100644 index ef064f1ae1f19..0000000000000 --- a/projects/packages/publicize/changelog/fix-social-threads-profile-url +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fixed Threads connections not having a profile_url diff --git a/projects/packages/publicize/package.json b/projects/packages/publicize/package.json index 384d69583cf41..68470b3d90601 100644 --- a/projects/packages/publicize/package.json +++ b/projects/packages/publicize/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize", - "version": "0.47.4-alpha", + "version": "0.47.4", "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/publicize/#readme", "bugs": { diff --git a/projects/packages/waf/CHANGELOG.md b/projects/packages/waf/CHANGELOG.md index f40777cf0a88d..2755de0c5209f 100644 --- a/projects/packages/waf/CHANGELOG.md +++ b/projects/packages/waf/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.18.0] - 2024-08-01 +### Added +- Adds global statistics [#38388] + +### Fixed +- Fix global stats type check [#38634] + ## [0.17.0] - 2024-07-22 ### Added - Added the ability to toggle IP block and allow lists individually. [#38184] @@ -333,6 +340,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Core: do not ship .phpcs.dir.xml in production builds. +[0.18.0]: https://github.com/Automattic/jetpack-waf/compare/v0.17.0...v0.18.0 [0.17.0]: https://github.com/Automattic/jetpack-waf/compare/v0.16.10...v0.17.0 [0.16.10]: https://github.com/Automattic/jetpack-waf/compare/v0.16.9...v0.16.10 [0.16.9]: https://github.com/Automattic/jetpack-waf/compare/v0.16.8...v0.16.9 diff --git a/projects/packages/waf/changelog/add-protect-global-waf-stats b/projects/packages/waf/changelog/add-protect-global-waf-stats deleted file mode 100644 index 8016100ea4055..0000000000000 --- a/projects/packages/waf/changelog/add-protect-global-waf-stats +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Adds global statistics diff --git a/projects/packages/waf/changelog/add-sync-waf-options b/projects/packages/waf/changelog/add-sync-waf-options deleted file mode 100644 index 0facd8da053ce..0000000000000 --- a/projects/packages/waf/changelog/add-sync-waf-options +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Janitorial: improved type consistency in WAF settings API - - diff --git a/projects/packages/waf/changelog/fix-protect-global-stats-type-check b/projects/packages/waf/changelog/fix-protect-global-stats-type-check deleted file mode 100644 index e4c7352e60e2b..0000000000000 --- a/projects/packages/waf/changelog/fix-protect-global-stats-type-check +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fix global stats type check diff --git a/projects/plugins/social/CHANGELOG.md b/projects/plugins/social/CHANGELOG.md index c3303d195ccba..4c2c606bd5757 100644 --- a/projects/plugins/social/CHANGELOG.md +++ b/projects/plugins/social/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 5.0.0 - 2024-08-01 +### Changed +- Social: Removed unnecessary feature checks for social connections [#38216] +- Updated package dependencies. [#38228] [#38235] [#38464] + +### Removed +- General: update WordPress version requirements to WordPress 6.5. [#38382] +- Removed the unused code for image auto-conversion from social store [#38609] +- Social | Removed the media auto-conversion UI [#38497] + ## 4.5.2 - 2024-07-03 ### Changed - General: indicate compatibility with the upcoming version of WordPress - 6.6. [#37962] diff --git a/projects/plugins/social/changelog/add-mj-protect-card-auto-firewall-status b/projects/plugins/social/changelog/add-mj-protect-card-auto-firewall-status deleted file mode 100644 index 66603f18e7ca1..0000000000000 --- a/projects/plugins/social/changelog/add-mj-protect-card-auto-firewall-status +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: update lock file - - diff --git a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#2 b/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#2 deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#2 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#3 b/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#3 deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time#3 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-my-jetpack-aa-experiment b/projects/plugins/social/changelog/add-my-jetpack-aa-experiment deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-my-jetpack-aa-experiment +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-my-jetpack-aa-experiment#2 b/projects/plugins/social/changelog/add-my-jetpack-aa-experiment#2 deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-my-jetpack-aa-experiment#2 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-protect-global-waf-stats b/projects/plugins/social/changelog/add-protect-global-waf-stats deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-protect-global-waf-stats +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-segmentation-to-jetpack-products-my-jetpack b/projects/plugins/social/changelog/add-segmentation-to-jetpack-products-my-jetpack deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-segmentation-to-jetpack-products-my-jetpack +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/add-sync-waf-options b/projects/plugins/social/changelog/add-sync-waf-options deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/add-sync-waf-options +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/change-my-jetpack-ai-card b/projects/plugins/social/changelog/change-my-jetpack-ai-card deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/change-my-jetpack-ai-card +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/fix-jsx-runtime-react-19-polyfill b/projects/plugins/social/changelog/fix-jsx-runtime-react-19-polyfill deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/fix-jsx-runtime-react-19-polyfill +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/fix-mu-wpcom-scssphp b/projects/plugins/social/changelog/fix-mu-wpcom-scssphp deleted file mode 100644 index 427aa2192f0dc..0000000000000 --- a/projects/plugins/social/changelog/fix-mu-wpcom-scssphp +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: added -Comment: Add production-include for wikimedia/aho-corasick, now required via my-jetpack in #38332. - - diff --git a/projects/plugins/social/changelog/fix-sync-hpos-checksum-support b/projects/plugins/social/changelog/fix-sync-hpos-checksum-support deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/fix-sync-hpos-checksum-support +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/prerelease b/projects/plugins/social/changelog/prerelease deleted file mode 100644 index 7d3f9cba4bc0e..0000000000000 --- a/projects/plugins/social/changelog/prerelease +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Updated package dependencies. diff --git a/projects/plugins/social/changelog/remove-social-auto-conversion-logic-from-data-store b/projects/plugins/social/changelog/remove-social-auto-conversion-logic-from-data-store deleted file mode 100644 index 8f024ecc67e92..0000000000000 --- a/projects/plugins/social/changelog/remove-social-auto-conversion-logic-from-data-store +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: removed - -Removed the unused code for image auto-conversion from social store diff --git a/projects/plugins/social/changelog/renovate-lock-file-maintenance b/projects/plugins/social/changelog/renovate-lock-file-maintenance deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/social/changelog/renovate-lock-file-maintenance +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/social/changelog/renovate-playwright-monorepo b/projects/plugins/social/changelog/renovate-playwright-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/social/changelog/renovate-playwright-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/social/changelog/update-minimum-wp-to-6.5 b/projects/plugins/social/changelog/update-minimum-wp-to-6.5 deleted file mode 100644 index 324db53ba465c..0000000000000 --- a/projects/plugins/social/changelog/update-minimum-wp-to-6.5 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: major -Type: removed - -General: update WordPress version requirements to WordPress 6.5. diff --git a/projects/plugins/social/changelog/update-remove-social-connection-feature-checks b/projects/plugins/social/changelog/update-remove-social-connection-feature-checks deleted file mode 100644 index 52e5c87b3b699..0000000000000 --- a/projects/plugins/social/changelog/update-remove-social-connection-feature-checks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Social: Removed unnecessary feature checks for social connections diff --git a/projects/plugins/social/changelog/update-remove-stats-from-connection-interstitial b/projects/plugins/social/changelog/update-remove-stats-from-connection-interstitial deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/update-remove-stats-from-connection-interstitial +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/changelog/update-social-Remove-the-auto-conversion-toggle-UI b/projects/plugins/social/changelog/update-social-Remove-the-auto-conversion-toggle-UI deleted file mode 100644 index 4de390bcd2d2b..0000000000000 --- a/projects/plugins/social/changelog/update-social-Remove-the-auto-conversion-toggle-UI +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: removed - -Social | Removed the media auto-conversion UI diff --git a/projects/plugins/social/changelog/update-sync-must-sync-data-settings b/projects/plugins/social/changelog/update-sync-must-sync-data-settings deleted file mode 100644 index 9aa70e3ec1f75..0000000000000 --- a/projects/plugins/social/changelog/update-sync-must-sync-data-settings +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Updated composer.lock. - - diff --git a/projects/plugins/social/composer.json b/projects/plugins/social/composer.json index a6fede8af54da..169ed193d0cdf 100644 --- a/projects/plugins/social/composer.json +++ b/projects/plugins/social/composer.json @@ -84,6 +84,6 @@ "automattic/jetpack-autoloader": true, "automattic/jetpack-composer-plugin": true }, - "autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_socialⓥ5_0_0_alpha" + "autoloader-suffix": "c4802e05bbcf59fd3b6350e8d3e5482c_socialⓥ5_0_0" } } diff --git a/projects/plugins/social/jetpack-social.php b/projects/plugins/social/jetpack-social.php index 47bfb4fe1d3f5..9c469b8838a63 100644 --- a/projects/plugins/social/jetpack-social.php +++ b/projects/plugins/social/jetpack-social.php @@ -4,7 +4,7 @@ * Plugin Name: Jetpack Social * Plugin URI: https://wordpress.org/plugins/jetpack-social * Description: Share your site’s posts on several social media networks automatically when you publish a new post. - * Version: 5.0.0-alpha + * Version: 5.0.0 * Author: Automattic - Jetpack Social team * Author URI: https://jetpack.com/social/ * License: GPLv2 or later diff --git a/projects/plugins/social/readme.txt b/projects/plugins/social/readme.txt index a7d2c02f520cd..19f2fda149801 100644 --- a/projects/plugins/social/readme.txt +++ b/projects/plugins/social/readme.txt @@ -102,15 +102,15 @@ The easiest way is to use the Custom Message option in the publishing options bo 6. Managing Social media accounts in the post editor == Changelog == -### 4.5.2 - 2024-07-03 +### 5.0.0 - 2024-08-01 #### Changed -- General: indicate compatibility with the upcoming version of WordPress - 6.6. +- Social: Removed unnecessary feature checks for social connections - Updated package dependencies. -#### Fixed -- Fixed E2E tests navigating to block editor -- Fixed the admin page pricing table not shown -- Social: Fixed broken connections reconnect link to point it to new connections UI +#### Removed +- General: update WordPress version requirements to WordPress 6.5. +- Removed the unused code for image auto-conversion from social store +- Social | Removed the media auto-conversion UI == Upgrade Notice == From c47c8634b28e5499d090944a67d0351ab3bcccc2 Mon Sep 17 00:00:00 2001 From: Caroline Moore Date: Thu, 1 Aug 2024 09:47:25 -0400 Subject: [PATCH 08/33] AIOWP Helper: Add `target_blog_id` eventprop to Tracks events (#38615) * Add target blog id eventprop to aiowp migration events * changelog * Fixup project versions * Use jetpack option for blog id * Switch to helper function * Fixup project versions * Ensure we're using the target blog id with closures --- .../plugins/wpcomsh/changelog/add-aiowp-target-blog-id | 4 ++++ projects/plugins/wpcomsh/composer.json | 2 +- projects/plugins/wpcomsh/package.json | 2 +- .../wpcom-migration-helpers/site-migration-helpers.php | 8 ++++++-- projects/plugins/wpcomsh/wpcomsh.php | 4 ++-- 5 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 projects/plugins/wpcomsh/changelog/add-aiowp-target-blog-id diff --git a/projects/plugins/wpcomsh/changelog/add-aiowp-target-blog-id b/projects/plugins/wpcomsh/changelog/add-aiowp-target-blog-id new file mode 100644 index 0000000000000..3def85e081bad --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/add-aiowp-target-blog-id @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Add target_blog_id prop to AIOWP tracks events diff --git a/projects/plugins/wpcomsh/composer.json b/projects/plugins/wpcomsh/composer.json index b031c12a2c918..1ebd0a1bf3546 100644 --- a/projects/plugins/wpcomsh/composer.json +++ b/projects/plugins/wpcomsh/composer.json @@ -128,7 +128,7 @@ "composer/installers": true, "roots/wordpress-core-installer": true }, - "autoloader-suffix": "26841ac2064774301cbe06d174833bfc_wpcomshⓥ5_1_3_alpha" + "autoloader-suffix": "26841ac2064774301cbe06d174833bfc_wpcomshⓥ5_2_0_alpha" }, "extra": { "mirror-repo": "Automattic/wpcom-site-helper", diff --git a/projects/plugins/wpcomsh/package.json b/projects/plugins/wpcomsh/package.json index ea13d416f7d21..4c488eeeac439 100644 --- a/projects/plugins/wpcomsh/package.json +++ b/projects/plugins/wpcomsh/package.json @@ -3,7 +3,7 @@ "name": "@automattic/jetpack-wpcomsh", "description": "A helper for connecting WordPress.com sites to external host infrastructure.", "homepage": "https://jetpack.com", - "version": "5.1.3-alpha", + "version": "5.2.0-alpha", "bugs": { "url": "https://github.com/Automattic/jetpack/labels/[Plugin] Wpcomsh" }, diff --git a/projects/plugins/wpcomsh/wpcom-migration-helpers/site-migration-helpers.php b/projects/plugins/wpcomsh/wpcom-migration-helpers/site-migration-helpers.php index 8471424e542db..4ab07dc95c126 100644 --- a/projects/plugins/wpcomsh/wpcom-migration-helpers/site-migration-helpers.php +++ b/projects/plugins/wpcomsh/wpcom-migration-helpers/site-migration-helpers.php @@ -67,14 +67,17 @@ function aiowp_migration_logging_helper() { return; } + $target_blog_id = _wpcom_get_current_blog_id(); + // Filter that gets called when import starts add_filter( 'ai1wm_import', - function ( $params = array() ) { + function ( $params = array() ) use ( $target_blog_id ) { wpcomsh_record_tracks_event( 'wpcom_site_migration_start', array( 'migration_tool' => 'aiowp', + 'target_blog_id' => $target_blog_id, ) ); return $params; @@ -85,11 +88,12 @@ function ( $params = array() ) { // Filter that gets called when import finishes or is cancelled by the user add_filter( 'ai1wm_import', - function ( $params = array() ) { + function ( $params = array() ) use ( $target_blog_id ) { wpcomsh_record_tracks_event( 'wpcom_site_migration_done', array( 'migration_tool' => 'aiowp', + 'target_blog_id' => $target_blog_id, ) ); return $params; diff --git a/projects/plugins/wpcomsh/wpcomsh.php b/projects/plugins/wpcomsh/wpcomsh.php index d43e47618f670..35f773d19d55d 100644 --- a/projects/plugins/wpcomsh/wpcomsh.php +++ b/projects/plugins/wpcomsh/wpcomsh.php @@ -2,14 +2,14 @@ /** * Plugin Name: WordPress.com Site Helper * Description: A helper for connecting WordPress.com sites to external host infrastructure. - * Version: 5.1.3-alpha + * Version: 5.2.0-alpha * Author: Automattic * Author URI: http://automattic.com/ * * @package wpcomsh */ -define( 'WPCOMSH_VERSION', '5.1.3-alpha' ); +define( 'WPCOMSH_VERSION', '5.2.0-alpha' ); // If true, Typekit fonts will be available in addition to Google fonts add_filter( 'jetpack_fonts_enable_typekit', '__return_true' ); From 20cd3b3bb5dd6480092e172db1a1fd3c004c9c15 Mon Sep 17 00:00:00 2001 From: Karen Attfield Date: Thu, 1 Aug 2024 15:41:00 +0100 Subject: [PATCH 09/33] Podcast Player Block: Ensure React 19 compatibility by replacing unmountComponentAtNode (#38619) --- ...update-react-19-compat-unmountComponentAtNode | 4 ++++ .../extensions/blocks/podcast-player/view.js | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-react-19-compat-unmountComponentAtNode diff --git a/projects/plugins/jetpack/changelog/update-react-19-compat-unmountComponentAtNode b/projects/plugins/jetpack/changelog/update-react-19-compat-unmountComponentAtNode new file mode 100644 index 0000000000000..70c3459ff55f2 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-react-19-compat-unmountComponentAtNode @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Podcast Player Block: Update component unmount method to be compatible with React 19. diff --git a/projects/plugins/jetpack/extensions/blocks/podcast-player/view.js b/projects/plugins/jetpack/extensions/blocks/podcast-player/view.js index f66e28e186605..8ed494c37ee7b 100644 --- a/projects/plugins/jetpack/extensions/blocks/podcast-player/view.js +++ b/projects/plugins/jetpack/extensions/blocks/podcast-player/view.js @@ -1,4 +1,4 @@ -import { render, createElement, unmountComponentAtNode } from '@wordpress/element'; +import { createElement, createRoot } from '@wordpress/element'; import debugFactory from 'debug'; import '../../store/media-source'; import PodcastPlayer from './components/podcast-player'; @@ -32,6 +32,7 @@ const initializeBlock = function ( id ) { if ( ! block ) { return; } + const root = createRoot( block ); if ( block.getAttribute( 'data-jetpack-block-initialized' ) === 'true' ) { return; @@ -57,7 +58,7 @@ const initializeBlock = function ( id ) { const fallbackHTML = block.innerHTML; // Abort if not tracks found. - if ( ! data || ! data.tracks.length ) { + if ( ! data?.tracks?.length ) { debug( 'no tracks found' ); downgradeBlockToStatic( block ); return; @@ -69,14 +70,17 @@ const initializeBlock = function ( id ) { ...data, onError: function () { // Unmount React version and bring back the static HTML. - unmountComponentAtNode( block ); - block.innerHTML = fallbackHTML; - downgradeBlockToStatic( block ); + requestAnimationFrame( () => { + root.unmount(); + block.innerHTML = fallbackHTML; + downgradeBlockToStatic( block ); + } ); }, } ); // Render and save instance to the list of active ones. - playerInstances[ id ] = render( component, block ); + root.render( component ); + playerInstances[ id ] = root; } catch ( err ) { debug( 'unable to render', err ); downgradeBlockToStatic( block ); From e7966203627f2b8f83ed67cb8cd702da0bd08167 Mon Sep 17 00:00:00 2001 From: Luiz Kowalski Date: Thu, 1 Aug 2024 12:28:06 -0300 Subject: [PATCH 10/33] AI Logo Generator: open the logo block extension to 10% of user base (#38646) * Enable AI Logo Generator extension for beta sites and site IDs ending in 0, 10% of site base * Changelog --- ...update-ai-logo-generator-release-to-simple-sites | 4 ++++ .../extended-blocks/core-site-logo/index.tsx | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 projects/plugins/jetpack/changelog/update-ai-logo-generator-release-to-simple-sites diff --git a/projects/plugins/jetpack/changelog/update-ai-logo-generator-release-to-simple-sites b/projects/plugins/jetpack/changelog/update-ai-logo-generator-release-to-simple-sites new file mode 100644 index 0000000000000..1886a3471c50c --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-ai-logo-generator-release-to-simple-sites @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +AI Logo Generator: Release site logo extension to 10% of sites. diff --git a/projects/plugins/jetpack/extensions/extended-blocks/core-site-logo/index.tsx b/projects/plugins/jetpack/extensions/extended-blocks/core-site-logo/index.tsx index e3bb7b5f4e72b..4e55a89da77eb 100644 --- a/projects/plugins/jetpack/extensions/extended-blocks/core-site-logo/index.tsx +++ b/projects/plugins/jetpack/extensions/extended-blocks/core-site-logo/index.tsx @@ -143,6 +143,17 @@ const siteLogoEditWithAiComponents = createHigherOrderComponent( BlockEdit => { }; }, 'SiteLogoEditWithAiComponents' ); +/** + * Function to check if the feature is available depending on the site ID. + * + * @returns {boolean} True if the feature is available. + */ +function isFeatureAvailable() { + const siteId = parseInt( window?.Jetpack_Editor_Initial_State?.wpcomBlogId ); + + return getFeatureAvailability( SITE_LOGO_BLOCK_AI_EXTENSION ) || siteId % 10 === 0; +} + /** * Function to check if the block can be extended. * @@ -162,7 +173,7 @@ function canExtendBlock( name: string ): boolean { } // Disable if the feature is not available. - if ( ! getFeatureAvailability( SITE_LOGO_BLOCK_AI_EXTENSION ) ) { + if ( ! isFeatureAvailable() ) { return false; } From 74f29c30dfa99e3b1abb0f10c84beb7c89dac17a Mon Sep 17 00:00:00 2001 From: arthur791004 Date: Thu, 1 Aug 2024 23:37:19 +0800 Subject: [PATCH 11/33] Revert "MU WPCOM: Port the wpcom block editor nux feature from the ETK (#38446)" (#38673) This reverts commit 1723da10dda28538032429ac8f314ce56dbd867d. --- pnpm-lock.yaml | 47 -- .../changelog/wpcom-block-editor-nux | 4 - .../packages/jetpack-mu-wpcom/package.json | 7 +- .../src/class-jetpack-mu-wpcom.php | 1 - .../components/keyboard-navigation.tsx | 61 --- .../tour-kit/components/tour-kit-frame.tsx | 286 ----------- .../components/tour-kit-minimized.tsx | 30 -- .../tour-kit/components/tour-kit-overlay.tsx | 20 - .../tour-kit-spotlight-interactivity.tsx | 38 -- .../components/tour-kit-spotlight.tsx | 122 ----- .../tour-kit/components/tour-kit-step.tsx | 52 -- .../common/tour-kit/components/tour-kit.tsx | 40 -- .../src/common/tour-kit/constants.ts | 3 - ...-seen-seller-celebration-modal-context.tsx | 68 --- ...s-seen-video-celebration-modal-context.tsx | 68 --- .../src/common/tour-kit/contexts/index.ts | 4 - ...how-first-post-published-modal-context.jsx | 31 -- .../tour-kit/contexts/tour-kit-context.tsx | 16 - .../src/common/tour-kit/error-boundary.tsx | 33 -- .../src/common/tour-kit/hooks/index.ts | 9 - .../tour-kit/hooks/use-focus-handler.ts | 60 --- .../common/tour-kit/hooks/use-focus-trap.ts | 56 --- .../use-has-selected-payment-block-once.js | 62 --- .../tour-kit/hooks/use-keydown-handler.ts | 52 -- ...se-should-show-seller-celebration-modal.js | 67 --- ...use-should-show-video-celebration-modal.ts | 51 -- .../common/tour-kit/hooks/use-site-intent.js | 14 - .../common/tour-kit/hooks/use-site-plan.js | 24 - .../tour-kit/hooks/use-step-tracking.ts | 26 - .../src/common/tour-kit/index.ts | 6 - .../src/common/tour-kit/styles.scss | 91 ---- .../src/common/tour-kit/types.ts | 164 ------- .../src/common/tour-kit/utils/index.ts | 16 - .../tour-kit/utils/live-resize-modifier.tsx | 111 ----- .../components/wpcom-tour-kit-minimized.tsx | 50 -- .../wpcom-tour-kit-pagination-control.scss | 44 -- .../wpcom-tour-kit-pagination-control.tsx | 49 -- .../components/wpcom-tour-kit-rating.tsx | 73 --- .../wpcom-tour-kit-step-card-navigation.tsx | 62 --- ...om-tour-kit-step-card-overlay-controls.tsx | 41 -- .../components/wpcom-tour-kit-step-card.tsx | 109 ----- .../wpcom/components/wpcom-tour-kit-step.tsx | 28 -- .../wpcom/components/wpcom-tour-kit.tsx | 29 -- .../wpcom/hooks/use-prefetch-tour-assets.tsx | 16 - .../variants/wpcom/icons/maximize.tsx | 13 - .../variants/wpcom/icons/minimize.tsx | 14 - .../variants/wpcom/icons/thumbs_down.tsx | 14 - .../variants/wpcom/icons/thumbs_up.tsx | 14 - .../common/tour-kit/variants/wpcom/index.ts | 2 - .../tour-kit/variants/wpcom/styles.scss | 232 --------- .../features/wpcom-block-editor-nux/README.md | 7 - ...-first-post-published-modal-controller.php | 94 ---- ...com-block-editor-nux-status-controller.php | 126 ----- ...or-seller-celebration-modal-controller.php | 101 ---- ...-block-editor-sharing-modal-controller.php | 89 ---- ...tor-video-celebration-modal-controller.php | 101 ---- .../class-wpcom-block-editor-nux.php | 78 --- .../features/wpcom-block-editor-nux/index.js | 6 - .../src/block-editor-nux.js | 137 ------ .../src/blogging-prompts-modal/icons.js | 13 - .../src/blogging-prompts-modal/index.js | 106 ----- .../src/blogging-prompts-modal/style.scss | 53 --- .../src/disable-core-nux.js | 43 -- .../draft-post-modal/images/draft-post.svg | 20 - .../src/draft-post-modal/index.js | 50 -- .../src/draft-post-modal/style.scss | 17 - .../images/post-published.svg | 15 - .../src/first-post-published-modal/index.tsx | 117 ----- .../src/first-post-published-modal/style.scss | 23 - .../src/nux-modal/index.tsx | 58 --- .../src/nux-modal/style.scss | 96 ---- .../src/purchase-notice/index.jsx | 34 -- .../src/purchase-notice/style.scss | 15 - .../images/product-published.svg | 37 -- .../src/seller-celebration-modal/index.jsx | 139 ------ .../src/seller-celebration-modal/style.scss | 27 -- .../src/sharing-modal/clipboard-button.tsx | 47 -- .../src/sharing-modal/form-checkbox.tsx | 19 - .../src/sharing-modal/form-label.tsx | 37 -- .../src/sharing-modal/images/illo-share.svg | 24 - .../src/sharing-modal/index.tsx | 291 ------------ .../src/sharing-modal/inline-social-logo.tsx | 48 -- .../inline-social-logos-sprite.tsx | 286 ----------- .../src/sharing-modal/style.scss | 173 ------- .../src/sharing-modal/suggested-tags.tsx | 132 ------ .../src/sharing-modal/use-add-tags-to-post.ts | 31 -- .../use-sharing-modal-dismissed.ts | 24 - .../wpcom-block-editor-nux/src/store.js | 150 ------ .../src/test/store.test.js | 127 ----- .../src/video-celebration-modal/index.jsx | 113 ----- .../src/video-celebration-modal/style.scss | 37 -- .../video-celebration-modal/video-success.svg | 56 --- .../src/welcome-modal/images/block-picker.svg | 39 -- .../src/welcome-modal/images/editor.svg | 21 - .../src/welcome-modal/images/preview.svg | 16 - .../src/welcome-modal/images/private.svg | 26 - .../src/welcome-modal/style.scss | 208 -------- .../src/welcome-modal/wpcom-nux.js | 153 ------ .../src/welcome-tour/get-editor-type.ts | 41 -- .../src/welcome-tour/style-tour.scss | 51 -- .../src/welcome-tour/test/tour-steps.test.ts | 110 ----- .../src/welcome-tour/tour-launch.jsx | 243 ---------- .../src/welcome-tour/use-tour-steps.tsx | 445 ------------------ .../jetpack-mu-wpcom/webpack.config.js | 1 - 104 files changed, 1 insertion(+), 7080 deletions(-) delete mode 100644 projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/form-checkbox.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/form-label.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/images/illo-share.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/index.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx delete mode 100644 projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9591209e2002f..5d163c92cb441 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2194,9 +2194,6 @@ importers: projects/packages/jetpack-mu-wpcom: dependencies: - '@automattic/calypso-color-schemes': - specifier: 3.1.3 - version: 3.1.3 '@automattic/color-studio': specifier: 2.6.0 version: 2.6.0 @@ -2215,9 +2212,6 @@ importers: '@automattic/typography': specifier: 1.0.0 version: 1.0.0 - '@popperjs/core': - specifier: ^2.11.8 - version: 2.11.8 '@preact/signals': specifier: ^1.2.2 version: 1.3.0(preact@10.22.1) @@ -2260,24 +2254,15 @@ importers: '@wordpress/plugins': specifier: 7.2.0 version: 7.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/private-apis': - specifier: ^1.2.0 - version: 1.2.0 '@wordpress/url': specifier: 4.2.0 version: 4.2.0 clsx: specifier: 2.1.1 version: 2.1.1 - debug: - specifier: 4.3.4 - version: 4.3.4 preact: specifier: ^10.13.1 version: 10.22.1 - react-popper: - specifier: ^2.3.0 - version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) redux: specifier: ^4.2.1 version: 4.2.1 @@ -6384,9 +6369,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@preact/signals-core@1.7.0': resolution: {integrity: sha512-bEZLgmJGSBVP5PUPDowhPW3bVdMmp9Tr5OEl+SQK+8Tv9T7UsIfyN905cfkmmeqw8z4xp8T6zrl4M1uj9+HAfg==} @@ -12869,9 +12851,6 @@ packages: react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -12890,13 +12869,6 @@ packages: peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-popper@2.3.0: - resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} - peerDependencies: - '@popperjs/core': ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - react-redux@7.2.8: resolution: {integrity: sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==} peerDependencies: @@ -14407,9 +14379,6 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - warning@4.0.3: - resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} - watchpack@2.4.1: resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} engines: {node: '>=10.13.0'} @@ -16514,8 +16483,6 @@ snapshots: dependencies: playwright: 1.45.1 - '@popperjs/core@2.11.8': {} - '@preact/signals-core@1.7.0': {} '@preact/signals@1.3.0(preact@10.22.1)': @@ -26117,8 +26084,6 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-is: 18.1.0 - react-fast-compare@3.2.2: {} - react-is@16.13.1: {} react-is@17.0.2: {} @@ -26132,14 +26097,6 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 - react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@popperjs/core': 2.11.8 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-fast-compare: 3.2.2 - warning: 4.0.3 - react-redux@7.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.24.7 @@ -27854,10 +27811,6 @@ snapshots: dependencies: makeerror: 1.0.12 - warning@4.0.3: - dependencies: - loose-envify: 1.4.0 - watchpack@2.4.1: dependencies: glob-to-regexp: 0.4.1 diff --git a/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux b/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux deleted file mode 100644 index 44b012c3bcd33..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/changelog/wpcom-block-editor-nux +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Added wpcom-block-editor-nux feature from calypso's ETK package. diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index e4e684ca3ad91..40a68e6784eee 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -48,14 +48,12 @@ "webpack-cli": "4.9.1" }, "dependencies": { - "@automattic/calypso-color-schemes": "3.1.3", "@automattic/color-studio": "2.6.0", "@automattic/i18n-utils": "1.2.3", "@automattic/jetpack-base-styles": "workspace:*", "@automattic/jetpack-shared-extension-utils": "workspace:*", "@automattic/typography": "1.0.0", "@automattic/page-pattern-modal": "1.1.5", - "@popperjs/core": "^2.11.8", "@preact/signals": "^1.2.2", "@sentry/browser": "7.80.1", "@tanstack/react-query": "^5.15.5", @@ -68,16 +66,13 @@ "@wordpress/element": "6.2.0", "@wordpress/hooks": "4.2.0", "@wordpress/i18n": "5.2.0", - "@wordpress/icons": "10.2.0", "@wordpress/plugins": "7.2.0", - "@wordpress/private-apis": "^1.2.0", "@wordpress/url": "4.2.0", + "@wordpress/icons": "10.2.0", "clsx": "2.1.1", - "debug": "4.3.4", "preact": "^10.13.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-popper": "^2.3.0", "redux": "^4.2.1", "redux-saga": "^1.3.0", "swiper": "^8.4.5", 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 a2749e1cca762..aa7d9f2f259da 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 @@ -160,7 +160,6 @@ public static function load_etk_features() { require_once __DIR__ . '/features/paragraph-block-placeholder/paragraph-block-placeholder.php'; require_once __DIR__ . '/features/tags-education/tags-education.php'; require_once __DIR__ . '/features/wpcom-block-description-links/wpcom-block-description-links.php'; - require_once __DIR__ . '/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php'; require_once __DIR__ . '/features/wpcom-blocks/a8c-posts-list/a8c-posts-list.php'; require_once __DIR__ . '/features/wpcom-blocks/event-countdown/event-countdown.php'; require_once __DIR__ . '/features/wpcom-blocks/timeline/timeline.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx deleted file mode 100644 index 7d913d40d3a7f..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/** - * External dependencies - */ -import * as React from 'react'; -/** - * Internal dependencies - */ -import useFocusHandler from '../hooks/use-focus-handler'; -import useFocusTrap from '../hooks/use-focus-trap'; -import useKeydownHandler from '../hooks/use-keydown-handler'; - -interface Props { - onMinimize: () => void; - onDismiss: ( target: string ) => () => void; - onNextStepProgression: () => void; - onPreviousStepProgression: () => void; - tourContainerRef: React.MutableRefObject< null | HTMLElement >; - isMinimized: boolean; -} - -const KeyboardNavigation: React.FunctionComponent< Props > = ( { - onMinimize, - onDismiss, - onNextStepProgression, - onPreviousStepProgression, - tourContainerRef, - isMinimized, -} ) => { - /** - * Expand Tour Nav - */ - function ExpandedTourNav() { - useKeydownHandler( { - onEscape: onMinimize, - onArrowRight: onNextStepProgression, - onArrowLeft: onPreviousStepProgression, - } ); - useFocusTrap( tourContainerRef ); - - return null; - } - - /** - * Minimize Tour Nav - */ - function MinimizedTourNav() { - useKeydownHandler( { onEscape: onDismiss( 'esc-key-minimized' ) } ); - - return null; - } - - const isTourFocused = useFocusHandler( tourContainerRef ); - - if ( ! isTourFocused ) { - return null; - } - - return isMinimized ? : ; -}; - -export default KeyboardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx deleted file mode 100644 index e030a37cdda26..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx +++ /dev/null @@ -1,286 +0,0 @@ -/** - * External Dependencies - */ -import { useViewportMatch } from '@wordpress/compose'; -import { useEffect, useState, useCallback, useMemo, useRef } from '@wordpress/element'; -import clsx from 'clsx'; -import { usePopper } from 'react-popper'; -/** - * Internal Dependencies - */ -import useStepTracking from '../hooks/use-step-tracking'; -import { classParser } from '../utils'; -import { liveResizeModifier } from '../utils/live-resize-modifier'; -import KeyboardNavigation from './keyboard-navigation'; -import TourKitMinimized from './tour-kit-minimized'; -import Overlay from './tour-kit-overlay'; -import Spotlight from './tour-kit-spotlight'; -import TourKitStep from './tour-kit-step'; -import type { Callback, Config } from '../types'; - -const handleCallback = ( currentStepIndex: number, callback?: Callback ) => { - typeof callback === 'function' && callback( currentStepIndex ); -}; - -interface Props { - config: Config; -} - -const TourKitFrame: React.FunctionComponent< Props > = ( { config } ) => { - const [ currentStepIndex, setCurrentStepIndex ] = useState( 0 ); - const [ initialFocusedElement, setInitialFocusedElement ] = useState< HTMLElement | null >( - null - ); - const [ isMinimized, setIsMinimized ] = useState( config.isMinimized ?? false ); - - const [ popperElement, setPopperElement ] = useState< HTMLElement | null >( null ); - const [ tourReady, setTourReady ] = useState( false ); - const tourContainerRef = useRef( null ); - const isMobile = useViewportMatch( 'mobile', '<' ); - const lastStepIndex = config.steps.length - 1; - const referenceElements = config.steps[ currentStepIndex ].referenceElements; - const referenceElementSelector = - referenceElements?.[ isMobile ? 'mobile' : 'desktop' ] || referenceElements?.desktop; - const referenceElement = referenceElementSelector - ? document.querySelector< HTMLElement >( referenceElementSelector ) - : null; - - useEffect( () => { - if ( config.isMinimized ) { - setIsMinimized( true ); - } - }, [ config.isMinimized ] ); - - const showArrowIndicator = useCallback( () => { - if ( config.options?.effects?.arrowIndicator === false ) { - return false; - } - - return !! ( referenceElement && ! isMinimized && tourReady ); - }, [ config.options?.effects?.arrowIndicator, isMinimized, referenceElement, tourReady ] ); - - const showSpotlight = useCallback( () => { - if ( ! config.options?.effects?.spotlight ) { - return false; - } - - return ! isMinimized; - }, [ config.options?.effects?.spotlight, isMinimized ] ); - - const showOverlay = useCallback( () => { - if ( showSpotlight() || ! config.options?.effects?.overlay ) { - return false; - } - - return ! isMinimized; - }, [ config.options?.effects?.overlay, isMinimized, showSpotlight ] ); - - const handleDismiss = useCallback( - ( source: string ) => { - return () => { - config.closeHandler( config.steps, currentStepIndex, source ); - }; - }, - [ config, currentStepIndex ] - ); - - const handleNextStepProgression = useCallback( () => { - let newStepIndex = currentStepIndex; - if ( lastStepIndex > currentStepIndex ) { - newStepIndex = currentStepIndex + 1; - setCurrentStepIndex( newStepIndex ); - } - handleCallback( newStepIndex, config.options?.callbacks?.onNextStep ); - }, [ config.options?.callbacks?.onNextStep, currentStepIndex, lastStepIndex ] ); - - const handlePreviousStepProgression = useCallback( () => { - let newStepIndex = currentStepIndex; - if ( currentStepIndex > 0 ) { - newStepIndex = currentStepIndex - 1; - setCurrentStepIndex( newStepIndex ); - } - handleCallback( newStepIndex, config.options?.callbacks?.onPreviousStep ); - }, [ config.options?.callbacks?.onPreviousStep, currentStepIndex ] ); - - const handleGoToStep = useCallback( - ( stepIndex: number ) => { - setCurrentStepIndex( stepIndex ); - handleCallback( stepIndex, config.options?.callbacks?.onGoToStep ); - }, - [ config.options?.callbacks?.onGoToStep ] - ); - - const handleMinimize = useCallback( () => { - setIsMinimized( true ); - handleCallback( currentStepIndex, config.options?.callbacks?.onMinimize ); - }, [ config.options?.callbacks?.onMinimize, currentStepIndex ] ); - - const handleMaximize = useCallback( () => { - setIsMinimized( false ); - handleCallback( currentStepIndex, config.options?.callbacks?.onMaximize ); - }, [ config.options?.callbacks?.onMaximize, currentStepIndex ] ); - - const { - styles: popperStyles, - attributes: popperAttributes, - update: popperUpdate, - } = usePopper( referenceElement, popperElement, { - strategy: 'fixed', - placement: config?.placement ?? 'bottom', - modifiers: [ - { - name: 'preventOverflow', - options: { - rootBoundary: 'document', - padding: 16, // same as the left/margin of the tour frame - }, - }, - { - name: 'arrow', - options: { - padding: 12, - }, - }, - { - name: 'offset', - options: { - offset: [ 0, showArrowIndicator() ? 12 : 10 ], - }, - }, - { - name: 'flip', - options: { - fallbackPlacements: [ 'top', 'left', 'right' ], - }, - }, - useMemo( - () => liveResizeModifier( config.options?.effects?.liveResize ), - [ config.options?.effects?.liveResize ] - ), - ...( config.options?.popperModifiers || [] ), - ], - } ); - - const stepRepositionProps = - ! isMinimized && referenceElement && tourReady - ? { - style: popperStyles?.popper, - ...popperAttributes?.popper, - } - : null; - - const arrowPositionProps = - ! isMinimized && referenceElement && tourReady - ? { - style: popperStyles?.arrow, - ...popperAttributes?.arrow, - } - : null; - - /* - * Focus first interactive element when step renders. - */ - useEffect( () => { - setTimeout( () => initialFocusedElement?.focus() ); - }, [ initialFocusedElement ] ); - - /* - * Fixes issue with Popper misplacing the instance on mount - * See: https://stackoverflow.com/questions/65585859/react-popper-incorrect-position-on-mount - */ - useEffect( () => { - // If no reference element to position step near - if ( ! referenceElement ) { - setTourReady( true ); - return; - } - - setTourReady( false ); - - if ( popperUpdate ) { - popperUpdate() - .then( () => setTourReady( true ) ) - .catch( () => setTourReady( true ) ); - } - }, [ popperUpdate, referenceElement ] ); - - useEffect( () => { - if ( referenceElement && config.options?.effects?.autoScroll ) { - referenceElement.scrollIntoView( config.options.effects.autoScroll ); - } - }, [ config.options?.effects?.autoScroll, referenceElement ] ); - - const classes = clsx( - 'tour-kit-frame', - isMobile ? 'is-mobile' : 'is-desktop', - { 'is-visible': tourReady }, - classParser( config.options?.classNames ) - ); - - useStepTracking( currentStepIndex, config.options?.callbacks?.onStepViewOnce ); - - useEffect( () => { - if ( config.options?.callbacks?.onStepView ) { - handleCallback( currentStepIndex, config.options?.callbacks?.onStepView ); - } - }, [ config.options?.callbacks?.onStepView, currentStepIndex ] ); - - return ( - <> - -
- { showOverlay() && } - { showSpotlight() && ( - - ) } -
) } - > - { showArrowIndicator() && ( -
) } - /> - ) } - { ! isMinimized ? ( - - ) : ( - - ) } -
-
- - ); -}; - -export default TourKitFrame; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx deleted file mode 100644 index 6831fbf8f6051..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-minimized.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Internal Dependencies - */ -import { MinimizedTourRendererProps } from '../types'; -import type { Config } from '../types'; - -interface Props extends MinimizedTourRendererProps { - config: Config; -} - -const TourKitMinimized: React.FunctionComponent< Props > = ( { - config, - steps, - currentStepIndex, - onMaximize, - onDismiss, -} ) => { - return ( -
- -
- ); -}; - -export default TourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx deleted file mode 100644 index 3c440bcdc2de7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-overlay.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/** - * External Dependencies - */ -import clsx from 'clsx'; - -interface Props { - visible: boolean; -} - -const TourKitOverlay: React.FunctionComponent< Props > = ( { visible } ) => { - return ( -
- ); -}; - -export default TourKitOverlay; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx deleted file mode 100644 index 61b7e94bca203..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight-interactivity.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { SPOTLIT_ELEMENT_CLASS } from './tour-kit-spotlight'; - -export interface SpotlightInteractivityConfiguration { - /** If true, the user will be allowed to interact with the spotlit element. Defaults to false. */ - enabled?: boolean; - /** - * This element is the root element within which all children will have - * pointer-events disabled during the tour. Defaults to '#wpwrap' - */ - rootElementSelector?: string; -} - -export const SpotlightInteractivity: React.VFC< SpotlightInteractivityConfiguration > = ( { - enabled = false, - rootElementSelector = '#wpwrap', -}: SpotlightInteractivityConfiguration ) => { - if ( ! enabled ) { - return null; - } - return ( - - ); -}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx deleted file mode 100644 index 81e9ed80ff131..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-spotlight.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useMemo, useState, useEffect } from '@wordpress/element'; -import clsx from 'clsx'; -import { usePopper } from 'react-popper'; -import { LiveResizeConfiguration, liveResizeModifier } from '../utils/live-resize-modifier'; -import Overlay from './tour-kit-overlay'; -import { - SpotlightInteractivity, - SpotlightInteractivityConfiguration, -} from './tour-kit-spotlight-interactivity'; -import type { Rect, Placement } from '@popperjs/core'; - -export const SPOTLIT_ELEMENT_CLASS = 'wp-tour-kit-spotlit'; -interface Props { - referenceElement: HTMLElement | null; - styles?: React.CSSProperties; - interactivity?: SpotlightInteractivityConfiguration; - liveResize?: LiveResizeConfiguration; -} - -const TourKitSpotlight: React.FunctionComponent< Props > = ( { - referenceElement, - styles, - interactivity, - liveResize, -} ) => { - const [ popperElement, sePopperElement ] = useState< HTMLElement | null >( null ); - const referenceRect = referenceElement?.getBoundingClientRect(); - - const modifiers = [ - { - name: 'flip', - enabled: false, - }, - { - name: 'preventOverflow', - options: { - mainAxis: false, // true by default - }, - }, - useMemo( - () => ( { - name: 'offset', - options: { - offset: ( { - placement, - reference, - popper, - }: { - placement: Placement; - reference: Rect; - popper: Rect; - } ): [ number, number ] => { - if ( placement === 'bottom' ) { - return [ 0, -( reference.height + ( popper.height - reference.height ) / 2 ) ]; - } - return [ 0, 0 ]; - }, - }, - } ), - [] - ), - // useMemo because https://popper.js.org/react-popper/v2/faq/#why-i-get-render-loop-whenever-i-put-a-function-inside-the-popper-configuration - useMemo( () => { - return liveResizeModifier( liveResize ); - }, [ liveResize ] ), - ]; - - const { styles: popperStyles, attributes: popperAttributes } = usePopper( - referenceElement, - popperElement, - { - strategy: 'fixed', - placement: 'bottom', - modifiers, - } - ); - - const clipDimensions = referenceRect - ? { - width: `${ referenceRect.width }px`, - height: `${ referenceRect.height }px`, - } - : null; - - const clipRepositionProps = referenceElement - ? { - style: { - ...( clipDimensions && clipDimensions ), - ...popperStyles?.popper, - ...( styles && styles ), - }, - ...popperAttributes?.popper, - } - : null; - - /** - * Add a .wp-spotlit class to the referenced element so that we can - * apply CSS styles to it, for whatever purposes such as interactivity - */ - useEffect( () => { - referenceElement?.classList.add( SPOTLIT_ELEMENT_CLASS ); - return () => { - referenceElement?.classList.remove( SPOTLIT_ELEMENT_CLASS ); - }; - }, [ referenceElement ] ); - - return ( - <> - - -
) } - /> - - ); -}; - -export default TourKitSpotlight; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx deleted file mode 100644 index ded21b229ed03..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-step.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * External Dependencies - */ -import { useViewportMatch } from '@wordpress/compose'; -import clsx from 'clsx'; -/** - * Internal Dependencies - */ -import { classParser } from '../utils'; -import type { Config, TourStepRendererProps } from '../types'; - -interface Props extends TourStepRendererProps { - config: Config; -} - -const TourKitStep: React.FunctionComponent< Props > = ( { - config, - steps, - currentStepIndex, - onMinimize, - onDismiss, - onNextStep, - onPreviousStep, - setInitialFocusedElement, - onGoToStep, -} ) => { - const isMobile = useViewportMatch( 'mobile', '<' ); - const classes = clsx( - 'tour-kit-step', - `is-step-${ currentStepIndex }`, - classParser( - config.steps[ currentStepIndex ].options?.classNames?.[ isMobile ? 'mobile' : 'desktop' ] - ) - ); - - return ( -
- -
- ); -}; - -export default TourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx deleted file mode 100644 index 97d0fea0f31ec..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createPortal, useEffect, useRef } from '@wordpress/element'; -import React from 'react'; -import { TourKitContextProvider } from '../contexts'; -import ErrorBoundary from '../error-boundary'; -import TourKitFrame from './tour-kit-frame'; -import type { Config } from '../types'; - -import '../styles.scss'; - -interface Props { - config: Config; - __temp__className?: string; -} - -const TourKit: React.FunctionComponent< Props > = ( { config, __temp__className } ) => { - const portalParent = useRef( document.createElement( 'div' ) ).current; - - useEffect( () => { - const classes = [ 'tour-kit', ...( __temp__className ? [ __temp__className ] : [] ) ]; - - portalParent.classList.add( ...classes ); - - const portalParentElement = config.options?.portalParentElement || document.body; - portalParentElement.appendChild( portalParent ); - - return () => { - portalParentElement.removeChild( portalParent ); - }; - }, [ __temp__className, portalParent, config.options?.portalParentElement ] ); - - return ( - - -
{ createPortal( , portalParent ) }
-
-
- ); -}; - -export default TourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts deleted file mode 100644 index b8522601535a0..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Copied from https://github.com/Automattic/wp-calypso/blob/ce1a376af4bcc8987855ced4c961efacda6e1d32/packages/onboarding/src/utils/flows.ts#L32-L33 -export const START_WRITING_FLOW = 'start-writing'; -export const DESIGN_FIRST_FLOW = 'design-first'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx deleted file mode 100644 index 94bc6c49e7e13..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-seller-celebration-modal-context.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; -import { useState, useEffect } from '@wordpress/element'; -import * as React from 'react'; -import { useContext } from 'react'; - -type HasSeenSCModalResult = { - has_seen_seller_celebration_modal: boolean; -}; - -type HasSeenSellerCelebrationModalContextType = { - hasSeenSellerCelebrationModal: boolean; - updateHasSeenSellerCelebrationModal: ( value: boolean ) => void; -}; - -const HasSeenSCModalContext = React.createContext< HasSeenSellerCelebrationModalContextType >( { - hasSeenSellerCelebrationModal: false, - // eslint-disable-next-line @typescript-eslint/no-empty-function - updateHasSeenSellerCelebrationModal: () => {}, -} ); - -export const useHasSeenSellerCelebrationModal = () => { - return useContext( HasSeenSCModalContext ); -}; - -export const HasSeenSellerCelebrationModalProvider: React.FC< { children: JSX.Element } > = - function ( { children } ) { - const [ hasSeenSellerCelebrationModal, setHasSeenSellerCelebrationModal ] = useState( false ); - - /** - * Fetch the value that whether the video celebration modal has been seen. - */ - function fetchHasSeenSellerCelebrationModal() { - apiFetch< HasSeenSCModalResult >( { - path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', - } ) - .then( ( result: HasSeenSCModalResult ) => - setHasSeenSellerCelebrationModal( result.has_seen_seller_celebration_modal ) - ) - .catch( () => setHasSeenSellerCelebrationModal( false ) ); - } - - /** - * Update the value that whether the video celebration modal has been seen. - * - * @param value - The value to update. - */ - function updateHasSeenSellerCelebrationModal( value: boolean ) { - apiFetch( { - method: 'PUT', - path: '/wpcom/v2/block-editor/has-seen-seller-celebration-modal', - data: { has_seen_seller_celebration_modal: value }, - } ).finally( () => { - setHasSeenSellerCelebrationModal( true ); - } ); - } - - useEffect( () => { - fetchHasSeenSellerCelebrationModal(); - }, [] ); - - return ( - - { children } - - ); - }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx deleted file mode 100644 index 18b9345434ec6..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/has-seen-video-celebration-modal-context.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; -import { useState, useEffect } from '@wordpress/element'; -import * as React from 'react'; -import { useContext } from 'react'; - -type HasSeenVCModalResult = { - has_seen_video_celebration_modal: boolean; -}; - -type HasSeenVideoCelebrationModalContextType = { - hasSeenVideoCelebrationModal: boolean; - updateHasSeenVideoCelebrationModal: ( value: boolean ) => void; -}; - -const HasSeenVCModalContext = React.createContext< HasSeenVideoCelebrationModalContextType >( { - hasSeenVideoCelebrationModal: false, - // eslint-disable-next-line @typescript-eslint/no-empty-function - updateHasSeenVideoCelebrationModal: () => {}, -} ); - -export const useHasSeenVideoCelebrationModal = () => { - return useContext( HasSeenVCModalContext ); -}; - -export const HasSeenVideoCelebrationModalProvider: React.FC< { children: JSX.Element } > = - function ( { children } ) { - const [ hasSeenVideoCelebrationModal, setHasSeenVideoCelebrationModal ] = useState( false ); - - useEffect( () => { - fetchHasSeenVideoCelebrationModal(); - }, [] ); - - /** - * Fetch the value that whether the video celebration modal has been seen. - */ - function fetchHasSeenVideoCelebrationModal() { - apiFetch< HasSeenVCModalResult >( { - path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', - } ) - .then( ( result: HasSeenVCModalResult ) => - setHasSeenVideoCelebrationModal( result.has_seen_video_celebration_modal ) - ) - .catch( () => setHasSeenVideoCelebrationModal( false ) ); - } - - /** - * Update the value that whether the video celebration modal has been seen. - * - * @param value - The value to update. - */ - function updateHasSeenVideoCelebrationModal( value: boolean ) { - apiFetch( { - method: 'PUT', - path: '/wpcom/v2/block-editor/has-seen-video-celebration-modal', - data: { has_seen_video_celebration_modal: value }, - } ).finally( () => { - setHasSeenVideoCelebrationModal( value ); - } ); - } - - return ( - - { children } - - ); - }; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts deleted file mode 100644 index 21d21c340259d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './has-seen-seller-celebration-modal-context'; -export * from './has-seen-video-celebration-modal-context'; -export * from './should-show-first-post-published-modal-context'; -export * from './tour-kit-context'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx deleted file mode 100644 index d9b5b30e8ee0f..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/should-show-first-post-published-modal-context.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import * as React from 'react'; -import { useContext } from 'react'; - -const ShouldShowFPPModalContext = React.createContext( false ); - -export const useShouldShowFirstPostPublishedModal = () => { - return useContext( ShouldShowFPPModalContext ); -}; - -export const ShouldShowFirstPostPublishedModalProvider = ( { children } ) => { - const value = useSelect( - select => select( 'automattic/wpcom-welcome-guide' ).getShouldShowFirstPostPublishedModal(), - [] - ); - - const { fetchShouldShowFirstPostPublishedModal } = useDispatch( - 'automattic/wpcom-welcome-guide' - ); - - useEffect( () => { - fetchShouldShowFirstPostPublishedModal(); - }, [ fetchShouldShowFirstPostPublishedModal ] ); - - return ( - - { children } - - ); -}; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx deleted file mode 100644 index 392911e574750..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/contexts/tour-kit-context.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { createContext, useContext } from '@wordpress/element'; -import type { Config } from '../types'; - -interface TourKitContext { - config: Config; -} - -const TourKitContext = createContext< TourKitContext >( {} as TourKitContext ); - -export const TourKitContextProvider: React.FunctionComponent< - TourKitContext & { children?: React.ReactNode } -> = ( { config, children } ) => { - return { children }; -}; - -export const useTourKitContext = (): TourKitContext => useContext( TourKitContext ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx deleted file mode 100644 index 250ce916b1496..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/error-boundary.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { ErrorInfo } from 'react'; - -type State = { - hasError: boolean; -}; - -class ErrorBoundary extends React.Component< { children: React.ReactNode }, State > { - state = { - hasError: false, - }; - - static getDerivedStateFromError() { - // Update state so the next render will show the fallback UI. - return { hasError: true }; - } - - componentDidCatch( error: Error, errorInfo: ErrorInfo ) { - // You can also log the error to an error reporting service - // eslint-disable-next-line no-console - console.error( error, errorInfo ); - } - - render() { - if ( this.state.hasError ) { - // You can render any custom fallback UI - return

Something went wrong.

; - } - - return this.props.children; - } -} - -export default ErrorBoundary; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts deleted file mode 100644 index 7c2fc8e6c2b2b..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { default as useFocusHandle } from './use-focus-handler'; -export { default as useFocusTrap } from './use-focus-trap'; -export { default as useHasSelectedPaymentBlockOnce } from './use-has-selected-payment-block-once'; -export { default as useKeydownHandler } from './use-keydown-handler'; -export { default as useShouldShowSellerCelebrationModal } from './use-should-show-seller-celebration-modal'; -export { default as useShouldShowVideoCelebrationModal } from './use-should-show-video-celebration-modal'; -export { default as useSiteIntent } from './use-site-intent'; -export { default as useSitePlan } from './use-site-plan'; -export { default as useStepTracking } from './use-step-tracking'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts deleted file mode 100644 index 7e54ac45673dc..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-handler.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * External Dependencies - */ -import { useEffect, useCallback, useState } from '@wordpress/element'; - -/** - * A hook that returns true/false if ref node receives focus by either tabbing or clicking into any of its children. - * @param ref - React.MutableRefObject< null | HTMLElement > - */ -const useFocusHandler = ( ref: React.MutableRefObject< null | HTMLElement > ): boolean => { - const [ hasFocus, setHasFocus ] = useState( false ); - - const handleFocus = useCallback( () => { - if ( document.hasFocus() && ref.current?.contains( document.activeElement ) ) { - setHasFocus( true ); - } else { - setHasFocus( false ); - } - }, [ ref ] ); - - const handleMousedown = useCallback( - ( event: MouseEvent ) => { - if ( ref.current?.contains( event.target as Node ) ) { - setHasFocus( true ); - } else { - setHasFocus( false ); - } - }, - [ ref ] - ); - - const handleKeyup = useCallback( - ( event: KeyboardEvent ) => { - if ( event.key === 'Tab' ) { - if ( ref.current?.contains( event.target as Node ) ) { - setHasFocus( true ); - } else { - setHasFocus( false ); - } - } - }, - [ ref ] - ); - - useEffect( () => { - document.addEventListener( 'focusin', handleFocus ); - document.addEventListener( 'mousedown', handleMousedown ); - document.addEventListener( 'keyup', handleKeyup ); - - return () => { - document.removeEventListener( 'focusin', handleFocus ); - document.removeEventListener( 'mousedown', handleMousedown ); - document.removeEventListener( 'keyup', handleKeyup ); - }; - }, [ ref, handleFocus, handleKeyup, handleMousedown ] ); - - return hasFocus; -}; - -export default useFocusHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts deleted file mode 100644 index b8e0bc7b313a9..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-focus-trap.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * External Dependencies - */ -import { focus } from '@wordpress/dom'; -import { useEffect, useCallback, useState } from '@wordpress/element'; -/** - * A hook that constraints tabbing/focus on focuable elements in the given element ref. - * @param ref - React.MutableRefObject< null | HTMLElement > - */ -const useFocusTrap = ( ref: React.MutableRefObject< null | HTMLElement > ): void => { - const [ firstFocusableElement, setFirstFocusableElement ] = useState< HTMLElement | undefined >(); - const [ lastFocusableElement, setLastFocusableElement ] = useState< HTMLElement | undefined >(); - - const handleTrapFocus = useCallback( - ( event: KeyboardEvent ) => { - let handled = false; - - if ( event.key === 'Tab' ) { - if ( event.shiftKey ) { - // Shift + Tab - if ( document.activeElement === firstFocusableElement ) { - lastFocusableElement?.focus(); - handled = true; - } - } else if ( document.activeElement === lastFocusableElement ) { - // Tab - firstFocusableElement?.focus(); - handled = true; - } - } - - if ( handled ) { - event.preventDefault(); - event.stopPropagation(); - } - }, - [ firstFocusableElement, lastFocusableElement ] - ); - - useEffect( () => { - const focusableElements = ref.current ? focus.focusable.find( ref.current as HTMLElement ) : []; - - if ( focusableElements && focusableElements.length ) { - setFirstFocusableElement( focusableElements[ 0 ] as HTMLElement ); - setLastFocusableElement( focusableElements[ focusableElements.length - 1 ] as HTMLElement ); - } - - document.addEventListener( 'keydown', handleTrapFocus ); - - return () => { - document.removeEventListener( 'keydown', handleTrapFocus ); - }; - }, [ ref, handleTrapFocus ] ); -}; - -export default useFocusTrap; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js deleted file mode 100644 index f8060b9a2d232..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-has-selected-payment-block-once.js +++ /dev/null @@ -1,62 +0,0 @@ -import { useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; - -/** - * Watch the user's block selection and keep a note if they ever select a payments - * block. - * A payments block is any block with 'payments' in the name, like jetpack/simple-payments - * or jetpack/recurring-payments. - * Selecting a block whose direct parent has 'payments' in the name also counts. - * This is to account for clicking inside the button in a payments block, for example. - * @returns {boolean} Has the user selected a payments block (or a direct descendant) at least once? - */ -const useHasSelectedPaymentBlockOnce = () => { - const [ hasSelectedPaymentsOnce, setHasSelectedPaymentsOnce ] = useState( false ); - - // Get the name of the currently selected block - const selectedBlockName = useSelect( select => { - // Special case: We know we're returning true, so we don't need to find block names. - if ( hasSelectedPaymentsOnce ) { - return ''; - } - - const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); - return selectedBlock?.name ?? ''; - } ); - - // Get the name of the currently selected block's direct parent, if one exists - const parentSelectedBlockName = useSelect( select => { - // Special case: We know we're returning true, so we don't need to find block names. - if ( hasSelectedPaymentsOnce ) { - return ''; - } - - const selectedBlock = select( 'core/block-editor' ).getSelectedBlock(); - if ( selectedBlock?.clientId ) { - const parentIds = select( 'core/block-editor' ).getBlockParents( selectedBlock?.clientId ); - if ( parentIds && parentIds.length ) { - const parent = select( 'core/block-editor' ).getBlock( parentIds[ parentIds.length - 1 ] ); - return parent?.name ?? ''; - } - } - return ''; - } ); - - // On selection change, set hasSelectedPaymentsOnce=true if block name or parent's block name contains 'payments' - useEffect( () => { - if ( - ! hasSelectedPaymentsOnce && - ( selectedBlockName.includes( 'payments' ) || parentSelectedBlockName.includes( 'payments' ) ) - ) { - setHasSelectedPaymentsOnce( true ); - } - }, [ - selectedBlockName, - parentSelectedBlockName, - hasSelectedPaymentsOnce, - setHasSelectedPaymentsOnce, - ] ); - - return hasSelectedPaymentsOnce; -}; -export default useHasSelectedPaymentBlockOnce; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts deleted file mode 100644 index 6ea129fe08f2e..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable jsdoc/require-param */ -/** - * External Dependencies - */ -import { useEffect, useCallback } from '@wordpress/element'; - -interface Props { - onEscape?: () => void; - onArrowRight?: () => void; - onArrowLeft?: () => void; -} - -/** - * A hook the applies the respective callbacks in response to keydown events. - */ -const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): void => { - const handleKeydown = useCallback( - ( event: KeyboardEvent ) => { - let handled = false; - - switch ( event.key ) { - case 'Escape': - onEscape && ( onEscape(), ( handled = true ) ); - break; - case 'ArrowRight': - onArrowRight && ( onArrowRight(), ( handled = true ) ); - break; - case 'ArrowLeft': - onArrowLeft && ( onArrowLeft(), ( handled = true ) ); - break; - default: - break; - } - - if ( handled ) { - event.preventDefault(); - event.stopPropagation(); - } - }, - [ onEscape, onArrowRight, onArrowLeft ] - ); - - useEffect( () => { - document.addEventListener( 'keydown', handleKeydown ); - - return () => { - document.removeEventListener( 'keydown', handleKeydown ); - }; - }, [ handleKeydown ] ); -}; - -export default useKeydownHandler; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js deleted file mode 100644 index 80f37b3b530cf..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-seller-celebration-modal.js +++ /dev/null @@ -1,67 +0,0 @@ -import { useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; -import { useHasSeenSellerCelebrationModal } from '../contexts/has-seen-seller-celebration-modal-context'; -import useHasSelectedPaymentBlockOnce from './use-has-selected-payment-block-once'; -import useSiteIntent from './use-site-intent'; - -const useShouldShowSellerCelebrationModal = () => { - const [ shouldShowSellerCelebrationModal, setShouldShowSellerCelebrationModal ] = - useState( false ); - - const { siteIntent: intent } = useSiteIntent(); - const hasSelectedPaymentsOnce = useHasSelectedPaymentBlockOnce(); - - const { hasSeenSellerCelebrationModal } = useHasSeenSellerCelebrationModal(); - - const hasPaymentsBlock = useSelect( select => { - const isSiteEditor = !! select( 'core/edit-site' ); - - if ( isSiteEditor ) { - const page = select( 'core/edit-site' ).getPage(); - const pageId = parseInt( page?.context?.postId ); - const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); - - let paymentsBlock = false; - // Only check for payment blocks if we haven't seen the celebration modal text yet - if ( ! hasSeenSellerCelebrationModal ) { - const didCountRecurringPayments = - select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/recurring-payments' ) > 0; - const didCountSimplePayments = - select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ) > 0; - paymentsBlock = - ( pageEntity?.content?.raw?.includes( '' ) || - pageEntity?.content?.raw?.includes( '' ) || - didCountRecurringPayments || - didCountSimplePayments ) ?? - false; - } - - return paymentsBlock; - } - - let paymentBlockCount = 0; - // Only check for payment blocks if we haven't seen the celebration modal yet - if ( ! hasSeenSellerCelebrationModal ) { - paymentBlockCount += select( 'core/block-editor' ).getGlobalBlockCount( - 'jetpack/recurring-payments' - ); - paymentBlockCount += - select( 'core/block-editor' ).getGlobalBlockCount( 'jetpack/simple-payments' ); - } - - return paymentBlockCount > 0; - } ); - - useEffect( () => { - if ( - intent === 'sell' && - hasPaymentsBlock && - hasSelectedPaymentsOnce && - ! hasSeenSellerCelebrationModal - ) { - setShouldShowSellerCelebrationModal( true ); - } - }, [ intent, hasPaymentsBlock, hasSelectedPaymentsOnce, hasSeenSellerCelebrationModal ] ); - return shouldShowSellerCelebrationModal; -}; -export default useShouldShowSellerCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts deleted file mode 100644 index ebbc5ce2ce1d2..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-should-show-video-celebration-modal.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useState, useEffect } from '@wordpress/element'; -import request from 'wpcom-proxy-request'; -import { useHasSeenVideoCelebrationModal } from '../contexts/has-seen-video-celebration-modal-context'; -import useSiteIntent from './use-site-intent'; - -declare global { - interface Window { - _currentSiteId: number; - _currentSiteType: string; - } -} - -interface Site { - options?: { - launchpad_checklist_tasks_statuses?: { - video_uploaded: boolean; - }; - }; -} -const useShouldShowVideoCelebrationModal = ( isEditorSaving: boolean ) => { - const { siteIntent: intent } = useSiteIntent(); - - const [ shouldShowVideoCelebrationModal, setShouldShowVideoCelebrationModal ] = useState( false ); - const { hasSeenVideoCelebrationModal } = useHasSeenVideoCelebrationModal(); - - useEffect( () => { - const maybeRenderVideoCelebrationModal = async () => { - // Get latest site options since the video may have just been uploaded. - const siteObj = ( await request( { - path: `/sites/${ window._currentSiteId }?http_envelope=1`, - apiVersion: '1.1', - } ) ) as Site; - - if ( siteObj?.options?.launchpad_checklist_tasks_statuses?.video_uploaded ) { - setShouldShowVideoCelebrationModal( true ); - } - }; - - if ( 'videopress' === intent && ! hasSeenVideoCelebrationModal ) { - maybeRenderVideoCelebrationModal(); - } else if ( hasSeenVideoCelebrationModal ) { - setShouldShowVideoCelebrationModal( false ); - } - }, [ - isEditorSaving, // included so that we check whether the video has been uploaded on save. - intent, - hasSeenVideoCelebrationModal, - ] ); - return shouldShowVideoCelebrationModal; -}; -export default useShouldShowVideoCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js deleted file mode 100644 index 496ee2bc23bbd..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-intent.js +++ /dev/null @@ -1,14 +0,0 @@ -// FIXME: We can use `useSiteIntent` from `@automattic/data-stores` and remove this. -// https://github.com/Automattic/wp-calypso/pull/73565#discussion_r1113839120 -const useSiteIntent = () => { - // We can skip the request altogether since this information is already added to the window in - // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/plugins/jetpack/extensions/plugins/launchpad-save-modal/launchpad-save-modal.php#LL31C8-L31C34 - // We could update this to use the launchpad endpoint in jetpack-mu-wpcom, but that may require - // permissions changes as it requires 'manage_options' to read - // https://github.com/Automattic/jetpack/blob/e135711f9a130946dae1bca6c9c0967350331067/projects/packages/jetpack-mu-wpcom/src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php#L121. - return { - siteIntent: window.Jetpack_LaunchpadSaveModal?.siteIntentOption, - siteIntentFetched: true, - }; -}; -export default useSiteIntent; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js deleted file mode 100644 index d3c64400da453..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-site-plan.js +++ /dev/null @@ -1,24 +0,0 @@ -import { useState, useEffect } from '@wordpress/element'; -import { useCallback } from 'react'; -import request from 'wpcom-proxy-request'; - -const useSitePlan = siteIdOrSlug => { - const [ sitePlan, setSitePlan ] = useState( {} ); - - const fetchSite = useCallback( async () => { - const siteObj = await request( { - path: `/sites/${ siteIdOrSlug }?http_envelope=1`, - apiVersion: '1.1', - } ); - if ( siteObj?.plan ) { - setSitePlan( siteObj.plan ); - } - }, [ siteIdOrSlug ] ); - - useEffect( () => { - fetchSite(); - }, [ fetchSite ] ); - - return sitePlan; -}; -export default useSitePlan; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts deleted file mode 100644 index a1e231ace668c..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-step-tracking.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * External Dependencies - */ -import { useState, useEffect } from '@wordpress/element'; -/** - * Internal Dependencies - */ -import { Callback } from '../types'; - -const useStepTracking = ( - currentStepIndex: number, - onStepViewOnce: Callback | undefined -): void => { - const [ stepsViewed, setStepsViewed ] = useState< number[] >( [] ); - - useEffect( () => { - if ( stepsViewed.includes( currentStepIndex ) ) { - return; - } - - setStepsViewed( prev => [ ...prev, currentStepIndex ] ); - onStepViewOnce?.( currentStepIndex ); - }, [ currentStepIndex, onStepViewOnce, stepsViewed ] ); -}; - -export default useStepTracking; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts deleted file mode 100644 index 2cf54b34efa8a..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default } from './components/tour-kit'; -export * from './constants'; -export { default as WpcomTourKit, usePrefetchTourAssets } from './variants/wpcom'; -export * from './contexts'; -export * from './hooks'; -export * from './types'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss deleted file mode 100644 index ef38a0c05b1e1..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/styles.scss +++ /dev/null @@ -1,91 +0,0 @@ -.tour-kit-frame { - visibility: hidden; - - &.is-visible { - visibility: visible; - } -} - -.tour-kit-frame__container { - border-radius: 2px; - bottom: 44px; - display: inline; - left: 16px; - position: fixed; - z-index: 9999; - // Avoid the text cursor when the text is not selectable - cursor: default; - box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.25); - background: var(--studio-white); -} - -.tour-kit-overlay { - position: fixed; - width: 100vw; - height: 100vh; - top: 0; - left: 0; - background: rgba(0, 0, 0); - opacity: 0; - - &.is-visible { - opacity: 0.5; - } -} - -.tour-kit-spotlight { - &.is-visible { - position: fixed; - overflow: hidden; - // box-shadow: 0 0 0 9999px rgba(0, 0, 255, 0.2); - outline: 99999px solid rgba(0, 0, 0, 0.5); - z-index: 1; - } -} - -.tour-kit-frame__arrow { - visibility: hidden; -} - -.tour-kit-frame__arrow, -.tour-kit-frame__arrow::before { - position: absolute; - width: 12px; - height: 12px; - background: inherit; - z-index: -1; -} - -.tour-kit-frame__arrow::before { - visibility: visible; - content: ""; - transform: rotate(45deg); -} - -.tour-kit-frame__container[data-popper-placement^="top"] > .tour-kit-frame__arrow { - bottom: -6px; - &::before { - box-shadow: 1px 1px 2px -1px rgb(0 0 0 / 25%); - } -} - -.tour-kit-frame__container[data-popper-placement^="bottom"] > .tour-kit-frame__arrow { - top: -6px; - &::before { - box-shadow: -1px -1px 2px -1px rgb(0 0 0 / 25%); - } -} - -.tour-kit-frame__container[data-popper-placement^="left"] > .tour-kit-frame__arrow { - right: -6px; - &::before { - box-shadow: 1px -1px 2px -1px rgb(0 0 0 / 25%); - } -} - -.tour-kit-frame__container[data-popper-placement^="right"] > .tour-kit-frame__arrow { - left: -6px; - &::before { - box-shadow: -1px 1px 2px -1px rgb(0 0 0 / 25%); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts deleted file mode 100644 index 3f4ec5c080a13..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/types.ts +++ /dev/null @@ -1,164 +0,0 @@ -import * as PopperJS from '@popperjs/core'; -import React from 'react'; -import { SpotlightInteractivityConfiguration } from './components/tour-kit-spotlight-interactivity'; -import { LiveResizeConfiguration } from './utils/live-resize-modifier'; -import type { Modifier } from 'react-popper'; - -export interface Step { - slug?: string; - referenceElements?: { - desktop?: string; - mobile?: string; - }; - meta: { - [ key: string ]: unknown; - // | React.FunctionComponent< Record< string, unknown > > - // | HTMLElement - // | string - // | ... - }; - options?: { - classNames?: { - /** - * `desktop` classes are applied when min-width is larger or equal to 480px. - */ - desktop?: string | string[]; - /** - * `mobile` classes are applied when max-width is smaller than 480px. - */ - mobile?: string | string[]; - }; - }; -} - -export interface TourStepRendererProps { - steps: Step[]; - currentStepIndex: number; - onDismiss: ( source: string ) => () => void; - onNextStep: () => void; - onPreviousStep: () => void; - onMinimize: () => void; - setInitialFocusedElement: React.Dispatch< React.SetStateAction< HTMLElement | null > >; - onGoToStep: ( stepIndex: number ) => void; -} - -export interface MinimizedTourRendererProps { - steps: Step[]; - currentStepIndex: number; - onMaximize: () => void; - onDismiss: ( source: string ) => () => void; -} - -export type TourStepRenderer = React.FunctionComponent< TourStepRendererProps >; -export type MinimizedTourRenderer = React.FunctionComponent< MinimizedTourRendererProps >; -export type Callback = ( currentStepIndex: number ) => void; -export type CloseHandler = ( steps: Step[], currentStepIndex: number, source: string ) => void; -export type PopperModifier = Partial< Modifier< unknown, Record< string, unknown > > >; - -export interface Callbacks { - onMinimize?: Callback; - onMaximize?: Callback; - onGoToStep?: Callback; - onNextStep?: Callback; - onPreviousStep?: Callback; - onStepViewOnce?: Callback; - onStepView?: Callback; -} - -export interface Options { - classNames?: string | string[]; - callbacks?: Callbacks; - /** An object to enable/disable/combine various tour effects, such as spotlight, overlay, and autoscroll */ - effects?: { - /** - * Adds a semi-transparent overlay and highlights the reference element - * when provided with a transparent box over it. The existence of this configuration - * key implies enabling the spotlight effect. - */ - spotlight?: { - /** An object that configures whether the user is allowed to interact with the referenced element during the tour */ - interactivity?: SpotlightInteractivityConfiguration; - /** CSS properties that configures the styles applied to the spotlight overlay */ - styles?: React.CSSProperties; - }; - /** Shows a little triangle that points to the referenced element. Defaults to true */ - arrowIndicator?: boolean; - /** - * Includes the semi-transparent overlay for all the steps Also blocks interactions for everything except the tour dialogues, - * including the referenced elements. Refer to spotlight interactivity configuration to affect this. - * - * Defaults to false, but if spotlight is enabled it implies this is enabled as well. - */ - overlay?: boolean; - /** Configures the autoscroll behaviour. Defaults to False. More information about the configuration at: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView */ - autoScroll?: ScrollIntoViewOptions | boolean; - /** Configures the behaviour for automatically resizing the tour kit elements (TourKitFrame and Spotlight). Defaults to disabled. */ - liveResize?: LiveResizeConfiguration; - }; - popperModifiers?: PopperModifier[]; - portalParentElement?: HTMLElement | null; -} - -export interface Config { - steps: Step[]; - renderers: { - tourStep: TourStepRenderer; - tourMinimized: MinimizedTourRenderer; - }; - closeHandler: CloseHandler; - isMinimized?: boolean; - options?: Options; - placement?: PopperJS.Placement; -} - -export type Tour = React.FunctionComponent< { config: Config } >; - -/************************ - * WPCOM variant types: * - ************************/ - -export type OnTourRateCallback = ( currentStepIndex: number, liked: boolean ) => void; - -export interface WpcomStep extends Step { - meta: { - heading: string | null; - descriptions: { - desktop: string | React.ReactElement | null; - mobile: string | React.ReactElement | null; - }; - imgSrc?: { - desktop?: { - src: string; - type: string; - }; - mobile?: { - src: string; - type: string; - }; - }; - imgLink?: { - href: string; - playable?: boolean; - onClick?: () => void; - }; - }; -} - -export interface WpcomTourStepRendererProps extends TourStepRendererProps { - steps: WpcomStep[]; -} - -export interface WpcomOptions extends Options { - tourRating?: { - enabled: boolean; - useTourRating?: () => 'thumbs-up' | 'thumbs-down' | undefined; - onTourRate?: ( rating: 'thumbs-up' | 'thumbs-down' ) => void; - }; -} - -export interface WpcomConfig extends Omit< Config, 'renderers' > { - steps: WpcomStep[]; - options?: WpcomOptions; -} - -export type WpcomTour = React.FunctionComponent< { config: WpcomConfig } >; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts deleted file mode 100644 index 3f00a820d1703..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import debugFactory from 'debug'; - -/** - * Helper to convert CSV of `classes` to an array. - * @param classes - String or array of classes to format. - * @returns Array of classes - */ -export function classParser( classes?: string | string[] ): string[] | null { - if ( classes?.length ) { - return classes.toString().split( ',' ); - } - - return null; -} - -export const debug = debugFactory( 'tour-kit' ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx deleted file mode 100644 index ab1a14cf8568b..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/utils/live-resize-modifier.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { debug } from '../utils'; -import type { ModifierArguments, Options, State } from '@popperjs/core'; -import type { Modifier } from 'react-popper'; - -// Adds the resizeObserver and mutationObserver properties to the popper effect function argument -type ModifierArgumentsWithObserversProp = ModifierArguments< Options > & { - state: State & { - elements: State[ 'elements' ] & { - reference: State[ 'elements' ][ 'reference' ] & { - [ key: symbol ]: { - resizeObserver: ResizeObserver; - mutationObserver: MutationObserver; - }; - }; - }; - }; -}; - -export interface LiveResizeConfiguration { - /** CSS Selector for the the DOM node (and children) to observe for mutations */ - rootElementSelector?: string; - /** True to enable update on reference element resize, defaults to false */ - resize?: boolean; - /** True to enable update on node and subtree mutation, defaults to false. May be performance intensive */ - mutation?: boolean; -} - -type liveResizeModifierFactory = ( - params: LiveResizeConfiguration | undefined -) => Modifier< 'liveResizeModifier', Record< string, unknown > >; - -/** - * Function that returns a Popper modifier that observes the specified root element as well as - * reference element for any changes. The reason for being a currying function is so that - * we can customise the root element selector, otherwise observing at a higher than necessary - * level might cause unnecessary performance penalties. - * - * The Popper modifier queues an asynchronous update on the Popper instance whenever either of the - * Observers trigger its callback. - * - * @param config - The config. - * @param config.rootElementSelector - The selector of the root element. - * @param config.mutation - Whether to mutate. - * @param config.resize - Whether to resize. - * @returns custom Popper modifier. - */ -export const liveResizeModifier: liveResizeModifierFactory = ( - { rootElementSelector, mutation = false, resize = false }: LiveResizeConfiguration = { - mutation: false, - resize: false, - } -) => ( { - name: 'liveResizeModifier', - enabled: true, - phase: 'main', - fn: () => { - return; - }, - effect: arg0 => { - try { - const { state, instance } = arg0 as ModifierArgumentsWithObserversProp; // augment types here because we are mutating the properties on the argument that is passed in - - const ObserversProp = Symbol(); // use a symbol here so that we don't clash with multiple poppers using this modifier on the same reference node - const { reference } = state.elements; - - reference[ ObserversProp ] = { - resizeObserver: new ResizeObserver( () => { - instance.update(); - } ), - - mutationObserver: new MutationObserver( () => { - instance.update(); - } ), - }; - - if ( resize ) { - if ( reference instanceof Element ) { - reference[ ObserversProp ].resizeObserver.observe( reference ); - } else { - debug( - 'Error: ResizeObserver does not work with virtual elements, Tour Kit will not resize automatically if the size of the referenced element changes.' - ); - } - } - - if ( mutation ) { - const rootElementNode = document.querySelector( rootElementSelector || '#wpwrap' ); - if ( rootElementNode instanceof Element ) { - reference[ ObserversProp ].mutationObserver.observe( rootElementNode, { - attributes: true, - characterData: true, - childList: true, - subtree: true, - } ); - } else { - debug( - `Error: ${ rootElementSelector } selector did not find a valid DOM element, Tour Kit will not update automatically if the DOM layout changes.` - ); - } - } - - return () => { - reference[ ObserversProp ].resizeObserver.disconnect(); - reference[ ObserversProp ].mutationObserver.disconnect(); - delete reference[ ObserversProp ]; - }; - } catch ( error ) { - debug( 'Error: Tour Kit live resize modifier failed unexpectedly:', error ); - } - }, -} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx deleted file mode 100644 index 1afd5ce37fca4..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-minimized.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Button, Flex } from '@wordpress/components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf } from '@wordpress/i18n'; -import { Icon, close } from '@wordpress/icons'; -import { useI18n } from '@wordpress/react-i18n'; -import maximize from '../icons/maximize'; -import type { MinimizedTourRendererProps } from '../../../types'; - -const WpcomTourKitMinimized: React.FunctionComponent< MinimizedTourRendererProps > = ( { - steps, - onMaximize, - onDismiss, - currentStepIndex, -} ) => { - const { __ } = useI18n(); - const lastStepIndex = steps.length - 1; - const page = currentStepIndex + 1; - const numberOfPages = lastStepIndex + 1; - - return ( - - - - - ); -}; - -export default WpcomTourKitMinimized; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss deleted file mode 100644 index fe9a19fc4b508..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.scss +++ /dev/null @@ -1,44 +0,0 @@ -@import "@automattic/calypso-color-schemes"; - -.wpcom-tour-kit-pagination-control { - margin: 0; - display: flex; - justify-content: center; - width: 100%; - - li { - display: inline-flex; - margin: auto 4px; - height: 18px; - align-items: center; - border: none; - } - - li.pagination-control__last-item { - margin-left: auto; - height: 32px; - } - - button.pagination-control__page { - border: none; - padding: 0; - width: 6px; - height: 6px; - border-radius: 50%; - cursor: pointer; - transition: all 0.2s ease-in-out; - background-color: var(--color-neutral-10); - - &:hover { - background-color: var(--color-neutral-40); - } - - &:disabled { - cursor: default; - } - - &.is-current { - background-color: var(--wp-admin-theme-color); - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx deleted file mode 100644 index a37ce82c23b98..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-pagination-control.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { __, sprintf } from '@wordpress/i18n'; -import clsx from 'clsx'; -import './wpcom-tour-kit-pagination-control.scss'; - -interface Props { - onChange: ( page: number ) => void; - activePageIndex: number; - numberOfPages: number; - classNames?: string | string[]; - children?: React.ReactNode; -} - -const WpcomTourKitPaginationControl: React.FunctionComponent< Props > = ( { - activePageIndex, - numberOfPages, - onChange, - classNames, - children, -} ) => { - const classes = clsx( 'wpcom-tour-kit-pagination-control', classNames ); - - return ( -
    - { Array.from( { length: numberOfPages } ).map( ( value, index ) => ( -
  • -
  • - ) ) } - { children &&
  • { children }
  • } -
- ); -}; - -export default WpcomTourKitPaginationControl; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx deleted file mode 100644 index 82c20f1410019..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-rating.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { useI18n } from '@wordpress/react-i18n'; -import clsx from 'clsx'; -import { useTourKitContext } from '../../../index'; -import thumbsDown from '../icons/thumbs_down'; -import thumbsUp from '../icons/thumbs_up'; -import type { WpcomConfig } from '../../../index'; - -const WpcomTourKitRating: React.FunctionComponent = () => { - const [ tempRating, setTempRating ] = useState< 'thumbs-up' | 'thumbs-down' >(); - const context = useTourKitContext(); - const config = context.config as unknown as WpcomConfig; - const tourRating = config.options?.tourRating?.useTourRating?.() ?? tempRating; - const { __ } = useI18n(); - - let isDisabled = false; - - if ( ! config.options?.tourRating?.enabled ) { - return null; - } - - // check is on tempRating to allow rerating in a restarted tour - if ( ! isDisabled && tempRating !== undefined ) { - isDisabled = true; - } - - const rateTour = ( isThumbsUp: boolean ) => { - if ( isDisabled ) { - return; - } - - const rating = isThumbsUp ? 'thumbs-up' : 'thumbs-down'; - - if ( rating !== tourRating ) { - isDisabled = true; - setTempRating( rating ); - config.options?.tourRating?.onTourRate?.( rating ); - } - }; - - return ( - <> -

- { __( 'Did you find this guide helpful?', 'jetpack-mu-wpcom' ) } -

-
-
- - ); -}; - -export default WpcomTourKitRating; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx deleted file mode 100644 index e324f0b2a4380..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-navigation.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useI18n } from '@wordpress/react-i18n'; -import WpcomTourKitPaginationControl from './wpcom-tour-kit-pagination-control'; -import type { WpcomTourStepRendererProps } from '../../../types'; - -type Props = Omit< WpcomTourStepRendererProps, 'onMinimize' >; - -const WpcomTourKitStepCardNavigation: React.FunctionComponent< Props > = ( { - currentStepIndex, - onDismiss, - onGoToStep, - onNextStep, - onPreviousStep, - setInitialFocusedElement, - steps, -} ) => { - const { __ } = useI18n(); - const isFirstStep = currentStepIndex === 0; - const lastStepIndex = steps.length - 1; - - return ( - <> - - { isFirstStep ? ( -
- - -
- ) : ( -
- - -
- ) } -
- - ); -}; - -export default WpcomTourKitStepCardNavigation; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx deleted file mode 100644 index bfad1e0204dc7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card-overlay-controls.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Button, Flex } from '@wordpress/components'; -import { close } from '@wordpress/icons'; -import { useI18n } from '@wordpress/react-i18n'; -import minimize from '../icons/minimize'; -import type { TourStepRendererProps } from '../../../types'; - -interface Props { - onMinimize: TourStepRendererProps[ 'onMinimize' ]; - onDismiss: TourStepRendererProps[ 'onDismiss' ]; -} - -const WpcomTourKitStepCardOverlayControls: React.FunctionComponent< Props > = ( { - onMinimize, - onDismiss, -} ) => { - const { __ } = useI18n(); - - return ( -
- - - - -
- ); -}; - -export default WpcomTourKitStepCardOverlayControls; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx deleted file mode 100644 index 743d4f1cce9ac..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step-card.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Button, Card, CardBody, CardFooter, CardMedia } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; -import { Icon } from '@wordpress/icons'; -import { useI18n } from '@wordpress/react-i18n'; -import clsx from 'clsx'; -import WpcomTourKitRating from './wpcom-tour-kit-rating'; -import WpcomTourKitStepCardNavigation from './wpcom-tour-kit-step-card-navigation'; -import WpcomTourKitStepCardOverlayControls from './wpcom-tour-kit-step-card-overlay-controls'; -import type { WpcomTourStepRendererProps } from '../../../types'; - -const WpcomTourKitStepCard: React.FunctionComponent< WpcomTourStepRendererProps > = ( { - steps, - currentStepIndex, - onMinimize, - onDismiss, - onGoToStep, - onNextStep, - onPreviousStep, - setInitialFocusedElement, -} ) => { - const { __ } = useI18n(); - const lastStepIndex = steps.length - 1; - const { descriptions, heading, imgSrc, imgLink } = steps[ currentStepIndex ].meta; - const isLastStep = currentStepIndex === lastStepIndex; - const isMobile = useViewportMatch( 'mobile', '<' ); - const description = descriptions[ isMobile ? 'mobile' : 'desktop' ] ?? descriptions.desktop; - - return ( - - - { imgSrc && ( - - - { imgSrc.mobile && ( - - ) } - { - - { imgLink && ( - - - - - } - size={ 27 } - /> - - ) } - - ) } - -

{ heading }

-

- { description } - { isLastStep ? ( - - ) : null } -

-
- - { isLastStep ? ( - - ) : ( - - ) } - -
- ); -}; - -export default WpcomTourKitStepCard; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx deleted file mode 100644 index 24064230211d2..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit-step.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import WpcomTourKitStepCard from './wpcom-tour-kit-step-card'; -import type { WpcomTourStepRendererProps } from '../../../types'; - -const WpcomTourKitStep: React.FunctionComponent< WpcomTourStepRendererProps > = ( { - steps, - currentStepIndex, - onDismiss, - onNextStep, - onPreviousStep, - onMinimize, - setInitialFocusedElement, - onGoToStep, -} ) => { - return ( - - ); -}; - -export default WpcomTourKitStep; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx deleted file mode 100644 index 863d20ac6745d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/components/wpcom-tour-kit.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import TourKit from '../../../components/tour-kit'; -import usePrefetchTourAssets from '../hooks/use-prefetch-tour-assets'; -import WpcomTourKitMinimized from './wpcom-tour-kit-minimized'; -import WpcomTourKitStep from './wpcom-tour-kit-step'; -import '../styles.scss'; -import type { WpcomConfig, TourStepRenderer } from '../../../types'; - -interface Props { - config: WpcomConfig; -} - -const WpcomTourKit: React.FunctionComponent< Props > = ( { config } ) => { - usePrefetchTourAssets( config.steps ); - - return ( - - ); -}; - -export default WpcomTourKit; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx deleted file mode 100644 index 76db7335bf3eb..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/hooks/use-prefetch-tour-assets.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useEffect } from '@wordpress/element'; -import type { WpcomStep } from '../../../types'; - -/** - * The hook to prefetch the assets of the tour - * - * @param steps - The steps that require assets. - */ -export default function usePrefetchTourAssets( steps: WpcomStep[] ): void { - useEffect( () => { - steps.forEach( step => { - step.meta.imgSrc?.mobile && ( new window.Image().src = step.meta.imgSrc.mobile.src ); - step.meta.imgSrc?.desktop && ( new window.Image().src = step.meta.imgSrc.desktop.src ); - } ); - }, [ steps ] ); -} diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx deleted file mode 100644 index 0d0ee00282e4c..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/maximize.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { SVG, Path } from '@wordpress/primitives'; - -const minimize = ( - - - -); - -export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx deleted file mode 100644 index e844c2cd1fc64..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/minimize.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { SVG, Path } from '@wordpress/primitives'; - -const minimize = ( - - - -); - -export default minimize; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx deleted file mode 100644 index 784e6668db965..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_down.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { SVG, Path } from '@wordpress/primitives'; - -const thumbsDown = ( - - - -); - -export default thumbsDown; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx deleted file mode 100644 index d57b147512eac..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/icons/thumbs_up.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { SVG, Path } from '@wordpress/primitives'; - -const thumbsUp = ( - - - -); - -export default thumbsUp; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts deleted file mode 100644 index f092f4ed3fa54..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './components/wpcom-tour-kit'; -export { default as usePrefetchTourAssets } from './hooks/use-prefetch-tour-assets'; diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss deleted file mode 100644 index 56483b6839201..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/variants/wpcom/styles.scss +++ /dev/null @@ -1,232 +0,0 @@ -@use "sass:math"; -@import "@wordpress/base-styles/colors"; -@import "@wordpress/base-styles/mixins"; -@import "@wordpress/base-styles/variables"; -@import "@wordpress/base-styles/z-index"; - -$wpcom-tour-kit-step-card-overlay-controls-button-bg-color: #32373c; // former $dark-gray-700. TODO: replace with standard color - -.wpcom-tour-kit-minimized { - border-radius: 2px; - box-shadow: - 0 2px 6px rgba(60, 66, 87, 0.08), - 0 0 0 1px rgba(60, 66, 87, 0.16), - 0 1px 1px rgba(0, 0, 0, 0.08); - background-color: $white; - color: $black; - - .components-button { - height: 44px; - - .wpcom-tour-kit-minimized__tour-index { - color: $gray-600; - } - - svg { - color: #50575e; - } - - &:hover { - .wpcom-tour-kit-minimized__tour-index, - svg { - color: inherit; - } - } - } -} - -.wpcom-tour-kit-step-card__heading { - font-size: 1.125rem; /* stylelint-disable-line scales/font-sizes */ - margin: 0.5rem 0; -} - -.wpcom-tour-kit-step-card__description { - font-size: 0.875rem; - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - line-height: 1.5rem; - margin: 0; - - .components-button { - height: auto; - line-height: 1; - text-decoration: underline; - padding: 0 0 0 4px; - } -} - -// @todo clk - update? -.wpcom-tour-kit .tour-kit-frame__container { - box-shadow: none; -} - -.wpcom-tour-kit-step-card { - width: 416px; - max-width: 92vw; - - &.wpcom-tour-kit-step-card.is-elevated { - box-shadow: rgba(0, 0, 0, 0.1) 0 0 0 1px, rgba(0, 0, 0, 0.1) 0 2px 4px 0; - } - - &.components-card { - border: none; - border-radius: 4px; - box-shadow: none; - } - - .components-card__body { - min-height: 114px; - } - - .components-card__body, - .components-card__footer { - border-top: none; - padding: $grid-unit-20 !important; - } - - .components-card__footer { - .wpcom-tour-kit-rating__end-text { - color: $gray-600; - font-size: 0.875rem; - font-style: italic; - } - - .wpcom-tour-kit-rating__end-icon.components-button.has-icon { - background-color: #f6f7f7; - border-radius: 50%; - color: $gray-600; - margin-left: 8px; - - path { - fill: $gray-600; - } - - &.active { - background-color: $black; - opacity: 1; - - path { - fill: $white; - } - } - } - } - - .components-card__media { - height: 0; - padding-top: math.percentage(math.div(math.ceil(math.div(1, 1.53) * 100), 100)); // img width:height ratio (1:1.53) - position: relative; - width: 100%; - - img { - left: 0; - position: absolute; - top: 0; - width: 100%; - } - } - - .components-guide__page-control { - margin: 0; - - .components-button { - min-width: auto; - &.has-icon { - padding: 3px; - } - } - - li { - margin-bottom: 0; - } - } -} - -.wpcom-tour-kit-step-card-overlay-controls__minimize-icon svg { - position: relative; - left: -2px; -} - -.wpcom-tour-kit-step-card-overlay-controls { - left: 0; - padding: $grid-unit-15; - right: 0; - z-index: 1; // z-index is needed because overlay controls are written before components-card__media, and so ends up under the image - - .components-button { - width: 32px; - min-width: 32px; - height: 32px; - background: $wpcom-tour-kit-step-card-overlay-controls-button-bg-color; - transition: opacity 200ms; - opacity: 0.7; - - &:active { - opacity: 0.9; - } - } - - @media (hover: hover) and (pointer: fine) { - // styles only applicable for hoverable viewports with precision pointing devices connected (eg: mouse) - .components-button { - opacity: 0; - } - - .tour-kit-frame__container:hover &, - .tour-kit-frame__container:focus-within & { - .components-button { - opacity: 0.7; - - &:hover, - &:focus { - opacity: 0.9; - } - } - } - } -} - -.wpcom-tour-kit-step-card-navigation__next-btn { - margin-left: $grid-unit-15; - justify-content: center; - min-width: 85px; -} - -.wpcom-tour-kit-step-card__media { - position: relative; -} - -// TODO: Remove once @wordpress/components/src/card/styles/card-styles.js is updated -.wpcom-tour-kit-step-card__media img { - display: block; - height: auto; - max-width: 100%; - width: 100%; -} - -.wpcom-tour-kit-step-card__media-link { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - display: flex; - align-items: center; - justify-content: center; - - svg { - display: none; - transition: transform 0.15s ease-in-out; - - &:hover { - transform: scale(1.05); - } - } - - &--playable { - background-color: rgba(0, 0, 0, 0.5); - - svg { - display: block; - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md deleted file mode 100644 index 4c7e0026936e7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Nux Welcome Tour Modal - -A help tour to show new users some of the basics of using the editor. - -## Testing Instructions - -Instructions for testing the modal and its variants, and for resetting the state of `nux-status` so that the modal is shown again can be found on the PR [#47779](https://github.com/Automattic/wp-calypso/pull/47779) diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php deleted file mode 100644 index 5c9370e9d539d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php +++ /dev/null @@ -1,94 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'block-editor/should-show-first-post-published-modal'; - - add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_script' ), 100 ); - } - - /** - * Enqueue Launchpad options. - */ - public function enqueue_script() { - $launchpad_options = array( - 'launchpadScreenOption' => get_option( 'launchpad_screen' ), - 'siteUrlOption' => get_option( 'siteurl' ), - 'siteIntentOption' => get_option( 'site_intent' ), - ); - - wp_add_inline_script( - 'wpcom-block-editor-nux', - 'var launchpadOptions = ' . wp_json_encode( $launchpad_options, JSON_HEX_TAG | JSON_HEX_AMP ) . ';', - 'before' - ); - } - - /** - * Register available routes. - */ - public function register_rest_route() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::READABLE, - 'callback' => array( $this, 'should_show_first_post_published_modal' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - ) - ); - } - - /** - * Callback to determine whether the request can proceed. - * - * @return boolean - */ - public function permission_callback() { - return current_user_can( 'read' ); - } - - /** - * Should we show the first post published modal - * - * @return \WP_REST_Response - */ - public function should_show_first_post_published_modal() { - // As we has synced the `has_never_published_post` option to part of atomic sites but we cannot - // update the value now, always return false to avoid showing the modal at every publishing until - // we can update the value on atomic sites. See D69932-code. - if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { - return rest_ensure_response( - array( - 'should_show_first_post_published_modal' => false, - ) - ); - } - - $has_never_published_post = (bool) get_option( 'has_never_published_post', false ); - $intent = get_option( 'site_intent', '' ); - $should_show_first_post_published_modal = $has_never_published_post && 'write' === $intent; - - return rest_ensure_response( - array( - 'should_show_first_post_published_modal' => $should_show_first_post_published_modal, - ) - ); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php deleted file mode 100644 index 9f8930d7392dd..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-nux-status-controller.php +++ /dev/null @@ -1,126 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'block-editor/nux'; - } - - /** - * Register available routes. - */ - public function register_rest_route() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_nux_status' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_nux_status' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - ) - ); - } - - /** - * Callback to determine whether the request can proceed. - * - * @return boolean - */ - public function permission_callback() { - return is_user_logged_in(); - } - - /** - * Should we show the wpcom welcome guide (i.e. welcome tour or nux modal) - * - * @param mixed $nux_status Can be "enabled", "dismissed", or undefined. - * @return boolean - */ - public function show_wpcom_welcome_guide( $nux_status ) { - return 'enabled' === $nux_status; - } - - /** - * Return the WPCOM NUX status - * - * This is only called for sites where the user hasn't already dismissed the tour. - * Once the tour has been dismissed, the closed state is saved in local storage (for the current site) - * see src/block-editor-nux.js - * - * @return \WP_REST_Response - */ - public function get_nux_status() { - - $should_open_patterns_panel = (bool) get_option( 'was_created_with_blank_canvas_design' ); - - if ( $should_open_patterns_panel ) { - $variant = 'blank-canvas-tour'; - } else { - $variant = 'tour'; - } - - if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { - $is_p2 = false; - } else { - $blog_id = get_current_blog_id(); - $is_p2 = \WPForTeams\is_wpforteams_site( $blog_id ); - } - - if ( $is_p2 ) { - // disable welcome tour for authoring P2s. - // see: https://github.com/Automattic/wp-calypso/issues/62973. - $nux_status = 'disabled'; - } elseif ( has_filter( 'wpcom_block_editor_nux_get_status' ) ) { - $nux_status = apply_filters( 'wpcom_block_editor_nux_get_status', false ); - } elseif ( ! metadata_exists( 'user', get_current_user_id(), 'wpcom_block_editor_nux_status' ) ) { - $nux_status = 'enabled'; - } else { - $nux_status = get_user_meta( get_current_user_id(), 'wpcom_block_editor_nux_status', true ); - } - - $show_welcome_guide = $this->show_wpcom_welcome_guide( $nux_status ); - - return rest_ensure_response( - array( - 'show_welcome_guide' => $show_welcome_guide, - 'variant' => $variant, - ) - ); - } - - /** - * Update the WPCOM NUX status - * - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response - */ - public function update_nux_status( $request ) { - $params = $request->get_json_params(); - $nux_status = $params['show_welcome_guide'] ? 'enabled' : 'dismissed'; - if ( has_action( 'wpcom_block_editor_nux_update_status' ) ) { - do_action( 'wpcom_block_editor_nux_update_status', $nux_status ); - } - update_user_meta( get_current_user_id(), 'wpcom_block_editor_nux_status', $nux_status ); - return rest_ensure_response( array( 'show_welcome_guide' => $this->show_wpcom_welcome_guide( $nux_status ) ) ); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php deleted file mode 100644 index 6e4045465bd4a..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php +++ /dev/null @@ -1,101 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'block-editor/has-seen-seller-celebration-modal'; - $this->wpcom_is_site_specific_endpoint = true; - $this->wpcom_is_wpcom_only_endpoint = true; - } - - /** - * Register available routes. - */ - public function register_rest_route() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::READABLE, - 'callback' => array( $this, 'has_seen_seller_celebration_modal' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - ) - ); - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'set_has_seen_seller_celebration_modal' ), - 'permission_callback' => array( $this, 'permission_callback' ), - 'args' => array( - 'has_seen_seller_celebration_modal' => array( - 'required' => true, - 'type' => 'boolean', - ), - ), - ), - ) - ); - } - - /** - * Callback to determine whether the request can proceed. - * - * @return boolean - */ - public function permission_callback() { - return current_user_can( 'read' ); - } - - /** - * Whether the user has seen the seller celebration modal - * - * @return \WP_REST_Response - */ - public function has_seen_seller_celebration_modal() { - // See D69932-code and apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php. - if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { - return rest_ensure_response( - array( - 'has_seen_seller_celebration_modal' => false, - ) - ); - } - $has_seen_seller_celebration_modal = (bool) get_option( 'has_seen_seller_celebration_modal', false ); - - return rest_ensure_response( - array( - 'has_seen_seller_celebration_modal' => $has_seen_seller_celebration_modal, - ) - ); - } - - /** - * Update the option for whether the user has seen the seller celebration modal. - * - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response - */ - public function set_has_seen_seller_celebration_modal( $request ) { - $params = $request->get_json_params(); - update_option( 'has_seen_seller_celebration_modal', $params['has_seen_seller_celebration_modal'] ); - return rest_ensure_response( array( 'has_seen_seller_celebration_modal' => $params['has_seen_seller_celebration_modal'] ) ); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php deleted file mode 100644 index 5365109e39b84..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php +++ /dev/null @@ -1,89 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'block-editor/sharing-modal-dismissed'; - - add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_script' ), 100 ); - } - - /** - * Enqueue sharing modal options. - */ - public function enqueue_script() { - $modal_options = array( - 'isDismissed' => $this->get_wpcom_sharing_modal_dismissed(), - ); - - wp_add_inline_script( - 'wpcom-block-editor-nux', - 'var sharingModalOptions = ' . wp_json_encode( $modal_options, JSON_HEX_TAG | JSON_HEX_AMP ) . ';', - 'before' - ); - } - - /** - * Register available routes. - */ - public function register_rest_route() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'set_wpcom_sharing_modal_dismissed' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - ) - ); - } - - /** - * Callback to determine whether the request can proceed. - * - * @return boolean - */ - public function permission_callback() { - return current_user_can( 'read' ); - } - - /** - * Get the sharing modal dismissed status - * - * @return boolean - */ - public function get_wpcom_sharing_modal_dismissed() { - $old_sharing_modal_dismissed = (bool) get_option( 'sharing_modal_dismissed', false ); - if ( $old_sharing_modal_dismissed ) { - return true; - } - return (bool) get_option( 'wpcom_sharing_modal_dismissed', false ); - } - - /** - * Dismiss the sharing modal - * - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response - */ - public function set_wpcom_sharing_modal_dismissed( $request ) { - $params = $request->get_json_params(); - update_option( 'wpcom_sharing_modal_dismissed', $params['wpcom_sharing_modal_dismissed'] ); - return rest_ensure_response( array( 'wpcom_sharing_modal_dismissed' => $this->get_wpcom_sharing_modal_dismissed() ) ); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php deleted file mode 100644 index c74f8dcdf51ed..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php +++ /dev/null @@ -1,101 +0,0 @@ -namespace = 'wpcom/v2'; - $this->rest_base = 'block-editor/has-seen-video-celebration-modal'; - $this->wpcom_is_site_specific_endpoint = true; - $this->wpcom_is_wpcom_only_endpoint = true; - } - - /** - * Register available routes. - */ - public function register_rest_route() { - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::READABLE, - 'callback' => array( $this, 'has_seen_video_celebration_modal' ), - 'permission_callback' => array( $this, 'permission_callback' ), - ), - ) - ); - register_rest_route( - $this->namespace, - $this->rest_base, - array( - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'set_has_seen_video_celebration_modal' ), - 'permission_callback' => array( $this, 'permission_callback' ), - 'args' => array( - 'has_seen_video_celebration_modal' => array( - 'required' => true, - 'type' => 'boolean', - ), - ), - ), - ) - ); - } - - /** - * Callback to determine whether the request can proceed. - * - * @return boolean - */ - public function permission_callback() { - return current_user_can( 'read' ); - } - - /** - * Whether the site has displayed the video upload celebration modal. - * - * @return \WP_REST_Response - */ - public function has_seen_video_celebration_modal() { - // See D69932-code and apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php. - if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) { - return rest_ensure_response( - array( - 'has_seen_video_celebration_modal' => true, - ) - ); - } - $has_seen_video_celebration_modal = (bool) get_option( 'has_seen_video_celebration_modal', false ); - - return rest_ensure_response( - array( - 'has_seen_video_celebration_modal' => $has_seen_video_celebration_modal, - ) - ); - } - - /** - * Update the option for whether the user has seen the video upload celebration modal. - * - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response - */ - public function set_has_seen_video_celebration_modal( $request ) { - $params = $request->get_json_params(); - update_option( 'has_seen_video_celebration_modal', $params['has_seen_video_celebration_modal'] ); - return rest_ensure_response( array( 'has_seen_video_celebration_modal' => $params['has_seen_video_celebration_modal'] ) ); - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php deleted file mode 100644 index d720a6a32b52e..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php +++ /dev/null @@ -1,78 +0,0 @@ -register_rest_route(); - - require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-first-post-published-modal-controller.php'; - $first_post_published_modal_controller = new WP_REST_WPCOM_Block_Editor_First_Post_Published_Modal_Controller(); - $first_post_published_modal_controller->register_rest_route(); - - require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-seller-celebration-modal-controller.php'; - $seller_celebration_modal_controller = new WP_REST_WPCOM_Block_Editor_Seller_Celebration_Modal_Controller(); - $seller_celebration_modal_controller->register_rest_route(); - - require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-video-celebration-modal-controller.php'; - $video_celebration_modal_controller = new WP_REST_WPCOM_Block_Editor_Video_Celebration_Modal_Controller(); - $video_celebration_modal_controller->register_rest_route(); - - require_once __DIR__ . '/class-wp-rest-wpcom-block-editor-sharing-modal-controller.php'; - $sharing_modal_controller = new WP_REST_WPCOM_Block_Editor_Sharing_Modal_Controller(); - $sharing_modal_controller->register_rest_route(); - } -} -add_action( 'init', array( __NAMESPACE__ . '\WPCOM_Block_Editor_NUX', 'init' ) ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js deleted file mode 100644 index 67979daf701d1..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import { register } from './src/store'; - -import './src/disable-core-nux'; -import './src/block-editor-nux'; - -register(); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js deleted file mode 100644 index 1749ed15908da..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/block-editor-nux.js +++ /dev/null @@ -1,137 +0,0 @@ -import { LocaleProvider } from '@automattic/i18n-utils'; -import { Guide, GuidePage } from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect, useState } from '@wordpress/element'; -import { applyFilters } from '@wordpress/hooks'; -import { registerPlugin } from '@wordpress/plugins'; -import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; -import { getQueryArg } from '@wordpress/url'; -import { - HasSeenSellerCelebrationModalProvider, - HasSeenVideoCelebrationModalProvider, - ShouldShowFirstPostPublishedModalProvider, -} from '../../../common/tour-kit'; -import { BloggingPromptsModal } from './blogging-prompts-modal'; -import DraftPostModal from './draft-post-modal'; -import FirstPostPublishedModal from './first-post-published-modal'; -import PurchaseNotice from './purchase-notice'; -import SellerCelebrationModal from './seller-celebration-modal'; -import PostPublishedSharingModal from './sharing-modal'; -import { DEFAULT_VARIANT, BLANK_CANVAS_VARIANT } from './store'; -import VideoPressCelebrationModal from './video-celebration-modal'; -import WpcomNux from './welcome-modal/wpcom-nux'; -import LaunchWpcomWelcomeTour from './welcome-tour/tour-launch'; - -/** - * Sometimes Gutenberg doesn't allow you to re-register the module and throws an error. - * FIXME: The new version allow it by default, but we might need to ensure that all the site has the new version. - * @see https://github.com/Automattic/wp-calypso/pull/79663 - */ -let unlock; -try { - unlock = __dangerousOptInToUnstableAPIsOnlyForCoreModules( - 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.', - '@wordpress/edit-site' - ).unlock; -} catch ( error ) { - // eslint-disable-next-line no-console - console.error( 'Error: Unable to get the unlock api. Reason: %s', error ); -} - -/** - * The WelcomeTour component - */ -function WelcomeTour() { - const [ showDraftPostModal ] = useState( - getQueryArg( window.location.href, 'showDraftPostModal' ) - ); - - const { - show, - isLoaded, - variant, - isManuallyOpened, - isNewPageLayoutModalOpen, - siteEditorCanvasMode, - } = useSelect( select => { - const welcomeGuideStoreSelect = select( 'automattic/wpcom-welcome-guide' ); - const starterPageLayoutsStoreSelect = select( 'automattic/starter-page-layouts' ); - let canvasMode; - if ( unlock && select( 'core/edit-site' ) ) { - canvasMode = - select( 'core/edit-site' ) && unlock( select( 'core/edit-site' ) ).getCanvasMode(); - } - - return { - show: welcomeGuideStoreSelect.isWelcomeGuideShown(), - isLoaded: welcomeGuideStoreSelect.isWelcomeGuideStatusLoaded(), - variant: welcomeGuideStoreSelect.getWelcomeGuideVariant(), - isManuallyOpened: welcomeGuideStoreSelect.isWelcomeGuideManuallyOpened(), - isNewPageLayoutModalOpen: starterPageLayoutsStoreSelect?.isOpen(), // Handle the case where SPT is not initalized. - siteEditorCanvasMode: canvasMode, - }; - }, [] ); - - const setOpenState = useDispatch( 'automattic/starter-page-layouts' )?.setOpenState; - - const { fetchWelcomeGuideStatus } = useDispatch( 'automattic/wpcom-welcome-guide' ); - - // On mount check if the WPCOM welcome guide status exists in state (from local storage), otherwise fetch it from the API. - useEffect( () => { - if ( ! isLoaded ) { - fetchWelcomeGuideStatus(); - } - }, [ fetchWelcomeGuideStatus, isLoaded ] ); - - const filteredShow = applyFilters( 'a8c.WpcomBlockEditorWelcomeTour.show', show ); - - if ( ! filteredShow || isNewPageLayoutModalOpen ) { - return null; - } - - // Hide the Welcome Tour when not in the edit mode. Note that canvas mode is available only in the site editor - if ( siteEditorCanvasMode && siteEditorCanvasMode !== 'edit' ) { - return null; - } - - // Open patterns panel before Welcome Tour if necessary (e.g. when using Blank Canvas theme) - // Do this only when Welcome Tour is not manually opened. - // NOTE: at the moment, 'starter-page-templates' assets are not loaded on /site-editor/ page so 'setOpenState' may be undefined - if ( variant === BLANK_CANVAS_VARIANT && ! isManuallyOpened && setOpenState ) { - setOpenState( 'OPEN_FOR_BLANK_CANVAS' ); - return null; - } - - if ( variant === DEFAULT_VARIANT ) { - return ( - - { showDraftPostModal ? : } - - ); - } - - // This case is redundant now and it will be cleaned up in a follow-up PR - if ( variant === 'modal' && Guide && GuidePage ) { - return ; - } - - return null; -} - -registerPlugin( 'wpcom-block-editor-nux', { - render: () => ( - - - - - - - - - - - - - - ), -} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js deleted file mode 100644 index 1b968dbc81d19..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/icons.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Path, SVG } from '@wordpress/components'; - -export const ArrowRightIcon = () => ( - - - -); - -export const ArrowLeftIcon = () => ( - - - -); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js deleted file mode 100644 index 74ff6b5be19c2..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/index.js +++ /dev/null @@ -1,106 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; -import { createBlock } from '@wordpress/blocks'; -import { Button, Modal } from '@wordpress/components'; -import { dispatch, select } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -import { addQueryArgs, getQueryArg } from '@wordpress/url'; -import moment from 'moment'; -import { useEffect, useState } from 'react'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import { ArrowLeftIcon, ArrowRightIcon } from './icons'; - -import './style.scss'; - -export const BloggingPromptsModalInner = () => { - const [ isOpen, setIsOpen ] = useState( true ); - const [ prompts, setPrompts ] = useState( [] ); - const [ promptIndex, setPromptIndex ] = useState( 0 ); - - useEffect( () => { - const path = addQueryArgs( `/wpcom/v3/blogging-prompts`, { - per_page: 10, - after: moment().format( '--MM-DD' ), - order: 'desc', - force_year: new Date().getFullYear(), - } ); - apiFetch( { - path, - } ) - .then( result => { - wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_viewed' ); - return setPrompts( result ); - } ) - // eslint-disable-next-line no-console - .catch( () => console.error( 'Unable to fetch writing prompts' ) ); - }, [] ); - - if ( ! isOpen || ! prompts.length ) { - return null; - } - - const selectPrompt = () => { - const promptId = prompts[ promptIndex ]?.id; - dispatch( 'core/editor' ).resetEditorBlocks( [ - createBlock( 'jetpack/blogging-prompt', { promptId } ), - ] ); - wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_prompt_selected', { - prompt_id: promptId, - } ); - setIsOpen( false ); - }; - - const closeModal = () => { - wpcomTrackEvent( 'calypso_editor_writing_prompts_modal_closed' ); - setIsOpen( false ); - }; - - return ( - -
-
- -

{ prompts[ promptIndex ]?.text }

- -
- -
-
- ); -}; - -export const BloggingPromptsModal = () => { - const hasQueryArg = getQueryArg( window.location.href, 'new_prompt' ); - const editorType = select( 'core/editor' ).getCurrentPostType(); - - const shouldOpen = hasQueryArg && editorType === 'post'; - - if ( ! shouldOpen ) { - return null; - } - return ; -}; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss deleted file mode 100644 index 67969ac4d706b..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/blogging-prompts-modal/style.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import "@automattic/typography/styles/variables"; -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.blogging-prompts-modal { - @include break-small { - width: 80%; - } - @include break-large { - width: 60%; - } - max-width: 800px; - margin: auto; - - .components-modal__header-heading { - font-size: $font-body; - font-weight: 400; - } -} - -.blogging-prompts-modal__prompt { - display: flex; - flex-direction: column; - align-items: flex-end; - - .blogging-prompts-modal__prompt-navigation { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 24px; - gap: 24px; - width: 100%; - } - - .blogging-prompts-modal__prompt-navigation-button { - border-radius: 50%; - width: 44px; - height: 44px; - &.components-button:hover:not(:disabled,[aria-disabled="true"]) { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)); - } - } - - .blogging-prompts-modal__prompt-text { - font-size: 1.25rem; - font-weight: 500; - line-height: 26px; - text-align: left; - width: 100%; - text-wrap: pretty; - } -} - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js deleted file mode 100644 index 6591d4e0b5c2d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/disable-core-nux.js +++ /dev/null @@ -1,43 +0,0 @@ -import { select, dispatch, subscribe } from '@wordpress/data'; - -import '@wordpress/nux'; //ensure nux store loads - -// Disable nux and welcome guide features from core. -const unsubscribe = subscribe( () => { - dispatch( 'core/nux' ).disableTips(); - if ( select( 'core/edit-post' )?.isFeatureActive( 'welcomeGuide' ) ) { - dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); - unsubscribe(); - } - if ( select( 'core/edit-site' )?.isFeatureActive( 'welcomeGuide' ) ) { - dispatch( 'core/edit-site' ).toggleFeature( 'welcomeGuide' ); - unsubscribe(); - } -} ); - -// Listen for these features being triggered to call dotcom welcome guide instead. -// Note migration of areTipsEnabled: https://github.com/WordPress/gutenberg/blob/5c3a32dabe4393c45f7fe6ac5e4d78aebd5ee274/packages/data/src/plugins/persistence/index.js#L269 -subscribe( () => { - if ( select( 'core/nux' ).areTipsEnabled() ) { - dispatch( 'core/nux' ).disableTips(); - dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true ); - } - if ( select( 'core/edit-post' )?.isFeatureActive( 'welcomeGuide' ) ) { - dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); - // On mounting, the welcomeGuide feature is turned on by default. This opens the welcome guide despite `welcomeGuideStatus` value. - // This check ensures that we only listen to `welcomeGuide` changes if the welcomeGuideStatus value is loaded and respected - if ( select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideStatusLoaded() ) { - dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true, { - openedManually: true, - } ); - } - } - if ( select( 'core/edit-site' )?.isFeatureActive( 'welcomeGuide' ) ) { - dispatch( 'core/edit-site' ).toggleFeature( 'welcomeGuide' ); - if ( select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideStatusLoaded() ) { - dispatch( 'automattic/wpcom-welcome-guide' ).setShowWelcomeGuide( true, { - openedManually: true, - } ); - } - } -} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg deleted file mode 100644 index cf24d89ced98a..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/images/draft-post.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js deleted file mode 100644 index ada982e317304..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useState } from '@wordpress/element'; -import { doAction, hasAction } from '@wordpress/hooks'; -import { __ } from '@wordpress/i18n'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import NuxModal from '../nux-modal'; -import draftPostImage from './images/draft-post.svg'; -import './style.scss'; - -const CLOSE_EDITOR_ACTION = 'a8c.wpcom-block-editor.closeEditor'; - -const DraftPostModal = () => { - const homeUrl = `/home/${ window.location.hostname }`; - const [ isOpen, setIsOpen ] = useState( true ); - const closeModal = () => setIsOpen( false ); - const closeEditor = () => { - if ( hasAction( CLOSE_EDITOR_ACTION ) ) { - doAction( CLOSE_EDITOR_ACTION, homeUrl ); - } else { - window.location.href = `https://wordpress.com${ homeUrl }`; - } - }; - - return ( - - - - - } - onRequestClose={ closeModal } - onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_draft_post_modal_show' ) } - /> - ); -}; - -export default DraftPostModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss deleted file mode 100644 index 2317634ed12b7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/draft-post-modal/style.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-block-editor-draft-post-modal { - .components-modal__content { - @include break-small { - padding: 48px 128px; - } - } - - .wpcom-block-editor-nux-modal__image-container { - img { - width: 209px; - height: 95px; - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg deleted file mode 100644 index 3b55263b209af..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/images/post-published.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx deleted file mode 100644 index 3d27771c6d50a..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/index.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { useEffect, useRef, useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { isURL } from '@wordpress/url'; -import React from 'react'; -import { useSiteIntent, useShouldShowFirstPostPublishedModal } from '../../../../common/tour-kit'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import NuxModal from '../nux-modal'; -import postPublishedImage from './images/post-published.svg'; - -import './style.scss'; - -type CoreEditorPlaceholder = { - getCurrentPost: ( ...args: unknown[] ) => { link: string }; - getCurrentPostType: ( ...args: unknown[] ) => string; - isCurrentPostPublished: ( ...args: unknown[] ) => boolean; -}; - -/** - * Show the first post publish modal - */ -const FirstPostPublishedModalInner: React.FC = () => { - const { link } = useSelect( - select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPost(), - [] - ); - const postType = useSelect( - select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPostType(), - [] - ); - - const isCurrentPostPublished = useSelect( - select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).isCurrentPostPublished(), - [] - ); - const previousIsCurrentPostPublished = useRef( isCurrentPostPublished ); - const shouldShowFirstPostPublishedModal = useShouldShowFirstPostPublishedModal(); - const [ isOpen, setIsOpen ] = useState( false ); - const closeModal = () => setIsOpen( false ); - - const { siteUrlOption, launchpadScreenOption, siteIntentOption } = window?.launchpadOptions || {}; - - let siteUrl = ''; - if ( isURL( siteUrlOption ) ) { - // https://mysite.wordpress.com/path becomes mysite.wordpress.com - siteUrl = new URL( siteUrlOption ).hostname; - } - - useEffect( () => { - // If the user is set to see the first post modal and current post status changes to publish, - // open the post publish modal - if ( - shouldShowFirstPostPublishedModal && - ! previousIsCurrentPostPublished.current && - isCurrentPostPublished && - postType === 'post' - ) { - previousIsCurrentPostPublished.current = isCurrentPostPublished; - - // When the post published panel shows, it is focused automatically. - // Thus, we need to delay open the modal so that the modal would not be close immediately - // because the outside of modal is focused - window.setTimeout( () => { - setIsOpen( true ); - } ); - } - }, [ postType, shouldShowFirstPostPublishedModal, isCurrentPostPublished ] ); - - const handleViewPostClick = ( event: React.MouseEvent ) => { - event.preventDefault(); - ( window.top as Window ).location.href = link; - }; - - const handleNextStepsClick = ( event: React.MouseEvent ) => { - event.preventDefault(); - ( - window.top as Window - ).location.href = `https://wordpress.com/setup/write/launchpad?siteSlug=${ siteUrl }`; - }; - return ( - - - { launchpadScreenOption === 'full' && siteIntentOption === 'write' && ( - - ) } - - } - onRequestClose={ closeModal } - onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_first_post_published_modal_show' ) } - /> - ); -}; - -const FirstPostPublishedModal = () => { - const { siteIntent: intent } = useSiteIntent(); - if ( intent === 'write' ) { - return ; - } - return null; -}; - -export default FirstPostPublishedModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss deleted file mode 100644 index ebbe84e61dfc8..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/first-post-published-modal/style.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-block-editor-post-published-modal { - .components-modal__content { - @include break-small { - padding: 48px 90px; - } - } - - .wpcom-block-editor-nux-modal__image-container { - img { - width: 158px; - height: 85px; - } - } - - .wpcom-block-editor-nux-modal__buttons { - .components-button { - min-width: 113px; - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx deleted file mode 100644 index e46656c0ce2e3..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Modal } from '@wordpress/components'; -import { useEffect, useRef } from '@wordpress/element'; -import clsx from 'clsx'; -import React from 'react'; -import './style.scss'; - -interface Props { - isOpen: boolean; - className?: string; - title: string; - description: string; - imageSrc: string; - actionButtons: React.ReactElement; - onRequestClose: () => void; - onOpen?: () => void; -} - -const NuxModal: React.FC< Props > = ( { - isOpen, - className, - title, - description, - imageSrc, - actionButtons, - onRequestClose, - onOpen, -} ) => { - const prevIsOpen = useRef< boolean | null >( null ); - - useEffect( () => { - if ( ! prevIsOpen.current && isOpen ) { - onOpen?.(); - } - - prevIsOpen.current = isOpen; - }, [ prevIsOpen, isOpen, onOpen ] ); - - if ( ! isOpen ) { - return null; - } - - return ( - -
- { -
-

{ title }

-

{ description }

-
{ actionButtons }
-
- ); -}; - -export default NuxModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss deleted file mode 100644 index ee7ee1b10caef..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/nux-modal/style.scss +++ /dev/null @@ -1,96 +0,0 @@ -@import "@automattic/typography/styles/variables"; -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-block-editor-nux-modal { - .components-modal__header { - height: auto; - padding: 10px; - border-bottom: 0; - - // Fix styles when Gutenberg is deactivated - position: absolute; - left: 0; - right: 0; - margin: 0; - background-color: transparent; - - button { - left: unset; - - svg path { - transform: scale(1.4); - transform-origin: center; - } - } - } - - .components-modal__content { - padding: 84px 20px 20px; - margin-top: 0; - - &::before { - margin: 0; - } - - @include break-mobile { - text-align: center; - } - } - - .wpcom-block-editor-nux-modal__image-container { - display: flex; - justify-content: center; - } - - .wpcom-block-editor-nux-modal__title { - margin: 34px 0 0; - font-size: $font-headline-small; - font-weight: 500; - line-height: 1.2; - - @include break-mobile { - margin-top: 24px; - } - } - - .wpcom-block-editor-nux-modal__description { - max-width: 352px; - margin: 16px 0 0; - font-size: $font-body; - - @include break-mobile { - margin: 20px auto 0; - font-size: $font-body-large; - } - } - - .wpcom-block-editor-nux-modal__buttons { - display: flex; - flex-direction: column; - justify-content: center; - margin-top: 24px; - - .components-button { - min-width: 130px; - height: 40px; - justify-content: center; - border-radius: 3px; - font-size: $font-body-small; - } - - .components-button + .components-button { - margin-top: 12px; - } - - @include break-mobile { - flex-direction: row; - margin-top: 28px; - - .components-button + .components-button { - margin-top: 0; - margin-left: 16px; - } - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx deleted file mode 100644 index b6ffafa4ab99d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/index.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -import { store as noticesStore } from '@wordpress/notices'; -import { useEffect, useRef } from 'react'; -import './style.scss'; - -/** - * Display the purchase notice snackbar - */ -function PurchaseNotice() { - const hasPaymentNotice = useRef( false ); - const { createNotice } = useDispatch( noticesStore ); - - useEffect( () => { - const noticePattern = /[&?]notice=([\w_-]+)/; - const match = noticePattern.exec( document.location.search ); - const notice = match && match[ 1 ]; - if ( 'purchase-success' === notice && hasPaymentNotice.current === false ) { - hasPaymentNotice.current = true; - createNotice( - 'info', - __( 'Congrats! Premium blocks are now available to use.', 'jetpack-mu-wpcom' ), - { - isDismissible: true, - type: 'snackbar', - } - ); - } - }, [ createNotice ] ); - - return null; -} - -export default PurchaseNotice; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss deleted file mode 100644 index f9802fcb34e2b..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/purchase-notice/style.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-block-editor-purchase-notice { - position: fixed; - overflow: visible; - bottom: 0; - left: 0; - width: 100%; - z-index: 10000; - justify-content: center; - display: flex; - pointer-events: none; - padding-bottom: 1rem; -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg deleted file mode 100644 index 258f49c3780c7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/images/product-published.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx deleted file mode 100644 index 070eea11fb0e2..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/index.jsx +++ /dev/null @@ -1,139 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; -import { useState, useRef, useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { - useSiteIntent, - useShouldShowSellerCelebrationModal, - useHasSeenSellerCelebrationModal, -} from '../../../../common/tour-kit'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import NuxModal from '../nux-modal'; -import contentSubmittedImage from './images/product-published.svg'; -import './style.scss'; - -/** - * Show the seller celebration modal - */ -const SellerCelebrationModalInner = () => { - const { addEntities } = useDispatch( 'core' ); - - useEffect( () => { - // @TODO - not sure if I actually need this; need to test with it removed. - // Teach core data about the status entity so we can use selectors like `getEntityRecords()` - addEntities( [ - { - baseURL: '/wp/v2/statuses', - key: 'slug', - kind: 'root', - name: 'status', - plural: 'statuses', - }, - ] ); - // Only register entity once - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); - // conditions to show: - // - user just finished saving (check) - // - editor has not yet displayed modal once (check) - // - user is a seller (check) - // - user has not saved site before - // - content includes product block, and a user has selected it at least once (check) - const [ isModalOpen, setIsModalOpen ] = useState( false ); - const [ hasDisplayedModal, setHasDisplayedModal ] = useState( false ); - - const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ) ); - const previousIsEditorSaving = useRef( false ); - - const { updateHasSeenSellerCelebrationModal } = useHasSeenSellerCelebrationModal(); - - const linkUrl = useSelect( select => { - if ( isSiteEditor ) { - const page = select( 'core/edit-site' ).getPage(); - const pageId = parseInt( page?.context?.postId ); - const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); - return pageEntity?.link; - } - const currentPost = select( 'core/editor' ).getCurrentPost(); - return currentPost.link; - } ); - - const shouldShowSellerCelebrationModal = useShouldShowSellerCelebrationModal(); - - const isEditorSaving = useSelect( select => { - if ( isSiteEditor ) { - const page = select( 'core/edit-site' ).getPage(); - const pageId = parseInt( page?.context?.postId ); - const isSavingSite = - select( 'core' ).isSavingEntityRecord( 'root', 'site' ) && - ! select( 'core' ).isAutosavingEntityRecord( 'root', 'site' ); - const isSavingEntity = - select( 'core' ).isSavingEntityRecord( 'postType', 'page', pageId ) && - ! select( 'core' ).isAutosavingEntityRecord( 'postType', 'page', pageId ); - - return isSavingSite || isSavingEntity; - } - const currentPost = select( 'core/editor' ).getCurrentPost(); - const isSavingEntity = - select( 'core' ).isSavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ) && - ! select( 'core' ).isAutosavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ); - return isSavingEntity; - } ); - - useEffect( () => { - if ( - ! isEditorSaving && - previousIsEditorSaving.current && - ! hasDisplayedModal && - shouldShowSellerCelebrationModal - ) { - setIsModalOpen( true ); - setHasDisplayedModal( true ); - updateHasSeenSellerCelebrationModal( true ); - } - previousIsEditorSaving.current = isEditorSaving; - }, [ - isEditorSaving, - hasDisplayedModal, - shouldShowSellerCelebrationModal, - updateHasSeenSellerCelebrationModal, - ] ); - - // if save state has changed and was saving on last render - // then it has finished saving - // open modal if content has sell block, - - const closeModal = () => setIsModalOpen( false ); - return ( - - - - - } - onRequestClose={ closeModal } - onOpen={ () => wpcomTrackEvent( 'calypso_editor_wpcom_seller_celebration_modal_show' ) } - /> - ); -}; - -const SellerCelebrationModal = () => { - const { siteIntent: intent } = useSiteIntent(); - if ( intent === 'sell' ) { - return ; - } - return null; -}; - -export default SellerCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss deleted file mode 100644 index c1576d9f74db5..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/seller-celebration-modal/style.scss +++ /dev/null @@ -1,27 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-site-editor-seller-celebration-modal { - .components-modal__content { - @include break-small { - padding: 48px 90px; - } - } - - .wpcom-block-editor-nux-modal__image-container { - img { - width: 158px; - height: 85px; - } - } - - .wpcom-block-editor-nux-modal__buttons { - .components-button { - min-width: 113px; - &:not(.is-primary) { - border: 1px solid #c3c4c7; - border-radius: 4px; - } - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx deleted file mode 100644 index bd077bf7625cc..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/clipboard-button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Button } from '@wordpress/components'; -import clsx from 'clsx'; -import { forwardRef } from 'react'; - -interface ClipboardButtonProps { - className?: string; - compact?: boolean; - disabled?: boolean; - primary?: boolean; - scary?: boolean; - busy?: boolean; - borderless?: boolean; - plain?: boolean; - transparent?: boolean; - text: string | null; - onCopy?: () => void; - onMouseLeave?: () => void; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -const noop = () => {}; - -const ClipboardButton = forwardRef< - HTMLButtonElement, - React.PropsWithChildren< ClipboardButtonProps > ->( ( { className, text, onCopy = noop, ...rest }, ref ) => { - /** - * The copy handler - */ - function onCopyHandler() { - if ( text ) { - navigator.clipboard.writeText( text ); - onCopy(); - } - } - - return ( - - - - - -
- - { - updateIsDismissed( ! isDismissed ); - } } - /> - { __( "Don't show again", 'jetpack-mu-wpcom' ) } - -
-
-
- { shouldShowSuggestedTags ? ( - - ) : ( - { - ) } -
-
- - ); -}; - -const SharingModal = () => { - const { siteIntent: intent } = useSiteIntent(); - if ( intent === START_WRITING_FLOW || intent === DESIGN_FIRST_FLOW ) { - return null; - } - return ; -}; -export default SharingModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx deleted file mode 100644 index b3d72b5eae882..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logo.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import clsx from 'clsx'; -import * as React from 'react'; -import { Assign } from 'utility-types'; - -interface Props { - icon: string; - size?: number; -} - -/** - * InlineSocialLogo is a copy of client/components/social-logo that references an inline SVG sprite. - * This componenet is needed because: - * - * The XML element does not work with SVGs loaded from external domains. - * In the editor, images are loaded from the CDN (s0.wp.com) in production. - * useInline allows us to reference an svg sprite from the current page instead. - * see https://github.com/w3c/svgwg/issues/707 - * - * InlineSocialLogosSprite must be included on the page where this is used - * @param props - The props of the component, - * @returns A Social Logo SVG - */ -function InlineSocialLogo( props: Assign< React.SVGProps< SVGSVGElement >, Props > ) { - const { size = 24, icon, className, ...otherProps } = props; - - // Using a missing icon doesn't produce any errors, just a blank icon, which is the exact intended behaviour. - // This means we don't need to perform any checks on the icon name. - const iconName = `social-logo-${ icon }`; - // The current CSS expects individual icon classes in the form of e.g. `.twitter`, but the social-logos build - // appears to generate them in the form of `.social-logo-twitter` instead. - // We add both here, to ensure compatibility. - const iconClass = clsx( 'social-logo', iconName, icon, className ); - - return ( - - - - ); -} - -export default React.memo( InlineSocialLogo ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx deleted file mode 100644 index c0c5e9388cdad..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/inline-social-logos-sprite.tsx +++ /dev/null @@ -1,286 +0,0 @@ -/** - * A hidden inline svg sprite of social logos. - * - * Sprite was coppied from https://wordpress.com/calypso/images/social-logos-d55401f99bb02ebd6cf4.svg - * @returns see above. - */ -const InlineSocialLogosSprite = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; -export default InlineSocialLogosSprite; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss deleted file mode 100644 index c7738fc6fc0d5..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/style.scss +++ /dev/null @@ -1,173 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-block-editor-post-published-sharing-modal { - .components-modal__content { - margin-top: 0; - padding-bottom: 0; - .wpcom-block-editor-post-published-sharing-modal__inner { - display: flex; - .wpcom-block-editor-post-published-sharing-modal__left { - width: 50%; - padding: 52px 40px 52px 20px; - - p a { - color: var(--color-text); - text-decoration: underline; - white-space: nowrap; - - &:hover { - color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); - text-decoration: none; - } - - .components-external-link__icon { - margin-left: 4px; - } - } - - .wpcom-block-editor-post-published-buttons { - display: flex; - } - } - .wpcom-block-editor-post-published-sharing-modal__right { - width: 50%; - padding: 52px 20px 52px 40px; - border-left: 1px solid var(--studio-gray-5); - display: flex; - justify-content: center; - min-width: 300px; - } - - @media only screen and (max-width: 600px) { - flex-direction: column-reverse; - - .wpcom-block-editor-post-published-sharing-modal__left { - padding: 44px 0 20px; - width: 100%; - } - .wpcom-block-editor-post-published-sharing-modal__right { - border-left: none; - padding: 52px 0 0; - width: 100%; - } - .wpcom-block-editor-post-published-sharing-modal__image { - height: 140px; - } - } - } - h1 { - margin-top: 0; - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 26px; - @media only screen and (max-width: 600px) { - font-size: 2.25rem; - font-weight: 500; - line-height: 1; - } - } - p { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 18px; - @media only screen and (max-width: 600px) { - font-size: 1rem; - } - } - .link-button { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 14px; - font-weight: 500; - display: inline-flex; - border: none; - background: transparent; - color: var(--color-text); - padding: 0 14px 0 0; - line-height: 2.71428571; - min-height: 40px; - margin-bottom: 4px; - align-items: center; - text-decoration: underline; - white-space: nowrap; - - &:hover { - background: transparent; - color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba)); - text-decoration: none; - } - - svg { - fill: currentColor; - } - } - hr { - margin-top: 20px; - border-top: 1px solid var(--studio-gray-5); - border-bottom: none; - } - h2 { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 18px; - font-weight: 600px; - } - .wpcom-block-editor-post-published-sharing-modal__checkbox-section { - margin-top: 40px; - color: var(--studio-gray-60); - } - .form-checkbox { - margin-top: 1px; - height: 1rem; - width: 1rem; - &::before { - width: 1.3125rem; - height: 1.3125rem; - } - } - .wpcom-block-editor-post-published-sharing-modal__suggest-tags { - width: 250px; - flex: fit-content; - } - } -} - -.wpcom-block-editor-post-published-buttons .link-button { - gap: 4px; - padding-left: 0; -} - -.wpcom-block-editor-post-published-sharing-modal__sharing-button { - display: inline-block; - position: relative; - width: 32px; - height: 32px; - top: -2px; - margin: 8px 8px 0 0; - border-radius: 50%; - color: var(--color-neutral-80); - background: var(--studio-white); - padding: 7px; - - &:hover { - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22), 0 0 0 1px rgba(0, 0, 0, 0.22); - } - - .social-logo { - top: 0; - color: var(--color-text-inverted); - } -} - -@mixin sharing-button-service( $name, $color ) { - .wpcom-block-editor-post-published-sharing-modal__sharing-button.share-#{ $name } { - background: $color; - - &:hover { - background: $color; - } - } -} - -@include sharing-button-service( "facebook", var( --color-facebook ) ); -@include sharing-button-service( "twitter", var( --color-twitter ) ); -@include sharing-button-service( "linkedin", var( --color-linkedin ) ); -@include sharing-button-service( "tumblr", var( --color-tumblr ) ); -@include sharing-button-service( "pinterest", var( --color-pinterest ) ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx deleted file mode 100644 index 8f47b85adb592..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/suggested-tags.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useLocale } from '@automattic/i18n-utils'; -import { Button, FormTokenField } from '@wordpress/components'; -import { TokenItem } from '@wordpress/components/build-types/form-token-field/types'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import { store as noticesStore } from '@wordpress/notices'; -import { useI18n } from '@wordpress/react-i18n'; -import * as React from 'react'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import useAddTagsToPost from './use-add-tags-to-post'; - -type PostMeta = { - reader_suggested_tags: string; -}; - -type CoreEditorPlaceholder = { - getCurrentPost: ( ...args: unknown[] ) => { - id: number; - meta: PostMeta; - }; -}; - -type SuggestedTagsEventProps = { - number_of_original_suggested_tags: number; - number_of_selected_tags: number; - number_of_suggested_tags_selected: number; - number_of_added_tags: number; -}; - -type SuggestedTagsProps = { - setShouldShowSuggestedTags: ( shouldShow: boolean ) => void; -}; - -/** - * Display the suggested tags. - * - * @param props - The props of the component. - */ -function SuggestedTags( props: SuggestedTagsProps ) { - const { __, _n } = useI18n(); - const localeSlug = useLocale(); - const { id: postId, meta: postMeta } = useSelect( - select => ( select( 'core/editor' ) as CoreEditorPlaceholder ).getCurrentPost(), - [] - ); - const { createNotice } = useDispatch( noticesStore ); - const origSuggestedTags = postMeta?.reader_suggested_tags - ? JSON.parse( postMeta.reader_suggested_tags ) - : []; - const [ selectedTags, setSelectedTags ] = React.useState( origSuggestedTags ); - const onAddTagsButtonClick = ( numAddedTags: number ) => { - // Compare origSuggestedTags and selectedTags and determine the number of tags that are different - const numSuggestedTags = origSuggestedTags.length; - const numSelectedTags = selectedTags.length; - const numSameTags = origSuggestedTags.filter( ( tag: string ) => - selectedTags.includes( tag ) - ).length; - const eventProps: SuggestedTagsEventProps = { - number_of_original_suggested_tags: numSuggestedTags, - number_of_selected_tags: numSelectedTags, - number_of_suggested_tags_selected: numSameTags, - number_of_added_tags: numAddedTags, - }; - wpcomTrackEvent( 'calypso_reader_post_publish_add_tags', eventProps ); - if ( numAddedTags > 0 ) { - createNotice( - 'success', - _n( 'Tag Added.', 'Tags Added.', numAddedTags, 'jetpack-mu-wpcom' ), - { - type: 'snackbar', - } - ); - } else { - createNotice( 'warning', __( 'No Tags Added.', 'jetpack-mu-wpcom' ), { - type: 'snackbar', - } ); - } - props.setShouldShowSuggestedTags( false ); - }; - const { saveTags } = useAddTagsToPost( postId, selectedTags, onAddTagsButtonClick ); - - useEffect( () => { - if ( origSuggestedTags?.length === 0 ) { - // Check if localeSlug begins with 'en' - if ( localeSlug && localeSlug.startsWith( 'en' ) ) { - wpcomTrackEvent( 'calypso_reader_post_publish_no_suggested_tags' ); - } - props.setShouldShowSuggestedTags( false ); - } else { - wpcomTrackEvent( 'calypso_reader_post_publish_show_suggested_tags', { - number_of_original_suggested_tags: origSuggestedTags.length, - } ); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); - - const onChangeSelectedTags = ( newTags: ( string | TokenItem )[] ) => { - setSelectedTags( newTags ); - wpcomTrackEvent( 'calypso_reader_post_publish_update_suggested_tags' ); - }; - - const tokenField = ( - - ); - - return ( -
-

{ __( 'Recommended tags:', 'jetpack-mu-wpcom' ) }

-

- { __( - 'Based on the topics and themes in your post, here are some suggested tags to consider:', - 'jetpack-mu-wpcom' - ) } -

- { tokenField } -

{ __( 'Adding tags can help drive more traffic to your post.', 'jetpack-mu-wpcom' ) }

- -
- ); -} - -export default React.memo( SuggestedTags ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts deleted file mode 100644 index afa6e8fe4f435..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-add-tags-to-post.ts +++ /dev/null @@ -1,31 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; - -type HasAddedTagsResult = { - added_tags: number; - success: boolean; -}; - -type OnSaveTagsCallback = ( addedTags: number ) => void; -const useAddTagsToPost = ( postId: number, tags: string[], onSaveTags: OnSaveTagsCallback ) => { - /** - * Save tags - */ - async function saveTags() { - let addedTags = 0; - try { - const result: HasAddedTagsResult = await apiFetch( { - method: 'POST', - path: `/wpcom/v2/read/posts/${ postId }/tags/add`, - data: { tags }, - } ); - addedTags = result.added_tags ?? 0; - } catch ( error ) { - // eslint-disable-next-line no-console - console.error( 'Error: Unable to add tags. Reason: %s', JSON.stringify( error ) ); - } - onSaveTags( addedTags ); - } - return { saveTags }; -}; - -export default useAddTagsToPost; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts deleted file mode 100644 index ef136077fb30d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/sharing-modal/use-sharing-modal-dismissed.ts +++ /dev/null @@ -1,24 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; -import { useState } from '@wordpress/element'; - -const useSharingModalDismissed = ( initial: boolean ) => { - const [ isDismissed, setSharingModalDismissed ] = useState( initial ); - - /** - * Update the value to dismiss the sharing modal - * - * @param value - The value to update. - */ - function updateIsDismissed( value: boolean ) { - apiFetch( { - method: 'PUT', - path: '/wpcom/v2/block-editor/sharing-modal-dismissed', - data: { wpcom_sharing_modal_dismissed: value }, - } ).finally( () => { - setSharingModalDismissed( value ); - } ); - } - return { isDismissed, updateIsDismissed }; -}; - -export default useSharingModalDismissed; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js deleted file mode 100644 index 6cc4da7c641eb..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/store.js +++ /dev/null @@ -1,150 +0,0 @@ -import apiFetch from '@wordpress/api-fetch'; -import { combineReducers, registerStore } from '@wordpress/data'; -import { apiFetch as apiFetchControls, controls } from '@wordpress/data-controls'; - -export const DEFAULT_VARIANT = 'tour'; -export const BLANK_CANVAS_VARIANT = 'blank-canvas-tour'; - -const showWelcomeGuideReducer = ( state = undefined, action ) => { - switch ( action.type ) { - case 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS': - return action.response.show_welcome_guide; - case 'WPCOM_WELCOME_GUIDE_SHOW_SET': - return action.show; - case 'WPCOM_WELCOME_GUIDE_RESET_STORE': - return undefined; - default: - return state; - } -}; - -const welcomeGuideManuallyOpenedReducer = ( state = false, action ) => { - switch ( action.type ) { - case 'WPCOM_WELCOME_GUIDE_SHOW_SET': - if ( typeof action.openedManually !== 'undefined' ) { - return action.openedManually; - } - return state; - - case 'WPCOM_WELCOME_GUIDE_RESET_STORE': - return false; - - default: - return state; - } -}; - -// TODO: next PR convert file to Typescript to ensure control of tourRating values: null, 'thumbs-up' 'thumbs-down' -const tourRatingReducer = ( state = undefined, action ) => { - switch ( action.type ) { - case 'WPCOM_WELCOME_GUIDE_TOUR_RATING_SET': - return action.tourRating; - case 'WPCOM_WELCOME_GUIDE_RESET_STORE': - return undefined; - default: - return state; - } -}; - -const welcomeGuideVariantReducer = ( state = DEFAULT_VARIANT, action ) => { - switch ( action.type ) { - case 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS': - return action.response.variant; - case 'WPCOM_HAS_USED_PATTERNS_MODAL': - return state === BLANK_CANVAS_VARIANT ? DEFAULT_VARIANT : state; - case 'WPCOM_WELCOME_GUIDE_RESET_STORE': - return DEFAULT_VARIANT; - default: - return state; - } -}; - -const shouldShowFirstPostPublishedModalReducer = ( state = false, action ) => { - switch ( action.type ) { - case 'WPCOM_SET_SHOULD_SHOW_FIRST_POST_PUBLISHED_MODAL': - return action.value; - case 'WPCOM_WELCOME_GUIDE_RESET_STORE': - return false; - default: - return state; - } -}; - -const reducer = combineReducers( { - welcomeGuideManuallyOpened: welcomeGuideManuallyOpenedReducer, - showWelcomeGuide: showWelcomeGuideReducer, - tourRating: tourRatingReducer, - welcomeGuideVariant: welcomeGuideVariantReducer, - shouldShowFirstPostPublishedModal: shouldShowFirstPostPublishedModalReducer, -} ); - -export const actions = { - *fetchWelcomeGuideStatus() { - const response = yield apiFetchControls( { path: '/wpcom/v2/block-editor/nux' } ); - - return { - type: 'WPCOM_WELCOME_GUIDE_FETCH_STATUS_SUCCESS', - response, - }; - }, - *fetchShouldShowFirstPostPublishedModal() { - const response = yield apiFetchControls( { - path: '/wpcom/v2/block-editor/should-show-first-post-published-modal', - } ); - - return { - type: 'WPCOM_SET_SHOULD_SHOW_FIRST_POST_PUBLISHED_MODAL', - value: response.should_show_first_post_published_modal, - }; - }, - setShowWelcomeGuide: ( show, { openedManually, onlyLocal } = {} ) => { - if ( ! onlyLocal ) { - apiFetch( { - path: '/wpcom/v2/block-editor/nux', - method: 'POST', - data: { show_welcome_guide: show }, - } ); - } - - return { - type: 'WPCOM_WELCOME_GUIDE_SHOW_SET', - show, - openedManually, - }; - }, - setTourRating: tourRating => { - return { type: 'WPCOM_WELCOME_GUIDE_TOUR_RATING_SET', tourRating }; - }, - setUsedPageOrPatternsModal: () => { - return { type: 'WPCOM_HAS_USED_PATTERNS_MODAL' }; - }, - // The `resetStore` action is only used for testing to reset the - // store inbetween tests. - resetStore: () => ( { - type: 'WPCOM_WELCOME_GUIDE_RESET_STORE', - } ), -}; - -export const selectors = { - isWelcomeGuideManuallyOpened: state => state.welcomeGuideManuallyOpened, - isWelcomeGuideShown: state => !! state.showWelcomeGuide, - isWelcomeGuideStatusLoaded: state => typeof state.showWelcomeGuide !== 'undefined', - getTourRating: state => state.tourRating, - // the 'modal' variant previously used for mobile has been removed but its slug may still be persisted in local storage - getWelcomeGuideVariant: state => - state.welcomeGuideVariant === 'modal' ? DEFAULT_VARIANT : state.welcomeGuideVariant, - getShouldShowFirstPostPublishedModal: state => state.shouldShowFirstPostPublishedModal, -}; - -/** - * Register the wpcom-welcome-guide store - */ -export function register() { - return registerStore( 'automattic/wpcom-welcome-guide', { - reducer, - actions, - selectors, - controls, - persist: true, - } ); -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js deleted file mode 100644 index 352fbd939e698..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/test/store.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import { dispatch, select } from '@wordpress/data'; -import waitForExpect from 'wait-for-expect'; -import { register, DEFAULT_VARIANT } from '../store'; - -const STORE_KEY = 'automattic/wpcom-welcome-guide'; - -beforeAll( () => { - register(); - jest.useRealTimers(); // Required for wait-for-expect to work. -} ); - -let originalFetch; -beforeEach( () => { - dispatch( STORE_KEY ).resetStore(); - originalFetch = window.fetch; - jest.spyOn( window, 'fetch' ).mockImplementation(); -} ); - -afterEach( () => { - window.fetch = originalFetch; -} ); - -test( 'resetting the store', async () => { - window.fetch.mockResolvedValue( { - status: 200, - json: () => Promise.resolve( { show_welcome_guide: true, variant: 'modal' } ), - } ); - - dispatch( STORE_KEY ).fetchWelcomeGuideStatus(); - await waitForExpect( () => - expect( select( STORE_KEY ).isWelcomeGuideStatusLoaded() ).toBe( true ) - ); - dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); - dispatch( STORE_KEY ).setTourRating( 'thumbs-up' ); - - dispatch( STORE_KEY ).resetStore(); - - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); - expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( false ); - expect( select( STORE_KEY ).isWelcomeGuideStatusLoaded() ).toBe( false ); - expect( select( STORE_KEY ).getTourRating() ).toBeUndefined(); - expect( select( STORE_KEY ).getWelcomeGuideVariant() ).toBe( DEFAULT_VARIANT ); -} ); - -test( "by default the store isn't loaded", () => { - const isLoaded = select( STORE_KEY ).isWelcomeGuideStatusLoaded(); - expect( isLoaded ).toBe( false ); -} ); - -test( 'after fetching the guide status the store is loaded', async () => { - window.fetch.mockResolvedValue( { - status: 200, - json: () => Promise.resolve( { show_welcome_guide: true, variant: DEFAULT_VARIANT } ), - } ); - - dispatch( STORE_KEY ).fetchWelcomeGuideStatus(); - - await waitForExpect( () => { - const isLoaded = select( STORE_KEY ).isWelcomeGuideStatusLoaded(); - expect( isLoaded ).toBe( true ); - } ); - - expect( window.fetch ).toHaveBeenCalledWith( - '/wpcom/v2/block-editor/nux?_locale=user', - expect.anything() - ); - - // Check the store is loaded with the state that came from the server - const isWelcomeGuideShown = select( STORE_KEY ).isWelcomeGuideShown(); - expect( isWelcomeGuideShown ).toBe( true ); - const welcomeGuideVariant = select( STORE_KEY ).getWelcomeGuideVariant(); - expect( welcomeGuideVariant ).toBe( DEFAULT_VARIANT ); -} ); - -test( 'toggle welcome guide visibility', () => { - // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise - // rejection errors that appear in CLI output - window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( true ); - expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( true ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( false ); - expect( select( STORE_KEY ).isWelcomeGuideShown() ).toBe( false ); -} ); - -test( 'guide manually opened flag is false by default', () => { - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); -} ); - -test( '"manually opened" flag can be set when opening welcome guide', () => { - // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise - // rejection errors that appear in CLI output - window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: false } ); - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); -} ); - -test( 'leaving `openedManually` unspecified leaves the flag unchanged', () => { - // setShowWelcomeGuide kicks off a save. This mock fixes unresolved promise - // rejection errors that appear in CLI output - window.fetch.mockResolvedValue( { status: 200, json: () => Promise.resolve( {} ) } ); - - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( false ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( true, { openedManually: true } ); - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); - - dispatch( STORE_KEY ).setShowWelcomeGuide( false ); - expect( select( STORE_KEY ).isWelcomeGuideManuallyOpened() ).toBe( true ); -} ); - -test( 'tour rating is "undefined" by default', () => { - expect( select( STORE_KEY ).getTourRating() ).toBeUndefined(); -} ); - -test( 'tour rating can be set to "thumbs-up" or "thumbs-down"', () => { - dispatch( STORE_KEY ).setTourRating( 'thumbs-up' ); - expect( select( STORE_KEY ).getTourRating() ).toBe( 'thumbs-up' ); - - dispatch( STORE_KEY ).setTourRating( 'thumbs-down' ); - expect( select( STORE_KEY ).getTourRating() ).toBe( 'thumbs-down' ); -} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx deleted file mode 100644 index 733e36bdad499..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/index.jsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { useState, useRef, useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { - useShouldShowVideoCelebrationModal, - useSiteIntent, - useHasSeenVideoCelebrationModal, -} from '../../../../common/tour-kit'; -import NuxModal from '../nux-modal'; -import videoSuccessImage from './video-success.svg'; -import './style.scss'; - -// Shows a celebration modal after a video is first uploaded to a site and the editor is saved. -const VideoCelebrationModalInner = () => { - const [ isModalOpen, setIsModalOpen ] = useState( false ); - const [ hasDisplayedModal, setHasDisplayedModal ] = useState( false ); - const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ) ); - const previousIsEditorSaving = useRef( false ); - const { updateHasSeenVideoCelebrationModal } = useHasSeenVideoCelebrationModal(); - - const { isEditorSaving } = useSelect( select => { - if ( isSiteEditor ) { - const isSavingSite = - select( 'core' ).isSavingEntityRecord( 'root', 'site' ) && - ! select( 'core' ).isAutosavingEntityRecord( 'root', 'site' ); - - const page = select( 'core/edit-site' ).getPage(); - const pageId = parseInt( page?.context?.postId ); - const isSavingEntity = - select( 'core' ).isSavingEntityRecord( 'postType', 'page', pageId ) && - ! select( 'core' ).isAutosavingEntityRecord( 'postType', 'page', pageId ); - const pageEntity = select( 'core' ).getEntityRecord( 'postType', 'page', pageId ); - - return { - isEditorSaving: isSavingSite || isSavingEntity, - linkUrl: pageEntity?.link, - }; - } - - const currentPost = select( 'core/editor' ).getCurrentPost(); - const isSavingEntity = - select( 'core' ).isSavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ) && - ! select( 'core' ).isAutosavingEntityRecord( 'postType', currentPost?.type, currentPost?.id ); - - return { - isEditorSaving: isSavingEntity, - }; - } ); - const shouldShowVideoCelebrationModal = useShouldShowVideoCelebrationModal( isEditorSaving ); - - useEffect( () => { - // Conditions to show modal: - // - user just finished saving - // - celebration modal hasn't been viewed/isn't visible - // - site intent is 'videopress' - // - site has uploaded a video - if ( - ! isEditorSaving && - previousIsEditorSaving.current && - ! hasDisplayedModal && - shouldShowVideoCelebrationModal - ) { - setIsModalOpen( true ); - setHasDisplayedModal( true ); - updateHasSeenVideoCelebrationModal( true ); - } - previousIsEditorSaving.current = isEditorSaving; - }, [ - isEditorSaving, - hasDisplayedModal, - shouldShowVideoCelebrationModal, - updateHasSeenVideoCelebrationModal, - ] ); - - const closeModal = () => setIsModalOpen( false ); - return ( - - - - - } - onRequestClose={ closeModal } - /> - ); -}; - -const VideoCelebrationModal = () => { - const { siteIntent: intent } = useSiteIntent(); - if ( 'videopress' === intent ) { - return ; - } - return null; -}; - -export default VideoCelebrationModal; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss deleted file mode 100644 index eca641aefdaa7..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/style.scss +++ /dev/null @@ -1,37 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.wpcom-site-editor-video-celebration-modal { - .components-modal__content { - @include break-small { - padding: 48px 90px; - } - } - - .wpcom-block-editor-nux-modal__image-container { - img { - width: 158px; - height: 85px; - } - } - - .wpcom-block-editor-nux-modal__buttons { - .components-button { - min-width: 113px; - &:not(.is-primary) { - border: 1px solid #c3c4c7; - border-radius: 4px; - } - } - } - - .components-modal__header { - button { - svg { - path { - transform: scale(1); - } - } - } - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg deleted file mode 100644 index 038f9f732ade2..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/video-celebration-modal/video-success.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg deleted file mode 100644 index a7fe75f9d393e..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/block-picker.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg deleted file mode 100644 index b3f080ffd46fd..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/editor.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg deleted file mode 100644 index 120e144993c70..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/preview.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg deleted file mode 100644 index 6603602512258..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/images/private.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss deleted file mode 100644 index 5d16245ab222f..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/style.scss +++ /dev/null @@ -1,208 +0,0 @@ -@import "@automattic/typography/styles/fonts"; - -$wpcom-modal-breakpoint: 660px; - -$wpcom-modal-padding-v: 40px; -$wpcom-modal-padding-h: 50px; -$wpcom-modal-content-min-height: 350px; -$wpcom-modal-footer-padding-v: 20px; -$wpcom-modal-footer-height: 30px + ( $wpcom-modal-footer-padding-v * 2 ); - -// Core modal style overrides -.wpcom-block-editor-nux { - &.components-modal__frame { - overflow: visible; - height: 65vh; - top: calc(17.5vh - #{$wpcom-modal-footer-height * 0.5}); - - @media (max-width: $wpcom-modal-breakpoint) { - width: 90vw; - min-width: 90vw; - left: 5vw; - right: 5vw; - } - - @media (min-width: $wpcom-modal-breakpoint) { - width: 720px; - height: $wpcom-modal-content-min-height; - top: calc(50% - #{$wpcom-modal-footer-height * 0.5}); - } - } - - .components-modal__header { - position: absolute; - max-width: 90%; - left: 5%; - @media (min-width: $wpcom-modal-breakpoint) { - display: none; - } - } - - .components-guide__container { - margin-top: 0; - } - - .components-guide__footer { - position: absolute; - width: 100%; - height: $wpcom-modal-footer-height; - bottom: $wpcom-modal-footer-height * -1; - left: 0; - padding: $wpcom-modal-footer-padding-v 0; - margin: 0; - display: flex; - justify-content: center; - background: var(--studio-white); - border-top: 1px solid #dcdcde; - - @media (min-width: $wpcom-modal-breakpoint) { - border-top: none; - } - } - - .components-guide__page { - position: absolute; - width: 100%; - max-width: 90vw; - height: 100%; - justify-content: start; - - @media (min-width: $wpcom-modal-breakpoint) { - max-width: 100%; - } - } - - .components-guide__page-control { - position: relative; - height: 0; - top: 100%; - overflow: visible; - margin: 0 auto; - z-index: 2; - - &::before { - display: inline-block; - content: ""; - height: $wpcom-modal-footer-height; - vertical-align: middle; - } - - li { - vertical-align: middle; - margin-bottom: 0; - } - - // Temporarily disable dots on mobile as alignment is wonky. - display: none; - @media (min-width: $wpcom-modal-breakpoint) { - display: block; - } - } -} - -.wpcom-block-editor-nux__page { - display: flex; - flex-direction: column-reverse; - justify-content: flex-end; - background: var(--studio-white); - width: 100%; - height: 90%; - max-width: 90vw; - - @media (min-width: $wpcom-modal-breakpoint) { - flex-direction: row; - justify-content: flex-start; - position: absolute; - max-width: 100%; - min-height: $wpcom-modal-content-min-height; - bottom: 0; - } -} - -.wpcom-block-editor-nux__text, -.wpcom-block-editor-nux__visual { - @media (min-width: $wpcom-modal-breakpoint) { - flex: 1 0 50%; - min-width: 290px; - } -} - -.wpcom-block-editor-nux__text { - padding: 0 25px 25px; - height: 60%; - - @media (min-width: $wpcom-modal-breakpoint) { - height: auto; - padding: $wpcom-modal-padding-v $wpcom-modal-padding-h; - } -} -.wpcom-block-editor-nux__visual { - height: 40%; - background: #1381d8; - text-align: center; - - @media (min-width: $wpcom-modal-breakpoint) { - height: auto; - } -} - -.wpcom-block-editor-nux__heading { - /* Gray / Gray 90 */ - color: #1d2327; - - font-family: $brand-serif; - font-weight: 400; - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 32px; - line-height: 1.19; - letter-spacing: -0.4px; - - @media (min-width: $wpcom-modal-breakpoint) { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 42px; - } - - // TODO: remove this hack once the welcome editor deals better with - // overflowing text - body.locale-de & { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 24px; - - @media (min-width: $wpcom-modal-breakpoint) { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 28px; - } - } -} - -.wpcom-block-editor-nux__description { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 15px; - line-height: 22px; - - /* Gray / Gray 60 */ - color: #50575e; - - @media (min-width: $wpcom-modal-breakpoint) { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - font-size: 17px; - line-height: 26px; - } -} - -.wpcom-block-editor-nux__image { - max-width: 100%; - height: auto; - flex: 1; - align-self: center; - - &.align-bottom { - align-self: flex-end; - } - - max-height: 100%; - - @media (min-width: $wpcom-modal-breakpoint) { - max-height: none; - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js deleted file mode 100644 index 020c21390770a..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-modal/wpcom-nux.js +++ /dev/null @@ -1,153 +0,0 @@ -import { Guide, GuidePage } from '@wordpress/components'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import blockPickerImage from './images/block-picker.svg'; -import editorImage from './images/editor.svg'; -import previewImage from './images/preview.svg'; -import privateImage from './images/private.svg'; - -import './style.scss'; - -/** - * The nux component. - */ -function WpcomNux() { - const { show, isNewPageLayoutModalOpen, isManuallyOpened } = useSelect( select => ( { - show: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideShown(), - isNewPageLayoutModalOpen: - select( 'automattic/starter-page-layouts' ) && // Handle the case where SPT is not initalized. - select( 'automattic/starter-page-layouts' ).isOpen(), - isManuallyOpened: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideManuallyOpened(), - } ) ); - - const { setShowWelcomeGuide } = useDispatch( 'automattic/wpcom-welcome-guide' ); - - // Track opening of the welcome guide - useEffect( () => { - if ( show && ! isNewPageLayoutModalOpen ) { - wpcomTrackEvent( 'calypso_editor_wpcom_nux_open', { - is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, - is_manually_opened: isManuallyOpened, - } ); - } - }, [ isManuallyOpened, isNewPageLayoutModalOpen, show ] ); - - if ( ! show || isNewPageLayoutModalOpen ) { - return null; - } - - const dismissWpcomNux = () => { - wpcomTrackEvent( 'calypso_editor_wpcom_nux_dismiss', { - is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, - } ); - setShowWelcomeGuide( false, { openedManually: false } ); - }; - - const nuxPages = getWpcomNuxPages(); - - return ( - - { nuxPages.map( ( nuxPage, index ) => ( - - ) ) } - - ); -} - -/** - * This function returns a collection of NUX slide data - * @returns { Array } a collection of props - */ -function getWpcomNuxPages() { - return [ - { - heading: __( 'Welcome to your website', 'jetpack-mu-wpcom' ), - description: __( - 'Edit your homepage, add the pages you need, and change your site’s look and feel.', - 'jetpack-mu-wpcom' - ), - imgSrc: editorImage, - alignBottom: true, - }, - { - heading: __( 'Add or edit your content', 'jetpack-mu-wpcom' ), - description: __( - 'Edit the placeholder content we’ve started you off with, or click the plus sign to add more content.', - 'jetpack-mu-wpcom' - ), - imgSrc: blockPickerImage, - }, - { - heading: __( 'Preview your site as you go', 'jetpack-mu-wpcom' ), - description: __( - 'As you edit your site content, click “Preview” to see your site the way your visitors will.', - 'jetpack-mu-wpcom' - ), - imgSrc: previewImage, - alignBottom: true, - }, - { - heading: __( 'Hidden until you’re ready', 'jetpack-mu-wpcom' ), - description: __( - 'Your site will remain hidden until launched. Click “Launch” in the toolbar to share it with the world.', - 'jetpack-mu-wpcom' - ), - imgSrc: privateImage, - alignBottom: true, - }, - ]; -} - -/** - * Display the Nux page - * - * @param props - The props of the component. - * @param props.pageNumber - The number of page. - * @param props.isLastPage - Whether the current page is the last one. - * @param props.alignBottom - Whether to align bottom. - * @param props.heading - The text of heading. - * @param props.description - The text of description. - * @param props.imgSrc - The src of image. - */ -function NuxPage( { pageNumber, isLastPage, alignBottom = false, heading, description, imgSrc } ) { - useEffect( () => { - wpcomTrackEvent( 'calypso_editor_wpcom_nux_slide_view', { - slide_number: pageNumber, - is_last_slide: isLastPage, - is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, - } ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [] ); - return ( - -
-

{ heading }

-
{ description }
-
-
- -
-
- ); -} - -export default WpcomNux; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts deleted file mode 100644 index 876bd2e34a5d3..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/get-editor-type.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { select } from '@wordpress/data'; - -/** - * Post (Post Type: ‘post’) - * Page (Post Type: ‘page’) - * Attachment (Post Type: ‘attachment’) - * Revision (Post Type: ‘revision’) - * Navigation menu (Post Type: ‘nav_menu_item’) - * Block templates (Post Type: ‘wp_template’) - * Template parts (Post Type: ‘wp_template_part’) - * @see https://developer.wordpress.org/themes/basics/post-types/#default-post-types - */ - -type PostType = - | 'post' - | 'page' - | 'attachment' - | 'revision' - | 'nav_menu_item' - | 'wp_template' - | 'wp_template_part' - | null; - -type EditorType = 'site' | PostType; - -export const getEditorType = (): EditorType | undefined => { - /** - * Beware when using this method to figure out if we are in the site editor. - * @see https://github.com/WordPress/gutenberg/issues/46616#issuecomment-1355301090 - * @see https://github.com/Automattic/jetpack/blob/2e56d0d/projects/plugins/jetpack/extensions/shared/get-editor-type.js - */ - if ( select( 'core/edit-site' ) ) { - return 'site'; - } - - if ( select( 'core/editor' ) ) { - return select( 'core/editor' ).getCurrentPostType() as PostType; - } - - return undefined; -}; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss deleted file mode 100644 index 51a9eaaf2407f..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/style-tour.scss +++ /dev/null @@ -1,51 +0,0 @@ -@use "sass:math"; -@import "@wordpress/base-styles/colors"; -@import "@wordpress/base-styles/mixins"; -@import "@wordpress/base-styles/variables"; -@import "@wordpress/base-styles/z-index"; - -$welcome-tour-card-media-extra-padding: 14%; // temporary value, to match the padding of the desktop instructional graphics - -.wpcom-editor-welcome-tour { - .wpcom-editor-welcome-tour__step { - &.is-with-extra-padding { - .components-card__media { - background-color: #e7eaeb; // the color of the background used in desktop graphics - - img { - left: $welcome-tour-card-media-extra-padding; - top: $welcome-tour-card-media-extra-padding; - width: 100% - $welcome-tour-card-media-extra-padding; - } - } - } - } - - .wpcom-tour-kit-step-card-overlay-controls { - position: absolute; - } -} - -// @todo clk - it this used? -.wpcom-editor-welcome-tour-card-frame { - position: relative; - - .components-guide__page-control { - bottom: 0; - left: $grid-unit-20; - margin: 0; - position: absolute; - - li { - margin-bottom: 0; - } - } -} - -// Adding it to hide the WelcomeTour when the W-icon is pressed on mobile -#wpwrap.wp-responsive-open { - - .tour-kit.wpcom-tour-kit { - display: none; - } -} diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts deleted file mode 100644 index fac4d7e1547a1..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/test/tour-steps.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import '../get-editor-type'; -import getTourSteps from '../tour-steps'; - -jest.mock( '../get-editor-type', () => { - return { getEditorType: () => 'post' }; -} ); - -describe( 'Welcome Tour', () => { - describe( 'Tour Steps', () => { - it( 'should retrieve the "Welcome to WordPress!" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Welcome to WordPress!' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Everything is a block" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Everything is a block' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Adding a new block" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Adding a new block' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Click a block to change it" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Click a block to change it' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "More Options" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'More Options' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Find your way" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Find your way' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Undo any mistake" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Undo any mistake' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Drag & drop" slide', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Undo any mistake' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Edit your site" slide, when in site editor', () => { - expect( getTourSteps( 'en', true, true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Edit your site' } ), - } ), - ] ) - ); - } ); - it( 'should not retrieve the "Edit your site" slide, when not in site editor', () => { - expect( getTourSteps( 'en', true, false ) ).not.toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Edit your site' } ), - } ), - ] ) - ); - } ); - it( 'should retrieve the "Congratulations!" slide, with correct url', () => { - expect( getTourSteps( 'en', true ) ).toEqual( - expect.arrayContaining( [ - expect.objectContaining( { - meta: expect.objectContaining( { heading: 'Congratulations!' } ), - } ), - ] ) - ); - } ); - } ); -} ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx deleted file mode 100644 index 1df746ef992b5..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/tour-launch.jsx +++ /dev/null @@ -1,243 +0,0 @@ -import { useLocale } from '@automattic/i18n-utils'; -import { useDispatch, useSelect, dispatch } from '@wordpress/data'; -import { useEffect, useMemo } from '@wordpress/element'; -import { - WpcomTourKit, - usePrefetchTourAssets, - START_WRITING_FLOW, - DESIGN_FIRST_FLOW, - useSiteIntent, - useSitePlan, -} from '../../../../common/tour-kit'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import { getEditorType } from './get-editor-type'; -import useTourSteps from './use-tour-steps'; -import './style-tour.scss'; - -/** - * The Welcome Tour of the Launch. - */ -function LaunchWpcomWelcomeTour() { - const { show, isNewPageLayoutModalOpen, isManuallyOpened } = useSelect( - select => ( { - show: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideShown(), - // Handle the case where the new page pattern modal is initialized and open - isNewPageLayoutModalOpen: - select( 'automattic/starter-page-layouts' ) && - select( 'automattic/starter-page-layouts' ).isOpen(), - isManuallyOpened: select( 'automattic/wpcom-welcome-guide' ).isWelcomeGuideManuallyOpened(), - } ), - [] - ); - const { siteIntent, siteIntentFetched } = useSiteIntent(); - const localeSlug = useLocale(); - const editorType = getEditorType(); - const { siteIntent: intent } = useSiteIntent(); - // We check the URL param along with site intent because the param loads faster and prevents element flashing. - const isBlogOnboardingFlow = intent === START_WRITING_FLOW || intent === DESIGN_FIRST_FLOW; - - const tourSteps = useTourSteps( localeSlug, false, false, null, siteIntent ); - - // Preload first card image (others preloaded after open state confirmed) - usePrefetchTourAssets( [ tourSteps[ 0 ] ] ); - - useEffect( () => { - if ( isBlogOnboardingFlow ) { - return; - } - if ( ! show && ! isNewPageLayoutModalOpen ) { - return; - } - - if ( ! siteIntentFetched ) { - return; - } - - // Track opening of the Welcome Guide - wpcomTrackEvent( 'calypso_editor_wpcom_tour_open', { - is_gutenboarding: window.calypsoifyGutenberg?.isGutenboarding, - is_manually_opened: isManuallyOpened, - intent: siteIntent, - editor_type: editorType, - } ); - }, [ - isNewPageLayoutModalOpen, - isManuallyOpened, - show, - siteIntent, - siteIntentFetched, - editorType, - isBlogOnboardingFlow, - ] ); - - if ( ! show || isNewPageLayoutModalOpen || isBlogOnboardingFlow ) { - return null; - } - - return ; -} - -/** - * Display the welcome tour. - * - * @param props - The props of the component. - * @param props.siteIntent - The intent of the site. - */ -function WelcomeTour( { siteIntent } ) { - const sitePlan = useSitePlan( window._currentSiteId ); - const localeSlug = useLocale(); - const { setShowWelcomeGuide } = useDispatch( 'automattic/wpcom-welcome-guide' ); - const isGutenboarding = window.calypsoifyGutenberg?.isGutenboarding; - const isWelcomeTourNext = () => { - return new URLSearchParams( document.location.search ).has( 'welcome-tour-next' ); - }; - const isSiteEditor = useSelect( select => !! select( 'core/edit-site' ), [] ); - const currentTheme = useSelect( select => select( 'core' ).getCurrentTheme() ); - const themeName = currentTheme?.name?.raw?.toLowerCase() ?? null; - - const tourSteps = useTourSteps( - localeSlug, - isWelcomeTourNext(), - isSiteEditor, - themeName, - siteIntent - ); - - // Only keep Payment block step if user comes from seller simple flow - if ( ! ( 'sell' === siteIntent && sitePlan && 'ecommerce-bundle' !== sitePlan.product_slug ) ) { - const paymentBlockIndex = tourSteps.findIndex( step => step.slug === 'payment-block' ); - tourSteps.splice( paymentBlockIndex, 1 ); - } - const { isInserterOpened, isSidebarOpened, isSettingsOpened } = useSelect( - select => ( { - isInserterOpened: select( 'core/edit-post' ).isInserterOpened(), - isSidebarOpened: select( 'automattic/block-editor-nav-sidebar' )?.isSidebarOpened() ?? false, // The sidebar store may not always be loaded. - isSettingsOpened: - select( 'core/interface' ).getActiveComplementaryArea( 'core/edit-post' ) === - 'edit-post/document', - } ), - [] - ); - - const isTourMinimized = - isSidebarOpened || - ( window.matchMedia( `(max-width: 782px)` ).matches && - ( isInserterOpened || isSettingsOpened ) ); - - const editorType = getEditorType(); - - const tourConfig = { - steps: tourSteps, - closeHandler: ( _steps, currentStepIndex, source ) => { - wpcomTrackEvent( 'calypso_editor_wpcom_tour_dismiss', { - is_gutenboarding: isGutenboarding, - slide_number: currentStepIndex + 1, - action: source, - intent: siteIntent, - editor_type: editorType, - } ); - setShowWelcomeGuide( false, { openedManually: false } ); - }, - isMinimized: isTourMinimized, - options: { - tourRating: { - enabled: true, - useTourRating: () => { - return useSelect( - select => select( 'automattic/wpcom-welcome-guide' ).getTourRating(), - [] - ); - }, - onTourRate: rating => { - dispatch( 'automattic/wpcom-welcome-guide' ).setTourRating( rating ); - wpcomTrackEvent( 'calypso_editor_wpcom_tour_rate', { - thumbs_up: rating === 'thumbs-up', - is_gutenboarding: false, - intent: siteIntent, - editor_type: editorType, - } ); - }, - }, - callbacks: { - onMinimize: currentStepIndex => { - wpcomTrackEvent( 'calypso_editor_wpcom_tour_minimize', { - is_gutenboarding: isGutenboarding, - slide_number: currentStepIndex + 1, - intent: siteIntent, - editor_type: editorType, - } ); - }, - onMaximize: currentStepIndex => { - wpcomTrackEvent( 'calypso_editor_wpcom_tour_maximize', { - is_gutenboarding: isGutenboarding, - slide_number: currentStepIndex + 1, - intent: siteIntent, - editor_type: editorType, - } ); - }, - onStepViewOnce: currentStepIndex => { - const lastStepIndex = tourSteps.length - 1; - const { heading } = tourSteps[ currentStepIndex ].meta; - - wpcomTrackEvent( 'calypso_editor_wpcom_tour_slide_view', { - slide_number: currentStepIndex + 1, - is_last_slide: currentStepIndex === lastStepIndex, - slide_heading: heading, - is_gutenboarding: isGutenboarding, - intent: siteIntent, - editor_type: editorType, - } ); - }, - }, - effects: { - spotlight: isWelcomeTourNext() - ? { - styles: { - minWidth: '50px', - minHeight: '50px', - borderRadius: '2px', - }, - } - : undefined, - arrowIndicator: false, - }, - popperModifiers: [ - useMemo( - () => ( { - name: 'offset', - options: { - offset: ( { placement, reference } ) => { - if ( placement === 'bottom' ) { - const boundary = document.querySelector( '.edit-post-header' ); - - if ( ! boundary ) { - return; - } - - const boundaryRect = boundary.getBoundingClientRect(); - const boundaryBottomY = boundaryRect.height + boundaryRect.y; - const referenceBottomY = reference.height + reference.y; - - return [ 0, boundaryBottomY - referenceBottomY + 16 ]; - } - return [ 0, 0 ]; - }, - }, - } ), - [] - ), - ], - classNames: 'wpcom-editor-welcome-tour', - portalParentElement: document.getElementById( 'wpwrap' ), - }, - }; - - // Theme isn't immediately available, so we prevent rendering so the content doesn't switch after it is presented, since some content is based on theme - if ( null === themeName ) { - return null; - } - - return ; -} - -export default LaunchWpcomWelcomeTour; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx deleted file mode 100644 index 6d0ad267c70c1..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-block-editor-nux/src/welcome-tour/use-tour-steps.tsx +++ /dev/null @@ -1,445 +0,0 @@ -import { localizeUrl } from '@automattic/i18n-utils'; -import { ExternalLink } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; -import { createInterpolateElement } from '@wordpress/element'; -import { __, _x } from '@wordpress/i18n'; -import { getQueryArg } from '@wordpress/url'; -import { wpcomTrackEvent } from '../../../../common/tracks'; -import { getEditorType } from './get-editor-type'; -import type { WpcomStep } from '../../../../common/tour-kit'; - -interface TourAsset { - desktop?: { src: string; type: string }; - mobile?: { src: string; type: string }; -} - -/** - * Get the tour asset by the key. - * - * @param key - The key of the tour asset. - */ -function getTourAssets( key: string ): TourAsset | undefined { - const CDN_PREFIX = 'https://s0.wp.com/i/editor-welcome-tour'; - const tourAssets = { - addBlock: { - desktop: { src: `${ CDN_PREFIX }/slide-add-block.gif`, type: 'image/gif' }, - mobile: { src: `${ CDN_PREFIX }/slide-add-block_mobile.gif`, type: 'image/gif' }, - }, - allBlocks: { desktop: { src: `${ CDN_PREFIX }/slide-all-blocks.gif`, type: 'image/gif' } }, - finish: { desktop: { src: `${ CDN_PREFIX }/slide-finish.png`, type: 'image/gif' } }, - makeBold: { desktop: { src: `${ CDN_PREFIX }/slide-make-bold.gif`, type: 'image/gif' } }, - moreOptions: { - desktop: { src: `${ CDN_PREFIX }/slide-more-options.gif`, type: 'image/gif' }, - mobile: { src: `${ CDN_PREFIX }/slide-more-options_mobile.gif`, type: 'image/gif' }, - }, - moveBlock: { - desktop: { src: `${ CDN_PREFIX }/slide-move-block.gif`, type: 'image/gif' }, - mobile: { src: `${ CDN_PREFIX }/slide-move-block_mobile.gif`, type: 'image/gif' }, - }, - findYourWay: { - desktop: { src: `${ CDN_PREFIX }/slide-find-your-way.gif`, type: 'image/gif' }, - }, - undo: { desktop: { src: `${ CDN_PREFIX }/slide-undo.gif`, type: 'image/gif' } }, - welcome: { - desktop: { src: `${ CDN_PREFIX }/slide-welcome.png`, type: 'image/png' }, - mobile: { src: `${ CDN_PREFIX }/slide-welcome_mobile.jpg`, type: 'image/jpeg' }, - }, - editYourSite: { - desktop: { - src: `https://s.w.org/images/block-editor/edit-your-site.gif?1`, - type: 'image/gif', - }, - mobile: { - src: `https://s.w.org/images/block-editor/edit-your-site.gif?1`, - type: 'image/gif', - }, - }, - videomakerWelcome: { - desktop: { src: `${ CDN_PREFIX }/slide-videomaker-welcome.png`, type: 'image/png' }, - }, - videomakerEdit: { - desktop: { src: `${ CDN_PREFIX }/slide-videomaker-edit.png`, type: 'image/png' }, - }, - } as { [ key: string ]: TourAsset }; - - return tourAssets[ key ]; -} - -/** - * Get the steps of the tour - * - * @param localeSlug - The slug of the locale. - * @param referencePositioning - The reference positioning. - * @param isSiteEditor - Whether is the site editor. - * @param themeName - The name of the theme. - * @param siteIntent - The intent of the current site. - */ -function useTourSteps( - localeSlug: string, - referencePositioning = false, - isSiteEditor = false, - themeName: string | null = null, - siteIntent: string | undefined = undefined -): WpcomStep[] { - const isVideoMaker = 'videomaker' === ( themeName ?? '' ); - const isPatternAssembler = !! getQueryArg( window.location.href, 'assembler' ); - const isMobile = useViewportMatch( 'mobile', '<' ); - const siteEditorCourseUrl = `https://wordpress.com/home/${ window.location.hostname }?courseSlug=site-editor-quick-start`; - const editorType = getEditorType(); - const onSiteEditorCourseLinkClick = () => { - wpcomTrackEvent( 'calypso_editor_wpcom_tour_site_editor_course_link_click', { - is_pattern_assembler: isPatternAssembler, - intent: siteIntent, - editor_type: editorType, - } ); - }; - - return [ - { - slug: 'welcome', - meta: { - heading: isPatternAssembler - ? __( 'Nice job! Your new page is set up.', 'jetpack-mu-wpcom' ) - : _x( 'Welcome to WordPress!', 'jetpack-mu-wpcom', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: ( () => { - if ( isPatternAssembler ) { - return createInterpolateElement( - __( - 'This is the Site Editor, where you can change everything about your site, including adding content to your homepage. Watch these short videos and take this tour to get started.', - 'jetpack-mu-wpcom' - ), - { - link_to_site_editor_course: ( - - ), - } - ); - } - - return isSiteEditor - ? __( - 'Take this short, interactive tour to learn the fundamentals of the WordPress Site Editor.', - 'jetpack-mu-wpcom' - ) - : _x( - 'Take this short, interactive tour to learn the fundamentals of the WordPress editor.', - 'jetpack-mu-wpcom', - 'jetpack-mu-wpcom' - ); - } )(), - mobile: null, - }, - imgSrc: getTourAssets( isVideoMaker ? 'videomakerWelcome' : 'welcome' ), - imgLink: isPatternAssembler - ? { - href: siteEditorCourseUrl, - playable: true, - onClick: onSiteEditorCourseLinkClick, - } - : undefined, - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: [ 'is-with-extra-padding', 'calypso_editor_wpcom_draft_post_modal_show' ], - }, - }, - }, - { - slug: 'everything-is-a-block', - meta: { - heading: __( 'Everything is a block', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( - 'In the WordPress Editor, paragraphs, images, and videos are all blocks.', - 'jetpack-mu-wpcom' - ), - mobile: null, - }, - imgSrc: getTourAssets( 'allBlocks' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - { - slug: 'add-block', - ...( referencePositioning && { - referenceElements: { - mobile: - '.edit-post-header .edit-post-header__toolbar .components-button.edit-post-header-toolbar__inserter-toggle', - desktop: - '.edit-post-header .edit-post-header__toolbar .components-button.edit-post-header-toolbar__inserter-toggle', - }, - } ), - meta: { - heading: __( 'Adding a new block', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( - 'Click + to open the inserter. Then click the block you want to add.', - 'jetpack-mu-wpcom' - ), - mobile: __( - 'Tap + to open the inserter. Then tap the block you want to add.', - 'jetpack-mu-wpcom' - ), - }, - imgSrc: getTourAssets( 'addBlock' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], - }, - }, - }, - { - slug: 'edit-block', - meta: { - heading: __( 'Click a block to change it', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: isVideoMaker - ? __( - 'Use the toolbar to change the appearance of a selected block. Try replacing a video!', - 'jetpack-mu-wpcom' - ) - : _x( - 'Use the toolbar to change the appearance of a selected block. Try making it bold.', - 'jetpack-mu-wpcom', - 'jetpack-mu-wpcom' - ), - mobile: null, - }, - imgSrc: getTourAssets( isVideoMaker ? 'videomakerEdit' : 'makeBold' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - { - slug: 'settings', - ...( referencePositioning && { - referenceElements: { - mobile: - '.edit-post-header .edit-post-header__settings .interface-pinned-items > button:nth-child(1)', - desktop: - '.edit-post-header .edit-post-header__settings .interface-pinned-items > button:nth-child(1)', - }, - } ), - meta: { - heading: __( 'More Options', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( 'Click the settings icon to see even more options.', 'jetpack-mu-wpcom' ), - mobile: __( 'Tap the settings icon to see even more options.', 'jetpack-mu-wpcom' ), - }, - imgSrc: getTourAssets( 'moreOptions' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], - }, - }, - }, - ...( ! isMobile - ? [ - { - slug: 'find-your-way', - meta: { - heading: __( 'Find your way', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( - "Use List View to see all the blocks you've added. Click and drag any block to move it around.", - 'jetpack-mu-wpcom' - ), - mobile: null, - }, - imgSrc: getTourAssets( 'findYourWay' ), - }, - options: { - classNames: { - desktop: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - ] - : [] ), - ...( ! isMobile - ? [ - { - slug: 'undo', - ...( referencePositioning && { - referenceElements: { - desktop: - '.edit-post-header .edit-post-header__toolbar .components-button.editor-history__undo', - }, - } ), - meta: { - heading: __( 'Undo any mistake', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( - "Click the Undo button if you've made a mistake.", - 'jetpack-mu-wpcom' - ), - mobile: null, - }, - imgSrc: getTourAssets( 'undo' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - ] - : [] ), - { - slug: 'drag-drop', - meta: { - heading: __( 'Drag & drop', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: __( 'To move blocks around, click and drag the handle.', 'jetpack-mu-wpcom' ), - mobile: __( 'To move blocks around, tap the up and down arrows.', 'jetpack-mu-wpcom' ), - }, - imgSrc: getTourAssets( 'moveBlock' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], - }, - }, - }, - { - slug: 'payment-block', - meta: { - heading: __( 'The Payments block', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: ( - <> - { __( - 'The Payments block allows you to accept payments for one-time, monthly recurring, or annual payments on your website', - 'jetpack-mu-wpcom' - ) } -
- - { __( 'Learn more', 'jetpack-mu-wpcom' ) } - - - ), - mobile: null, - }, - imgSrc: getTourAssets( 'welcome' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - ...( isSiteEditor - ? [ - { - slug: 'edit-your-site', - meta: { - heading: __( 'Edit your site', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: createInterpolateElement( - __( - 'Design everything on your site - from the header right down to the footer - in the Site Editor. Learn more', - 'jetpack-mu-wpcom' - ), - { - link_to_fse_docs: ( - - ), - } - ), - mobile: __( - 'Design everything on your site - from the header right down to the footer - in the Site Editor.', - 'jetpack-mu-wpcom' - ), - }, - imgSrc: getTourAssets( 'editYourSite' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: [ 'is-with-extra-padding', 'wpcom-editor-welcome-tour__step' ], - }, - }, - }, - ] - : [] ), - { - slug: 'congratulations', - meta: { - heading: __( 'Congratulations!', 'jetpack-mu-wpcom' ), - descriptions: { - desktop: createInterpolateElement( - __( - "You've learned the basics. Remember, your site is private until you decide to launch. View the block editing docs to learn more.", - 'jetpack-mu-wpcom' - ), - { - link_to_launch_site_docs: ( - - ), - link_to_editor_docs: ( - - ), - } - ), - mobile: null, - }, - imgSrc: getTourAssets( 'finish' ), - }, - options: { - classNames: { - desktop: 'wpcom-editor-welcome-tour__step', - mobile: 'wpcom-editor-welcome-tour__step', - }, - }, - }, - ]; -} - -export default useTourSteps; diff --git a/projects/packages/jetpack-mu-wpcom/webpack.config.js b/projects/packages/jetpack-mu-wpcom/webpack.config.js index cfdb59d970f19..ea855ad9da300 100644 --- a/projects/packages/jetpack-mu-wpcom/webpack.config.js +++ b/projects/packages/jetpack-mu-wpcom/webpack.config.js @@ -37,7 +37,6 @@ module.exports = [ 'wpcom-blocks-timeline-editor': './src/features/wpcom-blocks/timeline/editor.js', 'wpcom-blocks-timeline-view': './src/features/wpcom-blocks/timeline/view.js', 'wpcom-block-description-links': './src/features/wpcom-block-description-links/index.tsx', - 'wpcom-block-editor-nux': './src/features/wpcom-block-editor-nux/index.js', 'wpcom-global-styles-editor': './src/features/wpcom-global-styles/index.js', 'wpcom-global-styles-frontend': './src/features/wpcom-global-styles/wpcom-global-styles-view.js', From 1092a65a68dd49a4ef61c617b2a3531f3e920144 Mon Sep 17 00:00:00 2001 From: Igor Zinovyev Date: Thu, 1 Aug 2024 18:56:12 +0300 Subject: [PATCH 12/33] Fixed the user identifier field name. (#38671) --- tools/includes/send_tracks_event.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/includes/send_tracks_event.sh b/tools/includes/send_tracks_event.sh index 457f7eb0be8fa..101f91212a664 100644 --- a/tools/includes/send_tracks_event.sh +++ b/tools/includes/send_tracks_event.sh @@ -14,7 +14,7 @@ function send_tracks_event { USER_AGENT='jetpack-monorepo-cli' PAYLOAD=$(jq -nr \ --arg email "$(git config --get user.email)" \ - '.commonProps = {"_ul": $email, "_ut": "anon", "_rt": ( now * 1000 | round ) }' + '.commonProps = {"_ui": $email, "_ut": "anon", "_rt": ( now * 1000 | round ) }' ) # Add event name to payload. From 1d723856f4b8ed405042588e60c3976bd04bc737 Mon Sep 17 00:00:00 2001 From: Luiz Kowalski Date: Thu, 1 Aug 2024 18:25:16 -0300 Subject: [PATCH 13/33] AI Logo Generator: small UI fixes (#38676) * Change the wording on the error message to remove reference to a Jetpack AI account * Make click handler optional and remove modal close when the learn more link is clicked * Changelog --- .../changelog/update-ai-logo-generator-small-ui-fixes | 4 ++++ .../components/feature-fetch-failure-screen.tsx | 2 +- .../src/logo-generator/components/generator-modal.tsx | 2 +- .../src/logo-generator/components/visit-site-banner.tsx | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 projects/js-packages/ai-client/changelog/update-ai-logo-generator-small-ui-fixes diff --git a/projects/js-packages/ai-client/changelog/update-ai-logo-generator-small-ui-fixes b/projects/js-packages/ai-client/changelog/update-ai-logo-generator-small-ui-fixes new file mode 100644 index 0000000000000..77945125c637c --- /dev/null +++ b/projects/js-packages/ai-client/changelog/update-ai-logo-generator-small-ui-fixes @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +AI Logo Generator: fix small UI issues. diff --git a/projects/js-packages/ai-client/src/logo-generator/components/feature-fetch-failure-screen.tsx b/projects/js-packages/ai-client/src/logo-generator/components/feature-fetch-failure-screen.tsx index dc26c0570e1ca..cd1f53acdf38c 100644 --- a/projects/js-packages/ai-client/src/logo-generator/components/feature-fetch-failure-screen.tsx +++ b/projects/js-packages/ai-client/src/logo-generator/components/feature-fetch-failure-screen.tsx @@ -13,7 +13,7 @@ export const FeatureFetchFailureScreen: React.FC< { onRetry: () => void; } > = ( { onCancel, onRetry } ) => { const errorMessage = __( - 'We are sorry. There was an error loading your Jetpack AI account settings. Please, try again.', + 'We are sorry. There was an error loading your Jetpack AI plan data. Please, try again.', 'jetpack-ai-client' ); diff --git a/projects/js-packages/ai-client/src/logo-generator/components/generator-modal.tsx b/projects/js-packages/ai-client/src/logo-generator/components/generator-modal.tsx index dd5c261c3db91..7ae0c62baef04 100644 --- a/projects/js-packages/ai-client/src/logo-generator/components/generator-modal.tsx +++ b/projects/js-packages/ai-client/src/logo-generator/components/generator-modal.tsx @@ -235,7 +235,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( { /> { logoAccepted ? (
- +
+ + ); +} diff --git a/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx b/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx new file mode 100644 index 0000000000000..a30441e693ca9 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx @@ -0,0 +1,46 @@ +import { TabPanel } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as socialStore } from '../../social-store'; +import ConnectionIcon from '../connection-icon'; +import styles from './styles.module.scss'; + +/** + * Preview section of the social post modal. + * + * @returns {import('react').ReactNode} - Preview section of the social post modal. + */ +export function PreviewSection() { + const connections = useSelect( select => { + const store = select( socialStore ); + + return store.getConnections().map( connection => { + const title = connection.display_name || connection.external_display; + const name = `${ connection.service_name }-${ connection.connection_id }`; + const icon = ( + + ); + + return { + ...connection, + // Add the props needed for the TabPanel component + name, + title, + icon, + }; + } ); + }, [] ); + + return ( +
+ + { ( tab: ( typeof connections )[ number ] ) => ( +
Content for { tab.title }
+ ) } +
+
+ ); +} diff --git a/projects/js-packages/publicize-components/src/components/social-post-modal/settings-section.tsx b/projects/js-packages/publicize-components/src/components/social-post-modal/settings-section.tsx new file mode 100644 index 0000000000000..100fc49b76676 --- /dev/null +++ b/projects/js-packages/publicize-components/src/components/social-post-modal/settings-section.tsx @@ -0,0 +1,20 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.scss'; + +/** + * Settings section of the social post modal. + * + * @returns {import('react').ReactNode} - Settings section of the social post modal. + */ +export function SettingsSection() { + return ( +
+
+

{ __( 'Social Preview', 'jetpack' ) }

+
+
+