diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx index 76862e085c394..b12d721233324 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx @@ -17,11 +17,11 @@ export const AutoFirewallStatus = () => { const { protect: { wafConfig: wafData }, } = getMyJetpackWindowInitialState(); - const { jetpack_waf_automatic_rules: isAutoFirewallEnabled } = wafData; + const { jetpack_waf_automatic_rules: isAutoFirewallEnabled } = wafData || {}; if ( isPluginActive && isSiteConnected ) { if ( isAutoFirewallEnabled ) { - return ; + return ; } return ; @@ -34,18 +34,18 @@ export const AutoFirewallStatus = () => { * WafStatus component * * @param {PropsWithChildren} props - The component props - * @param {'success' | 'inactive' | 'off'} props.status - The number of threats + * @param {'active' | 'inactive' | 'off'} props.status - The status of the WAF * * @returns {ReactElement} rendered component */ -function WafStatus( { status }: { status: 'success' | 'inactive' | 'off' } ) { +function WafStatus( { status }: { status: 'active' | 'inactive' | 'off' } ) { const slug = 'protect'; const { detail } = useProduct( slug ); - const { hasPaidPlanForProduct: hasProtectPaidPlan } = detail; + const { hasPaidPlanForProduct = false } = detail || {}; const tooltipContent = useProtectTooltipCopy(); const { autoFirewallTooltip } = tooltipContent; - if ( status === 'success' ) { + if ( status === 'active' ) { return ( <>
@@ -70,21 +70,19 @@ function WafStatus( { status }: { status: 'success' | 'inactive' | 'off' } ) { />
{ __( 'Inactive', 'jetpack-my-jetpack' ) }
- { ! hasProtectPaidPlan && ( - - <> -

{ autoFirewallTooltip.title }

-

{ autoFirewallTooltip.text }

- -
- ) } + + <> +

{ autoFirewallTooltip.title }

+

{ autoFirewallTooltip.text }

+ +
); } diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx index a8e9d937d87ce..bcfbbe7f2a04d 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/index.tsx @@ -9,7 +9,7 @@ const ProtectCard: FC< { admin: boolean } > = ( { admin } ) => { const { recordEvent } = useAnalytics(); const slug = 'protect'; const { detail } = useProduct( slug ); - const { isPluginActive, hasPaidPlanForProduct: hasProtectPaidPlan } = detail; + const { isPluginActive, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; /** * Called when secondary "View" button is clicked. diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/logins-blocked-status.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/logins-blocked-status.tsx new file mode 100644 index 0000000000000..4459b3df94736 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/logins-blocked-status.tsx @@ -0,0 +1,144 @@ +import { __ } from '@wordpress/i18n'; +import useProduct from '../../../data/products/use-product'; +import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import useMyJetpackConnection from '../../../hooks/use-my-jetpack-connection'; +import { isJetpackPluginActive } from '../../../utils/is-jetpack-plugin-active'; +import ShieldOff from './assets/shield-off.svg'; +import ShieldPartial from './assets/shield-partial.svg'; +import { InfoTooltip } from './info-tooltip'; +import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; +import type { ReactElement, PropsWithChildren } from 'react'; + +export const LoginsBlockedStatus = () => { + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { isPluginActive: isProtectPluginActive = false } = detail || {}; + const { isSiteConnected } = useMyJetpackConnection(); + const { + protect: { wafConfig: wafData }, + } = getMyJetpackWindowInitialState(); + const { blocked_logins: blockedLoginsCount, brute_force_protection: hasBruteForceProtection } = + wafData || {}; + + // The Brute Force Protection module is available when either the Jetpack plugin Or the Protect plugin is active. + const isPluginActive = isProtectPluginActive || isJetpackPluginActive(); + + if ( isPluginActive && isSiteConnected ) { + if ( hasBruteForceProtection ) { + return ; + } + + return ; + } + if ( isSiteConnected && blockedLoginsCount > 0 ) { + // logins have been blocked previoulsy, but either the Jetpack or Protect plugin is not active + return ; + } + return ; +}; + +/** + * BlockedStatus component + * + * @param {PropsWithChildren} props - The component props + * @param {'active' | 'inactive' | 'off'} props.status - The status of Brute Force Protection + * + * @returns {ReactElement} rendered component + */ +function BlockedStatus( { status }: { status: 'active' | 'inactive' | 'off' } ) { + const { + protect: { wafConfig: wafData }, + } = getMyJetpackWindowInitialState(); + const { blocked_logins: blockedLoginsCount } = wafData || {}; + + const tooltipContent = useProtectTooltipCopy(); + const { blockedLoginsTooltip } = tooltipContent; + + if ( status === 'active' ) { + return blockedLoginsCount > 0 ? ( +
{ blockedLoginsCount }
+ ) : ( + <> +
+ { +
+ + <> +

{ blockedLoginsTooltip.title }

+

{ blockedLoginsTooltip.text }

+ +
+ + ); + } + if ( status === 'inactive' ) { + return ( + <> + { blockedLoginsCount > 0 ? ( + <> +
+ { +
+
{ blockedLoginsCount }
+ + ) : ( +
+ { +
+ ) } + + <> +

{ blockedLoginsTooltip.title }

+

{ blockedLoginsTooltip.text }

+ +
+ + ); + } + return ( + <> +
+ { +
+
{ __( 'Off', 'jetpack-my-jetpack' ) }
+ + ); +} diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx index 79a0b50099b2b..bf420f7576ac4 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/protect-value-section.tsx @@ -1,14 +1,10 @@ -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; import useProduct from '../../../data/products/use-product'; -import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; -import { timeSince } from '../../../utils/time-since'; import { AutoFirewallStatus } from './auto-firewall-status'; import { InfoTooltip } from './info-tooltip'; +import { LoginsBlockedStatus } from './logins-blocked-status'; import { ScanAndThreatStatus } from './scan-threats-status'; +import { useLastScanText } from './use-last-scan-text'; import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; -import type { TooltipContent } from './use-protect-tooltip-copy'; -import type { FC } from 'react'; import './style.scss'; @@ -16,76 +12,15 @@ const ProtectValueSection = () => { const slug = 'protect'; const { detail } = useProduct( slug ); const { isPluginActive = false } = detail || {}; - const { - plugins, - themes, - protect: { scanData }, - } = getMyJetpackWindowInitialState(); - const { - plugins: fromScanPlugins, - themes: fromScanThemes, - num_threats: numThreats = 0, - last_checked: lastScanTime = null, - } = scanData; - - const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; - const themesCount = fromScanThemes.length || Object.keys( themes ).length; - - const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : false; - const lastScanText = useMemo( () => { - if ( isPluginActive ) { - if ( timeSinceLastScan ) { - return sprintf( - /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ - __( 'Last scan: %s', 'jetpack-my-jetpack' ), - timeSinceLastScan - ); - } - return null; - } - return ( - sprintf( - /* translators: %d is the number of plugins installed on the site. */ - _n( '%d plugin', '%d plugins', pluginsCount, 'jetpack-my-jetpack' ), - pluginsCount - ) + - ' ' + - /* translators: The ampersand symbol here (&) is meaning "and". */ - __( '&', 'jetpack-my-jetpack' ) + - '\xa0' + // `\xa0` is a non-breaking space. - sprintf( - /* translators: %d is the number of themes installed on the site. */ - _n( '%d theme', '%d themes', themesCount, 'jetpack-my-jetpack' ).replace( ' ', '\xa0' ), // `\xa0` is a non-breaking space. - themesCount - ) - ); - }, [ isPluginActive, timeSinceLastScan, pluginsCount, themesCount ] ); - - const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); - - return ( - - ); -}; - -export default ProtectValueSection; - -const ValueSection: FC< { - isProtectActive: boolean; - lastScanText?: string; - tooltipContent: TooltipContent; -} > = ( { isProtectActive, lastScanText, tooltipContent } ) => { + const lastScanText = useLastScanText(); + const tooltipContent = useProtectTooltipCopy(); const { pluginsThemesTooltip } = tooltipContent; return ( <>
{ lastScanText &&
{ lastScanText }
} - { ! isProtectActive && ( + { ! isPluginActive && ( { - const { - protect: { wafConfig: wafData }, - } = getMyJetpackWindowInitialState(); - const { blocked_logins: blockedLoginsCount = 0 } = wafData || {}; - - return
{ blockedLoginsCount }
; -}; +export default ProtectValueSection; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx index 443a4ee75ec12..15633df50b79f 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx @@ -11,7 +11,7 @@ import ShieldOff from './assets/shield-off.svg'; import ShieldPartial from './assets/shield-partial.svg'; import ShieldSuccess from './assets/shield-success.svg'; import { InfoTooltip } from './info-tooltip'; -import { useProtectTooltipCopy, type TooltipContent } from './use-protect-tooltip-copy'; +import { useProtectTooltipCopy } from './use-protect-tooltip-copy'; import type { ReactElement, PropsWithChildren } from 'react'; export const ScanAndThreatStatus = () => { @@ -20,28 +20,17 @@ export const ScanAndThreatStatus = () => { const { isPluginActive = false, hasPaidPlanForProduct: hasProtectPaidPlan } = detail || {}; const { isSiteConnected } = useMyJetpackConnection(); const { - plugins, - themes, protect: { scanData }, } = getMyJetpackWindowInitialState(); - const { - plugins: fromScanPlugins, - themes: fromScanThemes, - num_threats: numThreats = 0, - } = scanData; - - const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; - const themesCount = fromScanThemes.length || Object.keys( themes ).length; - const tooltipContent = useProtectTooltipCopy( { pluginsCount, themesCount, numThreats } ); - const { scanThreatsTooltip } = tooltipContent; + const { plugins, themes, num_threats: numThreats = 0 } = scanData || {}; const criticalScanThreatCount = useMemo( () => { - const { core, database, files, num_plugins_threats, num_themes_threats } = scanData; + const { core, database, files, num_plugins_threats, num_themes_threats } = scanData || {}; const pluginsThreats = num_plugins_threats - ? fromScanPlugins.reduce( ( accum, plugin ) => accum.concat( plugin.threats ), [] ) + ? plugins.reduce( ( accum, plugin ) => accum.concat( plugin.threats ), [] ) : []; const themesThreats = num_themes_threats - ? fromScanThemes.reduce( ( accum, theme ) => accum.concat( theme.threats ), [] ) + ? themes.reduce( ( accum, theme ) => accum.concat( theme.threats ), [] ) : []; const allThreats = [ ...pluginsThreats, @@ -54,29 +43,25 @@ export const ScanAndThreatStatus = () => { ( accum, threat ) => ( threat.severity >= 5 ? ( accum += 1 ) : accum ), 0 ); - }, [ fromScanPlugins, fromScanThemes, scanData ] ); + }, [ plugins, themes, scanData ] ); if ( isPluginActive && isSiteConnected ) { if ( hasProtectPaidPlan ) { if ( numThreats ) { return ( - + ); } - return ; + return ; } return numThreats ? ( - + ) : ( - + ); } - return ; + return ; }; /** @@ -85,23 +70,24 @@ export const ScanAndThreatStatus = () => { * @param {PropsWithChildren} props - The component props * @param {number} props.numThreats - The number of threats * @param {number} props.criticalThreatCount - The number of critical threats - * @param {TooltipContent[ 'scanThreatsTooltip' ]} props.tooltipContent - The number of critical threats + * * @returns {ReactElement} rendered component */ function ThreatStatus( { numThreats, criticalThreatCount, - tooltipContent, }: { numThreats: number; criticalThreatCount?: number; - tooltipContent?: TooltipContent[ 'scanThreatsTooltip' ]; } ) { const { recordEvent } = useAnalytics(); const useTooltipRef = useRef< HTMLButtonElement >(); const isMobileViewport: boolean = useViewportMatch( 'medium', '<' ); const [ isPopoverVisible, setIsPopoverVisible ] = useState( false ); + const tooltipContent = useProtectTooltipCopy(); + const { scanThreatsTooltip } = tooltipContent; + const toggleTooltip = useCallback( () => setIsPopoverVisible( prevState => { @@ -110,8 +96,7 @@ function ThreatStatus( { page: 'my-jetpack', feature: 'jetpack-protect', location: 'scan', - status: 'alert', - hasPaidPlan: true, + has_paid_plan: true, threats: numThreats, } ); } @@ -152,8 +137,8 @@ function ThreatStatus( { onClose={ hideTooltip } > <> -

{ tooltipContent.title }

-

{ tooltipContent.text }

+

{ scanThreatsTooltip.title }

+

{ scanThreatsTooltip.text }

) } @@ -179,16 +164,13 @@ function ThreatStatus( { * * @param {PropsWithChildren} props - The component props * @param {'success' | 'partial' | 'off'} props.status - The number of threats - * @param {TooltipContent[ 'scanThreatsTooltip' ]} props.tooltipContent - The number of critical threats + * * @returns { ReactElement} rendered component */ -function ScanStatus( { - status, - tooltipContent, -}: { - status: 'success' | 'partial' | 'off'; - tooltipContent?: TooltipContent[ 'scanThreatsTooltip' ]; -} ) { +function ScanStatus( { status }: { status: 'success' | 'partial' | 'off' } ) { + const tooltipContent = useProtectTooltipCopy(); + const { scanThreatsTooltip } = tooltipContent; + if ( status === 'success' ) { return ( <> @@ -225,14 +207,14 @@ function ScanStatus( { tracksEventName={ 'protect_card_tooltip_open' } tracksEventProps={ { location: 'scan', - status: 'partial', + status: status, hasPaidPlan: false, threats: 0, } } > <> -

{ tooltipContent.title }

-

{ tooltipContent.text }

+

{ scanThreatsTooltip.title }

+

{ scanThreatsTooltip.text }

diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss index 8fb0364baa8b5..9c3b0d0970d20 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/style.scss @@ -52,6 +52,13 @@ font-size: var(--font-body); line-height: var(--font-title-small); color: var(--jp-gray-70); + a { + color: var(--jp-black); + text-decoration: underline; + } + a:hover, a:focus { + text-decoration: none; + } } &__data { diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts new file mode 100644 index 0000000000000..1cf61f6ce0edf --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts @@ -0,0 +1,55 @@ +import { __, _n, sprintf } from '@wordpress/i18n'; +import { useMemo } from 'react'; +import useProduct from '../../../data/products/use-product'; +import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import { timeSince } from '../../../utils/time-since'; + +export const useLastScanText = () => { + const slug = 'protect'; + const { detail } = useProduct( slug ); + const { isPluginActive = false } = detail || {}; + const { + plugins, + themes, + protect: { scanData }, + } = getMyJetpackWindowInitialState(); + const { + plugins: fromScanPlugins, + themes: fromScanThemes, + last_checked: lastScanTime = null, + } = scanData || {}; + + const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; + const themesCount = fromScanThemes.length || Object.keys( themes ).length; + + const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : false; + + return useMemo( () => { + if ( isPluginActive ) { + if ( timeSinceLastScan ) { + return sprintf( + /* translators: %s is how long ago since the last scan took place, i.e.- "17 hours ago" */ + __( 'Last scan: %s', 'jetpack-my-jetpack' ), + timeSinceLastScan + ); + } + return null; + } + return ( + sprintf( + /* translators: %d is the number of plugins installed on the site. */ + _n( '%d plugin', '%d plugins', pluginsCount, 'jetpack-my-jetpack' ), + pluginsCount + ) + + ' ' + + /* translators: The ampersand symbol here (&) is meaning "and". */ + __( '&', 'jetpack-my-jetpack' ) + + '\xa0' + // `\xa0` is a non-breaking space. + sprintf( + /* translators: %d is the number of themes installed on the site. */ + _n( '%d theme', '%d themes', themesCount, 'jetpack-my-jetpack' ).replace( ' ', '\xa0' ), // `\xa0` is a non-breaking space. + themesCount + ) + ); + }, [ isPluginActive, timeSinceLastScan, pluginsCount, themesCount ] ); +}; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts index 7fc85ea4dba43..4459d36c006b7 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts @@ -1,8 +1,16 @@ +import { createInterpolateElement } from '@wordpress/element'; import { __, _n, sprintf } from '@wordpress/i18n'; +import { useCallback, useMemo, createElement, type ReactElement } from 'react'; import useProduct from '../../../data/products/use-product'; -import type { ReactElement } from 'react'; +import { getMyJetpackWindowInitialState } from '../../../data/utils/get-my-jetpack-window-state'; +import useAnalytics from '../../../hooks/use-analytics'; +import { isJetpackPluginActive } from '../../../utils/is-jetpack-plugin-active'; -type TooltipType = 'pluginsThemesTooltip' | 'scanThreatsTooltip' | 'autoFirewallTooltip'; +type TooltipType = + | 'pluginsThemesTooltip' + | 'scanThreatsTooltip' + | 'autoFirewallTooltip' + | 'blockedLoginsTooltip'; export type TooltipContent = { [ key in TooltipType ]: { title: ReactElement | string; @@ -13,24 +21,131 @@ export type TooltipContent = { /** * Gets the translated tooltip copy based on Protect Scan details. * - * @param {object} props - React props - * @param {number} props.pluginsCount - The number of installed plugins on the site. - * @param {number} props.themesCount - The number of installed themes on the site. - * @param {number} props.numThreats - The number of detected scan threats on the site. * @returns {TooltipContent} An object containing each tooltip's title and text content. */ -export function useProtectTooltipCopy( { - pluginsCount = 0, - themesCount = 0, - numThreats = 0, -}: { - pluginsCount?: number; - themesCount?: number; - numThreats?: number; -} = {} ): TooltipContent { +export function useProtectTooltipCopy(): TooltipContent { const slug = 'protect'; const { detail } = useProduct( slug ); - const { hasPaidPlanForProduct: hasProtectPaidPlan } = detail; + const { isPluginActive: isProtectPluginActive, hasPaidPlanForProduct: hasProtectPaidPlan } = + detail || {}; + const { recordEvent } = useAnalytics(); + const { + plugins, + themes, + protect: { scanData, wafConfig: wafData }, + } = getMyJetpackWindowInitialState(); + const { + plugins: fromScanPlugins, + themes: fromScanThemes, + num_threats: numThreats = 0, + } = scanData || {}; + const { + jetpack_waf_automatic_rules: isAutoFirewallEnabled, + blocked_logins: blockedLoginsCount, + brute_force_protection: hasBruteForceProtection, + } = wafData || {}; + + const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; + const themesCount = fromScanThemes.length || Object.keys( themes ).length; + + const settingsLink = useMemo( () => { + if ( isProtectPluginActive ) { + return 'admin.php?page=jetpack-protect#/firewall'; + } + return isJetpackPluginActive() ? 'admin.php?page=jetpack#/settings' : null; + }, [ isProtectPluginActive ] ); + + const trackFirewallSettingsLinkClick = useCallback( () => { + recordEvent( 'jetpack_protect_card_tooltip_content_link_click', { + page: 'my-jetpack', + feature: 'jetpack-protect', + location: 'auto-firewall-tooltip', + path: settingsLink, + } ); + }, [ recordEvent, settingsLink ] ); + + const isBruteForcePluginsActive = isProtectPluginActive || isJetpackPluginActive(); + + const blockedLoginsTooltip = useMemo( () => { + if ( blockedLoginsCount === 0 ) { + if ( hasBruteForceProtection ) { + return { + title: __( 'Brute Force Protection: Active', 'jetpack-my-jetpack' ), + text: __( + 'Brute Force Protection is actively blocking malicious login attempts. The number of blocked login attempts will display here soon!', + 'jetpack-my-jetpack' + ), + }; + } + return { + title: __( 'Brute Force Protection: Inactive', 'jetpack-my-jetpack' ), + text: settingsLink + ? createInterpolateElement( + sprintf( + /* translators: %s is the location/page where the settings are located. Either "firewall settings" or "Jetpack settings". */ + __( + 'Brute Force Protection is disabled and not actively blocking malicious login attempts. Go to %s to activate it.', + 'jetpack-my-jetpack' + ), + isProtectPluginActive ? 'firewall settings' : 'Jetpack settings' + ), + { + a: createElement( 'a', { + href: settingsLink, + onClick: trackFirewallSettingsLinkClick, + } ), + } + ) + : __( + 'Brute Force Protection is disabled and not actively blocking malicious login attempts.', + 'jetpack-my-jetpack' + ), + }; + } + // blockedLoginsCount is greator than 0 here. + if ( ! hasBruteForceProtection ) { + if ( ! isBruteForcePluginsActive ) { + return { + title: __( 'Brute Force Protection: Inactive', 'jetpack-my-jetpack' ), + text: __( + 'For Brute Force Protection, activate the Jetpack or Protect plugin and enable it in settings.', + 'jetpack-my-jetpack' + ), + }; + } + return { + title: __( 'Brute Force Protection: Inactive', 'jetpack-my-jetpack' ), + text: settingsLink + ? createInterpolateElement( + sprintf( + /* translators: %s is the location/page where the settings are located. Either "firewall settings" or "Jetpack settings". */ + __( + 'Brute Force Protection is disabled and not actively blocking malicious login attempts. Go to %s to activate it.', + 'jetpack-my-jetpack' + ), + isProtectPluginActive ? 'firewall settings' : 'Jetpack settings' + ), + { + a: createElement( 'a', { + href: settingsLink, + onClick: trackFirewallSettingsLinkClick, + } ), + } + ) + : __( + 'Brute Force Protection is disabled and not actively blocking malicious login attempts.', + 'jetpack-my-jetpack' + ), + }; + } + }, [ + blockedLoginsCount, + hasBruteForceProtection, + isBruteForcePluginsActive, + isProtectPluginActive, + settingsLink, + trackFirewallSettingsLinkClick, + ] ); return { pluginsThemesTooltip: { @@ -82,9 +197,30 @@ export function useProtectTooltipCopy( { 'jetpack-my-jetpack' ), }, - autoFirewallTooltip: { - title: __( 'Auto-Firewall: Inactive', 'jetpack-my-jetpack' ), - text: __( 'Upgrade required for activation. Manual rules available.', 'jetpack-my-jetpack' ), - }, + autoFirewallTooltip: + hasProtectPaidPlan && ! isAutoFirewallEnabled + ? { + title: __( 'Auto-Firewall: Inactive', 'jetpack-my-jetpack' ), + text: createInterpolateElement( + __( + 'You have Auto-Firewall disabled, visit your Protect firewall settings to activate.', + 'jetpack-my-jetpack' + ), + { + a: createElement( 'a', { + href: settingsLink, + onClick: trackFirewallSettingsLinkClick, + } ), + } + ), + } + : { + title: __( 'Auto-Firewall: Inactive', 'jetpack-my-jetpack' ), + text: __( + 'Upgrade required for activation. Manual rules available.', + 'jetpack-my-jetpack' + ), + }, + blockedLoginsTooltip: blockedLoginsTooltip, }; } diff --git a/projects/packages/my-jetpack/_inc/utils/is-jetpack-plugin-active.ts b/projects/packages/my-jetpack/_inc/utils/is-jetpack-plugin-active.ts new file mode 100644 index 0000000000000..2b0942d4ba902 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/utils/is-jetpack-plugin-active.ts @@ -0,0 +1,13 @@ +import { getMyJetpackWindowInitialState } from '../data/utils/get-my-jetpack-window-state'; + +/** + * Check if the Jetpack plugin is active or not. + * + * @returns {boolean} Returns true if the Jetpack plugin is active, otherwise false. + */ +export const isJetpackPluginActive = () => { + const { plugins } = getMyJetpackWindowInitialState() || {}; + const jetpackPlugin = Object.values( plugins ).find( plugin => plugin?.Name === 'Jetpack' ); + + return jetpackPlugin && jetpackPlugin.active; +}; diff --git a/projects/packages/my-jetpack/changelog/update-mj-protect-card-update-n-refactor b/projects/packages/my-jetpack/changelog/update-mj-protect-card-update-n-refactor new file mode 100644 index 0000000000000..5fc615be4d69b --- /dev/null +++ b/projects/packages/my-jetpack/changelog/update-mj-protect-card-update-n-refactor @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Final minor enhancements to the Protect product card in My Jetpack.