From 06d23d9717c6ae87d843f78a6a828cdfe0ca92d7 Mon Sep 17 00:00:00 2001 From: Renatho De Carli Rosa Date: Fri, 25 Oct 2024 11:34:28 -0300 Subject: [PATCH] Replace overview when site is with a pending migration (#95497) * Fix bottom spacing in the content Since the parent has an overflow hidden and a padding bottom it wasn't being respected. * Extract hero components * Move type to follow the same standard of the others * Add key to the loop * Extract grid component * Add functions to get migration status and migration type * Tweak styles for when it does not have a link * Add migration overview variation * Temporarily add check for pending When we implement the started status, we can also use the MigrationOverview, and remove this conditional. * Redirect users to the proper step * Redirect users to the proper step when the user didn't buy a plan yet * Add query string to trigger the calypso_signup_start event * Add tests for the links * Join imports * Extract canInstallPlugins logic * Improve check to match what is checked in the flows * Fix tests * Fix type to get from relative path * Fix DYI URL * Remove not used conditional * Update tests to also mock the canInstallPlugins Similar to the other mocks in these tests. * Remove missed import after rebase --- client/components/hosting-card/index.tsx | 25 ++-- client/components/hosting-card/style.scss | 35 +++++ client/components/hosting-hero/index.tsx | 16 +++ client/components/hosting-hero/style.scss | 27 ++++ .../components/hosting-features.tsx | 28 ++-- .../hosting-features/components/style.scss | 60 +------- .../overview/components/hosting-overview.tsx | 12 +- .../components/migration-overview.tsx | 130 ++++++++++++++++++ .../components/test/migration-overview.tsx | 64 +++++++++ .../sites/components/dotcom-style.scss | 1 + .../index.tsx | 9 +- client/sites-dashboard/test/utils.js | 60 +++++++- client/sites-dashboard/utils.ts | 32 +++++ packages/sites/src/can-install-plugins.ts | 7 + packages/sites/src/index.ts | 1 + 15 files changed, 420 insertions(+), 87 deletions(-) create mode 100644 client/components/hosting-hero/index.tsx create mode 100644 client/components/hosting-hero/style.scss create mode 100644 client/hosting/overview/components/migration-overview.tsx create mode 100644 client/hosting/overview/components/test/migration-overview.tsx create mode 100644 packages/sites/src/can-install-plugins.ts diff --git a/client/components/hosting-card/index.tsx b/client/components/hosting-card/index.tsx index b0e11743cfd712..4c2637c2a365d7 100644 --- a/client/components/hosting-card/index.tsx +++ b/client/components/hosting-card/index.tsx @@ -8,6 +8,7 @@ interface HostingCardProps { className?: string; headingId?: string; title?: string; + inGrid?: boolean; children: ReactNode; } @@ -22,9 +23,19 @@ interface HostingCardDescriptionProps { children: string | ReactNode; } -export function HostingCard( { className, headingId, title, children }: HostingCardProps ) { +interface HostingCardLinkButtonProps { + to: string; + children: string | ReactNode; + hideOnMobile?: boolean; +} + +interface HostingCardGridProps { + children: ReactNode; +} + +export function HostingCard( { className, headingId, title, inGrid, children }: HostingCardProps ) { return ( - + { title && (

{ title } @@ -52,12 +63,6 @@ export function HostingCardDescription( { children }: HostingCardDescriptionProp return

{ children }

; } -interface HostingCardLinkButtonProps { - to: string; - children: string | ReactNode; - hideOnMobile?: boolean; -} - export function HostingCardLinkButton( { to, children, @@ -75,3 +80,7 @@ export function HostingCardLinkButton( { ); } + +export function HostingCardGrid( props: HostingCardGridProps ) { + return
{ props.children }
; +} diff --git a/client/components/hosting-card/style.scss b/client/components/hosting-card/style.scss index 9d112a08417205..38ef08264683d3 100644 --- a/client/components/hosting-card/style.scss +++ b/client/components/hosting-card/style.scss @@ -1,5 +1,6 @@ @import "@automattic/components/src/styles/typography"; @import "@wordpress/base-styles/breakpoints"; +@import "@wordpress/base-styles/mixins"; .hosting-card { width: 100%; @@ -22,6 +23,27 @@ .form-select { font-size: rem(14px) !important; } + + /* Styles for when it's inside a grid */ + &--in-grid { + > p { + font-size: rem(14px); + margin-bottom: 0; + color: var(--color-text-subtle); + flex-grow: 1; + } + + > a { + display: inline-block; + font-size: rem(13px); + font-weight: 400; + line-height: 16px; + } + + > p + a { + margin-top: 20px; + } + } } h3.hosting-card__title { @@ -58,3 +80,16 @@ a.hosting-card__link-button { display: none; } } + +.hosting-card-grid { + display: grid; + grid-template-columns: 1fr; + grid-column-gap: 16px; + grid-row-gap: 16px; + padding: 0 20px; + + @include break-medium { + grid-template-columns: 1fr 1fr 1fr; + padding: 0; + } +} diff --git a/client/components/hosting-hero/index.tsx b/client/components/hosting-hero/index.tsx new file mode 100644 index 00000000000000..882ba278cf1e54 --- /dev/null +++ b/client/components/hosting-hero/index.tsx @@ -0,0 +1,16 @@ +import { Button } from '@wordpress/components'; +import type { ReactNode, ComponentProps } from 'react'; + +import './style.scss'; + +interface HostingHeroProps { + children: ReactNode; +} + +export function HostingHero( { children }: HostingHeroProps ) { + return
{ children }
; +} + +export function HostingHeroButton( props: ComponentProps< typeof Button > ) { + return + { title = unlockTitle; description = unlockDescription; buttons = ( - + ); } return (
-
+ { isTransferInProgress && }

{ title }

{ description }

{ buttons } -
-
+ + { promoCards.map( ( card ) => ( ) ) } -
+
); }; diff --git a/client/hosting/hosting-features/components/style.scss b/client/hosting/hosting-features/components/style.scss index 8bd15209554f6c..a1a9f65aaad262 100644 --- a/client/hosting/hosting-features/components/style.scss +++ b/client/hosting/hosting-features/components/style.scss @@ -2,61 +2,9 @@ @import "@wordpress/base-styles/breakpoints"; @import "@wordpress/base-styles/mixins"; -.hosting-features__hero { - grid-column: 1 / -1; - text-align: center; - margin-bottom: 70px; - - > h1 { - margin-top: 30px; - font-style: normal; - font-weight: 500; - font-size: rem(36px); - font-family: Recoleta, sans-serif; - } - > p { - font-style: normal; - font-weight: 400; - font-size: rem(18px); - } - .hosting-features__button { - margin: 0 5px; - padding: 10px 14px; - font-size: rem(14px); - height: 40px; - } - - .hosting-features__content-spinner { - margin-top: 16px; - & + h1 { - margin-top: 0; - } - } -} - -.hosting-features__cards { - display: grid; - grid-template-columns: 1fr; - grid-column-gap: 16px; - grid-row-gap: 16px; - padding: 0 20px; - @include break-medium { - grid-template-columns: 1fr 1fr 1fr; - padding: 0; - } -} - -.hosting-features__card { - > p { - font-size: rem(14px); - margin-bottom: 20px; - color: var(--color-text-subtle); - flex-grow: 1; - } - - > a { - font-size: rem(13px); - font-weight: 400; - line-height: 16px; +.hosting-features__content-spinner { + margin-top: 16px; + & + h1 { + margin-top: 0; } } diff --git a/client/hosting/overview/components/hosting-overview.tsx b/client/hosting/overview/components/hosting-overview.tsx index b936b83b2b8ca9..53770439978496 100644 --- a/client/hosting/overview/components/hosting-overview.tsx +++ b/client/hosting/overview/components/hosting-overview.tsx @@ -5,15 +5,25 @@ import ActiveDomainsCard from 'calypso/hosting/overview/components/active-domain import PlanCard from 'calypso/hosting/overview/components/plan-card'; import QuickActionsCard from 'calypso/hosting/overview/components/quick-actions-card'; import SiteBackupCard from 'calypso/hosting/overview/components/site-backup-card'; -import { isNotAtomicJetpack } from 'calypso/sites-dashboard/utils'; +import { + isNotAtomicJetpack, + isMigrationInProgress, + getMigrationStatus, +} from 'calypso/sites-dashboard/utils'; import { useSelector } from 'calypso/state'; import { getSelectedSite } from 'calypso/state/ui/selectors'; +import MigrationOverview from './migration-overview'; import SupportCard from './support-card'; import './style.scss'; const HostingOverview: FC = () => { const site = useSelector( getSelectedSite ); + + if ( site && isMigrationInProgress( site ) && getMigrationStatus( site ) === 'pending' ) { + return ; + } + const isJetpackNotAtomic = site && isNotAtomicJetpack( site ); const subtitle = isJetpackNotAtomic ? translate( 'Get a quick glance at your plans and upgrades.' ) diff --git a/client/hosting/overview/components/migration-overview.tsx b/client/hosting/overview/components/migration-overview.tsx new file mode 100644 index 00000000000000..d459a08eee5252 --- /dev/null +++ b/client/hosting/overview/components/migration-overview.tsx @@ -0,0 +1,130 @@ +import { canInstallPlugins } from '@automattic/sites'; +import { translate } from 'i18n-calypso'; +import { HostingCard, HostingCardGrid } from 'calypso/components/hosting-card'; +import { HostingHero, HostingHeroButton } from 'calypso/components/hosting-hero'; +import { addQueryArgs } from 'calypso/lib/url'; +import { getMigrationType } from 'calypso/sites-dashboard/utils'; +import type { SiteDetails } from '@automattic/data-stores'; + +const cards = [ + { + title: translate( 'Seriously secure' ), + text: translate( + 'Firewalls, encryption, brute force, and DDoS protection. Your security’s all taken care of so you can stay one step ahead of any threats.' + ), + }, + { + title: translate( 'Unmetered bandwidth' ), + text: translate( + 'With 99.999% uptime and entirely unmetered bandwidth and traffic on every plan, you’ll never need to worry about being too successful.' + ), + }, + { + title: translate( 'Power, meet performance' ), + text: translate( + 'Our custom 28+ location CDN and 99.999% uptime ensure your site is always fast and always available from anywhere in the world.' + ), + }, + { + title: translate( 'Plugins, themes, and custom code' ), + text: translate( + 'Build anything with full support and automatic updates for 50,000+ plugins and themes. Or start from scratch with your own custom code.' + ), + }, + { + title: translate( 'Expert support' ), + text: translate( + 'Whenever you’re stuck, whatever you’re trying to make happen – our Happiness Engineers have the answers.' + ), + }, +]; + +const MigrationOverview = ( { site }: { site: SiteDetails } ) => { + const migrationType = getMigrationType( site ); + + const baseQueryArgs = { + siteId: site.ID, + siteSlug: site.slug, + start: 'true', + ref: 'hosting-migration-overview', + }; + + let continueMigrationUrl; + + if ( ! canInstallPlugins( site ) ) { + // For the flows where the checkout is after the choice. + switch ( migrationType ) { + case 'diy': + continueMigrationUrl = addQueryArgs( + { + ...baseQueryArgs, + destination: 'upgrade', + how: 'myself', + }, + '/setup/site-migration/site-migration-upgrade-plan' + ); + break; + case 'difm': + continueMigrationUrl = addQueryArgs( + { + ...baseQueryArgs, + destination: 'upgrade', + how: 'difm', + }, + '/setup/site-migration/site-migration-upgrade-plan' + ); + break; + default: + continueMigrationUrl = addQueryArgs( + baseQueryArgs, + '/setup/site-migration/site-migration-how-to-migrate' + ); + } + } else { + // For the /setup/migration, where the checkout is before the choice. + switch ( migrationType ) { + case 'diy': + continueMigrationUrl = addQueryArgs( + baseQueryArgs, + '/setup/migration/site-migration-instructions' + ); + break; + case 'difm': + continueMigrationUrl = addQueryArgs( + baseQueryArgs, + '/setup/migration/site-migration-credentials' + ); + break; + default: + continueMigrationUrl = addQueryArgs( + baseQueryArgs, + '/setup/migration/migration-how-to-migrate' + ); + } + } + + return ( +
+ +

{ translate( 'Your WordPress site is ready to be migrated' ) }

+

+ { translate( + 'Start your migration today and get ready for unmatched WordPress hosting.' + ) } +

+ + { translate( 'Start your migration' ) } + +
+ + { cards.map( ( { title, text } ) => ( + +

{ text }

+
+ ) ) } +
+
+ ); +}; + +export default MigrationOverview; diff --git a/client/hosting/overview/components/test/migration-overview.tsx b/client/hosting/overview/components/test/migration-overview.tsx new file mode 100644 index 00000000000000..327d47b1ea5d5d --- /dev/null +++ b/client/hosting/overview/components/test/migration-overview.tsx @@ -0,0 +1,64 @@ +/** + * @jest-environment jsdom + */ +import { canInstallPlugins } from '@automattic/sites'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { getMigrationStatus, getMigrationType } from 'calypso/sites-dashboard/utils'; +import MigrationOverview from '../migration-overview'; +import type { SiteDetails } from '@automattic/data-stores'; + +jest.mock( '@automattic/sites' ); +jest.mock( 'calypso/sites-dashboard/utils' ); + +const baseSite = { + ID: 123, + slug: 'example.com', +} as SiteDetails; + +describe( 'MigrationOverview', () => { + it.each( [ + [ + 'diy', + false, + '/setup/site-migration/site-migration-upgrade-plan?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview&destination=upgrade&how=myself', + ], + [ + 'difm', + false, + '/setup/site-migration/site-migration-upgrade-plan?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview&destination=upgrade&how=difm', + ], + [ + undefined, + false, + '/setup/site-migration/site-migration-how-to-migrate?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview', + ], + [ + 'diy', + true, + '/setup/migration/site-migration-instructions?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview', + ], + [ + 'difm', + true, + '/setup/migration/site-migration-credentials?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview', + ], + [ + undefined, + true, + '/setup/migration/migration-how-to-migrate?siteId=123&siteSlug=example.com&start=true&ref=hosting-migration-overview', + ], + ] )( + 'should set continueMigrationUrl correctly for migrationType: %s and isFreePlan: %s', + ( migrationType, siteCanInstallPlugins, expectedUrl ) => { + ( getMigrationType as jest.Mock ).mockReturnValue( migrationType ); + ( getMigrationStatus as jest.Mock ).mockReturnValue( 'pending' ); + ( canInstallPlugins as jest.Mock ).mockReturnValue( siteCanInstallPlugins ); + + const { getByRole } = render( ); + const button = getByRole( 'link', { name: 'Start your migration' } ); + + expect( button ).toHaveAttribute( 'href', expectedUrl ); + } + ); +} ); diff --git a/client/hosting/sites/components/dotcom-style.scss b/client/hosting/sites/components/dotcom-style.scss index ed0874860652f5..38f86f498e2f79 100644 --- a/client/hosting/sites/components/dotcom-style.scss +++ b/client/hosting/sites/components/dotcom-style.scss @@ -479,6 +479,7 @@ margin-top: 0; margin-left: 0; margin-right: 0; + margin-bottom: auto; padding-top: 0; padding-left: 0; padding-right: 0; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx index 9a49e709b9b46f..8efa094ce99ebf 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-import-or-migrate/index.tsx @@ -1,6 +1,7 @@ import { getPlan, PLAN_BUSINESS } from '@automattic/calypso-products'; import { BadgeType } from '@automattic/components'; import { StepContainer } from '@automattic/onboarding'; +import { canInstallPlugins } from '@automattic/sites'; import { getQueryArg } from '@wordpress/url'; import { useTranslate } from 'i18n-calypso'; import DocumentHead from 'calypso/components/data/document-head'; @@ -47,14 +48,10 @@ const SiteMigrationImportOrMigrate: Step = function ( { navigation } ) { const shouldDisplayHostIdentificationMessage = ! hostingProviderDetails.is_unknown && ! hostingProviderDetails.is_a8c; - const canInstallPlugins = site?.plan?.features?.active.find( - ( feature ) => feature === 'install-plugins' - ) - ? true - : false; + const siteCanInstallPlugins = canInstallPlugins( site ); const handleClick = ( destination: string ) => { - if ( destination === 'migrate' && ! canInstallPlugins ) { + if ( destination === 'migrate' && ! siteCanInstallPlugins ) { return navigation.submit?.( { destination: 'upgrade' } ); } diff --git a/client/sites-dashboard/test/utils.js b/client/sites-dashboard/test/utils.js index 594984c0b97692..a4777527de4111 100644 --- a/client/sites-dashboard/test/utils.js +++ b/client/sites-dashboard/test/utils.js @@ -1,4 +1,4 @@ -import { isMigrationInProgress } from 'calypso/sites-dashboard/utils'; +import { getMigrationStatus, getMigrationType, isMigrationInProgress } from '../utils'; // Adjust the import path as necessary describe( 'isMigrationInProgress', () => { it.each( [ @@ -16,3 +16,61 @@ describe( 'isMigrationInProgress', () => { expect( isMigrationInProgress( site ) ).toBe( true ); } ); } ); + +describe( 'getMigrationStatus', () => { + it.each( [ + { + site: { site_migration: { migration_status: 'migration-started-anything' } }, + expected: 'started', + }, + { + site: { site_migration: { migration_status: 'migration-pending-anything' } }, + expected: 'pending', + }, + { + site: { site_migration: { migration_status: 'migration-completed-anything' } }, + expected: 'completed', + }, + { + site: { site_migration: { migration_status: 'migration-unknown-anything' } }, + expected: undefined, + }, + { + site: { site_migration: {} }, + expected: undefined, + }, + { + site: {}, + expected: undefined, + }, + ] )( + 'returns $expected for migration status $site.site_migration.migration_status', + ( { site, expected } ) => { + expect( getMigrationStatus( site ) ).toBe( expected ); + } + ); +} ); + +describe( 'getMigrationType', () => { + it.each( [ + { site: { site_migration: { migration_status: 'migration-anything-diy' } }, expected: 'diy' }, + { site: { site_migration: { migration_status: 'migration-anything-difm' } }, expected: 'difm' }, + { + site: { site_migration: { migration_status: 'migration-anything-unknown' } }, + expected: undefined, + }, + { + site: { site_migration: {} }, + expected: undefined, + }, + { + site: {}, + expected: undefined, + }, + ] )( + 'returns $expected for migration status $site.site_migration.migration_status', + ( { site, expected } ) => { + expect( getMigrationType( site ) ).toBe( expected ); + } + ); +} ); diff --git a/client/sites-dashboard/utils.ts b/client/sites-dashboard/utils.ts index 2559b60768f186..cd8a72c506f921 100644 --- a/client/sites-dashboard/utils.ts +++ b/client/sites-dashboard/utils.ts @@ -80,6 +80,38 @@ export const isMigrationInProgress = ( site: SiteExcerptData ): boolean => { return ! migrationStatus.startsWith( 'migration-completed' ); }; +export const getMigrationStatus = ( + site: SiteExcerptData +): 'pending' | 'started' | 'completed' | undefined => { + const migrationStatus = site?.site_migration?.migration_status; + if ( ! migrationStatus ) { + return undefined; + } + + const status = migrationStatus.split( '-' )[ 1 ]; + + if ( ! [ 'pending', 'started', 'completed' ].includes( status ) ) { + return undefined; + } + + return status as 'pending' | 'started' | 'completed'; +}; + +export const getMigrationType = ( site: SiteExcerptData ): 'diy' | 'difm' | undefined => { + const migrationStatus = site?.site_migration?.migration_status; + if ( ! migrationStatus ) { + return undefined; + } + + const type = migrationStatus.split( '-' )[ 2 ]; + + if ( ! [ 'difm', 'diy' ].includes( type ) ) { + return undefined; + } + + return type as 'diy' | 'difm'; +}; + export const isHostingTrialSite = ( site: SiteExcerptNetworkData ) => { return site?.plan?.product_slug === PLAN_HOSTING_TRIAL_MONTHLY; }; diff --git a/packages/sites/src/can-install-plugins.ts b/packages/sites/src/can-install-plugins.ts new file mode 100644 index 00000000000000..2fa03481eae83a --- /dev/null +++ b/packages/sites/src/can-install-plugins.ts @@ -0,0 +1,7 @@ +import type { SiteExcerptData } from './site-excerpt-types'; + +export function canInstallPlugins( site: SiteExcerptData | null ): boolean { + return site?.plan?.features?.active.find( ( feature ) => feature === 'install-plugins' ) + ? true + : false; +} diff --git a/packages/sites/src/index.ts b/packages/sites/src/index.ts index 15e9117bbc7559..bee12602d2f87e 100644 --- a/packages/sites/src/index.ts +++ b/packages/sites/src/index.ts @@ -19,3 +19,4 @@ export { } from './site-excerpt-constants'; export type { SiteExcerptData, SiteExcerptNetworkData } from './site-excerpt-types'; export { getSiteLaunchStatus, useSiteLaunchStatusLabel } from './site-status'; +export { canInstallPlugins } from './can-install-plugins';