diff --git a/client/my-sites/importer/importing-pane.jsx b/client/my-sites/importer/importing-pane.jsx index a9354423b61b7..ef4e85148b423 100644 --- a/client/my-sites/importer/importing-pane.jsx +++ b/client/my-sites/importer/importing-pane.jsx @@ -32,7 +32,7 @@ const sum = ( a, b ) => a + b; * … * } */ -const calculateProgress = ( progress ) => { +export const calculateProgress = ( progress ) => { const { attachment = {} } = progress; if ( attachment.total > 0 && attachment.completed >= 0 ) { diff --git a/client/signup/steps/import-from/index.tsx b/client/signup/steps/import-from/index.tsx index 0ad941154ecba..c897574be7327 100644 --- a/client/signup/steps/import-from/index.tsx +++ b/client/signup/steps/import-from/index.tsx @@ -1,6 +1,21 @@ import { isEnabled } from '@automattic/calypso-config'; -import React from 'react'; +import { Title } from '@automattic/onboarding'; +import page from 'page'; +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { LoadingEllipsis } from 'calypso/components/loading-ellipsis'; +import { Interval, EVERY_FIVE_SECONDS } from 'calypso/lib/interval'; +import { decodeURIComponentIfValid } from 'calypso/lib/url'; import StepWrapper from 'calypso/signup/step-wrapper'; +import { fetchImporterState } from 'calypso/state/imports/actions'; +import { + getImporterStatusForSiteId, + isImporterStatusHydrated, +} from 'calypso/state/imports/selectors'; +import { canCurrentUser } from 'calypso/state/selectors/can-current-user'; +import { getSiteId } from 'calypso/state/sites/selectors'; +import { Importer, QueryObject, ImportJob } from './types'; +import { getImporterTypeForEngine } from './util'; import WixImporter from './wix'; import './style.scss'; @@ -8,31 +23,136 @@ import './style.scss'; /* eslint-disable wpcalypso/jsx-classname-namespace */ interface Props { + path: string; stepName: string; stepSectionName: string; - queryObject: { - temp: string; - }; + queryObject: QueryObject; + siteId: number; + siteSlug: string; + fromSite: string; + canImport: boolean; + isImporterStatusHydrated: boolean; + siteImports: ImportJob[]; + fetchImporterState: ( siteId: number ) => void; } - const ImportOnboardingFrom: React.FunctionComponent< Props > = ( props ) => { - const { stepSectionName, queryObject } = props; + const { + stepSectionName, + siteId, + canImport, + siteSlug, + siteImports, + isImporterStatusHydrated, + fromSite, + } = props; + + /** + ↓ Fields + */ + const engine: Importer = stepSectionName.toLowerCase() as Importer; + const [ runImportInitially, setRunImportInitially ] = useState( false ); + const getImportJob = ( engine: Importer ): ImportJob | undefined => { + return siteImports.find( ( x ) => x.type === getImporterTypeForEngine( engine ) ); + }; + + /** + ↓ Effects + */ + useEffect( fetchImporters, [ siteId ] ); + useEffect( checkInitialRunState, [ siteId ] ); + + /** + ↓ Methods + */ + function fetchImporters() { + siteId && props.fetchImporterState( siteId ); + } + + function isLoading() { + return ! isImporterStatusHydrated; + } + + function hasPermission(): boolean { + return canImport; + } + + function checkInitialRunState() { + const searchParams = new URLSearchParams( window.location.search ); + + // run query param indicates that the import process can be run immediately, + // but before proceeding, remove it from the URL path + // because of the browser's back edge case + if ( searchParams.get( 'run' ) === 'true' ) { + setRunImportInitially( true ); + page.replace( props.path.replace( '&run=true', '' ).replace( 'run=true', '' ) ); + } + } + return ( - - { stepSectionName === 'wix' && isEnabled( 'gutenboarding/import-from-wix' ) && ( - - ) } - - } - /> + <> + + + +
+ { ( () => { + /** + * Loading screen + */ + if ( isLoading() ) { + return ; + } else if ( ! hasPermission() ) { + /** + * Permission screen + */ + return You are not authorized to view this page; + } else if ( engine === 'wix' && isEnabled( 'gutenboarding/import-from-wix' ) ) { + /** + * Wix importer + */ + return ( + + ); + } + } )() } +
+ + } + /> + ); }; -export default ImportOnboardingFrom; +export default connect( + ( state ) => { + const searchParams = new URLSearchParams( window.location.search ); + + const siteSlug = decodeURIComponentIfValid( searchParams.get( 'to' ) ); + const fromSite = decodeURIComponentIfValid( searchParams.get( 'from' ) ); + const siteId = getSiteId( state, siteSlug ) as number; + + return { + siteId, + siteSlug, + fromSite, + siteImports: getImporterStatusForSiteId( state, siteId ), + isImporterStatusHydrated: isImporterStatusHydrated( state ), + canImport: canCurrentUser( state, siteId, 'manage_options' ), + }; + }, + { + fetchImporterState, + } +)( ImportOnboardingFrom ); diff --git a/client/signup/steps/import-from/style.scss b/client/signup/steps/import-from/style.scss index 7ce40dd4ad40e..b27e4d5dfda50 100644 --- a/client/signup/steps/import-from/style.scss +++ b/client/signup/steps/import-from/style.scss @@ -9,4 +9,8 @@ min-width: 465px; } } + + .wpcom__loading-ellipsis { + margin: auto; + } } diff --git a/client/signup/steps/import-from/types.ts b/client/signup/steps/import-from/types.ts new file mode 100644 index 0000000000000..608b9f2760d58 --- /dev/null +++ b/client/signup/steps/import-from/types.ts @@ -0,0 +1,33 @@ +export type Importer = 'wix' | 'medium'; +export type QueryObject = { + from: string; + to: string; +}; + +export interface ImportJob { + importerId: string; + importerState: string; + type: string; + site: { ID: number }; + customData: { [ key: string ]: any }; + errorData: { + type: string; + description: string; + }; + progress: { + page: { completed: number; total: number }; + post: { completed: number; total: number }; + comment: { completed: number; total: number }; + attachment: { completed: number; total: number }; + }; +} + +export interface ImportJobParams { + engine: Importer; + importerStatus: ImportJob; + params: { engine: Importer }; + site: { ID: number }; + targetSiteUrl: string; + supportedContent: string[]; + unsupportedContent: string[]; +} diff --git a/client/signup/steps/import-from/util.ts b/client/signup/steps/import-from/util.ts new file mode 100644 index 0000000000000..a23ea5fd64edf --- /dev/null +++ b/client/signup/steps/import-from/util.ts @@ -0,0 +1,3 @@ +import { Importer } from './types'; + +export const getImporterTypeForEngine = ( engine: Importer ) => `importer-type-${ engine }`; diff --git a/client/signup/steps/import-from/wix/done-button.tsx b/client/signup/steps/import-from/wix/done-button.tsx new file mode 100644 index 0000000000000..7583abf791ddb --- /dev/null +++ b/client/signup/steps/import-from/wix/done-button.tsx @@ -0,0 +1,30 @@ +import { NextButton } from '@automattic/onboarding'; +import { useI18n } from '@wordpress/react-i18n'; +import page from 'page'; +import React from 'react'; +import { ImportJob } from '../types'; + +interface Props { + job: ImportJob; + siteId: number; + siteSlug: string; + resetImport: ( siteId: number, importerId: string ) => void; +} +const DoneButton: React.FunctionComponent< Props > = ( props ) => { + const { __ } = useI18n(); + const { job, siteId, siteSlug, resetImport } = props; + + function onButtonClick() { + redirectToSiteView(); + resetImport( siteId, job.importerId ); + } + + function redirectToSiteView() { + const destination = '/view/' + ( siteSlug || '' ); + page( destination ); + } + + return { __( 'View site' ) }; +}; + +export default DoneButton; diff --git a/client/signup/steps/import-from/wix/index.tsx b/client/signup/steps/import-from/wix/index.tsx index 1045ca30ebbe8..3661706f4bde1 100644 --- a/client/signup/steps/import-from/wix/index.tsx +++ b/client/signup/steps/import-from/wix/index.tsx @@ -1,41 +1,137 @@ import { ProgressBar } from '@automattic/components'; -import { Progress, Title, SubTitle, Hooray, NextButton } from '@automattic/onboarding'; +import { Progress, Title, SubTitle, Hooray } from '@automattic/onboarding'; import { useI18n } from '@wordpress/react-i18n'; import classnames from 'classnames'; -import React from 'react'; +import React, { useEffect } from 'react'; +import { connect } from 'react-redux'; +import { LoadingEllipsis } from 'calypso/components/loading-ellipsis'; +import { calculateProgress } from 'calypso/my-sites/importer/importing-pane'; +import { startImport, resetImport } from 'calypso/state/imports/actions'; +import { appStates } from 'calypso/state/imports/constants'; +import { importSite } from 'calypso/state/imports/site-importer/actions'; +import { Importer, ImportJob, ImportJobParams } from '../types'; +import { getImporterTypeForEngine } from '../util'; +import DoneButton from './done-button'; import './style.scss'; interface Props { - queryObject: { - temp: string; - }; + job?: ImportJob; + run: boolean; + siteId: number; + siteSlug: string; + fromSite: string; + importSite: ( params: ImportJobParams ) => void; + startImport: ( siteId: number, type: string ) => void; + resetImport: ( siteId: number, importerId: string ) => void; } -const WixImporter: React.FunctionComponent< Props > = ( props ) => { +export const WixImporter: React.FunctionComponent< Props > = ( props ) => { + const importer: Importer = 'wix'; const { __ } = useI18n(); - const { queryObject } = props; + const { job, run, siteId, siteSlug, fromSite, importSite, startImport, resetImport } = props; + + /** + ↓ Effects + */ + useEffect( runImport, [ job ] ); + + /** + ↓ Methods + */ + function runImport() { + if ( ! run ) return; + + // If there is no existing import job, start a new + if ( job === undefined ) { + startImport( siteId, getImporterTypeForEngine( importer ) ); + } else if ( job.importerState === appStates.READY_FOR_UPLOAD ) { + importSite( prepareImportParams() ); + } + } + + function prepareImportParams(): ImportJobParams { + const targetSiteUrl = fromSite.startsWith( 'http' ) ? fromSite : 'https://' + fromSite; + + return { + engine: importer, + importerStatus: job as ImportJob, + params: { engine: importer }, + site: { ID: siteId }, + targetSiteUrl, + supportedContent: [], + unsupportedContent: [], + }; + } + + function checkLoading() { + return ( + job?.importerState === appStates.READY_FOR_UPLOAD || + job?.importerState === appStates.UPLOAD_SUCCESS + ); + } + + function checkProgress() { + return job && job.importerState === appStates.IMPORTING; + } + + function checkIsSuccess() { + return job && job.importerState === appStates.IMPORT_SUCCESS; + } return ( -
- { queryObject.temp === 'progress' && ( - - { __( 'Importing' ) }... - - - { __( "This may take a few minutes. We'll notify you by email when it's done." ) } - - - ) } - - { queryObject.temp === 'hooray' && ( - - { __( 'Hooray!' ) } - { __( 'Congratulations. Your content was successfully imported.' ) } - { __( 'View site' ) } - - ) } -
+ <> +
+ { ( () => { + if ( checkLoading() ) { + /** + * Loading screen + */ + return ; + } else if ( checkProgress() ) { + /** + * Progress screen + */ + const progress = calculateProgress( job?.progress ); + return ( + + { __( 'Importing' ) }... + + + { __( "This may take a few minutes. We'll notify you by email when it's done." ) } + + + ); + } else if ( checkIsSuccess() ) { + /** + * Complete screen + */ + return ( + + { __( 'Hooray!' ) } + + { __( 'Congratulations. Your content was successfully imported.' ) } + + + + ); + } + } )() } +
+ ); }; -export default WixImporter; +export default connect( null, { + importSite, + startImport, + resetImport, +} )( WixImporter ); diff --git a/client/signup/steps/import-from/wix/style.scss b/client/signup/steps/import-from/wix/style.scss index e69de29bb2d1d..c0fe60e5fb282 100644 --- a/client/signup/steps/import-from/wix/style.scss +++ b/client/signup/steps/import-from/wix/style.scss @@ -0,0 +1,4 @@ +.importer-wix { + // ... +} + diff --git a/client/signup/steps/import/index.tsx b/client/signup/steps/import/index.tsx index 2a1073c9f37be..1e8bf050f1eac 100644 --- a/client/signup/steps/import/index.tsx +++ b/client/signup/steps/import/index.tsx @@ -1,3 +1,4 @@ +import { isEnabled } from '@automattic/calypso-config'; import { useI18n } from '@wordpress/react-i18n'; import page from 'page'; import React from 'react'; @@ -9,7 +10,7 @@ import CaptureStep from './capture'; import ListStep from './list'; import { ReadyPreviewStep, ReadyNotStep, ReadyStep, ReadyAlreadyOnWPCOMStep } from './ready'; import { GoToStep, GoToNextStep, UrlData } from './types'; -import { getImporterUrl } from './util'; +import { getImporterUrl, getWpComOnboardingUrl } from './util'; import './style.scss'; type Props = ConnectedProps< typeof connector > & { @@ -64,7 +65,12 @@ const ImportOnboarding: React.FunctionComponent< Props > = ( props ) => { }; const goToImporterPage = ( platform: string ): void => { - const importerUrl = getImporterUrl( signupDependencies.siteSlug, platform, urlData.url ); + let importerUrl; + if ( platform === 'wix' && isEnabled( 'gutenboarding/import-from-wix' ) ) { + importerUrl = getWpComOnboardingUrl( signupDependencies.siteSlug, platform, urlData.url ); + } else { + importerUrl = getImporterUrl( signupDependencies.siteSlug, platform, urlData.url ); + } importerUrl.includes( 'wp-admin' ) ? ( window.location.href = importerUrl ) diff --git a/client/signup/steps/import/util.ts b/client/signup/steps/import/util.ts index 4d9021711b8fa..8c6e2278fa60f 100644 --- a/client/signup/steps/import/util.ts +++ b/client/signup/steps/import/util.ts @@ -54,6 +54,17 @@ export function getWpComMigrateUrl( siteSlug: string, fromSite?: string ): strin .replace( '{fromSite}', fromSite || '' ); } +export function getWpComOnboardingUrl( + siteSlug: string, + platform: string, + fromSite?: string +): string { + return '/start/from/importing/{importer}?from={fromSite}&to={siteSlug}&run=true' + .replace( '{siteSlug}', siteSlug ) + .replace( '{importer}', getPlatformImporterName( platform ) ) + .replace( '{fromSite}', fromSite || '' ); +} + export function getWpComImporterUrl( siteSlug: string, platform: string,