From 19b687415b45ca09a078cf54f66e4d338bce7a29 Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Mon, 20 Apr 2026 12:39:29 +0200 Subject: [PATCH 1/3] EDM-3687: Allow user to go back to step to fix problems --- libs/cypress/e2e/fleets/createFleet.cy.ts | 15 +++++++++++++++ libs/cypress/pages/CreateFleetWizardPage.ts | 4 ++++ .../Fleet/CreateFleet/CreateFleetWizard.tsx | 7 +++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/cypress/e2e/fleets/createFleet.cy.ts b/libs/cypress/e2e/fleets/createFleet.cy.ts index a0b8a55fd..f91c9a02c 100644 --- a/libs/cypress/e2e/fleets/createFleet.cy.ts +++ b/libs/cypress/e2e/fleets/createFleet.cy.ts @@ -58,6 +58,21 @@ describe('Create fleet form', () => { fleetDetailsPage.title.should('have.text', 'sample-fleet'); }); + it('allows navigating between previous and current step when there are validation errors', () => { + fleetsPage.openCreateFleetFormButton.click(); + createFleetWizardPage = new CreateFleetWizardPage(); + + createFleetWizardPage.newFleetNameField.type('another-fleet'); + createFleetWizardPage.nextFleetWizardButton.should('be.enabled').click(); + + createFleetWizardPage.newFleetSystemImageField.type('invalid!oci').blur(); + createFleetWizardPage.nextFleetWizardButton.should('be.disabled'); + createFleetWizardPage.backFleetWizardButton.click(); + createFleetWizardPage.nextFleetWizardButton.should('be.enabled').click(); + + createFleetWizardPage.newFleetSystemImageField.should('be.visible').should('have.value', 'invalid!oci'); + }); + it('disables the create fleet button if a fleet with the same name exists', () => { const existingFleetName = 'eu-west-prod-001'; fleetsPage.fleetRow(existingFleetName).should('exist'); diff --git a/libs/cypress/pages/CreateFleetWizardPage.ts b/libs/cypress/pages/CreateFleetWizardPage.ts index b74576f0f..af52ce254 100644 --- a/libs/cypress/pages/CreateFleetWizardPage.ts +++ b/libs/cypress/pages/CreateFleetWizardPage.ts @@ -59,6 +59,10 @@ export class CreateFleetWizardPage { return cy.contains('button', 'Next'); } + get backFleetWizardButton() { + return cy.contains('button', 'Back'); + } + get addConfigurationButton() { return cy.contains('button', 'Add configuration'); } diff --git a/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx b/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx index 38a7163f8..627f82afa 100644 --- a/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx +++ b/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx @@ -57,9 +57,12 @@ const getValidStepIds = (formikErrors: FormikErrors): string[] return validStepIds; }; +// Do not disable the current step where there could be validation errors const isDisabledStep = (stepId: string, validStepIds: string[]) => { - const validIndex = validStepIds.indexOf(stepId); - return validIndex === -1 || validIndex !== orderedIds.indexOf(stepId); + const stepIdx = orderedIds.findIndex((orderedStepId) => orderedStepId === stepId); + return orderedIds.some((orderedId, orderedStepIdx) => { + return orderedStepIdx < stepIdx && !validStepIds.includes(orderedId); + }); }; const CreateFleetWizard = () => { From c93c7d719f8b696c264a1418b2fa2f97edeb78f6 Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Thu, 14 May 2026 10:30:40 +0200 Subject: [PATCH 2/3] Unify logic for disabling wizard steps correctly --- .../AddCatalogItemWizard.tsx | 18 +--- .../Catalog/EditWizard/EditAppWizard.tsx | 28 +++++- .../Catalog/EditWizard/EditOsWizard.tsx | 24 ++++- .../InstallWizard/InstallAppWizard.tsx | 35 +++++-- .../Catalog/InstallWizard/InstallOsWizard.tsx | 32 +++++- .../EditDeviceWizard/EditDeviceWizard.tsx | 46 +++++++-- .../Fleet/CreateFleet/CreateFleetWizard.tsx | 15 +-- .../CreateImageBuildWizard.tsx | 14 +-- .../ImportResourceWizard.tsx | 97 +++++++++++-------- libs/ui-components/src/utils/wizards.ts | 19 ++++ 10 files changed, 230 insertions(+), 98 deletions(-) create mode 100644 libs/ui-components/src/utils/wizards.ts diff --git a/libs/ui-components/src/components/Catalog/AddCatalogItemWizard/AddCatalogItemWizard.tsx b/libs/ui-components/src/components/Catalog/AddCatalogItemWizard/AddCatalogItemWizard.tsx index 9343df6e8..2c50da5ee 100644 --- a/libs/ui-components/src/components/Catalog/AddCatalogItemWizard/AddCatalogItemWizard.tsx +++ b/libs/ui-components/src/components/Catalog/AddCatalogItemWizard/AddCatalogItemWizard.tsx @@ -29,6 +29,7 @@ import { getInitialValuesFromItem, getValidationSchema, } from './utils'; +import { isWizardStepDisabled } from '../../../utils/wizards'; import { AddCatalogItemFormValues } from './types'; import FlightCtlWizardFooter from '../../common/FlightCtlWizardFooter'; import LeaveFormConfirmation from '../../common/LeaveFormConfirmation'; @@ -57,11 +58,6 @@ const getValidStepIds = (formikErrors: FormikErrors): return validStepIds; }; -const isDisabledStep = (stepId: string, validStepIds: string[]) => { - const validIndex = validStepIds.indexOf(stepId); - return validIndex === -1 || validIndex !== orderedIds.indexOf(stepId); -}; - const validateStep = (activeStepId: string, errors: FormikErrors) => { switch (activeStepId) { case generalInfoStepId: @@ -178,27 +174,21 @@ const AddCatalogItemWizard = () => { {currentStep?.id === typeConfigStepId && } {currentStep?.id === versionStepId && } {currentStep?.id === reviewStepId && ( diff --git a/libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx b/libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx index 67b66d3c9..3477ccd6a 100644 --- a/libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx +++ b/libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx @@ -18,11 +18,28 @@ import UpdateStep, { isUpdateStepValid } from './steps/UpdateStep'; import ReviewStep from './steps/ReviewStep'; import LeaveFormConfirmation from '../../common/LeaveFormConfirmation'; import { validApplicationAndVolumeName } from '../../form/validations'; +import { isWizardStepDisabled } from '../../../utils/wizards'; const versionStepId = 'version-step'; const configStepId = 'config-step'; const reviewStepId = 'review-step'; +const orderedIds = [versionStepId, configStepId, reviewStepId]; + +const getValidStepIds = (errors: FormikErrors, values: AppUpdateFormik): string[] => { + const validStepIds: string[] = []; + if (isUpdateStepValid(errors)) { + validStepIds.push(versionStepId); + } + if (isAppConfigStepValid(values, errors)) { + validStepIds.push(configStepId); + } + if (validStepIds.length === orderedIds.length - 1) { + validStepIds.push(reviewStepId); + } + return validStepIds; +}; + const validateUpdateWizardStep = ( activeStepId: string, errors: FormikErrors, @@ -55,8 +72,7 @@ const WizardContent: React.FC = ({ const { values, errors } = useFormikContext(); - const isVersionStepValid = !!values.version; - const isConfigStepValid = isAppConfigStepValid(values, errors); + const validStepIds = getValidStepIds(errors, values); return ( <> @@ -82,13 +98,17 @@ const WizardContent: React.FC = ({ )} - + {currentStep?.id === configStepId && } {currentStep?.id === reviewStepId && ( diff --git a/libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx b/libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx index 4c7e48180..73fcefe34 100644 --- a/libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx +++ b/libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx @@ -13,10 +13,24 @@ import UpdateStep, { isUpdateStepValid } from './steps/UpdateStep'; import { getErrorMessage } from '../../../utils/error'; import ReviewStep from './steps/ReviewStep'; import LeaveFormConfirmation from '../../common/LeaveFormConfirmation'; +import { isWizardStepDisabled } from '../../../utils/wizards'; const versionStepId = 'version-step'; const reviewStepId = 'review-step'; +const orderedIds = [versionStepId, reviewStepId]; + +const getValidStepIds = (errors: FormikErrors): string[] => { + const validStepIds: string[] = []; + if (isUpdateStepValid(errors)) { + validStepIds.push(versionStepId); + } + if (validStepIds.length === orderedIds.length - 1) { + validStepIds.push(reviewStepId); + } + return validStepIds; +}; + const validateUpdateWizardStep = (activeStepId: string, errors: FormikErrors) => { if (activeStepId === versionStepId) return isUpdateStepValid(errors); return true; @@ -34,9 +48,9 @@ const WizardContent: React.FC = ({ currentVersion, catalogIt const { t } = useTranslation(); const [currentStep, setCurrentStep] = React.useState(); - const { values } = useFormikContext(); + const { errors } = useFormikContext(); - const isVersionStepValid = !!values.version; + const validStepIds = getValidStepIds(errors); return ( <> @@ -62,7 +76,11 @@ const WizardContent: React.FC = ({ currentVersion, catalogIt )} - + {currentStep?.id === reviewStepId && } diff --git a/libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx b/libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx index 1e4514990..fad50c96b 100644 --- a/libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx +++ b/libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx @@ -1,6 +1,6 @@ import { CatalogItem } from '@flightctl/types/alpha'; import { Wizard, WizardStep, WizardStepType } from '@patternfly/react-core'; -import { Formik, useFormikContext } from 'formik'; +import { Formik, FormikErrors, useFormikContext } from 'formik'; import * as React from 'react'; import * as Yup from 'yup'; import { load } from 'js-yaml'; @@ -22,6 +22,26 @@ import { useAppContext } from '../../../hooks/useAppContext'; import { getInitialAppConfig } from './utils'; import { useSubmitCatalogForm } from '../useSubmitCatalogForm'; import { validApplicationAndVolumeName } from '../../form/validations'; +import { isWizardStepDisabled } from '../../../utils/wizards'; + +const orderedIds = [specificationsStepId, selectTargetStepId, appConfigStepId, reviewStepId]; + +const getValidStepIds = (errors: FormikErrors, values: InstallAppFormik): string[] => { + const validStepIds: string[] = []; + if (isSpecsStepValid(errors)) { + validStepIds.push(specificationsStepId); + } + if (isSelectTargetStepValid(errors)) { + validStepIds.push(selectTargetStepId); + } + if (isAppConfigStepValid(values, errors)) { + validStepIds.push(appConfigStepId); + } + if (validStepIds.length === orderedIds.length - 1) { + validStepIds.push(reviewStepId); + } + return validStepIds; +}; export const validateAppWizardStep: FlightCtlWizardFooterProps['validateStep'] = ( activeStepId, @@ -55,6 +75,7 @@ const InstallAppWizardContent = ({ }: InstallAppWizardContentProps) => { const { t } = useTranslation(); const { values, errors } = useFormikContext(); + const validStepIds = getValidStepIds(errors, values); return isSuccessful ? ( ) : ( @@ -81,22 +102,24 @@ const InstallAppWizardContent = ({ )} - + {currentStep?.id === selectTargetStepId && } {currentStep?.id === appConfigStepId && } {currentStep?.id === reviewStepId && } diff --git a/libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx b/libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx index 533cadfb4..258c48c51 100644 --- a/libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx +++ b/libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx @@ -1,6 +1,6 @@ import { CatalogItem } from '@flightctl/types/alpha'; import { Wizard, WizardStep, WizardStepType } from '@patternfly/react-core'; -import { Formik, useFormikContext } from 'formik'; +import { Formik, FormikErrors, useFormikContext } from 'formik'; import * as React from 'react'; import * as Yup from 'yup'; import { Device, Fleet } from '@flightctl/types'; @@ -18,6 +18,26 @@ import UpdateSuccessPage from './UpdateSuccessPage'; import FlightCtlWizardFooter, { FlightCtlWizardFooterProps } from '../../common/FlightCtlWizardFooter'; import { useAppContext } from '../../../hooks/useAppContext'; import { useNavigate } from '../../../hooks/useNavigate'; +import { isWizardStepDisabled } from '../../../utils/wizards'; + +const getOrderedStepIds = (target: InstallOsFormik['target']) => + target === 'new-device' + ? [specificationsStepId, selectTargetStepId] + : [specificationsStepId, selectTargetStepId, reviewStepId]; + +const getValidStepIds = (errors: FormikErrors, orderedStepIds: string[]): string[] => { + const validStepIds: string[] = []; + if (isSpecsStepValid(errors)) { + validStepIds.push(specificationsStepId); + } + if (isSelectTargetStepValid(errors)) { + validStepIds.push(selectTargetStepId); + } + if (orderedStepIds.includes(reviewStepId) && validStepIds.length === orderedStepIds.length - 1) { + validStepIds.push(reviewStepId); + } + return validStepIds; +}; export const validateOsWizardStep: FlightCtlWizardFooterProps['validateStep'] = ( activeStepId, @@ -47,6 +67,8 @@ const InstallOsWizardContent = ({ }: InstallOsWizardContentProps) => { const { t } = useTranslation(); const { values, errors } = useFormikContext(); + const orderedStepIds = getOrderedStepIds(values.target); + const validStepIds = getValidStepIds(errors, orderedStepIds); const showLeaveConfirmation = !(values.target === 'new-device' && currentStep?.id === selectTargetStepId); return isSuccessful ? ( @@ -75,14 +97,18 @@ const InstallOsWizardContent = ({ )} - + {currentStep?.id === selectTargetStepId && } {values.target !== 'new-device' && ( {currentStep?.id === reviewStepId && } diff --git a/libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx b/libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx index 36017a087..f56dd9cfc 100644 --- a/libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx +++ b/libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Formik } from 'formik'; +import { Formik, FormikErrors } from 'formik'; import { Alert, Breadcrumb, @@ -40,6 +40,26 @@ import EditDeviceWizardFooter from './EditDeviceWizardFooter'; import PageWithPermissions from '../../common/PageWithPermissions'; import { usePermissionsContext } from '../../common/PermissionsContext'; import { RESOURCE, VERB } from '../../../types/rbac'; +import { isWizardStepDisabled } from '../../../utils/wizards'; + +const orderedIds = [generalInfoStepId, deviceTemplateStepId, deviceUpdatePolicyStepId, reviewDeviceStepId]; + +const getValidStepIds = (formikErrors: FormikErrors): string[] => { + const validStepIds: string[] = []; + if (isGeneralInfoStepValid(formikErrors)) { + validStepIds.push(generalInfoStepId); + } + if (isDeviceTemplateStepValid(formikErrors)) { + validStepIds.push(deviceTemplateStepId); + } + if (isUpdatePolicyStepValid(formikErrors)) { + validStepIds.push(deviceUpdatePolicyStepId); + } + if (validStepIds.length === orderedIds.length - 1) { + validStepIds.push(reviewDeviceStepId); + } + return validStepIds; +}; const EditDeviceWizard = () => { const { t } = useTranslation(); @@ -109,13 +129,9 @@ const EditDeviceWizard = () => { }} > {({ values, errors: formikErrors }) => { - const generalStepValid = isGeneralInfoStepValid(formikErrors); - const templateStepValid = isDeviceTemplateStepValid(formikErrors); - const updateStepValid = isUpdatePolicyStepValid(formikErrors); + const validStepIds = getValidStepIds(formikErrors); const isFleetless = !values.fleetMatch; - const isTemplateStepDisabled = !(generalStepValid && isFleetless); - const isUpdateStepDisabled = !(generalStepValid && templateStepValid && isFleetless); return ( <> @@ -132,13 +148,25 @@ const EditDeviceWizard = () => { - + - + - + diff --git a/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx b/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx index 627f82afa..704937aa0 100644 --- a/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx +++ b/libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx @@ -36,6 +36,7 @@ import { usePermissionsContext } from '../../common/PermissionsContext'; import PageWithPermissions from '../../common/PageWithPermissions'; import { useAppContext } from '../../../hooks/useAppContext'; import ResourceLink from '../../common/ResourceLink'; +import { isWizardStepDisabled } from '../../../utils/wizards'; const orderedIds = [generalInfoStepId, deviceTemplateStepId, updatePolicyStepId, reviewStepId]; @@ -57,14 +58,6 @@ const getValidStepIds = (formikErrors: FormikErrors): string[] return validStepIds; }; -// Do not disable the current step where there could be validation errors -const isDisabledStep = (stepId: string, validStepIds: string[]) => { - const stepIdx = orderedIds.findIndex((orderedStepId) => orderedStepId === stepId); - return orderedIds.some((orderedId, orderedStepIdx) => { - return orderedStepIdx < stepIdx && !validStepIds.includes(orderedId); - }); -}; - const CreateFleetWizard = () => { const { t } = useTranslation(); const { post, patch } = useFetch(); @@ -148,7 +141,7 @@ const CreateFleetWizard = () => { {currentStep?.id === deviceTemplateStepId && ( @@ -157,14 +150,14 @@ const CreateFleetWizard = () => { {currentStep?.id === updatePolicyStepId && } {currentStep?.id === reviewStepId && } diff --git a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx index 0fd5a5539..e297bcf1a 100644 --- a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx +++ b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx @@ -35,6 +35,7 @@ import { useFetch } from '../../../hooks/useFetch'; import { useEditImageBuild } from './useEditImageBuild'; import { OciRegistriesContextProvider, useOciRegistriesContext } from '../OciRegistriesContext'; import { getImageBuildStatusReason } from '../../../utils/imageBuilds'; +import { isWizardStepDisabled } from '../../../utils/wizards'; const orderedIds = [sourceImageStepId, outputImageStepId, registrationStepId, reviewStepId]; @@ -56,13 +57,6 @@ const getValidStepIds = (formikErrors: FormikErrors): stri return validStepIds; }; -const isDisabledStep = (stepId: string, validStepIds: string[]) => { - const stepIdx = orderedIds.findIndex((stepOrderId) => stepOrderId === stepId); - return orderedIds.some((orderedId, orderedStepIdx) => { - return orderedStepIdx < stepIdx && !validStepIds.includes(orderedId); - }); -}; - const CreateImageBuildWizard = () => { const { t } = useTranslation(); const { post } = useFetch(); @@ -195,21 +189,21 @@ const CreateImageBuildWizard = () => { {currentStep?.id === outputImageStepId && } {currentStep?.id === registrationStepId && } {currentStep?.id === reviewStepId && } diff --git a/libs/ui-components/src/components/ImportResourceWizard/ImportResourceWizard.tsx b/libs/ui-components/src/components/ImportResourceWizard/ImportResourceWizard.tsx index c678c2535..92bf849d6 100644 --- a/libs/ui-components/src/components/ImportResourceWizard/ImportResourceWizard.tsx +++ b/libs/ui-components/src/components/ImportResourceWizard/ImportResourceWizard.tsx @@ -39,6 +39,26 @@ import PageWithPermissions from '../common/PageWithPermissions'; import { usePermissionsContext } from '../common/PermissionsContext'; import FlightCtlWizardFooter from '../common/FlightCtlWizardFooter'; import { RESOURCE, VERB } from '../../types/rbac'; +import { isWizardStepDisabled } from '../../utils/wizards'; + +const orderedIds = [repositoryStepId, resourceSyncStepId, reviewStepId]; + +const getValidStepIds = ( + values: ImportResourceFormValues, + formikErrors: FormikErrors, +): string[] => { + const validStepIds: string[] = []; + if (isRepoStepValid(values, formikErrors)) { + validStepIds.push(repositoryStepId); + } + if (isResourceSyncStepValid(formikErrors)) { + validStepIds.push(resourceSyncStepId); + } + if (validStepIds.length === orderedIds.length - 1) { + validStepIds.push(reviewStepId); + } + return validStepIds; +}; const validationSchema = (t: TFunction) => Yup.lazy((values: ImportResourceFormValues) => @@ -137,46 +157,47 @@ const ImportResourceWizardContent = ({ navigate(successRoute); }} > - {({ values, errors: formikErrors }) => ( - <> - - - firstStepId={repositoryStepId} - submitStepId={reviewStepId} - validateStep={validateFooterStep} - saveButtonText={t('Import')} - /> - } - onStepChange={(_, step) => setCurrentStep(step)} - > - - {(!currentStep || currentStep?.id === repositoryStepId) && ( - - )} - - { + const validStepIds = getValidStepIds(values, formikErrors); + return ( + <> + + + firstStepId={repositoryStepId} + submitStepId={reviewStepId} + validateStep={validateFooterStep} + saveButtonText={t('Import')} + /> } + onStepChange={(_, step) => setCurrentStep(step)} > - {currentStep?.id === resourceSyncStepId && ( - - )} - - - {currentStep?.id === reviewStepId && } - - - - )} + + {(!currentStep || currentStep?.id === repositoryStepId) && ( + + )} + + + {currentStep?.id === resourceSyncStepId && ( + + )} + + + {currentStep?.id === reviewStepId && } + + + + ); + }} ); }; diff --git a/libs/ui-components/src/utils/wizards.ts b/libs/ui-components/src/utils/wizards.ts new file mode 100644 index 000000000..bd7b42dac --- /dev/null +++ b/libs/ui-components/src/utils/wizards.ts @@ -0,0 +1,19 @@ +/** + * Determines if a WizardStep should be disabled. + * + * A step becomes disabled when any of the steps before it are invalid: + * - This allows users to move from the last step they reached, back to previous steps. + * - Users cannot skip ahead from an invalid step to a subsequent step. + * + * @example + * // orderedStepIds: [A, B, C]. validStepIds: [A] + * isWizardStepDisabled('A', ['A','B','C'], ['A']); // false — A is valid + * isWizardStepDisabled('B', ['A','B','C'], ['A']); // false — User can access step B since A is valid. + * isWizardStepDisabled('C', ['A','B','C'], ['A']); // true — User is blocked from accessing step C since B is invalid. + */ +export const isWizardStepDisabled = (stepId: string, orderedStepIds: string[], validStepIds: string[]) => { + const stepIdx = orderedStepIds.findIndex((orderedStepId) => orderedStepId === stepId); + return orderedStepIds.some((orderedId, orderedStepIdx) => { + return orderedStepIdx < stepIdx && !validStepIds.includes(orderedId); + }); +}; From 11ba06b8565aff3c72493beab06145b3ecc1177f Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Thu, 14 May 2026 11:23:16 +0200 Subject: [PATCH 3/3] Improve function documentation --- libs/ui-components/src/utils/wizards.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/ui-components/src/utils/wizards.ts b/libs/ui-components/src/utils/wizards.ts index bd7b42dac..72fbcf43c 100644 --- a/libs/ui-components/src/utils/wizards.ts +++ b/libs/ui-components/src/utils/wizards.ts @@ -11,9 +11,14 @@ * isWizardStepDisabled('B', ['A','B','C'], ['A']); // false — User can access step B since A is valid. * isWizardStepDisabled('C', ['A','B','C'], ['A']); // true — User is blocked from accessing step C since B is invalid. */ + export const isWizardStepDisabled = (stepId: string, orderedStepIds: string[], validStepIds: string[]) => { - const stepIdx = orderedStepIds.findIndex((orderedStepId) => orderedStepId === stepId); - return orderedStepIds.some((orderedId, orderedStepIdx) => { - return orderedStepIdx < stepIdx && !validStepIds.includes(orderedId); - }); + const stepIndex = orderedStepIds.indexOf(stepId); + if (stepIndex <= 0) { + return false; + } + + // If there are any invalid steps before the current one, the current step should be disabled. + const previousStepIds = orderedStepIds.slice(0, stepIndex); + return previousStepIds.some((id) => !validStepIds.includes(id)); };