diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6a6d147b9c7eb..fbb0bbdb03d94 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4202,6 +4202,9 @@ importers:
specifier: 6.2.2
version: 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
+ '@automattic/babel-plugin-replace-textdomain':
+ specifier: workspace:*
+ version: link:../../js-packages/babel-plugin-replace-textdomain
'@automattic/jetpack-webpack-config':
specifier: workspace:*
version: link:../../js-packages/webpack-config
diff --git a/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils
new file mode 100644
index 0000000000000..ad8fa81458278
--- /dev/null
+++ b/projects/js-packages/scan/changelog/add-threat-subtitle-and-icon-utils
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add utilities for generating threat subtitle and icons
diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts
index fbafc9fcede36..29e017c837b68 100644
--- a/projects/js-packages/scan/src/utils/index.ts
+++ b/projects/js-packages/scan/src/utils/index.ts
@@ -17,6 +17,36 @@ export const getThreatType = ( threat: Threat ) => {
return null;
};
+export const getThreatIcon = ( threat: Threat ) => {
+ switch ( getThreatType( threat ) ) {
+ case 'core':
+ return 'wordpress-alt';
+ case 'plugin':
+ return 'plugins';
+ case 'theme':
+ return 'appearance';
+ case 'file':
+ return 'media-code';
+ default:
+ return 'shield-alt';
+ }
+};
+
+export const getThreatSubtitle = ( threat: Threat ) => {
+ switch ( getThreatType( threat ) ) {
+ case 'core':
+ return __( 'Vulnerable WordPress Version', 'jetpack-scan' );
+ case 'plugin':
+ return __( 'Vulnerable Plugin', 'jetpack-scan' );
+ case 'theme':
+ return __( 'Vulnerable Theme', 'jetpack-scan' );
+ case 'file':
+ return __( 'File Threat', 'jetpack-scan' );
+ default:
+ return __( 'Threat', 'jetpack-scan' );
+ }
+};
+
export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => {
const now = new Date();
const lastUpdated = new Date( lastUpdatedTimestamp );
@@ -124,7 +154,7 @@ export const getFixerDescription = ( threat: Threat ) => {
}
break;
case 'update':
- if ( threat.fixedIn && threat.extension.name ) {
+ if ( threat.fixedIn && threat.extension?.name ) {
return sprintf(
/* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */
__( 'Update %1$s to version %2$s', 'jetpack-scan' ),
diff --git a/projects/plugins/protect/changelog/add-threats-data-views b/projects/plugins/protect/changelog/add-threats-data-views
new file mode 100644
index 0000000000000..e15bd6a461a71
--- /dev/null
+++ b/projects/plugins/protect/changelog/add-threats-data-views
@@ -0,0 +1,5 @@
+Significance: minor
+Type: changed
+
+Added DataViews component for viewing scan results.
+
diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json
index 9d11e44dc60af..8708e2c101c9f 100644
--- a/projects/plugins/protect/package.json
+++ b/projects/plugins/protect/package.json
@@ -49,6 +49,7 @@
"react-router-dom": "6.2.2"
},
"devDependencies": {
+ "@automattic/babel-plugin-replace-textdomain": "workspace:*",
"@automattic/jetpack-webpack-config": "workspace:*",
"@babel/core": "7.26.0",
"@babel/preset-env": "7.26.0",
diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php
index bd034c375caf9..8ea1dec7156e7 100644
--- a/projects/plugins/protect/src/class-scan-history.php
+++ b/projects/plugins/protect/src/class-scan-history.php
@@ -207,43 +207,19 @@ public static function fetch_from_api() {
* Normalize API Data
* Formats the payload from the Scan API into an instance of History_Model.
*
- * @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
- *
* @param object $scan_data The data returned by the scan API.
* @return History_Model
*/
private static function normalize_api_data( $scan_data ) {
- $history = new History_Model();
- $history->num_threats = 0;
- $history->num_core_threats = 0;
- $history->num_plugins_threats = 0;
- $history->num_themes_threats = 0;
-
+ $history = new History_Model();
$history->last_checked = $scan_data->last_checked;
if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) {
return $history;
}
- foreach ( $scan_data->threats as $threat ) {
- if ( isset( $threat->extension->type ) ) {
- if ( 'plugin' === $threat->extension->type ) {
- self::handle_extension_threats( $threat, $history, 'plugin' );
- continue;
- }
-
- if ( 'theme' === $threat->extension->type ) {
- self::handle_extension_threats( $threat, $history, 'theme' );
- continue;
- }
- }
-
- if ( 'Vulnerable.WP.Core' === $threat->signature ) {
- self::handle_core_threats( $threat, $history );
- continue;
- }
-
- self::handle_additional_threats( $threat, $history );
+ foreach ( $scan_data->threats as $source_threat ) {
+ $history->threats[] = new Threat_Model( $source_threat );
}
return $history;
diff --git a/projects/plugins/protect/src/js/api.ts b/projects/plugins/protect/src/js/api.ts
index 2b98a6164bf8b..97d11fd5c0f2b 100644
--- a/projects/plugins/protect/src/js/api.ts
+++ b/projects/plugins/protect/src/js/api.ts
@@ -1,6 +1,7 @@
-import { type FixersStatus, type ScanStatus, type WafStatus } from '@automattic/jetpack-scan';
+import { type FixersStatus, type ScanStatus } from '@automattic/jetpack-scan';
import apiFetch from '@wordpress/api-fetch';
import camelize from 'camelize';
+import { WafStatus } from './types/waf';
const API = {
getWaf: (): Promise< WafStatus > =>
diff --git a/projects/plugins/protect/src/js/components/admin-page/index.jsx b/projects/plugins/protect/src/js/components/admin-page/index.jsx
index 2d023560517f3..68f9359a9bd81 100644
--- a/projects/plugins/protect/src/js/components/admin-page/index.jsx
+++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx
@@ -9,9 +9,9 @@ import { useConnection } from '@automattic/jetpack-connection';
import { __, sprintf } from '@wordpress/i18n';
import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
+import useScanStatusQuery from '../../data/scan/use-scan-status-query';
import useNotices from '../../hooks/use-notices';
import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
import useWafData from '../../hooks/use-waf-data';
import Notice from '../notice';
import ScanButton from '../scan-button';
@@ -23,11 +23,7 @@ const AdminPage = ( { children } ) => {
const { isRegistered } = useConnection();
const { isSeen: wafSeen } = useWafData();
const navigate = useNavigate();
- const {
- counts: {
- current: { threats: numThreats },
- },
- } = useProtectData();
+ const { data: status } = useScanStatusQuery();
const location = useLocation();
const { hasPlan } = usePlan();
@@ -71,11 +67,11 @@ const AdminPage = ( { children } ) => {
link="/scan"
label={
- { numThreats > 0
+ { status.threats.length > 0
? sprintf(
// translators: %d is the number of threats found.
__( 'Scan (%d)', 'jetpack-protect' ),
- numThreats
+ status.threats.length
)
: __( 'Scan', 'jetpack-protect' ) }
diff --git a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss
index adc0cee561ba5..da2e9510cd7d9 100644
--- a/projects/plugins/protect/src/js/components/admin-page/styles.module.scss
+++ b/projects/plugins/protect/src/js/components/admin-page/styles.module.scss
@@ -5,10 +5,21 @@
.header {
display: flex;
justify-content: space-between;
+ flex-direction: column;
+ gap: calc( var( --spacing-base ) * 3 ); // 24px
+ align-items: center;
+
+ @media ( min-width: 600px ) {
+ flex-direction: row;
+ }
&__scan_buttons {
display: flex;
gap: calc( var( --spacing-base ) * 3 ); // 24px
+
+ @media ( min-width: 600px ) {
+ flex-direction: row;
+ }
}
}
diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx
index 5214531dcf362..1a9bc87387fa9 100644
--- a/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx
+++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx
@@ -2,7 +2,6 @@ import { Text } from '@automattic/jetpack-components';
import { __ } from '@wordpress/i18n';
import { Icon, warning } from '@wordpress/icons';
import AdminSectionHero from '../admin-section-hero';
-import ScanNavigation from '../scan-navigation';
import styles from './styles.module.scss';
interface ErrorAdminSectionHeroProps {
@@ -32,9 +31,6 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( {
{ displayErrorMessage }
-
-
-
>
}
preserveSecondaryOnMobile={ false }
diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx
index e1274e8e29a17..cbb49498c353f 100644
--- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx
+++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx
@@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header';
import UserConnectionGate from '../user-connection-gate';
import styles from './styles.module.scss';
-const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
+const FixThreatModal = ( { threat } ) => {
const { setModal } = useModal();
const { fixThreats, isLoading: isFixersLoading } = useFixers();
@@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
const handleFixClick = () => {
return async event => {
event.preventDefault();
- await fixThreats( [ id ] );
+ await fixThreats( [ threat.id ] );
setModal( { type: null } );
};
};
@@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
-
+
diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx
deleted file mode 100644
index e801d9374fd33..0000000000000
--- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
-import clsx from 'clsx';
-import React, { useState, useCallback, useContext } from 'react';
-import styles from './styles.module.scss';
-
-const FreeAccordionContext = React.createContext();
-
-export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => {
- const accordionData = useContext( FreeAccordionContext );
- const open = accordionData?.open === id;
- const setOpen = accordionData?.setOpen;
-
- const bodyClassNames = clsx( styles[ 'accordion-body' ], {
- [ styles[ 'accordion-body-open' ] ]: open,
- [ styles[ 'accordion-body-close' ] ]: ! open,
- } );
-
- const handleClick = useCallback( () => {
- if ( ! open ) {
- onOpen?.();
- }
- setOpen( current => {
- return current === id ? null : id;
- } );
- }, [ open, onOpen, setOpen, id ] );
-
- return (
-
-
-
-
-
- { label }
-
-
- { title }
-
-
-
-
-
-
-
- { children }
-
-
- );
-};
-
-const FreeAccordion = ( { children } ) => {
- const [ open, setOpen ] = useState();
-
- return (
-
- { children }
-
- );
-};
-
-export default FreeAccordion;
diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx
deleted file mode 100644
index 43ad41e2501eb..0000000000000
--- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { wordpress, plugins } from '@wordpress/icons';
-import React from 'react';
-import FreeAccordion, { FreeAccordionItem } from '..';
-
-export default {
- title: 'Plugins/Protect/Free Accordion',
- component: FreeAccordion,
- parameters: {
- layout: 'centered',
- },
- decorators: [
- Story => (
-
-
-
- ),
- ],
-};
-
-// eslint-disable-next-line no-unused-vars
-export const Default = args => (
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-);
diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss
deleted file mode 100644
index 5278f6eff39f4..0000000000000
--- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss
+++ /dev/null
@@ -1,79 +0,0 @@
-.accordion {
- border-radius: var( --jp-border-radius );
- border: 1px solid var( --jp-gray );
-
- & > *:not(:last-child) {
- border-bottom: 1px solid var( --jp-gray );
- }
-}
-
-.accordion-item {
- background-color: var( --jp-white );
-}
-
-.accordion-header {
- margin: 0;
- display: grid;
- grid-template-columns: repeat(9, 1fr);
- cursor: pointer;
- box-sizing: border-box;
- background: none;
- border: none;
- width: 100%;
- align-items: center;
- outline-color: var( --jp-black );
- padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px
- text-align: start;
-
- >:first-of-type {
- grid-column: 1/8;
- }
-
- >:last-of-type {
- grid-column: 9;
- }
-
- &:hover {
- background: var( --jp-gray-0 );
- }
-}
-
-.accordion-header-label {
- display: flex;
- align-items: center;
- font-size: var( --font-body-small );
- font-weight: normal;
-}
-
-.accordion-header-label-icon {
- margin-right: var( --spacing-base ); // 8px
-}
-
-.accordion-header-description {
- font-weight: 600;
- margin-left: calc( var( --spacing-base ) * 4 ); // 32px
- margin-bottom: var( --spacing-base ); // 8px
-}
-
-.accordion-header-button {
- align-items: center;
-}
-
-.accordion-body {
- transform-origin: top center;
- overflow: hidden;
-
- &-close {
- transition: all .1s;
- max-height: 0;
- padding: 0;
- transform: scaleY(0);
- }
-
- &-open {
- transition: max-height .3s, transform .2s;
- padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px
- max-height: 1000px;
- transform: scaleY(1);
- }
-}
diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
index 7e8113b6f38ab..0788eb8bd7a41 100644
--- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
+++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
@@ -1,16 +1,18 @@
import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components';
+import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan';
+import { Icon } from '@wordpress/components';
import { createInterpolateElement, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
-import { Icon } from '@wordpress/icons';
import useIgnoreThreatMutation from '../../data/scan/use-ignore-threat-mutation';
import useModal from '../../hooks/use-modal';
import UserConnectionGate from '../user-connection-gate';
import styles from './styles.module.scss';
-const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
+const IgnoreThreatModal = ( { threat } ) => {
const { setModal } = useModal();
const ignoreThreatMutation = useIgnoreThreatMutation();
const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' );
+ const icon = getThreatIcon( threat );
const [ isIgnoring, setIsIgnoring ] = useState( false );
@@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
return async event => {
event.preventDefault();
setIsIgnoring( true );
- await ignoreThreatMutation.mutateAsync( id );
+ await ignoreThreatMutation.mutateAsync( threat.id );
setModal( { type: null } );
setIsIgnoring( false );
};
@@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
- { label }
+ { getThreatSubtitle( threat ) }
- { title }
+ { threat.title }
-
+
diff --git a/projects/plugins/protect/src/js/components/navigation/badge.jsx b/projects/plugins/protect/src/js/components/navigation/badge.jsx
deleted file mode 100644
index 93ebecf7235ef..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/badge.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { Popover, Spinner } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import { Icon, check, info } from '@wordpress/icons';
-import PropTypes from 'prop-types';
-import React, { useState, useCallback, useMemo } from 'react';
-import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query';
-import styles from './styles.module.scss';
-
-/**
- * Gets the Badge element
- *
- * @param {number} count - The number of threats found for this item.
- * @param {boolean} checked - Whether this item was checked for threats yet.
- * @return {object} The badge element
- */
-const getBadgeElement = ( count, checked ) => {
- if ( ! checked ) {
- return {
- popoverText: __(
- 'This item was added to your site after the most recent scan. We will check for threats during the next scheduled one.',
- 'jetpack-protect'
- ),
- badgeElement: (
-
- ),
- };
- }
-
- if ( count === 0 ) {
- return {
- popoverText: __( 'No known threats found to affect this version', 'jetpack-protect' ),
- badgeElement: (
-
- ),
- };
- }
-
- return {
- popoverText: null,
- badgeElement: (
-
- { count }
-
- ),
- };
-};
-
-const ItemBadge = ( { count, checked } ) => {
- const { data: status } = useScanStatusQuery();
-
- const { popoverText, badgeElement } = getBadgeElement( count, checked );
- const [ showPopover, setShowPopover ] = useState( false );
-
- const inProgress = useMemo( () => isScanInProgress( status ), [ status ] );
-
- const handleEnter = useCallback( () => {
- if ( inProgress ) {
- return;
- }
-
- setShowPopover( true );
- }, [ inProgress ] );
-
- const handleOut = useCallback( () => {
- setShowPopover( false );
- }, [] );
-
- return (
-
- { ! inProgress ? badgeElement :
}
- { showPopover && (
-
-
- { popoverText }
-
-
- ) }
-
- );
-};
-
-ItemBadge.propTypes = {
- /* The number of threats found for this item */
- count: PropTypes.number,
- /* Whether this item was checked for threats yet */
- checked: PropTypes.bool,
-};
-
-export default ItemBadge;
diff --git a/projects/plugins/protect/src/js/components/navigation/group.jsx b/projects/plugins/protect/src/js/components/navigation/group.jsx
deleted file mode 100644
index 9352ae5c63d67..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/group.jsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Button } from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import React, { useState, useCallback, useContext } from 'react';
-import ItemLabel from './label';
-import styles from './styles.module.scss';
-import { NavigationContext } from './use-menu-navigation';
-
-const MAX_ITEMS = 8;
-
-const NavigationGroup = ( { icon, label, children } ) => {
- const [ collapsed, setCollapsed ] = useState( true );
- const { mode } = useContext( NavigationContext );
- const needsTruncate =
- Array.isArray( children ) && children?.length >= MAX_ITEMS && mode === 'list';
- const content = needsTruncate && collapsed ? children.slice( 0, MAX_ITEMS ) : children;
- const totalHideItems = needsTruncate ? children?.length - MAX_ITEMS : 0;
-
- const handleCollapsedToggle = useCallback( () => {
- setCollapsed( current => ! current );
- }, [] );
-
- return (
-
-
- { label }
-
-
-
- { needsTruncate && (
-
-
- { collapsed
- ? sprintf(
- /* translators: %s: Number of hide items */
- __( 'Show %s more', 'jetpack-protect' ),
- totalHideItems
- )
- : sprintf(
- /* translators: %s: Number of hide items */
- __( 'Hide %s items', 'jetpack-protect' ),
- totalHideItems
- ) }
-
-
- ) }
-
-
- );
-};
-
-export default NavigationGroup;
diff --git a/projects/plugins/protect/src/js/components/navigation/index.jsx b/projects/plugins/protect/src/js/components/navigation/index.jsx
deleted file mode 100644
index bd30dfbdad964..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/index.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { Popover } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
-import React, { useState, useRef, useCallback } from 'react';
-import NavigationGroup from './group';
-import NavigationItem from './item';
-import styles from './styles.module.scss';
-import useMenuNavigation, { NavigationContext } from './use-menu-navigation';
-
-const NavigationList = ( { children } ) => (
-
-);
-
-const NavigationDropdown = ( { children, data } ) => {
- const ref = useRef( undefined );
- const [ listOpen, setListOpen ] = useState( false );
- const item = data?.items?.find( navItem => navItem?.id === data?.selectedItem ) ?? {
- label: __( 'See all results', 'jetpack-protect' ),
- };
- const { label, icon } = item;
-
- const handleOpen = useCallback( () => {
- setListOpen( open => ! open );
- }, [] );
-
- return (
-
-
- { icon && }
- { label }
-
-
-
-
- { children }
-
-
-
- );
-};
-
-const getNavigationComponent = mode => {
- switch ( mode ) {
- case 'list':
- return NavigationList;
- case 'dropdown':
- return NavigationDropdown;
- default:
- return NavigationList;
- }
-};
-
-const Navigation = ( { children, selected, onSelect, mode = 'list' } ) => {
- const data = useMenuNavigation( { selected, onSelect } );
- const Component = getNavigationComponent( mode );
-
- return (
-
- { children }
-
- );
-};
-
-export default Navigation;
-export { NavigationItem, NavigationGroup };
diff --git a/projects/plugins/protect/src/js/components/navigation/item.jsx b/projects/plugins/protect/src/js/components/navigation/item.jsx
deleted file mode 100644
index d902625c3997d..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/item.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import clsx from 'clsx';
-import React, { useContext, useEffect, useCallback } from 'react';
-import ItemBadge from './badge';
-import ItemLabel from './label';
-import styles from './styles.module.scss';
-import { NavigationContext } from './use-menu-navigation';
-
-const NavigationItem = ( {
- id,
- label,
- icon,
- badge,
- disabled,
- onClick,
- onKeyDown,
- onFocus,
- checked,
-} ) => {
- const context = useContext( NavigationContext );
-
- const selected = context?.selectedItem === id;
- const registerItem = context?.registerItem;
- const registerRef = context?.registerRef;
- const handleClickItem = context?.handleClickItem;
- const handleKeyDownItem = context?.handleKeyDownItem;
- const handleFocusItem = context?.handleFocusItem;
-
- const wrapperClassName = clsx( styles[ 'navigation-item' ], {
- [ styles.clickable ]: ! disabled,
- [ styles.selected ]: selected,
- } );
-
- const handleClick = useCallback(
- evt => {
- onClick?.( evt );
- handleClickItem?.( id );
- },
- [ handleClickItem, id, onClick ]
- );
-
- const handleKeyDown = useCallback(
- evt => {
- onKeyDown?.( evt );
- handleKeyDownItem?.( evt );
- },
- [ handleKeyDownItem, onKeyDown ]
- );
-
- const handleRef = useCallback(
- ref => {
- registerRef( ref, id );
- },
- [ registerRef, id ]
- );
-
- const handleFocus = useCallback(
- evt => {
- onFocus?.( evt );
- handleFocusItem?.( id );
- },
- [ handleFocusItem, id, onFocus ]
- );
-
- useEffect( () => {
- registerItem( { id, disabled, label, icon } );
- // eslint-disable-next-line
- }, [] );
-
- return (
-
- { label }
-
-
- );
-};
-
-export default NavigationItem;
diff --git a/projects/plugins/protect/src/js/components/navigation/label.jsx b/projects/plugins/protect/src/js/components/navigation/label.jsx
deleted file mode 100644
index 8f075caae020a..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/label.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { Icon } from '@wordpress/icons';
-import clsx from 'clsx';
-import PropTypes from 'prop-types';
-import React from 'react';
-import styles from './styles.module.scss';
-
-const ItemLabel = ( { icon, children, className } ) => {
- return (
-
- { icon && }
- { children }
-
- );
-};
-
-ItemLabel.propTypes = {
- /* An icon that will be rendered before text */
- icon: PropTypes.node,
- /* Label text that will be rendered */
- children: PropTypes.node.isRequired,
-};
-
-export default ItemLabel;
diff --git a/projects/plugins/protect/src/js/components/navigation/styles.module.scss b/projects/plugins/protect/src/js/components/navigation/styles.module.scss
deleted file mode 100644
index df9e3ef1f8a25..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/styles.module.scss
+++ /dev/null
@@ -1,142 +0,0 @@
-.navigation {
- background-color: var( --jp-white );
- box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08);
- border-radius: var( --jp-border-radius );
- margin: 0;
-}
-
-.navigation-item {
- display: flex;
- padding: calc( var( --spacing-base ) * 2 ); // 16px;
- align-items: center;
- justify-content: space-between;
- margin: 0;
- text-align: left;
-
- // Clickable State
- &.clickable {
- cursor: pointer;
- outline-color: var( --jp-black );
-
- // Focus/Hover State
- &:hover:not(.selected),
- &:focus:not(.selected) {
- background-color: var( --jp-gray-0 );
- }
- }
-
- // Selected State
- &.selected {
- background-color: var( --jp-black );
-
- & .navigation-item-label {
- color: var( --jp-white )
- }
-
- & .navigation-item-icon {
- fill: var( --jp-white );
- }
-
- & .navigation-item-badge {
- border: 1px solid var( --jp-red );
- background-color: var( --jp-red );
- color: var( --jp-white );
- }
- }
-
- // CHILDRENS
-
- // .navigation-item-label
- &-label {
- display: flex;
- align-items: center;
- padding-right: var( --spacing-base ); // 8px
- overflow-x: hidden;
- }
-
- // .navigation-item-label-content
- &-label-text {
- display: block;
- overflow-x: hidden;
- text-overflow: ellipsis;
- }
-
- // .navigation-item-icon
- &-icon {
- margin-right: calc( var( --spacing-base ) * 2); // 16px
- }
-
- // .navigation-item-badge
- &-badge {
- border: 1px solid var( --jp-red-60 );
- color: var( --jp-red-60 );
- border-radius: 50%;
- padding: calc( var( --spacing-base ) / 2 ) var( --spacing-base ); // 4px | 8px
- min-width: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- box-sizing: border-box;
- }
-
- &-check-badge {
- fill: var( --jp-green-50 );
- }
-
- &-info-badge {
- fill: var( --jp-gray-20 );
- }
-}
-
-.navigation-group {
- --icon-size: 28px;
- --item-spacing: calc( var( --spacing-base ) * 2 ); // 16px
- --left-spacing: calc( var( --icon-size ) + var( --item-spacing ) ); // 28px + 16px
-
- list-style: none;
-
- &-label {
- padding: calc( var( --spacing-base ) * 2 ); // 16px
- }
-
- &-content {
- padding: 0;
- }
-
- &-list {
- margin-left: var( --left-spacing ) ;
- }
-
- &-truncate {
- padding: calc( var( --spacing-base ) * 2 ); // 16px
- display: flex;
- justify-content: flex-start;
- }
-}
-
-.popover-text {
- width: 250px;
- padding: calc( var( --spacing-base ) * 2 ); // 16px
-}
-
-.navigation-dropdown {
- &-button {
- display: flex;
- border: 1px solid var( --jp-gray-10 );
- border-radius: var( --jp-border-radius );
- padding: calc( var( --spacing-base ) * 2 ); // 16px
- background-color: var( --jp-white );
- justify-content: space-between;
- align-items: center;
- width: 100%;
- }
-
- &-label {
- display: flex;
- justify-content: flex-start;
- }
-
- &-icon {
- margin-right: var( --spacing-base ); // 8px
- }
-}
diff --git a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js b/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js
deleted file mode 100644
index 2972dac06b572..0000000000000
--- a/projects/plugins/protect/src/js/components/navigation/use-menu-navigation.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { useState } from 'react';
-
-export const NavigationContext = React.createContext();
-
-const useMenuNavigation = ( { selected, onSelect } ) => {
- const [ items, setItems ] = useState( [] );
- const [ refs, setRef ] = useState( [] );
- const [ focusedItem, setFocusedItem ] = useState();
-
- const handleClickItem = id => {
- onSelect( id );
- };
-
- const handleFocusItem = id => {
- setFocusedItem( id );
- };
-
- const getPrevItem = ( current, last ) => {
- const startMinusOne = current - 1;
- const prevIndex = startMinusOne < 0 ? last : startMinusOne;
- const prevItem = items[ prevIndex ];
- return prevItem?.disabled ? getPrevItem( prevIndex, last ) : prevItem;
- };
-
- const getNextItem = ( current, last ) => {
- const startPlusOne = current + 1;
- const nextIndex = startPlusOne > last ? 0 : startPlusOne;
- const nextItem = items[ nextIndex ];
- return nextItem?.disabled ? getNextItem( nextIndex, last ) : nextItem;
- };
-
- const handleKeyDownItem = input => {
- const code = input?.code;
- const current = items.findIndex( item => item?.id === selected );
- const lastIndex = items.length - 1;
-
- let nextId;
-
- if ( code === 'ArrowUp' ) {
- const prevItem = getPrevItem( current, lastIndex );
- nextId = prevItem?.id;
- } else if ( code === 'ArrowDown' ) {
- const nextItem = getNextItem( current, lastIndex );
- nextId = nextItem?.id;
- } else if ( ( code === 'Enter' || code === 'Space' ) && focusedItem ) {
- nextId = focusedItem;
- }
-
- if ( nextId ) {
- const element = refs[ nextId ];
- element?.focus();
- onSelect( nextId );
- }
- };
-
- const registerRef = ( ref, id ) => {
- setRef( allRefs => {
- if ( ! allRefs[ id ] && ref ) {
- return { ...allRefs, [ id ]: ref };
- }
- return allRefs;
- } );
- };
-
- const registerItem = data => {
- setItems( allItems => {
- const newItems = [ ...allItems ];
- const id = data?.id;
- const currentIdx = newItems.findIndex( item => item?.id === id );
-
- if ( currentIdx >= 0 ) {
- newItems[ currentIdx ] = data;
- } else {
- newItems.push( data );
- }
-
- return newItems;
- } );
- };
-
- return {
- selectedItem: selected,
- handleClickItem,
- handleKeyDownItem,
- handleFocusItem,
- registerRef,
- registerItem,
- items,
- };
-};
-
-export default useMenuNavigation;
diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx
deleted file mode 100644
index c733ff1f0a08c..0000000000000
--- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx
+++ /dev/null
@@ -1,192 +0,0 @@
-import {
- IconTooltip,
- Spinner,
- Text,
- ThreatSeverityBadge,
- useBreakpointMatch,
-} from '@automattic/jetpack-components';
-import { ExternalLink } from '@wordpress/components';
-import { dateI18n } from '@wordpress/date';
-import { createInterpolateElement } from '@wordpress/element';
-import { sprintf, __ } from '@wordpress/i18n';
-import { Icon, check, chevronDown, chevronUp } from '@wordpress/icons';
-import clsx from 'clsx';
-import React, { useState, useCallback, useContext, useMemo } from 'react';
-import { PAID_PLUGIN_SUPPORT_URL } from '../../constants';
-import useFixers from '../../hooks/use-fixers';
-import styles from './styles.module.scss';
-
-// Extract context provider for clarity and reusability
-const PaidAccordionContext = React.createContext();
-
-// Component for displaying threat dates
-const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => {
- const statusText = useMemo( () => {
- if ( status === 'fixed' ) {
- return sprintf(
- /* translators: %s: Fixed on date */
- __( 'Threat fixed %s', 'jetpack-protect' ),
- dateI18n( 'M j, Y', fixedOn )
- );
- }
- if ( status === 'ignored' ) {
- return __( 'Threat ignored', 'jetpack-protect' );
- }
- return null;
- }, [ status, fixedOn ] );
-
- return (
- firstDetected && (
- <>
-
- { sprintf(
- /* translators: %s: First detected date */
- __( 'Threat found %s', 'jetpack-protect' ),
- dateI18n( 'M j, Y', firstDetected )
- ) }
- { statusText && (
- <>
-
- { statusText }
- >
- ) }
-
- { [ 'fixed', 'ignored' ].includes( status ) && }
- >
- )
- );
-};
-
-// Badge for displaying the status (fixed or ignored)
-const StatusBadge = ( { status } ) => (
-
- { status === 'fixed'
- ? __( 'Fixed', 'jetpack-protect' )
- : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) }
-
-);
-
-const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => {
- if ( isStaleFixInProgress ) {
- return (
-
-
- { createInterpolateElement(
- __(
- 'The fixer is taking longer than expected. Please try again or contact support .',
- 'jetpack-protect'
- ),
- {
- supportLink: (
-
- ),
- }
- ) }
-
-
- );
- }
-
- if ( isActiveFixInProgress ) {
- return ;
- }
-
- return ;
-};
-
-export const PaidAccordionItem = ( {
- id,
- title,
- label,
- icon,
- fixable,
- severity,
- children,
- firstDetected,
- fixedOn,
- onOpen,
- status,
- hideAutoFixColumn = false,
-} ) => {
- const { open, setOpen } = useContext( PaidAccordionContext );
- const isOpen = open === id;
-
- const { isThreatFixInProgress, isThreatFixStale } = useFixers();
-
- const handleClick = useCallback( () => {
- if ( ! isOpen ) {
- onOpen?.();
- }
- setOpen( current => ( current === id ? null : id ) );
- }, [ isOpen, onOpen, setOpen, id ] );
-
- const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] );
-
- return (
-
-
-
-
-
- { label }
-
-
- { title }
-
- { [ 'fixed', 'ignored' ].includes( status ) && (
-
- ) }
-
-
-
-
- { ! hideAutoFixColumn && fixable && (
-
- { renderFixerStatus( isThreatFixInProgress( id ), isThreatFixStale( id ) ) }
- { isSmall && { __( 'Auto-fix', 'jetpack-protect' ) } }
-
- ) }
-
-
-
-
-
- { children }
-
-
- );
-};
-
-const PaidAccordion = ( { children } ) => {
- const [ open, setOpen ] = useState();
-
- return (
-
- { children }
-
- );
-};
-
-export default PaidAccordion;
diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx
deleted file mode 100644
index 252f22b2bad77..0000000000000
--- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { wordpress, plugins } from '@wordpress/icons';
-import React from 'react';
-import PaidAccordion, { PaidAccordionItem } from '..';
-
-export default {
- title: 'Plugins/Protect/Paid Accordion',
- component: PaidAccordion,
- parameters: {
- layout: 'centered',
- },
- decorators: [
- Story => (
-
-
-
- ),
- ],
-};
-
-// eslint-disable-next-line no-unused-vars
-export const Default = args => (
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-
- What is the problem?
-
-
- Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or
- Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to
- perform to Stored Cross-Site Scripting attacks
-
-
- How to fix it?
-
- Update to WordPress 5.9.2
-
-
-);
diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss
deleted file mode 100644
index 8304942b206d4..0000000000000
--- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss
+++ /dev/null
@@ -1,202 +0,0 @@
-.accordion {
- display: inline-block;
- width: 100%;
- border-radius: var( --jp-border-radius );
- border: 1px solid var( --jp-gray );
-
- & > *:not(:last-child) {
- border-bottom: 1px solid var( --jp-gray );
- }
-}
-
-.accordion-item {
- background-color: var( --jp-white );
-}
-
-.accordion-header {
- margin: 0;
- display: grid;
- grid-template-columns: repeat(9, 1fr);
- cursor: pointer;
- box-sizing: border-box;
- background: none;
- border: none;
- width: 100%;
- align-items: center;
- outline-color: var( --jp-black );
- padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px
- text-align: start;
-
- >:first-of-type {
- grid-column: 1/7;
- }
-
- >:last-of-type {
- grid-column: 9;
- }
-
- >:not( :first-child ) {
- margin: auto;
- }
-
- &:hover {
- background: var( --jp-gray-0 );
- }
-}
-
-.accordion-header-label {
- display: flex;
- align-items: center;
- font-size: var( --font-body-small );
- font-weight: normal;
-}
-
-.accordion-header-label-icon {
- margin-right: var( --spacing-base ); // 8px
-}
-
-.accordion-header-description {
- font-weight: 600;
- margin-left: calc( var( --spacing-base ) * 4 ); // 32px
- margin-bottom: var( --spacing-base ); // 8px
-}
-
-.accordion-header-status {
- font-size: var( --font-body-small );
- font-weight: normal;
- margin-left: calc( var( --spacing-base ) * 4 ); // 32px
- margin-bottom: var( --spacing-base ); // 8px
-}
-
-.accordion-header-status-separator {
- display: inline-block;
- height: 4px;
- margin: 2px 12px;
- width: 4px;
- background-color: var( --jp-gray-50 );
-}
-
-.accordion-header-button {
- align-items: center;
-}
-
-.accordion-body {
- transform-origin: top center;
- overflow: hidden;
-
- &-close {
- transition: all .1s;
- max-height: 0;
- padding: 0;
- transform: scaleY(0);
- }
-
- &-open {
- transition: max-height .3s, transform .2s;
- padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px
- max-height: 1000px;
- transform: scaleY(1);
- }
-}
-
-.icon-check {
- fill: var( --jp-green-40 );
-}
-
-.status-badge {
- border-radius: 32px;
- flex-shrink: 0;
- font-size: 12px;
- font-style: normal;
- font-weight: 600;
- line-height: 16px;
- padding: calc( var( --spacing-base ) / 2 ); // 4px
- position: relative;
- text-align: center;
- width: 60px;
- margin-left: calc( var( --spacing-base ) * 4 ); // 32px
-
- &.fixed {
- color: var( --jp-white );
- background-color: #008a20;
- }
-
- &.ignored {
- color: var( --jp-white );
- background-color: var( --jp-gray-50 );
- }
-}
-
-.is-fixed {
- color: #008a20;
-}
-
-.support-link {
- color: inherit;
-
- &:focus,
- &:hover {
- color: inherit;
- box-shadow: none;
- }
-}
-
-.icon-tooltip {
- max-height: 20px;
- margin-left: calc( var( --spacing-base ) / 2 ); // 4px
-
- &__icon {
- color: var( --jp-red );
- }
-
- &__content {
- color: var( --jp-gray-70 );
- font-weight: 400;
- line-height: 24px;
- }
-}
-
-@media ( max-width: 599px ) {
- .accordion-header {
- display: grid;
- grid-auto-rows: minmax( auto, auto );
-
- >:first-child {
- grid-column: 1/8;
- grid-row: 1;
- }
-
- >:nth-child( 2 ) {
- padding-left: calc( var( --spacing-base ) * 4 ); // 32px
- grid-row: 2;
- }
-
- >:nth-child( 3 ) {
- grid-row: 2;
- }
-
- >:nth-child( 3 ) span {
- position: absolute;
- margin-top: var( --spacing-base ); // 8px
- }
-
- >:last-child {
- grid-column: 10;
- grid-row: 1/3;
- }
- }
-
- .status-badge {
- display: none;
- }
-}
-
-@media ( max-width: 1200px ) {
- .accordion-header-status {
- display: grid;
- }
-
- .accordion-header-status-separator {
- display: none;
- }
-}
diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx
index 3edd7911a0b6a..0f857430d92d8 100644
--- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx
+++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx
@@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n';
import React, { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import useConnectSiteMutation from '../../data/use-connection-mutation';
+import useProductDataQuery from '../../data/use-product-data-query';
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
/**
* Product Detail component.
@@ -30,7 +30,7 @@ const ConnectedPricingTable = () => {
} );
// Access paid protect product data
- const { jetpackScan } = useProtectData();
+ const { data: jetpackScan } = useProductDataQuery();
const { pricingForUi } = jetpackScan;
const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi;
diff --git a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx
deleted file mode 100644
index d1100d8ce6d5e..0000000000000
--- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { type JSX } from 'react';
-
-/**
- * Protect Shield and Checkmark SVG Icon
- *
- * @return {JSX.Element} Protect Shield and Checkmark SVG Icon
- */
-export default function ProtectCheck(): JSX.Element {
- return (
-
-
-
-
- );
-}
diff --git a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx b/projects/plugins/protect/src/js/components/scan-navigation/index.jsx
deleted file mode 100644
index e626b6af066c7..0000000000000
--- a/projects/plugins/protect/src/js/components/scan-navigation/index.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { __ } from '@wordpress/i18n';
-import React, { useCallback } from 'react';
-import { useLocation, useNavigate } from 'react-router-dom';
-import usePlan from '../../hooks/use-plan';
-import ButtonGroup from '../button-group';
-
-/**
- * Navigation for scan sections.
- *
- * @return {React.Element} The React Component.
- */
-export default function ScanNavigation() {
- const navigate = useNavigate();
- const location = useLocation();
- const { hasPlan } = usePlan();
-
- const viewingScanPage = location.pathname === '/scan';
- const viewingHistoryPage = location.pathname.includes( '/scan/history' );
- const navigateToScanPage = useCallback( () => navigate( '/scan' ), [ navigate ] );
- const navigateToHistoryPage = useCallback( () => navigate( '/scan/history' ), [ navigate ] );
-
- if ( ! hasPlan || ( ! viewingScanPage && ! viewingHistoryPage ) ) {
- return null;
- }
-
- return (
- <>
-
-
- { __( 'Scanner', 'jetpack-protect' ) }
-
-
- { __( 'History', 'jetpack-protect' ) }
-
-
- >
- );
-}
diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx
index bc5e0107cea80..45a8524e60b59 100644
--- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx
+++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx
@@ -1,6 +1,7 @@
import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components';
+import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan';
+import { Icon } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
-import { Icon } from '@wordpress/icons';
import React, { useState, useCallback } from 'react';
import styles from './styles.module.scss';
@@ -65,10 +66,10 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } )
return (
<>
-
+
- { threat.label }
+ { getThreatSubtitle( threat ) }
{ getFixerMessage( threat.fixable ) }
diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx
deleted file mode 100644
index 2d493b11e64a4..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import { H3, Text } from '@automattic/jetpack-components';
-import { createInterpolateElement } from '@wordpress/element';
-import { sprintf, __, _n } from '@wordpress/i18n';
-import { useMemo, useState } from 'react';
-import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query';
-import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
-import OnboardingPopover from '../onboarding-popover';
-import ScanButton from '../scan-button';
-import styles from './styles.module.scss';
-
-const ProtectCheck = () => (
-
-
-
-
-);
-
-/**
- * Time Since
- *
- * @param {string} date - The past date to compare to the current date.
- * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago".
- */
-const timeSince = date => {
- 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-protect' ),
- 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-protect' ),
- 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-protect' ),
- 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-protect' ),
- 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-protect' ),
- Math.floor( interval )
- );
- }
-
- return __( 'a few seconds ago', 'jetpack-protect' );
-};
-
-const EmptyList = () => {
- const { lastChecked } = useProtectData();
- const { hasPlan } = usePlan();
- const { data: status } = useScanStatusQuery();
-
- const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] =
- useState( null );
-
- const timeSinceLastScan = useMemo( () => {
- return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null;
- }, [ lastChecked ] );
-
- return (
-
-
-
- { __( "Don't worry about a thing", 'jetpack-protect' ) }
-
-
- { timeSinceLastScan
- ? createInterpolateElement(
- sprintf(
- // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago".
- __(
- 'The last Protect scan ran %s and everything looked great.',
- 'jetpack-protect'
- ),
- timeSinceLastScan
- ),
- {
- strong: ,
- }
- )
- : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) }
-
- { hasPlan && (
- <>
-
- { ! isScanInProgress( status ) && (
-
- ) }
- >
- ) }
-
- );
-};
-
-export default EmptyList;
diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx
deleted file mode 100644
index 88d4a92f9bac5..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import React, { useCallback } from 'react';
-import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
-import usePlan from '../../hooks/use-plan';
-import FreeAccordion, { FreeAccordionItem } from '../free-accordion';
-import Pagination from './pagination';
-import styles from './styles.module.scss';
-
-const ThreatAccordionItem = ( {
- description,
- fixedIn,
- icon,
- id,
- label,
- name,
- source,
- title,
- type,
-} ) => {
- const { recordEvent } = useAnalyticsTracks();
- const { upgradePlan } = usePlan();
-
- const getScan = useCallback( () => {
- recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' );
- upgradePlan();
- }, [ recordEvent, upgradePlan ] );
-
- const learnMoreButton = source ? (
-
- { __( 'See more technical details of this threat', 'jetpack-protect' ) }
-
- ) : null;
-
- return (
- {
- if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) {
- return;
- }
- recordEvent( `jetpack_protect_${ type }_threat_open` );
- }, [ recordEvent, type ] ) }
- >
- { description && (
-
-
- { __( 'What is the problem?', 'jetpack-protect' ) }
-
- { description }
- { learnMoreButton }
-
- ) }
- { fixedIn && (
-
-
- { __( 'How to fix it?', 'jetpack-protect' ) }
-
-
- {
- /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */
- sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn )
- }
-
-
-
- ) }
- { ! description && { learnMoreButton }
}
-
- );
-};
-
-const FreeList = ( { list } ) => {
- return (
-
- { ( { currentItems } ) => (
-
- { currentItems.map(
- ( {
- description,
- fixedIn,
- icon,
- id,
- label,
- name,
- source,
- table,
- title,
- type,
- version,
- } ) => (
-
- )
- ) }
-
- ) }
-
- );
-};
-
-export default FreeList;
diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx
deleted file mode 100644
index 2823a804c1412..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/index.jsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import {
- Container,
- Col,
- Title,
- Button,
- useBreakpointMatch,
- Text,
-} from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import React, { useCallback, useMemo, useState } from 'react';
-import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query';
-import useFixers from '../../hooks/use-fixers';
-import useModal from '../../hooks/use-modal';
-import usePlan from '../../hooks/use-plan';
-import OnboardingPopover from '../onboarding-popover';
-import ScanButton from '../scan-button';
-import EmptyList from './empty';
-import FreeList from './free-list';
-import ThreatsNavigation from './navigation';
-import PaidList from './paid-list';
-import styles from './styles.module.scss';
-import useThreatsList from './use-threats-list';
-
-const ThreatsList = () => {
- const { hasPlan } = usePlan();
- const { item, list, selected, setSelected } = useThreatsList();
- const [ isSm ] = useBreakpointMatch( 'sm' );
- const { isThreatFixInProgress, isThreatFixStale } = useFixers();
-
- const { data: status } = useScanStatusQuery();
- const scanning = isScanInProgress( status );
-
- // List of fixable threats that do not have a fix in progress
- const fixableList = useMemo( () => {
- return list.filter( threat => {
- const threatId = parseInt( threat.id );
- return (
- threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId )
- );
- } );
- }, [ list, isThreatFixInProgress, isThreatFixStale ] );
-
- // Popover anchors
- const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null );
- const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null );
- const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null );
- const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] =
- useState( null );
-
- const { setModal } = useModal();
-
- const handleShowAutoFixersClick = threatList => {
- return event => {
- event.preventDefault();
- setModal( {
- type: 'FIX_ALL_THREATS',
- props: { threatList },
- } );
- };
- };
-
- const getTitle = useCallback( () => {
- switch ( selected ) {
- case 'all':
- if ( list.length === 1 ) {
- return __( 'All threats', 'jetpack-protect' );
- }
- return sprintf(
- /* translators: placeholder is the amount of threats found on the site. */
- __( 'All %s threats', 'jetpack-protect' ),
- list.length
- );
- case 'core':
- return sprintf(
- /* translators: placeholder is the amount of WordPress threats found on the site. */
- __( '%1$s WordPress %2$s', 'jetpack-protect' ),
- list.length,
- list.length === 1 ? 'threat' : 'threats'
- );
- case 'files':
- return sprintf(
- /* translators: placeholder is the amount of file threats found on the site. */
- __( '%1$s file %2$s', 'jetpack-protect' ),
- list.length,
- list.length === 1 ? 'threat' : 'threats'
- );
- case 'database':
- return sprintf(
- /* translators: placeholder is the amount of database threats found on the site. */
- __( '%1$s database %2$s', 'jetpack-protect' ),
- list.length,
- list.length === 1 ? 'threat' : 'threats'
- );
- default:
- return sprintf(
- /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */
- __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ),
- list.length,
- list.length === 1 ? 'threat' : 'threats',
- item?.name,
- item?.version
- );
- }
- }, [ selected, list, item ] );
-
- return (
-
-
-
-
-
- { ! scanning && (
-
- ) }
-
-
- { list?.length > 0 ? (
- <>
-
-
{ getTitle() }
- { hasPlan && (
-
- { fixableList.length > 0 && (
- <>
-
- { sprintf(
- /* translators: Translates to Show auto fixers $s: Number of fixable threats. */
- __( 'Show auto fixers (%s)', 'jetpack-protect' ),
- fixableList.length
- ) }
-
- { ! scanning && (
-
- ) }
-
- { ! scanning && (
-
- ) }
- >
- ) }
-
- ) }
-
- { hasPlan ? (
- <>
-
-
-
-
- { __(
- 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.',
- 'jetpack-protect'
- ) }
-
-
-
-
- { ! scanning && (
-
- ) }
- >
- ) : (
-
- ) }
- >
- ) : (
-
- ) }
-
-
- );
-};
-
-export default ThreatsList;
diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx
deleted file mode 100644
index 9befe85a78612..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import { useBreakpointMatch } from '@automattic/jetpack-components';
-import { __ } from '@wordpress/i18n';
-import {
- wordpress as coreIcon,
- plugins as pluginsIcon,
- warning as warningIcon,
- color as themesIcon,
- code as filesIcon,
-} from '@wordpress/icons';
-import { useCallback, useMemo } from 'react';
-import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
-import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
-import Navigation, { NavigationItem, NavigationGroup } from '../navigation';
-
-const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => {
- const { hasPlan } = usePlan();
- const {
- results: { plugins, themes },
- counts: {
- current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats },
- },
- } = useProtectData( { sourceType, filter: { status: statusFilter } } );
-
- const { recordEvent } = useAnalyticsTracks();
- const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' );
-
- const trackNavigationClickAll = useCallback( () => {
- recordEvent( 'jetpack_protect_navigation_all_click' );
- }, [ recordEvent ] );
-
- const trackNavigationClickCore = useCallback( () => {
- recordEvent( 'jetpack_protect_navigation_core_click' );
- }, [ recordEvent ] );
-
- const trackNavigationClickPlugin = useCallback( () => {
- recordEvent( 'jetpack_protect_navigation_plugin_click' );
- }, [ recordEvent ] );
-
- const trackNavigationClickTheme = useCallback( () => {
- recordEvent( 'jetpack_protect_navigation_theme_click' );
- }, [ recordEvent ] );
-
- const trackNavigationClickFiles = useCallback( () => {
- recordEvent( 'jetpack_protect_navigation_file_click' );
- }, [ recordEvent ] );
-
- const allLabel = useMemo( () => {
- if ( statusFilter === 'fixed' ) {
- return __( 'All fixed threats', 'jetpack-protect' );
- }
- if ( statusFilter === 'ignored' ) {
- return __(
- 'All ignored threats',
- 'jetpack-protect',
- /** dummy arg to avoid bad minification */ 0
- );
- }
- return __( 'All threats', 'jetpack-protect' );
- }, [ statusFilter ] );
-
- return (
-
-
-
-
- { plugins.map( ( { name, threats, checked } ) => (
-
- ) ) }
-
-
- { themes.map( ( { name, threats, checked } ) => (
-
- ) ) }
-
- { hasPlan && (
- <>
-
- >
- ) }
-
- );
-};
-
-export default ThreatsNavigation;
diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx
deleted file mode 100644
index 3e17bed0eeac4..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import { Button, useBreakpointMatch } from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import { chevronLeft, chevronRight } from '@wordpress/icons';
-import React, { useCallback, useState, useMemo } from 'react';
-import styles from './styles.module.scss';
-
-const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => {
- const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] );
-
- const handleClick = useCallback( () => {
- onPageChange( pageNumber );
- }, [ onPageChange, pageNumber ] );
-
- return (
-
- { pageNumber }
-
- );
-};
-
-const Pagination = ( { list, itemPerPage = 10, children } ) => {
- const [ isSm ] = useBreakpointMatch( 'sm' );
-
- const [ currentPage, setCurrentPage ] = useState( 1 );
-
- const handlePreviousPageClick = useCallback(
- () => setCurrentPage( currentPage - 1 ),
- [ currentPage, setCurrentPage ]
- );
- const handleNextPageClick = useCallback(
- () => setCurrentPage( currentPage + 1 ),
- [ currentPage, setCurrentPage ]
- );
-
- const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] );
-
- const currentItems = useMemo( () => {
- const indexOfLastItem = currentPage * itemPerPage;
- const indexOfFirstItem = indexOfLastItem - itemPerPage;
- return list.slice( indexOfFirstItem, indexOfLastItem );
- }, [ currentPage, list, itemPerPage ] );
-
- const pageNumbers = useMemo( () => {
- if ( isSm ) {
- return [ currentPage ];
- }
-
- const result = [ 1 ];
- if ( currentPage > 3 && totalPages > 4 ) {
- result.push( '…' );
- }
-
- if ( currentPage === 1 ) {
- // Current page is the first page.
- // i.e. [ 1 ] 2 3 4 ... 10
- result.push( currentPage + 1, currentPage + 2, currentPage + 3 );
- } else if ( currentPage === 2 ) {
- // Current page is the second to first page.
- // i.e. 1 [ 2 ] 3 4 ... 10
- result.push( currentPage, currentPage + 1, currentPage + 2 );
- } else if ( currentPage < totalPages - 1 ) {
- // Current page is positioned in the middle of the pagination.
- // i.e. 1 ... 3 [ 4 ] 5 ... 10
- result.push( currentPage - 1, currentPage, currentPage + 1 );
- } else if ( currentPage === totalPages - 1 ) {
- // Current page is the second to last page.
- // i.e. 1 ... 7 8 [ 9 ] 10
- currentPage > 3 && result.push( currentPage - 2 );
- currentPage > 2 && result.push( currentPage - 1 );
- result.push( currentPage );
- } else if ( currentPage === totalPages ) {
- // Current page is the last page.
- // i.e. 1 ... 7 8 9 [ 10 ]
- currentPage >= 5 && result.push( currentPage - 3 );
- currentPage >= 4 && result.push( currentPage - 2 );
- result.push( currentPage - 1 );
- }
-
- if ( result[ result.length - 1 ] < totalPages - 1 ) {
- result.push( '…' );
- result.push( totalPages );
- } else if ( result[ result.length - 1 ] < totalPages ) {
- result.push( totalPages );
- }
-
- return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) );
- }, [ currentPage, isSm, totalPages ] );
-
- return (
- <>
- { children( { currentItems } ) }
- { totalPages > 1 && (
-
-
- { pageNumbers.map( ( pageNumber, index ) =>
- typeof pageNumber === 'number' ? (
-
- ) : (
- { pageNumber }
- )
- ) }
-
-
- ) }
- >
- );
-};
-
-export default Pagination;
diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx
deleted file mode 100644
index baedf8dfa5184..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx
+++ /dev/null
@@ -1,253 +0,0 @@
-import {
- Text,
- Button,
- DiffViewer,
- MarkedLines,
- useBreakpointMatch,
-} from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import React, { useCallback } from 'react';
-import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
-import useFixers from '../../hooks/use-fixers';
-import useModal from '../../hooks/use-modal';
-import PaidAccordion, { PaidAccordionItem } from '../paid-accordion';
-import Pagination from './pagination';
-import styles from './styles.module.scss';
-
-const ThreatAccordionItem = ( {
- context,
- description,
- diff,
- filename,
- firstDetected,
- fixedIn,
- fixedOn,
- icon,
- fixable,
- id,
- label,
- name,
- source,
- title,
- type,
- severity,
- status,
- hideAutoFixColumn = false,
-} ) => {
- const { setModal } = useModal();
- const { recordEvent } = useAnalyticsTracks();
-
- const { isThreatFixInProgress, isThreatFixStale } = useFixers();
- const isActiveFixInProgress = isThreatFixInProgress( id );
- const isStaleFixInProgress = isThreatFixStale( id );
-
- const learnMoreButton = source ? (
-
- { __( 'See more technical details of this threat', 'jetpack-protect' ) }
-
- ) : null;
-
- const handleIgnoreThreatClick = () => {
- return event => {
- event.preventDefault();
- setModal( {
- type: 'IGNORE_THREAT',
- props: { id, label, title, icon, severity },
- } );
- };
- };
-
- const handleUnignoreThreatClick = () => {
- return event => {
- event.preventDefault();
- setModal( {
- type: 'UNIGNORE_THREAT',
- props: { id, label, title, icon, severity },
- } );
- };
- };
-
- const handleFixThreatClick = () => {
- return event => {
- event.preventDefault();
- setModal( {
- type: 'FIX_THREAT',
- props: { id, fixable, label, icon, severity },
- } );
- };
- };
-
- return (
- {
- if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) {
- return;
- }
- recordEvent( `jetpack_protect_${ type }_threat_open` );
- }, [ recordEvent, type ] ) }
- hideAutoFixColumn={ hideAutoFixColumn }
- >
- { description && (
-
-
- { status !== 'fixed'
- ? __( 'What is the problem?', 'jetpack-protect' )
- : __(
- 'What was the problem?',
- 'jetpack-protect',
- /** dummy arg to avoid bad minification */ 0
- ) }
-
- { description }
- { learnMoreButton }
-
- ) }
- { ( filename || context || diff ) && (
-
- { __( 'The technical details', 'jetpack-protect' ) }
-
- ) }
- { filename && (
- <>
-
- {
- /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */
- __( 'Threat found in file:', 'jetpack-protect' )
- }
-
- { filename }
- >
- ) }
- { context && }
- { diff && }
- { fixedIn && status !== 'fixed' && (
-
-
- { __( 'How to fix it?', 'jetpack-protect' ) }
-
-
- {
- /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */
- sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn )
- }
-
-
- ) }
- { ! description && { learnMoreButton }
}
- { [ 'ignored', 'current' ].includes( status ) && (
-
- { 'ignored' === status && (
-
- { __( 'Unignore threat', 'jetpack-protect' ) }
-
- ) }
- { 'current' === status && (
- <>
-
- { __( 'Ignore threat', 'jetpack-protect' ) }
-
- { fixable && (
-
- { __( 'Fix threat', 'jetpack-protect' ) }
-
- ) }
- >
- ) }
-
- ) }
-
- );
-};
-
-const PaidList = ( { list, hideAutoFixColumn = false } ) => {
- const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] );
-
- return (
- <>
- { ! isSmall && (
-
- { __( 'Details', 'jetpack-protect' ) }
- { __( 'Severity', 'jetpack-protect' ) }
- { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } }
-
-
- ) }
-
- { ( { currentItems } ) => (
-
- { currentItems.map(
- ( {
- context,
- description,
- diff,
- filename,
- firstDetected,
- fixedIn,
- fixedOn,
- icon,
- fixable,
- id,
- label,
- name,
- severity,
- source,
- table,
- title,
- type,
- version,
- status,
- } ) => (
-
- )
- ) }
-
- ) }
-
- >
- );
-};
-
-export default PaidList;
diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss
deleted file mode 100644
index 4a50d87b2562b..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss
+++ /dev/null
@@ -1,129 +0,0 @@
-.empty {
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
- max-height: 600px;
- flex-direction: column;
-}
-
-.threat-section + .threat-section {
- margin-top: calc( var( --spacing-base ) * 5 ); // 40px
-}
-
-.threat-filename {
- background-color: var( --jp-gray-0 );
- padding: calc( var( --spacing-base ) * 3 ); // 24px
- overflow-x: scroll;
-}
-
-.threat-footer {
- display: flex;
- justify-content: flex-end;
- border-top: 1px solid var( --jp-gray );
- padding-top: calc( var( --spacing-base ) * 3 ); // 24px
- margin-top: calc( var( --spacing-base ) * 3 ); // 24px
-}
-.threat-item-cta {
- margin-top: calc( var( --spacing-base ) * 4 ); // 36px
-}
-
-.list-header {
- display: flex;
- align-items: flex-end;
- margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px
-}
-
-.list-title {
- flex: 1;
- margin-bottom: 0;
-}
-
-.list-header__controls {
- display: flex;
- gap: calc( var( --spacing-base ) * 2 ); // 16px
-}
-
-.threat-footer {
- width: 100%;
- display: flex;
- justify-content: right;
- padding-top: calc( var( --spacing-base ) * 4 ); // 32px
- border-top: 1px solid var( --jp-gray );
-
- > :last-child {
- margin-left: calc( var( --spacing-base ) * 2 ); // 16px
- }
-}
-
-.accordion-header {
- display: grid;
- grid-template-columns: repeat( 9, 1fr );
- background-color: white;
- padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px
- border: 1px solid var( --jp-gray );
- border-bottom: none;
- color: var( --jp-gray-50 );
- width: 100%;
-
- > span:first-child {
- grid-column: 1 / 7;
- }
-
- > span:not( :first-child ) {
- text-align: center;
- }
-}
-
-.manual-scan {
- margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px
- text-align: center;
-}
-
-@media ( max-width: 599px ) {
-
- .list-header {
- margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px
- }
-
- .list-title {
- display: none;
- }
-
- .threat-footer {
- justify-content: center;
-
- > * {
- width: 50%;
- }
- }
-}
-
-.pagination-container {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 4px;
- margin-top: calc( var( --spacing-base ) * 4 ); // 24px
- margin-bottom: calc(var(--spacing-base) * 2); // 16px
-
- button {
- font-size: var( --font-body );
- width: auto;
- height: auto;
- padding: 0 var( --spacing-base ); // 0 | 8px
- line-height: 32px;
- min-width: 32px;
-
- &.unfocused {
- color: var( --jp-black );
- background: none;
-
- &:hover:not(:disabled) {
- color: var( --jp-black );
- background: none;
- }
- }
- }
-}
diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js
deleted file mode 100644
index de000288251ae..0000000000000
--- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import {
- plugins as pluginsIcon,
- wordpress as coreIcon,
- color as themesIcon,
- code as filesIcon,
- grid as databaseIcon,
-} from '@wordpress/icons';
-import { useEffect, useMemo, useState } from 'react';
-import useProtectData from '../../hooks/use-protect-data';
-
-const sortThreats = ( a, b ) => b.severity - a.severity;
-
-/**
- * Flatten threats data
- *
- * Merges threat category data with each threat it contains, plus any additional data provided.
- *
- * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc.
- * @param {object} newData - Additional data to add to each threat.
- * @return {object[]} Array of threats with additional properties from the threat category and function argument.
- */
-const flattenThreats = ( data, newData ) => {
- // If "data" is an empty object
- if ( typeof data === 'object' && Object.keys( data ).length === 0 ) {
- return [];
- }
-
- // If "data" has multiple entries, recursively flatten each one.
- if ( Array.isArray( data ) ) {
- return data.map( extension => flattenThreats( extension, newData ) ).flat();
- }
-
- // Merge the threat category data with each threat it contains, plus any additional data provided.
- return data?.threats.map( threat => ( {
- ...threat,
- ...data,
- ...newData,
- } ) );
-};
-
-/**
- * Threats List Hook
- *
- * @param {object} args - Arguments for the hook.
- * @param {string} args.source - "scan" or "history".
- * @param {string} args.status - "all", "fixed", or "ignored".
- * ---
- * @typedef {object} UseThreatsList
- * @property {object} item - The selected threat category.
- * @property {object[]} list - The list of threats to display.
- * @property {string} selected - The selected threat category.
- * @property {Function} setSelected - Sets the selected threat category.
- * ---
- * @return {UseThreatsList} useThreatsList hook.
- */
-const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => {
- const [ selected, setSelected ] = useState( 'all' );
- const {
- results: { plugins, themes, core, files, database },
- } = useProtectData( {
- sourceType: source,
- filter: { status, key: selected },
- } );
-
- const { unsortedList, item } = useMemo( () => {
- // If a specific threat category is selected, filter for and flatten the category's threats.
- if ( selected && selected !== 'all' ) {
- // Core, files, and database data threats are already grouped together,
- // so we just need to flatten them and add the appropriate icon.
- switch ( selected ) {
- case 'core':
- return {
- unsortedList: flattenThreats( core, { icon: coreIcon } ),
- item: core,
- };
- case 'files':
- return {
- unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ),
- item: files,
- };
- case 'database':
- return {
- unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ),
- item: database,
- };
- default:
- break;
- }
-
- // Extensions (i.e. plugins and themes) have entries for each individual extension,
- // so we need to check for a matching threat in each extension.
- const selectedPlugin = plugins.find( plugin => plugin?.name === selected );
- if ( selectedPlugin ) {
- return {
- unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ),
- item: selectedPlugin,
- };
- }
- const selectedTheme = themes.find( theme => theme?.name === selected );
- if ( selectedTheme ) {
- return {
- unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ),
- item: selectedTheme,
- };
- }
- }
-
- // Otherwise, return all threats.
- return {
- unsortedList: [
- ...flattenThreats( core, { icon: coreIcon } ),
- ...flattenThreats( plugins, { icon: pluginsIcon } ),
- ...flattenThreats( themes, { icon: themesIcon } ),
- ...flattenThreats( { threats: files }, { icon: filesIcon } ),
- ...flattenThreats( { threats: database }, { icon: databaseIcon } ),
- ],
- item: null,
- };
- }, [ core, database, files, plugins, selected, themes ] );
-
- const getLabel = threat => {
- if ( threat.name && threat.version ) {
- // Extension threat i.e. "Woocommerce (3.0.0)"
- return `${ threat.name } (${ threat.version })`;
- }
-
- if ( threat.filename ) {
- // File threat i.e. "index.php"
- return threat.filename.split( '/' ).pop();
- }
-
- if ( threat.table ) {
- // Database threat i.e. "wp_posts"
- return threat.table;
- }
- };
-
- const list = useMemo( () => {
- return unsortedList
- .sort( sortThreats )
- .map( threat => ( { label: getLabel( threat ), ...threat } ) );
- }, [ unsortedList ] );
-
- useEffect( () => {
- if ( selected !== 'all' && status !== 'all' && list.length === 0 ) {
- setSelected( 'all' );
- }
- }, [ selected, status, item, list ] );
-
- return {
- item,
- list,
- selected,
- setSelected,
- };
-};
-
-export default useThreatsList;
diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx
index 81f1eabb27d5b..7f1ef3652bb85 100644
--- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx
+++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx
@@ -1,4 +1,5 @@
import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components';
+import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan';
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import { useState } from 'react';
@@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal';
import UserConnectionGate from '../user-connection-gate';
import styles from './styles.module.scss';
-const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
+const UnignoreThreatModal = ( { threat } ) => {
const { setModal } = useModal();
+
+ const icon = getThreatIcon( threat );
+
+ const [ isUnignoring, setIsUnignoring ] = useState( false );
const unignoreThreatMutation = useUnIgnoreThreatMutation();
+
const handleCancelClick = () => {
return event => {
event.preventDefault();
@@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
};
};
- const [ isUnignoring, setIsUnignoring ] = useState( false );
-
const handleUnignoreClick = () => {
return async event => {
event.preventDefault();
setIsUnignoring( true );
- await unignoreThreatMutation.mutateAsync( id );
+ await unignoreThreatMutation.mutateAsync( threat.id );
setModal( { type: null } );
setIsUnignoring( false );
};
@@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
- { label }
+ { getThreatSubtitle( threat ) }
- { title }
+ { threat.title }
-
+
diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts
deleted file mode 100644
index 2338d306e6780..0000000000000
--- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import { type ExtensionStatus, type Threat, type ThreatStatus } from '@automattic/jetpack-scan';
-import { __ } from '@wordpress/i18n';
-import { useMemo } from 'react';
-import useHistoryQuery from '../../data/scan/use-history-query';
-import useScanStatusQuery from '../../data/scan/use-scan-status-query';
-import useProductDataQuery from '../../data/use-product-data-query';
-
-type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string;
-
-type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' };
-
-// Valid "key" values for filtering.
-const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ];
-
-/**
- * Filter Extension Threats
- *
- * @param {Array} threats - The threats to filter.
- * @param {object} filter - The filter to apply to the data.
- * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'.
- * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name.
- * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name.
- *
- * @return {Array} The filtered threats.
- */
-const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => {
- if ( ! Array.isArray( threats ) ) {
- return [];
- }
-
- return threats.filter( threat => {
- if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) {
- return false;
- }
- if ( filter.key && filter.key !== 'all' && filter.key !== key ) {
- return false;
- }
- return true;
- } );
-};
-
-/**
- * Get parsed data from the initial state
- *
- * @param {object} options - The options to use when getting the data.
- * @param {string} options.sourceType - 'scan' or 'history'.
- * @param {object} options.filter - The filter to apply to the data.
- * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'.
- * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name.
- *
- * @return {object} The information available in Protect's initial state.
- */
-export default function useProtectData(
- { sourceType, filter } = {
- sourceType: 'scan',
- filter: { status: null, key: null },
- }
-) {
- const { data: status } = useScanStatusQuery();
- const { data: scanHistory } = useHistoryQuery();
- const { data: jetpackScan } = useProductDataQuery();
-
- const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => {
- // This hook can provide data from two sources: the current scan or the scan history.
- const data = sourceType === 'history' ? { ...scanHistory } : { ...status };
-
- // Prepare the result object.
- const result = {
- results: {
- core: [],
- plugins: [],
- themes: [],
- files: [],
- database: [],
- },
- counts: {
- all: {
- threats: 0,
- core: 0,
- plugins: 0,
- themes: 0,
- files: 0,
- database: 0,
- },
- current: {
- threats: 0,
- core: 0,
- plugins: 0,
- themes: 0,
- files: 0,
- database: 0,
- },
- },
- error: null,
- lastChecked: data.lastChecked || null,
- hasUncheckedItems: data.hasUncheckedItems || false,
- };
-
- // Loop through the provided extensions, and update the result object.
- const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => {
- if ( ! Array.isArray( extensions ) ) {
- return [];
- }
- extensions.forEach( extension => {
- // Update the total counts.
- result.counts.all[ key ] += extension?.threats?.length || 0;
- result.counts.all.threats += extension?.threats?.length || 0;
-
- // Filter the extension's threats based on the current filters.
- const filteredThreats = filterThreats(
- extension?.threats || [],
- filter,
- KEY_FILTERS.includes( filter.key ) ? key : extension?.name
- );
-
- // Update the result object with the extension and its filtered threats.
- result.results[ key ].push( { ...extension, threats: filteredThreats } );
-
- // Update the current counts.
- result.counts.current[ key ] += filteredThreats.length;
- result.counts.current.threats += filteredThreats.length;
- } );
- };
-
- // Loop through the provided threats, and update the result object.
- const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => {
- if ( ! Array.isArray( threatsToProcess ) ) {
- return [];
- }
-
- result.counts.all[ key ] += threatsToProcess.length;
- result.counts.all.threats += threatsToProcess.length;
-
- const filteredThreats = filterThreats( threatsToProcess, filter, key );
-
- result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ];
- result.counts.current[ key ] += filteredThreats.length;
- result.counts.current.threats += filteredThreats.length;
- };
-
- // Core data may be either a single object or an array of multiple objects.
- let cores = Array.isArray( data.core ) ? data.core : [];
- if ( data?.core?.threats ) {
- cores = [ data.core ];
- }
-
- // Process the data
- processExtensions( cores, 'core' );
- processExtensions( data?.plugins, 'plugins' );
- processExtensions( data?.themes, 'themes' );
- processThreats( data?.files, 'files' );
- processThreats( data?.database, 'database' );
-
- // Handle errors
- if ( data.error ) {
- result.error = {
- message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ),
- code: data.errorCode || 500,
- };
- }
-
- return result;
- }, [ scanHistory, sourceType, status, filter ] );
-
- return {
- results,
- counts,
- error,
- lastChecked,
- hasUncheckedItems,
- jetpackScan,
- };
-}
diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx
index b8983d65bb836..2b91f4b090b92 100644
--- a/projects/plugins/protect/src/js/index.tsx
+++ b/projects/plugins/protect/src/js/index.tsx
@@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import * as WPElement from '@wordpress/element';
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom';
import Modal from './components/modal';
import PaidPlanGate from './components/paid-plan-gate';
@@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding';
import { CheckoutProvider } from './hooks/use-plan';
import FirewallRoute from './routes/firewall';
import ScanRoute from './routes/scan';
-import ScanHistoryRoute from './routes/scan/history';
import SetupRoute from './routes/setup';
import './styles.module.scss';
@@ -62,7 +61,7 @@ function render() {
path="/scan/history"
element={
-
+
}
/>
@@ -70,7 +69,7 @@ function render() {
path="/scan/history/:filter"
element={
-
+
}
/>
diff --git a/projects/plugins/protect/src/js/routes/firewall/index.jsx b/projects/plugins/protect/src/js/routes/firewall/index.jsx
index 53af919b17f78..52ec1333bd958 100644
--- a/projects/plugins/protect/src/js/routes/firewall/index.jsx
+++ b/projects/plugins/protect/src/js/routes/firewall/index.jsx
@@ -22,7 +22,6 @@ import useWafUpgradeSeenMutation from '../../data/waf/use-waf-upgrade-seen-mutat
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import usePlan from '../../hooks/use-plan';
import useWafData from '../../hooks/use-waf-data';
-import ScanFooter from '../scan/scan-footer';
import FirewallAdminSectionHero from './firewall-admin-section-hero';
import FirewallFooter from './firewall-footer';
import styles from './styles.module.scss';
@@ -597,7 +596,7 @@ const FirewallPage = () => {
- { wafSupported ? : }
+ { wafSupported && }
);
};
diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx
deleted file mode 100644
index 141c51cde284a..0000000000000
--- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Text } from '@automattic/jetpack-components';
-import { dateI18n } from '@wordpress/date';
-import { __, sprintf } from '@wordpress/i18n';
-import { useMemo } from 'react';
-import { useParams } from 'react-router-dom';
-import AdminSectionHero from '../../../components/admin-section-hero';
-import ErrorAdminSectionHero from '../../../components/error-admin-section-hero';
-import useThreatsList from '../../../components/threats-list/use-threats-list';
-import useProtectData from '../../../hooks/use-protect-data';
-import styles from './styles.module.scss';
-
-const HistoryAdminSectionHero: React.FC = () => {
- const { filter = 'all' } = useParams();
- const { list } = useThreatsList( {
- source: 'history',
- status: filter,
- } );
- const { counts, error } = useProtectData( {
- sourceType: 'history',
- filter: { status: filter },
- } );
- const { threats: numAllThreats } = counts.all;
-
- const oldestFirstDetected = useMemo( () => {
- if ( ! list.length ) {
- return null;
- }
-
- return list.reduce( ( oldest, current ) => {
- return new Date( current.firstDetected ) < new Date( oldest.firstDetected )
- ? current
- : oldest;
- } ).firstDetected;
- }, [ list ] );
-
- if ( error ) {
- return (
-
- );
- }
-
- return (
-
-
- { oldestFirstDetected ? (
-
- { sprintf(
- /* translators: %s: Oldest first detected date */
- __( '%s - Today', 'jetpack-protect' ),
- dateI18n( 'F jS g:i A', oldestFirstDetected, false )
- ) }
-
- ) : (
- __( 'Most recent results', 'jetpack-protect' )
- ) }
-
-
- { numAllThreats > 0
- ? sprintf(
- /* translators: %s: Total number of threats */
- __( '%1$s previous %2$s', 'jetpack-protect' ),
- numAllThreats,
- numAllThreats === 1 ? 'threat' : 'threats'
- )
- : __( 'No previous threats', 'jetpack-protect' ) }
-
-
-
- { __( 'Here you can view all of your threats till this date.', 'jetpack-protect' ) }
-
-
- >
- }
- />
- );
-};
-
-export default HistoryAdminSectionHero;
diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx
deleted file mode 100644
index 723f9de9ab230..0000000000000
--- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx
+++ /dev/null
@@ -1,301 +0,0 @@
-import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components';
-import { __, _n, sprintf } from '@wordpress/i18n';
-import { useCallback } from 'react';
-import { Navigate, useParams } from 'react-router-dom';
-import AdminPage from '../../../components/admin-page';
-import ProtectCheck from '../../../components/protect-check-icon';
-import ThreatsNavigation from '../../../components/threats-list/navigation';
-import PaidList from '../../../components/threats-list/paid-list';
-import useThreatsList from '../../../components/threats-list/use-threats-list';
-import useAnalyticsTracks from '../../../hooks/use-analytics-tracks';
-import usePlan from '../../../hooks/use-plan';
-import useProtectData from '../../../hooks/use-protect-data';
-import ScanFooter from '../scan-footer';
-import HistoryAdminSectionHero from './history-admin-section-hero';
-import StatusFilters from './status-filters';
-import styles from './styles.module.scss';
-
-const ScanHistoryRoute = () => {
- // Track page view.
- useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } );
-
- const { hasPlan } = usePlan();
- const { filter = 'all' } = useParams();
-
- const { item, list, selected, setSelected } = useThreatsList( {
- source: 'history',
- status: filter,
- } );
-
- const { counts, error } = useProtectData( {
- sourceType: 'history',
- filter: { status: filter },
- } );
- const { threats: numAllThreats } = counts.all;
-
- const { counts: fixedCounts } = useProtectData( {
- sourceType: 'history',
- filter: { status: 'fixed', key: selected },
- } );
- const { threats: numFixed } = fixedCounts.current;
-
- const { counts: ignoredCounts } = useProtectData( {
- sourceType: 'history',
- filter: { status: 'ignored', key: selected },
- } );
- const { threats: numIgnored } = ignoredCounts.current;
-
- /**
- * Get the title for the threats list based on the selected filters and the amount of threats.
- */
- const getTitle = useCallback( () => {
- switch ( selected ) {
- case 'all':
- if ( list.length === 1 ) {
- switch ( filter ) {
- case 'fixed':
- return __( 'All fixed threats', 'jetpack-protect' );
- case 'ignored':
- return __(
- 'All ignored threats',
- 'jetpack-protect',
- /** dummy arg to avoid bad minification */ 0
- );
- default:
- return __( 'All threats', 'jetpack-protect' );
- }
- }
- switch ( filter ) {
- case 'fixed':
- return sprintf(
- /* translators: placeholder is the amount of fixed threats found on the site. */
- __( 'All %s fixed threats', 'jetpack-protect' ),
- list.length
- );
- case 'ignored':
- return sprintf(
- /* translators: placeholder is the amount of ignored threats found on the site. */
- __( 'All %s ignored threats', 'jetpack-protect' ),
- list.length
- );
- default:
- return sprintf(
- /* translators: placeholder is the amount of threats found on the site. */
- __( 'All %s threats', 'jetpack-protect' ),
- list.length
- );
- }
- case 'core':
- switch ( filter ) {
- case 'fixed':
- return sprintf(
- /* translators: placeholder is the amount of fixed WordPress threats found on the site. */
- _n(
- '%1$s fixed WordPress threat',
- '%1$s fixed WordPress threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- case 'ignored':
- return sprintf(
- /* translators: placeholder is the amount of ignored WordPress threats found on the site. */
- _n(
- '%1$s ignored WordPress threat',
- '%1$s ignored WordPress threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- default:
- return sprintf(
- /* translators: placeholder is the amount of WordPress threats found on the site. */
- _n(
- '%1$s WordPress threat',
- '%1$s WordPress threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- }
- case 'files':
- switch ( filter ) {
- case 'fixed':
- return sprintf(
- /* translators: placeholder is the amount of fixed file threats found on the site. */
- _n(
- '%1$s fixed file threat',
- '%1$s fixed file threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- case 'ignored':
- return sprintf(
- /* translators: placeholder is the amount of ignored file threats found on the site. */
- _n(
- '%1$s ignored file threat',
- '%1$s ignored file threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- default:
- return sprintf(
- /* translators: placeholder is the amount of file threats found on the site. */
- _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ),
- list.length
- );
- }
- case 'database':
- switch ( filter ) {
- case 'fixed':
- return sprintf(
- /* translators: placeholder is the amount of fixed database threats found on the site. */
- _n(
- '%1$s fixed database threat',
- '%1$s fixed database threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- case 'ignored':
- return sprintf(
- /* translators: placeholder is the amount of ignored database threats found on the site. */
- _n(
- '%1$s ignored database threat',
- '%1$s ignored database threats',
- list.length,
- 'jetpack-protect'
- ),
- list.length
- );
- default:
- return sprintf(
- /* translators: placeholder is the amount of database threats found on the site. */
- _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ),
- list.length
- );
- }
- default:
- switch ( filter ) {
- case 'fixed':
- return sprintf(
- /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */
- _n(
- '%1$s fixed threat in %2$s %3$s',
- '%1$s fixed threats in %2$s %3$s',
- list.length,
- 'jetpack-protect'
- ),
- list.length,
- item?.name,
- item?.version
- );
- case 'ignored':
- return sprintf(
- /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */
- _n(
- '%1$s ignored threat in %2$s %3$s',
- '%1$s ignored threats in %2$s %3$s',
- list.length,
- 'jetpack-protect'
- ),
- list.length,
- item?.name,
- item?.version
- );
- default:
- return sprintf(
- /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */
- _n(
- '%1$s threat in %2$s %3$s',
- '%1$s threats in %2$s %3$s',
- list.length,
- 'jetpack-protect'
- ),
- list.length,
- item?.name,
- item?.version
- );
- }
- }
- }, [ selected, list.length, filter, item?.name, item?.version ] );
-
- // Threat history is only available for paid plans.
- if ( ! hasPlan ) {
- return ;
- }
-
- // Remove the filter if there are no threats to show.
- if ( list.length === 0 && filter !== 'all' ) {
- return ;
- }
-
- return (
-
-
- { ( ! error || numAllThreats ) && (
-
-
-
-
-
-
-
-
- { list.length > 0 ? (
-
- ) : (
- <>
-
-
-
-
- { __( "Don't worry about a thing", 'jetpack-protect' ) }
-
-
- { sprintf(
- /* translators: %s: Filter type */
- __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ),
- 'all' === filter ? ' ' : ` ${ filter } `
- ) }
-
-
- >
- ) }
-
-
-
-
-
- ) }
-
-
- );
-};
-
-export default ScanHistoryRoute;
diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx
deleted file mode 100644
index 1bc9668b11065..0000000000000
--- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { __ } from '@wordpress/i18n';
-import React, { useCallback } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-import ButtonGroup from '../../../components/button-group';
-
-/**
- * Status Filters component.
- *
- * @param {object} props - Component props.
- * @param {number} props.numFixed - Number of fixed threats.
- * @param {number} props.numIgnored - Number of ignored threats.
- *
- * @return {React.ReactNode} StatusFilters component.
- */
-export default function StatusFilters( { numFixed, numIgnored } ) {
- const navigate = useNavigate();
- const { filter = 'all' } = useParams();
- const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] );
-
- return (
-
-
- { __( 'All', 'jetpack-protect' ) }
-
-
- { __( 'Fixed', 'jetpack-protect' ) }
-
-
- { __( 'Ignored', 'jetpack-protect' ) }
-
-
- );
-}
diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss
deleted file mode 100644
index d30f3e0ac3344..0000000000000
--- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-.empty {
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
- max-height: 600px;
- flex-direction: column;
-}
-
-.list-header {
- display: flex;
- justify-content: flex-end;
- align-items: flex-end;
- margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px
-}
-
-.list-title {
- flex: 1;
- margin-bottom: 0;
-}
-
-.list-header__controls {
- display: flex;
- gap: calc( var( --spacing-base ) * 2 ); // 16px
-}
-
-@media ( max-width: 599px ) {
-
- .list-header {
- margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px
- }
-
- .list-title {
- display: none;
- }
-}
\ No newline at end of file
diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx
index 1f3cdfdd7520f..c56ae3c747f3e 100644
--- a/projects/plugins/protect/src/js/routes/scan/index.jsx
+++ b/projects/plugins/protect/src/js/routes/scan/index.jsx
@@ -1,14 +1,16 @@
import { AdminSection, Container, Col } from '@automattic/jetpack-components';
+import { useMemo, useState } from 'react';
+import { useLocation, useParams } from 'react-router-dom';
import AdminPage from '../../components/admin-page';
-import ThreatsList from '../../components/threats-list';
-import useScanStatusQuery from '../../data/scan/use-scan-status-query';
+import OnboardingPopover from '../../components/onboarding-popover';
+import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query';
import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import { OnboardingContext } from '../../hooks/use-onboarding';
import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
import onboardingSteps from './onboarding-steps';
import ScanAdminSectionHero from './scan-admin-section-hero';
-import ScanFooter from './scan-footer';
+import ScanResultsDataView from './scan-results-data-view';
+import styles from './styles.module.scss';
/**
* Scan Page
@@ -19,23 +21,41 @@ import ScanFooter from './scan-footer';
*/
const ScanPage = () => {
const { hasPlan } = usePlan();
- const {
- counts: {
- current: { threats: numThreats },
- },
- lastChecked,
- } = useProtectData();
+ const location = useLocation();
+ const { filter } = useParams();
const { data: status } = useScanStatusQuery( { usePolling: true } );
+ const [ scanResultsAnchor, setScanResultsAnchor ] = useState( null );
+
let currentScanStatus;
if ( status.error ) {
currentScanStatus = 'error';
- } else if ( ! lastChecked ) {
+ } else if ( ! status.lastChecked ) {
currentScanStatus = 'in_progress';
} else {
currentScanStatus = 'active';
}
+ const filters = useMemo( () => {
+ if ( location.pathname.includes( '/scan/history' ) ) {
+ return [
+ {
+ field: 'status',
+ value: filter ? [ filter ] : [ 'fixed', 'ignored' ],
+ operator: 'isAny',
+ },
+ ];
+ }
+
+ return [
+ {
+ field: 'status',
+ value: [ 'current' ],
+ operator: 'isAny',
+ },
+ ];
+ }, [ filter, location.pathname ] );
+
// Track view for Protect admin page.
useAnalyticsTracks( {
pageViewEventName: 'protect_admin',
@@ -49,16 +69,33 @@ const ScanPage = () => {
- { ( ! status.error || numThreats ) && (
-
-
-
-
-
-
-
- ) }
-
+
+
+
+
+
+
+ { !! status && ! isScanInProgress( status ) && (
+
+ ) }
+ { !! status && ! isScanInProgress( status ) && hasPlan && (
+
+ ) }
+
+
+
);
diff --git a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx
index 0e85aa56d9289..c29af26bcb409 100644
--- a/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx
+++ b/projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx
@@ -6,15 +6,6 @@ import usePlan from '../../hooks/use-plan';
const { siteSuffix } = window.jetpackProtectInitialState;
-const scanResultsTitle = __( 'Your scan results', 'jetpack-protect' );
-const scanResultsDescription = (
-
- { __(
- 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files',
- 'jetpack-protect'
- ) }
-
-);
const UpgradeButton = props => {
const { upgradePlan } = usePlan();
const { recordEvent } = useAnalyticsTracks();
@@ -27,11 +18,6 @@ const UpgradeButton = props => {
};
export default [
- {
- id: 'free-scan-results',
- title: scanResultsTitle,
- description: scanResultsDescription,
- },
{
id: 'free-daily-scans',
title: __( 'Daily automated scans', 'jetpack-protect' ),
@@ -49,10 +35,41 @@ export default [
),
},
+ {
+ id: 'paid-daily-and-manual-scans',
+ title: __( 'Daily & manual scanning', 'jetpack-protect' ),
+ description: (
+
+ { __(
+ 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.',
+ 'jetpack-protect'
+ ) }
+
+ ),
+ },
+ {
+ id: 'free-scan-results',
+ title: __( 'Your scan results', 'jetpack-protect' ),
+ description: (
+
+ { __(
+ 'Navigate through the results of the scan on your WordPress installation, plugins, and themes.',
+ 'jetpack-protect'
+ ) }
+
+ ),
+ },
{
id: 'paid-scan-results',
- title: scanResultsTitle,
- description: scanResultsDescription,
+ title: __( 'Your scan results', 'jetpack-protect' ),
+ description: (
+
+ { __(
+ 'Navigate through the results of the scan on your WordPress installation, plugins, themes, and other files.',
+ 'jetpack-protect'
+ ) }
+
+ ),
},
{
id: 'paid-fix-all-threats',
@@ -97,16 +114,4 @@ export default [
),
},
- {
- id: 'paid-daily-and-manual-scans',
- title: __( 'Daily & manual scanning', 'jetpack-protect' ),
- description: (
-
- { __(
- 'We run daily automated scans but you can also run on-demand scans if you want to check the latest status.',
- 'jetpack-protect'
- ) }
-
- ),
- },
];
diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx
index 9e1b9c102a037..db76bac1b15b0 100644
--- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx
+++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx
@@ -1,33 +1,41 @@
import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components';
+import { Tooltip } from '@wordpress/components';
import { dateI18n } from '@wordpress/date';
import { __, _n, sprintf } from '@wordpress/i18n';
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
import { useMemo } from 'react';
import AdminSectionHero from '../../components/admin-section-hero';
import ErrorAdminSectionHero from '../../components/error-admin-section-hero';
import OnboardingPopover from '../../components/onboarding-popover';
-import useThreatsList from '../../components/threats-list/use-threats-list';
import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query';
+import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
import useFixers from '../../hooks/use-fixers';
import useModal from '../../hooks/use-modal';
import usePlan from '../../hooks/use-plan';
-import useProtectData from '../../hooks/use-protect-data';
+import useWafData from '../../hooks/use-waf-data';
import ScanningAdminSectionHero from './scanning-admin-section-hero';
import styles from './styles.module.scss';
const ScanAdminSectionHero: React.FC = () => {
- const {
- counts: {
- current: { threats: numThreats },
- },
- lastChecked,
- } = useProtectData();
- const { hasPlan } = usePlan();
+ const { recordEvent } = useAnalyticsTracks();
+ const { hasPlan, upgradePlan } = usePlan();
+ const { setModal } = useModal();
const [ isSm ] = useBreakpointMatch( 'sm' );
const { data: status } = useScanStatusQuery();
- const { list } = useThreatsList();
const { isThreatFixInProgress, isThreatFixStale } = useFixers();
- const { setModal } = useModal();
+
+ const getScan = useCallback( () => {
+ recordEvent( 'jetpack_protect_scan_header_get_scan_link_click' );
+ upgradePlan();
+ }, [ recordEvent, upgradePlan ] );
+
+ const { globalStats } = useWafData();
+ const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities );
+ const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities )
+ ? '50,000'
+ : totalVulnerabilities.toLocaleString();
+
+ const numThreats = status.threats.length;
// Popover anchor
const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null );
@@ -35,20 +43,20 @@ const ScanAdminSectionHero: React.FC = () => {
// List of fixable threats that do not have a fix in progress
const fixableList = useMemo( () => {
- return list.filter( threat => {
- const threatId = parseInt( threat.id );
+ return status.threats.filter( threat => {
+ const threatId = typeof threat.id === 'string' ? parseInt( threat.id ) : threat.id;
return (
threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId )
);
} );
- }, [ list, isThreatFixInProgress, isThreatFixStale ] );
+ }, [ status.threats, isThreatFixInProgress, isThreatFixStale ] );
const scanning = isScanInProgress( status );
let lastCheckedLocalTimestamp = null;
- if ( lastChecked ) {
+ if ( status.lastChecked ) {
// Convert the lastChecked UTC date to a local timestamp
- lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime();
+ lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime();
}
const handleShowAutoFixersClick = threatList => {
@@ -88,13 +96,11 @@ const ScanAdminSectionHero: React.FC = () => {
)
: __( 'Most recent results', 'jetpack-protect' ) }
- { ! hasPlan && (
-
- ) }
+
0 ? 'error' : 'success' }>
{ numThreats > 0
? sprintf(
@@ -105,48 +111,60 @@ const ScanAdminSectionHero: React.FC = () => {
? _n( 'threat', 'threats', numThreats, 'jetpack-protect' )
: _n( 'vulnerability', 'vulnerabilities', numThreats, 'jetpack-protect' )
)
- : sprintf(
- /* translators: %s: Pluralized type of threat/vulnerability */
- __( 'No active %s', 'jetpack-protect' ),
- hasPlan
- ? __( 'threats', 'jetpack-protect' )
- : __(
- 'vulnerabilities',
- 'jetpack-protect',
- /* dummy arg to avoid bad minification */ 0
- )
- ) }
+ : __( "Don't worry about a thing", 'jetpack-protect' ) }
<>
-
- { __(
- 'We actively review your sites files line-by-line to identify threats and vulnerabilities.',
- 'jetpack-protect'
- ) }
-
- { fixableList.length > 0 && (
+ { hasPlan ? (
+
+ { __(
+ "We actively review your site's files line-by-line to identify threats and vulnerabilities.",
+ 'jetpack-protect'
+ ) }
+
+ ) : (
<>
-
+
{ sprintf(
- /* translators: Translates to Show auto fixers $s: Number of fixable threats. */
- __( 'Show auto fixers (%s)', 'jetpack-protect' ),
- fixableList.length
+ // translators: placeholder is the number of total vulnerabilities i.e. "22,000".
+ __(
+ 'Every day we check your plugins, themes, and WordPress version against our %s listed vulnerabilities powered by WPScan, an Automattic brand.',
+ 'jetpack-protect'
+ ),
+ totalVulnerabilitiesFormatted
) }
-
- { ! scanning && (
-
- ) }
+
+
+
+ { __( 'Upgrade to unlock malware scanning', 'jetpack-protect' ) }
+
+
+ >
+ ) }
+ { fixableList.length > 0 && (
+ <>
+
+
+ { sprintf(
+ /* translators: Translates to Show auto fixers $s: Number of fixable threats. */
+ __( 'Show auto fixers (%s)', 'jetpack-protect' ),
+ fixableList.length
+ ) }
+
+
+
>
) }
>
diff --git a/projects/plugins/protect/src/js/routes/scan/scan-footer.jsx b/projects/plugins/protect/src/js/routes/scan/scan-footer.jsx
deleted file mode 100644
index 56d7ea1c14a7d..0000000000000
--- a/projects/plugins/protect/src/js/routes/scan/scan-footer.jsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import {
- Text,
- Button,
- Title,
- getRedirectUrl,
- ContextualUpgradeTrigger,
- Col,
- Container,
-} from '@automattic/jetpack-components';
-import { __, sprintf } from '@wordpress/i18n';
-import React, { useCallback } from 'react';
-import SeventyFiveLayout from '../../components/seventy-five-layout';
-import useAnalyticsTracks from '../../hooks/use-analytics-tracks';
-import usePlan from '../../hooks/use-plan';
-import useWafData from '../../hooks/use-waf-data';
-import styles from './styles.module.scss';
-
-const ProductPromotion = () => {
- const { recordEvent } = useAnalyticsTracks();
- const { hasPlan, upgradePlan } = usePlan();
- const { siteSuffix, blogID } = window.jetpackProtectInitialState || {};
-
- const getScan = useCallback( () => {
- recordEvent( 'jetpack_protect_footer_get_scan_link_click' );
- upgradePlan();
- }, [ recordEvent, upgradePlan ] );
-
- if ( hasPlan ) {
- const goToCloudUrl = getRedirectUrl( 'jetpack-scan-dash', { site: blogID ?? siteSuffix } );
-
- return (
-
-
{ __( 'Get access to our Cloud', 'jetpack-protect' ) }
-
- { __(
- 'With your Protect upgrade, you have free access to scan your site on our Cloud, so you can be aware and fix your threats even if your site goes down.',
- 'jetpack-protect'
- ) }
-
-
-
- { __( 'Go to Cloud', 'jetpack-protect' ) }
-
-
- );
- }
-
- return (
-
-
{ __( 'Advanced scan results', 'jetpack-protect' ) }
-
- { __(
- 'Upgrade Jetpack Protect to get advanced scan tools, including one-click fixes for most threats and malware scanning.',
- 'jetpack-protect'
- ) }
-
-
-
-
- );
-};
-
-const FooterInfo = () => {
- const { hasPlan } = usePlan();
- const { globalStats } = useWafData();
- const totalVulnerabilities = parseInt( globalStats?.totalVulnerabilities );
- const totalVulnerabilitiesFormatted = isNaN( totalVulnerabilities )
- ? '50,000'
- : totalVulnerabilities.toLocaleString();
-
- if ( hasPlan ) {
- const learnMoreScanUrl = getRedirectUrl( 'protect-footer-learn-more-scan' );
-
- return (
-
-
{ __( 'Line-by-line scanning', 'jetpack-protect' ) }
-
- { __(
- 'We actively review line-by-line of your site files to identify threats and vulnerabilities. Jetpack monitors millions of websites to keep your site secure all the time.',
- 'jetpack-protect'
- ) }{ ' ' }
-
- { __( 'Learn more', 'jetpack-protect' ) }
-
-
-
- );
- }
-
- const learnMoreProtectUrl = getRedirectUrl( 'jetpack-protect-footer-learn-more' );
-
- return (
-
-
- { sprintf(
- // translators: placeholder is the number of total vulnerabilities i.e. "22,000".
- __( 'Over %s listed vulnerabilities', 'jetpack-protect' ),
- totalVulnerabilitiesFormatted
- ) }
-
-
- { sprintf(
- // translators: placeholder is the number of total vulnerabilities i.e. "22,000".
- __(
- 'Every day we check your plugin, theme, and WordPress versions against our %s listed vulnerabilities powered by WPScan, an Automattic brand.',
- 'jetpack-protect'
- ),
- totalVulnerabilitiesFormatted
- ) }
-
-
-
- { __( 'Learn more', 'jetpack-protect' ) }
-
-
- );
-};
-
-const ScanFooter = () => {
- const { waf } = window.jetpackProtectInitialState || {};
- return waf.wafSupported ? (
- }
- secondary={ }
- preserveSecondaryOnMobile={ true }
- />
- ) : (
-
-
-
-
-
- );
-};
-
-export default ScanFooter;
diff --git a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx
new file mode 100644
index 0000000000000..616f4e256ed44
--- /dev/null
+++ b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx
@@ -0,0 +1,56 @@
+import { ThreatsDataViews } from '@automattic/jetpack-components';
+import { Threat } from '@automattic/jetpack-scan';
+import { useCallback } from 'react';
+import useHistoryQuery from '../../data/scan/use-history-query';
+import useScanStatusQuery from '../../data/scan/use-scan-status-query';
+import useModal from '../../hooks/use-modal';
+
+/**
+ * Scan Results Data View
+ *
+ * @param {object} props - Component props.
+ * @param {Array} props.filters - Default filters to apply to the data view.
+ *
+ * @return {JSX.Element} ScanResultDataView component.
+ */
+export default function ScanResultsDataView( {
+ filters = [],
+}: {
+ filters: React.ComponentProps< typeof ThreatsDataViews >[ 'filters' ];
+} ) {
+ const { setModal } = useModal();
+
+ const { data: scanStatus } = useScanStatusQuery();
+ const { data: history } = useHistoryQuery();
+
+ const onFixThreats = useCallback(
+ ( threats: Threat[] ) => {
+ setModal( { type: 'FIX_THREAT', props: { threat: threats[ 0 ] } } );
+ },
+ [ setModal ]
+ );
+
+ const onIgnoreThreats = useCallback(
+ ( threats: Threat[] ) => {
+ setModal( { type: 'IGNORE_THREAT', props: { threat: threats[ 0 ] } } );
+ },
+ [ setModal ]
+ );
+
+ const onUnignoreThreats = useCallback(
+ ( threats: Threat[] ) => {
+ setModal( { type: 'UNIGNORE_THREAT', props: { threat: threats[ 0 ] } } );
+ },
+ [ setModal ]
+ );
+
+ return (
+
+ );
+}
diff --git a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx
index c214c52ff8892..4db4449b60119 100644
--- a/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx
+++ b/projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx
@@ -3,7 +3,6 @@ import { __, sprintf } from '@wordpress/i18n';
import AdminSectionHero from '../../components/admin-section-hero';
import InProgressAnimation from '../../components/in-progress-animation';
import ProgressBar from '../../components/progress-bar';
-import ScanNavigation from '../../components/scan-navigation';
import useScanStatusQuery from '../../data/scan/use-scan-status-query';
import usePlan from '../../hooks/use-plan';
import useWafData from '../../hooks/use-waf-data';
@@ -35,20 +34,22 @@ const ScanningAdminSectionHero: React.FC = () => {
/>
) }
- { sprintf(
- // translators: placeholder is the number of total vulnerabilities i.e. "22,000".
- __(
- 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.',
- 'jetpack-protect'
- ),
- totalVulnerabilitiesFormatted
- ) }
+ { hasPlan
+ ? __(
+ "Jetpack is actively scanning your site's files line-by-line to identify threats and vulnerabilities. This could take a minute or two.",
+ 'jetpack-protect'
+ )
+ : sprintf(
+ // translators: placeholder is the number of total vulnerabilities i.e. "22,000".
+ __(
+ 'We are scanning for security threats from our more than %s listed vulnerabilities, powered by WPScan. This could take a minute or two.',
+ 'jetpack-protect'
+ ),
+ totalVulnerabilitiesFormatted
+ ) }
>
-
-
-
>
}
secondary={ }
diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss
index 908e34f6e71d7..163fd23248aaa 100644
--- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss
+++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss
@@ -1,12 +1,14 @@
-.subheading-text {
- white-space: nowrap;
+.auto-fixers {
+ margin-top: calc( var( --spacing-base ) * 4 ); // 32px
}
-.product-section, .info-section {
- margin-top: calc( var( --spacing-base ) * 7 ); // 56px
- margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px
-}
+.scan-results-container {
+ padding-left: 0;
+ padding-right: 0;
+ overflow: hidden;
-.auto-fixers {
- margin-top: calc( var( --spacing-base ) * 4 ); // 32px
-}
\ No newline at end of file
+ > * {
+ margin-left: calc( var( --spacing-base ) * -3 ); // -24px
+ margin-right: calc( var( --spacing-base ) * -3 ); // -24px
+ }
+}
diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js
index 2f6a45721b100..0c65dfec146a7 100644
--- a/projects/plugins/protect/webpack.config.js
+++ b/projects/plugins/protect/webpack.config.js
@@ -33,6 +33,24 @@ module.exports = [
includeNodeModules: [ '@automattic/jetpack-' ],
} ),
+ /**
+ * Transpile @wordpress/dataviews in node_modules too.
+ *
+ * @see https://github.com/Automattic/jetpack/issues/39907
+ */
+ jetpackWebpackConfig.TranspileRule( {
+ includeNodeModules: [ '@wordpress/dataviews/' ],
+ babelOpts: {
+ configFile: false,
+ plugins: [
+ [
+ require.resolve( '@automattic/babel-plugin-replace-textdomain' ),
+ { textdomain: 'jetpack-protect' },
+ ],
+ ],
+ },
+ } ),
+
// Handle CSS.
jetpackWebpackConfig.CssRule( {
extensions: [ 'css', 'sass', 'scss' ],