Skip to content

Commit

Permalink
Jetpack: Handle disconnected site case (#47121)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rcanepa committed Nov 6, 2020
1 parent 8b3ef57 commit 054d9d1
Show file tree
Hide file tree
Showing 16 changed files with 347 additions and 9 deletions.
5 changes: 5 additions & 0 deletions client/assets/images/jetpack/disconnected-gray.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 76 additions & 0 deletions client/components/jetpack/is-jetpack-disconnected-switch/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<RenderSwitch
loadingCondition={ isLoading }
renderCondition={ isJetpackDisconnected }
queryComponent={
<>
<QueryRewindState siteId={ siteId } />
<QueryJetpackScan siteId={ siteId } />
</>
}
loadingComponent={ loadingComponent }
trueComponent={ trueComponent }
falseComponent={ falseComponent }
/>
);
};

type Props = {
loadingComponent?: ReactElement;
trueComponent?: ReactElement;
falseComponent?: ReactElement;
};

export default IsJetpackDisconnectedSwitch;
77 changes: 77 additions & 0 deletions client/components/jetpack/jetpack-disconnected-wpcom/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<PromoCard
title={ preventWidows( translate( 'Jetpack connection has failed' ) ) }
image={ { path: JetpackDisconnected } }
isPrimary
>
<p>
{ preventWidows(
translate( 'Jetpack is unable to reach your site {{siteName/}} at this moment.', {
components: { siteName: <strong>{ siteName }</strong> },
} )
) }
</p>
<p>
{ preventWidows(
translate(
'Please visit {{siteUrl/}} to ensure your site loading correctly and reconnect Jetpack if necessary.',
{
components: {
siteUrl: <ExternalLink href={ siteUrl }>{ siteUrl }</ExternalLink>,
},
}
)
) }
</p>
<div className="jetpack-disconnected-wpcom__ctas">
<Button
className="jetpack-disconnected-wpcom__cta"
href={ reconnectUrl }
onClick={ onReconnectClick }
primary
>
{ translate( 'Reconnect Jetpack' ) }
</Button>
<Button
className="jetpack-disconnected-wpcom__cta"
href={ JETPACK_SUPPORT }
onClick={ onSupportClick }
>
{ translate( 'I need help' ) }
</Button>
</div>
</PromoCard>
);
};

export default JetpackDisconnectedWPCOM;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.jetpack-disconnected-wpcom__ctas {
margin: 16px 0;
}

.jetpack-disconnected-wpcom__cta {
margin-top: 8px;
margin-right: 16px;
}
73 changes: 73 additions & 0 deletions client/components/jetpack/jetpack-disconnected/index.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<div className="jetpack-disconnected__icon-header">
<img src={ JetpackDisconnectedSVG } alt="Jetpack disconnected status" />
</div>
);

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 = [
<span className="jetpack-disconnected__paragraph" key="paragraph-1">
{ preventWidows(
translate( 'Jetpack is unable to reach your site {{siteName/}} at this moment.', {
components: { siteName: <strong>{ siteName }</strong> },
} )
) }
</span>,
<span className="jetpack-disconnected__paragraph" key="paragraph-2">
{ preventWidows(
translate(
'Please visit {{siteUrl/}} to ensure your site loading correctly and reconnect Jetpack if necessary.',
{
components: {
siteUrl: <ExternalLink href={ siteUrl }>{ siteUrl }</ExternalLink>,
},
}
)
) }
</span>,
];
return (
<Upsell
headerText={ translate( 'Jetpack connection has failed' ) }
bodyText={ body }
buttonLink={ reconnectUrl }
buttonText={ translate( 'Reconnect Jetpack' ) }
onClick={ () => dispatch( recordTracksEvent( 'calypso_jetpack_backup_reconnect_click' ) ) }
iconComponent={ <JetpackDisconnectedIcon /> }
secondaryButtonLink={ JETPACK_SUPPORT }
secondaryButtonText={ translate( 'I need help' ) }
secondaryOnClick={ () =>
dispatch( recordTracksEvent( 'calypso_jetpack_backup_support_click' ) )
}
/>
);
};

export default JetpackDisconnected;
18 changes: 18 additions & 0 deletions client/components/jetpack/jetpack-disconnected/style.scss
Original file line number Diff line number Diff line change
@@ -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;
}
22 changes: 19 additions & 3 deletions client/components/jetpack/upsell/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import React, { FunctionComponent, ReactChild } from 'react';
import React, { FunctionComponent, ReactNode } from 'react';
import { useTranslate, TranslateResult } from 'i18n-calypso';

/**
Expand All @@ -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 > = ( {
Expand All @@ -30,6 +33,9 @@ const JetpackCloudUpsell: FunctionComponent< Props > = ( {
headerText,
iconComponent,
onClick,
secondaryButtonLink,
secondaryButtonText,
secondaryOnClick,
} ) => {
const translate = useTranslate();

Expand All @@ -49,6 +55,16 @@ const JetpackCloudUpsell: FunctionComponent< Props > = ( {
{ buttonText || translate( 'Upgrade now' ) }
</Button>
) }
{ secondaryButtonLink && (
<Button
className="upsell__button"
href={ secondaryButtonLink }
onClick={ secondaryOnClick }
target="_blank"
>
{ secondaryButtonText }
</Button>
) }
</div>
);
};
Expand Down
10 changes: 10 additions & 0 deletions client/components/jetpack/upsell/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
4 changes: 4 additions & 0 deletions client/my-sites/backup/backup-upsell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -91,6 +92,9 @@ const BackupsUpsellPage: FunctionComponent< UpsellComponentProps > = ( { reason
case 'multisite_not_supported':
body = <BackupsMultisiteBody />;
break;
case 'no_connected_jetpack':
body = <JetpackDisconnected />;
break;
default:
body = <BackupsUpsellBody />;
break;
Expand Down
16 changes: 16 additions & 0 deletions client/my-sites/backup/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,6 +48,21 @@ export function showUpsellIfNoBackup( context, next ) {
next();
}

export function showJetpackIsDisconnected( context, next ) {
const JetpackConnectionFailed = isJetpackCloud() ? (
<BackupUpsell reason="no_connected_jetpack" />
) : (
<WPCOMBackupUpsell reason="no_connected_jetpack" />
);
context.primary = (
<IsJetpackDisconnectedSwitch
trueComponent={ JetpackConnectionFailed }
falseComponent={ context.primary }
/>
);
next();
}

export function showUnavailableForVaultPressSites( context, next ) {
const message = isJetpackCloud() ? (
<BackupUpsell reason="vp_active_on_site" />
Expand Down

0 comments on commit 054d9d1

Please sign in to comment.