From 054d9d16588b137376f81df1562b05541dd7666f Mon Sep 17 00:00:00 2001 From: Renzo Canepa Date: Fri, 6 Nov 2020 15:07:40 -0300 Subject: [PATCH] Jetpack: Handle disconnected site case (#47121) * Create Jetpack disconnected interstitial page * Create Jetpack disconnected switch component * Create new Upsell component status * Add an optional secondary button to Upsell * Include Jetpack Scan status in the check * Add Jetpack Disconnected gray SVG * Rename the switch component * Use Jetpack Support URL * Configure CTAs links * Abstract Disconnected components in their own files * Support disconnected state in Scan controller * Add `type=down` query parameter --- .../images/jetpack/disconnected-gray.svg | 5 ++ .../is-jetpack-disconnected-switch/index.tsx | 76 ++++++++++++++++++ .../jetpack-disconnected-wpcom/index.tsx | 77 +++++++++++++++++++ .../jetpack-disconnected-wpcom/style.scss | 8 ++ .../jetpack/jetpack-disconnected/index.tsx | 73 ++++++++++++++++++ .../jetpack/jetpack-disconnected/style.scss | 18 +++++ client/components/jetpack/upsell/index.tsx | 22 +++++- client/components/jetpack/upsell/style.scss | 10 +++ .../my-sites/backup/backup-upsell/index.tsx | 4 + client/my-sites/backup/controller.js | 16 ++++ client/my-sites/backup/index.js | 4 + .../my-sites/backup/wpcom-backup-upsell.tsx | 6 +- client/my-sites/scan/controller.js | 16 ++++ client/my-sites/scan/index.js | 4 + client/my-sites/scan/scan-upsell/index.jsx | 13 ++-- client/my-sites/scan/wpcom-scan-upsell.tsx | 4 + 16 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 client/assets/images/jetpack/disconnected-gray.svg create mode 100644 client/components/jetpack/is-jetpack-disconnected-switch/index.tsx create mode 100644 client/components/jetpack/jetpack-disconnected-wpcom/index.tsx create mode 100644 client/components/jetpack/jetpack-disconnected-wpcom/style.scss create mode 100644 client/components/jetpack/jetpack-disconnected/index.tsx create mode 100644 client/components/jetpack/jetpack-disconnected/style.scss diff --git a/client/assets/images/jetpack/disconnected-gray.svg b/client/assets/images/jetpack/disconnected-gray.svg new file mode 100644 index 0000000000000..b9a041360a446 --- /dev/null +++ b/client/assets/images/jetpack/disconnected-gray.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/client/components/jetpack/is-jetpack-disconnected-switch/index.tsx b/client/components/jetpack/is-jetpack-disconnected-switch/index.tsx new file mode 100644 index 0000000000000..21f1626940ed5 --- /dev/null +++ b/client/components/jetpack/is-jetpack-disconnected-switch/index.tsx @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import React, { ReactElement, useCallback } from 'react'; +import { useSelector } from 'react-redux'; + +/** + * Internal dependencies + */ +import QueryJetpackScan from 'calypso/components/data/query-jetpack-scan'; +import QueryRewindState from 'calypso/components/data/query-rewind-state'; +import RenderSwitch from 'calypso/components/jetpack/render-switch'; +import getSelectedSiteId from 'calypso/state/ui/selectors/get-selected-site-id'; +import getRewindState from 'calypso/state/selectors/get-rewind-state'; +import getSiteScanState from 'calypso/state/selectors/get-site-scan-state'; + +const stateImpliesJetpackIsDisconnected = ( productState?: { + state?: string; + reason?: string; +} ) => { + if ( ! productState ) { + return undefined; + } + + return productState.state === 'unavailable' && productState.reason === 'unknown'; +}; + +const isInitialized = ( productState: { state?: string } | null ) => + productState && productState.state !== 'uninitialized'; + +const IsJetpackDisconnectedSwitch: React.FC< Props > = ( { + loadingComponent, + trueComponent, + falseComponent, +} ) => { + const siteId = useSelector( getSelectedSiteId ); + const rewindState = useSelector( ( state ) => getRewindState( state, siteId ) ); + const scanState = useSelector( ( state ) => getSiteScanState( state, siteId ) ); + + const isJetpackDisconnected = useCallback( + () => [ rewindState, scanState ].some( stateImpliesJetpackIsDisconnected ), + [ rewindState, scanState ] + ); + + const isLoading = useCallback( () => { + if ( isJetpackDisconnected() ) { + return false; + } + + return [ rewindState, scanState ].some( ( state ) => ! isInitialized( state ) ); + }, [ isJetpackDisconnected, rewindState, scanState ] ); + + return ( + + + + + } + loadingComponent={ loadingComponent } + trueComponent={ trueComponent } + falseComponent={ falseComponent } + /> + ); +}; + +type Props = { + loadingComponent?: ReactElement; + trueComponent?: ReactElement; + falseComponent?: ReactElement; +}; + +export default IsJetpackDisconnectedSwitch; diff --git a/client/components/jetpack/jetpack-disconnected-wpcom/index.tsx b/client/components/jetpack/jetpack-disconnected-wpcom/index.tsx new file mode 100644 index 0000000000000..035adc3eecb87 --- /dev/null +++ b/client/components/jetpack/jetpack-disconnected-wpcom/index.tsx @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import React, { FunctionComponent } from 'react'; +import { useTranslate } from 'i18n-calypso'; +import { useSelector } from 'react-redux'; +import { Button } from '@automattic/components'; + +/** + * Internal dependencies + */ +import { getSelectedSite } from 'calypso/state/ui/selectors'; +import { preventWidows } from 'calypso/lib/formatting'; +import { JETPACK_SUPPORT } from 'calypso/lib/url/support'; +import ExternalLink from 'calypso/components/external-link'; +import PromoCard from 'calypso/components/promo-section/promo-card'; +import useTrackCallback from 'calypso/lib/jetpack/use-track-callback'; + +/** + * Asset dependencies + */ +import JetpackDisconnected from 'calypso/assets/images/jetpack/disconnected.svg'; +import './style.scss'; + +const JetpackDisconnectedWPCOM: FunctionComponent = () => { + const translate = useTranslate(); + const { name: siteName, slug: siteSlug, URL: siteUrl } = useSelector( getSelectedSite ); + const reconnectUrl = `/settings/disconnect-site/${ siteSlug }?type=down`; + const onReconnectClick = useTrackCallback( undefined, 'calypso_jetpack_backup_reconnect_click' ); + const onSupportClick = useTrackCallback( undefined, 'calypso_jetpack_backup_support_click' ); + return ( + +

+ { preventWidows( + translate( 'Jetpack is unable to reach your site {{siteName/}} at this moment.', { + components: { siteName: { siteName } }, + } ) + ) } +

+

+ { preventWidows( + translate( + 'Please visit {{siteUrl/}} to ensure your site loading correctly and reconnect Jetpack if necessary.', + { + components: { + siteUrl: { siteUrl }, + }, + } + ) + ) } +

+
+ + +
+
+ ); +}; + +export default JetpackDisconnectedWPCOM; diff --git a/client/components/jetpack/jetpack-disconnected-wpcom/style.scss b/client/components/jetpack/jetpack-disconnected-wpcom/style.scss new file mode 100644 index 0000000000000..7e56df35512cd --- /dev/null +++ b/client/components/jetpack/jetpack-disconnected-wpcom/style.scss @@ -0,0 +1,8 @@ +.jetpack-disconnected-wpcom__ctas { + margin: 16px 0; +} + +.jetpack-disconnected-wpcom__cta { + margin-top: 8px; + margin-right: 16px; +} diff --git a/client/components/jetpack/jetpack-disconnected/index.tsx b/client/components/jetpack/jetpack-disconnected/index.tsx new file mode 100644 index 0000000000000..70a7e5ccc827a --- /dev/null +++ b/client/components/jetpack/jetpack-disconnected/index.tsx @@ -0,0 +1,73 @@ +/** + * External dependencies + */ +import React, { FunctionComponent } from 'react'; +import { useTranslate } from 'i18n-calypso'; +import { useSelector, useDispatch } from 'react-redux'; + +/** + * Internal dependencies + */ +import { getSelectedSite } from 'calypso/state/ui/selectors'; +import ExternalLink from 'calypso/components/external-link'; +import Upsell from 'calypso/components/jetpack/upsell'; +import { preventWidows } from 'calypso/lib/formatting'; +import { JETPACK_SUPPORT } from 'calypso/lib/url/support'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; + +/** + * Style dependencies + */ +import JetpackDisconnectedSVG from 'calypso/assets/images/jetpack/disconnected-gray.svg'; +import './style.scss'; + +const JetpackDisconnectedIcon = () => ( +
+ Jetpack disconnected status +
+); + +const JetpackDisconnected: FunctionComponent = () => { + const translate = useTranslate(); + const dispatch = useDispatch(); + const { name: siteName, slug: siteSlug, URL: siteUrl } = useSelector( getSelectedSite ); + const reconnectUrl = `https://wordpress.com/settings/disconnect-site/${ siteSlug }?type=down`; + const body = [ + + { preventWidows( + translate( 'Jetpack is unable to reach your site {{siteName/}} at this moment.', { + components: { siteName: { siteName } }, + } ) + ) } + , + + { preventWidows( + translate( + 'Please visit {{siteUrl/}} to ensure your site loading correctly and reconnect Jetpack if necessary.', + { + components: { + siteUrl: { siteUrl }, + }, + } + ) + ) } + , + ]; + return ( + dispatch( recordTracksEvent( 'calypso_jetpack_backup_reconnect_click' ) ) } + iconComponent={ } + secondaryButtonLink={ JETPACK_SUPPORT } + secondaryButtonText={ translate( 'I need help' ) } + secondaryOnClick={ () => + dispatch( recordTracksEvent( 'calypso_jetpack_backup_support_click' ) ) + } + /> + ); +}; + +export default JetpackDisconnected; diff --git a/client/components/jetpack/jetpack-disconnected/style.scss b/client/components/jetpack/jetpack-disconnected/style.scss new file mode 100644 index 0000000000000..a70c09713e5c0 --- /dev/null +++ b/client/components/jetpack/jetpack-disconnected/style.scss @@ -0,0 +1,18 @@ +.jetpack-disconnected__icon-header { + text-align: center; + + img { + height: 72px; + width: auto; + } + + @include breakpoint-deprecated( '>660px' ) { + text-align: start; + } +} + +.jetpack-disconnected__paragraph { + display: block; + + margin-bottom: 1.5rem; +} diff --git a/client/components/jetpack/upsell/index.tsx b/client/components/jetpack/upsell/index.tsx index 4a4e4dd41bade..d6d48a3ff99d1 100644 --- a/client/components/jetpack/upsell/index.tsx +++ b/client/components/jetpack/upsell/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import React, { FunctionComponent, ReactChild } from 'react'; +import React, { FunctionComponent, ReactNode } from 'react'; import { useTranslate, TranslateResult } from 'i18n-calypso'; /** @@ -15,12 +15,15 @@ import { Button } from '@automattic/components'; import './style.scss'; interface Props { - bodyText: TranslateResult; + bodyText: TranslateResult | ReactNode; buttonLink?: TranslateResult; buttonText?: TranslateResult; headerText: TranslateResult; - iconComponent?: ReactChild; + iconComponent?: ReactNode; onClick?: () => void; + secondaryButtonLink?: TranslateResult; + secondaryButtonText?: TranslateResult; + secondaryOnClick?: () => void; } const JetpackCloudUpsell: FunctionComponent< Props > = ( { @@ -30,6 +33,9 @@ const JetpackCloudUpsell: FunctionComponent< Props > = ( { headerText, iconComponent, onClick, + secondaryButtonLink, + secondaryButtonText, + secondaryOnClick, } ) => { const translate = useTranslate(); @@ -49,6 +55,16 @@ const JetpackCloudUpsell: FunctionComponent< Props > = ( { { buttonText || translate( 'Upgrade now' ) } ) } + { secondaryButtonLink && ( + + ) } ); }; diff --git a/client/components/jetpack/upsell/style.scss b/client/components/jetpack/upsell/style.scss index de626e77e0d54..8c6d700d15920 100644 --- a/client/components/jetpack/upsell/style.scss +++ b/client/components/jetpack/upsell/style.scss @@ -17,3 +17,13 @@ width: auto; } } + +.upsell__button:not( .is-primary ) { + margin-left: 0; + + border-color: var( --color-neutral-10 ); + + @include breakpoint-deprecated( '>660px' ) { + margin-left: 16px; + } +} diff --git a/client/my-sites/backup/backup-upsell/index.tsx b/client/my-sites/backup/backup-upsell/index.tsx index d2bf729b10603..da379051a9dea 100644 --- a/client/my-sites/backup/backup-upsell/index.tsx +++ b/client/my-sites/backup/backup-upsell/index.tsx @@ -10,6 +10,7 @@ import { useSelector, useDispatch } from 'react-redux'; */ import { getSelectedSiteSlug } from 'calypso/state/ui/selectors'; import DocumentHead from 'calypso/components/data/document-head'; +import JetpackDisconnected from 'calypso/components/jetpack/jetpack-disconnected'; import Main from 'calypso/components/main'; import SidebarNavigation from 'calypso/my-sites/sidebar-navigation'; import Upsell from 'calypso/components/jetpack/upsell'; @@ -91,6 +92,9 @@ const BackupsUpsellPage: FunctionComponent< UpsellComponentProps > = ( { reason case 'multisite_not_supported': body = ; break; + case 'no_connected_jetpack': + body = ; + break; default: body = ; break; diff --git a/client/my-sites/backup/controller.js b/client/my-sites/backup/controller.js index 4bfc644471643..e2513322b7026 100644 --- a/client/my-sites/backup/controller.js +++ b/client/my-sites/backup/controller.js @@ -21,6 +21,7 @@ import QueryRewindState from 'calypso/components/data/query-rewind-state'; import isJetpackCloud from 'calypso/lib/jetpack/is-jetpack-cloud'; import { isJetpackBackupSlug } from 'calypso/lib/products-values'; import HasVaultPressSwitch from 'calypso/components/jetpack/has-vaultpress-switch'; +import IsJetpackDisconnectedSwitch from 'calypso/components/jetpack/is-jetpack-disconnected-switch'; export function showUpsellIfNoBackup( context, next ) { const UpsellComponent = isJetpackCloud() ? BackupUpsell : WPCOMBackupUpsell; @@ -47,6 +48,21 @@ export function showUpsellIfNoBackup( context, next ) { next(); } +export function showJetpackIsDisconnected( context, next ) { + const JetpackConnectionFailed = isJetpackCloud() ? ( + + ) : ( + + ); + context.primary = ( + + ); + next(); +} + export function showUnavailableForVaultPressSites( context, next ) { const message = isJetpackCloud() ? ( diff --git a/client/my-sites/backup/index.js b/client/my-sites/backup/index.js index 7b456a6b55507..483590f6e31b5 100644 --- a/client/my-sites/backup/index.js +++ b/client/my-sites/backup/index.js @@ -10,6 +10,7 @@ import { backupDownload, backupRestore, backups, + showJetpackIsDisconnected, showUpsellIfNoBackup, showUnavailableForVaultPressSites, showUnavailableForMultisites, @@ -47,6 +48,7 @@ export default function () { wrapInSiteOffsetProvider, wpcomUpsellController( WPCOMUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, @@ -63,6 +65,7 @@ export default function () { wrapInSiteOffsetProvider, wpcomUpsellController( WPCOMUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, @@ -80,6 +83,7 @@ export default function () { showUpsellIfNoBackup, wpcomUpsellController( WPCOMUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, diff --git a/client/my-sites/backup/wpcom-backup-upsell.tsx b/client/my-sites/backup/wpcom-backup-upsell.tsx index 16cf7918e8048..8c11bf182b145 100644 --- a/client/my-sites/backup/wpcom-backup-upsell.tsx +++ b/client/my-sites/backup/wpcom-backup-upsell.tsx @@ -10,11 +10,12 @@ import { Button } from '@automattic/components'; /** * Internal dependencies */ -import { getSelectedSiteSlug, getSelectedSiteId } from 'calypso/state/ui/selectors'; +import { getSelectedSiteId, getSelectedSiteSlug } from 'calypso/state/ui/selectors'; import canCurrentUser from 'calypso/state/selectors/can-current-user'; import { preventWidows } from 'calypso/lib/formatting'; import DocumentHead from 'calypso/components/data/document-head'; import FormattedHeader from 'calypso/components/formatted-header'; +import JetpackDisconnectedWPCOM from 'calypso/components/jetpack/jetpack-disconnected-wpcom'; import Main from 'calypso/components/main'; import Notice from 'calypso/components/notice'; import PageViewTracker from 'calypso/lib/analytics/page-view-tracker'; @@ -135,6 +136,9 @@ export default function WPCOMUpsellPage( { reason }: { reason: string } ): React case 'vp_active_on_site': body = ; break; + case 'no_connected_jetpack': + body = ; + break; default: body = ; } diff --git a/client/my-sites/scan/controller.js b/client/my-sites/scan/controller.js index bf65bdfff2dc8..e269aaca7fcb9 100644 --- a/client/my-sites/scan/controller.js +++ b/client/my-sites/scan/controller.js @@ -17,6 +17,7 @@ import getSiteScanState from 'calypso/state/selectors/get-site-scan-state'; import isJetpackSiteMultiSite from 'calypso/state/sites/selectors/is-jetpack-site-multi-site'; import getSelectedSiteId from 'calypso/state/ui/selectors/get-selected-site-id'; import QueryJetpackScan from 'calypso/components/data/query-jetpack-scan'; +import IsJetpackDisconnectedSwitch from 'calypso/components/jetpack/is-jetpack-disconnected-switch'; import ScanPlaceholder from 'calypso/components/jetpack/scan-placeholder'; import ScanHistoryPlaceholder from 'calypso/components/jetpack/scan-history-placeholder'; import isJetpackCloud from 'calypso/lib/jetpack/is-jetpack-cloud'; @@ -32,6 +33,21 @@ export function showUpsellIfNoScanHistory( context, next ) { next(); } +export function showJetpackIsDisconnected( context, next ) { + const JetpackConnectionFailed = isJetpackCloud() ? ( + + ) : ( + + ); + context.primary = ( + + ); + next(); +} + export function showUnavailableForVaultPressSites( context, next ) { const message = isJetpackCloud() ? ( diff --git a/client/my-sites/scan/index.js b/client/my-sites/scan/index.js index faf610c810887..9d438d2a2c060 100644 --- a/client/my-sites/scan/index.js +++ b/client/my-sites/scan/index.js @@ -14,6 +14,7 @@ import wpcomUpsellController from 'calypso/lib/jetpack/wpcom-upsell-controller'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; import isJetpackSectionEnabledForSite from 'calypso/state/selectors/is-jetpack-section-enabled-for-site'; import { + showJetpackIsDisconnected, showUpsellIfNoScan, showUpsellIfNoScanHistory, showUnavailableForVaultPressSites, @@ -48,6 +49,7 @@ export default function () { showUpsellIfNoScan, wpcomUpsellController( WPCOMScanUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, @@ -63,6 +65,7 @@ export default function () { showUpsellIfNoScanHistory, wpcomUpsellController( WPCOMScanUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, @@ -78,6 +81,7 @@ export default function () { showUpsellIfNoScan, wpcomUpsellController( WPCOMScanUpsellPage ), showUnavailableForVaultPressSites, + showJetpackIsDisconnected, showUnavailableForMultisites, notFoundIfNotEnabled, makeLayout, diff --git a/client/my-sites/scan/scan-upsell/index.jsx b/client/my-sites/scan/scan-upsell/index.jsx index 0c87d24a3b8df..b83316239187f 100644 --- a/client/my-sites/scan/scan-upsell/index.jsx +++ b/client/my-sites/scan/scan-upsell/index.jsx @@ -9,6 +9,7 @@ import { useTranslate } from 'i18n-calypso'; * Internal dependencies */ import DocumentHead from 'calypso/components/data/document-head'; +import JetpackDisconnected from 'calypso/components/jetpack/jetpack-disconnected'; import SecurityIcon from 'calypso/components/jetpack/security-icon'; import Main from 'calypso/components/main'; import SidebarNavigation from 'calypso/my-sites/sidebar-navigation'; @@ -84,11 +85,13 @@ function ScanUpsellBody() { } function renderUpsell( reason ) { - if ( 'multisite_not_supported' === reason ) { - return ; - } - if ( 'vp_active_on_site' === reason ) { - return ; + switch ( reason ) { + case 'vp_active_on_site': + return ; + case 'multisite_not_supported': + return ; + case 'no_connected_jetpack': + return ; } return ; } diff --git a/client/my-sites/scan/wpcom-scan-upsell.tsx b/client/my-sites/scan/wpcom-scan-upsell.tsx index 916e93f3032cb..c831b61554ce0 100644 --- a/client/my-sites/scan/wpcom-scan-upsell.tsx +++ b/client/my-sites/scan/wpcom-scan-upsell.tsx @@ -14,6 +14,7 @@ import { getSelectedSiteSlug, getSelectedSiteId } from 'calypso/state/ui/selecto import canCurrentUser from 'calypso/state/selectors/can-current-user'; import DocumentHead from 'calypso/components/data/document-head'; import FormattedHeader from 'calypso/components/formatted-header'; +import JetpackDisconnectedWPCOM from 'calypso/components/jetpack/jetpack-disconnected-wpcom'; import Main from 'calypso/components/main'; import Notice from 'calypso/components/notice'; import PageViewTracker from 'calypso/lib/analytics/page-view-tracker'; @@ -133,6 +134,9 @@ export default function WPCOMScanUpsellPage( { reason }: { reason?: string } ): case 'vp_active_on_site': body = ; break; + case 'no_connected_jetpack': + body = ; + break; default: body = ; }