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:
committed by
Quincy Larson
parent
2e46e60557
commit
dbecdc5618
@@ -73,8 +73,7 @@ function sendCertifiedEmail(
|
||||
username,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isDataVisCert,
|
||||
challengeMap
|
||||
isDataVisCert
|
||||
},
|
||||
send$
|
||||
) {
|
||||
|
99
server/boot/react.js
vendored
99
server/boot/react.js
vendored
@@ -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
15
server/utils/react.js
vendored
Normal 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);
|
||||
};
|
@@ -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') {
|
||||
|
Reference in New Issue
Block a user