chore(learn): Merge learn in to the client app
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@ node_modules
|
||||
*.swp
|
||||
|
||||
.DS_Store
|
||||
|
||||
curriculum/dist/
|
@ -1,12 +1,16 @@
|
||||
const path = require('path');
|
||||
|
||||
const { buildChallenges$ } = require('./utils/buildChallenges');
|
||||
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: 'freeCodeCamp',
|
||||
siteUrl: 'https://www.freecodecamp.org'
|
||||
},
|
||||
proxy: {
|
||||
prefix: '/internal',
|
||||
url: 'http://localhost:3000'
|
||||
},
|
||||
// proxy: {
|
||||
// prefix: '/internal',
|
||||
// url: 'http://localhost:3000'
|
||||
// },
|
||||
plugins: [
|
||||
'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',
|
||||
options: {
|
||||
name: 'freeCodeCamp',
|
||||
/* eslint-disable camelcase */
|
||||
short_name: 'fCC',
|
||||
start_url: '/',
|
||||
background_color: '#fff',
|
||||
theme_color: '#006400',
|
||||
background_color: '#fff',
|
||||
/* eslint-enable camelcase */
|
||||
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'
|
||||
|
@ -1,7 +1,178 @@
|
||||
/**
|
||||
* Implement Gatsby's Node APIs in this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.org/docs/node-apis/
|
||||
*/
|
||||
require('dotenv').config();
|
||||
|
||||
// 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 { Provider } from 'react-redux';
|
||||
|
||||
import headComponents from './src/head';
|
||||
import { createStore } from './src/redux/createStore';
|
||||
|
||||
const store = createStore();
|
||||
@ -13,3 +14,31 @@ export const wrapRootElement = ({ element }) => {
|
||||
wrapRootElement.propTypes = {
|
||||
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',
|
||||
[
|
||||
'transform-imports', {
|
||||
'react-bootstrap': {
|
||||
transform: 'react-bootstrap/lib/${member}',
|
||||
'@freecodecamp/react-bootstrap': {
|
||||
transform: '@freecodecamp/react-bootstrap/lib/${member}',
|
||||
preventFullImport: true
|
||||
},
|
||||
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",
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"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-svg-core": "^1.2.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.2.0",
|
||||
"@fortawesome/react-fontawesome": "0.0.20",
|
||||
"@freecodecamp/curriculum": "^3.2.1",
|
||||
"@freecodecamp/react-bootstrap": "^0.32.3",
|
||||
"@reach/router": "^1.1.1",
|
||||
"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-link": "^2.0.0-rc.2",
|
||||
"gatsby-plugin-create-client-paths": "^2.0.0-rc.1",
|
||||
"gatsby-plugin-manifest": "next",
|
||||
"gatsby-plugin-react-helmet": "^3.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",
|
||||
"loop-protect": "^2.1.6",
|
||||
"monaco-editor-webpack-plugin": "^1.5.4",
|
||||
"nanoid": "^1.2.2",
|
||||
"prismjs": "^1.15.0",
|
||||
"query-string": "^6.1.0",
|
||||
"react": "^16.4.2",
|
||||
"react-dom": "^16.4.2",
|
||||
"react-freecodecamp-search": "^2.0.2",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-media": "^1.8.0",
|
||||
"react-monaco-editor": "^0.18.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-reflex": "^2.2.9",
|
||||
"react-spinkit": "^3.0.0",
|
||||
"react-stripe-elements": "^2.0.1",
|
||||
"redux": "^4.0.0",
|
||||
"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",
|
||||
"reselect": "^3.0.1",
|
||||
"rxjs": "^6.3.3",
|
||||
"sinon": "^6.3.4",
|
||||
"store": "^2.0.12",
|
||||
"validator": "^10.7.0"
|
||||
"validator": "^10.7.0",
|
||||
"webpack-remove-serviceworker-plugin": "^1.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
],
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"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:utils": "prettier-eslint --write --trailing-comma none --single-quote './utils/**/*.js'",
|
||||
"format": "npm run format:gatsby && npm run format:src && npm run format:utils",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-transform-imports": "^1.5.0",
|
||||
"eslint": "^5.5.0",
|
||||
"eslint-config-freecodecamp": "^1.1.1",
|
||||
"jest": "^23.6.0",
|
||||
"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": {
|
||||
"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 Helmet from 'react-helmet';
|
||||
|
||||
import { signInLoadingSelector, userSelector } from '../redux';
|
||||
import {
|
||||
signInLoadingSelector,
|
||||
userSelector,
|
||||
isSignedInSelector
|
||||
} from '../redux';
|
||||
import { submitNewAbout, updateUserFlag, verifyCert } from '../redux/settings';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
|
||||
import Layout from '../components/Layout';
|
||||
import Layout from '../components/layouts/Default';
|
||||
import Spacer from '../components/helpers/Spacer';
|
||||
import Loader from '../components/helpers/Loader';
|
||||
import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||
@ -21,9 +25,11 @@ import Internet from '../components/settings/Internet';
|
||||
import Portfolio from '../components/settings/Portfolio';
|
||||
import Honesty from '../components/settings/Honesty';
|
||||
import Certification from '../components/settings/Certification';
|
||||
import RedirectHome from '../components/RedirectHome';
|
||||
|
||||
const propTypes = {
|
||||
createFlashMessage: PropTypes.func.isRequired,
|
||||
isSignedIn: PropTypes.bool,
|
||||
showLoading: PropTypes.bool,
|
||||
submitNewAbout: PropTypes.func.isRequired,
|
||||
toggleNightMode: PropTypes.func.isRequired,
|
||||
@ -83,9 +89,11 @@ const propTypes = {
|
||||
const mapStateToProps = createSelector(
|
||||
signInLoadingSelector,
|
||||
userSelector,
|
||||
(showLoading, user) => ({
|
||||
isSignedInSelector,
|
||||
(showLoading, user, isSignedIn) => ({
|
||||
showLoading,
|
||||
user
|
||||
user,
|
||||
isSignedIn
|
||||
})
|
||||
);
|
||||
|
||||
@ -107,6 +115,7 @@ const mapDispatchToProps = dispatch =>
|
||||
function ShowSettings(props) {
|
||||
const {
|
||||
createFlashMessage,
|
||||
isSignedIn,
|
||||
submitNewAbout,
|
||||
toggleNightMode,
|
||||
user: {
|
||||
@ -156,6 +165,10 @@ function ShowSettings(props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!showLoading && !isSignedIn) {
|
||||
return <RedirectHome />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
|
@ -4,7 +4,7 @@ import { Grid, Panel, Button } from '@freecodecamp/react-bootstrap';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import env from '../../config/env.json';
|
||||
import Layout from '../components/Layout';
|
||||
import Layout from '../components/layouts/Default';
|
||||
import FullWidthRow from '../components/helpers/FullWidthRow';
|
||||
import { Spacer } from '../components/helpers';
|
||||
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
userSelector,
|
||||
reportUser
|
||||
} from '../redux';
|
||||
import Layout from '../components/Layout';
|
||||
import Layout from '../components/layouts/Default';
|
||||
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
||||
|
||||
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 { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import { Modal } from '@freecodecamp/react-bootstrap';
|
||||
import { StripeProvider, Elements } from 'react-stripe-elements';
|
||||
|
||||
import ga from '../../analytics';
|
||||
@ -13,10 +13,10 @@ import {
|
||||
userSelector,
|
||||
closeDonationModal,
|
||||
isDonationModalOpenSelector
|
||||
} from '../../redux/app';
|
||||
} from '../../redux';
|
||||
|
||||
import './donation.css';
|
||||
import poweredByStripe from '../../../static/img/powered_by_stripe.svg';
|
||||
import poweredByStripe from '../../images/powered_by_stripe.svg';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
@ -82,7 +82,7 @@ class DonationModal extends PureComponent {
|
||||
};
|
||||
return (
|
||||
<div className='maybe-later-container'>
|
||||
<a onClick={handleClick}>Maybe later</a>
|
||||
<button onClick={handleClick}>Maybe later</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -109,7 +109,7 @@ class DonationModal extends PureComponent {
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<img src={poweredByStripe} />
|
||||
<img alt='powered by stripe' src={poweredByStripe} />
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</Elements>
|
@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import StripCardForm from './StripeCardForm';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Alert, Button } from 'react-bootstrap';
|
||||
import { Alert, Button } from '@freecodecamp/react-bootstrap';
|
||||
import Spinner from 'react-spinkit';
|
||||
|
||||
const propTypes = {
|
@ -132,13 +132,12 @@ class DonateForm extends PureComponent {
|
||||
renderAmountButtons() {
|
||||
return this.buttonAmounts.map(amount => (
|
||||
<li key={'amount-' + amount}>
|
||||
<a
|
||||
<button
|
||||
className={`amount-value ${this.isActive(amount) ? 'active' : ''}`}
|
||||
href=''
|
||||
id={amount}
|
||||
onClick={this.handleAmountClick}
|
||||
tabIndex='-1'
|
||||
>{`$${amount / 100}`}</a>
|
||||
>{`$${amount / 100}`}</button>
|
||||
</li>
|
||||
));
|
||||
}
|
||||
@ -187,7 +186,9 @@ class DonateForm extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { donationState: { processing, success, error } } = this.state;
|
||||
const {
|
||||
donationState: { processing, success, error }
|
||||
} = this.state;
|
||||
const { renderCompletion } = this.props;
|
||||
if (processing || success || error) {
|
||||
return renderCompletion({
|
@ -5,7 +5,7 @@ import {
|
||||
CardExpiryElement,
|
||||
CardCVCElement
|
||||
} from 'react-stripe-elements';
|
||||
import { ControlLabel, FormGroup } from 'react-bootstrap';
|
||||
import { ControlLabel, FormGroup } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
const propTypes = {
|
||||
getValidationState: PropTypes.func.isRequired
|
@ -18,7 +18,7 @@ function Header({ disableSettings }) {
|
||||
{disableSettings ? null : <FCCSearch />}
|
||||
<ul id='top-right-nav'>
|
||||
<li>
|
||||
<Link to='/'>Curriculum</Link>
|
||||
<Link to='/learn'>Curriculum</Link>
|
||||
</li>
|
||||
<li>
|
||||
<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 SuperBlock from './components/SuperBlock';
|
||||
import Spacer from '../util/Spacer';
|
||||
import Spacer from '../helpers/Spacer';
|
||||
|
||||
import './map.css';
|
||||
import { ChallengeNode } from '../../redux/propTypes';
|
@ -7,7 +7,7 @@ import { Link } from 'gatsby';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||
import { userSelector } from '../../../redux/app';
|
||||
import { userSelector } from '../../../redux';
|
||||
import Caret from '../../icons/Caret';
|
||||
/* eslint-disable max-len */
|
||||
import GreenPass from '../../../templates/Challenges/components/icons/GreenPass';
|
@ -3,8 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import uniq from 'lodash/uniq';
|
||||
import find from 'lodash/find';
|
||||
import { uniq, find } from 'lodash';
|
||||
|
||||
import Block from './Block';
|
||||
|
||||
@ -102,4 +101,7 @@ export class SuperBlock extends PureComponent {
|
||||
SuperBlock.displayName = 'SuperBlock';
|
||||
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';
|
||||
|
||||
const ns = 'map';
|
||||
const ns = 'curriculumMap';
|
||||
|
||||
export const getNS = () => ns;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { navigate } from 'gatsby';
|
||||
|
||||
const Redirecthome = () => {
|
||||
const RedirectHome = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
navigate('/');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Redirecthome;
|
||||
export default RedirectHome;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
|
||||
function BlockSaveButton(props) {
|
||||
return (
|
@ -7,7 +7,7 @@ import {
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
HelpBlock
|
||||
} from 'react-bootstrap';
|
||||
} from '@freecodecamp/react-bootstrap';
|
||||
|
||||
import './form-fields.css';
|
||||
|
@ -2,27 +2,27 @@ 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 ga from '../analytics';
|
||||
|
||||
import Header from '../components/Header';
|
||||
import DonationModal from '../components/Donation';
|
||||
import OfflineWarning from '../components/OfflineWarning';
|
||||
import ga from '../../analytics';
|
||||
import {
|
||||
fetchUser,
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
onlineStatusChange,
|
||||
isOnlineSelector,
|
||||
isSignedInSelector
|
||||
} from '../redux/app';
|
||||
isOnlineSelector
|
||||
} from '../../redux';
|
||||
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 './layout.css';
|
||||
import { createSelector } from 'reselect';
|
||||
import { isBrowser } from '../../utils';
|
||||
import './night.css';
|
||||
|
||||
const metaKeywords = [
|
||||
'javascript',
|
||||
@ -48,57 +48,67 @@ const metaKeywords = [
|
||||
'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(
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
flashMessagesSelector,
|
||||
isOnlineSelector,
|
||||
({ theme = 'default' }, isSignedIn, isOnline) => ({
|
||||
theme,
|
||||
(isSignedIn, flashMessages, isOnline) => ({
|
||||
isSignedIn,
|
||||
flashMessages,
|
||||
hasMessages: !!flashMessages.length,
|
||||
isOnline
|
||||
})
|
||||
);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ fetchUser, onlineStatusChange }, dispatch);
|
||||
bindActionCreators(
|
||||
{ fetchUser, removeFlashMessage, onlineStatusChange },
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.object,
|
||||
fetchUser: PropTypes.func.isRequired,
|
||||
isOnline: PropTypes.bool.isRequired,
|
||||
isSignedIn: PropTypes.bool.isRequired,
|
||||
onlineStatusChange: PropTypes.func.isRequired,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
class DefaultLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
class Layout extends Component {
|
||||
state = {
|
||||
location: ''
|
||||
};
|
||||
this.location = '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isSignedIn) {
|
||||
this.props.fetchUser();
|
||||
}
|
||||
const url = window.location.pathname + window.location.search;
|
||||
ga.pageview(url);
|
||||
|
||||
window.addEventListener('online', this.updateOnlineStatus);
|
||||
window.addEventListener('offline', this.updateOnlineStatus);
|
||||
|
||||
/* eslint-disable react/no-did-mount-set-state */
|
||||
// this is for local location tracking only, no re-rendering required
|
||||
this.setState({
|
||||
location: url
|
||||
});
|
||||
this.location = url;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const url = window.location.pathname + window.location.search;
|
||||
if (url !== this.state.location) {
|
||||
if (url !== this.location) {
|
||||
ga.pageview(url);
|
||||
/* eslint-disable react/no-did-update-set-state */
|
||||
// this is for local location tracking only, no re-rendering required
|
||||
this.setState({
|
||||
location: url
|
||||
});
|
||||
this.location = url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +125,16 @@ class Layout extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, theme, isOnline, isSignedIn } = this.props;
|
||||
const {
|
||||
children,
|
||||
disableSettings,
|
||||
hasMessages,
|
||||
flashMessages = [],
|
||||
removeFlashMessage,
|
||||
landingPage,
|
||||
isOnline,
|
||||
isSignedIn
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet
|
||||
@ -129,17 +148,23 @@ class Layout extends Component {
|
||||
{ name: 'keywords', content: metaKeywords.join(', ') }
|
||||
]}
|
||||
/>
|
||||
<Header />
|
||||
<div className={'app-wrapper ' + theme}>
|
||||
<Header disableSettings={disableSettings} />
|
||||
<main className={landingPage && 'landing-page'}>
|
||||
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<DonationModal />
|
||||
{hasMessages ? (
|
||||
<Flash messages={flashMessages} onClose={removeFlashMessage} />
|
||||
) : null}
|
||||
{children}
|
||||
</main>
|
||||
</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 @@
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.reflex-layout.vertical > .reflex-splitter,
|
||||
.reflex-layout > .reflex-element {
|
||||
#learn-app-wrapper .reflex-layout.vertical > .reflex-splitter,
|
||||
#learn-app-wrapper .reflex-layout > .reflex-element {
|
||||
/*
|
||||
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)
|
||||
*/
|
||||
max-height: calc(100vh - 36px);
|
||||
max-height: calc(100vh - 38px);
|
||||
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-right: 2px solid #006400 !important;
|
||||
background-color: #006400;
|
||||
}
|
||||
|
||||
.reflex-layout.vertical > .reflex-splitter {
|
||||
#learn-app-wrapper .reflex-layout.vertical > .reflex-splitter {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
margin-top: 36px;
|
||||
min-height: calc(100vh - 36px);
|
||||
}
|
@ -5,6 +5,10 @@ import sassjs from './sassjs';
|
||||
|
||||
const metaAndStyleSheets = meta
|
||||
.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;
|
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