@automattic/jetpack-connection 0.29.8

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.
Files changed (53) hide show
  1. package/.gitattributes +12 -0
  2. package/CHANGELOG.md +622 -0
  3. package/LICENSE.txt +357 -0
  4. package/README.md +284 -0
  5. package/SECURITY.md +38 -0
  6. package/components/connect-button/index.jsx +70 -0
  7. package/components/connect-screen/basic/index.jsx +104 -0
  8. package/components/connect-screen/basic/style.scss +46 -0
  9. package/components/connect-screen/basic/visual.jsx +109 -0
  10. package/components/connect-screen/layout/image-slider.jsx +37 -0
  11. package/components/connect-screen/layout/index.jsx +65 -0
  12. package/components/connect-screen/layout/style.scss +238 -0
  13. package/components/connect-screen/required-plan/index.jsx +127 -0
  14. package/components/connect-screen/required-plan/style.scss +109 -0
  15. package/components/connect-screen/required-plan/visual.jsx +151 -0
  16. package/components/connect-user/index.jsx +64 -0
  17. package/components/connected-plugins/index.jsx +67 -0
  18. package/components/connection-error-notice/index.jsx +110 -0
  19. package/components/connection-error-notice/styles.module.scss +97 -0
  20. package/components/disconnect-card/index.jsx +40 -0
  21. package/components/disconnect-card/style.scss +85 -0
  22. package/components/disconnect-dialog/images/disconnect-confirm.jpg +0 -0
  23. package/components/disconnect-dialog/images/disconnect-thanks.jpg +0 -0
  24. package/components/disconnect-dialog/index.jsx +409 -0
  25. package/components/disconnect-dialog/steps/step-disconnect-confirm.jsx +87 -0
  26. package/components/disconnect-dialog/steps/step-disconnect.jsx +206 -0
  27. package/components/disconnect-dialog/steps/step-survey.jsx +48 -0
  28. package/components/disconnect-dialog/steps/step-thank-you.jsx +54 -0
  29. package/components/disconnect-dialog/style.scss +218 -0
  30. package/components/disconnect-survey/_jp-connect_disconnect-survey-card.scss +60 -0
  31. package/components/disconnect-survey/index.jsx +181 -0
  32. package/components/disconnect-survey/survey-choice.jsx +43 -0
  33. package/components/in-place-connection/index.jsx +140 -0
  34. package/components/in-place-connection/style.scss +35 -0
  35. package/components/manage-connection-dialog/index.jsx +219 -0
  36. package/components/manage-connection-dialog/style.scss +106 -0
  37. package/components/use-connection/index.jsx +112 -0
  38. package/helpers/third-party-cookies-fallback.jsx +10 -0
  39. package/hooks/use-connection-error-notice/index.jsx +38 -0
  40. package/hooks/use-product-checkout-workflow/Readme.md +61 -0
  41. package/hooks/use-product-checkout-workflow/index.jsx +103 -0
  42. package/hooks/use-restore-connection/index.jsx +64 -0
  43. package/index.jsx +48 -0
  44. package/index.native.js +1 -0
  45. package/package.json +62 -0
  46. package/state/actions.jsx +166 -0
  47. package/state/controls.jsx +40 -0
  48. package/state/reducers.jsx +121 -0
  49. package/state/resolvers.jsx +32 -0
  50. package/state/selectors.jsx +35 -0
  51. package/state/store-holder.jsx +14 -0
  52. package/state/store-id.jsx +3 -0
  53. package/state/store.jsx +29 -0
