feat: Create and use dynamic active donation counts

This commit is contained in:
Bouncey
2018-11-29 14:24:17 +00:00
committed by mrugesh mohapatra
parent 331ea3ebf9
commit 8fab33ba99
6 changed files with 111 additions and 58 deletions

View File

@ -15,21 +15,28 @@ import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
const log = debugFactory('fcc:boot:user'); const log = debugFactory('fcc:boot:user');
const sendNonUserToHome = ifNoUserRedirectTo(homeLocation); const sendNonUserToHome = ifNoUserRedirectTo(homeLocation);
module.exports = function bootUser(app) { function bootUser(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
const getSessionUser = createReadSessionUser(app);
const postReportUserProfile = createPostReportUserProfile(app);
const postDeleteAccount = createPostDeleteAccount(app);
api.get('/account', sendNonUserToHome, getAccount); api.get('/account', sendNonUserToHome, getAccount);
api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial); api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial);
api.get('/user/get-session-user', readSessionUser); api.get('/user/get-session-user', getSessionUser);
api.post('/account/delete', ifNoUser401, createPostDeleteAccount(app)); api.post('/account/delete', ifNoUser401, postDeleteAccount);
api.post('/account/reset-progress', ifNoUser401, postResetProgress); api.post('/account/reset-progress', ifNoUser401, postResetProgress);
api.post('/user/report-user/', ifNoUser401, createPostReportUserProfile(app)); api.post('/user/report-user/', ifNoUser401, postReportUserProfile);
app.use('/internal', api); app.use('/internal', api);
}; }
function readSessionUser(req, res, next) { function createReadSessionUser(app) {
const { Donation } = app.models;
return function getSessionUser(req, res, next) {
const queryUser = req.user; const queryUser = req.user;
const source = const source =
@ -37,7 +44,9 @@ function readSessionUser(req, res, next) {
Observable.forkJoin( Observable.forkJoin(
queryUser.getCompletedChallenges$(), queryUser.getCompletedChallenges$(),
queryUser.getPoints$(), queryUser.getPoints$(),
(completedChallenges, progressTimestamps) => ({ Donation.getCurrentActiveDonationCount$(),
(completedChallenges, progressTimestamps, activeDonations) => ({
activeDonations,
completedChallenges, completedChallenges,
progress: getProgress(progressTimestamps, queryUser.timezone) progress: getProgress(progressTimestamps, queryUser.timezone)
}) })
@ -46,12 +55,17 @@ function readSessionUser(req, res, next) {
() => !queryUser, () => !queryUser,
Observable.of({ user: {}, result: '' }), Observable.of({ user: {}, result: '' }),
Observable.defer(() => source) Observable.defer(() => source)
.map(({ completedChallenges, progress }) => ({ .map(({ activeDonations, completedChallenges, progress }) => ({
user: {
...queryUser.toJSON(), ...queryUser.toJSON(),
...progress, ...progress,
completedChallenges: completedChallenges.map(fixCompletedChallengeItem) completedChallenges: completedChallenges.map(
fixCompletedChallengeItem
)
},
sessionMeta: { activeDonations }
})) }))
.map(user => ({ .map(({ user, sessionMeta }) => ({
user: { user: {
[user.username]: { [user.username]: {
...pick(user, userPropsForSession), ...pick(user, userPropsForSession),
@ -63,9 +77,11 @@ function readSessionUser(req, res, next) {
...normaliseUserFields(user) ...normaliseUserFields(user)
} }
}, },
sessionMeta,
result: user.username result: user.username
})) }))
).subscribe(user => res.json(user), next); ).subscribe(user => res.json(user), next);
};
} }
function getAccount(req, res) { function getAccount(req, res) {
@ -241,3 +257,4 @@ function createPostReportUserProfile(app) {
); );
}; };
} }
export default bootUser;

View File

@ -2,14 +2,18 @@ import { Observable } from 'rx';
export default function(Donation) { export default function(Donation) {
Donation.on('dataSourceAttached', () => { Donation.on('dataSourceAttached', () => {
Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation));
Donation.findOne$ = Observable.fromNodeCallback( Donation.findOne$ = Observable.fromNodeCallback(
Donation.findOne.bind(Donation) Donation.findOne.bind(Donation)
); );
Donation.prototype.validate$ = Observable.fromNodeCallback(
Donation.prototype.validate
);
Donation.prototype.destroy$ = Observable.fromNodeCallback(
Donation.prototype.destroy
);
}); });
function getCurrentActiveDonationCount$() {
// eslint-disable-next-line no-undefined
return Donation.find$({ where: { endDate: undefined } }).map(
instances => instances.length
);
}
Donation.getCurrentActiveDonationCount$ = getCurrentActiveDonationCount$;
} }

View File

