diff --git a/src/app/components/Step/StepValidationWrapper.js b/src/app/components/Step/StepValidationWrapper.js
new file mode 100644
index 000000000..ef26ee2f9
--- /dev/null
+++ b/src/app/components/Step/StepValidationWrapper.js
@@ -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 ;
+ };
+
+ // Set display name for debugging
+ StepValidationWrapper.displayName = `withStepValidation(${ WrappedComponent.displayName || WrappedComponent.name || 'Component' })`;
+
+ return StepValidationWrapper;
+};
diff --git a/src/app/steps/Migration/MigrationStep.js b/src/app/steps/Migration/MigrationStep.js
index 10da48501..6c3c3100e 100644
--- a/src/app/steps/Migration/MigrationStep.js
+++ b/src/app/steps/Migration/MigrationStep.js
@@ -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';
@@ -25,6 +25,7 @@ const MigrationStep = () => {
/**
* Track migration initiated event
+ *
* @param { string } instaWpMigrationUrl The migration url
* @return { Promise }
*/
@@ -46,10 +47,6 @@ const MigrationStep = () => {
);
};
- useEffect( () => {
- prepareMigration();
- }, [ prepareMigration ] );
-
const prepareMigration = useCallback( async () => {
try {
if ( ! canMigrateSite ) {
@@ -89,6 +86,10 @@ const MigrationStep = () => {
}
}, [ canMigrateSite, setInstaWpMigrationUrl ] );
+ useEffect( () => {
+ prepareMigration();
+ }, [ prepareMigration ] );
+
/**
* @return { string } The title content
*/
diff --git a/src/app/steps/index.js b/src/app/steps/index.js
index d574885d9..128a6afa3 100644
--- a/src/app/steps/index.js
+++ b/src/app/steps/index.js
@@ -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' ),
},
};
diff --git a/src/app/utils/helpers/stepValidation.js b/src/app/utils/helpers/stepValidation.js
new file mode 100644
index 000000000..e833743a7
--- /dev/null
+++ b/src/app/utils/helpers/stepValidation.js
@@ -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 );
+};