From 997e0b11eaa0cd9716895f5a0f4d8601e00a188f Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Thu, 14 Nov 2024 10:42:12 -0700 Subject: [PATCH 001/143] Init project branch From c6c82a95df8e2ffe7427d3776a8b75bc82ab2fd6 Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:03:36 -0800 Subject: [PATCH 002/143] Protect: Add Go to Cloud and Scan now button to Protect primary header (#40057) Co-authored-by: Nate Weller --- .../changelog/add-protect-header-buttons | 4 +++ .../src/js/components/admin-page/index.jsx | 27 +++++++++++++++++-- .../components/admin-page/styles.module.scss | 10 +++++++ .../src/js/components/scan-button/index.jsx | 4 +++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/protect/changelog/add-protect-header-buttons diff --git a/projects/plugins/protect/changelog/add-protect-header-buttons b/projects/plugins/protect/changelog/add-protect-header-buttons new file mode 100644 index 0000000000000..24c40f542d7ee --- /dev/null +++ b/projects/plugins/protect/changelog/add-protect-header-buttons @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Adds Go to Cloud and Scan now buttons to the primary header 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 4579831b5f0a5..4e93ae443aa72 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -1,16 +1,20 @@ import { AdminPage as JetpackAdminPage, + Button, Container, + getRedirectUrl, JetpackProtectLogo, } from '@automattic/jetpack-components'; import { useConnection } from '@automattic/jetpack-connection'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; 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'; import Tabs, { Tab } from '../tabs'; import styles from './styles.module.scss'; @@ -24,6 +28,8 @@ const AdminPage = ( { children } ) => { current: { threats: numThreats }, }, } = useProtectData(); + const location = useLocation(); + const { hasPlan } = usePlan(); // Redirect to the setup page if the site is not registered. useEffect( () => { @@ -36,10 +42,27 @@ const AdminPage = ( { children } ) => { return null; } + const viewingScanPage = location.pathname.includes( '/scan' ); + + const { siteSuffix, blogID } = window.jetpackProtectInitialState || {}; + const goToCloudUrl = getRedirectUrl( 'jetpack-scan-dash', { site: blogID ?? siteSuffix } ); + return ( } + header={ +
+ + { hasPlan && viewingScanPage && ( +
+ + +
+ ) } +
+ } > { notice && } 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 e70d2cdb076c7..adf7dc594b907 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 @@ -2,6 +2,16 @@ white-space: nowrap; } +.header { + display: flex; + justify-content: space-between; + + &__scan_buttons { + display: flex; + gap: calc( var( --spacing-base ) * 2 ); // 16px + } +} + .navigation { margin-top: calc( var( --spacing-base ) * 3 * -1 ); // -24px } diff --git a/projects/plugins/protect/src/js/components/scan-button/index.jsx b/projects/plugins/protect/src/js/components/scan-button/index.jsx index 9df71f5984cf1..19134582abe3c 100644 --- a/projects/plugins/protect/src/js/components/scan-button/index.jsx +++ b/projects/plugins/protect/src/js/components/scan-button/index.jsx @@ -1,12 +1,14 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; import React, { forwardRef, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import useStartScanMutator from '../../data/scan/use-start-scan-mutation'; const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, ref ) => { const startScanMutation = useStartScanMutator(); const { data: status } = useScanStatusQuery(); + const navigate = useNavigate(); const disabled = useMemo( () => { return startScanMutation.isPending || isScanInProgress( status ); @@ -15,6 +17,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, const handleScanClick = () => { return event => { event.preventDefault(); + navigate( '/scan' ); startScanMutation.mutate(); }; }; @@ -25,6 +28,7 @@ const ScanButton = forwardRef( ( { variant = 'secondary', children, ...props }, variant={ variant } onClick={ handleScanClick() } disabled={ disabled } + weight={ 'regular' } { ...props } > { children ?? __( 'Scan now', 'jetpack-protect' ) } From fc04ac528f115967c03b79c4f4edfe986c5b53af Mon Sep 17 00:00:00 2001 From: dkmyta <43220201+dkmyta@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:10:46 -0800 Subject: [PATCH 003/143] Protect: Update Scan and History headers (#40058) * Update Scan and History section header structure/content * changelog * Update projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx Co-authored-by: Nate Weller --------- Co-authored-by: Nate Weller --- .../update-protect-scan-and-history-headers | 4 + .../history/history-admin-section-hero.tsx | 36 +++--- .../js/routes/scan/history/styles.module.scss | 8 -- .../routes/scan/scan-admin-section-hero.tsx | 107 +++++++++++++----- .../src/js/routes/scan/styles.module.scss | 8 +- 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 projects/plugins/protect/changelog/update-protect-scan-and-history-headers diff --git a/projects/plugins/protect/changelog/update-protect-scan-and-history-headers b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers new file mode 100644 index 0000000000000..cd930e395e0ed --- /dev/null +++ b/projects/plugins/protect/changelog/update-protect-scan-and-history-headers @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Updates the structure and content of the Scan and History page headers 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 index 9c8f30b7b8067..4aa517f5f120b 100644 --- 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 @@ -1,11 +1,10 @@ -import { Status, Text } from '@automattic/jetpack-components'; +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 ScanNavigation from '../../../components/scan-navigation'; import useThreatsList from '../../../components/threats-list/use-threats-list'; import useProtectData from '../../../hooks/use-protect-data'; import styles from './styles.module.scss'; @@ -48,35 +47,34 @@ const HistoryAdminSectionHero: React.FC = () => { - + + { 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 previously active %2$s', 'jetpack-protect' ), + __( '%1$s previous %2$s', 'jetpack-protect' ), numAllThreats, numAllThreats === 1 ? 'threat' : 'threats' ) - : __( 'No previously active threats', 'jetpack-protect' ) } + : __( 'No previous threats', 'jetpack-protect' ) } - { 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' ) - ) } + { __( 'Here you can view all of your threats till this date.', '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 index f66602e59a9e9..d30f3e0ac3344 100644 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss @@ -8,10 +8,6 @@ flex-direction: column; } -.subheading-content { - font-weight: bold; -} - .list-header { display: flex; justify-content: flex-end; @@ -38,8 +34,4 @@ .list-title { display: none; } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px } \ No newline at end of file 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 60d484cd4a16f..1c5cc6cac49b9 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,30 +1,49 @@ -import { Text, Status, useBreakpointMatch } from '@automattic/jetpack-components'; +import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; import { dateI18n } from '@wordpress/date'; import { __, _n, sprintf } from '@wordpress/i18n'; import { 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 ScanNavigation from '../../components/scan-navigation'; +import useThreatsList from '../../components/threats-list/use-threats-list'; 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 useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { - const { hasPlan } = usePlan(); - const [ isSm ] = useBreakpointMatch( 'sm' ); const { counts: { current: { threats: numThreats }, }, lastChecked, } = useProtectData(); + const { hasPlan } = usePlan(); + const [ isSm ] = useBreakpointMatch( 'sm' ); const { data: status } = useScanStatusQuery(); + const { list } = useThreatsList(); + const { isThreatFixInProgress, isThreatFixStale } = useFixers(); + const { setModal } = useModal(); // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); + const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); + + // 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 ] ); + + const scanning = isScanInProgress( status ); let lastCheckedLocalTimestamp = null; if ( lastChecked ) { @@ -32,7 +51,17 @@ const ScanAdminSectionHero: React.FC = () => { lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); } - if ( isScanInProgress( status ) ) { + const handleShowAutoFixersClick = threatList => { + return event => { + event.preventDefault(); + setModal( { + type: 'FIX_ALL_THREATS', + props: { threatList }, + } ); + }; + }; + + if ( scanning ) { return ; } @@ -50,12 +79,27 @@ const ScanAdminSectionHero: React.FC = () => { - + + { lastCheckedLocalTimestamp + ? sprintf( + // translators: %s: date and time of the last scan + __( '%s results', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) + ) + : __( 'Most recent results', 'jetpack-protect' ) } + + { ! hasPlan && ( + + ) } { numThreats > 0 ? sprintf( /* translators: %s: Total number of threats/vulnerabilities */ - __( '%1$s %2$s found', 'jetpack-protect' ), + __( '%1$s active %2$s', 'jetpack-protect' ), numThreats, hasPlan ? _n( 'threat', 'threats', numThreats, 'jetpack-protect' ) @@ -63,7 +107,7 @@ const ScanAdminSectionHero: React.FC = () => { ) : sprintf( /* translators: %s: Pluralized type of threat/vulnerability */ - __( 'No %s found', 'jetpack-protect' ), + __( 'No active %s', 'jetpack-protect' ), hasPlan ? __( 'threats', 'jetpack-protect' ) : __( @@ -75,31 +119,38 @@ const ScanAdminSectionHero: React.FC = () => { <> - - { lastCheckedLocalTimestamp ? ( - <> - - { dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp, false ) } - -   - { __( 'results', 'jetpack-protect' ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) + + { __( + 'We actively review your sites files line-by-line to identify threats and vulnerabilities.', + 'jetpack-protect' ) } - { ! hasPlan && ( - + { fixableList.length > 0 && ( + <> + + { ! scanning && ( + -
- -
} /> 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 8651420159fa1..908e34f6e71d7 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -1,5 +1,5 @@ -.subheading-content { - font-weight: bold; +.subheading-text { + white-space: nowrap; } .product-section, .info-section { @@ -7,6 +7,6 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px +.auto-fixers { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px } \ No newline at end of file From fb3582e9e3eef95b28b64b03a83f4e9abde83710 Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Mon, 18 Nov 2024 15:33:35 -0700 Subject: [PATCH 004/143] Protect: de-emphasize cloud link by using link variant (#40211) --- projects/plugins/protect/src/js/components/admin-page/index.jsx | 2 +- .../protect/src/js/components/admin-page/styles.module.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 4e93ae443aa72..2d023560517f3 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -55,7 +55,7 @@ const AdminPage = ( { children } ) => { { hasPlan && viewingScanPage && (
- 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 adf7dc594b907..adc0cee561ba5 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 @@ -8,7 +8,7 @@ &__scan_buttons { display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px + gap: calc( var( --spacing-base ) * 3 ); // 24px } } From ad120a9e928814a7dcc41d457ff6cacde9d9fcc6 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 13:15:16 -0800 Subject: [PATCH 005/143] Add home tab, page and stat cards --- .../protect/src/class-jetpack-protect.php | 1 + .../src/js/components/admin-page/index.jsx | 1 + .../src/js/components/pricing-table/index.jsx | 2 +- .../components/protect-check-icon/index.tsx | 29 ++++- .../src/js/components/threats-list/empty.jsx | 18 +-- projects/plugins/protect/src/js/index.tsx | 4 +- .../routes/home/home-admin-section-hero.tsx | 47 ++++++++ .../src/js/routes/home/home-statcards.jsx | 113 ++++++++++++++++++ .../protect/src/js/routes/home/index.jsx | 25 ++++ .../src/js/routes/home/styles.module.scss | 73 +++++++++++ .../src/js/routes/scan/styles.module.scss | 60 ++++++++++ 11 files changed, 351 insertions(+), 22 deletions(-) create mode 100644 projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx create mode 100644 projects/plugins/protect/src/js/routes/home/home-statcards.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/index.jsx create mode 100644 projects/plugins/protect/src/js/routes/home/styles.module.scss diff --git a/projects/plugins/protect/src/class-jetpack-protect.php b/projects/plugins/protect/src/class-jetpack-protect.php index 293ccdaeb3ce7..c57d8194e4e0d 100644 --- a/projects/plugins/protect/src/class-jetpack-protect.php +++ b/projects/plugins/protect/src/class-jetpack-protect.php @@ -459,6 +459,7 @@ public static function get_waf_stats() { return array( 'blockedRequests' => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false, 'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(), + 'blockedLogins' => (int) get_option( 'jetpack_protect_blocked_attempts', 0 ), ); } } 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..92eab94652faf 100644 --- a/projects/plugins/protect/src/js/components/admin-page/index.jsx +++ b/projects/plugins/protect/src/js/components/admin-page/index.jsx @@ -67,6 +67,7 @@ const AdminPage = ( { children } ) => { { notice && } + { const getProtectFree = useCallback( async () => { recordEvent( 'jetpack_protect_connected_product_activated' ); await connectSiteMutation.mutateAsync(); - navigate( '/scan' ); + navigate( '/' ); }, [ connectSiteMutation, recordEvent, navigate ] ); const args = { 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 index d1100d8ce6d5e..e65edba5b4ae3 100644 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx @@ -3,16 +3,39 @@ import { type JSX } from 'react'; /** * Protect Shield and Checkmark SVG Icon * + * @param {object} props - Component props. + * @param {string} [props.width="80"] - The width of the SVG Icon. + * @param {string} [props.height="96"] - The height of the SVG Icon. + * @param {string} [props.status=null] - The status of the icon. + * * @return {JSX.Element} Protect Shield and Checkmark SVG Icon */ -export default function ProtectCheck(): JSX.Element { +export default function ProtectCheck( { + width = '80', + height = '96', + status = null, +} ): JSX.Element { + let fill = '#069E08'; + + if ( status === 'warning' ) { + fill = '#F0B849'; + } else if ( status === 'disabled' ) { + fill = '#A7AAAD'; + } + return ( - + ( - - - - -); - /** * Time Since * diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..96e021da2790e 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -11,6 +11,7 @@ import { NoticeProvider } from './hooks/use-notices'; import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; +import HomeRoute from './routes/home'; import ScanRoute from './routes/scan'; import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; @@ -57,6 +58,7 @@ function render() { } /> + } /> } /> } /> - } /> + } /> diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx new file mode 100644 index 0000000000000..2041ec8c17b59 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -0,0 +1,47 @@ +import { Text, Button } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import AdminSectionHero from '../../components/admin-section-hero'; +import HomeStatCards from './home-statcards'; +import styles from './styles.module.scss'; + +const HomeAdminSectionHero: React.FC = () => { + const navigate = useNavigate(); + const handleScanReportClick = useCallback( () => { + navigate( '/scan' ); + }, [ navigate ] ); + + return ( + + + { __( 'Your site is safe with us', 'jetpack-protect' ) } + + + <> + + { __( + 'We stay ahead of security threats to keep your site protected.', + 'jetpack-protect' + ) } + + + + + + } + secondary={ } + /> + ); +}; + +export default HomeAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx new file mode 100644 index 0000000000000..44bfbf69bc02f --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx @@ -0,0 +1,113 @@ +import { useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import ProtectCheck from '../../components/protect-check-icon'; +import useProtectData from '../../hooks/use-protect-data'; +import useWafData from '../../hooks/use-waf-data'; +import styles from './styles.module.scss'; + +const HomeStatCards = () => { + const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); + + const { + counts: { + current: { threats: numThreats }, + }, + } = useProtectData(); + + const { + config: { bruteForceProtection: isBruteForceModuleEnabled }, + isEnabled: isWafModuleEnabled, + wafSupported, + stats, + } = useWafData(); + + const { allTime: allTimeBlockCount } = stats ? stats.blockedRequests : { allTime: 0 }; + + const { blockedLogins: blockedLoginsCount } = stats; + + const defaultArgs = useMemo( + () => ( { + variant: isSmall ? 'horizontal' : 'square', + } ), + [ isSmall ] + ); + + const scanArgs = useMemo( + () => ( { + ...defaultArgs, + icon: ( + + + + ), + label: ( + + { __( 'Threats found', 'jetpack-protect' ) } + + ), + value: numThreats, + } ), + [ defaultArgs, numThreats ] + ); + + const wafArgs = useMemo( + () => ( { + ...defaultArgs, + className: isWafModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + + ), + label: ( + + { __( 'Blocked requests', 'jetpack-protect' ) } + + ), + value: allTimeBlockCount, + } ), + [ defaultArgs, isWafModuleEnabled, allTimeBlockCount ] + ); + + const bruteForceArgs = useMemo( + () => ( { + ...defaultArgs, + className: isBruteForceModuleEnabled ? styles.active : styles.disabled, + icon: ( + + + + ), + label: ( + + { __( 'Blocked logins', 'jetpack-protect' ) } + + ), + value: blockedLoginsCount, + } ), + [ defaultArgs, isBruteForceModuleEnabled, blockedLoginsCount ] + ); + + return ( +
+ + { wafSupported && } + +
+ ); +}; + +export default HomeStatCards; diff --git a/projects/plugins/protect/src/js/routes/home/index.jsx b/projects/plugins/protect/src/js/routes/home/index.jsx new file mode 100644 index 0000000000000..718349caaac3f --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/index.jsx @@ -0,0 +1,25 @@ +import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import AdminPage from '../../components/admin-page'; +import HomeAdminSectionHero from './home-admin-section-hero'; + +/** + * Home Page + * + * The entry point for the Home page. + * + * @return {Component} The root component for the scan page. + */ +const HomePage = () => { + return ( + + + + + { /* TODO: Add ScanReport component here */ } + + + + ); +}; + +export default HomePage; diff --git a/projects/plugins/protect/src/js/routes/home/styles.module.scss b/projects/plugins/protect/src/js/routes/home/styles.module.scss new file mode 100644 index 0000000000000..52ee0dc3f35cf --- /dev/null +++ b/projects/plugins/protect/src/js/routes/home/styles.module.scss @@ -0,0 +1,73 @@ +.subheading-text { + white-space: nowrap; +} + +.product-section, .info-section { + margin-top: calc( var( --spacing-base ) * 7 ); // 56px + margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px +} + +.scan-report { + margin-top: calc( var( --spacing-base ) * 4 ); // 32px +} + +.stat-card-wrapper { + display: flex; + margin-left: auto; + + > *:not( last-child ) { + margin-right: calc( var( --spacing-base ) * 3 ); // 24px + } + + .disabled { + opacity: 0.5; + } +} + +.stat-card-icon { + width: 100%; + margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px + display: flex; + justify-content: space-between; + align-items: center; + + .active { + fill: var( --jp-green-40 ); + } + + .warning { + fill: var( --jp-yellow-40 ); + } + + .disabled { + fill: var( --jp-gray-40 ); + } +} + +.stat-card-label { + white-space: nowrap; +} + +@media ( max-width: 1115px ) { + .stat-card-wrapper { + margin-top: calc( var( --spacing-base ) * 3 ); // 24px + flex-wrap: wrap; + + } +} + +@media ( max-width: 599px ) { + .stat-card-wrapper { + margin-top: calc( var( --spacing-base ) * 3 ); // 24px + flex-wrap: wrap; + + > *:not( last-child ) { + margin-right: 0; + margin-bottom: var( --spacing-base ); // 8px + } + } + + .stat-card-icon { + margin-bottom: 0; + } +} \ No newline at end of file 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..2e311f93d76fd 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -9,4 +9,64 @@ .auto-fixers { margin-top: calc( var( --spacing-base ) * 4 ); // 32px +} +.stat-card-wrapper { + display: flex; + margin-left: auto; + + > *:not( last-child ) { + margin-right: calc( var( --spacing-base ) * 3 ); // 24px + } + + .disabled { + opacity: 0.5; + } +} + +.stat-card-icon { + width: 100%; + margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px + display: flex; + justify-content: space-between; + align-items: center; + + .active { + fill: var( --jp-green-40 ); + } + + .warning { + fill: var( --jp-yellow-40 ); + } + + .disabled { + fill: var( --jp-gray-40 ); + } +} + +.stat-card-label { + white-space: nowrap; +} + +@media ( max-width: 1115px ) { + .stat-card-wrapper { + margin-top: calc( var( --spacing-base ) * 3 ); // 24px + flex-wrap: wrap; + + } +} + +@media ( max-width: 599px ) { + .stat-card-wrapper { + margin-top: calc( var( --spacing-base ) * 3 ); // 24px + flex-wrap: wrap; + + > *:not( last-child ) { + margin-right: 0; + margin-bottom: var( --spacing-base ); // 8px + } + } + + .stat-card-icon { + margin-bottom: 0; + } } \ No newline at end of file From d210012d463cd8a7a37ebef45dd86ca983aa59f3 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 14:10:41 -0800 Subject: [PATCH 006/143] Fixes and improvements --- .../src/js/components/alert-icon/index.jsx | 74 ------------------- .../src/js/components/alert-icon/index.tsx | 47 ++++++++++++ .../alert-icon/stories/index.stories.jsx | 6 +- .../components/alert-icon/styles.module.scss | 11 --- .../error-admin-section-hero/index.tsx | 4 +- .../styles.module.scss | 4 - .../components/protect-check-icon/index.tsx | 28 +++---- .../src/js/routes/home/home-statcards.jsx | 25 ++++--- .../src/js/routes/scan/styles.module.scss | 4 +- 9 files changed, 83 insertions(+), 120 deletions(-) delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.jsx create mode 100644 projects/plugins/protect/src/js/components/alert-icon/index.tsx delete mode 100644 projects/plugins/protect/src/js/components/alert-icon/styles.module.scss diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.jsx b/projects/plugins/protect/src/js/components/alert-icon/index.jsx deleted file mode 100644 index 8a4d32da59553..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Path, SVG, Rect, G } from '@wordpress/components'; -import React from 'react'; -import styles from './styles.module.scss'; - -/** - * Alert icon - * - * @param {object} props - Props. - * @param {string} props.className - Optional component class name. - * @param {string} props.color - Optional icon color. Defaults to '#D63638'. - * @return { React.ReactNode } The Alert Icon component. - */ -export default function AlertSVGIcon( { className, color = '#D63638' } ) { - return ( -
- - - - - - - - - - - - - - - - - - - -
- ); -} diff --git a/projects/plugins/protect/src/js/components/alert-icon/index.tsx b/projects/plugins/protect/src/js/components/alert-icon/index.tsx new file mode 100644 index 0000000000000..65f8b1ddf2eaa --- /dev/null +++ b/projects/plugins/protect/src/js/components/alert-icon/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +/** + * Alert icon + * + * @param {object} props - Props. + * @param {string} props.className - Optional component class name. + * @param {string} props.width - Optional icon width. Defaults to '127'. + * @param {string} props.height - Optional icon height. Defaults to '136'. + * @param {string} props.color - Optional icon color. Defaults to '#D63638'. + * + * @return {JSX.Element} The Alert Icon component. + */ +export default function Alert( { + className, + width = '40', + height = '48', + color = '#D63638', +}: { + className?: string; + width?: string; + height?: string; + color?: string; +} ): JSX.Element { + return ( + + + + + + ); +} diff --git a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx index 107e513394d42..c8d40b156016d 100644 --- a/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx +++ b/projects/plugins/protect/src/js/components/alert-icon/stories/index.stories.jsx @@ -1,10 +1,10 @@ /* eslint-disable react/react-in-jsx-scope */ import React from 'react'; -import AlertIcon from '../index.jsx'; +import Alert from '../index.jsx'; export default { title: 'Plugins/Protect/Alert Icon', - component: AlertIcon, + component: Alert, argTypes: { color: { control: { @@ -14,5 +14,5 @@ export default { }, }; -const FooterTemplate = args => ; +const FooterTemplate = args => ; export const Default = FooterTemplate.bind( {} ); diff --git a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss b/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss deleted file mode 100644 index 938a62897f2a8..0000000000000 --- a/projects/plugins/protect/src/js/components/alert-icon/styles.module.scss +++ /dev/null @@ -1,11 +0,0 @@ -.container { - width: 48px; - height: 56px; - margin-bottom: calc( var( --spacing-base ) * 8 ); // 64px - - > svg { - position: relative; - top: -36px; - left: -40px; - } -} 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..842421ba19692 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 @@ -1,7 +1,7 @@ import { Text } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import { Icon, warning } from '@wordpress/icons'; import AdminSectionHero from '../admin-section-hero'; +import Alert from '../alert-icon'; import ScanNavigation from '../scan-navigation'; import styles from './styles.module.scss'; @@ -25,7 +25,7 @@ const ErrorAdminSectionHero: React.FC< ErrorAdminSectionHeroProps > = ( { <>
- + { __( 'An error occurred', 'jetpack-protect' ) }
diff --git a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss index 6f0750abd02f8..e0053d4dece1b 100644 --- a/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss +++ b/projects/plugins/protect/src/js/components/error-admin-section-hero/styles.module.scss @@ -4,10 +4,6 @@ } .warning { - width: 54px; - height: 54px; - fill: var( --jp-red ); - margin-left: -8px; margin-right: var( --spacing-base ); // 8px } 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 index e65edba5b4ae3..e222d61adcadf 100644 --- a/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx +++ b/projects/plugins/protect/src/js/components/protect-check-icon/index.tsx @@ -3,28 +3,28 @@ import { type JSX } from 'react'; /** * Protect Shield and Checkmark SVG Icon * - * @param {object} props - Component props. - * @param {string} [props.width="80"] - The width of the SVG Icon. - * @param {string} [props.height="96"] - The height of the SVG Icon. - * @param {string} [props.status=null] - The status of the icon. + * @param {object} props - Component props. + * @param {string} [props.className] - The class name to add to the SVG Icon. + * @param {string} [props.width="80"] - The width of the SVG Icon. + * @param {string} [props.height="96"] - The height of the SVG Icon. + * @param {string} [props.color="#069E08"] - The color of the SVG Icon. * * @return {JSX.Element} Protect Shield and Checkmark SVG Icon */ export default function ProtectCheck( { + className, width = '80', height = '96', - status = null, + color = '#069E08', +}: { + className?: string; + width?: string; + height?: string; + color?: string; } ): JSX.Element { - let fill = '#069E08'; - - if ( status === 'warning' ) { - fill = '#F0B849'; - } else if ( status === 'disabled' ) { - fill = '#A7AAAD'; - } - return ( { ), @@ -61,11 +62,11 @@ const HomeStatCards = () => { className: isWafModuleEnabled ? styles.active : styles.disabled, icon: ( - + { isWafModuleEnabled ? ( + + ) : ( + + ) } ), label: ( @@ -84,11 +85,13 @@ const HomeStatCards = () => { className: isBruteForceModuleEnabled ? styles.active : styles.disabled, icon: ( - + + { isBruteForceModuleEnabled ? ( + + ) : ( + + ) } + ), label: ( 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 2e311f93d76fd..20b378d0d75e0 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -7,9 +7,11 @@ margin-bottom: calc( var( --spacing-base ) * 7 ); // 56px } -.auto-fixers { +.auto-fixers, +.scan-navigation { margin-top: calc( var( --spacing-base ) * 4 ); // 32px } + .stat-card-wrapper { display: flex; margin-left: auto; From b07220cd6c22dcd4071a849ef5614129656202b3 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 14:51:12 -0800 Subject: [PATCH 007/143] Add Tooltips --- .../src/js/routes/home/home-statcards.jsx | 94 ++++++++++++++++--- .../src/js/routes/home/styles.module.scss | 13 ++- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx index 51323e8b80ae7..ffc39960b7319 100644 --- a/projects/plugins/protect/src/js/routes/home/home-statcards.jsx +++ b/projects/plugins/protect/src/js/routes/home/home-statcards.jsx @@ -1,21 +1,33 @@ import { useBreakpointMatch, StatCard } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; +import { Tooltip } from '@wordpress/components'; +import { dateI18n } from '@wordpress/date'; +import { __, sprintf } from '@wordpress/i18n'; import { useMemo } from 'react'; import Alert from '../../components/alert-icon'; import ProtectCheck from '../../components/protect-check-icon'; +import usePlan from '../../hooks/use-plan'; import useProtectData from '../../hooks/use-protect-data'; import useWafData from '../../hooks/use-waf-data'; import styles from './styles.module.scss'; const HomeStatCards = () => { + const { hasPlan } = usePlan(); + const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); const { counts: { current: { threats: numThreats }, }, + lastChecked, } = useProtectData(); + let lastCheckedLocalTimestamp = null; + if ( lastChecked ) { + // Convert the lastChecked UTC date to a local timestamp + lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + } + const { config: { bruteForceProtection: isBruteForceModuleEnabled }, isEnabled: isWafModuleEnabled, @@ -85,13 +97,11 @@ const HomeStatCards = () => { className: isBruteForceModuleEnabled ? styles.active : styles.disabled, icon: ( - - { isBruteForceModuleEnabled ? ( - - ) : ( - - ) } - + { isBruteForceModuleEnabled ? ( + + ) : ( + + ) } ), label: ( @@ -104,11 +114,71 @@ const HomeStatCards = () => { [ defaultArgs, isBruteForceModuleEnabled, blockedLoginsCount ] ); + const lastCheckedMessage = useMemo( () => { + const getEntity = () => { + if ( numThreats === 1 ) { + return hasPlan + ? __( 'threat', 'jetpack-protect' ) + : __( 'vulnerability', 'jetpack-protect' ); + } + return hasPlan + ? __( 'threats', 'jetpack-protect' ) + : __( 'vulnerabilities', 'jetpack-protect' ); + }; + + if ( numThreats > 0 ) { + return sprintf( + // translators: %1$s: date and time of the last scan, %2$d: number of threats/vulnerabilities, %3$s: "threat(s)" or "vulnerabilities" + __( 'Last checked on %1$s: We found %2$d %3$s.', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp ), + numThreats, + getEntity() + ); + } + + return sprintf( + // translators: %s: date and time of the last scan + __( 'Last checked on %s: Your site is secure.', 'jetpack-protect' ), + dateI18n( 'F jS g:i A', lastCheckedLocalTimestamp ) + ); + }, [ numThreats, lastCheckedLocalTimestamp, hasPlan ] ); + return ( -
- - { wafSupported && } - +
+ +
+ +
+
+ { wafSupported && ( + +
+ +
+
+ ) } + +
+ +
+
); }; diff --git a/projects/plugins/protect/src/js/routes/home/styles.module.scss b/projects/plugins/protect/src/js/routes/home/styles.module.scss index 52ee0dc3f35cf..a5124e9671229 100644 --- a/projects/plugins/protect/src/js/routes/home/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/home/styles.module.scss @@ -11,7 +11,7 @@ margin-top: calc( var( --spacing-base ) * 4 ); // 32px } -.stat-card-wrapper { +.stat-cards-wrapper { display: flex; margin-left: auto; @@ -48,8 +48,15 @@ white-space: nowrap; } +.stat-card-tooltip { + margin-top: 8px; + max-width: 240px; + border-radius: 4px; + text-align: left; +} + @media ( max-width: 1115px ) { - .stat-card-wrapper { + .stat-cards-wrapper { margin-top: calc( var( --spacing-base ) * 3 ); // 24px flex-wrap: wrap; @@ -57,7 +64,7 @@ } @media ( max-width: 599px ) { - .stat-card-wrapper { + .stat-cards-wrapper { margin-top: calc( var( --spacing-base ) * 3 ); // 24px flex-wrap: wrap; From 3613c648f3a784f489734a11aeeede3320266d16 Mon Sep 17 00:00:00 2001 From: dkmyta Date: Fri, 22 Nov 2024 14:58:03 -0800 Subject: [PATCH 008/143] Fix translations --- .../routes/home/home-admin-section-hero.tsx | 16 +++++++--- .../src/js/routes/home/home-statcards.jsx | 32 +++++++++---------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx index 2041ec8c17b59..c9bc1b628f9cc 100644 --- a/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/home/home-admin-section-hero.tsx @@ -1,12 +1,14 @@ import { Text, Button } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import AdminSectionHero from '../../components/admin-section-hero'; +import usePlan from '../../hooks/use-plan'; import HomeStatCards from './home-statcards'; import styles from './styles.module.scss'; const HomeAdminSectionHero: React.FC = () => { + const { hasPlan } = usePlan(); const navigate = useNavigate(); const handleScanReportClick = useCallback( () => { navigate( '/scan' ); @@ -22,9 +24,15 @@ const HomeAdminSectionHero: React.FC = () => { <> - { __( - 'We stay ahead of security threats to keep your site protected.', - 'jetpack-protect' + { sprintf( + // translators: %s is replaced with "threats" or "vulnerabilities" depending on the user's plan. + __( + 'We stay ahead of security %s to keep your site protected.', + 'jetpack-protect' + ), + hasPlan + ? __( 'threats', 'jetpack-protect' ) + : __( 'vulnerabilities', 'jetpack-protect' ) ) }