@automattic/jetpack-components 0.61.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ### This is a list detailing changes for the Jetpack RNA Components package releases.
4
4
 
5
+ ## [0.62.0] - 2024-11-25
6
+ ### Added
7
+ - Add Stats icon [#40236]
8
+ - Adds ThreatModal component and stories [#40197]
9
+
10
+ ### Changed
11
+ - Updated package dependencies. [#40288]
12
+
5
13
  ## [0.61.0] - 2024-11-18
6
14
  ### Added
7
15
  - Gridicon Component: Add support for help-outline icon. [#39867]
@@ -1218,6 +1226,7 @@
1218
1226
  ### Changed
1219
1227
  - Update node version requirement to 14.16.1
1220
1228
 
1229
+ [0.62.0]: https://github.com/Automattic/jetpack-components/compare/0.61.0...0.62.0
1221
1230
  [0.61.0]: https://github.com/Automattic/jetpack-components/compare/0.60.0...0.61.0
1222
1231
  [0.60.0]: https://github.com/Automattic/jetpack-components/compare/0.59.0...0.60.0
1223
1232
  [0.59.0]: https://github.com/Automattic/jetpack-components/compare/0.58.1...0.59.0
@@ -14,7 +14,7 @@
14
14
  display: flex;
15
15
  font-family: "Courier 10 Pitch", Courier, monospace;
16
16
  flex-direction: row;
17
- overflow-x: scroll;
17
+ overflow-x: auto;
18
18
  white-space: pre;
19
19
  }
20
20
 
@@ -234,6 +234,18 @@ export const AiIcon: React.FC< BaseIconProps > = ( {
234
234
  );
235
235
  };
236
236
 
237
+ export const StatsIcon: React.FC< BaseIconProps > = ( { opacity = 1, size, color } ) => {
238
+ return (
239
+ <IconWrapper size={ size } opacity={ opacity } color={ color }>
240
+ <Path
241
+ fillRule="evenodd"
242
+ clipRule="evenodd"
243
+ d="M11.25 5H12.75V20H11.25V5ZM6 10H7.5V20H6V10ZM18 14H16.5V20H18V14Z"
244
+ />
245
+ </IconWrapper>
246
+ );
247
+ };
248
+
237
249
  const jetpackIcons = {
238
250
  'anti-spam': AntiSpamIcon,
239
251
  backup: BackupIcon,
@@ -249,6 +261,7 @@ const jetpackIcons = {
249
261
  jetpack: JetpackIcon,
250
262
  share: ShareIcon,
251
263
  ai: AiIcon,
264
+ stats: StatsIcon,
252
265
  };
253
266
 
254
267
  const iconsMap = {
@@ -4,7 +4,7 @@
4
4
  font-family: monospace;
5
5
  display: flex;
6
6
  flex-direction: row;
7
- overflow-x: scroll;
7
+ overflow-x: auto;
8
8
  }
9
9
 
10
10
  .marked-lines__marked-line {
@@ -1,9 +1,9 @@
1
1
  import { Button } from '@automattic/jetpack-components';
2
2
  import {
3
3
  type Threat,
4
- fixerIsInError,
5
- fixerIsInProgress,
6
- fixerStatusIsStale,
4
+ getFixerState,
5
+ getFixerAction,
6
+ getFixerMessage,
7
7
  } from '@automattic/jetpack-scan';
8
8
  import { Tooltip } from '@wordpress/components';
9
9
  import { useCallback, useMemo } from '@wordpress/element';
@@ -30,10 +30,7 @@ export default function ThreatFixerButton( {
30
30
  className?: string;
31
31
  } ): JSX.Element {
32
32
  const fixerState = useMemo( () => {
33
- const inProgress = threat.fixer && fixerIsInProgress( threat.fixer );
34
- const error = threat.fixer && fixerIsInError( threat.fixer );
35
- const stale = threat.fixer && fixerStatusIsStale( threat.fixer );
36
- return { inProgress, error, stale };
33
+ return getFixerState( threat.fixer );
37
34
  }, [ threat.fixer ] );
38
35
 
39
36
  const tooltipText = useMemo( () => {
@@ -53,54 +50,7 @@ export default function ThreatFixerButton( {
53
50
  return __( 'An auto-fixer is in progress.', 'jetpack' );
54
51
  }
55
52
 
56
- switch ( threat.fixable.fixer ) {
57
- case 'delete':
58
- if ( threat.filename ) {
59
- if ( threat.filename.endsWith( '/' ) ) {
60
- return __( 'Deletes the directory that the infected file is in.', 'jetpack' );
61
- }
62
-
63
- if ( threat.signature === 'Core.File.Modification' ) {
64
- return __( 'Deletes the unexpected file in a core WordPress directory.', 'jetpack' );
65
- }
66
-
67
- return __( 'Deletes the infected file.', 'jetpack' );
68
- }
69
-
70
- if ( threat.extension?.type === 'plugin' ) {
71
- return __( 'Deletes the plugin directory to fix the threat.', 'jetpack' );
72
- }
73
-
74
- if ( threat.extension?.type === 'theme' ) {
75
- return __( 'Deletes the theme directory to fix the threat.', 'jetpack' );
76
- }
77
- break;
78
- case 'update':
79
- return __( 'Upgrades the plugin or theme to a newer version.', 'jetpack' );
80
- case 'replace':
81
- case 'rollback':
82
- if ( threat.filename ) {
83
- return threat.signature === 'Core.File.Modification'
84
- ? __(
85
- 'Replaces the modified core WordPress file with the original clean version from the WordPress source code.',
86
- 'jetpack'
87
- )
88
- : __(
89
- 'Replaces the infected file with a previously backed up version that is clean.',
90
- 'jetpack'
91
- );
92
- }
93
-
94
- if ( threat.signature === 'php_hardening_WP_Config_NoSalts_001' ) {
95
- return __(
96
- 'Replaces the default salt keys in wp-config.php with unique ones.',
97
- 'jetpack'
98
- );
99
- }
100
- break;
101
- default:
102
- return __( 'An auto-fixer is available.', 'jetpack' );
103
- }
53
+ return getFixerMessage( threat );
104
54
  }, [ threat, fixerState ] );
105
55
 
106
56
  const buttonText = useMemo( () => {
@@ -112,18 +62,8 @@ export default function ThreatFixerButton( {
112
62
  return __( 'Error', 'jetpack' );
113
63
  }
114
64
 
115
- switch ( threat.fixable.fixer ) {
116
- case 'delete':
117
- return __( 'Delete', 'jetpack' );
118
- case 'update':
119
- return __( 'Update', 'jetpack' );
120
- case 'replace':
121
- case 'rollback':
122
- return __( 'Replace', 'jetpack' );
123
- default:
124
- return __( 'Fix', 'jetpack' );
125
- }
126
- }, [ threat.fixable, fixerState.error ] );
65
+ return getFixerAction( threat );
66
+ }, [ threat, fixerState.error ] );
127
67
 
128
68
  const handleClick = useCallback(
129
69
  ( event: React.MouseEvent ) => {
@@ -0,0 +1,65 @@
1
+ import { Text, Button } from '@automattic/jetpack-components';
2
+ import { Notice } from '@wordpress/components';
3
+ import { __ } from '@wordpress/i18n';
4
+ import React, { ReactElement } from 'react';
5
+ import styles from './styles.module.scss';
6
+
7
+ const CredentialsGate = ( {
8
+ siteCredentialsNeeded,
9
+ credentialsIsFetching,
10
+ credentialsRedirectUrl,
11
+ children,
12
+ }: {
13
+ siteCredentialsNeeded: boolean;
14
+ credentialsIsFetching: boolean;
15
+ credentialsRedirectUrl: string;
16
+ children: ReactElement;
17
+ } ): JSX.Element => {
18
+ if ( ! siteCredentialsNeeded ) {
19
+ return children;
20
+ }
21
+
22
+ return (
23
+ <>
24
+ <Notice
25
+ status="warning"
26
+ isDismissible={ false }
27
+ children={
28
+ <Text>
29
+ { __(
30
+ 'Before Jetpack can auto-fix threats on your site, it needs your server credentials.',
31
+ 'jetpack'
32
+ ) }
33
+ </Text>
34
+ }
35
+ />
36
+
37
+ <Text>
38
+ { __(
39
+ 'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.',
40
+ 'jetpack'
41
+ ) }
42
+ </Text>
43
+
44
+ <Text>
45
+ { __(
46
+ 'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.',
47
+ 'jetpack'
48
+ ) }
49
+ </Text>
50
+
51
+ <div className={ styles[ 'modal-actions' ] }>
52
+ <Button
53
+ isExternalLink={ true }
54
+ weight="regular"
55
+ href={ credentialsRedirectUrl }
56
+ isLoading={ credentialsIsFetching }
57
+ >
58
+ { __( 'Enter server credentials', 'jetpack' ) }
59
+ </Button>
60
+ </div>
61
+ </>
62
+ );
63
+ };
64
+
65
+ export default CredentialsGate;
@@ -0,0 +1,153 @@
1
+ import { Button, ThreatSeverityBadge } from '@automattic/jetpack-components';
2
+ import { type Threat, getFixerState } from '@automattic/jetpack-scan';
3
+ import { Modal, Notice } from '@wordpress/components';
4
+ import { __ } from '@wordpress/i18n';
5
+ import { useMemo } from 'react';
6
+ import Text from '../text';
7
+ import CredentialsGate from './credentials-gate';
8
+ import styles from './styles.module.scss';
9
+ import ThreatActions from './threat-actions';
10
+ import ThreatFixDetails from './threat-fix-details';
11
+ import ThreatTechnicalDetails from './threat-technical-details';
12
+ import UserConnectionGate from './user-connection-gate';
13
+
14
+ /**
15
+ * ThreatModal component
16
+ *
17
+ * @param {object} props - The props.
18
+ * @param {object} props.threat - The threat.
19
+ * @param {boolean} props.isUserConnected - Whether the user is connected.
20
+ * @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner.
21
+ * @param {boolean} props.userIsConnecting - Whether the user is connecting.
22
+ * @param {Function} props.handleConnectUser - The handleConnectUser function.
23
+ * @param {object} props.credentials - The credentials.
24
+ * @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching.
25
+ * @param {string} props.credentialsRedirectUrl - The credentials redirect URL.
26
+ * @param {Function} props.handleUpgradeClick - The handleUpgradeClick function.
27
+ * @param {Function} props.handleFixThreatClick - The handleFixThreatClick function.
28
+ * @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function.
29
+ * @param {Function} props.handleUnignoreThreatClick - The handleUnignoreThreatClick function.
30
+ *
31
+ * @return {JSX.Element} The threat modal.
32
+ */
33
+ export default function ThreatModal( {
34
+ threat,
35
+ isUserConnected,
36
+ hasConnectedOwner,
37
+ userIsConnecting,
38
+ handleConnectUser,
39
+ credentials,
40
+ credentialsIsFetching,
41
+ credentialsRedirectUrl,
42
+ handleUpgradeClick,
43
+ handleFixThreatClick,
44
+ handleIgnoreThreatClick,
45
+ handleUnignoreThreatClick,
46
+ ...modalProps
47
+ }: {
48
+ threat: Threat;
49
+ isUserConnected: boolean;
50
+ hasConnectedOwner: boolean;
51
+ userIsConnecting: boolean;
52
+ handleConnectUser: () => void;
53
+ credentials: false | Record< string, unknown >[];
54
+ credentialsIsFetching: boolean;
55
+ credentialsRedirectUrl: string;
56
+ handleUpgradeClick?: () => void;
57
+ handleFixThreatClick?: ( threats: Threat[] ) => void;
58
+ handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
59
+ handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
60
+ } & React.ComponentProps< typeof Modal > ): JSX.Element {
61
+ const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner;
62
+ const siteCredentialsNeeded = ! credentials || credentials.length === 0;
63
+
64
+ const fixerState = useMemo( () => {
65
+ return getFixerState( threat.fixer );
66
+ }, [ threat.fixer ] );
67
+
68
+ const getModalTitle = useMemo( () => {
69
+ if ( userConnectionNeeded ) {
70
+ return <Text variant="title-small">{ __( 'User connection needed', 'jetpack' ) }</Text>;
71
+ }
72
+
73
+ if ( siteCredentialsNeeded ) {
74
+ return <Text variant="title-small">{ __( 'Site credentials needed', 'jetpack' ) }</Text>;
75
+ }
76
+
77
+ return (
78
+ <>
79
+ <Text variant="title-small">{ threat.title }</Text>
80
+ { !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
81
+ </>
82
+ );
83
+ }, [ userConnectionNeeded, siteCredentialsNeeded, threat.title, threat.severity ] );
84
+
85
+ return (
86
+ <Modal
87
+ size="large"
88
+ title={ <div className={ styles.title }>{ getModalTitle }</div> }
89
+ { ...modalProps }
90
+ >
91
+ <div className={ styles[ 'threat-details' ] }>
92
+ <UserConnectionGate
93
+ userConnectionNeeded={ userConnectionNeeded }
94
+ userIsConnecting={ userIsConnecting }
95
+ handleConnectUser={ handleConnectUser }
96
+ >
97
+ <CredentialsGate
98
+ siteCredentialsNeeded={ siteCredentialsNeeded }
99
+ credentialsIsFetching={ credentialsIsFetching }
100
+ credentialsRedirectUrl={ credentialsRedirectUrl }
101
+ >
102
+ <>
103
+ { fixerState.error && (
104
+ <Notice isDismissible={ false } status="error">
105
+ <Text>{ __( 'An error occurred auto-fixing this threat.', 'jetpack' ) }</Text>
106
+ </Notice>
107
+ ) }
108
+ { fixerState.stale && (
109
+ <Notice isDismissible={ false } status="error">
110
+ <Text>{ __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) }</Text>
111
+ </Notice>
112
+ ) }
113
+ { fixerState.inProgress && ! fixerState.stale && (
114
+ <Notice isDismissible={ false } status="success">
115
+ <Text>{ __( 'The auto-fixer is in progress.', 'jetpack' ) }</Text>
116
+ </Notice>
117
+ ) }
118
+ <div className={ styles.section }>
119
+ { !! threat.description && <Text>{ threat.description }</Text> }
120
+
121
+ { !! threat.source && (
122
+ <div>
123
+ <Button
124
+ variant="link"
125
+ isExternalLink={ true }
126
+ weight="regular"
127
+ href={ threat.source }
128
+ >
129
+ { __( 'See more technical details of this threat', 'jetpack' ) }
130
+ </Button>
131
+ </div>
132
+ ) }
133
+ </div>
134
+
135
+ <ThreatFixDetails threat={ threat } handleUpgradeClick={ handleUpgradeClick } />
136
+
137
+ <ThreatTechnicalDetails threat={ threat } />
138
+
139
+ <ThreatActions
140
+ threat={ threat }
141
+ closeModal={ modalProps.onRequestClose }
142
+ handleFixThreatClick={ handleFixThreatClick }
143
+ handleIgnoreThreatClick={ handleIgnoreThreatClick }
144
+ handleUnignoreThreatClick={ handleUnignoreThreatClick }
145
+ fixerState={ fixerState }
146
+ />
147
+ </>
148
+ </CredentialsGate>
149
+ </UserConnectionGate>
150
+ </div>
151
+ </Modal>
152
+ );
153
+ }
@@ -0,0 +1,35 @@
1
+ .threat-details {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: calc( var( --spacing-base ) * 3 ); // 24px
5
+ }
6
+
7
+ .section {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: calc( var( --spacing-base ) * 2 ); // 16px
11
+ }
12
+
13
+ .title {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: calc( var( --spacing-base ) * 1.5 ); // 12px
17
+ }
18
+
19
+ .filename {
20
+ background-color: var( --jp-gray-0 );
21
+ padding: calc( var( --spacing-base ) * 3 ); // 24px
22
+ overflow-x: auto;
23
+ }
24
+
25
+ .modal-actions {
26
+ display: flex;
27
+ justify-content: flex-end;
28
+ padding-top: calc( var( --spacing-base ) * 3 ); // 24px
29
+ border-top: 1px solid var( --jp-gray-0 );
30
+
31
+ .threat-actions {
32
+ display: flex;
33
+ gap: calc( var( --spacing-base ) * 2 ); // 16px;
34
+ }
35
+ }
@@ -0,0 +1,97 @@
1
+ import { Button } from '@automattic/jetpack-components';
2
+ import { Threat, getFixerAction } from '@automattic/jetpack-scan';
3
+ import { __ } from '@wordpress/i18n';
4
+ import React, { useCallback, useMemo } from 'react';
5
+ import styles from './styles.module.scss';
6
+
7
+ /**
8
+ * ThreatActions component
9
+ *
10
+ * @param {object} props - The component props.
11
+ * @param {object} props.threat - The threat object containing action details.
12
+ * @param {Function} props.closeModal - Function to close the modal.
13
+ * @param {Function} [props.handleFixThreatClick] - Function to handle fixing the threat.
14
+ * @param {Function} [props.handleIgnoreThreatClick] - Function to handle ignoring the threat.
15
+ * @param {Function} [props.handleUnignoreThreatClick] - Function to handle unignoring the threat.
16
+ * @param {object} props.fixerState - The state of the fixer (inProgress, error, stale).
17
+ * @param {boolean} props.fixerState.inProgress - Whether the fixer is in progress.
18
+ * @param {boolean} props.fixerState.error - Whether the fixer encountered an error.
19
+ * @param {boolean} props.fixerState.stale - Whether the fixer is stale.
20
+ *
21
+ * @return {JSX.Element | null} The rendered action buttons or null if no actions are available.
22
+ */
23
+ const ThreatActions = ( {
24
+ threat,
25
+ closeModal,
26
+ handleFixThreatClick,
27
+ handleIgnoreThreatClick,
28
+ handleUnignoreThreatClick,
29
+ fixerState,
30
+ }: {
31
+ threat: Threat;
32
+ closeModal: () => void;
33
+ handleFixThreatClick?: ( threats: Threat[] ) => void;
34
+ handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
35
+ handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
36
+ fixerState: { inProgress: boolean; error: boolean; stale: boolean };
37
+ } ): JSX.Element => {
38
+ const fixerAction = useMemo( () => getFixerAction( threat ), [ threat ] );
39
+
40
+ const onFixClick = useCallback( () => {
41
+ handleFixThreatClick?.( [ threat ] );
42
+ closeModal();
43
+ }, [ threat, handleFixThreatClick, closeModal ] );
44
+
45
+ const onIgnoreClick = useCallback( () => {
46
+ handleIgnoreThreatClick?.( [ threat ] );
47
+ closeModal();
48
+ }, [ threat, handleIgnoreThreatClick, closeModal ] );
49
+
50
+ const onUnignoreClick = useCallback( () => {
51
+ handleUnignoreThreatClick?.( [ threat ] );
52
+ closeModal();
53
+ }, [ threat, handleUnignoreThreatClick, closeModal ] );
54
+
55
+ if ( ! handleFixThreatClick && ! handleIgnoreThreatClick && ! handleUnignoreThreatClick ) {
56
+ return null;
57
+ }
58
+
59
+ return (
60
+ <div className={ styles[ 'modal-actions' ] }>
61
+ <div className={ styles[ 'threat-actions' ] }>
62
+ { threat.status === 'ignored' && handleUnignoreThreatClick && (
63
+ <Button isDestructive={ true } variant="secondary" onClick={ onUnignoreClick }>
64
+ { __( 'Un-ignore', 'jetpack' ) }
65
+ </Button>
66
+ ) }
67
+ { threat.status === 'current' && (
68
+ <>
69
+ { handleIgnoreThreatClick && (
70
+ <Button
71
+ isDestructive={ true }
72
+ variant="secondary"
73
+ onClick={ onIgnoreClick }
74
+ disabled={ fixerState.inProgress && ! fixerState.stale }
75
+ >
76
+ { __( 'Ignore', 'jetpack' ) }
77
+ </Button>
78
+ ) }
79
+ { threat.fixable && handleFixThreatClick && (
80
+ <Button
81
+ isPrimary
82
+ disabled={ fixerState.inProgress && ! fixerState.stale }
83
+ onClick={ onFixClick }
84
+ >
85
+ { fixerState.error || fixerState.stale
86
+ ? __( 'Retry fix', 'jetpack' )
87
+ : fixerAction }
88
+ </Button>
89
+ ) }
90
+ </>
91
+ ) }
92
+ </div>
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default ThreatActions;
@@ -0,0 +1,68 @@
1
+ import { Threat, getFixerMessage } from '@automattic/jetpack-scan';
2
+ import { __, sprintf } from '@wordpress/i18n';
3
+ import React, { useMemo } from 'react';
4
+ import ContextualUpgradeTrigger from '../contextual-upgrade-trigger';
5
+ import Text from '../text';
6
+ import styles from './styles.module.scss';
7
+
8
+ /**
9
+ * ThreatFixDetails component
10
+ *
11
+ * @param {object} props - The component props.
12
+ * @param {object} props.threat - The threat object containing fix details.
13
+ * @param {Function} props.handleUpgradeClick - Function to handle upgrade click events.
14
+ *
15
+ * @return {JSX.Element | null} The rendered fix details or null if no fixable details are available.
16
+ */
17
+ const ThreatFixDetails = ( {
18
+ threat,
19
+ handleUpgradeClick,
20
+ }: {
21
+ threat: Threat;
22
+ handleUpgradeClick: () => void;
23
+ } ): JSX.Element => {
24
+ const title = useMemo( () => {
25
+ if ( threat.status === 'fixed' ) {
26
+ return __( 'How did Jetpack fix it?', 'jetpack' );
27
+ }
28
+ if ( threat.status === 'current' && threat.fixable ) {
29
+ return __( 'How can Jetpack auto-fix this threat?', 'jetpack' );
30
+ }
31
+ return __( 'How to fix it?', 'jetpack' );
32
+ }, [ threat ] );
33
+
34
+ const fix = useMemo( () => {
35
+ // The threat has a fixed version available, but no auto-fix is available.
36
+ // The user needs to update the extension to the fixed version.
37
+ if ( ! threat.fixable && threat.fixedIn ) {
38
+ return sprintf(
39
+ /* translators: Translates to Updates to version. %1$s: Name. %2$s: Fixed version */
40
+ __( 'Update %1$s to version %2$s.', 'jetpack' ),
41
+ threat.extension.name,
42
+ threat.fixedIn
43
+ );
44
+ }
45
+ // The threat has an auto-fix available.
46
+ return getFixerMessage( threat );
47
+ }, [ threat ] );
48
+
49
+ if ( ! threat.fixable && ! threat.fixedIn ) {
50
+ return null;
51
+ }
52
+
53
+ return (
54
+ <div className={ styles.section }>
55
+ <Text variant="title-small">{ title }</Text>
56
+ <Text>{ fix }</Text>
57
+ { handleUpgradeClick && (
58
+ <ContextualUpgradeTrigger
59
+ description={ __( 'Looking for advanced scan results and one-click fixes?', 'jetpack' ) }
60
+ cta={ __( 'Upgrade Jetpack now', 'jetpack' ) }
61
+ onClick={ handleUpgradeClick }
62
+ />
63
+ ) }
64
+ </div>
65
+ );
66
+ };
67
+
68
+ export default ThreatFixDetails;
@@ -0,0 +1,36 @@
1
+ import { Threat } from '@automattic/jetpack-scan';
2
+ import { __ } from '@wordpress/i18n';
3
+ import DiffViewer from '../diff-viewer';
4
+ import MarkedLines from '../marked-lines';
5
+ import Text from '../text';
6
+ import styles from './styles.module.scss';
7
+
8
+ /**
9
+ * ThreatTechnicalDetails component
10
+ *
11
+ * @param {object} props - The component props.
12
+ * @param {object} props.threat - The threat object containing technical details.
13
+ *
14
+ * @return {JSX.Element | null} The rendered technical details or null if no details are available.
15
+ */
16
+ const ThreatTechnicalDetails = ( { threat }: { threat: Threat } ): JSX.Element => {
17
+ if ( ! threat.filename && ! threat.context && ! threat.diff ) {
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <div className={ styles.section }>
23
+ <Text variant="title-small">{ __( 'The technical details', 'jetpack' ) }</Text>
24
+ { threat.filename && (
25
+ <>
26
+ <Text>{ __( 'Threat found in file:', 'jetpack' ) }</Text>
27
+ <pre className={ styles.filename }>{ threat.filename }</pre>
28
+ </>
29
+ ) }
30
+ { threat.context && <MarkedLines context={ threat.context } /> }
31
+ { threat.diff && <DiffViewer diff={ threat.diff } /> }
32
+ </div>
33
+ );
34
+ };
35
+
36
+ export default ThreatTechnicalDetails;
@@ -0,0 +1,64 @@
1
+ import { Text, Button } from '@automattic/jetpack-components';
2
+ import { Notice } from '@wordpress/components';
3
+ import { __ } from '@wordpress/i18n';
4
+ import React, { ReactElement } from 'react';
5
+ import styles from './styles.module.scss';
6
+
7
+ const UserConnectionGate = ( {
8
+ userConnectionNeeded,
9
+ userIsConnecting,
10
+ handleConnectUser,
11
+ children,
12
+ }: {
13
+ userConnectionNeeded: boolean;
14
+ userIsConnecting: boolean;
15
+ handleConnectUser: () => void;
16
+ children: ReactElement;
17
+ } ): JSX.Element => {
18
+ if ( ! userConnectionNeeded ) {
19
+ return children;
20
+ }
21
+ return (
22
+ <>
23
+ <Notice
24
+ status="warning"
25
+ isDismissible={ false }
26
+ children={
27
+ <Text>
28
+ { __(
29
+ 'Before Jetpack can ignore and auto-fix threats on your site, a user connection is needed.',
30
+ 'jetpack'
31
+ ) }
32
+ </Text>
33
+ }
34
+ />
35
+
36
+ <Text>
37
+ { __(
38
+ 'A user connection provides Jetpack the access necessary to perform these tasks.',
39
+ 'jetpack'
40
+ ) }
41
+ </Text>
42
+
43
+ <Text>
44
+ { __(
45
+ 'Once you’ve secured a user connection, all Jetpack features will be available for use.',
46
+ 'jetpack'
47
+ ) }
48
+ </Text>
49
+
50
+ <div className={ styles[ 'modal-actions' ] }>
51
+ <Button
52
+ isExternalLink={ true }
53
+ weight="regular"
54
+ isLoading={ userIsConnecting }
55
+ onClick={ handleConnectUser }
56
+ >
57
+ { __( 'Connect your user account', 'jetpack' ) }
58
+ </Button>
59
+ </div>
60
+ </>
61
+ );
62
+ };
63
+
64
+ export default UserConnectionGate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-components",
3
- "version": "0.61.0",
3
+ "version": "0.62.0",
4
4
  "description": "Jetpack Components Package",
5
5
  "author": "Automattic",
6
6
  "license": "GPL-2.0-or-later",
@@ -15,29 +15,29 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@automattic/format-currency": "1.0.1",
18
- "@automattic/jetpack-boost-score-api": "^0.1.46",
19
- "@automattic/jetpack-scan": "^0.2.0",
18
+ "@automattic/jetpack-boost-score-api": "^0.1.47",
19
+ "@automattic/jetpack-scan": "^0.3.0",
20
20
  "@babel/runtime": "^7",
21
- "@wordpress/browserslist-config": "6.11.0",
22
- "@wordpress/components": "28.11.0",
23
- "@wordpress/compose": "7.11.0",
24
- "@wordpress/data": "10.11.0",
25
- "@wordpress/dataviews": "4.7.0",
26
- "@wordpress/date": "5.11.0",
27
- "@wordpress/element": "6.11.0",
28
- "@wordpress/i18n": "5.11.0",
29
- "@wordpress/icons": "10.11.0",
30
- "@wordpress/notices": "5.11.0",
21
+ "@wordpress/browserslist-config": "6.12.0",
22
+ "@wordpress/components": "28.12.0",
23
+ "@wordpress/compose": "7.12.0",
24
+ "@wordpress/data": "10.12.0",
25
+ "@wordpress/dataviews": "4.8.0",
26
+ "@wordpress/date": "5.12.0",
27
+ "@wordpress/element": "6.12.0",
28
+ "@wordpress/i18n": "5.12.0",
29
+ "@wordpress/icons": "10.12.0",
30
+ "@wordpress/notices": "5.12.0",
31
31
  "clsx": "2.1.1",
32
32
  "prop-types": "^15.7.2",
33
33
  "qrcode.react": "3.1.0",
34
34
  "react-slider": "2.0.5",
35
- "social-logos": "^3.1.12",
35
+ "social-logos": "^3.1.13",
36
36
  "uplot": "1.6.31",
37
37
  "uplot-react": "1.1.4"
38
38
  },
39
39
  "devDependencies": {
40
- "@automattic/jetpack-base-styles": "^0.6.36",
40
+ "@automattic/jetpack-base-styles": "^0.6.37",
41
41
  "@babel/core": "7.26.0",
42
42
  "@babel/preset-react": "7.25.9",
43
43
  "@jest/globals": "29.4.3",