@@ -0,0 +1,127 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ import PropTypes from 'prop-types';
3
+ import React from 'react';
4
+ import useProductCheckoutWorkflow from '../../../hooks/use-product-checkout-workflow';
5
+ import useConnection from '../../use-connection';
6
+ import ConnectScreenRequiredPlanVisual from './visual';
7
+
8
+ /**
9
+ * The Connection Screen Visual component for consumers that require a Plan.
10
+ *
11
+ * @param {object} props -- The properties.
12
+ * @returns {React.Component} The `ConnectScreenForRequiredPlan` component.
13
+ */
14
+ const ConnectScreenRequiredPlan = props => {
15
+ const {
16
+ title,
17
+ autoTrigger,
18
+ buttonLabel,
19
+ apiRoot,
20
+ apiNonce,
21
+ registrationNonce,
22
+ from,
23
+ redirectUri,
24
+ children,
25
+ priceBefore,
26
+ priceAfter,
27
+ pricingIcon,
28
+ pricingTitle,
29
+ pricingCurrencyCode,
30
+ wpcomProductSlug,
31
+ siteProductAvailabilityHandler,
32
+ logo,
33
+ rna = false,
34
+ } = props;
35
+
36
+ const {
37
+ handleRegisterSite,
38
+ siteIsRegistering,
39
+ userIsConnecting,
40
+ registrationError,
41
+ isOfflineMode,
42
+ } = useConnection( {
43
+ registrationNonce,
44
+ redirectUri,
45
+ apiRoot,
46
+ apiNonce,
47
+ autoTrigger,
48
+ from,
49
+ } );
50
+
51
+ const productSlug = wpcomProductSlug ? wpcomProductSlug : '';
52
+
53
+ const { run: handleCheckoutWorkflow, hasCheckoutStarted } = useProductCheckoutWorkflow( {
54
+ productSlug,
55
+ redirectUrl: redirectUri,
56
+ siteProductAvailabilityHandler,
57
+ from,
58
+ } );
59
+
60
+ const displayButtonError = Boolean( registrationError );
61
+ const buttonIsLoading = siteIsRegistering || userIsConnecting || hasCheckoutStarted;
62
+ const handleButtonClick = productSlug ? handleCheckoutWorkflow : handleRegisterSite;
63
+
64
+ return (
65
+ <ConnectScreenRequiredPlanVisual
66
+ title={ title }
67
+ buttonLabel={ buttonLabel }
68
+ priceBefore={ priceBefore }
69
+ priceAfter={ priceAfter }
70
+ pricingIcon={ pricingIcon }
71
+ pricingTitle={ pricingTitle }
72
+ pricingCurrencyCode={ pricingCurrencyCode }
73
+ handleButtonClick={ handleButtonClick }
74
+ displayButtonError={ displayButtonError }
75
+ buttonIsLoading={ buttonIsLoading }
76
+ logo={ logo }
77
+ isOfflineMode={ isOfflineMode }
78
+ rna={ rna }
79
+ >
80
+ { children }
81
+ </ConnectScreenRequiredPlanVisual>
82
+ );
83
+ };
84
+
85
+ ConnectScreenRequiredPlan.propTypes = {
86
+ /** The Title. */
87
+ title: PropTypes.string,
88
+ /** The Connect Button label. */
89
+ buttonLabel: PropTypes.string,
90
+ /** API root. */
91
+ apiRoot: PropTypes.string.isRequired,
92
+ /** API nonce. */
93
+ apiNonce: PropTypes.string.isRequired,
94
+ /** Registration nonce. */
95
+ registrationNonce: PropTypes.string.isRequired,
96
+ /** Where the connection request is coming from. */
97
+ from: PropTypes.string,
98
+ /** The redirect admin URI. */
99
+ redirectUri: PropTypes.string.isRequired,
100
+ /** Whether to initiate the connection process automatically upon rendering the component. */
101
+ autoTrigger: PropTypes.bool,
102
+ /** The Pricing Card Title. */
103
+ pricingTitle: PropTypes.string.isRequired,
104
+ /** The Pricing Card Icon. */
105
+ pricingIcon: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ),
106
+ /** Price before discount. */
107
+ priceBefore: PropTypes.number.isRequired,
108
+ /** Price after discount. */
109
+ priceAfter: PropTypes.number.isRequired,
110
+ /** The Currency code, eg 'USD'. */
111
+ pricingCurrencyCode: PropTypes.string,
112
+ /** The WordPress.com product slug. If specified, the connection/authorization flow will go through the Checkout page for this product'. */
113
+ wpcomProductSlug: PropTypes.string,
114
+ /** A callback that will be used to check whether the site already has the wpcomProductSlug. This will be checked after registration and the checkout will be skipped if it returns true. */
115
+ checkSiteHasWpcomProduct: PropTypes.func,
116
+ /** The logo to display at the top of the component. */
117
+ logo: PropTypes.element,
118
+ };
119
+
120
+ ConnectScreenRequiredPlan.defaultProps = {
121
+ title: __( 'Over 5 million WordPress sites are faster and more secure', 'jetpack' ),
122
+ buttonLabel: __( 'Set up Jetpack', 'jetpack' ),
123
+ pricingCurrencyCode: 'USD',
124
+ autoTrigger: false,
125
+ };
126
+
127
+ export default ConnectScreenRequiredPlan;
@@ -0,0 +1,109 @@
1
+ @import '@wordpress/base-styles/breakpoints';
2
+ @import '@wordpress/base-styles/mixins';
3
+
4
+ .jp-connection__connect-screen-layout__left {
5
+ /**
6
+ * Start with 100% width;
7
+ * then account for a 384px pricing card that floats to the right;
8
+ * finally give 32px of padding between content and the pricing card
9
+ */
10
+ @include break-xlarge {
11
+ width: calc(100% - 384px - var(--spacing-base) * 4);
12
+ }
13
+ }
14
+
15
+ .jp-connection__connect-screen-required-plan {
16
+ @include break-xlarge {
17
+ position: relative;
18
+ background: linear-gradient(to right, white 70%, #f9f9f6 30%);
19
+ }
20
+
21
+ &__loading {
22
+ display: none;
23
+ }
24
+
25
+ /** There's a header above this list, which makes the top spacing
26
+ * look a bit taller than it actually is
27
+ */
28
+ ul.jp-product-promote {
29
+ margin-block-start: calc(var(--spacing-base) * 3);
30
+ margin-block-end: calc(var(--spacing-base) * 4);
31
+ }
32
+
33
+ &__pricing-card {
34
+ /** Line up with the top of the product logo,
35
+ * and mirror 96px horizontal padding seen in
36
+ * .jp-connection__connect-screen-layout__left
37
+ */
38
+ @include break-xlarge {
39
+ position: absolute;
40
+ top: calc(var(--spacing-base) * 8);
41
+ right: calc(var(--spacing-base) * 12);
42
+ }
43
+
44
+ .jp-action-button--button.components-button {
45
+ width: 100%;
46
+ height: auto;
47
+ font-size: 18px;
48
+ font-weight: 500;
49
+ background: var(--jp-black) !important;
50
+ color: var(--jp-white) !important;
51
+ border-radius: var(--jp-border-radius);
52
+ padding: 14px 24px;
53
+ margin: 24px 0px 32px;
54
+ justify-content: center;
55
+ align-items: center;
56
+
57
+ &:disabled {
58
+ background: var(--jp-gray) !important;
59
+ color: var(--jp-gray-20) !important;
60
+ }
61
+ }
62
+
63
+ .terms-of-service {
64
+ margin-top: calc(var(--spacing-base) * 4);
65
+ margin-bottom: var(--spacing-base);
66
+ }
67
+ }
68
+
69
+ &__with-subscription {
70
+ margin-top: calc(var(--spacing-base) * 4);
71
+
72
+ display: flex;
73
+ flex-wrap: wrap;
74
+ justify-content: flex-start;
75
+ gap: 1ch;
76
+ line-height: 1;
77
+
78
+ .jp-action-button--button.components-button.is-primary {
79
+ display: inline;
80
+ font-size: var(--font-title-small);
81
+ line-height: 20px;
82
+ color: var(--jp-black) !important;
83
+ background: inherit !important;
84
+ text-decoration: underline;
85
+
86
+ width: auto;
87
+ min-width: 0;
88
+ height: auto;
89
+ font: inherit;
90
+ padding: 0;
91
+
92
+ &:hover {
93
+ background: inherit;
94
+ text-decoration-thickness: var(--jp-underline-thickness);
95
+ }
96
+
97
+ &:focus {
98
+ background: inherit;
99
+ box-shadow: none !important;
100
+ }
101
+ }
102
+
103
+ .jp-components-spinner__inner,
104
+ .jp-components-spinner__outer {
105
+ border-top-color: var(--jp-black);
106
+ border-right-color: var(--jp-black);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,151 @@
1
+ import {
2
+ ActionButton,
3
+ getRedirectUrl,
4
+ PricingCard,
5
+ TermsOfService,
6
+ } from '@automattic/jetpack-components';
7
+ import { createInterpolateElement } from '@wordpress/element';
8
+ import { __ } from '@wordpress/i18n';
9
+ import classNames from 'classnames';
10
+ import debugFactory from 'debug';
11
+ import PropTypes from 'prop-types';
12
+ import React from 'react';
13
+ import ConnectScreenLayout from '../layout';
14
+ import './style.scss';
15
+
16
+ const debug = debugFactory( 'jetpack:connection:ConnectScreenRequiredPlanVisual' );
17
+
18
+ /**
19
+ * The Connection Screen Visual component for consumers that require a Plan.
20
+ *
21
+ * @param {object} props -- The properties.
22
+ * @returns {React.Component} The `ConnectScreenRequiredPlanVisual` component.
23
+ */
24
+ const ConnectScreenRequiredPlanVisual = props => {
25
+ const {
26
+ title,
27
+ buttonLabel,
28
+ children,
29
+ priceBefore,
30
+ priceAfter,
31
+ pricingIcon,
32
+ pricingTitle,
33
+ pricingCurrencyCode,
34
+ isLoading,
35
+ handleButtonClick,
36
+ displayButtonError,
37
+ buttonIsLoading,
38
+ logo,
39
+ isOfflineMode,
40
+ rna = false,
41
+ } = props;
42
+
43
+ debug( 'props are %o', props );
44
+
45
+ const withSubscription = createInterpolateElement(
46
+ __( 'Already have a subscription? <connectButton/>', 'jetpack' ),
47
+ {
48
+ connectButton: (
49
+ <ActionButton
50
+ label={ __( 'Log in to get started', 'jetpack' ) }
51
+ onClick={ handleButtonClick }
52
+ isLoading={ buttonIsLoading }
53
+ />
54
+ ),
55
+ }
56
+ );
57
+
58
+ const errorMessage = isOfflineMode
59
+ ? createInterpolateElement( __( 'Unavailable in <a>Offline Mode</a>', 'jetpack' ), {
60
+ a: (
61
+ <a
62
+ href={ getRedirectUrl( 'jetpack-support-development-mode' ) }
63
+ target="_blank"
64
+ rel="noopener noreferrer"
65
+ />
66
+ ),
67
+ } )
68
+ : undefined;
69
+
70
+ return (
71
+ <ConnectScreenLayout
72
+ title={ title }
73
+ className={ classNames(
74
+ 'jp-connection__connect-screen-required-plan',
75
+ isLoading ? 'jp-connection__connect-screen-required-plan__loading' : '',
76
+ rna ? 'rna' : ''
77
+ ) }
78
+ logo={ logo }
79
+ rna={ rna }
80
+ >
81
+ <div className="jp-connection__connect-screen-required-plan__content">
82
+ { children }
83
+
84
+ <div className="jp-connection__connect-screen-required-plan__pricing-card">
85
+ <PricingCard
86
+ title={ pricingTitle }
87
+ icon={ pricingIcon }
88
+ priceBefore={ priceBefore }
89
+ currencyCode={ pricingCurrencyCode }
90
+ priceAfter={ priceAfter }
91
+ >
92
+ <TermsOfService agreeButtonLabel={ buttonLabel } />
93
+ <ActionButton
94
+ label={ buttonLabel }
95
+ onClick={ handleButtonClick }
96
+ displayError={ displayButtonError || isOfflineMode }
97
+ errorMessage={ errorMessage }
98
+ isLoading={ buttonIsLoading }
99
+ isDisabled={ isOfflineMode }
100
+ />
101
+ </PricingCard>
102
+ </div>
103
+
104
+ { ! isOfflineMode && (
105
+ <div className="jp-connection__connect-screen-required-plan__with-subscription">
106
+ { withSubscription }
107
+ </div>
108
+ ) }
109
+ </div>
110
+ </ConnectScreenLayout>
111
+ );
112
+ };
113
+
114
+ ConnectScreenRequiredPlanVisual.propTypes = {
115
+ /** The Pricing Card Title. */
116
+ pricingTitle: PropTypes.string.isRequired,
117
+ /** Price before discount. */
118
+ priceBefore: PropTypes.number.isRequired,
119
+ /** Price after discount. */
120
+ priceAfter: PropTypes.number.isRequired,
121
+ /** The Currency code, eg 'USD'. */
122
+ pricingCurrencyCode: PropTypes.string,
123
+ /** The Title. */
124
+ title: PropTypes.string,
125
+ /** The Connect Button label. */
126
+ buttonLabel: PropTypes.string,
127
+ /** The Pricing Card Icon. */
128
+ pricingIcon: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ),
129
+ /** Whether the connection status is still loading. */
130
+ isLoading: PropTypes.bool,
131
+ /** Callback that is applied into click for all buttons. */
132
+ handleButtonClick: PropTypes.func,
133
+ /** Whether the button error is active or not. */
134
+ displayButtonError: PropTypes.bool,
135
+ /** Whether the button loading state is active or not. */
136
+ buttonIsLoading: PropTypes.bool,
137
+ /** The logo to display at the top of the component. */
138
+ logo: PropTypes.element,
139
+ /** Whether the site is in offline mode. */
140
+ isOfflineMode: PropTypes.bool,
141
+ };
142
+
143
+ ConnectScreenRequiredPlanVisual.defaultProps = {
144
+ pricingCurrencyCode: 'USD',
145
+ isLoading: false,
146
+ buttonIsLoading: false,
147
+ displayButtonError: false,
148
+ handleButtonClick: () => {},
149
+ };
150
+
151
+ export default ConnectScreenRequiredPlanVisual;
@@ -0,0 +1,64 @@
1
+ import restApi from '@automattic/jetpack-api';
2
+ import PropTypes from 'prop-types';
3
+ import { useState, useEffect } from 'react';
4
+
5
+ /**
6
+ * The user connection component.
7
+ *
8
+ * @param {object} props -- The properties.
9
+ * @param {Function} props.redirectFunc -- The redirect function (`window.location.assign()` by default).
10
+ * @param {string} props.connectUrl -- The authorization URL (no-iframe).
11
+ * @param {string} props.redirectUri -- The redirect admin URI.
12
+ * @param {string} props.from -- Where the connection request is coming from.
13
+ * @returns {null} -- Nothing to return.
14
+ */
15
+ const ConnectUser = props => {
16
+ const { redirectFunc, connectUrl, redirectUri, from } = props;
17
+
18
+ const [ authorizationUrl, setAuthorizationUrl ] = useState( null );
19
+
20
+ if ( connectUrl && connectUrl !== authorizationUrl ) {
21
+ setAuthorizationUrl( connectUrl );
22
+ }
23
+
24
+ /**
25
+ * Fetch the authorization URL on the first render.
26
+ * To be only run once.
27
+ */
28
+ useEffect( () => {
29
+ if ( ! authorizationUrl ) {
30
+ restApi
31
+ .fetchAuthorizationUrl( redirectUri )
32
+ .then( response => setAuthorizationUrl( response.authorizeUrl ) )
33
+ .catch( error => {
34
+ throw error;
35
+ } );
36
+ }
37
+ }, [] ); // eslint-disable-line react-hooks/exhaustive-deps
38
+
39
+ if ( ! authorizationUrl ) {
40
+ return null;
41
+ }
42
+
43
+ redirectFunc(
44
+ authorizationUrl +
45
+ ( from
46
+ ? ( authorizationUrl.includes( '?' ) ? '&' : '?' ) + 'from=' + encodeURIComponent( from )
47
+ : '' )
48
+ );
49
+ return null;
50
+ };
51
+
52
+ ConnectUser.propTypes = {
53
+ connectUrl: PropTypes.string,
54
+ redirectUri: PropTypes.string.isRequired,
55
+ from: PropTypes.string,
56
+ redirectFunc: PropTypes.func,
57
+ };
58
+
59
+ ConnectUser.defaultProps = {
60
+ redirectFunc: url => window.location.assign( url ),
61
+ redirectUri: null,
62
+ };
63
+
64
+ export default ConnectUser;
@@ -0,0 +1,67 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ import PropTypes from 'prop-types';
3
+ import React, { useMemo } from 'react';
4
+ import DisconnectCard from '../disconnect-card';
5
+
6
+ /**
7
+ * Render a list of connected plugins.
8
+ *
9
+ * @param {object} props - The properties
10
+ * @returns {React.Component} - The ConnectedPlugins React component
11
+ */
12
+
13
+ const ConnectedPlugins = props => {
14
+ const { connectedPlugins, disconnectingPlugin } = props;
15
+
16
+ /**
17
+ * Add a slug property to each ConnectedPlugins object so they can be converted to an array.
18
+ * This allows the connected plugins to be iterated over more easily for display.
19
+ */
20
+ const connectedPluginsArray = useMemo( () => {
21
+ if ( connectedPlugins ) {
22
+ const keys = Object.keys( connectedPlugins );
23
+ return keys
24
+ .map( key => {
25
+ return Object.assign( { slug: key }, connectedPlugins[ key ] );
26
+ } )
27
+ .filter( plugin => {
28
+ return disconnectingPlugin !== plugin.slug;
29
+ } );
30
+ }
31
+
32
+ // No connected plugins.
33
+ return [];
34
+ }, [ connectedPlugins, disconnectingPlugin ] );
35
+
36
+ if ( connectedPlugins && connectedPluginsArray.length > 0 ) {
37
+ return (
38
+ <React.Fragment>
39
+ <div className="jp-connection__disconnect-dialog__step-copy">
40
+ <p className="jp-connection__disconnect-dialog__large-text">
41
+ { __(
42
+ 'Jetpack is powering other plugins on your site. If you disconnect, these plugins will no longer work.',
43
+ 'jetpack'
44
+ ) }
45
+ </p>
46
+ </div>
47
+ <div className="jp-connection__disconnect-card__group">
48
+ { connectedPluginsArray.map( plugin => {
49
+ return <DisconnectCard title={ plugin.name } key={ plugin.slug } />;
50
+ } ) }
51
+ </div>
52
+ </React.Fragment>
53
+ );
54
+ }
55
+
56
+ // Default to null if there are no connected plugins passed on the props
57
+ return null;
58
+ };
59
+
60
+ ConnectedPlugins.propTypes = {
61
+ /** Plugins that are using the Jetpack connection. */
62
+ connectedPlugins: PropTypes.array,
63
+ /** Slug of the plugin that has initiated the disconnect. */
64
+ disconnectingPlugin: PropTypes.string,
65
+ };
66
+
67
+ export default ConnectedPlugins;
@@ -0,0 +1,110 @@
1
+ import { Spinner, useBreakpointMatch } from '@automattic/jetpack-components';
2
+ import { Icon, Notice, Path, SVG } from '@wordpress/components';
3
+ import { __, sprintf } from '@wordpress/i18n';
4
+ import PropTypes from 'prop-types';
5
+ import React from 'react';
6
+ import styles from './styles.module.scss';
7
+
8
+ /**
9
+ * The RNA Connection Error Notice component.
10
+ *
11
+ * @param {object} props -- The properties.
12
+ * @returns {React.Component} The `ConnectionErrorNotice` component.
13
+ */
14
+ const ConnectionErrorNotice = props => {
15
+ const { message, isRestoringConnection, restoreConnectionCallback, restoreConnectionError } =
16
+ props;
17
+
18
+ const [ isBiggerThanMedium ] = useBreakpointMatch( [ 'md' ], [ '>' ] );
19
+ const wrapperClassName =
20
+ styles.notice + ( isBiggerThanMedium ? ' ' + styles[ 'bigger-than-medium' ] : '' );
21
+
22
+ const icon = (
23
+ <Icon
24
+ icon={
25
+ <SVG
26
+ width="24"
27
+ height="24"
28
+ viewBox="0 0 24 24"
29
+ fill="none"
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ >
32
+ <Path
33
+ d="M11.7815 4.93772C11.8767 4.76626 12.1233 4.76626 12.2185 4.93772L20.519 19.8786C20.6116 20.0452 20.4911 20.25 20.3005 20.25H3.69951C3.50889 20.25 3.3884 20.0452 3.48098 19.8786L11.7815 4.93772Z"
34
+ stroke="#D63638"
35
+ strokeWidth="1.5"
36
+ />
37
+ <Path d="M13 10H11V15H13V10Z" fill="#D63638" />
38
+ <Path d="M13 16H11V18H13V16Z" fill="#D63638" />
39
+ </SVG>
40
+ }
41
+ />
42
+ );
43
+
44
+ if ( ! message ) {
45
+ return null;
46
+ }
47
+
48
+ if ( isRestoringConnection ) {
49
+ return (
50
+ <Notice status={ 'error' } isDismissible={ false } className={ wrapperClassName }>
51
+ <div className={ styles.message }>
52
+ <Spinner color="#B32D2E" size={ 24 } />
53
+ { __( 'Reconnecting Jetpack', 'jetpack' ) }
54
+ </div>
55
+ </Notice>
56
+ );
57
+ }
58
+
59
+ const errorRender = restoreConnectionError ? (
60
+ <Notice
61
+ status={ 'error' }
62
+ isDismissible={ false }
63
+ className={ wrapperClassName + ' ' + styles.error }
64
+ >
65
+ <div className={ styles.message }>
66
+ { icon }
67
+ { sprintf(
68
+ /* translators: placeholder is the error. */
69
+ __( 'There was an error reconnecting Jetpack. Error: %s', 'jetpack' ),
70
+ restoreConnectionError
71
+ ) }
72
+ </div>
73
+ </Notice>
74
+ ) : null;
75
+
76
+ return (
77
+ <>
78
+ { errorRender }
79
+ <Notice status={ 'error' } isDismissible={ false } className={ wrapperClassName }>
80
+ <div className={ styles.message }>
81
+ { icon }
82
+ { message }
83
+ </div>
84
+ { restoreConnectionCallback && (
85
+ <a
86
+ onClick={ restoreConnectionCallback }
87
+ onKeyDown={ restoreConnectionCallback }
88
+ className={ styles.button }
89
+ href="#"
90
+ >
91
+ { __( 'Restore Connection', 'jetpack' ) }
92
+ </a>
93
+ ) }
94
+ </Notice>
95
+ </>
96
+ );
97
+ };
98
+
99
+ ConnectionErrorNotice.propTypes = {
100
+ /** The notice message. */
101
+ message: PropTypes.string.isRequired,
102
+ /** "Restore Connection" button callback. */
103
+ restoreConnectionCallback: PropTypes.func,
104
+ /** Whether connection restore is in progress. */
105
+ isRestoringConnection: PropTypes.bool,
106
+ /** The connection error text if there is one. */
107
+ restoreConnectionError: PropTypes.string,
108
+ };
109
+
110
+ export default ConnectionErrorNotice;