From 9ef53016f0f022cc16c196e8d0278bc8aae8cd75 Mon Sep 17 00:00:00 2001 From: Dylan Munson <65001528+CodeyGuyDylan@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:59:15 -0700 Subject: [PATCH] Unify user connection flows in My Jetpack --- .../connected-product-card/index.tsx | 2 +- .../components/connection-screen/index.tsx | 4 +++ .../components/connections-section/index.jsx | 2 +- .../_inc/components/plans-section/index.tsx | 2 +- .../components/product-card/action-button.tsx | 25 ++++++++++------- .../_inc/components/product-card/index.tsx | 27 ++++++++++++++++--- .../product-card/secondary-button.tsx | 4 +-- .../packages/my-jetpack/_inc/constants.ts | 1 + .../hooks/use-my-jetpack-navigate/index.ts | 14 ++++------ .../use-site-connection-notice.tsx | 9 ++++--- 10 files changed, 59 insertions(+), 31 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/connected-product-card/index.tsx b/projects/packages/my-jetpack/_inc/components/connected-product-card/index.tsx index d93ac23088bb8..9c43c7f3c30c9 100644 --- a/projects/packages/my-jetpack/_inc/components/connected-product-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/connected-product-card/index.tsx @@ -66,7 +66,7 @@ const ConnectedProductCard: FC< ConnectedProductCardProps > = ( { manageUrl, } = detail; - const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.Connection ); + const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); /* * Redirect only if connected diff --git a/projects/packages/my-jetpack/_inc/components/connection-screen/index.tsx b/projects/packages/my-jetpack/_inc/components/connection-screen/index.tsx index 52954e68d9a06..f8da3fddc6c45 100644 --- a/projects/packages/my-jetpack/_inc/components/connection-screen/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/connection-screen/index.tsx @@ -1,5 +1,6 @@ import { Container, Col, AdminPage } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; +import { useSearchParams } from 'react-router-dom'; import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; import useMyJetpackReturnToPage from '../../hooks/use-my-jetpack-return-to-page'; import CloseLink from '../close-link'; @@ -9,6 +10,8 @@ import styles from './styles.module.scss'; import type { FC } from 'react'; const ConnectionScreen: FC = () => { + const [ searchParams ] = useSearchParams(); + const shouldSkipPricing = searchParams.get( 'skip_pricing' ) === 'true'; const returnToPage = useMyJetpackReturnToPage(); const { apiRoot, apiNonce, registrationNonce } = useMyJetpackConnection(); @@ -28,6 +31,7 @@ const ConnectionScreen: FC = () => { apiRoot={ apiRoot } apiNonce={ apiNonce } registrationNonce={ registrationNonce } + skipPricingPage={ shouldSkipPricing } footer={ } /> diff --git a/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx b/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx index ffb1da1e64cd9..a1ba26eff727f 100644 --- a/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx @@ -12,7 +12,7 @@ import ConnectionStatusCard from '../connection-status-card'; */ export default function ConnectionsSection() { const { apiRoot, apiNonce, topJetpackMenuItemUrl, connectedPlugins } = useMyJetpackConnection(); - const navigate = useMyJetpackNavigate( MyJetpackRoutes.Connection ); + const navigate = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); const products = useAllProducts(); const onDisconnected = () => document?.location?.reload( true ); // TODO: replace with a better experience. const productsThatRequireUserConnection = getProductSlugsThatRequireUserConnection( products ); diff --git a/projects/packages/my-jetpack/_inc/components/plans-section/index.tsx b/projects/packages/my-jetpack/_inc/components/plans-section/index.tsx index 0a7f47e1d3f04..a92023e96f30a 100644 --- a/projects/packages/my-jetpack/_inc/components/plans-section/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/plans-section/index.tsx @@ -157,7 +157,7 @@ const PlanSectionFooter: FC< PlanSectionHeaderAndFooterProps > = ( { numberOfPur recordEvent( 'jetpack_myjetpack_plans_purchase_click' ); }, [ recordEvent ] ); - const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.Connection ); + const navigateToConnectionPage = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); const activateLicenseClickHandler = useCallback( () => { recordEvent( 'jetpack_myjetpack_activate_license_click' ); if ( ! isUserConnected ) { diff --git a/projects/packages/my-jetpack/_inc/components/product-card/action-button.tsx b/projects/packages/my-jetpack/_inc/components/product-card/action-button.tsx index 61984095e7a30..b27606ae85cda 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/action-button.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/action-button.tsx @@ -7,14 +7,16 @@ import { useCallback, useState, useEffect, useMemo, useRef } from 'react'; import { PRODUCT_STATUSES } from '../../constants'; import useProduct from '../../data/products/use-product'; import useAnalytics from '../../hooks/use-analytics'; +import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; import useOutsideAlerter from '../../hooks/use-outside-alerter'; import styles from './style.module.scss'; import { ProductCardProps } from '.'; import type { SecondaryButtonProps } from './secondary-button'; -import type { FC, ComponentProps } from 'react'; +import type { FC, ComponentProps, MouseEvent } from 'react'; type ActionButtonProps< A = () => void > = ProductCardProps & { - onFixConnection?: A; + onFixUserConnection?: A; + onFixSiteConnection?: ( { e }: { e: MouseEvent< HTMLButtonElement > } ) => void; onManage?: A; onAdd?: A; onInstall?: A; @@ -34,7 +36,8 @@ const ActionButton: FC< ActionButtonProps > = ( { additionalActions, primaryActionOverride, onManage, - onFixConnection, + onFixUserConnection, + onFixSiteConnection, isFetching, isInstallingStandalone, className, @@ -48,6 +51,7 @@ const ActionButton: FC< ActionButtonProps > = ( { const [ currentAction, setCurrentAction ] = useState< ComponentProps< typeof Button > >( {} ); const { detail } = useProduct( slug ); const { manageUrl, purchaseUrl, managePaidPlanPurchaseUrl, renewPaidPlanPurchaseUrl } = detail; + const { siteIsRegistering } = useMyJetpackConnection(); const isManageDisabled = ! manageUrl; const dropdownRef = useRef( null ); const chevronRef = useRef( null ); @@ -55,7 +59,10 @@ const ActionButton: FC< ActionButtonProps > = ( { slug === 'jetpack-ai' && debug( slug, detail ); - const isBusy = isFetching || isInstallingStandalone; + const isBusy = + isFetching || + isInstallingStandalone || + ( siteIsRegistering && status === PRODUCT_STATUSES.SITE_CONNECTION_ERROR ); const hasAdditionalActions = additionalActions?.length > 0; const buttonState = useMemo< Partial< SecondaryButtonProps > >( () => { @@ -158,20 +165,19 @@ const ActionButton: FC< ActionButtonProps > = ( { case PRODUCT_STATUSES.SITE_CONNECTION_ERROR: return { ...buttonState, - href: '#/connection', variant: 'primary', label: __( 'Connect', 'jetpack-my-jetpack' ), - onClick: onFixConnection, + onClick: onFixSiteConnection, ...( primaryActionOverride && PRODUCT_STATUSES.SITE_CONNECTION_ERROR in primaryActionOverride && primaryActionOverride[ PRODUCT_STATUSES.SITE_CONNECTION_ERROR ] ), }; case PRODUCT_STATUSES.USER_CONNECTION_ERROR: return { - href: '#/connection', + href: '#/connection?skip_pricing=true', variant: 'primary', label: __( 'Connect', 'jetpack-my-jetpack' ), - onClick: onFixConnection, + onClick: onFixUserConnection, ...( primaryActionOverride && PRODUCT_STATUSES.USER_CONNECTION_ERROR in primaryActionOverride && primaryActionOverride[ PRODUCT_STATUSES.USER_CONNECTION_ERROR ] ), @@ -216,7 +222,8 @@ const ActionButton: FC< ActionButtonProps > = ( { buttonState, slug, onAdd, - onFixConnection, + onFixUserConnection, + onFixSiteConnection, onActivate, onInstall, onLearnMore, diff --git a/projects/packages/my-jetpack/_inc/components/product-card/index.tsx b/projects/packages/my-jetpack/_inc/components/product-card/index.tsx index 72b536197a895..7884fa5163a80 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/index.tsx @@ -4,6 +4,8 @@ import { useCallback, useEffect } from 'react'; import { PRODUCT_STATUSES } from '../../constants'; import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; import useAnalytics from '../../hooks/use-analytics'; +import useConnectSite from '../../hooks/use-connect-site'; +import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; import Card from '../card'; import ActionButton from './action-button'; import PriceComponent from './pricing-component'; @@ -13,7 +15,7 @@ import Status from './status'; import styles from './style.module.scss'; import type { AdditionalAction, SecondaryAction } from './types'; import type { MutateCallback } from '../../data/use-simple-mutation'; -import type { FC, MouseEventHandler, ReactNode } from 'react'; +import type { FC, MouseEvent, MouseEventHandler, ReactNode } from 'react'; export type ProductCardProps = { children?: ReactNode; @@ -85,6 +87,15 @@ const ProductCard: FC< ProductCardProps > = props => { } ); const { recordEvent } = useAnalytics(); + const { siteIsRegistering } = useMyJetpackConnection(); + const isLoading = + isFetching || ( siteIsRegistering && status === PRODUCT_STATUSES.SITE_CONNECTION_ERROR ); + const { connectSite } = useConnectSite( { + tracksInfo: { + event: 'jetpack_myjetpack_product_card_fix_site_connection', + properties: {}, + }, + } ); /** * Calls the passed function onActivate after firing Tracks event @@ -117,12 +128,19 @@ const ProductCard: FC< ProductCardProps > = props => { /** * Calls the passed function onFixConnection after firing Tracks event */ - const fixConnectionHandler = useCallback( () => { + const fixUserConnectionHandler = useCallback( () => { recordEvent( 'jetpack_myjetpack_product_card_fixconnection_click', { product: slug, } ); }, [ slug, recordEvent ] ); + const fixSiteConnectionHandler = useCallback( + ( { e }: { e: MouseEvent< HTMLButtonElement > } ) => { + connectSite( e ); + }, + [ connectSite ] + ); + /** * Calls when the "Learn more" button is clicked */ @@ -181,7 +199,8 @@ const ProductCard: FC< ProductCardProps > = props => { = props => { diff --git a/projects/packages/my-jetpack/_inc/components/product-card/secondary-button.tsx b/projects/packages/my-jetpack/_inc/components/product-card/secondary-button.tsx index 66f123af9e6b0..6e00803829ef8 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/secondary-button.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/secondary-button.tsx @@ -1,6 +1,6 @@ import { Button } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; -import type { FC, ReactNode } from 'react'; +import type { FC, ReactNode, MouseEvent } from 'react'; export type SecondaryButtonProps = { href?: string; @@ -9,7 +9,7 @@ export type SecondaryButtonProps = { weight?: 'bold' | 'regular'; label?: string; shouldShowButton?: () => boolean; - onClick?: () => void; + onClick?: ( () => void ) | ( ( { e }: { e: MouseEvent< HTMLButtonElement > } ) => void ); isExternalLink?: boolean; icon?: ReactNode; iconSize?: number; diff --git a/projects/packages/my-jetpack/_inc/constants.ts b/projects/packages/my-jetpack/_inc/constants.ts index af4cdacb11fe5..92e8ef5096f51 100644 --- a/projects/packages/my-jetpack/_inc/constants.ts +++ b/projects/packages/my-jetpack/_inc/constants.ts @@ -6,6 +6,7 @@ export const MY_JETPACK_PRODUCT_CHECKOUT = 'my-jetpack-product-checkout'; export const MyJetpackRoutes = { Home: '/', Connection: '/connection', + ConnectionSkipPricing: '/connection?skip_pricing=true', AddAkismet: '/add-akismet', AddAntiSpam: '/add-anti-spam', // Old route for Anti Spam AddBackup: '/add-backup', diff --git a/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-navigate/index.ts b/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-navigate/index.ts index b35c1d9a2bbfd..1a4a0936d3576 100644 --- a/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-navigate/index.ts +++ b/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-navigate/index.ts @@ -3,18 +3,14 @@ import { useNavigate } from 'react-router-dom'; import { MyJetpackRoutes } from '../../constants'; import type { NavigateOptions } from 'react-router-dom'; -/** - * Custom My Jetpack navigator hook - * - * @param {string} route - route to navigate to - * @return {Function} - navigate function - */ -export default function useMyJetpackNavigate( +const useMyJetpackNavigate = ( route: ( typeof MyJetpackRoutes )[ keyof typeof MyJetpackRoutes ] -) { +) => { const navigate = useNavigate(); return useCallback( ( options?: NavigateOptions ) => navigate( route, options ), [ navigate, route ] ); -} +}; + +export default useMyJetpackNavigate; diff --git a/projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-site-connection-notice.tsx b/projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-site-connection-notice.tsx index a2a731a4a956a..aee80801246ee 100644 --- a/projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-site-connection-notice.tsx +++ b/projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-site-connection-notice.tsx @@ -19,11 +19,11 @@ type RedBubbleAlerts = Window[ 'myJetpackInitialState' ][ 'redBubbleAlerts' ]; const useSiteConnectionNotice = ( redBubbleAlerts: RedBubbleAlerts ) => { const { recordEvent } = useAnalytics(); const { setNotice, resetNotice } = useContext( NoticeContext ); - const { siteIsRegistering } = useMyJetpackConnection( { + const { siteIsRegistering, isSiteConnected } = useMyJetpackConnection( { skipUserConnection: true, } ); const products = useAllProducts(); - const navToConnection = useMyJetpackNavigate( MyJetpackRoutes.Connection ); + const navToConnection = useMyJetpackNavigate( MyJetpackRoutes.ConnectionSkipPricing ); const redBubbleSlug = 'missing-connection'; const connectionError = redBubbleAlerts[ redBubbleSlug ]; const { connectSite } = useConnectSite( { @@ -48,9 +48,9 @@ const useSiteConnectionNotice = ( redBubbleAlerts: RedBubbleAlerts ) => { if ( requiresUserConnection ) { recordEvent( 'jetpack_my_jetpack_user_connection_notice_cta_click' ); navToConnection(); + } else { + connectSite( e ); } - - connectSite( e ); }; const oneProductMessage = sprintf( @@ -121,6 +121,7 @@ const useSiteConnectionNotice = ( redBubbleAlerts: RedBubbleAlerts ) => { options: noticeOptions, } ); }, [ + isSiteConnected, connectSite, navToConnection, products,