270 lines
7.9 KiB
JavaScript

import { createAction, handleActions } from 'redux-actions';
import { uniqBy } from 'lodash';
import { createTypes, createAsyncTypes } from '../utils/createTypes';
import { createFetchUserSaga } from './fetch-user-saga';
import { createAcceptTermsSaga } from './accept-terms-saga';
import { createAppMountSaga } from './app-mount-saga';
import { createReportUserSaga } from './report-user-saga';
import { createShowCertSaga } from './show-cert-saga';
import { createNightModeSaga } from './night-mode-saga';
import hardGoToEpic from './hard-go-to-epic';
import failedUpdatesEpic from './failed-updates-epic';
import updateCompleteEpic from './update-complete-epic';
import { types as settingsTypes } from './settings';
/** ***********************************/
const challengeReduxTypes = {};
/** ***********************************/
const ns = 'app';
const defaultFetchState = {
pending: true,
complete: false,
errored: false,
error: null
};
const initialState = {
appUsername: '',
completionCount: 0,
showCert: {},
showCertFetchState: {
...defaultFetchState
},
user: {},
userFetchState: {
...defaultFetchState
},
showDonationModal: false,
isOnline: true
};
export const types = createTypes(
[
'appMount',
'closeDonationModal',
'hardGoTo',
'openDonationModal',
'onlineStatusChange',
'updateComplete',
'updateFailed',
...createAsyncTypes('fetchUser'),
...createAsyncTypes('acceptTerms'),
...createAsyncTypes('showCert'),
...createAsyncTypes('reportUser')
],
ns
);
export const epics = [
hardGoToEpic,
failedUpdatesEpic,
updateCompleteEpic
];
export const sagas = [
...createAcceptTermsSaga(types),
...createAppMountSaga(types),
...createFetchUserSaga(types),
...createShowCertSaga(types),
...createReportUserSaga(types),
...createNightModeSaga({ ...types, ...settingsTypes })
];
export const appMount = createAction(types.appMount);
export const closeDonationModal = createAction(types.closeDonationModal);
export const openDonationModal = createAction(types.openDonationModal);
export const onlineStatusChange = createAction(types.onlineStatusChange);
// `hardGoTo` is used to hit the API server directly
// without going through /internal
// used for things like /signin and /signout
export const hardGoTo = createAction(types.hardGoTo);
export const updateComplete = createAction(types.updateComplete);
export const updateFailed = createAction(types.updateFailed);
export const acceptTerms = createAction(types.acceptTerms);
export const acceptTermsComplete = createAction(types.acceptTermsComplete);
export const acceptTermsError = createAction(types.acceptTermsError);
export const fetchUser = createAction(types.fetchUser);
export const fetchUserComplete = createAction(types.fetchUserComplete);
export const fetchUserError = createAction(types.fetchUserError);
export const reportUser = createAction(types.reportUser);
export const reportUserComplete = createAction(types.reportUserComplete);
export const reportUserError = createAction(types.reportUserError);
export const showCert = createAction(types.showCert);
export const showCertComplete = createAction(types.showCertComplete);
export const showCertError = createAction(types.showCertError);
export const completedChallengesSelector = state =>
userSelector(state).completedChallenges || [];
export const completionCountSelector = state => state[ns].completionCount;
export const currentChallengeIdSelector = state =>
userSelector(state).currentChallengeId || '';
export const isOnlineSelector = state => state[ns].isOnline;
export const isSignedInSelector = state => !!state[ns].appUsername;
export const isDonationModalOpenSelector = state => state[ns].showDonationModal;
export const signInLoadingSelector = state =>
userFetchStateSelector(state).pending;
export const showCertSelector = state => state[ns].showCert;
export const showCertFetchStateSelector = state => state[ns].showCertFetchState;
export const showDonationSelector = state => {
const completedChallenges = completedChallengesSelector(state);
const completionCount = completionCountSelector(state);
const currentCompletedLength = completedChallenges.length;
// the user has not completed 9 challenges in total yet
if (currentCompletedLength < 9) {
return false;
}
// this will mean we are on the 10th submission in total for the user
if (completedChallenges.length === 9) {
return true;
}
// this will mean we are on the 3rd submission for this browser session
if (completionCount === 2) {
return true;
}
return false;
};
export const userByNameSelector = username => state => {
const { user } = state[ns];
return username in user ? user[username] : {};
};
export const userFetchStateSelector = state => state[ns].userFetchState;
export const usernameSelector = state => state[ns].appUsername;
export const userSelector = state => {
const username = usernameSelector(state);
return state[ns].user[username] || {};
};
function spreadThePayloadOnUser(state, payload) {
return {
...state,
user: {
...state.user,
[state.appUsername]: {
...state.user[state.appUsername],
...payload
}
}
};
}
export const reducer = handleActions(
{
[types.fetchUser]: state => ({
...state,
userFetchState: { ...defaultFetchState }
}),
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({
...state,
user: {
...state.user,
[username]: user
},
appUsername: username,
userFetchState: {
pending: false,
complete: true,
errored: false,
error: null
}
}),
[types.fetchUserError]: (state, { payload }) => ({
...state,
userFetchState: {
pending: false,
complete: false,
errored: true,
error: payload
}
}),
[types.onlineStatusChange]: (state, { payload: isOnline }) => ({
...state,
isOnline
}),
[types.openDonationModal]: state => ({
...state,
showDonationModal: true
}),
[types.showCert]: state => ({
...state,
showCert: {},
showCertFetchState: { ...defaultFetchState }
}),
[types.showCertComplete]: (state, { payload }) => ({
...state,
showCert: payload,
showCertFetchState: {
pending: false,
complete: true,
errored: false,
error: null
}
}),
[types.showCertError]: (state, { payload }) => ({
...state,
showCert: {},
showCertFetchState: {
pending: false,
complete: false,
errored: true,
error: payload
}
}),
[challengeReduxTypes.submitComplete]: (state, { payload: { id } }) => {
const { appUsername } = state;
return {
...state,
completionCount: state.completionCount + 1,
user: {
...state.user,
[appUsername]: {
...state.user[appUsername],
completedChallenges: uniqBy(
[...state.user[appUsername].completedChallenges, { id }],
'id'
)
}
}
};
},
[settingsTypes.submitNewUsernameComplete]: (state, { payload }) =>
payload
? {
...state,
user: {
...state.user,
[state.appUsername]: {
...state.user[state.appUsername],
username: payload
}
}
}
: state,
[settingsTypes.submitNewAboutComplete]: (state, { payload }) =>
payload ? spreadThePayloadOnUser(state, payload) : state,
[settingsTypes.updateMyEmailComplete]: (state, { payload }) =>
payload ? spreadThePayloadOnUser(state, payload) : state,
[settingsTypes.updateUserFlagComplete]: (state, { payload }) =>
payload ? spreadThePayloadOnUser(state, payload) : state,
[settingsTypes.verifyCertComplete]: (state, { payload }) =>
payload ? spreadThePayloadOnUser(state, payload) : state
},
initialState
);