chore(learn): Merge learn in to the client app

This commit is contained in:
Bouncey
2018-09-30 11:37:19 +01:00
committed by Stuart Taylor
parent 9e869a46fc
commit 5b254f3ad6
320 changed files with 9820 additions and 27605 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@ node_modules
*.swp *.swp
.DS_Store .DS_Store
curriculum/dist/

View File

@ -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'

View File

@ -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
}
}
});
};

View File

@ -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/' />
]);
};

View File

@ -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: {

10083
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View 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}
`)
);
};

View File

@ -0,0 +1 @@
{"name": "fcc-source-challenges"}

View File

@ -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>

View File

@ -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';

View File

@ -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 = {

View 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 });
});

View File

@ -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>

View File

@ -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';

View File

@ -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 = {

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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 (

View File

@ -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';

View File

@ -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() {
if (!this.props.isSignedIn) {
this.props.fetchUser(); 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);

View 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;

View File

@ -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);
}

View File

@ -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;

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

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