From d4ab7705bf0fbc6bbd8cc08c1abacbb779726bf9 Mon Sep 17 00:00:00 2001
From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com>
Date: Tue, 1 Oct 2024 08:49:41 -0600
Subject: [PATCH] Update/at a glance unify connection ctas (#39585)
* Unify connection notices on At A Glance
* changelog
* Fix nudges on settings page
* Fix tests
---
.../_inc/client/at-a-glance/connections.jsx | 12 +---
.../_inc/client/at-a-glance/monitor.jsx | 34 ++++++-----
.../_inc/client/at-a-glance/style.scss | 34 +++++++++++
.../_inc/client/at-a-glance/test/component.js | 6 +-
.../components/connect-button/index.jsx | 44 ++++++++++----
.../connect-button/test/component.js | 59 ++++++++-----------
.../connect-button/test/fixtures.js | 27 +++++++++
.../update-at-a-glance-unify-connection-ctas | 4 ++
.../pages/wp-admin/jetpack-dashboard.js | 2 +-
9 files changed, 149 insertions(+), 73 deletions(-)
create mode 100644 projects/plugins/jetpack/_inc/client/components/connect-button/test/fixtures.js
create mode 100644 projects/plugins/jetpack/changelog/update-at-a-glance-unify-connection-ctas
diff --git a/projects/plugins/jetpack/_inc/client/at-a-glance/connections.jsx b/projects/plugins/jetpack/_inc/client/at-a-glance/connections.jsx
index 812d536a8e5d1..75b497f5429fa 100644
--- a/projects/plugins/jetpack/_inc/client/at-a-glance/connections.jsx
+++ b/projects/plugins/jetpack/_inc/client/at-a-glance/connections.jsx
@@ -71,7 +71,6 @@ export class DashConnections extends Component {
) }
- { __( 'Your site is connected to WordPress.com.', 'jetpack' ) }
{ this.props.isConnectionOwner && (
{ __( 'You are the Jetpack owner.', 'jetpack' ) }
@@ -100,7 +99,7 @@ export class DashConnections extends Component {
*/
userConnection() {
const maybeShowLinkUnlinkBtn = this.props.isConnectionOwner ? null : (
-
+
);
let cardContent = '';
@@ -131,14 +130,7 @@ export class DashConnections extends Component {
}
if ( ! this.props.isLinked ) {
- cardContent = (
-
-
- { __( 'Get the most out of Jetpack.', 'jetpack' ) }
-
-
{ maybeShowLinkUnlinkBtn }
-
- );
+ cardContent = { maybeShowLinkUnlinkBtn }
;
} else if ( this.props.isFetchingUserData ) {
cardContent = __( 'Loading…', 'jetpack' );
} else if ( ! this.props.wpComConnectedUser?.email ) {
diff --git a/projects/plugins/jetpack/_inc/client/at-a-glance/monitor.jsx b/projects/plugins/jetpack/_inc/client/at-a-glance/monitor.jsx
index 077da4c240e10..fbb2b5a1e08fd 100644
--- a/projects/plugins/jetpack/_inc/client/at-a-glance/monitor.jsx
+++ b/projects/plugins/jetpack/_inc/client/at-a-glance/monitor.jsx
@@ -3,9 +3,10 @@ import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import Button from 'components/button';
import DashItem from 'components/dash-item';
+import JetpackBanner from 'components/jetpack-banner';
import analytics from 'lib/analytics';
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import { Component } from 'react';
import { connect } from 'react-redux';
import { isOfflineMode, hasConnectedOwner, connectUser } from 'state/connection';
import { isModuleAvailable } from 'state/modules';
@@ -78,26 +79,29 @@ class DashMonitor extends Component {
support={ support }
className="jp-dash-item__is-inactive"
noToggle={ ! this.props.hasConnectedOwner }
+ overrideContent={
+ ( ! this.props.hasConnectedOwner && ! this.props.isOfflineMode && (
+
+ ) ) ||
+ null
+ }
>
{ this.props.isOfflineMode
? __( 'Unavailable in Offline Mode.', 'jetpack' )
: activateMessage }
-
- { ! this.props.isOfflineMode && ! this.props.hasConnectedOwner && (
-
- { createInterpolateElement(
- __(
- ' account to use this feature.',
- 'jetpack'
- ),
- {
- Button: ,
- }
- ) }
-
- ) }
);
}
diff --git a/projects/plugins/jetpack/_inc/client/at-a-glance/style.scss b/projects/plugins/jetpack/_inc/client/at-a-glance/style.scss
index c02e78ef720a4..5e5a65517d19b 100644
--- a/projects/plugins/jetpack/_inc/client/at-a-glance/style.scss
+++ b/projects/plugins/jetpack/_inc/client/at-a-glance/style.scss
@@ -717,3 +717,37 @@ a.jp-dash-item__manage-in-wpcom,
border-top: 1px solid $gray-5;
}
}
+
+.jp-dash-item.jp-connection-type {
+ border: 1px solid transparent;
+ border-radius: 4px;
+
+ .jp-dash-item__card {
+ padding: 16px 16px 16px 24px;
+ }
+
+ .jp-dash-item__content {
+ display: block;
+ }
+
+ .jp-connection-settings__text {
+ flex-grow: 1;
+ }
+
+ .jp-dash-item__content,
+ .jp-connection-settings__text {
+ align-self: center;
+ }
+
+ .dops-banner {
+ padding: 0;
+
+ &__title {
+ padding: 0 0.5rem 0 0;
+ }
+ }
+
+ .dops-banner.dops-card {
+ display: block;
+ }
+}
diff --git a/projects/plugins/jetpack/_inc/client/at-a-glance/test/component.js b/projects/plugins/jetpack/_inc/client/at-a-glance/test/component.js
index e23415045ce73..804f931ca4f8b 100644
--- a/projects/plugins/jetpack/_inc/client/at-a-glance/test/component.js
+++ b/projects/plugins/jetpack/_inc/client/at-a-glance/test/component.js
@@ -84,7 +84,7 @@ describe( 'Connections', () => {
it( 'shows a disconnection link', () => {
render( , { initialState: buildInitialState() } );
expect(
- withinCard( 'Site connection' ).getByRole( 'button', { name: 'Manage site connection' } )
+ withinCard( 'Site connection' ).getByRole( 'button', { name: 'Manage' } )
).toBeInTheDocument();
} );
@@ -128,8 +128,8 @@ describe( 'Connections', () => {
initialState: buildInitialState( { userIsLinked: false } ),
} );
expect(
- withinCard( 'Account connection' ).getByRole( 'link', {
- name: 'Connect your WordPress.com account',
+ withinCard( 'Account connection' ).getByRole( 'button', {
+ name: 'Connect',
} )
).toBeInTheDocument();
} );
diff --git a/projects/plugins/jetpack/_inc/client/components/connect-button/index.jsx b/projects/plugins/jetpack/_inc/client/components/connect-button/index.jsx
index 8192e4a424c84..858e6a48ffcaf 100644
--- a/projects/plugins/jetpack/_inc/client/components/connect-button/index.jsx
+++ b/projects/plugins/jetpack/_inc/client/components/connect-button/index.jsx
@@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n';
import { getFragment } from '@wordpress/url';
import Button from 'components/button';
import QuerySiteBenefits from 'components/data/query-site-benefits';
+import JetpackBanner from 'components/jetpack-banner';
import analytics from 'lib/analytics';
import PropTypes from 'prop-types';
import React from 'react';
@@ -47,6 +48,7 @@ export class ConnectButton extends React.Component {
connectUser: PropTypes.bool,
from: PropTypes.string,
asLink: PropTypes.bool,
+ asBanner: PropTypes.bool,
connectLegend: PropTypes.string,
connectInPlace: PropTypes.bool,
customConnect: PropTypes.func,
@@ -74,8 +76,11 @@ export class ConnectButton extends React.Component {
}
handleOpenModal = e => {
+ if ( e ) {
+ e.preventDefault();
+ }
+
analytics.tracks.recordJetpackClick( 'manage_site_connection' );
- e.preventDefault();
this.toggleVisibility();
};
@@ -89,7 +94,9 @@ export class ConnectButton extends React.Component {
};
loadConnectionScreen = e => {
- e.preventDefault();
+ if ( e ) {
+ e.preventDefault();
+ }
// If the iframe is already loaded or we don't have a connectUrl yet, return.
if ( this.props.isAuthorizing || this.props.fetchingConnectUrl ) {
return;
@@ -131,6 +138,23 @@ export class ConnectButton extends React.Component {
);
}
+ if ( this.props.asBanner ) {
+ return (
+
+ );
+ }
+
let connectUrl = this.props.connectUrl;
if ( this.props.from ) {
connectUrl += `&from=${ this.props.from }`;
@@ -162,15 +186,15 @@ export class ConnectButton extends React.Component {
if ( this.props.isSiteConnected ) {
return (
-
- { this.props.connectLegend || __( 'Manage site connection', 'jetpack' ) }
-
+ eventFeature="manage-site-connection"
+ path="dashboard"
+ eventProps={ { type: 'manage' } }
+ />
);
}
diff --git a/projects/plugins/jetpack/_inc/client/components/connect-button/test/component.js b/projects/plugins/jetpack/_inc/client/components/connect-button/test/component.js
index 63fa59414fbce..5a04f18247650 100644
--- a/projects/plugins/jetpack/_inc/client/components/connect-button/test/component.js
+++ b/projects/plugins/jetpack/_inc/client/components/connect-button/test/component.js
@@ -1,8 +1,8 @@
import { jest } from '@jest/globals';
import userEvent from '@testing-library/user-event';
-import React from 'react';
import { render, screen } from 'test/test-utils';
import { ConnectButton } from '../index';
+import { buildInitialState } from './fixtures';
// Mock components that do fetches in the background. We supply needed state directly.
jest.mock( 'components/data/query-site-benefits', () => ( {
@@ -25,30 +25,15 @@ describe( 'ConnectButton', () => {
asLink: false,
connectInPlace: false,
doNotUseConnectionIframe: false,
+ asBanner: true,
};
describe( 'Initially', () => {
it( 'renders a button to connect or link', () => {
- render( );
- expect(
- screen.getByRole( 'link', { name: 'Connect your WordPress.com account' } )
- ).toBeInTheDocument();
- } );
-
- it( 'disables the button while fetching the connect URL', () => {
- render( );
- expect( screen.getByRole( 'link', { name: 'Connect your WordPress.com account' } ) )
- // eslint-disable-next-line jest-dom/prefer-enabled-disabled -- `.toBeDisabled()` doesn't work on links.
- .toHaveAttribute( 'disabled' );
- } );
- } );
-
- describe( 'When it is used to link a user', () => {
- it( 'has a link to jetpack.wordpress.com', () => {
- render( );
- expect(
- screen.getByRole( 'link', { name: 'Connect your WordPress.com account' } )
- ).toHaveAttribute( 'href', 'https://jetpack.wordpress.com/jetpack.authorize/1/' );
+ render( , {
+ initialState: buildInitialState(),
+ } );
+ expect( screen.getByRole( 'button', { name: 'Connect' } ) ).toBeInTheDocument();
} );
} );
@@ -62,15 +47,15 @@ describe( 'ConnectButton', () => {
};
it( 'has a link to jetpack.wordpress.com', () => {
- render( );
- expect(
- screen.getByRole( 'link', { name: 'Link your account to WordPress.com' } )
- ).toHaveAttribute( 'href', 'https://jetpack.wordpress.com/jetpack.authorize/1/' );
+ render( , {
+ initialState: buildInitialState(),
+ } );
+ expect( screen.getByRole( 'button', { name: 'Connect' } ) ).toBeInTheDocument();
} );
it( 'when clicked, loadConnectionScreen() is called once', async () => {
const user = userEvent.setup();
- const loadConnectionScreen = jest.fn( e => e.preventDefault() );
+ const loadConnectionScreen = jest.fn();
class ConnectButtonMock extends ConnectButton {
constructor( props ) {
@@ -79,10 +64,10 @@ describe( 'ConnectButton', () => {
}
}
- render( );
- await user.click(
- screen.getByRole( 'link', { name: 'Link your account to WordPress.com' } )
- );
+ render( , {
+ initialState: buildInitialState(),
+ } );
+ await user.click( screen.getByRole( 'button', { name: 'Connect' } ) );
expect( loadConnectionScreen ).toHaveBeenCalledTimes( 1 );
} );
} );
@@ -96,7 +81,9 @@ describe( 'ConnectButton', () => {
};
it( 'does not link to a URL', () => {
- render( );
+ render( , {
+ initialState: buildInitialState(),
+ } );
expect(
screen.getByRole( 'button', { name: 'Unlink your account from WordPress.com' } )
).not.toHaveAttribute( 'href' );
@@ -152,7 +139,9 @@ describe( 'ConnectButton', () => {
};
it( 'does not link to a URL', () => {
- render( );
+ render( , {
+ initialState: buildInitialState(),
+ } );
expect(
screen.getByRole( 'button', { name: 'Disconnect your site from WordPress.com' } )
).not.toHaveAttribute( 'href' );
@@ -160,7 +149,7 @@ describe( 'ConnectButton', () => {
it( 'when clicked, handleOpenModal() is called once', async () => {
const user = userEvent.setup();
- const handleOpenModal = jest.fn( e => e.preventDefault() );
+ const handleOpenModal = jest.fn();
class ConnectButtonMock extends ConnectButton {
constructor( props ) {
@@ -169,7 +158,9 @@ describe( 'ConnectButton', () => {
}
}
- render( );
+ render( , {
+ initialState: buildInitialState(),
+ } );
await user.click(
screen.getByRole( 'button', { name: 'Disconnect your site from WordPress.com' } )
);
diff --git a/projects/plugins/jetpack/_inc/client/components/connect-button/test/fixtures.js b/projects/plugins/jetpack/_inc/client/components/connect-button/test/fixtures.js
new file mode 100644
index 0000000000000..2ffddcfba3695
--- /dev/null
+++ b/projects/plugins/jetpack/_inc/client/components/connect-button/test/fixtures.js
@@ -0,0 +1,27 @@
+/**
+ * Build an object that can be used as a Redux store initial state.
+ *
+ * @return {object} – initial Redux state
+ */
+export function buildInitialState() {
+ return {
+ jetpack: {
+ initialState: {
+ userData: {
+ currentUser: {
+ permissions: {
+ manage_modules: true,
+ },
+ },
+ },
+ },
+ connection: {
+ user: {
+ currentUser: {
+ isConnected: true,
+ },
+ },
+ },
+ },
+ };
+}
diff --git a/projects/plugins/jetpack/changelog/update-at-a-glance-unify-connection-ctas b/projects/plugins/jetpack/changelog/update-at-a-glance-unify-connection-ctas
new file mode 100644
index 0000000000000..af2f73b816a2b
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/update-at-a-glance-unify-connection-ctas
@@ -0,0 +1,4 @@
+Significance: patch
+Type: other
+
+Unify connection related CTAs on At A Glance
diff --git a/tools/e2e-commons/pages/wp-admin/jetpack-dashboard.js b/tools/e2e-commons/pages/wp-admin/jetpack-dashboard.js
index e5be2e4f82e6e..41df6fe5c5dbe 100644
--- a/tools/e2e-commons/pages/wp-admin/jetpack-dashboard.js
+++ b/tools/e2e-commons/pages/wp-admin/jetpack-dashboard.js
@@ -49,7 +49,7 @@ export default class JetpackDashboardPage extends WpPage {
logger.step( 'Checking that WordPress.com user is not connected' );
const selector = `${ this.#connectionInfoContainerSel } >> nth=1`;
return ( await this.page.locator( selector ).innerText() ).includes(
- 'Get the most out of Jetpack'
+ 'Get the most out of Jetpack by connecting your WordPress.com account'
);
}
}