chore(learn): Merge learn in to the client app
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@ -13,4 +13,6 @@ node_modules
|
|||||||
*.gz
|
*.gz
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
curriculum/dist/
|
@ -1,12 +1,16 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { buildChallenges$ } = require('./utils/buildChallenges');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
siteMetadata: {
|
siteMetadata: {
|
||||||
title: 'freeCodeCamp',
|
title: 'freeCodeCamp',
|
||||||
siteUrl: 'https://www.freecodecamp.org'
|
siteUrl: 'https://www.freecodecamp.org'
|
||||||
},
|
},
|
||||||
proxy: {
|
// proxy: {
|
||||||
prefix: '/internal',
|
// prefix: '/internal',
|
||||||
url: 'http://localhost:3000'
|
// url: 'http://localhost:3000'
|
||||||
},
|
// },
|
||||||
plugins: [
|
plugins: [
|
||||||
'gatsby-plugin-react-helmet',
|
'gatsby-plugin-react-helmet',
|
||||||
{
|
{
|
||||||
@ -20,16 +24,64 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
resolve: 'fcc-source-challenges',
|
||||||
|
options: {
|
||||||
|
name: 'challenges',
|
||||||
|
source: buildChallenges$
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolve: 'gatsby-source-filesystem',
|
||||||
|
options: {
|
||||||
|
name: 'introductions',
|
||||||
|
path: path.resolve(__dirname, './src/introductions')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resolve: 'gatsby-transformer-remark',
|
||||||
|
options: {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
resolve: 'gatsby-remark-prismjs',
|
||||||
|
options: {
|
||||||
|
// Class prefix for <pre> tags containing syntax highlighting;
|
||||||
|
// defaults to 'language-' (eg <pre class="language-js">).
|
||||||
|
// If your site loads Prism into the browser at runtime,
|
||||||
|
// (eg for use with libraries like react-live),
|
||||||
|
// you may use this to prevent Prism from re-processing syntax.
|
||||||
|
// This is an uncommon use-case though;
|
||||||
|
// If you're unsure, it's best to use the default value.
|
||||||
|
classPrefix: 'language-',
|
||||||
|
// This is used to allow setting a language for inline code
|
||||||
|
// (i.e. single backticks) by creating a separator.
|
||||||
|
// This separator is a string and will do no white-space
|
||||||
|
// stripping.
|
||||||
|
// A suggested value for English speakers is the non-ascii
|
||||||
|
// character '›'.
|
||||||
|
inlineCodeMarker: null,
|
||||||
|
// This lets you set up language aliases. For example,
|
||||||
|
// setting this to '{ sh: "bash" }' will let you use
|
||||||
|
// the language "sh" which will highlight using the
|
||||||
|
// bash highlighter.
|
||||||
|
aliases: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
resolve: 'gatsby-plugin-manifest',
|
resolve: 'gatsby-plugin-manifest',
|
||||||
options: {
|
options: {
|
||||||
name: 'freeCodeCamp',
|
name: 'freeCodeCamp',
|
||||||
|
/* eslint-disable camelcase */
|
||||||
short_name: 'fCC',
|
short_name: 'fCC',
|
||||||
start_url: '/',
|
start_url: '/',
|
||||||
background_color: '#fff',
|
|
||||||
theme_color: '#006400',
|
theme_color: '#006400',
|
||||||
|
background_color: '#fff',
|
||||||
|
/* eslint-enable camelcase */
|
||||||
display: 'minimal-ui',
|
display: 'minimal-ui',
|
||||||
icon: 'src/images/square_puck.png' // This path is relative to the root of the site.
|
icon: 'src/images/square_puck.png'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'gatsby-plugin-sitemap'
|
'gatsby-plugin-sitemap'
|
||||||
|
@ -1,7 +1,178 @@
|
|||||||
/**
|
require('dotenv').config();
|
||||||
* Implement Gatsby's Node APIs in this file.
|
|
||||||
*
|
|
||||||
* See: https://www.gatsbyjs.org/docs/node-apis/
|
|
||||||
*/
|
|
||||||
|
|
||||||
// You can delete this file if you're not using it
|
const { dasherize } = require('./utils');
|
||||||
|
const { blockNameify } = require('./utils/blockNameify');
|
||||||
|
const { createChallengePages, createIntroPages } = require('./utils/gatsby');
|
||||||
|
|
||||||
|
exports.onCreateNode = function onCreateNode({ node, actions }) {
|
||||||
|
const { createNodeField } = actions;
|
||||||
|
if (node.internal.type === 'ChallengeNode') {
|
||||||
|
const { tests = [], block, title, superBlock } = node;
|
||||||
|
|
||||||
|
const slug = `/learn/${dasherize(superBlock)}/${dasherize(
|
||||||
|
block
|
||||||
|
)}/${dasherize(title)}`;
|
||||||
|
createNodeField({ node, name: 'slug', value: slug });
|
||||||
|
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
|
||||||
|
createNodeField({ node, name: 'tests', value: tests });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.internal.type === 'MarkdownRemark') {
|
||||||
|
// console.log(node);
|
||||||
|
const {
|
||||||
|
frontmatter: { block, superBlock }
|
||||||
|
} = node;
|
||||||
|
|
||||||
|
let slug = `/${dasherize(superBlock)}`;
|
||||||
|
|
||||||
|
// Without this condition the slug for superblocks ends up as something like
|
||||||
|
// "/apis-and-microservice/undefined" and what we want instead is just
|
||||||
|
// "/apis-and-microservice"
|
||||||
|
if (typeof block !== 'undefined') {
|
||||||
|
slug = slug + `/${dasherize(block)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
createNodeField({ node, name: 'slug', value: slug });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.createPages = ({ graphql, actions }) => {
|
||||||
|
const { createPage } = actions;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Query for all markdown 'nodes' and for the slug we previously created.
|
||||||
|
resolve(
|
||||||
|
graphql(`
|
||||||
|
{
|
||||||
|
allChallengeNode(sort: { fields: [superOrder, order, suborder] }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
block
|
||||||
|
challengeType
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
id
|
||||||
|
order
|
||||||
|
required {
|
||||||
|
link
|
||||||
|
raw
|
||||||
|
src
|
||||||
|
}
|
||||||
|
suborder
|
||||||
|
superBlock
|
||||||
|
superOrder
|
||||||
|
template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allMarkdownRemark {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
fields {
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
frontmatter {
|
||||||
|
block
|
||||||
|
superBlock
|
||||||
|
title
|
||||||
|
}
|
||||||
|
html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`).then(result => {
|
||||||
|
if (result.errors) {
|
||||||
|
console.log(result.errors);
|
||||||
|
reject(result.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create challenge pages.
|
||||||
|
result.data.allChallengeNode.edges.forEach(
|
||||||
|
createChallengePages(createPage)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create intro pages
|
||||||
|
result.data.allMarkdownRemark.edges.forEach(
|
||||||
|
createIntroPages(createPage)
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const RmServiceWorkerPlugin = require('webpack-remove-serviceworker-plugin');
|
||||||
|
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
|
||||||
|
|
||||||
|
exports.onCreateWebpackConfig = ({ stage, rules, plugins, actions }) => {
|
||||||
|
actions.setWebpackConfig({
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
rules.js({
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
exclude: modulePath => {
|
||||||
|
return (
|
||||||
|
(/node_modules/).test(modulePath) &&
|
||||||
|
!(/(ansi-styles|chalk|strict-uri-encode|react-freecodecamp-search)/).test(
|
||||||
|
modulePath
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* eslint-enable max-len*/
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fs: 'empty'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
plugins.define({
|
||||||
|
HOME_PATH: JSON.stringify(
|
||||||
|
process.env.HOME_PATH || 'http://localhost:3000'
|
||||||
|
),
|
||||||
|
STRIPE_PUBLIC_KEY: JSON.stringify(process.env.STRIPE_PUBLIC_KEY || '')
|
||||||
|
}),
|
||||||
|
new RmServiceWorkerPlugin()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
if (stage !== 'build-html') {
|
||||||
|
actions.setWebpackConfig({
|
||||||
|
plugins: [new MonacoWebpackPlugin()]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (stage === 'build-html') {
|
||||||
|
actions.setWebpackConfig({
|
||||||
|
plugins: [
|
||||||
|
plugins.normalModuleReplacement(
|
||||||
|
/react-monaco-editor/,
|
||||||
|
require.resolve('./src/__mocks__/monacoEditorMock.js')
|
||||||
|
)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.onCreateBabelConfig = ({ actions }) => {
|
||||||
|
actions.setBabelPlugin({
|
||||||
|
name: '@babel/plugin-proposal-function-bind'
|
||||||
|
});
|
||||||
|
actions.setBabelPlugin({
|
||||||
|
name: '@babel/plugin-proposal-export-default-from'
|
||||||
|
});
|
||||||
|
actions.setBabelPlugin({
|
||||||
|
name: 'babel-plugin-transform-imports',
|
||||||
|
options: {
|
||||||
|
'@freecodecamp/react-bootstrap': {
|
||||||
|
transform: '@freecodecamp/react-bootstrap/lib/${member}',
|
||||||
|
preventFullImport: true
|
||||||
|
},
|
||||||
|
lodash: {
|
||||||
|
transform: 'lodash/${member}',
|
||||||
|
preventFullImport: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import headComponents from './src/head';
|
||||||
import { createStore } from './src/redux/createStore';
|
import { createStore } from './src/redux/createStore';
|
||||||
|
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
@ -13,3 +14,31 @@ export const wrapRootElement = ({ element }) => {
|
|||||||
wrapRootElement.propTypes = {
|
wrapRootElement.propTypes = {
|
||||||
element: PropTypes.any
|
element: PropTypes.any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => {
|
||||||
|
setHeadComponents([...headComponents]);
|
||||||
|
setPostBodyComponents([
|
||||||
|
<script
|
||||||
|
async={true}
|
||||||
|
key='chai-CDN'
|
||||||
|
src='https://cdnjs.cloudflare.com/ajax/libs/chai/4.1.2/chai.min.js'
|
||||||
|
/>,
|
||||||
|
<script
|
||||||
|
async={true}
|
||||||
|
key='gtag-script'
|
||||||
|
src='https://www.googletagmanager.com/gtag/js?id=AW-795617839'
|
||||||
|
/>,
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', 'AW-795617839');
|
||||||
|
`
|
||||||
|
}}
|
||||||
|
key='gtag-dataLayer'
|
||||||
|
/>,
|
||||||
|
<script async={true} id='stripe-js' src='https://js.stripe.com/v3/' />
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
@ -7,8 +7,8 @@ const babelOptions = {
|
|||||||
'@babel/plugin-proposal-function-bind',
|
'@babel/plugin-proposal-function-bind',
|
||||||
[
|
[
|
||||||
'transform-imports', {
|
'transform-imports', {
|
||||||
'react-bootstrap': {
|
'@freecodecamp/react-bootstrap': {
|
||||||
transform: 'react-bootstrap/lib/${member}',
|
transform: '@freecodecamp/react-bootstrap/lib/${member}',
|
||||||
preventFullImport: true
|
preventFullImport: true
|
||||||
},
|
},
|
||||||
lodash: {
|
lodash: {
|
10081
client/package-lock.json
generated
10081
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,58 +4,88 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||||
|
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||||
|
"@babel/standalone": "^7.1.0",
|
||||||
"@fortawesome/fontawesome": "^1.1.8",
|
"@fortawesome/fontawesome": "^1.1.8",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.2",
|
"@fortawesome/fontawesome-svg-core": "^1.2.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.2.0",
|
"@fortawesome/free-brands-svg-icons": "^5.2.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.2.0",
|
"@fortawesome/free-regular-svg-icons": "^5.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.2.0",
|
"@fortawesome/free-solid-svg-icons": "^5.2.0",
|
||||||
"@fortawesome/react-fontawesome": "0.0.20",
|
"@fortawesome/react-fontawesome": "0.0.20",
|
||||||
|
"@freecodecamp/curriculum": "^3.2.1",
|
||||||
"@freecodecamp/react-bootstrap": "^0.32.3",
|
"@freecodecamp/react-bootstrap": "^0.32.3",
|
||||||
"@reach/router": "^1.1.1",
|
"@reach/router": "^1.1.1",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
|
"browser-cookies": "^1.2.0",
|
||||||
|
"chai": "^4.2.0",
|
||||||
|
"enzyme": "^3.6.0",
|
||||||
|
"enzyme-adapter-react-16": "^1.5.0",
|
||||||
|
"fetchr": "^0.5.37",
|
||||||
"gatsby": "^2.0.0-rc.15",
|
"gatsby": "^2.0.0-rc.15",
|
||||||
"gatsby-link": "^2.0.0-rc.2",
|
"gatsby-link": "^2.0.0-rc.2",
|
||||||
"gatsby-plugin-create-client-paths": "^2.0.0-rc.1",
|
"gatsby-plugin-create-client-paths": "^2.0.0-rc.1",
|
||||||
"gatsby-plugin-manifest": "next",
|
"gatsby-plugin-manifest": "next",
|
||||||
"gatsby-plugin-react-helmet": "^3.0.0-rc.1",
|
"gatsby-plugin-react-helmet": "^3.0.0-rc.1",
|
||||||
"gatsby-plugin-sitemap": "^2.0.0-rc.1",
|
"gatsby-plugin-sitemap": "^2.0.0-rc.1",
|
||||||
|
"gatsby-remark-prismjs": "^3.0.0-rc.2",
|
||||||
|
"gatsby-source-filesystem": "^2.0.1-rc.6",
|
||||||
|
"gatsby-transformer-remark": "^2.1.1-rc.5",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
|
"loop-protect": "^2.1.6",
|
||||||
|
"monaco-editor-webpack-plugin": "^1.5.4",
|
||||||
"nanoid": "^1.2.2",
|
"nanoid": "^1.2.2",
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.15.0",
|
||||||
"query-string": "^6.1.0",
|
"query-string": "^6.1.0",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.2",
|
"react-dom": "^16.4.2",
|
||||||
"react-freecodecamp-search": "^2.0.2",
|
"react-freecodecamp-search": "^2.0.2",
|
||||||
|
"react-ga": "^2.5.3",
|
||||||
"react-helmet": "^5.2.0",
|
"react-helmet": "^5.2.0",
|
||||||
"react-media": "^1.8.0",
|
"react-media": "^1.8.0",
|
||||||
|
"react-monaco-editor": "^0.18.0",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
|
"react-reflex": "^2.2.9",
|
||||||
"react-spinkit": "^3.0.0",
|
"react-spinkit": "^3.0.0",
|
||||||
|
"react-stripe-elements": "^2.0.1",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-actions": "^2.6.1",
|
"redux-actions": "^2.6.1",
|
||||||
|
"redux-devtools-extension": "^2.13.5",
|
||||||
|
"redux-form": "^5.3.6",
|
||||||
|
"redux-observable": "^1.0.0",
|
||||||
"redux-saga": "^0.16.0",
|
"redux-saga": "^0.16.0",
|
||||||
"reselect": "^3.0.1",
|
"reselect": "^3.0.1",
|
||||||
|
"rxjs": "^6.3.3",
|
||||||
|
"sinon": "^6.3.4",
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"validator": "^10.7.0"
|
"validator": "^10.7.0",
|
||||||
|
"webpack-remove-serviceworker-plugin": "^1.0.0"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"gatsby"
|
"gatsby"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ../config/ensure-env.js && gatsby build",
|
"prebuild": "npm run build:frame-runner && node ../tools/scripts/ensure-env.js",
|
||||||
|
"build": "gatsby build",
|
||||||
|
"build:frame-runner": "webpack --env.production --config ./webpack-frame-runner.js",
|
||||||
|
"predevelop": "npm run prebuild",
|
||||||
"develop": "gatsby develop",
|
"develop": "gatsby develop",
|
||||||
"format:gatsby": "prettier-eslint --write --trailing-comma none --single-quote './gatsby-*.js'",
|
"format:gatsby": "prettier-eslint --write --trailing-comma none --single-quote './gatsby-*.js'",
|
||||||
"format:src": "prettier-eslint --write --trailing-comma none --single-quote './src/**/*.js'",
|
"format:src": "prettier-eslint --write --trailing-comma none --single-quote './src/**/*.js'",
|
||||||
"format:utils": "prettier-eslint --write --trailing-comma none --single-quote './utils/**/*.js'",
|
"format:utils": "prettier-eslint --write --trailing-comma none --single-quote './utils/**/*.js'",
|
||||||
"format": "npm run format:gatsby && npm run format:src && npm run format:utils",
|
"format": "npm run format:gatsby && npm run format:src && npm run format:utils",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-plugin-transform-imports": "^1.5.0",
|
"babel-plugin-transform-imports": "^1.5.0",
|
||||||
"eslint": "^5.5.0",
|
"eslint": "^5.5.0",
|
||||||
"eslint-config-freecodecamp": "^1.1.1",
|
"eslint-config-freecodecamp": "^1.1.1",
|
||||||
|
"jest": "^23.6.0",
|
||||||
"prettier": "^1.14.2",
|
"prettier": "^1.14.2",
|
||||||
"prettier-eslint-cli": "^4.7.1"
|
"prettier-eslint-cli": "^4.7.1",
|
||||||
|
"react-test-renderer": "^16.5.2",
|
||||||
|
"webpack-cli": "^3.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
27
client/plugins/fcc-source-challenges/gatsby-node.js
Normal file
27
client/plugins/fcc-source-challenges/gatsby-node.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const { createChallengeNodes } = require('./create-Challenge-nodes');
|
||||||
|
|
||||||
|
exports.sourceNodes = ({ actions, reporter }, pluginOptions) => {
|
||||||
|
if (typeof pluginOptions.source !== 'function') {
|
||||||
|
reporter.panic(`
|
||||||
|
"source" is a required option for fcc-source-challenges. It must be a function
|
||||||
|
that delivers challenge files to the plugin
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
// TODO: Add live seed updates
|
||||||
|
const { createNode } = actions;
|
||||||
|
|
||||||
|
const { source } = pluginOptions;
|
||||||
|
return source().subscribe(
|
||||||
|
challenges =>
|
||||||
|
challenges
|
||||||
|
.filter(challenge => challenge.superBlock !== 'Certificates')
|
||||||
|
.map(challenge => createChallengeNodes(challenge, reporter))
|
||||||
|
.map(node => createNode(node)),
|
||||||
|
e =>
|
||||||
|
reporter.panic(`fcc-sourec-challenges
|
||||||
|
|
||||||
|
${e.message}
|
||||||
|
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
};
|
1
client/plugins/fcc-source-challenges/package.json
Normal file
1
client/plugins/fcc-source-challenges/package.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name": "fcc-source-challenges"}
|
@ -6,11 +6,15 @@ import { createSelector } from 'reselect';
|
|||||||
import { Grid, Button } from '@freecodecamp/react-bootstrap';
|
import { Grid, Button } from '@freecodecamp/react-bootstrap';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import { signInLoadingSelector, userSelector } from '../redux';
|
import {
|
||||||
|
signInLoadingSelector,
|
||||||
|
userSelector,
|
||||||
|
isSignedInSelector
|
||||||
|
} from '../redux';
|
||||||
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
|
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
|
||||||
import { createFlashMessage } from '../components/Flash/redux';
|
import { createFlashMessage } from '../components/Flash/redux';
|
||||||
|
|
||||||
import Layout from '../components/Layout';
|
import Layout from '../components/layouts/Default';
|
||||||
import Spacer from '../components/helpers/Spacer';
|
import Spacer from '../components/helpers/Spacer';
|
||||||
import Loader from '../components/helpers/Loader';
|
import Loader from '../components/helpers/Loader';
|
||||||
import FullWidthRow from '../components/helpers/FullWidthRow';
|
import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||||
@ -21,9 +25,11 @@ import Internet from '../components/settings/Internet';
|
|||||||
import Portfolio from '../components/settings/Portfolio';
|
import Portfolio from '../components/settings/Portfolio';
|
||||||
import Honesty from '../components/settings/Honesty';
|
import Honesty from '../components/settings/Honesty';
|
||||||
import Certification from '../components/settings/Certification';
|
import Certification from '../components/settings/Certification';
|
||||||
|
import RedirectHome from '../components/RedirectHome';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
createFlashMessage: PropTypes.func.isRequired,
|
createFlashMessage: PropTypes.func.isRequired,
|
||||||
|
isSignedIn: PropTypes.bool,
|
||||||
showLoading: PropTypes.bool,
|
showLoading: PropTypes.bool,
|
||||||
submitNewAbout: PropTypes.func.isRequired,
|
submitNewAbout: PropTypes.func.isRequired,
|
||||||
toggleNightMode: PropTypes.func.isRequired,
|
toggleNightMode: PropTypes.func.isRequired,
|
||||||
@ -83,9 +89,11 @@ const propTypes = {
|
|||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
userSelector,
|
userSelector,
|
||||||
(showLoading, user) => ({
|
isSignedInSelector,
|
||||||
|
(showLoading, user, isSignedIn) => ({
|
||||||
showLoading,
|
showLoading,
|
||||||
user
|
user,
|
||||||
|
isSignedIn
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -107,6 +115,7 @@ const mapDispatchToProps = dispatch =>
|
|||||||
function ShowSettings(props) {
|
function ShowSettings(props) {
|
||||||
const {
|
const {
|
||||||
createFlashMessage,
|
createFlashMessage,
|
||||||
|
isSignedIn,
|
||||||
submitNewAbout,
|
submitNewAbout,
|
||||||
toggleNightMode,
|
toggleNightMode,
|
||||||
user: {
|
user: {
|
||||||
@ -156,6 +165,10 @@ function ShowSettings(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!showLoading && !isSignedIn) {
|
||||||
|
return <RedirectHome />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -4,7 +4,7 @@ import { Grid, Panel, Button } from '@freecodecamp/react-bootstrap';
|
|||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import env from '../../config/env.json';
|
import env from '../../config/env.json';
|
||||||
import Layout from '../components/Layout';
|
import Layout from '../components/layouts/Default';
|
||||||
import FullWidthRow from '../components/helpers/FullWidthRow';
|
import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||||
import { Spacer } from '../components/helpers';
|
import { Spacer } from '../components/helpers';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
userSelector,
|
userSelector,
|
||||||
reportUser
|
reportUser
|
||||||
} from '../redux';
|
} from '../redux';
|
||||||
import Layout from '../components/Layout';
|
import Layout from '../components/layouts/Default';
|
||||||
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
133
client/src/client/frame-runner.js
Normal file
133
client/src/client/frame-runner.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const {
|
||||||
|
timeout,
|
||||||
|
catchError,
|
||||||
|
map,
|
||||||
|
toArray,
|
||||||
|
switchMap,
|
||||||
|
of,
|
||||||
|
from,
|
||||||
|
throwError
|
||||||
|
} = document.__deps__.rx;
|
||||||
|
const frameReady = document.__frameReady;
|
||||||
|
const chai = parent.chai;
|
||||||
|
const source = document.__source;
|
||||||
|
const __getUserInput = document.__getUserInput || (x => x);
|
||||||
|
const checkChallengePayload = document.__checkChallengePayload;
|
||||||
|
|
||||||
|
const fiveSeconds = 5000;
|
||||||
|
|
||||||
|
function isPromise(value) {
|
||||||
|
return (
|
||||||
|
value &&
|
||||||
|
typeof value.subscribe !== 'function' &&
|
||||||
|
typeof value.then === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Fake Deep Equal dependency
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
|
||||||
|
// Hardcode Deep Freeze dependency
|
||||||
|
const DeepFreeze = o => {
|
||||||
|
Object.freeze(o);
|
||||||
|
Object.getOwnPropertyNames(o).forEach(function(prop) {
|
||||||
|
if (
|
||||||
|
o.hasOwnProperty(prop) &&
|
||||||
|
o[prop] !== null &&
|
||||||
|
(typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
|
||||||
|
!Object.isFrozen(o[prop])
|
||||||
|
) {
|
||||||
|
DeepFreeze(o[prop]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.Enzyme) {
|
||||||
|
window.Enzyme = document.Enzyme;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.__runTests = function runTests(tests = []) {
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
const code = source.slice(0);
|
||||||
|
const editor = {
|
||||||
|
getValue() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const userCode = document.createElement('script');
|
||||||
|
userCode.type = 'text/javascript';
|
||||||
|
userCode.text = code;
|
||||||
|
document.body.appendChild(userCode);
|
||||||
|
const assert = chai.assert;
|
||||||
|
const getUserInput = __getUserInput;
|
||||||
|
// Iterate through the test one at a time
|
||||||
|
// on new stacks
|
||||||
|
const results = from(tests).pipe(
|
||||||
|
switchMap(function runOneTest({ text, testString }) {
|
||||||
|
const newTest = { text, testString };
|
||||||
|
let test;
|
||||||
|
let __result;
|
||||||
|
// uncomment the following line to inspect
|
||||||
|
// the framerunner as it runs tests
|
||||||
|
// make sure the dev tools console is open
|
||||||
|
// debugger;
|
||||||
|
try {
|
||||||
|
/* eslint-disable no-eval */
|
||||||
|
// eval test string to actual JavaScript
|
||||||
|
// This return can be a function
|
||||||
|
// i.e. function() { assert(true, 'happy coding'); }
|
||||||
|
test = eval(testString);
|
||||||
|
/* eslint-enable no-eval */
|
||||||
|
if (typeof test === 'function') {
|
||||||
|
// all async tests must return a promise or observable
|
||||||
|
// sync tests can return Any type
|
||||||
|
__result = test(getUserInput);
|
||||||
|
if (isPromise(__result)) {
|
||||||
|
// resolve the promise before continuing
|
||||||
|
__result = from(__result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!__result || typeof __result.subscribe !== 'function') {
|
||||||
|
// make sure result is an observable
|
||||||
|
__result = of(null);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
__result = throwError(e);
|
||||||
|
}
|
||||||
|
return __result.pipe(
|
||||||
|
timeout(fiveSeconds),
|
||||||
|
map(() => {
|
||||||
|
// if we are here, then the assert passed
|
||||||
|
// mark test as passing
|
||||||
|
newTest.pass = true;
|
||||||
|
return newTest;
|
||||||
|
}),
|
||||||
|
catchError(err => {
|
||||||
|
const { message, stack } = err;
|
||||||
|
// we catch the error here to prevent the error from bubbling up
|
||||||
|
// and collapsing the pipe
|
||||||
|
let errMessage = message.slice(0) || '';
|
||||||
|
const assertIndex = errMessage.indexOf(': expected');
|
||||||
|
if (assertIndex !== -1) {
|
||||||
|
errMessage = errMessage.slice(0, assertIndex);
|
||||||
|
}
|
||||||
|
errMessage = errMessage.replace(/<code>(.*?)<\/code>/g, '$1');
|
||||||
|
newTest.err = errMessage + '\n' + stack;
|
||||||
|
newTest.stack = stack;
|
||||||
|
newTest.message = errMessage;
|
||||||
|
// RxJS catch expects an observable as a return
|
||||||
|
return of(newTest);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
toArray()
|
||||||
|
);
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
// notify that the window methods are ready to run
|
||||||
|
frameReady.next({ checkChallengePayload });
|
||||||
|
});
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Modal } from 'react-bootstrap';
|
import { Modal } from '@freecodecamp/react-bootstrap';
|
||||||
import { StripeProvider, Elements } from 'react-stripe-elements';
|
import { StripeProvider, Elements } from 'react-stripe-elements';
|
||||||
|
|
||||||
import ga from '../../analytics';
|
import ga from '../../analytics';
|
||||||
@ -13,10 +13,10 @@ import {
|
|||||||
userSelector,
|
userSelector,
|
||||||
closeDonationModal,
|
closeDonationModal,
|
||||||
isDonationModalOpenSelector
|
isDonationModalOpenSelector
|
||||||
} from '../../redux/app';
|
} from '../../redux';
|
||||||
|
|
||||||
import './donation.css';
|
import './donation.css';
|
||||||
import poweredByStripe from '../../../static/img/powered_by_stripe.svg';
|
import poweredByStripe from '../../images/powered_by_stripe.svg';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
userSelector,
|
||||||
@ -82,7 +82,7 @@ class DonationModal extends PureComponent {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className='maybe-later-container'>
|
<div className='maybe-later-container'>
|
||||||
<a onClick={handleClick}>Maybe later</a>
|
<button onClick={handleClick}>Maybe later</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ class DonationModal extends PureComponent {
|
|||||||
/>
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<img src={poweredByStripe} />
|
<img alt='powered by stripe' src={poweredByStripe} />
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Elements>
|
</Elements>
|
@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
import StripCardForm from './StripeCardForm';
|
import StripCardForm from './StripeCardForm';
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Alert, Button } from 'react-bootstrap';
|
import { Alert, Button } from '@freecodecamp/react-bootstrap';
|
||||||
import Spinner from 'react-spinkit';
|
import Spinner from 'react-spinkit';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
@ -132,13 +132,12 @@ class DonateForm extends PureComponent {
|
|||||||
renderAmountButtons() {
|
renderAmountButtons() {
|
||||||
return this.buttonAmounts.map(amount => (
|
return this.buttonAmounts.map(amount => (
|
||||||
<li key={'amount-' + amount}>
|
<li key={'amount-' + amount}>
|
||||||
<a
|
<button
|
||||||
className={`amount-value ${this.isActive(amount) ? 'active' : ''}`}
|
className={`amount-value ${this.isActive(amount) ? 'active' : ''}`}
|
||||||
href=''
|
|
||||||
id={amount}
|
id={amount}
|
||||||
onClick={this.handleAmountClick}
|
onClick={this.handleAmountClick}
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
>{`$${amount / 100}`}</a>
|
>{`$${amount / 100}`}</button>
|
||||||
</li>
|
</li>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -187,7 +186,9 @@ class DonateForm extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { donationState: { processing, success, error } } = this.state;
|
const {
|
||||||
|
donationState: { processing, success, error }
|
||||||
|
} = this.state;
|
||||||
const { renderCompletion } = this.props;
|
const { renderCompletion } = this.props;
|
||||||
if (processing || success || error) {
|
if (processing || success || error) {
|
||||||
return renderCompletion({
|
return renderCompletion({
|
@ -5,7 +5,7 @@ import {
|
|||||||
CardExpiryElement,
|
CardExpiryElement,
|
||||||
CardCVCElement
|
CardCVCElement
|
||||||
} from 'react-stripe-elements';
|
} from 'react-stripe-elements';
|
||||||
import { ControlLabel, FormGroup } from 'react-bootstrap';
|
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
getValidationState: PropTypes.func.isRequired
|
getValidationState: PropTypes.func.isRequired
|
@ -18,7 +18,7 @@ function Header({ disableSettings }) {
|
|||||||
{disableSettings ? null : <FCCSearch />}
|
{disableSettings ? null : <FCCSearch />}
|
||||||
<ul id='top-right-nav'>
|
<ul id='top-right-nav'>
|
||||||
<li>
|
<li>
|
||||||
<Link to='/'>Curriculum</Link>
|
<Link to='/learn'>Curriculum</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
import React, { Fragment, Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import Helmet from 'react-helmet';
|
|
||||||
import { StaticQuery, graphql } from 'gatsby';
|
|
||||||
|
|
||||||
import { fetchUser, isSignedInSelector } from '../redux';
|
|
||||||
import { flashMessagesSelector, removeFlashMessage } from './Flash/redux';
|
|
||||||
import Flash from './Flash';
|
|
||||||
import Header from './Header';
|
|
||||||
|
|
||||||
import './global.css';
|
|
||||||
import './layout.css';
|
|
||||||
import './night.css';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
disableSettings: PropTypes.bool,
|
|
||||||
fetchUser: PropTypes.func.isRequired,
|
|
||||||
flashMessages: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
id: PropTypes.string,
|
|
||||||
type: PropTypes.string,
|
|
||||||
message: PropTypes.string
|
|
||||||
})
|
|
||||||
),
|
|
||||||
hasMessages: PropTypes.bool,
|
|
||||||
isSignedIn: PropTypes.bool,
|
|
||||||
landingPage: PropTypes.bool,
|
|
||||||
removeFlashMessage: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
isSignedInSelector,
|
|
||||||
flashMessagesSelector,
|
|
||||||
(isSignedIn, flashMessages) => ({
|
|
||||||
isSignedIn,
|
|
||||||
flashMessages,
|
|
||||||
hasMessages: !!flashMessages.length
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const mapDispatchToProps = dispatch =>
|
|
||||||
bindActionCreators({ fetchUser, removeFlashMessage }, dispatch);
|
|
||||||
|
|
||||||
class Layout extends Component {
|
|
||||||
componentDidMount() {
|
|
||||||
if (!this.props.isSignedIn) {
|
|
||||||
this.props.fetchUser();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
disableSettings,
|
|
||||||
hasMessages,
|
|
||||||
flashMessages = [],
|
|
||||||
removeFlashMessage,
|
|
||||||
landingPage
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<StaticQuery
|
|
||||||
query={graphql`
|
|
||||||
query SiteTitleQuery {
|
|
||||||
site {
|
|
||||||
siteMetadata {
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
render={data => (
|
|
||||||
<Fragment>
|
|
||||||
<Helmet
|
|
||||||
meta={[
|
|
||||||
{ name: 'description', content: 'Sample' },
|
|
||||||
{ name: 'keywords', content: 'sample, something' }
|
|
||||||
]}
|
|
||||||
title={data.site.siteMetadata.title}
|
|
||||||
/>
|
|
||||||
<Header disableSettings={disableSettings} />
|
|
||||||
<main className={landingPage && 'landing-page'}>
|
|
||||||
{hasMessages ? (
|
|
||||||
<Flash messages={flashMessages} onClose={removeFlashMessage} />
|
|
||||||
) : null}
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Layout);
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
|
|
||||||
import SuperBlock from './components/SuperBlock';
|
import SuperBlock from './components/SuperBlock';
|
||||||
import Spacer from '../util/Spacer';
|
import Spacer from '../helpers/Spacer';
|
||||||
|
|
||||||
import './map.css';
|
import './map.css';
|
||||||
import { ChallengeNode } from '../../redux/propTypes';
|
import { ChallengeNode } from '../../redux/propTypes';
|
@ -7,7 +7,7 @@ import { Link } from 'gatsby';
|
|||||||
|
|
||||||
import ga from '../../../analytics';
|
import ga from '../../../analytics';
|
||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import { userSelector } from '../../../redux/app';
|
import { userSelector } from '../../../redux';
|
||||||
import Caret from '../../icons/Caret';
|
import Caret from '../../icons/Caret';
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
import GreenPass from '../../../templates/Challenges/components/icons/GreenPass';
|
import GreenPass from '../../../templates/Challenges/components/icons/GreenPass';
|
@ -3,8 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import uniq from 'lodash/uniq';
|
import { uniq, find } from 'lodash';
|
||||||
import find from 'lodash/find';
|
|
||||||
|
|
||||||
import Block from './Block';
|
import Block from './Block';
|
||||||
|
|
||||||
@ -102,4 +101,7 @@ export class SuperBlock extends PureComponent {
|
|||||||
SuperBlock.displayName = 'SuperBlock';
|
SuperBlock.displayName = 'SuperBlock';
|
||||||
SuperBlock.propTypes = propTypes;
|
SuperBlock.propTypes = propTypes;
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SuperBlock);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(SuperBlock);
|
@ -2,7 +2,7 @@ import { createAction, handleActions } from 'redux-actions';
|
|||||||
|
|
||||||
import { createTypes } from '../../../../utils/stateManagement';
|
import { createTypes } from '../../../../utils/stateManagement';
|
||||||
|
|
||||||
const ns = 'map';
|
const ns = 'curriculumMap';
|
||||||
|
|
||||||
export const getNS = () => ns;
|
export const getNS = () => ns;
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
import { navigate } from 'gatsby';
|
import { navigate } from 'gatsby';
|
||||||
|
|
||||||
const Redirecthome = () => {
|
const RedirectHome = () => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Redirecthome;
|
export default RedirectHome;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
function BlockSaveButton(props) {
|
function BlockSaveButton(props) {
|
||||||
return (
|
return (
|
@ -7,7 +7,7 @@ import {
|
|||||||
ControlLabel,
|
ControlLabel,
|
||||||
FormControl,
|
FormControl,
|
||||||
HelpBlock
|
HelpBlock
|
||||||
} from 'react-bootstrap';
|
} from '@freecodecamp/react-bootstrap';
|
||||||
|
|
||||||
import './form-fields.css';
|
import './form-fields.css';
|
||||||
|
|
@ -2,27 +2,27 @@ import React, { Fragment, Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
import ga from '../analytics';
|
import ga from '../../analytics';
|
||||||
|
|
||||||
import Header from '../components/Header';
|
|
||||||
import DonationModal from '../components/Donation';
|
|
||||||
import OfflineWarning from '../components/OfflineWarning';
|
|
||||||
import {
|
import {
|
||||||
fetchUser,
|
fetchUser,
|
||||||
userSelector,
|
isSignedInSelector,
|
||||||
onlineStatusChange,
|
onlineStatusChange,
|
||||||
isOnlineSelector,
|
isOnlineSelector
|
||||||
isSignedInSelector
|
} from '../../redux';
|
||||||
} from '../redux/app';
|
import { flashMessagesSelector, removeFlashMessage } from '../Flash/redux';
|
||||||
|
|
||||||
|
import { isBrowser } from '../../../utils';
|
||||||
|
|
||||||
|
import OfflineWarning from '../OfflineWarning';
|
||||||
|
import Flash from '../Flash';
|
||||||
|
import Header from '../Header';
|
||||||
|
|
||||||
import 'prismjs/themes/prism.css';
|
|
||||||
import 'react-reflex/styles.css';
|
|
||||||
import './global.css';
|
import './global.css';
|
||||||
import './layout.css';
|
import './layout.css';
|
||||||
import { createSelector } from 'reselect';
|
import './night.css';
|
||||||
import { isBrowser } from '../../utils';
|
|
||||||
|
|
||||||
const metaKeywords = [
|
const metaKeywords = [
|
||||||
'javascript',
|
'javascript',
|
||||||
@ -48,57 +48,67 @@ const metaKeywords = [
|
|||||||
'programming'
|
'programming'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
disableSettings: PropTypes.bool,
|
||||||
|
fetchUser: PropTypes.func.isRequired,
|
||||||
|
flashMessages: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
message: PropTypes.string
|
||||||
|
})
|
||||||
|
),
|
||||||
|
hasMessages: PropTypes.bool,
|
||||||
|
isOnline: PropTypes.bool.isRequired,
|
||||||
|
isSignedIn: PropTypes.bool,
|
||||||
|
landingPage: PropTypes.bool,
|
||||||
|
onlineStatusChange: PropTypes.func.isRequired,
|
||||||
|
removeFlashMessage: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userSelector,
|
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
|
flashMessagesSelector,
|
||||||
isOnlineSelector,
|
isOnlineSelector,
|
||||||
({ theme = 'default' }, isSignedIn, isOnline) => ({
|
(isSignedIn, flashMessages, isOnline) => ({
|
||||||
theme,
|
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
|
flashMessages,
|
||||||
|
hasMessages: !!flashMessages.length,
|
||||||
isOnline
|
isOnline
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const mapDispatchToProps = dispatch =>
|
const mapDispatchToProps = dispatch =>
|
||||||
bindActionCreators({ fetchUser, onlineStatusChange }, dispatch);
|
bindActionCreators(
|
||||||
|
{ fetchUser, removeFlashMessage, onlineStatusChange },
|
||||||
|
dispatch
|
||||||
|
);
|
||||||
|
|
||||||
const propTypes = {
|
class DefaultLayout extends Component {
|
||||||
children: PropTypes.object,
|
constructor(props) {
|
||||||
fetchUser: PropTypes.func.isRequired,
|
super(props);
|
||||||
isOnline: PropTypes.bool.isRequired,
|
|
||||||
isSignedIn: PropTypes.bool.isRequired,
|
|
||||||
onlineStatusChange: PropTypes.func.isRequired,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
class Layout extends Component {
|
this.location = '';
|
||||||
state = {
|
}
|
||||||
location: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchUser();
|
if (!this.props.isSignedIn) {
|
||||||
|
this.props.fetchUser();
|
||||||
|
}
|
||||||
const url = window.location.pathname + window.location.search;
|
const url = window.location.pathname + window.location.search;
|
||||||
ga.pageview(url);
|
ga.pageview(url);
|
||||||
|
|
||||||
window.addEventListener('online', this.updateOnlineStatus);
|
window.addEventListener('online', this.updateOnlineStatus);
|
||||||
window.addEventListener('offline', this.updateOnlineStatus);
|
window.addEventListener('offline', this.updateOnlineStatus);
|
||||||
|
|
||||||
/* eslint-disable react/no-did-mount-set-state */
|
this.location = url;
|
||||||
// this is for local location tracking only, no re-rendering required
|
|
||||||
this.setState({
|
|
||||||
location: url
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const url = window.location.pathname + window.location.search;
|
const url = window.location.pathname + window.location.search;
|
||||||
if (url !== this.state.location) {
|
if (url !== this.location) {
|
||||||
ga.pageview(url);
|
ga.pageview(url);
|
||||||
/* eslint-disable react/no-did-update-set-state */
|
this.location = url;
|
||||||
// this is for local location tracking only, no re-rendering required
|
|
||||||
this.setState({
|
|
||||||
location: url
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +125,16 @@ class Layout extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, theme, isOnline, isSignedIn } = this.props;
|
const {
|
||||||
|
children,
|
||||||
|
disableSettings,
|
||||||
|
hasMessages,
|
||||||
|
flashMessages = [],
|
||||||
|
removeFlashMessage,
|
||||||
|
landingPage,
|
||||||
|
isOnline,
|
||||||
|
isSignedIn
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet
|
<Helmet
|
||||||
@ -129,17 +148,23 @@ class Layout extends Component {
|
|||||||
{ name: 'keywords', content: metaKeywords.join(', ') }
|
{ name: 'keywords', content: metaKeywords.join(', ') }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Header />
|
<Header disableSettings={disableSettings} />
|
||||||
<div className={'app-wrapper ' + theme}>
|
<main className={landingPage && 'landing-page'}>
|
||||||
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
||||||
<main>{children}</main>
|
{hasMessages ? (
|
||||||
</div>
|
<Flash messages={flashMessages} onClose={removeFlashMessage} />
|
||||||
<DonationModal />
|
) : null}
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout.propTypes = propTypes;
|
DefaultLayout.displayName = 'DefaultLayout';
|
||||||
|
DefaultLayout.propTypes = propTypes;
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Layout);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(DefaultLayout);
|
25
client/src/components/layouts/Learn.js
Normal file
25
client/src/components/layouts/Learn.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import DefaultLayout from './Default';
|
||||||
|
import DonationModal from '../Donation';
|
||||||
|
|
||||||
|
import 'prismjs/themes/prism.css';
|
||||||
|
import 'react-reflex/styles.css';
|
||||||
|
import './learn.css';
|
||||||
|
|
||||||
|
function LearnLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<DefaultLayout>
|
||||||
|
<div id='learn-app-wrapper'>{children}</div>
|
||||||
|
</DefaultLayout>
|
||||||
|
<DonationModal />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LearnLayout.displayName = 'LearnLayout';
|
||||||
|
LearnLayout.propTypes = { children: PropTypes.any };
|
||||||
|
|
||||||
|
export default LearnLayout;
|
@ -1,10 +1,5 @@
|
|||||||
|
#learn-app-wrapper .reflex-layout.vertical > .reflex-splitter,
|
||||||
main {
|
#learn-app-wrapper .reflex-layout > .reflex-element {
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reflex-layout.vertical > .reflex-splitter,
|
|
||||||
.reflex-layout > .reflex-element {
|
|
||||||
/*
|
/*
|
||||||
Note(Bouncey): removing height: 100% makes the element 100% high ¯\_(ツ)_/¯
|
Note(Bouncey): removing height: 100% makes the element 100% high ¯\_(ツ)_/¯
|
||||||
*/
|
*/
|
||||||
@ -12,21 +7,16 @@ main {
|
|||||||
/*
|
/*
|
||||||
Note(renojvarghese): had to put a max-height because max-height was removed from app-wrapper (has background of #333 in night mode)
|
Note(renojvarghese): had to put a max-height because max-height was removed from app-wrapper (has background of #333 in night mode)
|
||||||
*/
|
*/
|
||||||
max-height: calc(100vh - 36px);
|
max-height: calc(100vh - 38px);
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reflex-layout.vertical .reflex-splitter:hover {
|
#learn-app-wrapper .reflex-layout.vertical .reflex-splitter:hover {
|
||||||
border-left: 2px solid #006400 !important;
|
border-left: 2px solid #006400 !important;
|
||||||
border-right: 2px solid #006400 !important;
|
border-right: 2px solid #006400 !important;
|
||||||
background-color: #006400;
|
background-color: #006400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reflex-layout.vertical > .reflex-splitter {
|
#learn-app-wrapper .reflex-layout.vertical > .reflex-splitter {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-wrapper {
|
|
||||||
margin-top: 36px;
|
|
||||||
min-height: calc(100vh - 36px);
|
|
||||||
}
|
|
@ -5,6 +5,10 @@ import sassjs from './sassjs';
|
|||||||
|
|
||||||
const metaAndStyleSheets = meta
|
const metaAndStyleSheets = meta
|
||||||
.concat(favicons, mathjax, sassjs)
|
.concat(favicons, mathjax, sassjs)
|
||||||
.map((element, i) => ({ ...element, key: `meta-stylesheet-${i}` }));
|
.map((element, i) => ({
|
||||||
|
...element,
|
||||||
|
key: `meta-stylesheet-${i}`,
|
||||||
|
props: { ...element.props, key: `meta-stylesheet-${i}` }
|
||||||
|
}));
|
||||||
|
|
||||||
export default metaAndStyleSheets;
|
export default metaAndStyleSheets;
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user