feat(Profile): Reactify profile page (#16743)

* feat(Profile): Reactify profile page

* chore(tidyup): Remove console.log

* fix(timeline): Remove legacy challenges from Timeline render

* fix(style): Remove underline on a:hover
This commit is contained in:
Stuart Taylor
2018-02-19 20:32:14 +00:00
committed by Quincy Larson
parent 24ef69cf7a
commit 3131c55782
41 changed files with 1368 additions and 242 deletions

View File

@@ -21,7 +21,8 @@ const routes = [
'/challenges/*',
'/map',
'/settings',
'/settings/*'
'/settings/*',
'/:username'
];
const devRoutes = [];

View File

@@ -1,9 +1,7 @@
import dedent from 'dedent';
import moment from 'moment-timezone';
import { Observable } from 'rx';
import debugFactory from 'debug';
// import { curry } from 'lodash';
import emoji from 'node-emoji';
import { curry } from 'lodash';
import {
frontEndChallengeId,
@@ -24,18 +22,10 @@ import {
ifNotVerifiedRedirectToSettings
} from '../utils/middleware';
import { observeQuery } from '../utils/rx';
import {
prepUniqueDaysByHours,
calcCurrentStreak,
calcLongestStreak
} from '../utils/user-stats';
import supportedLanguages from '../../common/utils/supported-languages';
import { encodeFcc } from '../../common/utils/encode-decode.js';
import { getChallengeInfo, cachedMap } from '../utils/map';
const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
// const sendNonUserToMapWithMessage = curry(ifNoUserRedirectTo, 2)('/map');
const sendNonUserToMapWithMessage = curry(ifNoUserRedirectTo, 2)('/map');
const certIds = {
[certTypes.frontEnd]: frontEndChallengeId,
[certTypes.backEnd]: backEndChallengeId,
@@ -77,84 +67,10 @@ const certText = {
[certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
};
const dateFormat = 'MMM DD, YYYY';
function isAlgorithm(challenge) {
// test if name starts with hike/waypoint/basejump/zipline
// fix for bug that saved different challenges with incorrect
// challenge types
return !(/^(waypoint|hike|zipline|basejump)/i).test(challenge.name) &&
+challenge.challengeType === 5;
}
function isProject(challenge) {
return +challenge.challengeType === 3 ||
+challenge.challengeType === 4;
}
function getChallengeGroup(challenge) {
if (isProject(challenge)) {
return 'projects';
} else if (isAlgorithm(challenge)) {
return 'algorithms';
}
return 'challenges';
}
// buildDisplayChallenges(
// entities: { challenge: Object, challengeIdToName: Object },
// challengeMap: Object,
// tz: String
// ) => Observable[{
// algorithms: Array,
// projects: Array,
// challenges: Array
// }]
function buildDisplayChallenges(
{ challengeMap, challengeIdToName },
userChallengeMap = {},
timezone
) {
return Observable.from(Object.keys(userChallengeMap))
.map(challengeId => userChallengeMap[challengeId])
.map(userChallenge => {
const challengeId = userChallenge.id;
const challenge = challengeMap[ challengeIdToName[challengeId] ];
let finalChallenge = { ...userChallenge, ...challenge };
if (userChallenge.completedDate) {
finalChallenge.completedDate = moment
.tz(userChallenge.completedDate, timezone)
.format(dateFormat);
}
if (userChallenge.lastUpdated) {
finalChallenge.lastUpdated = moment
.tz(userChallenge.lastUpdated, timezone)
.format(dateFormat);
}
return finalChallenge;
})
.filter(({ challengeType }) => challengeType !== 6)
.groupBy(getChallengeGroup)
.flatMap(group$ => {
return group$.toArray().map(challenges => ({
[getChallengeGroup(challenges[0])]: challenges
}));
})
.reduce((output, group) => ({ ...output, ...group}), {})
.map(groups => ({
algorithms: groups.algorithms || [],
projects: groups.projects ? groups.projects.reverse() : [],
challenges: groups.challenges ? groups.challenges.reverse() : []
}));
}
module.exports = function(app) {
const router = app.loopback.Router();
const api = app.loopback.Router();
const { Email, User } = app.models;
const map$ = cachedMap(app.models);
function findUserByUsername$(username, fields) {
return observeQuery(
@@ -194,16 +110,15 @@ module.exports = function(app) {
showCert
);
router.get('/:username', showUserProfile);
router.get(
'/:username/report-user/',
sendNonUserToMap,
'/user/:username/report-user/',
sendNonUserToMapWithMessage('You must be signed in to report a user'),
ifNotVerifiedRedirectToSettings,
getReportUserProfile
);
api.post(
'/:username/report-user/',
'/user/:username/report-user/',
ifNoUser401,
postReportUserProfile
);
@@ -270,102 +185,6 @@ module.exports = function(app) {
});
}
function showUserProfile(req, res, next) {
const username = req.params.username.toLowerCase();
const { user } = req;
// timezone of signed-in account
// to show all date related components
// using signed-in account's timezone
// not of the profile she is viewing
const timezone = user && user.timezone ?
user.timezone :
'EST';
const query = {
where: { username },
include: 'pledge'
};
return User.findOne$(query)
.filter(userPortfolio => {
if (!userPortfolio) {
next();
}
return !!userPortfolio;
})
.flatMap(userPortfolio => {
userPortfolio = userPortfolio.toJSON();
const timestamps = userPortfolio
.progressTimestamps
.map(objOrNum => {
return typeof objOrNum === 'number' ?
objOrNum :
objOrNum.timestamp;
});
const uniqueHours = prepUniqueDaysByHours(timestamps, timezone);
userPortfolio.currentStreak = calcCurrentStreak(uniqueHours, timezone);
userPortfolio.longestStreak = calcLongestStreak(uniqueHours, timezone);
const calender = userPortfolio
.progressTimestamps
.map((objOrNum) => {
return typeof objOrNum === 'number' ?
objOrNum :
objOrNum.timestamp;
})
.filter((timestamp) => {
return !!timestamp;
})
.reduce((data, timeStamp) => {
data[(timeStamp / 1000)] = 1;
return data;
}, {});
if (userPortfolio.isCheater && !user) {
req.flash(
'danger',
dedent`
Upon review, this account has been flagged for academic
dishonesty. If youre the owner of this account contact
team@freecodecamp.org for details.
`
);
}
if (userPortfolio.bio) {
userPortfolio.bio = emoji.emojify(userPortfolio.bio);
}
return getChallengeInfo(map$)
.flatMap(challengeInfo => buildDisplayChallenges(
challengeInfo,
userPortfolio.challengeMap,
timezone
))
.map(displayChallenges => ({
...userPortfolio,
...displayChallenges,
title: 'Camper ' + userPortfolio.username + '\'s Code Portfolio',
calender,
github: userPortfolio.githubURL,
moment,
encodeFcc,
supportedLanguages
}));
})
.doOnNext(data => {
return res.render('account/show', data);
})
.subscribe(
() => {},
next
);
}
function showCert(req, res, next) {
let { username, cert } = req.params;
username = username.toLowerCase();

View File

@@ -2,21 +2,40 @@ import { Observable } from 'rx';
import _ from 'lodash';
import {
userPropsForSession,
normaliseUserFields
getProgress,
normaliseUserFields,
userPropsForSession
} from '../utils/publicUserProps';
export default function userServices() {
return {
name: 'user',
read: (req, resource, params, config, cb) => {
const { user } = req;
read: function readUserService(
req,
resource,
params,
config,
cb) {
const queryUser = req.user;
const source = queryUser && Observable.forkJoin(
queryUser.getChallengeMap$(),
queryUser.getPoints$(),
(challengeMap, progressTimestamps) => ({
challengeMap,
progress: getProgress(progressTimestamps, queryUser.timezone)
})
);
Observable.if(
() => !user,
() => !queryUser,
Observable.of({}),
Observable.defer(() => user.getChallengeMap$())
.map(challengeMap => ({ ...user.toJSON(), challengeMap }))
.map(user => ({
Observable.defer(() => source)
.map(({ challengeMap, progress }) => ({
...queryUser.toJSON(),
...progress,
challengeMap
}))
.map(
user => ({
entities: {
user: {
[user.username]: {

View File

@@ -1,6 +1,6 @@
import { isURL } from 'validator';
import { addPlaceholderImage } from '../utils';
import { addPlaceholderImage } from './';
import {
prepUniqueDaysByHours,
calcCurrentStreak,

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Advanced Frontend Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/advanced-front-end-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/advanced-front-end-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong APIs and Microservices Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/apis-and-microservices-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/apis-and-microservices-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Back End Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/back-end-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/back-end-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Data Visualization Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/data-visualization-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/data-visualization-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Front End Libraries Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-libraries-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/front-end-libraries-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Front End Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/front-end-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Full Stack Development Projects
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/full-stack-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/full-stack-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Information Security and Quality Assurance Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/information-security-and-quality-assurance-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/information-security-and-quality-assurance-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong JavaScript Algorithms and Data Structures Certificate
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/javascript-algorithms-and-data-structures-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/javascript-algorithms-and-data-structures-certification

View File

@@ -21,7 +21,7 @@ include styles
h1
strong Responsive Web Design Projects
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
footer
.row.signatures
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
@@ -29,4 +29,4 @@ include styles
strong Quincy Larson
p Executive Director, freeCodeCamp.org
.row
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/responsive-web-design-certification
p.verify Verify this certificate at: https://freecodecamp.org/c/#{username}/responsive-web-design-certification