libs/state-machine/src/lib/stateMachineButtonPressed.ts (108 lines of code) (raw):
import { StateMachineError, StateMachineErrorCode } from './StateMachineError';
import type {
GenericStorageInterface,
WizardLayout,
WizardStepData,
WizardStepLayout,
} from './types';
import {
makeStepDataWithErrorMessage,
validateIncomingFormData,
} from './utility';
/**
* Perform the validation and actions required for a button press.Result a
* new WizardStepData containing either:
*
* - a copy of the incomingStepData with an error message added if the step
* failed; or
* - the currentStepId for the next step and the submitted form data
* if the step was success
*/
export async function stateMachineButtonPressed<
T extends GenericStorageInterface,
>(
buttonPressed: string,
incomingStepData: WizardStepData,
wizardLayout: WizardLayout<T>,
isEditPath: boolean,
shouldValidate: boolean,
storageInstance: T,
): Promise<WizardStepData> {
const currentStepLayout = wizardLayout[incomingStepData.currentStepId];
const buttonPressedDetails = currentStepLayout?.buttons[buttonPressed];
if (!currentStepLayout || !buttonPressedDetails) {
throw new StateMachineError(
`Button ${buttonPressed} not found in step ${incomingStepData.currentStepId}`,
StateMachineErrorCode.NoSuchStep,
);
}
const stepToMoveTo =
typeof buttonPressedDetails.stepToMoveTo === 'string'
? buttonPressedDetails.stepToMoveTo
: buttonPressedDetails.stepToMoveTo(
wizardLayout,
currentStepLayout as WizardStepLayout<unknown>,
isEditPath,
);
if (shouldValidate) {
const incomingDataError = validateIncomingFormData(
incomingStepData.currentStepId,
incomingStepData.formData,
wizardLayout[incomingStepData.currentStepId] as WizardStepLayout<unknown>,
);
if (incomingDataError) {
return makeStepDataWithErrorMessage(
incomingDataError.message,
incomingStepData.currentStepId,
incomingStepData.formData,
{ zodIssues: incomingDataError.issues },
);
}
}
if (buttonPressedDetails.onAfterStepStartValidate) {
const validationFailure =
await buttonPressedDetails.onAfterStepStartValidate(
incomingStepData,
undefined,
storageInstance,
);
if (validationFailure) {
return makeStepDataWithErrorMessage(
validationFailure.message,
incomingStepData.currentStepId,
incomingStepData.formData,
validationFailure.details,
);
}
}
if (buttonPressedDetails.onBeforeStepChangeValidate) {
const validationFailure =
await buttonPressedDetails.onBeforeStepChangeValidate(
incomingStepData,
currentStepLayout,
storageInstance,
);
if (validationFailure) {
return makeStepDataWithErrorMessage(
validationFailure.message,
incomingStepData.currentStepId,
incomingStepData.formData,
validationFailure.details,
);
}
}
if (!buttonPressedDetails.executeStep) {
return {
currentStepId: stepToMoveTo,
formData: incomingStepData.formData,
};
}
const executionResult = await buttonPressedDetails.executeStep(
incomingStepData,
currentStepLayout,
storageInstance,
);
if (executionResult.isFailure) {
return makeStepDataWithErrorMessage(
executionResult.message,
incomingStepData.currentStepId,
incomingStepData.formData,
executionResult.details,
);
}
return {
currentStepId: stepToMoveTo,
formData: executionResult.data,
};
}