Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/app/components/Step/StepValidationWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect } from '@wordpress/element';
import { useNavigate, useLocation } from 'react-router-dom';
import { shouldRedirectToFirstStep } from '../../utils/helpers/stepValidation';

/**
* Higher-order component that validates step data and redirects if necessary
*
* @param {React.Component} WrappedComponent - The step component to wrap
* @param {string} stepKey - The key of the step for validation
* @return {React.Component} - The wrapped component with validation logic
*/
export const withStepValidation = ( WrappedComponent, stepKey ) => {
const StepValidationWrapper = ( props ) => {
const navigate = useNavigate();
const location = useLocation();

useEffect( () => {
// Check if we should redirect to the first step
if ( shouldRedirectToFirstStep( stepKey ) ) {
// Only redirect if we're not already on the first step
if ( location.pathname !== '/' ) {
navigate( '/', { replace: true } );
}
}
}, [ navigate, location.pathname ] );

// If we should redirect, don't render the component
if ( shouldRedirectToFirstStep( stepKey ) ) {
return null;
}

// Otherwise, render the wrapped component
return <WrappedComponent { ...props } />;
};

// Set display name for debugging
StepValidationWrapper.displayName = `withStepValidation(${ WrappedComponent.displayName || WrappedComponent.name || 'Component' })`;

return StepValidationWrapper;
};
11 changes: 6 additions & 5 deletions src/app/steps/Migration/MigrationStep.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { Container, Title, Spinner } from '@newfold/ui-component-library';
import { ExclamationCircleIcon } from '@heroicons/react/24/solid';
Expand All @@ -25,6 +25,7 @@ const MigrationStep = () => {

/**
* Track migration initiated event
*
* @param { string } instaWpMigrationUrl The migration url
* @return { Promise<void> }
*/
Expand All @@ -46,10 +47,6 @@ const MigrationStep = () => {
);
};

useEffect( () => {
prepareMigration();
}, [ prepareMigration ] );

const prepareMigration = useCallback( async () => {
try {
if ( ! canMigrateSite ) {
Expand Down Expand Up @@ -89,6 +86,10 @@ const MigrationStep = () => {
}
}, [ canMigrateSite, setInstaWpMigrationUrl ] );

useEffect( () => {
prepareMigration();
}, [ prepareMigration ] );

/**
* @return { string } The title content
*/
Expand Down
15 changes: 8 additions & 7 deletions src/app/steps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,50 @@ import { GeneratingStep } from './Generating';
import { PreviewsStep } from './Previews';
import { CanvasStep } from './Canvas';
import { MigrationStep } from './Migration';
import { withStepValidation } from '../components/Step/StepValidationWrapper';

const STEPS = {
fork: {
path: '/',
order: 10,
isRequired: true,
Component: ForkStep,
Component: withStepValidation( ForkStep, 'fork' ),
},
intake: {
path: '/intake',
order: 20,
isRequired: true,
Component: IntakeStep,
Component: withStepValidation( IntakeStep, 'intake' ),
},
logo: {
path: '/logo',
order: 30,
isRequired: true,
Component: LogoStep,
Component: withStepValidation( LogoStep, 'logo' ),
},
generating: {
path: '/generating',
order: 50,
isRequired: true,
Component: GeneratingStep,
Component: withStepValidation( GeneratingStep, 'generating' ),
},
previews: {
path: '/previews',
order: 60,
isRequired: true,
Component: PreviewsStep,
Component: withStepValidation( PreviewsStep, 'previews' ),
},
design: {
path: '/canvas',
order: 70,
isRequired: true,
Component: CanvasStep,
Component: withStepValidation( CanvasStep, 'design' ),
},
migration: {
path: '/migration',
order: 80,
isRequired: false,
Component: MigrationStep,
Component: withStepValidation( MigrationStep, 'migration' ),
},
};

Expand Down
67 changes: 67 additions & 0 deletions src/app/utils/helpers/stepValidation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { select } from '@wordpress/data';
import { nfdOnboardingStore } from '../../data/store';

/**
* Check if the required data is available for a specific step
*
* @param {string} stepKey - The key of the step to validate
* @return {boolean} - True if the step has required data, false otherwise
*/
export const hasRequiredDataForStep = ( stepKey ) => {
switch ( stepKey ) {
case 'fork':
// Fork step doesn't require any specific data - it's the entry point
return true;

case 'intake':
// Intake step doesn't require any previous data - it's where data collection starts
return true;

case 'logo': {
// Logo step requires site title and prompt from intake
const inputSlice = select( nfdOnboardingStore ).getInputSlice();
return !! ( inputSlice.siteTitle && inputSlice.prompt );
}

case 'generating': {
// Generating step requires site title and prompt
const inputSlice = select( nfdOnboardingStore ).getInputSlice();
return !! ( inputSlice.siteTitle && inputSlice.prompt );
}

case 'previews': {
// Previews step requires homepages to be generated
const sitegenSlice = select( nfdOnboardingStore ).getSiteGenSlice();
return !! ( sitegenSlice.homepages && Object.keys( sitegenSlice.homepages ).length > 0 );
}

case 'design': {
// Canvas step requires a selected homepage
const sitegenSlice = select( nfdOnboardingStore ).getSiteGenSlice();
return !! ( sitegenSlice.selectedHomepage && sitegenSlice.homepages && sitegenSlice.homepages[ sitegenSlice.selectedHomepage ] );
}

case 'migration':
// Migration step is optional and doesn't require specific data
return true;

default:
return true;
}
};

/**
* Check if a step should redirect due to missing data
*
* @param {string} stepKey - The key of the step to check
* @return {boolean} - True if should redirect, false otherwise
*/
export const shouldRedirectToFirstStep = ( stepKey ) => {
// Don't redirect if we're already on the first step
if ( stepKey === 'fork' ) {
return false;
}

// Check if the step has required data
return ! hasRequiredDataForStep( stepKey );
};