From d327a5c36b03d019df7766b267f7d271abdab505 Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Thu, 29 Nov 2018 12:12:15 +0000 Subject: [PATCH] Feat: News in the client app (#34392) --- api-server/server/boot/news.js | 433 ++++++++++-------- api-server/server/datasources.production.js | 3 +- .../server/middlewares/error-handlers.js | 39 +- .../server/middlewares/error-reporter.js | 21 +- .../server/middlewares/jwt-authorization.js | 10 +- client/gatsby-config.js | 9 +- client/gatsby-node.js | 35 +- client/gatsby-ssr.js | 72 ++- client/package-lock.json | 101 +++- client/package.json | 3 +- .../fcc-create-nav-data/package-lock.json | 218 +++++++++ client/plugins/fcc-create-nav-data/yarn.lock | 170 ------- .../fcc-source-news/create-news-node.js | 26 ++ client/plugins/fcc-source-news/gatsby-node.js | 55 +++ .../plugins/fcc-source-news/package-lock.json | 80 ++++ client/plugins/fcc-source-news/package.json | 6 + client/src/__mocks__/news-article.js | 16 + .../integration/handled-error.test.js | 42 ++ .../__tests__/integration/news-slug.test.js | 33 ++ .../ShowDynamicNewsOrFourOhFour.js | 89 ++++ .../ShowProfileOrFourOhFour.js | 16 +- client/src/components/Flash/redux/index.js | 2 +- client/src/components/Header/header.css | 42 +- client/src/components/Header/index.js | 3 + client/src/components/Map/redux/index.js | 2 +- client/src/components/RedirectHome.js | 11 +- client/src/components/RedirectNews.js | 3 + client/src/components/createRedirect.js | 10 + client/src/pages/404.js | 11 +- client/src/pages/n.js | 15 + client/src/pages/news.js | 96 ++++ client/src/redux/error-saga.js | 25 + client/src/redux/index.js | 16 +- client/src/redux/rootReducer.js | 33 +- client/src/redux/rootSaga.js | 12 +- client/src/redux/settings/index.js | 2 +- .../src/templates/Challenges/redux/index.js | 2 +- .../src/templates/News/Featured/Featured.js | 62 +++ .../src/templates/News/Featured/featured.css | 33 ++ .../src/templates/News}/Featured/index.js | 0 .../src/templates/News}/NewsApp.js | 0 .../News/NewsReferalLinkHandler/index.js | 61 +++ .../News/ShowArticle}/components/Author.js | 2 +- .../src/templates/News/ShowArticle/index.js | 187 ++++++++ .../templates/News/ShowArticle/proptypes.js | 26 ++ .../News/ShowArticle/show-article.css | 34 ++ .../templates/News}/components/ArticleMeta.js | 0 .../templates/News}/components/BannerWide.js | 0 client/src/templates/News/redux/index.js | 67 +++ .../src/templates/News/redux/shortId-saga.js | 27 ++ client/src/utils/ajax.js | 24 + client/src/utils/handled-error.js | 84 ++++ client/src/utils/handled-error.test.js | 150 ++++++ client/src/utils/index.js | 16 + client/src/utils/report-error.js | 7 + client/src/utils/reportedErrorMessage.js | 6 + client/src/utils/utils.test.js | 45 ++ client/utils/gatsby/challengePageCreator.js | 114 +++++ client/utils/gatsby/guidePageCreator.js | 41 ++ client/utils/gatsby/index.js | 159 +------ client/utils/gatsby/newsPageCreator.js | 22 + client/utils/news.js | 18 + client/utils/news.test.js | 32 ++ config/secrets.js | 8 + lerna.json | 1 + news/client.js | 18 - news/components/Nav/LargeNav.js | 38 -- news/components/Nav/MediumNav.js | 42 -- news/components/Nav/Nav.js | 31 -- news/components/Nav/SmallNav.js | 44 -- news/components/Nav/components/NavLinks.js | 38 -- news/components/Nav/components/NavLogo.js | 41 -- news/components/Nav/index.js | 1 - news/components/Nav/navPropTypes.js | 3 - news/routes/EditArticle/EditArticle.js | 14 - news/routes/EditArticle/index.js | 1 - news/routes/Editor/index.js | 1 - news/routes/Featured/Featured.js | 166 ------- news/routes/Latest/Latest.js | 14 - news/routes/Latest/index.js | 1 - news/routes/Show/Show.js | 237 ---------- news/routes/Show/index.js | 1 - news/routes/index.js | 13 - news/utils/ajax.js | 27 -- sample.env | 1 + tools/scripts/seed/seedNewsArticles.js | 14 +- tools/scripts/start-develop.js | 3 +- 87 files changed, 2334 insertions(+), 1403 deletions(-) create mode 100644 client/plugins/fcc-create-nav-data/package-lock.json delete mode 100644 client/plugins/fcc-create-nav-data/yarn.lock create mode 100644 client/plugins/fcc-source-news/create-news-node.js create mode 100644 client/plugins/fcc-source-news/gatsby-node.js create mode 100644 client/plugins/fcc-source-news/package-lock.json create mode 100644 client/plugins/fcc-source-news/package.json create mode 100644 client/src/__mocks__/news-article.js create mode 100644 client/src/__tests__/integration/handled-error.test.js create mode 100644 client/src/__tests__/integration/news-slug.test.js create mode 100644 client/src/client-only-routes/ShowDynamicNewsOrFourOhFour.js create mode 100644 client/src/components/RedirectNews.js create mode 100644 client/src/components/createRedirect.js create mode 100644 client/src/pages/n.js create mode 100644 client/src/pages/news.js create mode 100644 client/src/redux/error-saga.js create mode 100644 client/src/templates/News/Featured/Featured.js create mode 100644 client/src/templates/News/Featured/featured.css rename {news/routes => client/src/templates/News}/Featured/index.js (100%) rename {news => client/src/templates/News}/NewsApp.js (100%) create mode 100644 client/src/templates/News/NewsReferalLinkHandler/index.js rename {news/routes/Show => client/src/templates/News/ShowArticle}/components/Author.js (94%) create mode 100644 client/src/templates/News/ShowArticle/index.js create mode 100644 client/src/templates/News/ShowArticle/proptypes.js create mode 100644 client/src/templates/News/ShowArticle/show-article.css rename {news => client/src/templates/News}/components/ArticleMeta.js (100%) rename {news => client/src/templates/News}/components/BannerWide.js (100%) create mode 100644 client/src/templates/News/redux/index.js create mode 100644 client/src/templates/News/redux/shortId-saga.js create mode 100644 client/src/utils/handled-error.js create mode 100644 client/src/utils/handled-error.test.js create mode 100644 client/src/utils/report-error.js create mode 100644 client/src/utils/reportedErrorMessage.js create mode 100644 client/src/utils/utils.test.js create mode 100644 client/utils/gatsby/challengePageCreator.js create mode 100644 client/utils/gatsby/guidePageCreator.js create mode 100644 client/utils/gatsby/newsPageCreator.js create mode 100644 client/utils/news.js create mode 100644 client/utils/news.test.js delete mode 100644 news/client.js delete mode 100644 news/components/Nav/LargeNav.js delete mode 100644 news/components/Nav/MediumNav.js delete mode 100644 news/components/Nav/Nav.js delete mode 100644 news/components/Nav/SmallNav.js delete mode 100644 news/components/Nav/components/NavLinks.js delete mode 100644 news/components/Nav/components/NavLogo.js delete mode 100644 news/components/Nav/index.js delete mode 100644 news/components/Nav/navPropTypes.js delete mode 100644 news/routes/EditArticle/EditArticle.js delete mode 100644 news/routes/EditArticle/index.js delete mode 100644 news/routes/Editor/index.js delete mode 100644 news/routes/Featured/Featured.js delete mode 100644 news/routes/Latest/Latest.js delete mode 100644 news/routes/Latest/index.js delete mode 100644 news/routes/Show/Show.js delete mode 100644 news/routes/Show/index.js delete mode 100644 news/routes/index.js delete mode 100644 news/utils/ajax.js diff --git a/api-server/server/boot/news.js b/api-server/server/boot/news.js index 8ee501075a..b7dc2e88b9 100644 --- a/api-server/server/boot/news.js +++ b/api-server/server/boot/news.js @@ -1,214 +1,259 @@ -// import React from 'react'; -// import { renderToString } from 'react-dom/server'; -// // import { StaticRouter } from 'react-router-dom'; -// import { has } from 'lodash'; +import { has, pick, isEmpty } from 'lodash'; import debug from 'debug'; +import { reportError } from '../middlewares/error-reporter'; -// import NewsApp from '../../news/NewsApp'; - -const routerLog = debug('fcc:boot:news:router'); -const apiLog = debug('fcc:boot:news:api'); +const log = debug('fcc:boot:news'); export default function newsBoot(app) { - // const router = app.loopback.Router(); - // const api = app.loopback.Router(); + const api = app.loopback.Router(); - // router.get('/n', (req, res) => res.redirect('/news')); - // router.get('/n/:shortId', createShortLinkHandler(app)); + api.get('/n/:shortId', createShortLinkHandler(app)); - // router.get('/news', serveNewsApp); - // router.get('/news/*', serveNewsApp); + api.post('/p', createPopularityHandler(app)); - // api.post('/p', createPopularityHandler(app)); - - // app.use(api); - // app.use(router); + app.use('/internal', api); } -// function serveNewsApp(req, res) { -// const context = {}; -// const markup = renderToString( -// -// -// -// ); -// if (context.url) { -// routerLog('redirect found in `renderToString`'); -// // 'client-side' routing hit on a redirect -// return res.redirect(context.url); -// } -// routerLog('news markup sending'); -// return res.render('layout-news', { title: 'News | freeCodeCamp', markup }); -// } +function createShortLinkHandler(app) { + const { Article } = app.models; -// function createShortLinkHandler(app) { -// const { Article } = app.models; + const referralHandler = createReferralHandler(app); -// const referralHandler = createRerralHandler(app); + return function shortLinkHandler(req, res, next) { + const { query, user } = req; + const { shortId } = req.params; -// return function shortLinkHandler(req, res, next) { -// const { query, user } = req; -// const { shortId } = req.params; + // We manually report the error here as it should not affect this request + referralHandler(query, shortId, !!user).catch(err => reportError(err)); -// referralHandler(query, shortId, !!user); + if (!shortId) { + return res.sendStatus(400); + } + return Article.findOne( + { + where: { + or: [{ shortId }, { slugPart: shortId }] + } + }, + (err, article) => { + if (err) { + next(err); + } + if (!article) { + return res.status(404).send('Could not find article by shortId'); + } + const { + slugPart, + shortId, + author: { username } + } = article; + const slug = `/news/${username}/${slugPart}--${shortId}`; + const articleData = { + ...pick(article, [ + 'author', + 'renderableContent', + 'firstPublishedDate', + 'viewCount', + 'title', + 'featureImage', + 'slugPart', + 'shortId', + 'meta' + ]), + slug + }; + return res.json(articleData); + } + ); + }; +} -// routerLog(req.origin); -// routerLog(query.refsource); -// if (!shortId) { -// return res.redirect('/news'); -// } -// routerLog('shortId', shortId); -// return Article.findOne( -// { -// where: { -// or: [{ shortId }, { slugPart: shortId }] -// } -// }, -// (err, article) => { -// if (err) { -// next(err); -// } -// if (!article) { -// return res.redirect('/news'); -// } -// const { -// slugPart, -// shortId, -// author: { username } -// } = article; -// const slug = `/news/${username}/${slugPart}--${shortId}`; -// return res.redirect(slug); -// } -// ); -// }; -// } +function createPopularityHandler(app) { + const { Article, Popularity } = app.models; -// function createPopularityHandler(app) { -// const { Article, Popularity } = app.models; + function findArticleByShortId(shortId) { + return new Promise((resolve, reject) => + Article.findOne({ where: { shortId } }, (err, article) => { + if (err) { + log('Error returned from Article.findOne(shortId)'); + return reject(err); + } + log('article found'); + return resolve(article); + }) + ); + } -// return function handlePopularityStats(req, res, next) { -// const { body, user } = req; + function findPopularityByShortId(shortId) { + return new Promise((resolve, reject) => + Popularity.findOne( + { where: { articleId: shortId } }, + (err, popularity) => { + if (err) { + log('Error returned from Popularity.findOne(shortId)'); + return reject(err); + } + log('popularity found'); + return resolve(popularity); + } + ) + ); + } -// if ( -// !has(body, 'event') || -// !has(body, 'timestamp') || -// !has(body, 'shortId') -// ) { -// console.warn('Popularity event recieved from client is malformed'); -// console.log(JSON.stringify(body, null, 2)); -// // sending 200 because the client shouldn't care for this -// return res.sendStatus(200); -// } -// res.sendStatus(200); -// const { shortId } = body; -// apiLog('shortId', shortId); -// const populartiyUpdate = { -// ...body, -// byAuthenticatedUser: !!user -// }; -// Popularity.findOne({ where: { articleId: shortId } }, (err, popularity) => { -// if (err) { -// apiLog(err); -// return next(err); -// } -// if (popularity) { -// return popularity.updateAttribute( -// 'events', -// [populartiyUpdate, ...popularity.events], -// err => { -// if (err) { -// apiLog(err); -// return next(err); -// } -// return apiLog('poplarity updated'); -// } -// ); -// } -// return Popularity.create( -// { -// events: [populartiyUpdate], -// articleId: shortId -// }, -// err => { -// if (err) { -// apiLog(err); -// return next(err); -// } -// return apiLog('poulartiy created'); -// } -// ); -// }); -// return body.event === 'view' -// ? Article.findOne({ where: { shortId } }, (err, article) => { -// if (err) { -// apiLog(err); -// next(err); -// } -// return article.updateAttributes( -// { viewCount: article.viewCount + 1 }, -// err => { -// if (err) { -// apiLog(err); -// return next(err); -// } -// return apiLog('article views updated'); -// } -// ); -// }) -// : null; -// }; -// } + function createPopularity(popularityUpdate, shortId) { + return new Promise((resolve, reject) => + Popularity.create( + { + events: [popularityUpdate], + articleId: shortId + }, + err => { + if (err) { + return reject(err); + } + log('poulartiy created'); + return resolve(); + } + ) + ); + } -// function createRerralHandler(app) { -// const { Popularity } = app.models; + function updatePopularity(popularity, popularityUpdate) { + return new Promise((resolve, reject) => + popularity.updateAttribute( + 'events', + [popularityUpdate, ...popularity.events], + err => { + if (err) { + log('Error returned from popularity.updateAttribute()'); + return reject(err); + } + log('poplarity updated'); + return resolve(); + } + ) + ); + } -// return function referralHandler(query, shortId, byAuthenticatedUser) { -// if (!query.refsource) { -// return null; -// } -// const eventUpdate = { -// event: `referral - ${query.refsource}`, -// timestamp: new Date(Date.now()), -// byAuthenticatedUser -// }; -// return Popularity.findOne( -// { where: { articleId: shortId } }, -// (err, popularity) => { -// if (err) { -// console.error( -// 'Failed finding a `Popularity` in a referral handler', -// err -// ); -// return null; -// } + function incrementArticleViews(article) { + return new Promise((resolve, reject) => + article.updateAttributes({ $inc: { viewCount: 1 } }, err => { + if (err) { + log(err); + return reject(err); + } + log('article views updated'); + return resolve(); + }) + ); + } -// if (popularity) { -// return popularity.updateAttribute( -// 'events', -// [eventUpdate, ...popularity.events], -// err => { -// if (err) { -// console.error( -// 'Failed in updating the `events` attribute of a `popularity`', -// err -// ); -// } -// } -// ); -// } -// return Popularity.create( -// { -// events: [eventUpdate], -// articleId: shortId -// }, -// err => { -// if (err) { -// return console.error('Failed creating a new `Popularity`', err); -// } -// return apiLog('poulartiy created'); -// } -// ); -// } -// ); -// }; -// } + return async function handlePopularityStats(req, res, next) { + const { body, user } = req; + + if ( + !has(body, 'event') || + !has(body, 'timestamp') || + !has(body, 'shortId') + ) { + console.warn('Popularity event recieved from client is malformed'); + console.log(JSON.stringify(body, null, 2)); + // sending 200 because the client shouldn't care for this + return res.sendStatus(200); + } + const { shortId } = body; + log('shortId', shortId); + + const articlePromise = findArticleByShortId(shortId); + const popularityPromise = findPopularityByShortId(shortId); + + const [article, popularity] = await Promise.all([ + articlePromise, + popularityPromise + ]).catch(err => { + log('find catch'); + return next(err); + }); + if (!article || isEmpty(article)) { + log('No article found to handle the populartity update'); + // sending 200 because the client shouldn't care for this + return res.sendStatus(200); + } + + const populartiyUpdate = { + ...body, + byAuthenticatedUser: !!user + }; + + const populartiyUpdateOrCreatePromise = isEmpty(popularity) + ? createPopularity(populartiyUpdate, shortId) + : updatePopularity(popularity, populartiyUpdate); + const maybeUpdateArticlePromise = + body.event === 'view' ? incrementArticleViews(article) : null; + return Promise.all([ + populartiyUpdateOrCreatePromise, + maybeUpdateArticlePromise + ]) + .then(() => res.sendStatus(200)) + .catch(err => { + log('updates catch'); + return next(err); + }); + }; +} + +function createReferralHandler(app) { + const { Popularity } = app.models; + + return function referralHandler(query, shortId, byAuthenticatedUser) { + if (!query.refsource) { + return Promise.resolve(); + } + const eventUpdate = { + event: `referral - ${query.refsource}`, + timestamp: new Date(Date.now()), + byAuthenticatedUser + }; + return new Promise((resolve, reject) => + Popularity.findOne( + { where: { articleId: shortId } }, + (err, popularity) => { + if (err) { + console.error( + 'Failed finding a `Popularity` in a referral handler', + err + ); + return reject(err); + } + + if (popularity) { + return popularity.updateAttribute( + 'events', + [eventUpdate, ...popularity.events], + err => { + if (err) { + return reject(err); + } + log('populartiy updated'); + return resolve(); + } + ); + } + return Popularity.create( + { + events: [eventUpdate], + articleId: shortId + }, + err => { + if (err) { + return reject(err); + } + log('poulartiy created'); + return resolve(); + } + ); + } + ) + ); + }; +} diff --git a/api-server/server/datasources.production.js b/api-server/server/datasources.production.js index b70b97e731..57cf572238 100644 --- a/api-server/server/datasources.production.js +++ b/api-server/server/datasources.production.js @@ -5,7 +5,8 @@ module.exports = { connector: 'mongodb', connectionTimeout: 10000, url: secrets.db, - useNewUrlParser: true + useNewUrlParser: true, + allowExtendedOperators: true }, mail: { connector: 'mail', diff --git a/api-server/server/middlewares/error-handlers.js b/api-server/server/middlewares/error-handlers.js index 14df134e18..5f3744869c 100644 --- a/api-server/server/middlewares/error-handlers.js +++ b/api-server/server/middlewares/error-handlers.js @@ -8,47 +8,10 @@ import { unwrapHandledError } from '../utils/create-handled-error.js'; const isDev = process.env.NODE_ENV !== 'production'; -// const toString = Object.prototype.toString; -// is full error or just trace -// _.toString(new Error('foo')) => "Error: foo -// Object.prototype.toString.call(new Error('foo')) => "[object Error]" -// const isInspect = val => !val.stack && _.toString(val) === toString.call(val); -// const stringifyErr = val => { -// if (val.stack) { -// return String(val.stack); -// } - -// const str = String(val); - -// return isInspect(val) ? -// inspect(val) : -// str; -// }; - -// const createStackHtml = _.flow( -// _.cond([ -// [isInspect, err => [err]], -// // may be stack or just err.msg -// [_.stubTrue, _.flow(stringifyErr, _.split('\n'), _.tail) ] -// ]), -// _.map(_.escape), -// _.map(line => `
  • ${line}`), -// _.join('') -// ); - -// const createErrorTitle = _.cond([ -// [ -// _.negate(isInspect), -// _.flow(stringifyErr, _.split('\n'), _.head, _.defaultTo('Error')) -// ], -// [_.stubTrue, _.constant('Error')] -// ]); - export default function prodErrorHandler() { // error handling in production. - // disabling eslint due to express parity rules for error handlers + // eslint-disable-next-line no-unused-vars return function(err, req, res, next) { - // eslint-disable-line const handled = unwrapHandledError(err); // respect handled error status let status = handled.status || err.status || res.statusCode; diff --git a/api-server/server/middlewares/error-reporter.js b/api-server/server/middlewares/error-reporter.js index 196b2dbe5d..514d3227c5 100644 --- a/api-server/server/middlewares/error-reporter.js +++ b/api-server/server/middlewares/error-reporter.js @@ -5,20 +5,30 @@ import { unwrapHandledError } from '../utils/create-handled-error.js'; -const { ROLLBAR_APP_ID } = process.env; +import { rollbar } from '../../../config/secrets'; -const rollbar = new Rollbar(ROLLBAR_APP_ID); +const { appId } = rollbar; +const reporter = new Rollbar(appId); const log = debug('fcc:middlewares:error-reporter'); -const errTemplate = ({message, ...restError}, req) => ` +const errTemplate = (error, req) => { + const { message, stack } = error; + return ` Time: ${new Date(Date.now()).toISOString()} Error: ${message} Is authenticated user: ${!!req.user} Route: ${JSON.stringify(req.route, null, 2)} +Stack: ${stack} -${JSON.stringify(restError, null, 2)} +// raw +${JSON.stringify(error, null, 2)} `; +}; + +export function reportError(err) { + return reporter.error(err.message, err); +} export default function errrorReporter() { if (process.env.NODE_ENV !== 'production' && process.env.ERROR_REPORTER) { @@ -44,6 +54,7 @@ export default function errrorReporter() { // logging the error provides us with more information, // i.e isAuthenticatedUser, req.route console.error(errTemplate(err, req)); - return rollbar.error(err.message, err); + reportError(err); + return next(err); }; } diff --git a/api-server/server/middlewares/jwt-authorization.js b/api-server/server/middlewares/jwt-authorization.js index 78250a559e..4d7043fd87 100644 --- a/api-server/server/middlewares/jwt-authorization.js +++ b/api-server/server/middlewares/jwt-authorization.js @@ -6,9 +6,17 @@ import { homeLocation } from '../../../config/env'; import { wrapHandledError } from '../utils/create-handled-error'; +// We need to tunnel through a proxy path set up within +// the gatsby app, at this time, that path is /internal +const whiteListRE = new RegExp([ + '^/internal/n/', + '^/internal/p\??' +].join('|')); + + export default () => function authorizeByJWT(req, res, next) { const path = req.path.split('/')[1]; - if (/^external$|^internal$/.test(path)) { + if (/^external$|^internal$/.test(path) && !whiteListRE.test(req.path)) { const cookie = req.signedCookies && req.signedCookies['jwt_access_token'] || req.cookie && req.cookie['jwt_access_token']; if (!cookie) { diff --git a/client/gatsby-config.js b/client/gatsby-config.js index d0236b4018..a0c262e5c1 100644 --- a/client/gatsby-config.js +++ b/client/gatsby-config.js @@ -32,7 +32,8 @@ module.exports = { '/certification/*', '/unsubscribed/*', '/user/*', - '/settings/*' + '/settings/*', + '/n/*' ] } }, @@ -45,6 +46,12 @@ module.exports = { curriculumPath: localeChallengesRootDir } }, + { + resolve: 'fcc-source-news', + options: { + maximumStaticRenderCount: 100 + } + }, { resolve: 'gatsby-source-filesystem', options: { diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 8dbc1d649e..e3a4f80e66 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -8,8 +8,10 @@ const { createChallengePages, createBlockIntroPages, createSuperBlockIntroPages, - createGuideArticlePages + createGuideArticlePages, + createNewsArticle } = require('./utils/gatsby'); +const { createArticleSlug } = require('./utils/news'); const createByIdentityMap = { guideMarkdown: createGuideArticlePages, @@ -35,6 +37,15 @@ exports.onCreateNode = function onCreateNode({ node, actions, getNode }) { createNodeField({ node, name: 'slug', value: slug }); } } + if (node.internal.type === 'NewsArticleNode') { + const { + author: { username }, + slugPart, + shortId + } = node; + const slug = createArticleSlug({ username, shortId, slugPart }); + createNodeField({ node, name: 'slug', value: slug }); + } }; exports.createPages = function createPages({ graphql, actions }) { @@ -86,6 +97,19 @@ exports.createPages = function createPages({ graphql, actions }) { } } } + allNewsArticleNode( + sort: { fields: firstPublishedDate, order: DESC } + ) { + edges { + node { + id + shortId + fields { + slug + } + } + } + } } `).then(result => { if (result.errors) { @@ -126,6 +150,11 @@ exports.createPages = function createPages({ graphql, actions }) { return null; }); + // Create news article pages + result.data.allNewsArticleNode.edges.forEach( + createNewsArticle(createPage) + ); + return null; }) ); @@ -161,7 +190,9 @@ exports.onCreateWebpackConfig = ({ stage, rules, plugins, actions }) => { HOME_PATH: JSON.stringify( process.env.HOME_PATH || 'http://localhost:3000' ), - STRIPE_PUBLIC_KEY: JSON.stringify(process.env.STRIPE_PUBLIC_KEY || '') + STRIPE_PUBLIC_KEY: JSON.stringify(process.env.STRIPE_PUBLIC_KEY || ''), + ROLLBAR_CLIENT_ID: JSON.stringify(process.env.ROLLBAR_CLIENT_ID || ''), + ENVIRONMENT: JSON.stringify(process.env.NODE_ENV || 'development') }), new RmServiceWorkerPlugin() ] diff --git a/client/gatsby-ssr.js b/client/gatsby-ssr.js index 87be57ed92..fea4c0b15d 100644 --- a/client/gatsby-ssr.js +++ b/client/gatsby-ssr.js @@ -1,3 +1,4 @@ +/* global ROLLBAR_CLIENT_ID ENVIRONMENT */ import React from 'react'; import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; @@ -5,14 +6,14 @@ import { Provider } from 'react-redux'; import headComponents from './src/head'; import { createStore } from './src/redux/createStore'; -import GuideNavigationContextProvider from './src/contexts/GuideNavigationContext'; +import GuideNavContextProvider from './src/contexts/GuideNavigationContext'; const store = createStore(); export const wrapRootElement = ({ element }) => { return ( - {element} + {element} ); }; @@ -23,28 +24,57 @@ wrapRootElement.propTypes = { export const onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => { setHeadComponents([...headComponents]); - setPostBodyComponents([ -