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:
committed by
Quincy Larson
parent
24ef69cf7a
commit
3131c55782
3
server/boot/react.js
vendored
3
server/boot/react.js
vendored
@@ -21,7 +21,8 @@ const routes = [
|
||||
'/challenges/*',
|
||||
'/map',
|
||||
'/settings',
|
||||
'/settings/*'
|
||||
'/settings/*',
|
||||
'/:username'
|
||||
];
|
||||
|
||||
const devRoutes = [];
|
||||
|
@@ -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 you’re 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();
|
||||
|
@@ -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]: {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { isURL } from 'validator';
|
||||
|
||||
import { addPlaceholderImage } from '../utils';
|
||||
import { addPlaceholderImage } from './';
|
||||
import {
|
||||
prepUniqueDaysByHours,
|
||||
calcCurrentStreak,
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user