@automattic/jetpack-connection 1.2.14 → 1.3.1

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 Connection Component releases.
4
4
 
5
+ ## [1.3.1] - 2025-07-28
6
+ ### Changed
7
+ - Internal updates.
8
+
9
+ ## [1.3.0] - 2025-07-23
10
+ ### Changed
11
+ - Connection: Remove hard-coded custom errors and added support for dynamic errors. [#44281]
12
+
5
13
  ## [1.2.14] - 2025-07-21
6
14
  ### Changed
7
15
  - Update package dependencies. [#44356]
@@ -1108,6 +1116,8 @@
1108
1116
  - `Main` and `ConnectUser` components added.
1109
1117
  - `JetpackRestApiClient` API client added.
1110
1118
 
1119
+ [1.3.1]: https://github.com/Automattic/jetpack-connection-js/compare/v1.3.0...v1.3.1
1120
+ [1.3.0]: https://github.com/Automattic/jetpack-connection-js/compare/v1.2.14...v1.3.0
1111
1121
  [1.2.14]: https://github.com/Automattic/jetpack-connection-js/compare/v1.2.13...v1.2.14
1112
1122
  [1.2.13]: https://github.com/Automattic/jetpack-connection-js/compare/v1.2.12...v1.2.13
1113
1123
  [1.2.12]: https://github.com/Automattic/jetpack-connection-js/compare/v1.2.11...v1.2.12
@@ -1,4 +1,4 @@
1
- @use '@wordpress/base-styles/breakpoints';
1
+ @use "@wordpress/base-styles/breakpoints";
2
2
 
3
3
  .jp-connection__connect-screen {
4
4
  --spacing-base: 8px;
@@ -49,7 +49,7 @@
49
49
  width: 1px;
50
50
  overflow: hidden;
51
51
  white-space: nowrap;
52
-
52
+
53
53
  &:empty {
54
54
  display: none;
55
55
  }
@@ -1,5 +1,5 @@
1
- @use '@wordpress/base-styles/mixins';
2
- @use '@automattic/jetpack-base-styles/style';
1
+ @use "@wordpress/base-styles/mixins";
2
+ @use "@automattic/jetpack-base-styles/style";
3
3
 
4
4
  .jp-connection__connect-screen-layout {
5
5
  background: var(--jp-white);
@@ -115,7 +115,7 @@
115
115
  .jp-connection__connect-screen-layout__right {
116
116
  flex-grow: 1;
117
117
  flex-basis: 47%;
118
- background: #F9F9F6;
118
+ background: #f9f9f6;
119
119
  display: none;
120
120
 
121
121
  @include mixins.break-xlarge {
@@ -145,7 +145,7 @@
145
145
  padding: 4rem 6rem 4rem 4rem;
146
146
  }
147
147
  }
148
-
148
+
149
149
  .jp-connection__connect-screen-required-plan__pricing-card {
150
150
 
151
151
  /** Line up with the top of the product logo,
@@ -157,7 +157,7 @@
157
157
  top: calc(var(--spacing-base) * 9.25);
158
158
  right: calc(var(--spacing-base) * -45);
159
159
  }
160
-
160
+
161
161
  .jp-components__pricing-card {
162
162
  border-radius: var(--jp-border-radius-rna);
163
163
  width: 425px;
@@ -209,7 +209,7 @@
209
209
  right: -100px;
210
210
  top: -275px;
211
211
  z-index: 3;
212
-
212
+
213
213
  background-color: var(--jp-blue-5);
214
214
  }
215
215
 
@@ -1,4 +1,4 @@
1
- @use '@wordpress/base-styles/mixins';
1
+ @use "@wordpress/base-styles/mixins";
2
2
 
3
3
  .jp-connection__connect-screen-layout__left {
4
4
 
@@ -1,4 +1,4 @@
1
- import { Spinner, useBreakpointMatch } from '@automattic/jetpack-components';
1
+ import { Spinner } from '@automattic/jetpack-components';
2
2
  import { Icon, Notice, Path, SVG } from '@wordpress/components';
3
3
  import { __, sprintf } from '@wordpress/i18n';
4
4
  import PropTypes from 'prop-types';
@@ -19,9 +19,7 @@ const ConnectionErrorNotice = props => {
19
19
  actions = [], // New prop for custom actions
20
20
  } = props;
21
21
 
22
- const [ isBiggerThanMedium ] = useBreakpointMatch( [ 'md' ], [ '>' ] );
23
- const wrapperClassName =
24
- styles.notice + ( isBiggerThanMedium ? ' ' + styles[ 'bigger-than-medium' ] : '' );
22
+ const wrapperClassName = styles.notice;
25
23
 
26
24
  const icon = (
27
25
  <Icon
@@ -82,31 +80,41 @@ const ConnectionErrorNotice = props => {
82
80
 
83
81
  if ( actions.length > 0 ) {
84
82
  // Use custom actions
85
- actionButtons = actions.map( ( action, index ) => (
86
- <a
87
- key={ index }
88
- onClick={ action.onClick }
89
- onKeyDown={ action.onClick }
90
- className={ `${ styles.button } ${ action.variant === 'primary' ? styles.primary : '' }` }
91
- href="#"
92
- >
93
- { action.isLoading
94
- ? action.loadingText || __( 'Loading…', 'jetpack-connection-js' )
95
- : action.label }
96
- </a>
97
- ) );
83
+ actionButtons = actions.map( ( action, index ) => {
84
+ let buttonClassName = styles.button;
85
+ if ( action.variant === 'primary' ) {
86
+ buttonClassName += ' ' + styles.primary;
87
+ } else if ( action.variant === 'secondary' ) {
88
+ buttonClassName += ' ' + styles.secondary;
89
+ }
90
+
91
+ return (
92
+ <button
93
+ key={ index }
94
+ type="button"
95
+ onClick={ action.onClick }
96
+ onKeyDown={ action.onClick }
97
+ className={ buttonClassName }
98
+ disabled={ action.isLoading }
99
+ >
100
+ { action.isLoading
101
+ ? action.loadingText || __( 'Loading…', 'jetpack-connection-js' )
102
+ : action.label }
103
+ </button>
104
+ );
105
+ } );
98
106
  } else if ( restoreConnectionCallback ) {
99
107
  // Use default restore connection action for backward compatibility
100
108
  actionButtons = [
101
- <a
109
+ <button
102
110
  key="restore"
111
+ type="button"
103
112
  onClick={ restoreConnectionCallback }
104
113
  onKeyDown={ restoreConnectionCallback }
105
114
  className={ styles.button }
106
- href="#"
107
115
  >
108
116
  { __( 'Restore Connection', 'jetpack-connection-js' ) }
109
- </a>,
117
+ </button>,
110
118
  ];
111
119
  }
112
120
 
@@ -25,7 +25,7 @@
25
25
  margin: 0;
26
26
  padding: 12px 4px;
27
27
  flex-direction: column;
28
- align-items: flex-start;
28
+ align-items: center;
29
29
  }
30
30
 
31
31
  // action button
@@ -44,7 +44,6 @@
44
44
  .button:visited,
45
45
  .button:active,
46
46
  .button:hover {
47
- color: var(--jp-white);
48
47
  font-weight: 600;
49
48
  font-size: 16px;
50
49
  line-height: 24px;
@@ -54,23 +53,69 @@
54
53
  justify-content: center;
55
54
  align-items: center;
56
55
  padding: 8px 24px;
56
+ border-radius: var(--jp-border-radius);
57
+ display: inline-block;
57
58
  margin-left: calc(var(--spacing-base) * 2 + 24px); // 40px
58
59
  margin-top: 24px;
59
- background: #000;
60
- border-radius: var(--jp-border-radius);
60
+ }
61
+
62
+ .primary,
63
+ .primary:visited,
64
+ .primary:active,
65
+ .primary:hover {
66
+ color: var(--jp-white);
67
+ background: var(--jp-black);
68
+ border: 1px solid transparent;
69
+ }
70
+
71
+ .secondary,
72
+ .secondary:visited,
73
+ .secondary:active,
74
+ .secondary:hover {
75
+ background: transparent;
76
+ color: var(--jp-gray-80);
77
+ border: 1px solid var(--jp-gray-30);
61
78
 
62
- &.primary {
63
- background: var(--jp-green-50);
79
+ &:hover {
80
+ color: var(--jp-gray-100);
81
+ border-color: var(--jp-gray-50);
64
82
  }
65
83
  }
66
84
 
67
85
  .actions {
68
86
  display: flex;
69
87
  gap: calc(var(--spacing-base) * 2); // 16px
70
- flex-wrap: wrap;
88
+ flex-direction: column;
89
+ text-align: center;
90
+ margin-top: calc(var(--spacing-base) * 2); // 16px
91
+
92
+ .button,
93
+ .button:visited,
94
+ .button:active,
95
+ .button:hover {
96
+ width: 100%;
97
+ max-width: 300px;
98
+ margin: 0;
99
+ }
71
100
  }
72
101
 
73
- &.bigger-than-medium {
102
+ // Media query for screens 1100px and above
103
+ @media (min-width: 1100px) {
104
+
105
+ .actions {
106
+ margin-left: 0;
107
+ margin-top: 0;
108
+ flex-direction: row;
109
+ align-items: center;
110
+
111
+ .button,
112
+ .button:visited,
113
+ .button:active,
114
+ .button:hover {
115
+ width: auto;
116
+ max-width: none;
117
+ }
118
+ }
74
119
 
75
120
  .button,
76
121
  .button:visited,
@@ -1,6 +1,6 @@
1
- @use '@automattic/jetpack-base-styles/style';
1
+ @use "@automattic/jetpack-base-styles/style";
2
2
 
3
- // Used to show cards in the disconnection flow for active plugins and site benefits.
3
+ // Show cards in the disconnection flow for active plugins and site benefits.
4
4
  .jp-connection__disconnect-card {
5
5
  background-color: var(--jp-white);
6
6
  width: 800px;
@@ -1,4 +1,4 @@
1
- @use '@automattic/jetpack-base-styles/style';
1
+ @use "@automattic/jetpack-base-styles/style";
2
2
 
3
3
  .jp-connection__disconnect-dialog {
4
4
 
@@ -91,7 +91,7 @@
91
91
  }
92
92
 
93
93
  &::before {
94
- content: '';
94
+ content: "";
95
95
  display: block;
96
96
  width: 100%;
97
97
  position: absolute;
@@ -156,7 +156,8 @@
156
156
  justify-content: center;
157
157
  align-items: center;
158
158
 
159
- // When the screen height is shorter, hide the decorative cards to show the text and controls without scrolling.
159
+ // When the screen height is shorter, hide the decorative cards to
160
+ // show the text and controls without scrolling.
160
161
  @media (max-height: 900px) {
161
162
 
162
163
  .jp-components__decorative-card {
@@ -1,4 +1,4 @@
1
- @use '@automattic/jetpack-base-styles/style';
1
+ @use "@automattic/jetpack-base-styles/style";
2
2
 
3
3
  .jp-connect__disconnect-survey-card {
4
4
  border: 2px solid transparent;
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  &::after {
21
- content: '';
21
+ content: "";
22
22
  display: block;
23
23
  width: 5px;
24
24
  height: 5px;
@@ -38,7 +38,7 @@
38
38
  &:focus {
39
39
 
40
40
  &:not(.jp-disconnect-survey-card--selected) {
41
- border-color: var(--jp-black-80)
41
+ border-color: var(--jp-black-80);
42
42
  }
43
43
  }
44
44
 
@@ -1,4 +1,4 @@
1
- @use '@automattic/jetpack-base-styles/style';
1
+ @use "@automattic/jetpack-base-styles/style";
2
2
 
3
3
  .jp-connection__manage-dialog {
4
4
  --spacing-base: 8px;
@@ -85,7 +85,7 @@
85
85
  }
86
86
 
87
87
  &.disabled::before {
88
- content: '';
88
+ content: "";
89
89
  display: block;
90
90
  position: absolute;
91
91
  top: 0;
@@ -3,34 +3,6 @@ import ConnectionErrorNotice from '../../components/connection-error-notice';
3
3
  import useConnection from '../../components/use-connection';
4
4
  import useRestoreConnection from '../../hooks/use-restore-connection/index.jsx';
5
5
 
6
- /**
7
- * Helper function to generate user creation URL with email prepopulation
8
- *
9
- * @param {object} connectionError - The connection error object
10
- * @param {string} baseUrl - Base admin URL (defaults to '/wp-admin/')
11
- * @return {string} The complete URL for user creation with email parameters
12
- */
13
- export function getProtectedOwnerCreateAccountUrl( connectionError, baseUrl = '/wp-admin/' ) {
14
- let redirectUrl = baseUrl + 'user-new.php';
15
-
16
- // Add protected owner email if available for prepopulation
17
- if ( connectionError?.error_data?.wpcom_user_email ) {
18
- const params = new URLSearchParams( {
19
- jetpack_protected_owner_email: connectionError.error_data.wpcom_user_email,
20
- jetpack_create_missing_account: '1',
21
- } );
22
- redirectUrl += '?' + params.toString();
23
- } else if ( connectionError?.error_data?.email ) {
24
- const params = new URLSearchParams( {
25
- jetpack_protected_owner_email: connectionError.error_data.email,
26
- jetpack_create_missing_account: '1',
27
- } );
28
- redirectUrl += '?' + params.toString();
29
- }
30
-
31
- return redirectUrl;
32
- }
33
-
34
6
  /**
35
7
  * Connection error notice hook.
36
8
  * Returns connection error data and conditional flag on whether
@@ -48,7 +20,7 @@ export default function useConnectionErrorNotice() {
48
20
 
49
21
  const connectionErrorMessage = firstError && firstError.error_message;
50
22
 
51
- // Return all connection errors, including protected owner errors
23
+ // Return all connection errors
52
24
  const hasConnectionError = Boolean( connectionErrorMessage );
53
25
 
54
26
  return {
@@ -60,9 +32,9 @@ export default function useConnectionErrorNotice() {
60
32
  }
61
33
 
62
34
  export const ConnectionError = ( {
63
- onCreateMissingAccount = null, // Custom handler for protected owner errors
35
+ actionHandlers = {}, // Handlers for specific actions like { create_missing_account: () => {}, custom_action: () => {} }
64
36
  trackingCallback = null, // Custom tracking function
65
- customActions = null, // Function that returns custom actions based on error
37
+ customActions = null, // Function that returns custom actions based on error (takes precedence)
66
38
  } = {} ) => {
67
39
  const { hasConnectionError, connectionErrorMessage, connectionError } =
68
40
  useConnectionErrorNotice();
@@ -73,48 +45,140 @@ export const ConnectionError = ( {
73
45
  return null;
74
46
  }
75
47
 
76
- // Determine error type
77
- const isProtectedOwnerError = connectionError && connectionError.error_type === 'protected_owner';
78
-
79
- // Build actions array based on error type
48
+ // Build actions array based on error data
80
49
  let actions = [];
81
50
 
82
51
  if ( customActions ) {
83
52
  // Use provided custom actions function
84
- actions = customActions( connectionError, { restoreConnection, isRestoringConnection } );
85
- } else if ( isProtectedOwnerError && onCreateMissingAccount ) {
86
- // Handle protected owner error with custom handler
87
- actions = [
88
- {
89
- label: __( 'Create missing account', 'jetpack-connection-js' ),
90
- onClick: () => {
91
- if ( trackingCallback ) {
92
- trackingCallback( 'jetpack_connection_protected_owner_create_account_attempt', {} );
93
- }
94
- onCreateMissingAccount();
53
+ try {
54
+ actions = customActions( connectionError, { restoreConnection, isRestoringConnection } );
55
+ } catch {
56
+ // Silently fall back to default behavior if customActions fails
57
+ actions = [];
58
+ }
59
+ } else {
60
+ // Get action info from error data
61
+ const errorData = connectionError?.error_data || {};
62
+ const suggestedAction = errorData.action;
63
+ const actionHandler = actionHandlers[ suggestedAction ];
64
+
65
+ if ( suggestedAction && actionHandler ) {
66
+ // Use action data from the error
67
+ const actionLabel = errorData.action_label || __( 'Take Action', 'jetpack-connection-js' );
68
+ const actionVariant = errorData.action_variant || 'primary';
69
+ const trackingEvent = errorData.tracking_event;
70
+
71
+ actions = [
72
+ {
73
+ label: actionLabel,
74
+ onClick: () => {
75
+ try {
76
+ if ( trackingCallback && trackingEvent ) {
77
+ trackingCallback( trackingEvent, {} );
78
+ }
79
+ actionHandler( connectionError );
80
+ } catch {
81
+ // Silently fail if action handler throws
82
+ }
83
+ },
84
+ variant: actionVariant,
85
+ },
86
+ ];
87
+ } else if ( errorData.action_url && errorData.action_label ) {
88
+ // Generic link action - requires both URL and label for clarity
89
+ const actionLabel = errorData.action_label;
90
+ const actionVariant = errorData.action_variant || 'primary';
91
+ const trackingEvent = errorData.tracking_event;
92
+
93
+ actions = [
94
+ {
95
+ label: actionLabel,
96
+ onClick: () => {
97
+ try {
98
+ if ( trackingCallback && trackingEvent ) {
99
+ trackingCallback( trackingEvent, {} );
100
+ }
101
+ window.location.href = errorData.action_url;
102
+ } catch {
103
+ // Silently fail if navigation throws
104
+ }
105
+ },
106
+ variant: actionVariant,
95
107
  },
96
- variant: 'primary',
97
- },
98
- ];
99
- } else if ( ! isProtectedOwnerError ) {
100
- // Standard connection error - use restore connection
101
- actions = [
102
- {
103
- label: __( 'Restore Connection', 'jetpack-connection-js' ),
104
- onClick: () => {
105
- if ( trackingCallback ) {
106
- trackingCallback( 'jetpack_connection_error_notice_reconnect_cta_click', {} );
107
- }
108
- restoreConnection();
108
+ ];
109
+ } else {
110
+ // Default action - restore connection
111
+ actions = [
112
+ {
113
+ label: __( 'Restore Connection', 'jetpack-connection-js' ),
114
+ onClick: () => {
115
+ try {
116
+ if ( trackingCallback ) {
117
+ trackingCallback( 'jetpack_connection_error_notice_reconnect_cta_click', {} );
118
+ }
119
+ restoreConnection();
120
+ } catch {
121
+ // Silently fail if restore connection throws
122
+ }
123
+ },
124
+ isLoading: isRestoringConnection,
125
+ loadingText: __( 'Reconnecting Jetpack…', 'jetpack-connection-js' ),
109
126
  },
110
- isLoading: isRestoringConnection,
111
- loadingText: __( 'Reconnecting Jetpack…', 'jetpack-connection-js' ),
112
- },
113
- ];
127
+ ];
128
+ }
129
+
130
+ // Add secondary action if available (only for custom errors, not default restore)
131
+ if ( actions.length > 0 && ( suggestedAction || errorData.action_url ) ) {
132
+ const secondaryAction = errorData.secondary_action;
133
+ const secondaryActionHandler = actionHandlers[ secondaryAction ];
134
+ const secondaryActionUrl = errorData.secondary_action_url;
135
+ const secondaryActionLabel = errorData.secondary_action_label;
136
+
137
+ // Secondary action with handler
138
+ if ( secondaryAction && secondaryActionHandler && secondaryActionLabel ) {
139
+ const secondaryActionVariant = errorData.secondary_action_variant || 'secondary';
140
+ const secondaryTrackingEvent = errorData.secondary_tracking_event;
141
+
142
+ actions.push( {
143
+ label: secondaryActionLabel,
144
+ onClick: () => {
145
+ try {
146
+ if ( trackingCallback && secondaryTrackingEvent ) {
147
+ trackingCallback( secondaryTrackingEvent, {} );
148
+ }
149
+ secondaryActionHandler( connectionError );
150
+ } catch {
151
+ // Silently fail if secondary action handler throws
152
+ }
153
+ },
154
+ variant: secondaryActionVariant,
155
+ } );
156
+ }
157
+ // Secondary action with URL (requires both URL and label)
158
+ else if ( secondaryActionUrl && secondaryActionLabel ) {
159
+ const secondaryActionVariant = errorData.secondary_action_variant || 'secondary';
160
+ const secondaryTrackingEvent = errorData.secondary_tracking_event;
161
+
162
+ actions.push( {
163
+ label: secondaryActionLabel,
164
+ onClick: () => {
165
+ try {
166
+ if ( trackingCallback && secondaryTrackingEvent ) {
167
+ trackingCallback( secondaryTrackingEvent, {} );
168
+ }
169
+ window.location.href = secondaryActionUrl;
170
+ } catch {
171
+ // Silently fail if secondary action navigation throws
172
+ }
173
+ },
174
+ variant: secondaryActionVariant,
175
+ } );
176
+ }
177
+ }
114
178
  }
115
179
 
116
- // For protected owner errors without custom handler, don't show the component
117
- if ( isProtectedOwnerError && ! onCreateMissingAccount && ! customActions ) {
180
+ // If no actions are available and no custom handler provided, don't render
181
+ if ( actions.length === 0 && ! customActions ) {
118
182
  return null;
119
183
  }
120
184
 
package/index.jsx CHANGED
@@ -49,7 +49,4 @@ export { STORE_ID as CONNECTION_STORE_ID } from './state/store';
49
49
  */
50
50
  export { default as useProductCheckoutWorkflow } from './hooks/use-product-checkout-workflow';
51
51
  export { default as useRestoreConnection } from './hooks/use-restore-connection';
52
- export {
53
- default as useConnectionErrorNotice,
54
- getProtectedOwnerCreateAccountUrl,
55
- } from './hooks/use-connection-error-notice';
52
+ export { default as useConnectionErrorNotice } from './hooks/use-connection-error-notice';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-connection",
3
- "version": "1.2.14",
3
+ "version": "1.3.1",
4
4
  "description": "Jetpack Connection Component",
5
5
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/connection/#readme",
6
6
  "bugs": {
@@ -16,7 +16,7 @@
16
16
  "dependencies": {
17
17
  "@automattic/jetpack-analytics": "^1.0.3",
18
18
  "@automattic/jetpack-api": "^1.0.5",
19
- "@automattic/jetpack-components": "^1.1.14",
19
+ "@automattic/jetpack-components": "^1.1.15",
20
20
  "@automattic/jetpack-config": "^1.0.3",
21
21
  "@automattic/jetpack-script-data": "^0.5.0",
22
22
  "@wordpress/base-styles": "6.2.0",
@@ -31,7 +31,7 @@
31
31
  "prop-types": "^15.7.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@automattic/jetpack-base-styles": "^1.0.5",
34
+ "@automattic/jetpack-base-styles": "^1.0.6",
35
35
  "@babel/core": "7.28.0",
36
36
  "@babel/preset-react": "7.27.1",
37
37
  "@testing-library/dom": "10.4.0",