Skip to content

Instantly share code, notes, and snippets.

@JohnRandom
Last active June 25, 2022 07:05
Show Gist options
  • Save JohnRandom/2fb3053fd32c5716e4cdca0a3a8459f9 to your computer and use it in GitHub Desktop.
Save JohnRandom/2fb3053fd32c5716e4cdca0a3a8459f9 to your computer and use it in GitHub Desktop.
import { push, replace, setOnboardingStep } from 'actions';
let step = null;
let finalStep = 'STEP_C';
const steps = {
STEP_A: { url: '/feature_1', nextStep: 'STEP_B' },
STEP_B: { url: '/feature_2', nextStep: 'STEP_C' },
STEP_C: { url: '/feature_3', nextStep: 'STEP_D' },
// ... and so on ...
};
const milestones = [
'SAVE_USER_PROFILE_SUCCESS',
'SAVE_PAYMENT_OPTIONS_SUCCESS',
'CONFIRM_SHOPPING_BASKET_SELECTION',
// ... and so on ...
];
const exclusiveUrls = [
'/onboarding/route/1',
'/onboarding/route/2',
'/onboarding/route/3',
// ... and so on ...
];
const middleware = store => next => action => {
// Make sure call this at the beginning, so that state
// changes already happened when we have to redirect.
const nextAction = next(action);
// These actions are always being handled, even if the user
// iss currently not being on-boarded.
switch (action.type) {
case 'SET_ONBOARDING_STEP':
step = action.payload;
const { url } = steps[step];
// Make sure we "replace" instead of "push" when we have an on-boarding
// exclusive URL.
const redirectMethod = exclusiveUrls.indexOf(url) >= 0 ? replace : push;
store.dispatch( redirectMethod(url) );
break;
case 'ADD_ONBOARDING_MILESTONE':
milestones.push(action.payload);
break;
case 'REMOVE_ONBOARDING_MILESTONE':
const idx = milestones.indexOf(action.payload);
if (idx >= 0) milestones.splice(idx, 1);
break;
case 'INSERT_ONBOARDING_STEP':
const { stepBefore, step, stepLabel } = action.payload;
steps[stepBefore].nextStep = stepLabel;
steps[stepLabel] = step;
break;
case 'REMOVE_ONBOARDING_STEP':
const { stepLabel, newNextStep } = action.payload;
// Rewrite all pointers to the deleted step with the new next step label.
Object.keys(steps).forEach((label) => {
if (steps[label].nextStep === stepLabel) steps[label].nextStep = newNextStep;
});
delete steps[stepLabel];
break;
case 'SET_FINAL_ONBOARDIND_STEP':
finalStep = action.payload;
break;
case 'LOGOUT':
step = null;
break;
}
// This are the actions being handled when the user is currently
// not being onboarded.
if (step === finalStep || step === null) {
// Handle all LOCATION_CHANGE action from react-router-redux.
if (action.type === '@@router/LOCATION_CHANGE';) {
// Make sure the user is redirected away from on-boarding exclusive URLs.
if (exclusiveUrls.indexOf(action.payload.pathname) >= 0) {
store.dispatch( push('/somewhere/else') );
}
}
// If we're already on the last step, we don't need to steer the flow anymore,
// so we can safely return.
return nextAction;
}
// These are the actions being handled, when the user is currently
// being on-boarded.
// If the user completed an on-boarding milestone,
// advance the flow to the next step.
if (milestones.indexOf(action.type) >= 0) {
const { nextStep } = states[step];
store.dispatch( setRegistrationStep(nextStep) );
}
// If we see a routing event during on-boarding, make sure that it only
// goes to routes allowed during on-boarding.
if (action.type === '@@router/LOCATION_CHANGE') {
const expectedUrl = steps[step].url;
const isOnCorrectUrl = action.payload.pathname === expectedUrl;
if (!isOnCorrectUrl) dispatch( push(expectedUrl) );
}
return nextAction;
};
export default middleware;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment