/* global WP_DEFINE_DEVELOPMENT, WP_DEFINE_IS_NODE */
import debugLib from 'debug';
import { destroy } from 'redux-form';
import isSubmissionError from '../utils/isSubmissionError';
import { HANDLE_SUBMIT_ERRORS } from './enhancedFormActions';
/**
* All functions in this module are action creators and the return value
* should be passed to the redux store dispatch() function
*
* @module enhanced-redux-form/actions/wizardFormActions
* @category forms
*/
const debug = debugLib('SlimmingWorld:wizardFormActions');
export const REGISTER_WIZARD_FORM = 'wizardFormActions/REGISTER_WIZARD_FORM';
export const DESTROY_WIZARD_FORM = 'wizardFormActions/DESTROY_WIZARD_FORM';
export const WIZARD_STEP_SUBMIT = 'wizardFormActions/WIZARD_STEP_SUBMIT';
export const WIZARD_STEP_MOUNT = 'wizardFormActions/WIZARD_STEP_MOUNT';
export const SUBMIT_WIZARD_FORM = 'wizardFormActions/SUBMIT_WIZARD_FORM';
export const TRIGGER_WIZARD_FORM_SUBMIT = 'wizardFormActions/TRIGGER_WIZARD_FORM_SUBMIT';
/* eslint-disable max-len */
/**
* __Note: this action is dispatched by the enhancedFormWizard() HOC. You probable won't need to
* call it yourself__
*
* Calls the given submit handler with the form values of the given wizard form. If submission
* errors occur during submit, it will persist these errors on the relevant form step and redirect
* to the first form step that contained any error.
*
* @function submitWizardForm
* @param {string} wizardName The wizard name as passed to
* the {@link module:enhanced-redux-form/enhancedFormWizard~enhancedFormWizard|enhancedFormWizard function}
* @param {function} submitHandler The handler that should be called to perform the submit. The
* handler will receive the following parameters:
* - **dispatch** The redux dispatch function
* - **values** An object containing all the values that have been submitted to individual form steps
* @param {function} [transformApiFieldNames] A function that maps the field names in the validation
* errors returned by the API to field names in the actual form.
* @param {string} generalErrorMessage When the API call comes back with an error that doesn't
* have the default error response shape, this message will be shown instead.
* @param {function} historyPush A function that performs a history.push. If the wizard form
* is mounted in QueryRouting, this can be a different history instance than the main
* browserHistory
*/
export const submitWizardForm = (
wizardName,
submitHandler = () => {},
transformApiFieldNames = name => name,
generalErrorMessage,
routingAdapter,
) => (dispatch, getState) => {
const state = getState();
const wizard = state.enhancedForm.wizard[wizardName];
if (!wizard) {
throw new ReferenceError(
`Trying to submit wizard with name "${wizardName}" but it is not found in the Redux state.`,
);
}
return dispatch({
type: SUBMIT_WIZARD_FORM,
payload: Promise.resolve(submitHandler(dispatch, wizard.submittedValues)).catch(
gatewayError => {
if (isSubmissionError(gatewayError) && gatewayError.response.parsed.error.fields) {
const errors = gatewayError.response.parsed.error.fields;
const errorsPerStep = wizard.steps.map(() => []);
errors.forEach(error => {
const localFieldName = transformApiFieldNames(error.field);
const stepWithErrorIndex = wizard.steps.findIndex(step =>
step.submittedKeys.includes(localFieldName),
);
if (stepWithErrorIndex >= 0) {
errorsPerStep[stepWithErrorIndex].push(error);
} else {
debug(
`Submission errors contain a field with name '${error.field}' (transformed to '${localFieldName}') but the field is not found on any form step`,
);
}
});
errorsPerStep.forEach((stepErrors, stepIndex) => {
if (stepErrors.length) {
dispatch({
type: HANDLE_SUBMIT_ERRORS,
payload: {
errors: stepErrors.reduce((errorObject, error) => {
// eslint-disable-next-line no-param-reassign
errorObject[transformApiFieldNames(error.field)] = {
message: error.message,
code: error.code,
};
return errorObject;
}, {}),
},
meta: { form: wizard.steps[stepIndex].form },
});
}
});
const stepsWithError = wizard.steps.filter((step, index) => errorsPerStep[index].length);
if (stepsWithError.length) {
dispatch(routingAdapter.gotoStep(stepsWithError[0].stepIndex));
}
} else if (generalErrorMessage) {
// the wizardReducer will respond to the error format below
// eslint-disable-next-line no-param-reassign
gatewayError.error = { message: generalErrorMessage };
}
// don't silence the error. This will reject the SUBMIT_WIZARD_FORM async action
throw gatewayError;
},
),
meta: { wizardName },
});
};
/**
* __Note: this action is used internally by enhanced-redux-form. You will probably not
* need this for general usage__
*
* Registers a new wizard form
* @function registerWizardForm
* @param {string} wizardName The name of the wizard
* @param {Array<Object>} steps An array of steps in the wizard
* @param {string} steps[].path The path of the route for this step
* @returns {object} The action
*/
export const registerWizardForm = (wizardName, steps) => ({
type: REGISTER_WIZARD_FORM,
payload: { steps },
meta: { wizardName },
});
/**
* __Note: this action is used internally by enhanced-redux-form. You will probably not
* need this for general usage__
*
* Registers a mount of a form that is part of a wizard.
* @function wizardStepMount
* @param {string} form The name of the form that is being mounted.
* @param {string} wizardName The name of the wizard this form is part of.
* todo: updated param docs
*/
export const wizardStepMount = (
form,
wizardName,
routingAdapter,
// note: this argument may be undefined when routingAdapter is not ReactRouterWizardRoutingAdapter
router,
) => (dispatch, getState) => {
const { activeStepIndex, steps, allowStepSkipping } = dispatch(
routingAdapter.getStepInfo(wizardName, router),
);
if (!allowStepSkipping) {
for (let i = 0; i < activeStepIndex; i++) {
const precedingStep = steps[i];
const { isDisabledCheck, submitted } = precedingStep;
// redirect to the step if it has not been submitted and it is not disabled either
if (!submitted && !(isDisabledCheck && isDisabledCheck(getState))) {
// temporary workaround until we have a better way of redirecting during page render
dispatch(routingAdapter.gotoStep(i, true));
return null;
}
}
}
// Check if the current step is disabled. if not, return early
const { isDisabledCheck } = steps[activeStepIndex];
if (!isDisabledCheck || !isDisabledCheck(getState)) {
return dispatch({
type: WIZARD_STEP_MOUNT,
payload: { stepIndex: activeStepIndex },
meta: { form, wizardName },
});
}
// the current step is disabled. find the first enabled step
for (let i = activeStepIndex + 1; i < steps.length; i++) {
const nextStep = steps[i];
if (!nextStep.isDisabledCheck || !nextStep.isDisabledCheck(getState)) {
dispatch(routingAdapter.gotoStep(i, true));
return null;
}
}
// we can't access any step. This should never be the case
throw new Error(
`All wizard form steps are disabled: WizardName: ${wizardName} activeStepIndex: ${activeStepIndex} isDisabledCheck: ${isDisabledCheck}`,
);
};
export const destroyWizardForm = wizardName => (dispatch, getState) => {
const state = getState();
const wizard = state.enhancedForm.wizard[wizardName];
if (!wizard || !wizard.steps) {
debug(`Could not destroy wizard form "${wizardName}": wizard not found`);
return;
}
const steps = wizard.steps;
steps.forEach(step => {
if (step.form) {
dispatch(destroy(step.form));
}
});
dispatch({
type: DESTROY_WIZARD_FORM,
meta: { wizardName },
});
};
/**
* __Note: this action is used internally by enhanced-redux-form. You will probably not
* need this for general usage__
*
* Handles when a form step submission has been completed
* @function wizardStepSubmit
* @param {string} wizardName The name of the wizard
* @param {number} stepIndex Index of the step that has been submitted in the steps array
* @param {Object} wizardRoutingAdapter The routingAdapter passed to enhancedWizardForm config
* @param {Object} formValues The values submitted to the form
*/
export const wizardStepSubmit = (wizardName, stepIndex, wizardRoutingAdapter, formValues) => (
dispatch,
getState,
) => {
if (!wizardRoutingAdapter) {
throw new ReferenceError(
`No wizardRoutingAdapter provided to submitForm in ${wizardName}[${stepIndex}]`,
);
}
const state = getState();
const wizard = state.enhancedForm.wizard[wizardName];
if (!wizard || !wizard.steps) {
throw new ReferenceError(`Could not find wizard "${wizardName}" in wizard reducer`);
}
const steps = wizard.steps;
dispatch({
type: WIZARD_STEP_SUBMIT,
payload: { stepIndex, formValues },
meta: { wizardName, form: steps[stepIndex].form },
});
const nextStep = steps[stepIndex + 1];
if (nextStep) {
dispatch(wizardRoutingAdapter.gotoStep(nextStep.stepIndex));
return null;
}
// this action will set 'submitting' to 'true' in the Redux state. It will signal the wizard
// component to call the 'submitWizardForm' action with the submit handler
return dispatch({
type: TRIGGER_WIZARD_FORM_SUBMIT,
meta: { wizardName },
});
};