From a16fe28fe5990bf481a3421b020dd7ff9cb3adf8 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 27 Jun 2024 11:44:45 -0400 Subject: [PATCH 01/34] Protect card: Add the Last scan time (with tooltip) based on plugin state. --- .../product-cards-section/protect-card.jsx | 13 -- .../protect-card/index.tsx | 23 +++ .../protect-card/protect-value-section.tsx | 113 ++++++++++++++ .../protect-card/style.scss | 51 +++++++ .../my-jetpack/_inc/utils/time-since.ts | 61 ++++++++ .../add-mj-protect-card-last-scan-time | 4 + .../changelog/add-mj-protect-last-scan-time | 4 + projects/packages/my-jetpack/composer.json | 4 +- projects/packages/my-jetpack/global.d.ts | 43 +++++- .../my-jetpack/src/class-initializer.php | 5 + .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/backup/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/boost/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/jetpack/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/migration/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/protect/composer.lock | 4 +- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/search/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/social/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/starter-plugin/composer.lock | 143 +++++++++++++++++- .../add-mj-protect-card-last-scan-time | 5 + projects/plugins/videopress/composer.lock | 143 +++++++++++++++++- 28 files changed, 1490 insertions(+), 24 deletions(-) delete mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card.jsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss create mode 100644 projects/packages/my-jetpack/_inc/utils/time-since.ts create mode 100644 projects/packages/my-jetpack/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/packages/my-jetpack/changelog/add-mj-protect-last-scan-time create mode 100644 projects/plugins/backup/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/boost/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/jetpack/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/migration/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/protect/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/search/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/social/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/starter-plugin/changelog/add-mj-protect-card-last-scan-time create mode 100644 projects/plugins/videopress/changelog/add-mj-protect-card-last-scan-time diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card.jsx deleted file mode 100644 index 1fe4e1639880b..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ProductCard from '../connected-product-card'; - -const ProtectCard = ( { admin } ) => { - return ; -}; - -ProtectCard.propTypes = { - admin: PropTypes.bool.isRequired, -}; - -export default ProtectCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx new file mode 100644 index 0000000000000..28bb78195c32d --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -0,0 +1,23 @@ +import { __ } from '@wordpress/i18n'; +import { PRODUCT_STATUSES } from '../../../constants'; +import ProductCard from '../../connected-product-card'; +import ProtectValueSection from './protect-value-section'; +import type { FC } from 'react'; + +const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { + // Override the primary action button to read "Protect your site" instead + // of the default text, "Lern more". + const primaryActionOverride = { + [ PRODUCT_STATUSES.ABSENT ]: { + label: __( 'Protect your site', 'jetpack-my-jetpack' ), + }, + }; + + return ( + + + + ); +}; + +export default ProtectCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx new file mode 100644 index 0000000000000..1ca63c91a60f6 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -0,0 +1,113 @@ +import { Gridicon } from '@automattic/jetpack-components'; +import { Popover } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; +import { useState } from 'react'; +import { PRODUCT_STATUSES } from '../../../constants'; +import useProduct from '../../../data/products/use-product'; +import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import { timeSince } from '../../../utils/time-since'; + +import './style.scss'; + +const ProtectValueSection: React.FunctionComponent = () => { + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { status } = detail; + const isProtectActive = + status === PRODUCT_STATUSES.ACTIVE || status === PRODUCT_STATUSES.CAN_UPGRADE; + + return isProtectActive ? : ; +}; + +export default ProtectValueSection; + +const WithProtectValueSection: React.FunctionComponent = () => { + const { protectStatus } = getMyJetpackWindowInitialState(); + const lastScanTime = protectStatus?.last_checked; + const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : '...'; + + const lastScanText = sprintf( + /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ + __( 'Last scan: %s', 'jetpack-my-jetpack' ), + timeSinceLastScan + ); + return ; +}; + +const NoProtectValueSection: React.FunctionComponent = () => { + const { plugins, themes } = getMyJetpackWindowInitialState(); + const pluginsCount = Object.keys( plugins ).length; + const themesCount = Object.keys( themes ).length; + + const pluginsThemesText = sprintf( + /* translators: %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ + __( '%1$s plugins & %2$s themes', 'jetpack-my-jetpack' ), + pluginsCount, + themesCount + ); + + return ; +}; + +const ValueSection: React.FunctionComponent< { + isProtectActive: boolean; + lastScanText: string; +} > = ( { isProtectActive, lastScanText } ) => { + const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); + const [ isVisiblePopover_PluginsThemes, setIsVisiblePopover_PluginsThemes ] = useState( false ); + + const togglePopover_PluginsThemes = function () { + setIsVisiblePopover_PluginsThemes( state => ! state ); + }; + + return ( + <> +
+ { lastScanText } + { ! isProtectActive && ( + + + + ) } +
+
+
+
Scan
+
+
+
+
Auto-Firewall
+
+
+
+
Logins Blocked
+
+
+
+ + ); +}; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss new file mode 100644 index 0000000000000..54d144aa6edfc --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -0,0 +1,51 @@ +.value-section { + display: flex; + justify-content: space-between; + + &__heading { + font-size: 12px; + color: var(--jp-gray-100); + font-weight: 500; + } + + &__last-scan { + display: flex; + align-items: center; + margin-top: var(--spacing-base); + column-gap: 1px; + position: absolute; + right: calc(var(--spacing-base)* 3); + span { + font-size: var(--font-body-extra-small); + line-height: var(--font-title-small); + color: var(--jp-gray-50); + } + } + + &__tooltip-button { + display: flex; + align-items: center; + background: transparent; + border: none; + color: #888888; + padding: 2px; + svg { + margin: 0 auto; + } + } + + &__tooltip-heading { + font-size: 20px; + line-height: 30px; + color: var(--jp-black); + margin: 0 0 16px; + font-weight: 500; + } + + &__tooltip-content { + font-size: var(--font-body); + line-height: var(--font-title-small); + color: #444; + font-weight: 400; + } +} \ No newline at end of file diff --git a/projects/packages/my-jetpack/_inc/utils/time-since.ts b/projects/packages/my-jetpack/_inc/utils/time-since.ts new file mode 100644 index 0000000000000..2c8c025d369b1 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/utils/time-since.ts @@ -0,0 +1,61 @@ +import { sprintf, _n, __ } from '@wordpress/i18n'; + +/** + * Time Since + * + * @param {number} date - The past date (timestamp) to compare to the current date. + * @returns {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". + */ +export function timeSince( date: number ) { + const now = new Date(); + const offset = now.getTimezoneOffset() * 60000; + + const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); + + let interval = seconds / 31536000; // 364 days + if ( interval > 1 ) { + return sprintf( + // translators: placeholder is a number amount of years i.e. "5 years ago". + _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-my-jetpack' ), + Math.floor( interval ) + ); + } + + interval = seconds / 2592000; // 30 days + if ( interval > 1 ) { + return sprintf( + // translators: placeholder is a number amount of months i.e. "5 months ago". + _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-my-jetpack' ), + Math.floor( interval ) + ); + } + + interval = seconds / 86400; // 1 day + if ( interval > 1 ) { + return sprintf( + // translators: placeholder is a number amount of days i.e. "5 days ago". + _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-my-jetpack' ), + Math.floor( interval ) + ); + } + + interval = seconds / 3600; // 1 hour + if ( interval > 1 ) { + return sprintf( + // translators: placeholder is a number amount of hours i.e. "5 hours ago". + _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-my-jetpack' ), + Math.floor( interval ) + ); + } + + interval = seconds / 60; // 1 minute + if ( interval > 1 ) { + return sprintf( + // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". + _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-my-jetpack' ), + Math.floor( interval ) + ); + } + + return __( 'a few seconds ago', 'jetpack-my-jetpack' ); +} diff --git a/projects/packages/my-jetpack/changelog/add-mj-protect-card-last-scan-time b/projects/packages/my-jetpack/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/my-jetpack/changelog/add-mj-protect-last-scan-time b/projects/packages/my-jetpack/changelog/add-mj-protect-last-scan-time new file mode 100644 index 0000000000000..f745a0f47608f --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-mj-protect-last-scan-time @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Protect card: Add the Last scan time (with tooltip) based on product state. diff --git a/projects/packages/my-jetpack/composer.json b/projects/packages/my-jetpack/composer.json index c9c9c21a46c4b..66439e47a03e6 100644 --- a/projects/packages/my-jetpack/composer.json +++ b/projects/packages/my-jetpack/composer.json @@ -15,7 +15,9 @@ "automattic/jetpack-redirect": "@dev", "automattic/jetpack-constants": "@dev", "automattic/jetpack-plans": "@dev", - "automattic/jetpack-status": "@dev" + "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", + "automattic/jetpack-protect-status": "@dev" }, "require-dev": { "yoast/phpunit-polyfills": "1.1.0", diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 412903cc0a2a2..ff3f05ebf9410 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -7,7 +7,6 @@ declare module '*.scss'; // These libraries don't have types, this suppresses the TypeScript errors declare module '@wordpress/components'; declare module '@wordpress/compose'; -declare module '@wordpress/i18n'; declare module '@wordpress/icons'; declare module '@automattic/jetpack-connection'; @@ -28,6 +27,15 @@ type JetpackModule = | 'stats' | 'ai'; +type ScanItem = { + checked: boolean; + name: string; + slug: string; + threats: string[]; + type: string; + version: string; +}; + interface Window { myJetpackInitialState?: { siteSuffix: string; @@ -145,6 +153,24 @@ interface Window { }; }; }; + protectStatus: { + core: ScanItem; + current_progress?: string; + data_source: string; + database: string[]; + error: boolean; + error_code?: string; + error_message?: string; + files: string[]; + has_unchecked_items: boolean; + last_checked: string; + num_plugins_threats: number; + num_themes_threats: number; + num_threats: number; + plugins: ScanItem[]; + status: string; + themes: ScanItem[]; + }; purchases: { items: Array< { ID: string; @@ -229,6 +255,21 @@ interface Window { }; }; }; + themes: { + [ key: string ]: { + Author: string; + Name: string; + RequiresPHP: string; + RequiresWP: string; + Status: string; + Template: string; + TextDomain: string; + ThemeURI: string; + Version: string; + active: boolean; + is_block_theme: boolean; + }; + }; topJetpackMenuItemUrl: string; userIsAdmin: string; userIsNewToJetpack: string; diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index c7822362b5b56..db515adb60e84 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -20,8 +20,10 @@ use Automattic\Jetpack\Licensing; use Automattic\Jetpack\Modules; use Automattic\Jetpack\Plugins_Installer; +use Automattic\Jetpack\Protect_Status\Status as Protect_Status; use Automattic\Jetpack\Status; use Automattic\Jetpack\Status\Host as Status_Host; +use Automattic\Jetpack\Sync\Functions as Sync_Functions; use Automattic\Jetpack\Terms_Of_Service; use Automattic\Jetpack\Tracking; use Jetpack; @@ -209,6 +211,7 @@ public static function enqueue_scripts() { $previous_score = $speed_score_history->latest( 1 ); } $latest_score['previousScores'] = $previous_score['scores'] ?? array(); + $protect_status = Protect_Status::get_status(); self::update_historically_active_jetpack_modules(); wp_localize_script( @@ -222,6 +225,7 @@ public static function enqueue_scripts() { 'items' => array(), ), 'plugins' => Plugins_Installer::get_plugins(), + 'themes' => Sync_Functions::get_themes(), 'myJetpackUrl' => admin_url( 'admin.php?page=my-jetpack' ), 'myJetpackCheckoutUri' => admin_url( 'admin.php?page=my-jetpack' ), 'topJetpackMenuItemUrl' => Admin_Menu::get_top_level_menu_item_url(), @@ -255,6 +259,7 @@ public static function enqueue_scripts() { 'isAgencyAccount' => Jetpack_Manage::is_agency_account(), ), 'latestBoostSpeedScores' => $latest_score, + 'protectStatus' => $protect_status, ) ); diff --git a/projects/plugins/backup/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/backup/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/backup/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock index 8750021b09731..14694444a7689 100644 --- a/projects/plugins/backup/composer.lock +++ b/projects/plugins/backup/composer.lock @@ -1088,7 +1088,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1100,8 +1100,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1356,6 +1358,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", diff --git a/projects/plugins/boost/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/boost/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/boost/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/boost/composer.lock b/projects/plugins/boost/composer.lock index ff538903c2a4b..82950c7333535 100644 --- a/projects/plugins/boost/composer.lock +++ b/projects/plugins/boost/composer.lock @@ -1007,7 +1007,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1019,8 +1019,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1340,6 +1342,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", diff --git a/projects/plugins/jetpack/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/jetpack/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..a1c1831fa1ef7 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: other +Comment: Updated composer.lock. + + diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index b34efbd35dc0c..69d234a6d320f 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -1914,7 +1914,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1926,8 +1926,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -2244,6 +2246,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-publicize", "version": "dev-trunk", diff --git a/projects/plugins/migration/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/migration/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/migration/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/migration/composer.lock b/projects/plugins/migration/composer.lock index 9d4af52a422e7..487a43cadf190 100644 --- a/projects/plugins/migration/composer.lock +++ b/projects/plugins/migration/composer.lock @@ -1088,7 +1088,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1100,8 +1100,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1356,6 +1358,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", diff --git a/projects/plugins/protect/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/protect/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/protect/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index c7e67bfb753cb..a4ad52878a78e 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -1001,7 +1001,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1013,8 +1013,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { diff --git a/projects/plugins/search/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/search/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/search/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/search/composer.lock b/projects/plugins/search/composer.lock index 4c031cfccba49..d55f5f040d2c8 100644 --- a/projects/plugins/search/composer.lock +++ b/projects/plugins/search/composer.lock @@ -944,7 +944,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -956,8 +956,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1212,6 +1214,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", 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 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/social/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index 6d82122e73ad2..a4c91ba6e130a 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -944,7 +944,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -956,8 +956,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1274,6 +1276,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-publicize", "version": "dev-trunk", diff --git a/projects/plugins/starter-plugin/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/starter-plugin/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/starter-plugin/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/starter-plugin/composer.lock b/projects/plugins/starter-plugin/composer.lock index d73f6e2471b28..925e0dcf1b38d 100644 --- a/projects/plugins/starter-plugin/composer.lock +++ b/projects/plugins/starter-plugin/composer.lock @@ -944,7 +944,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -956,8 +956,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1212,6 +1214,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", diff --git a/projects/plugins/videopress/changelog/add-mj-protect-card-last-scan-time b/projects/plugins/videopress/changelog/add-mj-protect-card-last-scan-time new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/videopress/changelog/add-mj-protect-card-last-scan-time @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/videopress/composer.lock b/projects/plugins/videopress/composer.lock index 6a7f847667038..a396bff599fa2 100644 --- a/projects/plugins/videopress/composer.lock +++ b/projects/plugins/videopress/composer.lock @@ -944,7 +944,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "8af97f65a2b748d65ebc013886022933fc6dc614" + "reference": "4d627f1bf93564d42135bcee1ab3f20bb0e0e6b1" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -956,8 +956,10 @@ "automattic/jetpack-licensing": "@dev", "automattic/jetpack-plans": "@dev", "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-status": "@dev", "automattic/jetpack-redirect": "@dev", "automattic/jetpack-status": "@dev", + "automattic/jetpack-sync": "@dev", "php": ">=7.0" }, "require-dev": { @@ -1212,6 +1214,145 @@ "relative": true } }, + { + "name": "automattic/jetpack-protect-models", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-models", + "reference": "a79c18207b3476214e4be25eb5184c452c952ea9" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "0.4.2", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-models/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-models", + "textdomain": "jetpack-protect-models", + "version-constants": { + "::PACKAGE_VERSION": "src/class-protect-models.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "test-php": [ + "@composer phpunit" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the models used in Protect. ", + "transport-options": { + "relative": true + } + }, + { + "name": "automattic/jetpack-protect-status", + "version": "dev-trunk", + "dist": { + "type": "path", + "url": "../../packages/protect-status", + "reference": "297c3a5f7826a8e4c76f9bc992d2bc3417a1b669" + }, + "require": { + "automattic/jetpack-connection": "@dev", + "automattic/jetpack-plans": "@dev", + "automattic/jetpack-plugins-installer": "@dev", + "automattic/jetpack-protect-models": "@dev", + "automattic/jetpack-sync": "@dev", + "php": ">=7.0" + }, + "require-dev": { + "automattic/jetpack-changelogger": "@dev", + "automattic/wordbless": "dev-master", + "yoast/phpunit-polyfills": "1.1.0" + }, + "suggest": { + "automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package." + }, + "type": "jetpack-library", + "extra": { + "autotagger": true, + "branch-alias": { + "dev-trunk": "0.1.x-dev" + }, + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}" + }, + "mirror-repo": "Automattic/jetpack-protect-status", + "textdomain": "jetpack-protect-status", + "version-constants": { + "::PACKAGE_VERSION": "src/class-status.php" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "build-development": [ + "echo 'Add your build step to composer.json, please!'" + ], + "build-production": [ + "echo 'Add your build step to composer.json, please!'" + ], + "phpunit": [ + "./vendor/phpunit/phpunit/phpunit --colors=always" + ], + "post-install-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "post-update-cmd": [ + "WorDBless\\Composer\\InstallDropin::copy" + ], + "test-php": [ + "@composer phpunit" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "description": "This package contains the Protect Status API functionality to retrieve a site's scan status (WordPress, Themes, and Plugins threats).", + "transport-options": { + "relative": true + } + }, { "name": "automattic/jetpack-redirect", "version": "dev-trunk", From 85b9f14d3f55423bf208522c58eba10dedbda405 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 11:47:00 -0400 Subject: [PATCH 02/34] Fix "Lern more" typo in comment, per feedback. --- .../components/product-cards-section/protect-card/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx index 28bb78195c32d..4d6a3a741cdf5 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -6,7 +6,7 @@ import type { FC } from 'react'; const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { // Override the primary action button to read "Protect your site" instead - // of the default text, "Lern more". + // of the default text, "Learn more". const primaryActionOverride = { [ PRODUCT_STATUSES.ABSENT ]: { label: __( 'Protect your site', 'jetpack-my-jetpack' ), From 751cdda11634c56129191f21409cd1b015188d88 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 11:52:44 -0400 Subject: [PATCH 03/34] Add defaults to destructured var, per feedback. --- .../protect-card/protect-value-section.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 1ca63c91a60f6..10d6eef19604b 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -3,7 +3,6 @@ import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; import { useState } from 'react'; -import { PRODUCT_STATUSES } from '../../../constants'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; @@ -13,11 +12,9 @@ import './style.scss'; const ProtectValueSection: React.FunctionComponent = () => { const slug = 'protect'; const { detail } = useProduct( slug ); - const { status } = detail; - const isProtectActive = - status === PRODUCT_STATUSES.ACTIVE || status === PRODUCT_STATUSES.CAN_UPGRADE; + const { isPluginActive = false } = detail || {}; - return isProtectActive ? : ; + return isPluginActive ? : ; }; export default ProtectValueSection; From 91fe0ebd90fdae551dcbbe39ba39d50f85f51f94 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 11:55:11 -0400 Subject: [PATCH 04/34] Use React "FC" type, per feedback. --- .../protect-card/protect-value-section.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 10d6eef19604b..3677665bd9fc5 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -6,10 +6,11 @@ import { useState } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; +import type { FC } from 'react'; import './style.scss'; -const ProtectValueSection: React.FunctionComponent = () => { +const ProtectValueSection = () => { const slug = 'protect'; const { detail } = useProduct( slug ); const { isPluginActive = false } = detail || {}; @@ -19,7 +20,7 @@ const ProtectValueSection: React.FunctionComponent = () => { export default ProtectValueSection; -const WithProtectValueSection: React.FunctionComponent = () => { +const WithProtectValueSection = () => { const { protectStatus } = getMyJetpackWindowInitialState(); const lastScanTime = protectStatus?.last_checked; const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : '...'; @@ -32,7 +33,7 @@ const WithProtectValueSection: React.FunctionComponent = () => { return ; }; -const NoProtectValueSection: React.FunctionComponent = () => { +const NoProtectValueSection = () => { const { plugins, themes } = getMyJetpackWindowInitialState(); const pluginsCount = Object.keys( plugins ).length; const themesCount = Object.keys( themes ).length; @@ -47,7 +48,7 @@ const NoProtectValueSection: React.FunctionComponent = () => { return ; }; -const ValueSection: React.FunctionComponent< { +const ValueSection: FC< { isProtectActive: boolean; lastScanText: string; } > = ( { isProtectActive, lastScanText } ) => { From a40f71dc0fe93c5b8a3f2915db85afbbecd0ddf2 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 12:02:03 -0400 Subject: [PATCH 05/34] Fix snake case naming & state naming, per feedback. --- .../protect-card/protect-value-section.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 3677665bd9fc5..2fa92bbd998a5 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -53,10 +53,10 @@ const ValueSection: FC< { lastScanText: string; } > = ( { isProtectActive, lastScanText } ) => { const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); - const [ isVisiblePopover_PluginsThemes, setIsVisiblePopover_PluginsThemes ] = useState( false ); + const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); - const togglePopover_PluginsThemes = function () { - setIsVisiblePopover_PluginsThemes( state => ! state ); + const togglePopover = function () { + setIsPopoverVisible( prevState => ! prevState ); }; return ( @@ -68,10 +68,10 @@ const ValueSection: FC< { diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts new file mode 100644 index 0000000000000..6503be581edf0 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts @@ -0,0 +1,69 @@ +import { __, sprintf } from '@wordpress/i18n'; +import useProduct from '../../../data/products/use-product'; +import type { ReactElement } from 'react'; + +type TooltipType = 'pluginsThemesTooltip' | 'scanThreatsTooltip'; +export type TooltipContent = { + [ key in TooltipType ]: { + title: ReactElement | string; + text: ReactElement | string; + }; +}; + +/** + * Gets the translated tooltip copy based on Protect Scan details. + * + * @param {object} props - React props + * @param {number} props.pluginsCount - The number of installed plugins on the site. + * @param {number} props.themesCount - The number of installed themes on the site. + * @param {number} props.numThreats - The number of detected scan threats on the site. + * @returns {TooltipContent} An object containing each tooltip's title and text content. + */ +export function useProtectTooltipCopy( { + pluginsCount = 0, + themesCount = 0, + numThreats = 0, +}: { + pluginsCount: number; + themesCount: number; + numThreats: number; +} ): TooltipContent { + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { hasPaidPlanForProduct: hasProtectPaidPlan } = detail; + + return { + pluginsThemesTooltip: { + title: __( 'Improve site safety: secure plugins & themes', 'jetpack-my-jetpack' ), + text: sprintf( + /* translators: %1$d is the number of plugins and %2$d is the number of themes installed on the site. */ + __( + 'Your site has %1$d plugins and %2$d themes lacking security measures. Improve your site’s safety by adding protection at no cost.', + 'jetpack-my-jetpack' + ), + pluginsCount, + themesCount + ), + }, + scanThreatsTooltip: + hasProtectPaidPlan && numThreats + ? { + title: __( 'Auto-fix threats', 'jetpack-my-jetpack' ), + text: sprintf( + /* translators: %d is the number of detected scan threats on the site. */ + __( + 'The last scan identified %d critical threats. But don’t worry, use the “Auto-fix” button in the product to automatically fix most threats.', + 'jetpack-my-jetpack' + ), + numThreats + ), + } + : { + title: __( 'Elevate your malware protection', 'jetpack-my-jetpack' ), + text: __( + 'We’ve checked items against our database, and all appears well. For a more detailed, line-by-line malware scan, consider upgrading your plan.', + 'jetpack-my-jetpack' + ), + }, + }; +} diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index ff3f05ebf9410..8cdd63114d5bb 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -153,7 +153,7 @@ interface Window { }; }; }; - protectStatus: { + scanData: { core: ScanItem; current_progress?: string; data_source: string; diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index db515adb60e84..2bcdbc5ae75ee 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -211,7 +211,7 @@ public static function enqueue_scripts() { $previous_score = $speed_score_history->latest( 1 ); } $latest_score['previousScores'] = $previous_score['scores'] ?? array(); - $protect_status = Protect_Status::get_status(); + $scan_data = Protect_Status::get_status(); self::update_historically_active_jetpack_modules(); wp_localize_script( @@ -259,7 +259,7 @@ public static function enqueue_scripts() { 'isAgencyAccount' => Jetpack_Manage::is_agency_account(), ), 'latestBoostSpeedScores' => $latest_score, - 'protectStatus' => $protect_status, + 'scanData' => $scan_data, ) ); From 410b99985ef050f1a9ee4c0779fb0ebbd96e573e Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 17:33:12 -0400 Subject: [PATCH 08/34] Close tooltip/popover when outside focus, per feedback. --- .../protect-card/protect-value-section.tsx | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 30f25e179c39c..206b15b1e3f60 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -2,7 +2,7 @@ import { Gridicon } from '@automattic/jetpack-components'; import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; -import { useState } from 'react'; +import { useState, useCallback } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; @@ -64,34 +64,36 @@ const ValueSection: FC< { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { pluginsThemesTooltip, scanThreatsTooltip } = tooltipContent; - const togglePopover = function () { - setIsPopoverVisible( prevState => ! prevState ); - }; + const toggleTooltip = useCallback( + () => setIsPopoverVisible( prevState => ! prevState ), + [ setIsPopoverVisible ] + ); + const hideTooltip = useCallback( () => setIsPopoverVisible( false ), [ setIsPopoverVisible ] ); return ( <>
- { lastScanText } +
{ lastScanText }
{ ! isProtectActive && ( - - - + { isPopoverVisible && ( + + <> +

{ pluginsThemesTooltip.title }

+

{ pluginsThemesTooltip.text }

+ +
+ ) } +
) }
From 21df889f90760e4e1c85913b0a70cf29207efb31 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 19:40:20 -0400 Subject: [PATCH 09/34] Fix tooltip close fires twice when tooltip focus switches to button clicked. --- .../protect-card/protect-value-section.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 206b15b1e3f60..6b4df15aa61e3 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -2,7 +2,7 @@ import { Gridicon } from '@automattic/jetpack-components'; import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useRef } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; @@ -58,6 +58,7 @@ const ValueSection: FC< { lastScanText: string; tooltipContent: TooltipContent; } > = ( { isProtectActive, lastScanText, tooltipContent } ) => { + const useTooltipRef = useRef< HTMLButtonElement >(); const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); // TODO: `scanThreatsTooltip` will be utilized in a followup PR. @@ -68,7 +69,13 @@ const ValueSection: FC< { () => setIsPopoverVisible( prevState => ! prevState ), [ setIsPopoverVisible ] ); - const hideTooltip = useCallback( () => setIsPopoverVisible( false ), [ setIsPopoverVisible ] ); + const hideTooltip = useCallback( () => { + // Don't hide the tooltip here if it's the toolTip button that was clicked (the button + // becoming the document's activeElement). Instead let toggleTooltip() handle the closing. + if ( useTooltipRef.current && ! useTooltipRef.current.contains( document.activeElement ) ) { + setIsPopoverVisible( false ); + } + }, [ setIsPopoverVisible, useTooltipRef ] ); return ( <> @@ -76,7 +83,11 @@ const ValueSection: FC< {
{ lastScanText }
{ ! isProtectActive && (
- { isPopoverVisible && ( From 9e9fb38d56558d72d485fe08c715df7e920e6ab2 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 21:50:36 -0400 Subject: [PATCH 10/34] Fix responsiveness, per feedback. --- .../protect-card/protect-value-section.tsx | 4 ++-- .../product-cards-section/protect-card/style.scss | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 6b4df15aa61e3..ffebefeec628d 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -35,8 +35,8 @@ const ProtectValueSection = () => { timeSinceLastScan ) : sprintf( - /* translators: %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ - __( '%1$s plugins & %2$s themes', 'jetpack-my-jetpack' ), + /* translators: `\xa0` is a non-breaking space. %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ + __( '%1$d plugins &\xa0%2$d\xa0themes', 'jetpack-my-jetpack' ), pluginsCount, themesCount ); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 6a62b64c3f7f9..8bbe079f84fe7 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -11,14 +11,17 @@ &__last-scan { display: flex; align-items: center; + justify-content: flex-end; margin-top: var(--spacing-base); column-gap: 1px; position: absolute; right: calc(var(--spacing-base)*3); - span { + width: calc(60% - (var(--spacing-base) * 3)); + div { font-size: var(--font-body-extra-small); - line-height: var(--font-title-small); + line-height: var(--font-body); color: var(--jp-gray-50); + text-align: right; } } From 389b0fed26399ee1bcd6a34920ca72bd65a033ec Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Sun, 30 Jun 2024 21:56:09 -0400 Subject: [PATCH 11/34] Only show "Last scan:" if value available, per feedback. --- .../protect-card/protect-value-section.tsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index ffebefeec628d..7449338c1b13c 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -27,19 +27,20 @@ const ProtectValueSection = () => { const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; const themesCount = fromScanThemes.length || Object.keys( themes ).length; - const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : '...'; - const lastScanText = isPluginActive - ? sprintf( - /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ - __( 'Last scan: %s', 'jetpack-my-jetpack' ), - timeSinceLastScan - ) - : sprintf( - /* translators: `\xa0` is a non-breaking space. %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ - __( '%1$d plugins &\xa0%2$d\xa0themes', 'jetpack-my-jetpack' ), - pluginsCount, - themesCount - ); + const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : false; + const lastScanText = + timeSinceLastScan && isPluginActive + ? sprintf( + /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ + __( 'Last scan: %s', 'jetpack-my-jetpack' ), + timeSinceLastScan + ) + : sprintf( + /* translators: `\xa0` is a non-breaking space. %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ + __( '%1$d plugins &\xa0%2$d\xa0themes', 'jetpack-my-jetpack' ), + pluginsCount, + themesCount + ); const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); return ( From 3203c4a765fd9bbe6fbbd63b94b05683d434319d Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Mon, 1 Jul 2024 13:32:44 -0400 Subject: [PATCH 12/34] Add cursor: pointer to tooltip button, per feedback. --- .../components/product-cards-section/protect-card/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 8bbe079f84fe7..225f799733874 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -32,6 +32,7 @@ border: none; color: var(--jp-gray-50); padding: 2px; + cursor: pointer; svg { margin: 0 auto; } From 831bf8a6bb5aee8c17a741009594ec534dfe7fed Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Mon, 1 Jul 2024 13:39:02 -0400 Subject: [PATCH 13/34] Use --font-body-extra-small & fix comment typo, per feedback. --- .../protect-card/protect-value-section.tsx | 2 +- .../components/product-cards-section/protect-card/style.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 7449338c1b13c..0055687b9a668 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -71,7 +71,7 @@ const ValueSection: FC< { [ setIsPopoverVisible ] ); const hideTooltip = useCallback( () => { - // Don't hide the tooltip here if it's the toolTip button that was clicked (the button + // Don't hide the tooltip here if it's the tooltip button that was clicked (the button // becoming the document's activeElement). Instead let toggleTooltip() handle the closing. if ( useTooltipRef.current && ! useTooltipRef.current.contains( document.activeElement ) ) { setIsPopoverVisible( false ); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 225f799733874..001f737144f63 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -3,7 +3,7 @@ justify-content: space-between; &__heading { - font-size: 12px; + font-size: var(--font-body-extra-small); color: var(--jp-gray-100); font-weight: 500; } From 35726a113395be3504987f0ae0f46892674fa2d7 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Mon, 1 Jul 2024 14:45:19 -0400 Subject: [PATCH 14/34] Only show "Plugins & Themes" when scan data availavle, per feedback. --- .../protect-card/protect-value-section.tsx | 32 +++++++++++-------- .../protect-card/style.scss | 8 ++++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 0055687b9a668..4e6600aae9fa1 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -2,7 +2,7 @@ import { Gridicon } from '@automattic/jetpack-components'; import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; -import { useState, useCallback, useRef } from 'react'; +import { useState, useCallback, useMemo, useRef } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; @@ -28,19 +28,25 @@ const ProtectValueSection = () => { const themesCount = fromScanThemes.length || Object.keys( themes ).length; const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : false; - const lastScanText = - timeSinceLastScan && isPluginActive - ? sprintf( + const lastScanText = useMemo( () => { + if ( isPluginActive ) { + if ( timeSinceLastScan ) { + return sprintf( /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ __( 'Last scan: %s', 'jetpack-my-jetpack' ), timeSinceLastScan - ) - : sprintf( - /* translators: `\xa0` is a non-breaking space. %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ - __( '%1$d plugins &\xa0%2$d\xa0themes', 'jetpack-my-jetpack' ), - pluginsCount, - themesCount - ); + ); + } + return null; + } + return sprintf( + /* translators: `\xa0` is a non-breaking space. %1$d is the number (integer) of plugins and %2$d is the number (integer) of themes the site has. */ + __( '%1$d plugins &\xa0%2$d\xa0themes', 'jetpack-my-jetpack' ), + pluginsCount, + themesCount + ); + }, [ isPluginActive, timeSinceLastScan, pluginsCount, themesCount ] ); + const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); return ( @@ -56,7 +62,7 @@ export default ProtectValueSection; const ValueSection: FC< { isProtectActive: boolean; - lastScanText: string; + lastScanText?: string; tooltipContent: TooltipContent; } > = ( { isProtectActive, lastScanText, tooltipContent } ) => { const useTooltipRef = useRef< HTMLButtonElement >(); @@ -81,7 +87,7 @@ const ValueSection: FC< { return ( <>
-
{ lastScanText }
+ { lastScanText &&
{ lastScanText }
} { ! isProtectActive && (
+ { isPopoverVisible && ( + + { children } + + ) } + + ); +}; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/style.scss new file mode 100644 index 0000000000000..a39f6451f89ff --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/style.scss @@ -0,0 +1,12 @@ +.info-tooltip__button { + display: flex; + align-items: center; + background: transparent; + border: none; + color: var(--jp-gray-50); + padding: 2px; + cursor: pointer; + svg { + margin: 0 auto; + } +} diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 76ac08a3a561e..dceed4233075b 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -1,11 +1,9 @@ -import { Gridicon } from '@automattic/jetpack-components'; -import { Popover } from '@wordpress/components'; -import { useViewportMatch } from '@wordpress/compose'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { useState, useCallback, useMemo, useRef } from 'react'; +import { useMemo } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; +import { InfoTooltip } from './info-tooltip'; import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; import type { TooltipContent } from './use-protect-tooltip-copy'; import type { FC } from 'react'; @@ -75,53 +73,19 @@ const ValueSection: FC< { lastScanText?: string; tooltipContent: TooltipContent; } > = ( { isProtectActive, lastScanText, tooltipContent } ) => { - const useTooltipRef = useRef< HTMLButtonElement >(); - const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); - const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); - // TODO: `scanThreatsTooltip` will be utilized in a followup PR. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { pluginsThemesTooltip, scanThreatsTooltip } = tooltipContent; - - const toggleTooltip = useCallback( - () => setIsPopoverVisible( prevState => ! prevState ), - [ setIsPopoverVisible ] - ); - const hideTooltip = useCallback( () => { - // Don't hide the tooltip here if it's the tooltip button that was clicked (the button - // becoming the document's activeElement). Instead let toggleTooltip() handle the closing. - if ( useTooltipRef.current && ! useTooltipRef.current.contains( document.activeElement ) ) { - setIsPopoverVisible( false ); - } - }, [ setIsPopoverVisible, useTooltipRef ] ); + const { pluginsThemesTooltip } = tooltipContent; return ( <>
{ lastScanText &&
{ lastScanText }
} { ! isProtectActive && ( -
- - { isPopoverVisible && ( - - <> -

{ pluginsThemesTooltip.title }

-

{ pluginsThemesTooltip.text }

- -
- ) } -
+ + <> +

{ pluginsThemesTooltip.title }

+

{ pluginsThemesTooltip.text }

+ +
) }
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index f2f6af7938b61..01b2701a70e6b 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -31,19 +31,6 @@ } } - &__tooltip-button { - display: flex; - align-items: center; - background: transparent; - border: none; - color: var(--jp-gray-50); - padding: 2px; - cursor: pointer; - svg { - margin: 0 auto; - } - } - &__tooltip-heading { font-size: var(--font-title-small); line-height: 30px; From 43850624625e79581174ea9380b15cea333d48c8 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Tue, 2 Jul 2024 16:07:18 -0400 Subject: [PATCH 18/34] Protect card: Add the Scan/Threats status column (with tooltip) --- .../protect-card/assets/shield-off.svg | 5 + .../protect-card/assets/shield-partial.svg | 4 + .../protect-card/assets/shield-success.svg | 4 + .../protect-card/protect-value-section.tsx | 4 +- .../protect-card/scan-threats.tsx | 101 ++++++++++++++++++ .../protect-card/style.scss | 24 +++++ 6 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-off.svg create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-partial.svg create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-success.svg create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-off.svg b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-off.svg new file mode 100644 index 0000000000000..e1f7f232bc396 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-off.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-partial.svg b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-partial.svg new file mode 100644 index 0000000000000..03b09674fdade --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-partial.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-success.svg b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-success.svg new file mode 100644 index 0000000000000..0f3ebeca1048d --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/assets/shield-success.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index dceed4233075b..9eaf92d9a2385 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -4,6 +4,7 @@ import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; import { InfoTooltip } from './info-tooltip'; +import { ScanThreats } from './scan-threats'; import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; import type { TooltipContent } from './use-protect-tooltip-copy'; import type { FC } from 'react'; @@ -90,8 +91,7 @@ const ValueSection: FC< {
-
Scan
-
+
Auto-Firewall
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx new file mode 100644 index 0000000000000..28caceded2fef --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -0,0 +1,101 @@ +import { __ } from '@wordpress/i18n'; +import useProduct from '../../../data/products/use-product'; +import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import ShieldOff from './assets/shield-off.svg'; +import ShieldPartial from './assets/shield-partial.svg'; +import ShieldSuccess from './assets/shield-success.svg'; +import { InfoTooltip } from './info-tooltip'; +import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; + +export const ScanThreats = () => { + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { isPluginActive = false, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; + const { plugins, themes, scanData } = getMyJetpackWindowInitialState(); + const { + plugins: fromScanPlugins, + themes: fromScanThemes, + num_threats: numThreats = 0, + } = scanData; + + const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; + const themesCount = fromScanThemes.length || Object.keys( themes ).length; + const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); + const { scanThreatsTooltip } = tooltipContent; + + if ( isPluginActive ) { + if ( hasProtectPaidPlan ) { + if ( numThreats ) { + // TODO: if ( has critical threats ) { + // TODO: render the jsx here: + return ( + <> +
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
+
{ numThreats }
+
+ + ); + } + return ( + <> +
{ __( 'Scan', 'jetpack-my-jetpack' ) }
+
+
+ { +
+
Secure
+
+ + ); + } + return numThreats ? ( + <> +
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
+
{ numThreats }
+
+ + ) : ( + <> +
{ __( 'Scan', 'jetpack-my-jetpack' ) }
+
+
+ { +
+
Partial
+ + <> +

{ scanThreatsTooltip.title }

+

{ scanThreatsTooltip.text }

+ +
+
+ + ); + } + + return ( + <> +
{ __( 'Scan', 'jetpack-my-jetpack' ) }
+
+
+ { +
+
Off
+
+ + ); +}; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 01b2701a70e6b..e06feb9b83a32 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -6,6 +6,7 @@ font-size: var(--font-body-extra-small); color: var(--jp-gray-100); font-weight: 500; + margin-bottom: 10px; } &__last-scan { @@ -44,4 +45,27 @@ line-height: var(--font-title-small); color: var(--jp-gray-70); } + + &__data { + display: flex; + align-items: center; + font-size: var(--font-body-extra-small); + line-height: var(--font-title-small); + color: var(--jp-gray-50); + font-weight: 500; + } + + &__status-icon { + display: block; + width: 18.6px; + height: auto; + margin-right: 6px; + } + + &__status-text { + margin-right: 1px; + letter-spacing: -0.24px; + } } + + From e4213b63579be9d8affbf207b4b5acb0d96fa84c Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 3 Jul 2024 22:42:03 -0400 Subject: [PATCH 19/34] Critical threats, tooltip tracking, & additional styles. --- .../protect-card/info-tooltip/index.tsx | 35 ++++- .../protect-card/protect-value-section.tsx | 8 +- .../protect-card/scan-threats.tsx | 122 +++++++++++++++++- .../protect-card/style.scss | 26 ++++ projects/packages/my-jetpack/global.d.ts | 2 +- 5 files changed, 181 insertions(+), 12 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/index.tsx index ed80e005ba1bf..de355f68cf165 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/info-tooltip/index.tsx @@ -2,19 +2,46 @@ import { Gridicon } from '@automattic/jetpack-components'; import { Popover } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { useState, useCallback, useRef } from 'react'; +import useAnalytics from '../../../../hooks/use-analytics'; import type { FC, ReactNode } from 'react'; import './style.scss'; -export const InfoTooltip: FC< { children: ReactNode } > = ( { children } ) => { +type Props = { + children: ReactNode; + icon?: string; + iconSize?: number; + tracksEventName?: string; + tracksEventProps?: { [ key: string ]: string | boolean | number }; +}; + +export const InfoTooltip: FC< Props > = ( { + children, + icon = 'info-outline', + iconSize = 14, + tracksEventName, + tracksEventProps = {}, +} ) => { + const { recordEvent } = useAnalytics(); const useTooltipRef = useRef< HTMLButtonElement >(); const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); const toggleTooltip = useCallback( - () => setIsPopoverVisible( prevState => ! prevState ), - [ setIsPopoverVisible ] + () => + setIsPopoverVisible( prevState => { + if ( ! prevState === true && tracksEventName ) { + recordEvent( `jetpack_${ tracksEventName }`, { + page: 'my-jetpack', + feature: 'jetpack-protect', + ...tracksEventProps, + } ); + } + return ! prevState; + } ), + [ recordEvent, tracksEventName, tracksEventProps ] ); + const hideTooltip = useCallback( () => { // Don't hide the tooltip here if it's the tooltip button that was clicked (the button // becoming the document's activeElement). Instead let toggleTooltip() handle the closing. @@ -26,7 +53,7 @@ export const InfoTooltip: FC< { children: ReactNode } > = ( { children } ) => { return ( { isPopoverVisible && ( { lastScanText &&
{ lastScanText }
} { ! isProtectActive && ( - + <>

{ pluginsThemesTooltip.title }

{ pluginsThemesTooltip.text }

diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx index 28caceded2fef..c868863e9ed5e 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -1,6 +1,11 @@ +import { Gridicon } from '@automattic/jetpack-components'; +import { Popover } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; +import { useMemo, useState, useCallback, useRef } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import useAnalytics from '../../../hooks/use-analytics'; import ShieldOff from './assets/shield-off.svg'; import ShieldPartial from './assets/shield-partial.svg'; import ShieldSuccess from './assets/shield-success.svg'; @@ -8,6 +13,7 @@ import { InfoTooltip } from './info-tooltip'; import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; export const ScanThreats = () => { + const { recordEvent } = useAnalytics(); const slug = 'protect'; const { detail } = useProduct( slug ); const { isPluginActive = false, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; @@ -23,11 +29,103 @@ export const ScanThreats = () => { const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); const { scanThreatsTooltip } = tooltipContent; + const useTooltipRef = useRef< HTMLButtonElement >(); + const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); + const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); + + const criticalScanThreatCount = useMemo( () => { + const { core, database, files, num_plugins_threats, num_themes_threats } = scanData; + const pluginsThreats = num_plugins_threats + ? fromScanPlugins.reduce( ( accum, plugin ) => accum.concat( plugin.threats ), [] ) + : []; + const themesThreats = num_themes_threats + ? fromScanThemes.reduce( ( accum, theme ) => accum.concat( theme.threats ), [] ) + : []; + const allThreats = [ + ...pluginsThreats, + ...themesThreats, + ...core.threats, + ...database, + ...files, + ]; + return allThreats.reduce( + ( accum, threat ) => ( threat.severity >= 5 ? ( accum += 1 ) : accum ), + 0 + ); + }, [ fromScanPlugins, fromScanThemes, scanData ] ); + + const toggleTooltip = useCallback( + () => + setIsPopoverVisible( prevState => { + if ( ! prevState === true ) { + recordEvent( 'jetpack_protect_card_tooltip_open', { + page: 'my-jetpack', + feature: 'jetpack-protect', + location: 'scan', + status: 'alert', + hasPaidPlan: true, + threats: numThreats, + } ); + } + return ! prevState; + } ), + [ numThreats, recordEvent ] + ); + const hideTooltip = useCallback( () => { + // Don't hide the tooltip here if it's the tooltip button that was clicked (the button + // becoming the document's activeElement). Instead let toggleTooltip() handle the closing. + if ( useTooltipRef.current && ! useTooltipRef.current.contains( document.activeElement ) ) { + setIsPopoverVisible( false ); + } + }, [ setIsPopoverVisible, useTooltipRef ] ); + if ( isPluginActive ) { if ( hasProtectPaidPlan ) { if ( numThreats ) { - // TODO: if ( has critical threats ) { - // TODO: render the jsx here: + if ( criticalScanThreatCount ) { + return ( + <> +
+ { __( 'Threats', 'jetpack-my-jetpack' ) } +
+
+
+
{ numThreats }
+
+ + { isPopoverVisible && ( + + <> +

+ { scanThreatsTooltip.title } +

+

+ { scanThreatsTooltip.text } +

+ +
+ ) } +
+
+
+ + ); + } return ( <>
{ __( 'Threats', 'jetpack-my-jetpack' ) }
@@ -48,7 +146,9 @@ export const ScanThreats = () => { alt={ __( 'Shield icon - Scan Status: Secure', 'jetpack-my-jetpack' ) } />
-
Secure
+
+ { __( 'Secure', 'jetpack-my-jetpack' ) } +
); @@ -71,8 +171,18 @@ export const ScanThreats = () => { alt={ __( 'Shield icon - Scan Status: Partial', 'jetpack-my-jetpack' ) } />
-
Partial
- +
+ { __( 'Partial', 'jetpack-my-jetpack' ) } +
+ <>

{ scanThreatsTooltip.title }

{ scanThreatsTooltip.text }

@@ -94,7 +204,7 @@ export const ScanThreats = () => { alt={ __( 'Shield icon - Scan Status: Off', 'jetpack-my-jetpack' ) } />
-
Off
+
{ __( 'Off', 'jetpack-my-jetpack' ) }
); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index e06feb9b83a32..cff239e0b5c3d 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -68,4 +68,30 @@ } } +.scan-threats { + &__threat-count { + font-size: 32px; + line-height: var(--font-title-large); + font-weight: 400; + color: var(--jp-black); + } + + &__critical-threats { + display: flex; + align-items: center; + } + + &__critical-threat-container { + margin-left: 1px; + > button > svg { + fill: var(--jp-red-50); + } + } + + &__critical-threat-count { + color: var(--jp-red-50); + margin-left: 2px; + } +} + diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 8cdd63114d5bb..884b7dc2cd568 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -31,7 +31,7 @@ type ScanItem = { checked: boolean; name: string; slug: string; - threats: string[]; + threats: object[]; type: string; version: string; }; From c26a73eccdeb6161eea58f39060dad5599e541fc Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 4 Jul 2024 16:03:43 -0400 Subject: [PATCH 20/34] Fix core.threats not iterable error. --- .../product-cards-section/protect-card/scan-threats.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx index c868863e9ed5e..fe5fa062d4cd5 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -44,7 +44,7 @@ export const ScanThreats = () => { const allThreats = [ ...pluginsThreats, ...themesThreats, - ...core.threats, + ...( core?.threats ?? [] ), ...database, ...files, ]; From ab96aad79f654b9aabfaacfffa7ca10b34fe1d0b Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Fri, 5 Jul 2024 10:17:22 -0400 Subject: [PATCH 21/34] changelog. --- .../my-jetpack/changelog/add-my-protect-card-scan-threats | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/my-jetpack/changelog/add-my-protect-card-scan-threats diff --git a/projects/packages/my-jetpack/changelog/add-my-protect-card-scan-threats b/projects/packages/my-jetpack/changelog/add-my-protect-card-scan-threats new file mode 100644 index 0000000000000..d8d47d70e31dc --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-my-protect-card-scan-threats @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Add scan/threat info to the Protect card in My Jetpack. From 4df8c71b8811ae7ffb3bedd86620843b8a36e86d Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Fri, 5 Jul 2024 10:57:14 -0400 Subject: [PATCH 22/34] Scan status "Off" when site not connected. --- .../product-cards-section/protect-card/scan-threats.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx index fe5fa062d4cd5..0d7ec06277551 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -6,6 +6,7 @@ import { useMemo, useState, useCallback, useRef } from 'react'; import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import useAnalytics from '../../../hooks/use-analytics'; +import useMyJetpackConnection from '../../../hooks/use-my-jetpack-connection'; import ShieldOff from './assets/shield-off.svg'; import ShieldPartial from './assets/shield-partial.svg'; import ShieldSuccess from './assets/shield-success.svg'; @@ -17,6 +18,7 @@ export const ScanThreats = () => { const slug = 'protect'; const { detail } = useProduct( slug ); const { isPluginActive = false, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; + const { isSiteConnected } = useMyJetpackConnection(); const { plugins, themes, scanData } = getMyJetpackWindowInitialState(); const { plugins: fromScanPlugins, @@ -79,7 +81,7 @@ export const ScanThreats = () => { } }, [ setIsPopoverVisible, useTooltipRef ] ); - if ( isPluginActive ) { + if ( isPluginActive && isSiteConnected ) { if ( hasProtectPaidPlan ) { if ( numThreats ) { if ( criticalScanThreatCount ) { From 158ccaaed99c9256c2b84b1bdbeca35719a4aea7 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 15:00:32 -0400 Subject: [PATCH 23/34] Remove unnecessary non-breaking space. --- .../protect-card/protect-value-section.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 6198342ec6337..bf21969d07f9e 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -41,13 +41,13 @@ const ProtectValueSection = () => { return ( sprintf( /* translators: %d is the number of plugins installed on the site. */ - _n( '%d plugin', '%d plugins', pluginsCount, 'jetpack-my-jetpack' ).replace( ' ', '\xa0' ), // `\xa0` is a non-breaking space. + _n( '%d plugin', '%d plugins', pluginsCount, 'jetpack-my-jetpack' ), pluginsCount ) + ' ' + /* translators: The ampersand symbol here (&) is meaning "and". */ __( '&', 'jetpack-my-jetpack' ) + - '\xa0' + + '\xa0' + // `\xa0` is a non-breaking space. sprintf( /* translators: %d is the number of themes installed on the site. */ _n( '%d theme', '%d themes', themesCount, 'jetpack-my-jetpack' ).replace( ' ', '\xa0' ), // `\xa0` is a non-breaking space. From 95c92c7128c174e78ce583cfa404905a8f463501 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 15:23:11 -0400 Subject: [PATCH 24/34] Remove unnecessary icon width & height properties is scss. --- .../components/product-cards-section/protect-card/style.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index cff239e0b5c3d..81bd6c0f52398 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -57,8 +57,6 @@ &__status-icon { display: block; - width: 18.6px; - height: auto; margin-right: 6px; } From c3e054ada5c4079a21b39dcc3455624237666d06 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 16:32:01 -0400 Subject: [PATCH 25/34] Combine singular/plural translations into one translated string, per feedback. --- .../protect-card/style.scss | 2 +- .../protect-card/use-protect-tooltip-copy.ts | 46 +++++++++---------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 81bd6c0f52398..657faf9e8393a 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -68,7 +68,7 @@ .scan-threats { &__threat-count { - font-size: 32px; + font-size: calc( var(--font-title-large) - 4px ); line-height: var(--font-title-large); font-weight: 400; color: var(--jp-black); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts index 7e8c52f0bf9ab..1f51776b2153f 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts @@ -35,49 +35,45 @@ export function useProtectTooltipCopy( { return { pluginsThemesTooltip: { title: __( 'Improve site safety: secure plugins & themes', 'jetpack-my-jetpack' ), - text: + text: sprintf( + /* translators: %1$s the singular or plural of number of plugin(s), and %2$s is the singular or plural of the number of theme(s). */ + __( + 'Your site has %1$s and %2$s lacking security measures. Improve your site’s safety by adding protection at no cost.', + 'jetpack-my-jetpack' + ), sprintf( /* translators: %d is the number of plugins installed on the site. */ - _n( - 'Your site has %d plugin', - 'Your site has %d plugins', - pluginsCount, - 'jetpack-my-jetpack' - ), + _n( '%d plugin', '%d plugins', pluginsCount, 'jetpack-my-jetpack' ), pluginsCount - ) + - ' ' + + ), sprintf( /* translators: %d is the number of themes installed on the site. */ - _n( 'and %d theme', 'and %d themes', themesCount, 'jetpack-my-jetpack' ), + _n( '%d theme', '%d themes', themesCount, 'jetpack-my-jetpack' ), themesCount - ) + - ' ' + - __( - 'lacking security measures. Improve your site’s safety by adding protection at no cost.', - 'jetpack-my-jetpack' - ), + ) + ), }, scanThreatsTooltip: hasProtectPaidPlan && numThreats ? { title: __( 'Auto-fix threats', 'jetpack-my-jetpack' ), - text: + text: sprintf( + /* translators: %s is the singular or plural of number of detected critical threats on the site. */ + __( + 'The last scan identified %s. But don’t worry, use the “Auto-fix” button in the product to automatically fix most threats.', + 'jetpack-my-jetpack' + ), sprintf( /* translators: %d is the number of detected scan threats on the site. */ _n( - 'The last scan identified %d critical threat.', - 'The last scan identified %d critical threats.', + '%d critical threat.', + '%d critical threats.', numThreats, 'jetpack-my-jetpack' ), numThreats - ) + - ' ' + - __( - 'But don’t worry, use the “Auto-fix” button in the product to automatically fix most threats.', - 'jetpack-my-jetpack' - ), + ) + ), } : { title: __( 'Elevate your malware protection', 'jetpack-my-jetpack' ), From 162ccdf6bf548c798481c378bf43942891b116d7 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 17:58:48 -0400 Subject: [PATCH 26/34] Split up scan-threats into smaller components for readability, per feedback. --- .../protect-card/protect-value-section.tsx | 4 +- .../protect-card/scan-threats.tsx | 212 ++++++++++-------- 2 files changed, 126 insertions(+), 90 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index bf21969d07f9e..5d71e2d9993a6 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -4,7 +4,7 @@ import useProduct from '../../../data/products/use-product'; import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; import { timeSince } from '../../../utils/time-since'; import { InfoTooltip } from './info-tooltip'; -import { ScanThreats } from './scan-threats'; +import { ScanAndThreatStatus } from './scan-threats'; import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; import type { TooltipContent } from './use-protect-tooltip-copy'; import type { FC } from 'react'; @@ -97,7 +97,7 @@ const ValueSection: FC< {
- +
Auto-Firewall
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx index 0d7ec06277551..dfafcd80270ed 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -11,10 +11,10 @@ import ShieldOff from './assets/shield-off.svg'; import ShieldPartial from './assets/shield-partial.svg'; import ShieldSuccess from './assets/shield-success.svg'; import { InfoTooltip } from './info-tooltip'; -import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; +import { useProtectTooltipCopy, type TooltipContent } from './use-protect-tooltip-copy'; +import type { ReactElement, PropsWithChildren } from 'react'; -export const ScanThreats = () => { - const { recordEvent } = useAnalytics(); +export const ScanAndThreatStatus = () => { const slug = 'protect'; const { detail } = useProduct( slug ); const { isPluginActive = false, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; @@ -31,10 +31,6 @@ export const ScanThreats = () => { const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); const { scanThreatsTooltip } = tooltipContent; - const useTooltipRef = useRef< HTMLButtonElement >(); - const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); - const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); - const criticalScanThreatCount = useMemo( () => { const { core, database, files, num_plugins_threats, num_themes_threats } = scanData; const pluginsThreats = num_plugins_threats @@ -56,6 +52,52 @@ export const ScanThreats = () => { ); }, [ fromScanPlugins, fromScanThemes, scanData ] ); + if ( isPluginActive && isSiteConnected ) { + if ( hasProtectPaidPlan ) { + if ( numThreats ) { + return ( + + ); + } + return ; + } + return numThreats ? ( + + ) : ( + + ); + } + + return ; +}; + +/** + * ThreatStatus component + * + * @param {PropsWithChildren} props - The component props + * @param {number} props.numThreats - The number of threats + * @param {number} props.criticalThreatCount - The number of critical threats + * @param {TooltipContent[ 'scanThreatsTooltip' ]} props.tooltipContent - The number of critical threats + * @returns {ReactElement} rendered component + */ +function ThreatStatus( { + numThreats, + criticalThreatCount, + tooltipContent, +}: { + numThreats: number; + criticalThreatCount?: number; + tooltipContent?: TooltipContent[ 'scanThreatsTooltip' ]; +} ) { + const { recordEvent } = useAnalytics(); + const useTooltipRef = useRef< HTMLButtonElement >(); + const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); + const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); + const toggleTooltip = useCallback( () => setIsPopoverVisible( prevState => { @@ -81,88 +123,83 @@ export const ScanThreats = () => { } }, [ setIsPopoverVisible, useTooltipRef ] ); - if ( isPluginActive && isSiteConnected ) { - if ( hasProtectPaidPlan ) { - if ( numThreats ) { - if ( criticalScanThreatCount ) { - return ( - <> -
- { __( 'Threats', 'jetpack-my-jetpack' ) } -
-
-
-
{ numThreats }
-
- - { isPopoverVisible && ( - - <> -

- { scanThreatsTooltip.title } -

-

- { scanThreatsTooltip.text } -

- -
- ) } -
-
-
- - ); - } - return ( - <> -
{ __( 'Threats', 'jetpack-my-jetpack' ) }
-
-
{ numThreats }
-
- - ); - } - return ( - <> -
{ __( 'Scan', 'jetpack-my-jetpack' ) }
-
-
- { -
-
- { __( 'Secure', 'jetpack-my-jetpack' ) } -
+ return criticalThreatCount ? ( + <> +
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
+
+
{ numThreats }
+
+ + { isPopoverVisible && ( + + <> +

{ tooltipContent.title }

+

{ tooltipContent.text }

+ +
+ ) }
- - ); - } - return numThreats ? ( +
+
+ + ) : ( + <> +
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
+
{ numThreats }
+
+ + ); +} + +/** + * ScanStatus component + * + * @param {PropsWithChildren} props - The component props + * @param {'success' | 'partial' | 'off'} props.status - The number of threats + * @param {TooltipContent[ 'scanThreatsTooltip' ]} props.tooltipContent - The number of critical threats + * @returns { ReactElement} rendered component + */ +function ScanStatus( { + status, + tooltipContent, +}: { + status: 'success' | 'partial' | 'off'; + tooltipContent?: TooltipContent[ 'scanThreatsTooltip' ]; +} ) { + if ( status === 'success' ) { + return ( <> -
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
{ __( 'Scan', 'jetpack-my-jetpack' ) }
-
{ numThreats }
+
+ { +
+
{ __( 'Secure', 'jetpack-my-jetpack' ) }
- ) : ( + ); + } + if ( status === 'partial' ) { + return ( <>
{ __( 'Scan', 'jetpack-my-jetpack' ) }
@@ -186,15 +223,14 @@ export const ScanThreats = () => { } } > <> -

{ scanThreatsTooltip.title }

-

{ scanThreatsTooltip.text }

+

{ tooltipContent.title }

+

{ tooltipContent.text }

); } - return ( <>
{ __( 'Scan', 'jetpack-my-jetpack' ) }
@@ -210,4 +246,4 @@ export const ScanThreats = () => {
); -}; +} From 0e9d04ce915e6c8a3179667783392b5260f38fe9 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 21:47:49 -0400 Subject: [PATCH 27/34] Hide Protect card description, per feedback. --- .../product-cards-section/protect-card/index.tsx | 11 +++++++++-- .../product-cards-section/protect-card/style.scss | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx index 4d6a3a741cdf5..0cfdf7db817fd 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -1,8 +1,8 @@ import { __ } from '@wordpress/i18n'; +import { useCallback, type FC } from 'react'; import { PRODUCT_STATUSES } from '../../../constants'; import ProductCard from '../../connected-product-card'; import ProtectValueSection from './protect-value-section'; -import type { FC } from 'react'; const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { // Override the primary action button to read "Protect your site" instead @@ -13,8 +13,15 @@ const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { }, }; + const noDescription = useCallback( () => null, [] ); + return ( - + ); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 657faf9e8393a..d6069c9ab952e 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -1,12 +1,14 @@ .value-section { display: flex; justify-content: space-between; + margin-top: calc(var(--spacing-base) / 2); &__heading { font-size: var(--font-body-extra-small); color: var(--jp-gray-100); font-weight: 500; margin-bottom: 10px; + line-height: var(--font-title-small); } &__last-scan { @@ -16,7 +18,7 @@ margin-top: var(--spacing-base); column-gap: 1px; position: absolute; - right: calc(var(--spacing-base)*3); + right: calc(var(--spacing-base) * 3); width: calc(57% - (var(--spacing-base) * 3)); div { font-size: var(--font-body-extra-small); @@ -68,7 +70,7 @@ .scan-threats { &__threat-count { - font-size: calc( var(--font-title-large) - 4px ); + font-size: calc(var(--font-title-large) - 4px); line-height: var(--font-title-large); font-weight: 400; color: var(--jp-black); From b38310c6797fb0c87e5010beee653e7a0ab6f028 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Wed, 10 Jul 2024 23:17:44 -0400 Subject: [PATCH 28/34] Show "Upgrade" button & secondary "View" button when no paid plan. --- .../protect-card/index.tsx | 38 ++++++++++++++----- .../my-jetpack/src/products/class-protect.php | 29 +++++++++++--- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx index 0cfdf7db817fd..ee82b57eb7a32 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -1,16 +1,35 @@ import { __ } from '@wordpress/i18n'; import { useCallback, type FC } from 'react'; -import { PRODUCT_STATUSES } from '../../../constants'; +import useProduct from '../../../data/products/use-product'; +import useAnalytics from '../../../hooks/use-analytics'; import ProductCard from '../../connected-product-card'; import ProtectValueSection from './protect-value-section'; const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { - // Override the primary action button to read "Protect your site" instead - // of the default text, "Learn more". - const primaryActionOverride = { - [ PRODUCT_STATUSES.ABSENT ]: { - label: __( 'Protect your site', 'jetpack-my-jetpack' ), - }, + const { recordEvent } = useAnalytics(); + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { hasPaidPlanForProduct: hasProtectPaidPlan } = detail; + + /** + * Called when secondary "View" button is clicked. + */ + const onViewButtonClick = useCallback( () => { + recordEvent( 'jetpack_myjetpack_product_card_manage_click', { + product: slug, + } ); + }, [ recordEvent ] ); + + const shouldShowSecondaryButton = useCallback( + () => ! hasProtectPaidPlan, + [ hasProtectPaidPlan ] + ); + + const viewButton = { + href: 'admin.php?page=jetpack-protect', + label: __( 'View', 'jetpack-my-jetpack' ), + onClick: onViewButtonClick, + shouldShowButton: shouldShowSecondaryButton, }; const noDescription = useCallback( () => null, [] ); @@ -18,8 +37,9 @@ const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { return ( diff --git a/projects/packages/my-jetpack/src/products/class-protect.php b/projects/packages/my-jetpack/src/products/class-protect.php index 331ea2b46e58d..70827a2fded19 100644 --- a/projects/packages/my-jetpack/src/products/class-protect.php +++ b/projects/packages/my-jetpack/src/products/class-protect.php @@ -255,16 +255,33 @@ public static function get_pricing_for_ui() { } /** - * Checks if the site has a paid plan for the product + * Checks whether the current plan (or purchases) of the site already supports the product * - * @return bool + * @return boolean */ public static function has_paid_plan_for_product() { - $scan_data = static::get_state_from_wpcom(); - if ( is_wp_error( $scan_data ) ) { + $purchases_data = Wpcom_Products::get_site_current_purchases(); + if ( is_wp_error( $purchases_data ) ) { return false; } - return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state; + if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) { + foreach ( $purchases_data as $purchase ) { + // Protect is available as jetpack_scan product and as part of the Security or Complete plan. + if ( strpos( $purchase->product_slug, 'jetpack_scan' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_security' ) || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) { + return true; + } + } + } + return false; + } + + /** + * Checks whether the product can be upgraded - i.e. this shows the /#add-protect interstitial + * + * @return boolean + */ + public static function is_upgradable() { + return ! self::has_paid_plan_for_product(); } /** @@ -292,6 +309,6 @@ public static function get_manage_url() { * @return array Products bundle list. */ public static function is_upgradable_by_bundle() { - return array( 'security' ); + return array( 'security', 'complete' ); } } From 1855b089cf909816fe551852139573c46d2d6de5 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 22:46:09 -0400 Subject: [PATCH 29/34] Define Threat object in global.d.ts, per feedback. --- projects/packages/my-jetpack/global.d.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 884b7dc2cd568..e8fc1136d62a1 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -27,11 +27,28 @@ type JetpackModule = | 'stats' | 'ai'; +type ThreatItem = { + // Protect API properties (free plan) + id: string; + title: string; + fixed_in: string; + description: string | null; + source: string | null; + // Scan API properties (paid plan) + context: string | null; + filename: string | null; + first_detected: string | null; + fixable: boolean | null; + severity: number | null; + signature: string | null; + status: number | null; +}; + type ScanItem = { checked: boolean; name: string; slug: string; - threats: object[]; + threats: ThreatItem[]; type: string; version: string; }; From 1010f7cb7055fdf947b6d5aca0b01247ee0f27c0 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 22:55:07 -0400 Subject: [PATCH 30/34] Add scss comment explaining specific letter-spacing, per feedback --- .../components/product-cards-section/protect-card/style.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index d6069c9ab952e..a67ae73a48b24 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -64,7 +64,9 @@ &__status-text { margin-right: 1px; - letter-spacing: -0.24px; + // Reduce the letter spacing slightly. + // The specific letter spacing is per the design mockup. + letter-spacing: -0.24px; // https://wp.me/p1HpG7-rFA } } From 9f75120913e4cf8be80db0554a06dbc5123340f4 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 23:07:28 -0400 Subject: [PATCH 31/34] scss, use --spacing-base where possible. --- .../product-cards-section/protect-card/style.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index a67ae73a48b24..3b82be661559d 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -7,7 +7,7 @@ font-size: var(--font-body-extra-small); color: var(--jp-gray-100); font-weight: 500; - margin-bottom: 10px; + margin-bottom: calc(var(--spacing-base) + 2px); line-height: var(--font-title-small); } @@ -36,9 +36,9 @@ &__tooltip-heading { font-size: var(--font-title-small); - line-height: 30px; + line-height: calc(var(--font-title-small) + 6px);; color: var(--jp-black); - margin: 0 0 16px; + margin: 0 0 calc(var(--spacing-base) * 2); font-weight: 500; } @@ -59,7 +59,7 @@ &__status-icon { display: block; - margin-right: 6px; + margin-right: calc(var(--spacing-base) - 2px); } &__status-text { @@ -92,7 +92,7 @@ &__critical-threat-count { color: var(--jp-red-50); - margin-left: 2px; + margin-left: calc(var(--spacing-base) / 4); } } From 386f824ee3c2b921bb78cb039635bf4bca9eb373 Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 23:19:54 -0400 Subject: [PATCH 32/34] Add comment about removing the Description, per feedback. --- .../components/product-cards-section/protect-card/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx index ee82b57eb7a32..5f6b6cd236b97 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -32,6 +32,9 @@ const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { shouldShowButton: shouldShowSecondaryButton, }; + // This is a workaround to remove the Description from the product card. However if we end + // up needing to remove the Description from additional cards in the future, we might consider + // extending functionality to support that. const noDescription = useCallback( () => null, [] ); return ( From 60cbe2fa44acce1e357401102db8b3ed60f9b20e Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 23:40:10 -0400 Subject: [PATCH 33/34] Split if condition into multiple lines, per feedback. --- projects/packages/my-jetpack/src/products/class-protect.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/packages/my-jetpack/src/products/class-protect.php b/projects/packages/my-jetpack/src/products/class-protect.php index 70827a2fded19..70624db3a0526 100644 --- a/projects/packages/my-jetpack/src/products/class-protect.php +++ b/projects/packages/my-jetpack/src/products/class-protect.php @@ -267,7 +267,11 @@ public static function has_paid_plan_for_product() { if ( is_array( $purchases_data ) && ! empty( $purchases_data ) ) { foreach ( $purchases_data as $purchase ) { // Protect is available as jetpack_scan product and as part of the Security or Complete plan. - if ( strpos( $purchase->product_slug, 'jetpack_scan' ) !== false || str_starts_with( $purchase->product_slug, 'jetpack_security' ) || str_starts_with( $purchase->product_slug, 'jetpack_complete' ) ) { + if ( + strpos( $purchase->product_slug, 'jetpack_scan' ) !== false || + str_starts_with( $purchase->product_slug, 'jetpack_security' ) || + str_starts_with( $purchase->product_slug, 'jetpack_complete' ) + ) { return true; } } From c5eb3db040c613b400c5ef1d78963a1d0841c86f Mon Sep 17 00:00:00 2001 From: Bryan Elliott Date: Thu, 11 Jul 2024 23:49:45 -0400 Subject: [PATCH 34/34] Remove ternary & split into 2 returns instead, per feedback. --- .../protect-card/scan-threats.tsx | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx index dfafcd80270ed..055f9f39976b7 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats.tsx @@ -63,16 +63,16 @@ export const ScanAndThreatStatus = () => { /> ); } - return ; + return ; } return numThreats ? ( ) : ( - + ); } - return ; + return ; }; /** @@ -123,40 +123,44 @@ function ThreatStatus( { } }, [ setIsPopoverVisible, useTooltipRef ] ); - return criticalThreatCount ? ( - <> -
{ __( 'Threats', 'jetpack-my-jetpack' ) }
-
-
-
{ numThreats }
-
- - { isPopoverVisible && ( - +
{ __( 'Threats', 'jetpack-my-jetpack' ) }
+
+
+
{ numThreats }
+
+ + { isPopoverVisible && ( + + <> +

{ tooltipContent.title }

+

{ tooltipContent.text }

+ +
+ ) } +
-
- - ) : ( + + ); + } + + return ( <>
{ __( 'Threats', 'jetpack-my-jetpack' ) }