feat: prep for modern challenges (#15781)

* feat(seed): Add modern challenge

* chore(react): Use prop-types package

* feat: Initial refactor to redux-first-router

BREAKING CHANGE: Everything is different!

* feat: First rendering

* feat(routes): Challenges view render but failing

* fix(Challenges): Remove contain HOC

* fix(RFR): Add params selector

* fix(RFR): :en should be :lang

* fix: Update berks utils for redux

* fix(Map): Challenge link to arg

* fix(Map): Add trailing slash to map page

* fix(RFR): Use FCC Link

Use fcc Link to get around issue of lang being undefined

* fix(Router): Link to is required

* fix(app): Rely on RFR state for app lang

* chore(RFR): Remove unused RFR Link

* fix(RFR): Hydrate initial challenge using RFR and RO

* fix: Casing issue

* fix(RFR): Undefined links

* fix(RFR): Use onRoute<name> convention for route types

* feat(server/react): Add helpful redux logging/throwing

* fix(server/react): Strip out nonjson from state

This prevents thunks in routesMap from breaking serialization

* fix(RFR/Link): Should accept any renderable

* fix(RFR): Get redirects working

* fix(RFR): Redirects and not found's

* fix(Map): Move challenge onClick handler

* fix(Map): Allow Router.link to handle clicks after onClick

* fix(routes): Remove react-router-redux

* feat(Router): Add lang to all route actions by default

* fix(entities): Only fetch challenge if not already loaded

* fix(Files): Move files to own feature

* chore(Challenges): Remove vestigial hints logic

* fix(RFR): Update challenges on route challenges

* fix(code-storage): Should use events instead of commands

* fix(Map): ClickOnMap should not hold on to event

* chore(lint): Use eslint-config-freecodecamp

Closes #15938

* feat(Panes): Update panes on route instead of render

* fix(Panes): Store panesmap and update on fetchchallenges

* fix(Panes): Normalize panesmaps

* fix(Panes): Remove filter from createpanemap

* fix(Panes): Middleware on location meta object

* feat(Panes): Filter preview on nonhtml challenges

* build(babel): Add lodash babel plugin

* chore(lint): Lint js files

* fix(server/user-stats): Remove use of lodash chain

this interferes with babel-plugin-lodash

* feat(dev): Add remote redux devtools for ssr

* fix(Panes): Dispatch mount action

this is needed to trigger window/divider epics

* fix(Panes): Getpane to use new panesmap format

* fix(Panes): Always update panes after state

this lets the panes logic be affected by changes in state
This commit is contained in:
Berkeley Martinez
2017-11-09 17:10:30 -08:00
committed by Quincy Larson
parent 2e46e60557
commit dbecdc5618
141 changed files with 4984 additions and 3186 deletions

View File

@@ -73,8 +73,7 @@ function sendCertifiedEmail(
username,
isFrontEndCert,
isBackEndCert,
isDataVisCert,
challengeMap
isDataVisCert
},
send$
) {

99
server/boot/react.js vendored
View File

@@ -1,13 +1,19 @@
import React from 'react';
import { RouterContext } from 'react-router';
import debug from 'debug';
import { renderToString } from 'redux-epic';
import createApp from '../../common/app';
import provideStore from '../../common/app/provide-store';
import { renderToString } from 'react-dom/server';
import createMemoryHistory from 'history/createMemoryHistory';
import { NOT_FOUND } from 'redux-first-router';
import devtoolsEnhancer from 'remote-redux-devtools';
import {
loggerMiddleware,
errorThrowerMiddleware
} from '../utils/react.js';
import { createApp, provideStore, App } from '../../common/app';
import waitForEpics from '../../common/utils/wait-for-epics.js';
import { titleSelector } from '../../common/app/redux';
const log = debug('fcc:react-server');
const isDev = process.env.NODE_ENV !== 'production';
// add routes here as they slowly get reactified
// remove their individual controllers
@@ -21,6 +27,10 @@ const routes = [
const devRoutes = [];
const middlewares = [
isDev ? loggerMiddleware : null,
isDev ? errorThrowerMiddleware : null
].filter(Boolean);
export default function reactSubRouter(app) {
var router = app.loopback.Router();
@@ -48,55 +58,50 @@ export default function reactSubRouter(app) {
const serviceOptions = { req };
createApp({
serviceOptions,
location: req.originalUrl,
initialState: { app: { lang } }
middlewares,
enhancers: [
devtoolsEnhancer({ name: 'server' })
],
history: createMemoryHistory({ initialEntries: [ req.originalUrl ] }),
defaultStaet: { app: { lang } }
})
// if react-router does not find a route send down the chain
.filter(({ redirect, props }) => {
if (!props && redirect) {
log('react router found a redirect');
return res.redirect(redirect.pathname + redirect.search);
}
if (!props) {
log(`react tried to find ${req.path} but got 404`);
return next();
}
return !!props;
})
.flatMap(({ props, store, epic }) => {
log('render react markup and pre-fetch data');
return renderToString(
provideStore(React.createElement(RouterContext, props), store),
epic
)
.map(({ markup }) => ({ markup, store, epic }));
})
.filter(({ store, epic }) => {
const { delayedRedirect } = store.getState().app;
if (delayedRedirect) {
res.redirect(delayedRedirect);
epic.dispose();
.filter(({
location: {
type,
kind,
pathname
} = {}
}) => {
if (kind === 'redirect') {
log('react found a redirect');
res.redirect(pathname);
return false;
}
if (type === NOT_FOUND) {
log(`react tried to find ${req.path} but got 404`);
next();
return false;
}
return true;
})
.flatMap(function({ markup, store, epic }) {
.flatMap(({ store, epic }) => {
return waitForEpics(epic)
.map(() => renderToString(
provideStore(App, store)
))
.map((markup) => ({ markup, store, epic }));
})
.do(({ markup, store, epic }) => {
log('react markup rendered, data fetched');
const state = store.getState();
const { title } = state.app;
const title = titleSelector(state);
epic.dispose();
res.expose(state, 'data');
res.expose(req.flash(), 'flash');
return res.render$(
'layout-react',
{ markup, title }
);
res.expose(state, 'data', { isJSON: true });
res.expose(req.flash(), 'flash', { isJSON: true });
res.render('layout-react', { markup, title });
})
.doOnNext(markup => res.send(markup))
.subscribe(
() => log('html rendered and ready to send'),
next
);
.subscribe(() => log('html rendered and sent'), next);
}
}

15
server/utils/react.js vendored Normal file
View File

@@ -0,0 +1,15 @@
import debug from 'debug';
const log = debug('fcc:server:react:utils');
export const errorThrowerMiddleware = () => next => action => {
if (action.error) {
throw action.payload;
}
return next(action);
};
export const loggerMiddleware = () => next => action => {
log('action: \n', action);
return next(action);
};

View File

@@ -5,12 +5,9 @@ import { dayCount } from '../utils/date-utils';
const daysBetween = 1.5;
export function prepUniqueDays(cals, tz = 'UTC') {
return _(cals)
.map(ts => moment(ts).tz(tz).startOf('day').valueOf())
.uniq()
.sort()
.value();
return _.uniq(
_.map(cals, ts => moment(ts).tz(tz).startOf('day').valueOf())
).sort();
}
export function calcCurrentStreak(cals, tz = 'UTC') {