feat: Create and use dynamic active donation counts
This commit is contained in:
committed by
mrugesh mohapatra
parent
331ea3ebf9
commit
8fab33ba99
@ -15,57 +15,73 @@ import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
||||
const log = debugFactory('fcc:boot:user');
|
||||
const sendNonUserToHome = ifNoUserRedirectTo(homeLocation);
|
||||
|
||||
module.exports = function bootUser(app) {
|
||||
function bootUser(app) {
|
||||
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/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('/user/report-user/', ifNoUser401, createPostReportUserProfile(app));
|
||||
api.post('/user/report-user/', ifNoUser401, postReportUserProfile);
|
||||
|
||||
app.use('/internal', api);
|
||||
};
|
||||
}
|
||||
|
||||
function readSessionUser(req, res, next) {
|
||||
const queryUser = req.user;
|
||||
function createReadSessionUser(app) {
|
||||
const { Donation } = app.models;
|
||||
|
||||
const source =
|
||||
queryUser &&
|
||||
Observable.forkJoin(
|
||||
queryUser.getCompletedChallenges$(),
|
||||
queryUser.getPoints$(),
|
||||
(completedChallenges, progressTimestamps) => ({
|
||||
completedChallenges,
|
||||
progress: getProgress(progressTimestamps, queryUser.timezone)
|
||||
})
|
||||
);
|
||||
Observable.if(
|
||||
() => !queryUser,
|
||||
Observable.of({ user: {}, result: '' }),
|
||||
Observable.defer(() => source)
|
||||
.map(({ completedChallenges, progress }) => ({
|
||||
...queryUser.toJSON(),
|
||||
...progress,
|
||||
completedChallenges: completedChallenges.map(fixCompletedChallengeItem)
|
||||
}))
|
||||
.map(user => ({
|
||||
user: {
|
||||
[user.username]: {
|
||||
...pick(user, userPropsForSession),
|
||||
isEmailVerified: !!user.emailVerified,
|
||||
isGithub: !!user.githubProfile,
|
||||
isLinkedIn: !!user.linkedin,
|
||||
isTwitter: !!user.twitter,
|
||||
isWebsite: !!user.website,
|
||||
...normaliseUserFields(user)
|
||||
}
|
||||
},
|
||||
result: user.username
|
||||
}))
|
||||
).subscribe(user => res.json(user), next);
|
||||
return function getSessionUser(req, res, next) {
|
||||
const queryUser = req.user;
|
||||
|
||||
const source =
|
||||
queryUser &&
|
||||
Observable.forkJoin(
|
||||
queryUser.getCompletedChallenges$(),
|
||||
queryUser.getPoints$(),
|
||||
Donation.getCurrentActiveDonationCount$(),
|
||||
(completedChallenges, progressTimestamps, activeDonations) => ({
|
||||
activeDonations,
|
||||
completedChallenges,
|
||||
progress: getProgress(progressTimestamps, queryUser.timezone)
|
||||
})
|
||||
);
|
||||
Observable.if(
|
||||
() => !queryUser,
|
||||
Observable.of({ user: {}, result: '' }),
|
||||
Observable.defer(() => source)
|
||||
.map(({ activeDonations, completedChallenges, progress }) => ({
|
||||
user: {
|
||||
...queryUser.toJSON(),
|
||||
...progress,
|
||||
completedChallenges: completedChallenges.map(
|
||||
fixCompletedChallengeItem
|
||||
)
|
||||
},
|
||||
sessionMeta: { activeDonations }
|
||||
}))
|
||||
.map(({ user, sessionMeta }) => ({
|
||||
user: {
|
||||
[user.username]: {
|
||||
...pick(user, userPropsForSession),
|
||||
isEmailVerified: !!user.emailVerified,
|
||||
isGithub: !!user.githubProfile,
|
||||
isLinkedIn: !!user.linkedin,
|
||||
isTwitter: !!user.twitter,
|
||||
isWebsite: !!user.website,
|
||||
...normaliseUserFields(user)
|
||||
}
|
||||
},
|
||||
sessionMeta,
|
||||
result: user.username
|
||||
}))
|
||||
).subscribe(user => res.json(user), next);
|
||||
};
|
||||
}
|
||||
|
||||
function getAccount(req, res) {
|
||||
@ -241,3 +257,4 @@ function createPostReportUserProfile(app) {
|
||||
);
|
||||
};
|
||||
}
|
||||
export default bootUser;
|
||||
|
@ -2,14 +2,18 @@ import { Observable } from 'rx';
|
||||
|
||||
export default function(Donation) {
|
||||
Donation.on('dataSourceAttached', () => {
|
||||
Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation));
|
||||
Donation.findOne$ = Observable.fromNodeCallback(
|
||||
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$;
|
||||
}
|
||||
|
@ -6,9 +6,12 @@ import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
<FullWidthRow>
|
||||
@ -16,10 +19,10 @@ function Supporters({ isDonating }) {
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<div id='supporter-progress-wrapper'>
|
||||
<ProgressBar max={10000} now={400} />
|
||||
<ProgressBar max={10000} now={activeDonations} />
|
||||
<div id='progress-label-wrapper'>
|
||||
<span className='progress-label'>
|
||||
4000 supporters out of 10,000 goal
|
||||
{activeDonations} supporters out of 10,000 goal
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,13 +14,15 @@ import Supporters from '../components/Supporters';
|
||||
import {
|
||||
userSelector,
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector
|
||||
isSignedInSelector,
|
||||
activeDonationsSelector
|
||||
} from '../redux';
|
||||
import { randomQuote } from '../utils/get-words';
|
||||
|
||||
import './welcome.css';
|
||||
|
||||
const propTypes = {
|
||||
activedonations: PropTypes.number,
|
||||
fetchState: PropTypes.shape({
|
||||
pending: PropTypes.bool,
|
||||
complete: PropTypes.bool,
|
||||
@ -42,7 +44,13 @@ const mapStateToProps = createSelector(
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
userSelector,
|
||||
(fetchState, isSignedIn, user) => ({ fetchState, isSignedIn, user })
|
||||
activeDonationsSelector,
|
||||
(fetchState, isSignedIn, user, activeDonations) => ({
|
||||
activeDonations,
|
||||
fetchState,
|
||||
isSignedIn,
|
||||
user
|
||||
})
|
||||
);
|
||||
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
|
||||
|
||||
@ -57,7 +65,8 @@ function Welcome({
|
||||
completedCertCount = 0,
|
||||
completedLegacyCertCount: completedLegacyCerts = 0,
|
||||
isDonating
|
||||
}
|
||||
},
|
||||
activeDonations
|
||||
}) {
|
||||
if (pending && !complete) {
|
||||
return (
|
||||
@ -94,7 +103,10 @@ function Welcome({
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer />
|
||||
<Supporters isDonating={isDonating} />
|
||||
<Supporters
|
||||
activeDonations={activeDonations}
|
||||
isDonating={isDonating}
|
||||
/>
|
||||
<Spacer size={2} />
|
||||
<Row>
|
||||
<Col sm={8} smOffset={2} xs={12}>
|
||||
|
@ -16,10 +16,12 @@ function* fetchSessionUser() {
|
||||
}
|
||||
try {
|
||||
const {
|
||||
data: { user = {}, result = '' }
|
||||
data: { user = {}, result = '', sessionMeta = {} }
|
||||
} = yield call(getSessionUser);
|
||||
const appUser = user[result] || {};
|
||||
yield put(fetchUserComplete({ user: appUser, username: result }));
|
||||
yield put(
|
||||
fetchUserComplete({ user: appUser, username: result, sessionMeta })
|
||||
);
|
||||
} catch (e) {
|
||||
yield put(fetchUserError(e));
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ const initialState = {
|
||||
userProfileFetchState: {
|
||||
...defaultFetchState
|
||||
},
|
||||
sessionMeta: {},
|
||||
showDonationModal: false,
|
||||
isOnline: true
|
||||
};
|
||||
@ -158,6 +159,13 @@ export const userSelector = state => {
|
||||
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) {
|
||||
return {
|
||||
...state,
|
||||
@ -181,7 +189,10 @@ export const reducer = handleActions(
|
||||
...state,
|
||||
userProfileFetchState: { ...defaultFetchState }
|
||||
}),
|
||||
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({
|
||||
[types.fetchUserComplete]: (
|
||||
state,
|
||||
{ payload: { user, username, sessionMeta } }
|
||||
) => ({
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
@ -193,6 +204,10 @@ export const reducer = handleActions(
|
||||
complete: true,
|
||||
errored: false,
|
||||
error: null
|
||||
},
|
||||
sessionMeta: {
|
||||
...state.sessionMeta,
|
||||
...sessionMeta
|
||||
}
|
||||
}),
|
||||
[types.fetchUserError]: (state, { payload }) => ({
|
||||
|
Reference in New Issue
Block a user