Skip to content

Commit

Permalink
Stepper: load steps individually and separately for each flow (#70478)
Browse files Browse the repository at this point in the history
* Load steps individually

* Types

* Load the needed flow only

* Handle query param correctly

* Name chunks for easier debugging

* Clean mapping up

* Address feedback

* Fix site setup flow

* Increase videopress styles specificity

* Add the new Free flow

* Removed not useful new line

* Allow calypso live redirect from checkout

* Remove tentative of calypsolive

Co-authored-by: escapemanuele <escapemanuele@gmail.com>
  • Loading branch information
alshakero and escapemanuele authored Dec 8, 2022
1 parent a4f5228 commit 01e654f
Show file tree
Hide file tree
Showing 24 changed files with 464 additions and 398 deletions.
27 changes: 23 additions & 4 deletions client/landing/stepper/declarative-flow/anchor-fm-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,36 @@ import { SITE_STORE } from 'calypso/landing/stepper/stores';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { useSiteSlugParam } from '../hooks/use-site-slug-param';
import { recordSubmitStep } from './internals/analytics/record-submit-step';
import DesignSetup from './internals/steps-repository/design-setup';
import ErrorStep from './internals/steps-repository/error-step';
import { redirect } from './internals/steps-repository/import/util';
import type { StepPath } from './internals/steps-repository';
import LoginStep from './internals/steps-repository/login';
import PodcastTitleStep from './internals/steps-repository/podcast-title';
import ProcessingStep from './internals/steps-repository/processing-step';
import type { Flow, ProvidedDependencies } from './internals/types';

export const anchorFmFlow: Flow = {
export function isAnchorFmFlow() {
const sanitizePodcast = ( id: string ) => id.replace( /[^a-zA-Z0-9]/g, '' );
const anchorPodcast = new URLSearchParams( window.location.search ).get( 'anchor_podcast' );

return Boolean( sanitizePodcast( anchorPodcast || '' ) );
}

const anchorFmFlow: Flow = {
name: 'anchor-fm',

useSteps() {
useEffect( () => {
recordTracksEvent( 'calypso_signup_start', { flow: this.name } );
}, [] );

return [ 'login', 'podcastTitle', 'designSetup', 'processing', 'error' ] as StepPath[];
return [
{ slug: 'login', component: LoginStep },
{ slug: 'podcastTitle', component: PodcastTitleStep },
{ slug: 'designSetup', component: DesignSetup },
{ slug: 'processing', component: ProcessingStep },
{ slug: 'error', component: ErrorStep },
];
},

useStepNavigation( currentStep, navigate ) {
Expand Down Expand Up @@ -66,10 +83,12 @@ export const anchorFmFlow: Flow = {
}
};

const goToStep = ( step: StepPath | `${ StepPath }?${ string }` ) => {
const goToStep = ( step: string ) => {
navigate( step );
};

return { goNext, goBack, goToStep, submit };
},
};

export default anchorFmFlow;
33 changes: 21 additions & 12 deletions client/landing/stepper/declarative-flow/free.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ import {
import { useSiteSlug } from '../hooks/use-site-slug';
import { USER_STORE, ONBOARD_STORE } from '../stores';
import { recordSubmitStep } from './internals/analytics/record-submit-step';
import type { StepPath } from './internals/steps-repository';
import DomainsStep from './internals/steps-repository/domains';
import Intro from './internals/steps-repository/intro';
import LaunchPad from './internals/steps-repository/launchpad';
import LinkInBioSetup from './internals/steps-repository/link-in-bio-setup';
import PatternsStep from './internals/steps-repository/patterns';
import PlansStep from './internals/steps-repository/plans';
import Processing from './internals/steps-repository/processing-step';
import SiteCreationStep from './internals/steps-repository/site-creation-step';
import type { Flow, ProvidedDependencies } from './internals/types';

export const free: Flow = {
const free: Flow = {
name: FREE_FLOW,
title: 'Free',
useSteps() {
Expand All @@ -31,15 +38,15 @@ export const free: Flow = {
}, [] );

return [
'intro',
'freeSetup',
'domains',
'plans',
'patterns',
'siteCreationStep',
'processing',
'launchpad',
] as StepPath[];
{ slug: 'intro', component: Intro },
{ slug: 'freeSetup', component: LinkInBioSetup },
{ slug: 'domains', component: DomainsStep },
{ slug: 'plans', component: PlansStep },
{ slug: 'patterns', component: PatternsStep },
{ slug: 'siteCreationStep', component: SiteCreationStep },
{ slug: 'processing', component: Processing },
{ slug: 'launchpad', component: LaunchPad },
];
},

useStepNavigation( _currentStep, navigate ) {
Expand Down Expand Up @@ -134,10 +141,12 @@ export const free: Flow = {
}
};

const goToStep = ( step: StepPath | `${ StepPath }?${ string }` ) => {
const goToStep = ( step: string ) => {
navigate( step );
};

return { goNext, goBack, goToStep, submit };
},
};

export default free;
59 changes: 37 additions & 22 deletions client/landing/stepper/declarative-flow/import-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,42 @@ import { useQuery } from 'calypso/landing/stepper/hooks/use-query';
import { useSiteSlugParam } from 'calypso/landing/stepper/hooks/use-site-slug-param';
import { ONBOARD_STORE } from 'calypso/landing/stepper/stores';
import { useSiteSetupFlowProgress } from '../hooks/use-site-setup-flow-progress';
import { StepPath } from './internals/steps-repository';
import DesignSetup from './internals/steps-repository/design-setup';
import ImportStep from './internals/steps-repository/import';
import ImportList from './internals/steps-repository/import-list';
import ImportReady from './internals/steps-repository/import-ready';
import ImportReadyNot from './internals/steps-repository/import-ready-not';
import ImportReadyPreview from './internals/steps-repository/import-ready-preview';
import ImportReadyWpcom from './internals/steps-repository/import-ready-wpcom';
import ImporterBlogger from './internals/steps-repository/importer-blogger';
import ImporterMedium from './internals/steps-repository/importer-medium';
import ImporterSquarespace from './internals/steps-repository/importer-squarespace';
import ImporterWix from './internals/steps-repository/importer-wix';
import ImporterWordpress from './internals/steps-repository/importer-wordpress';
import PatternAssembler from './internals/steps-repository/pattern-assembler';
import ProcessingStep from './internals/steps-repository/processing-step';
import { Flow, ProvidedDependencies } from './internals/types';

export const importFlow: Flow = {
const importFlow: Flow = {
name: IMPORT_FOCUSED_FLOW,

useSteps() {
return [
'import',
'importList',
'importReady',
'importReadyNot',
'importReadyWpcom',
'importReadyPreview',
'importerWix',
'importerBlogger',
'importerMedium',
'importerSquarespace',
'importerWordpress',
'designSetup',
'patternAssembler',
'processing',
] as StepPath[];
{ slug: 'import', component: ImportStep },
{ slug: 'importList', component: ImportList },
{ slug: 'importReady', component: ImportReady },
{ slug: 'importReadyNot', component: ImportReadyNot },
{ slug: 'importReadyWpcom', component: ImportReadyWpcom },
{ slug: 'importReadyPreview', component: ImportReadyPreview },
{ slug: 'importerWix', component: ImporterWix },
{ slug: 'importerBlogger', component: ImporterBlogger },
{ slug: 'importerMedium', component: ImporterMedium },
{ slug: 'importerSquarespace', component: ImporterSquarespace },
{ slug: 'importerWordpress', component: ImporterWordpress },
{ slug: 'designSetup', component: DesignSetup },
{ slug: 'patternAssembler', component: PatternAssembler },
{ slug: 'processing', component: ProcessingStep },
];
},

useStepNavigation( _currentStep, navigate ) {
Expand Down Expand Up @@ -74,10 +87,10 @@ export const importFlow: Flow = {
return exitFlow( providedDependencies?.url as string );
}

return navigate( providedDependencies?.url as StepPath );
return navigate( providedDependencies?.url as string );
}
case 'importReadyPreview': {
return navigate( providedDependencies?.url as StepPath );
return navigate( providedDependencies?.url as string );
}

case 'importerWix':
Expand All @@ -89,7 +102,7 @@ export const importFlow: Flow = {
return exitFlow( providedDependencies?.url as string );
}

return navigate( providedDependencies?.url as StepPath );
return navigate( providedDependencies?.url as string );
}

case 'designSetup': {
Expand Down Expand Up @@ -129,7 +142,7 @@ export const importFlow: Flow = {
if ( backToStep ) {
const path = `${ backToStep }?siteSlug=${ siteSlugParam }`;

return navigate( path as StepPath );
return navigate( path );
}

return navigate( 'import' );
Expand Down Expand Up @@ -157,7 +170,7 @@ export const importFlow: Flow = {
}
};

const goToStep = ( step: StepPath | `${ StepPath }?${ string }` ) => {
const goToStep = ( step: string ) => {
switch ( step ) {
case 'goals':
return exitFlow( `/setup/site-setup/goals?siteSlug=${ siteSlugParam }` );
Expand All @@ -169,3 +182,5 @@ export const importFlow: Flow = {
return { goNext, goBack, goToStep, submit };
},
};

export default importFlow;
65 changes: 34 additions & 31 deletions client/landing/stepper/declarative-flow/internals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import { STEPPER_INTERNAL_STORE } from 'calypso/landing/stepper/stores';
import SignupHeader from 'calypso/signup/signup-header';
import { ONBOARD_STORE } from '../../stores';
import recordStepStart from './analytics/record-step-start';
import * as Steps from './steps-repository';
import VideoPressIntroBackground from './steps-repository/intro/videopress-intro-background';
import { AssertConditionState, Flow } from './types';
import type { StepPath } from './steps-repository';
import { AssertConditionState, Flow, StepperStep } from './types';
import './global.scss';

const kebabCase = ( value: string ) => value.replace( /([a-z0-9])([A-Z])/g, '$1-$2' ).toLowerCase();
Expand All @@ -33,9 +31,10 @@ const kebabCase = ( value: string ) => value.replace( /([a-z0-9])([A-Z])/g, '$1-
export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
// Configure app element that React Modal will aria-hide when modal is open
Modal.setAppElement( '#wpcom' );
const stepPaths = flow.useSteps();
const flowSteps = flow.useSteps();
const stepPaths = flowSteps.map( ( step ) => step.slug );
const location = useLocation();
const currentRoute = location.pathname.split( '/' )[ 2 ]?.replace( /\/+$/, '' ) as StepPath;
const currentStepRoute = location.pathname.split( '/' )[ 2 ]?.replace( /\/+$/, '' );
const history = useHistory();
const { search } = useLocation();
const { setStepData } = useDispatch( STEPPER_INTERNAL_STORE );
Expand All @@ -48,21 +47,24 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
);
const previousProgressValue = stepProgress ? previousProgress / stepProgress.count : 0;

const stepNavigation = flow.useStepNavigation( currentRoute, async ( path, extraData = null ) => {
// If any extra data is passed to the navigate() function, store it to the stepper-internal store.
setStepData( {
path: path,
intent: intent,
...extraData,
} );

const _path = path.includes( '?' ) // does path contain search params
? generatePath( `/${ flow.name }/${ path }` )
: generatePath( `/${ flow.name }/${ path }${ search }` );

history.push( _path, stepPaths );
setPreviousProgress( stepProgress?.progress ?? 0 );
} );
const stepNavigation = flow.useStepNavigation(
currentStepRoute,
async ( path, extraData = null ) => {
// If any extra data is passed to the navigate() function, store it to the stepper-internal store.
setStepData( {
path: path,
intent: intent,
...extraData,
} );

const _path = path.includes( '?' ) // does path contain search params
? generatePath( `/${ flow.name }/${ path }` )
: generatePath( `/${ flow.name }/${ path }${ search }` );

history.push( _path, stepPaths );
setPreviousProgress( stepProgress?.progress ?? 0 );
}
);
// Retrieve any extra step data from the stepper-internal store. This will be passed as a prop to the current step.
const stepData = useSelect( ( select ) => select( STEPPER_INTERNAL_STORE ).getStepData() );

Expand All @@ -74,18 +76,18 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {

useEffect( () => {
// We record the event only when the step is not empty. Additionally, we should not fire this event whenever the intent is changed
if ( currentRoute ) {
recordStepStart( flow.name, kebabCase( currentRoute ), { intent } );
if ( currentStepRoute ) {
recordStepStart( flow.name, kebabCase( currentStepRoute ), { intent } );
}

// We leave out intent from the dependency list, due to the ONBOARD_STORE being reset in the exit flow.
// This causes the intent to become empty, and thus this event being fired again.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ flow.name, currentRoute ] );
}, [ flow.name, currentStepRoute ] );

const assertCondition = flow.useAssertConditions?.() ?? { state: AssertConditionState.SUCCESS };

const renderStep = ( path: StepPath ) => {
const renderStep = ( step: StepperStep ) => {
switch ( assertCondition.state ) {
case AssertConditionState.CHECKING:
/* eslint-disable wpcalypso/jsx-classname-namespace */
Expand All @@ -95,8 +97,7 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
throw new Error( assertCondition.message ?? 'An error has occurred.' );
}

const StepComponent = Steps[ path ];
return <StepComponent navigation={ stepNavigation } flow={ flow.name } data={ stepData } />;
return <step.component navigation={ stepNavigation } flow={ flow.name } data={ stepData } />;
};

const getDocumentHeadTitle = () => {
Expand All @@ -117,11 +118,13 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
<>
<DocumentHead title={ getDocumentHeadTitle() } />
<Switch>
{ stepPaths.map( ( path ) => {
{ flowSteps.map( ( step ) => {
return (
<Route key={ path } path={ `/${ flow.name }/${ path }` }>
<div className={ classnames( flow.name, flow.classnames, kebabCase( path ) ) }>
{ 'videopress' === flow.name && 'intro' === path && <VideoPressIntroBackground /> }
<Route key={ step.slug } path={ `/${ flow.name }/${ step.slug }` }>
<div className={ classnames( flow.name, flow.classnames, kebabCase( step.slug ) ) }>
{ 'videopress' === flow.name && 'intro' === step.slug && (
<VideoPressIntroBackground />
) }
<ProgressBar
// eslint-disable-next-line wpcalypso/jsx-classname-namespace
className="flow-progress"
Expand All @@ -130,7 +133,7 @@ export const FlowRenderer: React.FC< { flow: Flow } > = ( { flow } ) => {
style={ progressBarExtraStyle }
/>
<SignupHeader pageTitle={ flow.title } />
{ renderStep( path ) }
{ renderStep( step ) }
</div>
</Route>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ $videopress-background: #010101;
margin-top: 128px;
}

.segmented-control {
background: transparent;
border: solid 1px rgba(255, 255, 255, 0.2);
.plans-interval-toggle {
.segmented-control {
background: transparent;
border: solid 1px rgba(255, 255, 255, 0.2);

.segmented-control__text {
color: #fff;
}
.segmented-control__text {
color: #fff;
}

.is-selected {
.segmented-control__link {
background-color: rgba(255, 255, 255, 0.35);
border: none;
.plans-interval-toggle__label {
color: #fff;
.is-selected {
.segmented-control__link {
background-color: rgba(255, 255, 255, 0.35);
border: none;
.plans-interval-toggle__label {
color: #fff;
}
}
}
}
Expand Down
Loading

0 comments on commit 01e654f

Please sign in to comment.