From a950c3f0172479cd01e8a55ad030bb309684b80f Mon Sep 17 00:00:00 2001 From: Rafael Agostini Date: Tue, 20 Aug 2024 17:41:58 -0500 Subject: [PATCH] Backup: decouple connection screens from useConnection hook (#38948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add backup connection screen components * Add useBackupProductInfo hook This hook will fetch and manage backup product pricing information needed on connection screens * Rename useBackupProductInfo import * Refactor useConnection hook, decouple components Moved BackupConnectionScreen and BackupSecondaryAdminConnectionScreen away from this hook. * Update useConnection hook jsdoc * Update usage of useConnection hook - Replaced destructured array usage with direct connectionStatus assignment. - Use BackupConnectionScreen and BackupSecondaryAdminConnectionScreen directly. * Delete assets - These assets were moved inside backup-connection-screen component. - connect-right.png wasn’t in use * changelog * Fix lint issue --- .../changelog/fix-backup-duplicated-calls | 4 + .../backup/src/js/components/Admin/header.js | 2 +- .../backup/src/js/components/Admin/hooks.js | 4 +- .../backup/src/js/components/Admin/index.js | 8 +- .../src/js/components/back-up-now/hooks.js | 2 +- .../assets/ben-giordano-testimonial.png | Bin .../assets/connect-backup.png | Bin .../assets/tim-ferriss-testimonial.png | Bin .../backup-connection-screen/index.js | 83 ++++++++++++ .../secondary-admin.js | 32 +++++ .../components/backup-storage-space/index.jsx | 2 +- .../src/js/hooks/use-backup-product-info.js | 29 ++++ .../backup/src/js/hooks/useCapabilities.js | 2 +- .../backup/src/js/hooks/useConnection.js | 125 +----------------- 14 files changed, 162 insertions(+), 131 deletions(-) create mode 100644 projects/packages/backup/changelog/fix-backup-duplicated-calls rename projects/packages/backup/src/js/{hooks => components/backup-connection-screen}/assets/ben-giordano-testimonial.png (100%) rename projects/packages/backup/src/js/{hooks => components/backup-connection-screen}/assets/connect-backup.png (100%) rename projects/packages/backup/src/js/{hooks => components/backup-connection-screen}/assets/tim-ferriss-testimonial.png (100%) create mode 100644 projects/packages/backup/src/js/components/backup-connection-screen/index.js create mode 100644 projects/packages/backup/src/js/components/backup-connection-screen/secondary-admin.js create mode 100644 projects/packages/backup/src/js/hooks/use-backup-product-info.js diff --git a/projects/packages/backup/changelog/fix-backup-duplicated-calls b/projects/packages/backup/changelog/fix-backup-duplicated-calls new file mode 100644 index 0000000000000..f91d8d3b65cac --- /dev/null +++ b/projects/packages/backup/changelog/fix-backup-duplicated-calls @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Decoupled backup connection screens from useConnection hook to avoid unnecessary loading and prevent duplicated API calls. diff --git a/projects/packages/backup/src/js/components/Admin/header.js b/projects/packages/backup/src/js/components/Admin/header.js index 0397808ca8208..9a4db84b3b1cd 100644 --- a/projects/packages/backup/src/js/components/Admin/header.js +++ b/projects/packages/backup/src/js/components/Admin/header.js @@ -30,7 +30,7 @@ const Header = () => { }; const useShowActivateLicenseLink = () => { - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); const isFullyConnected = useIsFullyConnected(); const { capabilitiesLoaded, hasBackupPlan } = useCapabilities(); diff --git a/projects/packages/backup/src/js/components/Admin/hooks.js b/projects/packages/backup/src/js/components/Admin/hooks.js index 74c4a97af7d5d..89515253952f8 100644 --- a/projects/packages/backup/src/js/components/Admin/hooks.js +++ b/projects/packages/backup/src/js/components/Admin/hooks.js @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from '@wordpress/element'; import useConnection from '../../hooks/useConnection'; export const useIsFullyConnected = () => { - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); return useMemo( () => { const connectionLoaded = 0 < Object.keys( connectionStatus ).length; @@ -13,7 +13,7 @@ export const useIsFullyConnected = () => { export const useIsSecondaryAdminNotConnected = () => { const isFullyConnected = useIsFullyConnected(); - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); return useMemo( () => { return isFullyConnected && ! connectionStatus.isUserConnected; diff --git a/projects/packages/backup/src/js/components/Admin/index.js b/projects/packages/backup/src/js/components/Admin/index.js index 4a1e694cb34e8..c05e00ab609f6 100644 --- a/projects/packages/backup/src/js/components/Admin/index.js +++ b/projects/packages/backup/src/js/components/Admin/index.js @@ -17,6 +17,8 @@ import useBackupsState from '../../hooks/useBackupsState'; import useCapabilities from '../../hooks/useCapabilities'; import useConnection from '../../hooks/useConnection'; import { Backups, Loading as BackupsLoadingPlaceholder } from '../Backups'; +import { BackupConnectionScreen } from '../backup-connection-screen'; +import { BackupSecondaryAdminConnectionScreen } from '../backup-connection-screen/secondary-admin'; import BackupStorageSpace from '../backup-storage-space'; import ReviewRequest from '../review-request'; import Header from './header'; @@ -31,7 +33,7 @@ import '../masthead/masthead-style.scss'; /* eslint react/react-in-jsx-scope: 0 */ const Admin = () => { - const [ connectionStatus, , BackupSecondaryAdminConnectionScreen ] = useConnection(); + const connectionStatus = useConnection(); const { tracks } = useAnalytics(); const { hasConnectionError } = useConnectionErrorNotice(); const connectionLoaded = 0 < Object.keys( connectionStatus ).length; @@ -119,7 +121,7 @@ const Admin = () => { // Render additional segments for the backup admin page under the jp-hero section. // If the user has a backup plan and is connected, we render the storage space segment. const BackupSegments = ( { hasBackupPlan, connectionLoaded } ) => { - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); const { tracks } = useAnalytics(); const trackLearnMoreClick = useCallback( () => { @@ -337,8 +339,6 @@ const LoadedState = ( { capabilities, isFullyConnected, } ) => { - const [ , BackupConnectionScreen ] = useConnection(); - if ( ! isFullyConnected ) { return ( diff --git a/projects/packages/backup/src/js/components/back-up-now/hooks.js b/projects/packages/backup/src/js/components/back-up-now/hooks.js index a5cdb768f2f0d..7d44c382197ae 100644 --- a/projects/packages/backup/src/js/components/back-up-now/hooks.js +++ b/projects/packages/backup/src/js/components/back-up-now/hooks.js @@ -4,7 +4,7 @@ import useConnection from '../../hooks/useConnection'; import { useIsFullyConnected } from '../Admin/hooks'; export const useShowBackUpNow = () => { - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); const isFullyConnected = useIsFullyConnected(); const { capabilitiesLoaded, hasBackupPlan } = useCapabilities(); diff --git a/projects/packages/backup/src/js/hooks/assets/ben-giordano-testimonial.png b/projects/packages/backup/src/js/components/backup-connection-screen/assets/ben-giordano-testimonial.png similarity index 100% rename from projects/packages/backup/src/js/hooks/assets/ben-giordano-testimonial.png rename to projects/packages/backup/src/js/components/backup-connection-screen/assets/ben-giordano-testimonial.png diff --git a/projects/packages/backup/src/js/hooks/assets/connect-backup.png b/projects/packages/backup/src/js/components/backup-connection-screen/assets/connect-backup.png similarity index 100% rename from projects/packages/backup/src/js/hooks/assets/connect-backup.png rename to projects/packages/backup/src/js/components/backup-connection-screen/assets/connect-backup.png diff --git a/projects/packages/backup/src/js/hooks/assets/tim-ferriss-testimonial.png b/projects/packages/backup/src/js/components/backup-connection-screen/assets/tim-ferriss-testimonial.png similarity index 100% rename from projects/packages/backup/src/js/hooks/assets/tim-ferriss-testimonial.png rename to projects/packages/backup/src/js/components/backup-connection-screen/assets/tim-ferriss-testimonial.png diff --git a/projects/packages/backup/src/js/components/backup-connection-screen/index.js b/projects/packages/backup/src/js/components/backup-connection-screen/index.js new file mode 100644 index 0000000000000..a8c7ab6babcc3 --- /dev/null +++ b/projects/packages/backup/src/js/components/backup-connection-screen/index.js @@ -0,0 +1,83 @@ +import { JetpackVaultPressBackupLogo, Testimonials } from '@automattic/jetpack-components'; +import { ConnectScreenRequiredPlan } from '@automattic/jetpack-connection'; +import apiFetch from '@wordpress/api-fetch'; +import { useSelect } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import React from 'react'; +import { useBackupProductInfo } from '../../hooks/use-backup-product-info'; +import { STORE_ID } from '../../store'; +import BackupPromotionBlock from '../backup-promotion'; +import { BackupVideoSection } from '../backup-video-section'; +import { WhyINeedVPBackup } from '../why-i-need-vp-backup'; +import benGiordanoTestimonial from './assets/ben-giordano-testimonial.png'; +import timFerrissTestimonial from './assets/tim-ferriss-testimonial.png'; + +const testimonials = [ + { + quote: __( + 'Millions of people depend on my site, and downtime isn’t an option. Jetpack VaultPress Backup handles my site security and backups so I can focus on creation.', + 'jetpack-backup-pkg' + ), + author: 'Tim Ferriss', + profession: __( 'Author / Investor / Podcaster', 'jetpack-backup-pkg' ), + img: timFerrissTestimonial, + }, + { + quote: __( + 'Our developers use VaultPress Backup all the time. It’s a one‑click way to return to where we were before things got wonky. It gives us a little emergency parachute so if we’re working on a customization that breaks everything, we lose minutes, not hours.', + 'jetpack-backup-pkg' + ), + author: 'Ben Giordano', + profession: __( 'Founder, FreshySites.com', 'jetpack-backup-pkg' ), + + img: benGiordanoTestimonial, + }, +]; + +export const BackupConnectionScreen = () => { + const APINonce = useSelect( select => select( STORE_ID ).getAPINonce(), [] ); + const APIRoot = useSelect( select => select( STORE_ID ).getAPIRoot(), [] ); + const registrationNonce = useSelect( select => select( STORE_ID ).getRegistrationNonce(), [] ); + const { price, priceAfter } = useBackupProductInfo(); + + const checkSiteHasBackupProduct = useCallback( + () => apiFetch( { path: '/jetpack/v4/has-backup-plan' } ), + [] + ); + + return ( + <> + } + pricingTitle={ __( 'VaultPress Backup', 'jetpack-backup-pkg' ) } + title={ __( 'The best real-time WordPress backups', 'jetpack-backup-pkg' ) } + apiRoot={ APIRoot } + apiNonce={ APINonce } + registrationNonce={ registrationNonce } + from="jetpack-backup" + redirectUri="admin.php?page=jetpack-backup" + wpcomProductSlug="jetpack_backup_t1_yearly" + siteProductAvailabilityHandler={ checkSiteHasBackupProduct } + logo={ <> } + rna + > + + + + + + + + + + ); +}; diff --git a/projects/packages/backup/src/js/components/backup-connection-screen/secondary-admin.js b/projects/packages/backup/src/js/components/backup-connection-screen/secondary-admin.js new file mode 100644 index 0000000000000..a02b5c6be42d9 --- /dev/null +++ b/projects/packages/backup/src/js/components/backup-connection-screen/secondary-admin.js @@ -0,0 +1,32 @@ +import { JetpackVaultPressBackupLogo } from '@automattic/jetpack-components'; +import { ConnectScreen } from '@automattic/jetpack-connection'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import React from 'react'; +import { STORE_ID } from '../../store'; +import connectImage from './assets/connect-backup.png'; + +export const BackupSecondaryAdminConnectionScreen = () => { + const APINonce = useSelect( select => select( STORE_ID ).getAPINonce(), [] ); + const APIRoot = useSelect( select => select( STORE_ID ).getAPIRoot(), [] ); + const registrationNonce = useSelect( select => select( STORE_ID ).getRegistrationNonce(), [] ); + + return ( + } + > +

+ It looks like your site already has a backup plan activated. All you need to do is log in + with your WordPress account. +

+
+ ); +}; diff --git a/projects/packages/backup/src/js/components/backup-storage-space/index.jsx b/projects/packages/backup/src/js/components/backup-storage-space/index.jsx index e7836332b1a07..c7f073e7af56d 100644 --- a/projects/packages/backup/src/js/components/backup-storage-space/index.jsx +++ b/projects/packages/backup/src/js/components/backup-storage-space/index.jsx @@ -9,7 +9,7 @@ import StorageUsageDetails from './storage-usage-details'; import { getUsageLevel, StorageUsageLevels } from './storage-usage-levels'; const BackupStorageSpace = () => { - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); const isFetchingPolicies = useSelect( select => select( STORE_ID ).isFetchingBackupPolicies() ); const isFetchingSize = useSelect( select => select( STORE_ID ).isFetchingBackupSize() ); const hasBackupSizeLoaded = useSelect( select => select( STORE_ID ).hasBackupSizeLoaded() ); diff --git a/projects/packages/backup/src/js/hooks/use-backup-product-info.js b/projects/packages/backup/src/js/hooks/use-backup-product-info.js new file mode 100644 index 0000000000000..2aebae719d793 --- /dev/null +++ b/projects/packages/backup/src/js/hooks/use-backup-product-info.js @@ -0,0 +1,29 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect, useCallback } from '@wordpress/element'; + +/** + * Custom hook to fetch and manage backup product pricing information. + * + * @return {object} An object containing the current product price and the price after any introductory offer. + */ +export function useBackupProductInfo() { + const [ price, setPrice ] = useState( 0 ); + const [ priceAfter, setPriceAfter ] = useState( 0 ); + + const fetchBackupProductInfo = useCallback( () => { + return apiFetch( { path: '/jetpack/v4/backup-promoted-product-info' } ); + }, [] ); + + useEffect( () => { + fetchBackupProductInfo().then( res => { + setPrice( res.cost / 12 ); + if ( res.introductory_offer ) { + setPriceAfter( res.introductory_offer.cost_per_interval / 12 ); + } else { + setPriceAfter( res.cost / 12 ); + } + } ); + }, [ fetchBackupProductInfo ] ); + + return { price, priceAfter }; +} diff --git a/projects/packages/backup/src/js/hooks/useCapabilities.js b/projects/packages/backup/src/js/hooks/useCapabilities.js index f01c0b1360c1c..9e1c6b6302f4c 100644 --- a/projects/packages/backup/src/js/hooks/useCapabilities.js +++ b/projects/packages/backup/src/js/hooks/useCapabilities.js @@ -11,7 +11,7 @@ export default function useCapabilities() { const [ capabilities, setCapabilities ] = useState( null ); const [ capabilitiesError, setCapabilitiesError ] = useState( null ); const [ capabilitiesLoaded, setCapabilitiesLoaded ] = useState( false ); - const [ connectionStatus ] = useConnection(); + const connectionStatus = useConnection(); useEffect( () => { const connectionLoaded = 0 < Object.keys( connectionStatus ).length; diff --git a/projects/packages/backup/src/js/hooks/useConnection.js b/projects/packages/backup/src/js/hooks/useConnection.js index bfc75c1427430..05345c7c9b639 100644 --- a/projects/packages/backup/src/js/hooks/useConnection.js +++ b/projects/packages/backup/src/js/hooks/useConnection.js @@ -1,133 +1,16 @@ -import { JetpackVaultPressBackupLogo, Testimonials } from '@automattic/jetpack-components'; -import { - ConnectScreenRequiredPlan, - ConnectScreen, - CONNECTION_STORE_ID, -} from '@automattic/jetpack-connection'; -import apiFetch from '@wordpress/api-fetch'; +import { CONNECTION_STORE_ID } from '@automattic/jetpack-connection'; import { useSelect } from '@wordpress/data'; -import { useState, useEffect } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import BackupPromotionBlock from '../components/backup-promotion'; -import { BackupVideoSection } from '../components/backup-video-section'; -import { WhyINeedVPBackup } from '../components/why-i-need-vp-backup'; -import { STORE_ID } from '../store'; -import benGiordanoTestimonial from './assets/ben-giordano-testimonial.png'; -import connectImage from './assets/connect-backup.png'; -import timFerrissTestimonial from './assets/tim-ferriss-testimonial.png'; - -const testimonials = [ - { - quote: __( - 'Millions of people depend on my site, and downtime isn’t an option. Jetpack VaultPress Backup handles my site security and backups so I can focus on creation.', - 'jetpack-backup-pkg' - ), - author: 'Tim Ferriss', - profession: __( 'Author / Investor / Podcaster', 'jetpack-backup-pkg' ), - img: timFerrissTestimonial, - }, - { - quote: __( - 'Our developers use VaultPress Backup all the time. It’s a one‑click way to return to where we were before things got wonky. It gives us a little emergency parachute so if we’re working on a customization that breaks everything, we lose minutes, not hours.', - 'jetpack-backup-pkg' - ), - author: 'Ben Giordano', - profession: __( 'Founder, FreshySites.com', 'jetpack-backup-pkg' ), - - img: benGiordanoTestimonial, - }, -]; /** - * Expose the `connectionStatus` state object and `BackupConnectionScreen` to show a component used for connection. + * Expose the `connectionStatus` state object from the Jetpack connection store. * - * @return {Array} connectionStatus, BackupConnectionScreen + * @return {object} connectionStatus The connection status object. */ export default function useConnection() { - const APINonce = useSelect( select => select( STORE_ID ).getAPINonce(), [] ); - const APIRoot = useSelect( select => select( STORE_ID ).getAPIRoot(), [] ); - const registrationNonce = useSelect( select => select( STORE_ID ).getRegistrationNonce(), [] ); const connectionStatus = useSelect( select => select( CONNECTION_STORE_ID ).getConnectionStatus(), [] ); - const [ price, setPrice ] = useState( 0 ); - const [ priceAfter, setPriceAfter ] = useState( 0 ); - - const checkSiteHasBackupProduct = useCallback( - () => apiFetch( { path: '/jetpack/v4/has-backup-plan' } ), - [] - ); - - useEffect( () => { - apiFetch( { path: '/jetpack/v4/backup-promoted-product-info' } ).then( res => { - setPrice( res.cost / 12 ); - if ( res.introductory_offer ) { - setPriceAfter( res.introductory_offer.cost_per_interval / 12 ); - } else { - setPriceAfter( res.cost / 12 ); - } - } ); - }, [] ); - - const BackupConnectionScreen = () => { - return ( - <> - } - pricingTitle={ __( 'VaultPress Backup', 'jetpack-backup-pkg' ) } - title={ __( 'The best real-time WordPress backups', 'jetpack-backup-pkg' ) } - apiRoot={ APIRoot } - apiNonce={ APINonce } - registrationNonce={ registrationNonce } - from="jetpack-backup" - redirectUri="admin.php?page=jetpack-backup" - wpcomProductSlug="jetpack_backup_t1_yearly" - siteProductAvailabilityHandler={ checkSiteHasBackupProduct } - logo={ <> } - rna - > - - - - - - - - - - ); - }; - - const BackupSecondaryAdminConnectionScreen = () => { - return ( - } - > -

- It looks like your site already has a backup plan activated. All you need to do is log in - with your WordPress account. -

-
- ); - }; - return [ connectionStatus, BackupConnectionScreen, BackupSecondaryAdminConnectionScreen ]; + return connectionStatus; }