@ -6,9 +6,12 @@ import FullWidthRow from '../components/helpers/FullWidthRow';
import './supporters.css'; import './supporters.css';
const propTypes = { isDonating: PropTypes.bool.isRequired }; const propTypes = {
activeDonations: PropTypes.number.isRequired,
isDonating: PropTypes.bool.isRequired
};
function Supporters({ isDonating }) { function Supporters({ isDonating, activeDonations }) {
return ( return (
<Fragment> <Fragment>
<FullWidthRow> <FullWidthRow>
@ -16,10 +19,10 @@ function Supporters({ isDonating }) {
</FullWidthRow> </FullWidthRow>
<FullWidthRow> <FullWidthRow>
<div id='supporter-progress-wrapper'> <div id='supporter-progress-wrapper'>
<ProgressBar max={10000} now={400} /> <ProgressBar max={10000} now={activeDonations} />
<div id='progress-label-wrapper'> <div id='progress-label-wrapper'>
<span className='progress-label'> <span className='progress-label'>
4000 supporters out of 10,000 goal {activeDonations} supporters out of 10,000 goal
</span> </span>
</div> </div>
</div> </div>

View File

@ -14,13 +14,15 @@ import Supporters from '../components/Supporters';
import { import {
userSelector, userSelector,
userFetchStateSelector, userFetchStateSelector,
isSignedInSelector isSignedInSelector,
activeDonationsSelector
} from '../redux'; } from '../redux';
import { randomQuote } from '../utils/get-words'; import { randomQuote } from '../utils/get-words';
import './welcome.css'; import './welcome.css';
const propTypes = { const propTypes = {
activedonations: PropTypes.number,
fetchState: PropTypes.shape({ fetchState: PropTypes.shape({
pending: PropTypes.bool, pending: PropTypes.bool,
complete: PropTypes.bool, complete: PropTypes.bool,
@ -42,7 +44,13 @@ const mapStateToProps = createSelector(
userFetchStateSelector, userFetchStateSelector,
isSignedInSelector, isSignedInSelector,
userSelector, userSelector,
(fetchState, isSignedIn, user) => ({ fetchState, isSignedIn, user }) activeDonationsSelector,
(fetchState, isSignedIn, user, activeDonations) => ({
activeDonations,
fetchState,
isSignedIn,
user
})
); );
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch); const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
@ -57,7 +65,8 @@ function Welcome({
completedCertCount = 0, completedCertCount = 0,
completedLegacyCertCount: completedLegacyCerts = 0, completedLegacyCertCount: completedLegacyCerts = 0,
isDonating isDonating
} },
activeDonations
}) { }) {
if (pending && !complete) { if (pending && !complete) {
return ( return (
@ -94,7 +103,10 @@ function Welcome({
</Col> </Col>
</Row> </Row>
<Spacer /> <Spacer />
<Supporters isDonating={isDonating} /> <Supporters
activeDonations={activeDonations}
isDonating={isDonating}
/>
<Spacer size={2} /> <Spacer size={2} />
<Row> <Row>
<Col sm={8} smOffset={2} xs={12}> <Col sm={8} smOffset={2} xs={12}>

View File

@ -16,10 +16,12 @@ function* fetchSessionUser() {
} }
try { try {
const { const {
data: { user = {}, result = '' } data: { user = {}, result = '', sessionMeta = {} }
} = yield call(getSessionUser); } = yield call(getSessionUser);
const appUser = user[result] || {}; const appUser = user[result] || {};
yield put(fetchUserComplete({ user: appUser, username: result })); yield put(
fetchUserComplete({ user: appUser, username: result, sessionMeta })
);
} catch (e) { } catch (e) {
yield put(fetchUserError(e)); yield put(fetchUserError(e));
} }

View File

@ -39,6 +39,7 @@ const initialState = {
userProfileFetchState: { userProfileFetchState: {
...defaultFetchState ...defaultFetchState
}, },
sessionMeta: {},
showDonationModal: false, showDonationModal: false,
isOnline: true isOnline: true
}; };
@ -158,6 +159,13 @@ export const userSelector = state => {
return state[ns].user[username] || {}; return state[ns].user[username] || {};
}; };
export const sessionMetaSelector = state => state[ns].sessionMeta;
export const activeDonationsSelector = state =>
// this default is mostly for development where there are likely no donators
// in the local db
// If we see this in production then things are getting weird
sessionMetaSelector(state).activeDonations || 4040;
function spreadThePayloadOnUser(state, payload) { function spreadThePayloadOnUser(state, payload) {
return { return {
...state, ...state,
@ -181,7 +189,10 @@ export const reducer = handleActions(
...state, ...state,
userProfileFetchState: { ...defaultFetchState } userProfileFetchState: { ...defaultFetchState }
}), }),
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({ [types.fetchUserComplete]: (
state,
{ payload: { user, username, sessionMeta } }
) => ({
...state, ...state,
user: { user: {
...state.user, ...state.user,
@ -193,6 +204,10 @@ export const reducer = handleActions(
complete: true, complete: true,
errored: false, errored: false,
error: null error: null
},
sessionMeta: {
...state.sessionMeta,
...sessionMeta
} }
}), }),
[types.fetchUserError]: (state, { payload }) => ({ [types.fetchUserError]: (state, { payload }) => ({