Files
freeCodeCamp/client/src/templates/Challenges/redux/completion-epic.js

166 lines
4.4 KiB
JavaScript
Raw Normal View History

import { of, empty } from 'rxjs';
2018-05-24 19:45:38 +01:00
import {
switchMap,
retry,
catchError,
concat,
2018-09-12 15:58:08 +03:00
filter,
tap
2018-05-24 19:45:38 +01:00
} from 'rxjs/operators';
2018-04-06 14:51:52 +01:00
import { ofType } from 'redux-observable';
2018-09-12 15:58:08 +03:00
import { navigate } from 'gatsby';
2018-04-06 14:51:52 +01:00
import {
2018-05-24 19:45:38 +01:00
backendFormValuesSelector,
2018-09-27 14:19:03 +03:00
projectFormValuesSelector,
2018-05-24 19:45:38 +01:00
submitComplete,
2018-04-06 14:51:52 +01:00
types,
2018-05-24 19:45:38 +01:00
challengeMetaSelector,
challengeTestsSelector,
closeModal,
2018-09-21 17:30:16 +03:00
challengeFilesSelector,
updateProjectFormValues
2018-04-06 14:51:52 +01:00
} from './';
import {
userSelector,
isSignedInSelector,
openDonationModal,
showDonationSelector,
updateComplete,
updateFailed
} from '../../../redux';
2018-04-06 14:51:52 +01:00
import postUpdate$ from '../utils/postUpdate$';
2018-05-24 19:45:38 +01:00
import { challengeTypes, submitTypes } from '../../../../utils/challengeTypes';
2018-04-06 14:51:52 +01:00
function postChallenge(update, username) {
const saveChallenge = postUpdate$(update).pipe(
2018-05-24 19:45:38 +01:00
retry(3),
switchMap(({ points }) =>
of(
submitComplete({
username,
points,
...update.payload
}),
updateComplete()
)
2018-05-24 19:45:38 +01:00
),
catchError(() => of(updateFailed(update)))
2018-05-24 19:45:38 +01:00
);
return saveChallenge;
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitModern(type, state) {
const tests = challengeTestsSelector(state);
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
if (type === types.checkChallenge) {
return of({ type: 'this was a check challenge' });
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
if (type === types.submitChallenge) {
const { id } = challengeMetaSelector(state);
const files = challengeFilesSelector(state);
2018-05-24 19:45:38 +01:00
const { username } = userSelector(state);
const challengeInfo = {
id,
files
};
const update = {
endpoint: '/modern-challenge-completed',
payload: challengeInfo
};
return postChallenge(update, username);
2018-05-24 19:45:38 +01:00
}
}
return empty();
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitProject(type, state) {
if (type === types.checkChallenge) {
return empty();
}
2018-04-06 14:51:52 +01:00
2018-09-27 14:19:03 +03:00
const { solution, githubLink } = projectFormValuesSelector(state);
2018-05-24 19:45:38 +01:00
const { id, challengeType } = challengeMetaSelector(state);
const { username } = userSelector(state);
const challengeInfo = { id, challengeType, solution };
if (challengeType === challengeTypes.backEndProject) {
challengeInfo.githubLink = githubLink;
}
const update = {
endpoint: '/project-completed',
payload: challengeInfo
};
return postChallenge(update, username).pipe(
concat(of(updateProjectFormValues({})))
2018-05-24 19:45:38 +01:00
);
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitBackendChallenge(type, state) {
const tests = challengeTestsSelector(state);
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
if (type === types.submitChallenge) {
const { id } = challengeMetaSelector(state);
const { username } = userSelector(state);
const { solution: { value: solution } } = backendFormValuesSelector(
state
);
const challengeInfo = { id, solution };
const update = {
endpoint: '/backend-challenge-completed',
payload: challengeInfo
};
return postChallenge(update, username);
2018-05-24 19:45:38 +01:00
}
}
return empty();
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
const submitters = {
tests: submitModern,
backend: submitBackendChallenge,
'project.frontEnd': submitProject,
'project.backEnd': submitProject
};
2018-04-06 14:51:52 +01:00
function shouldShowDonate(state) {
return showDonationSelector(state) ? of(openDonationModal()) : empty();
}
export default function completionEpic(action$, state$) {
2018-04-06 14:51:52 +01:00
return action$.pipe(
ofType(types.submitChallenge),
switchMap(({ type }) => {
const state = state$.value;
2018-05-24 19:45:38 +01:00
const meta = challengeMetaSelector(state);
const { isDonating } = userSelector(state);
2018-05-24 19:45:38 +01:00
const { nextChallengePath, introPath, challengeType } = meta;
const showDonate = isDonating ? empty() : shouldShowDonate(state);
2018-05-24 19:45:38 +01:00
const closeChallengeModal = of(closeModal('completion'));
let submitter = () => of({ type: 'no-user-signed-in' });
2018-05-24 19:45:38 +01:00
if (
!(challengeType in submitTypes) ||
!(submitTypes[challengeType] in submitters)
) {
throw new Error(
'Unable to find the correct submit function for challengeType ' +
challengeType
);
}
if (isSignedInSelector(state)) {
submitter = submitters[submitTypes[challengeType]];
}
return submitter(type, state).pipe(
2018-09-12 15:58:08 +03:00
tap(() => navigate(introPath ? introPath : nextChallengePath)),
2018-05-24 19:45:38 +01:00
concat(closeChallengeModal),
concat(showDonate),
2018-05-24 19:45:38 +01:00
filter(Boolean)
);
2018-04-06 14:51:52 +01:00
})
);
}