diff --git a/api-server/server/component-passport.js b/api-server/server/component-passport.js
index ec6c667646..2283a50e9c 100644
--- a/api-server/server/component-passport.js
+++ b/api-server/server/component-passport.js
@@ -83,9 +83,9 @@ export const loginRedirect = () => {
const successRedirect = req => {
if (!!req && req.session && req.session.returnTo) {
delete req.session.returnTo;
- return `${homeLocation}/`;
+ return `${homeLocation}/learn`;
}
- return `${homeLocation}/`;
+ return `${homeLocation}/learn`;
};
let redirect = url.parse(successRedirect(req), true);
diff --git a/api-server/server/utils/middleware.js b/api-server/server/utils/middleware.js
index da8469bc2e..db4e89c7f7 100644
--- a/api-server/server/utils/middleware.js
+++ b/api-server/server/utils/middleware.js
@@ -55,7 +55,7 @@ export function ifNotVerifiedRedirectToUpdateEmail(req, res, next) {
return next();
}
-export function ifUserRedirectTo(path = `${homeLocation}/`, status) {
+export function ifUserRedirectTo(path = `${homeLocation}/learn`, status) {
status = status === 301 ? 301 : 302;
return (req, res, next) => {
const { accessToken } = getAccessTokenFromRequest(req);
diff --git a/client/src/components/Header/Header.test.js b/client/src/components/Header/Header.test.js
index 6f5852e7b7..b5a155a1f2 100644
--- a/client/src/components/Header/Header.test.js
+++ b/client/src/components/Header/Header.test.js
@@ -23,13 +23,13 @@ describe('', () => {
return acc;
}, []);
- const expectedLinks = ['/learn', '/portfolio', '/news', '/forum'];
+ const expectedLinks = ['/learn', '/news', '/forum'];
it('renders to the DOM', () => {
expect(root).toBeTruthy();
});
- it('has 2 links', () => {
- expect(aTags.length === 4).toBeTruthy();
+ it('has 3 links', () => {
+ expect(aTags.length === 3).toBeTruthy();
});
it('has links to news, forum, learn and portfolio', () => {
diff --git a/client/src/components/Header/components/NavLinks.js b/client/src/components/Header/components/NavLinks.js
index 54528cbe1f..f99f5ada5c 100644
--- a/client/src/components/Header/components/NavLinks.js
+++ b/client/src/components/Header/components/NavLinks.js
@@ -25,10 +25,7 @@ function NavLinks({ displayMenu }) {
- Projects
-
-
- Portfolio
+ Learn
diff --git a/client/src/components/Intro/Intro.test.js b/client/src/components/Intro/Intro.test.js
new file mode 100644
index 0000000000..d1708aa44b
--- /dev/null
+++ b/client/src/components/Intro/Intro.test.js
@@ -0,0 +1,42 @@
+/* global expect */
+import React from 'react';
+import renderer from 'react-test-renderer';
+// import ShallowRenderer from 'react-test-renderer/shallow';
+
+import 'jest-dom/extend-expect';
+
+import Intro from './';
+
+describe('', () => {
+ it('has no blockquotes when loggedOut', () => {
+ const container = renderer.create().root;
+ expect(container.findAllByType('blockquote').length === 0).toBeTruthy();
+ expect(container.findAllByType('h1').length === 1).toBeTruthy();
+ });
+
+ it('has a blockquote when loggedIn', () => {
+ const container = renderer.create().root;
+ expect(container.findAllByType('blockquote').length === 1).toBeTruthy();
+ expect(container.findAllByType('h1').length === 1).toBeTruthy();
+ });
+});
+
+const loggedInProps = {
+ complete: true,
+ isSignedIn: true,
+ name: 'Developement User',
+ navigate: () => {},
+ pending: false,
+ slug: '/',
+ username: 'DevelopmentUser'
+};
+
+const loggedOutProps = {
+ complete: true,
+ isSignedIn: false,
+ name: '',
+ navigate: () => {},
+ pending: false,
+ slug: '/',
+ username: ''
+};
diff --git a/client/src/components/Intro/index.js b/client/src/components/Intro/index.js
new file mode 100644
index 0000000000..cf1a3e9a19
--- /dev/null
+++ b/client/src/components/Intro/index.js
@@ -0,0 +1,130 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, Spacer, Loader } from '../helpers';
+import { Row, Col } from '@freecodecamp/react-bootstrap';
+import { navigate as gatsbyNavigate } from 'gatsby';
+import { apiLocation } from '../../../config/env.json';
+import { randomQuote } from '../../utils/get-words';
+
+import './intro.css';
+
+const propTypes = {
+ complete: PropTypes.bool,
+ isSignedIn: PropTypes.bool,
+ name: PropTypes.string,
+ navigate: PropTypes.func,
+ pending: PropTypes.bool,
+ slug: PropTypes.string,
+ username: PropTypes.string
+};
+
+function Intro({
+ isSignedIn,
+ username,
+ name,
+ navigate,
+ pending,
+ complete,
+ slug
+}) {
+ if (pending && !complete) {
+ return (
+ <>
+
+
+
+ >
+ );
+ } else if (isSignedIn) {
+ const { quote, author } = randomQuote();
+ return (
+ <>
+
+
+
+
+ {name
+ ? 'Welcome back, ' + name + '.'
+ : 'Welcome to freeCodeCamp.org'}
+
+
+
+
+
+
+
+
+
+
+
+
+ {quote}
+
+
+
+
+
+
+
+
+
+ If you are new to coding, we recommend you{' '}
+ start at the beginning.
+
+
+
+ >
+ );
+ } else {
+ return (
+ <>
+
+
+
+
+ Welcome to freeCodeCamp.org
+
+
+ Learn to code.
+ Build projects.
+ Earn certifications.
+
+ Since 2014, more than 40,000 freeCodeCamp.org graduates have
+ gotten jobs at tech companies including:
+
+
+
Apple
+ Google
+ Amazon
+ Microsoft
+ Spotify
+
+
+
+
+
+
+
+ >
+ );
+ }
+}
+
+Intro.propTypes = propTypes;
+Intro.displayName = 'Intro';
+
+export default Intro;
diff --git a/client/src/components/Intro/intro.css b/client/src/components/Intro/intro.css
new file mode 100644
index 0000000000..fc39cbbcdc
--- /dev/null
+++ b/client/src/components/Intro/intro.css
@@ -0,0 +1,49 @@
+.large-p {
+ font-size: 24px;
+}
+
+/* Buttons with a lot of text can overflow and mess up formatting on small
+ screens, this stops that unless the word itself is too large. */
+
+.btn {
+ white-space: pre-line;
+}
+
+.logo-row {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ align-content: center;
+}
+
+.logo-row h2 {
+ height: 35px;
+ padding: 0 10px 0 10px;
+}
+
+.landing-page ul {
+ list-style: none;
+ padding-left: 0px;
+}
+
+#learn-app-wrapper h2.medium-heading {
+ margin-bottom: 50px;
+ margin-top: 0px;
+ text-align: center;
+}
+
+.quote-partial .blockquote {
+ font-size: 1.3rem;
+ border: none;
+}
+
+@media (max-width: 500px) {
+ .quote-partial .blockquote {
+ font-size: 1.2rem;
+ border: none;
+ }
+}
+.quote-author {
+ font-style: normal;
+}
diff --git a/client/src/components/Map/index.js b/client/src/components/Map/index.js
index be17e71271..de1c165d5c 100644
--- a/client/src/components/Map/index.js
+++ b/client/src/components/Map/index.js
@@ -27,6 +27,7 @@ const propTypes = {
})
})
),
+ isSignedIn: PropTypes.bool,
nodes: PropTypes.arrayOf(ChallengeNode),
resetExpansion: PropTypes.func,
toggleBlock: PropTypes.func.isRequired,
@@ -68,9 +69,11 @@ export class Map extends Component {
nodes,
resetExpansion,
toggleBlock,
- toggleSuperBlock
+ toggleSuperBlock,
+ isSignedIn
} = this.props;
resetExpansion();
+
let node;
// find the challenge that has the same superblock with hash
@@ -78,13 +81,17 @@ export class Map extends Component {
node = nodes.find(node => dasherize(node.superBlock) === hash);
}
- // if there is no hash or the hash did not match any challenge superblock
- // and there was a currentChallengeId
- if (!node && currentChallengeId) {
- node = nodes.find(node => node.id === currentChallengeId);
+ // whitout hash only expand when signed in
+ if (isSignedIn) {
+ // if there is no hash or the hash did not match any challenge superblock
+ // and there was a currentChallengeId
+ if (!node && currentChallengeId) {
+ node = nodes.find(node => node.id === currentChallengeId);
+ }
+ if (!node) node = nodes[0];
}
- if (!node) node = nodes[0];
+ if (!node) return;
toggleBlock(node.block);
toggleSuperBlock(node.superBlock);
diff --git a/client/src/components/layouts/global.css b/client/src/components/layouts/global.css
index 6d9bd6e807..4765b0352a 100644
--- a/client/src/components/layouts/global.css
+++ b/client/src/components/layouts/global.css
@@ -10,7 +10,7 @@ body {
.btn-cta-big {
max-height: 100%;
- font-size: 40px;
+ font-size: 27px;
white-space: normal;
}
diff --git a/client/src/components/welcome/Welcome.test.js b/client/src/components/welcome/Welcome.test.js
deleted file mode 100644
index a7c985f134..0000000000
--- a/client/src/components/welcome/Welcome.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* global expect */
-import React from 'react';
-import renderer from 'react-test-renderer';
-import ShallowRenderer from 'react-test-renderer/shallow';
-
-import 'jest-dom/extend-expect';
-import { LearnPage } from '../../pages/learn';
-
-import Welcome from './';
-
-import mockChallengeNodes from '../../__mocks__/challenge-nodes';
-import mockIntroNodes from '../../__mocks__/intro-nodes';
-
-describe('', () => {
- it('renders when visiting index page and logged in', () => {
- const shallow = new ShallowRenderer();
- shallow.render();
- const result = shallow.getRenderOutput();
- expect(
- result.type.WrappedComponent.displayName === 'LearnLayout'
- ).toBeTruthy();
- });
-
- it('has a header', () => {
- const container = renderer.create()
- .root;
- expect(container.findAllByType('h1').length === 1).toBeTruthy();
- });
-
- it('has a blockquote', () => {
- const container = renderer.create()
- .root;
- expect(container.findAllByType('blockquote').length === 1).toBeTruthy();
- });
-});
-
-const nodes = mockChallengeNodes.map(node => {
- return { node };
-});
-const loggedInProps = {
- fetchState: {
- complete: true,
- error: null,
- errored: false,
- pending: false
- },
- isSignedIn: true,
- user: {
- name: 'Development User'
- },
- location: { hash: '' },
- data: {
- challengeNode: {
- fields: {
- slug:
- // eslint-disable-next-line max-len
- '/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
- }
- },
- allChallengeNode: { edges: nodes },
- allMarkdownRemark: { edges: [{ mdEdges: mockIntroNodes }] }
- }
-};
diff --git a/client/src/components/welcome/index.js b/client/src/components/welcome/index.js
deleted file mode 100644
index 6b18041469..0000000000
--- a/client/src/components/welcome/index.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { Fragment } from 'react';
-import { Row, Col } from '@freecodecamp/react-bootstrap';
-import PropTypes from 'prop-types';
-
-import { Spacer } from '../helpers';
-import { randomQuote } from '../../utils/get-words';
-
-import './welcome.css';
-
-// created outside the function, so that the quotes don't change unless the
-// page is reloaded.
-
-const { quote, author } = randomQuote();
-
-function Welcome({ name }) {
- return (
-
-
-
-
-
- {name
- ? 'Welcome back, ' + name + '.'
- : 'Welcome to freeCodeCamp.org'}
-
-
-
-
-
-
-
-
- {quote}
-
-
-
-
-
-
- );
-}
-
-const propTypes = {
- name: PropTypes.string
-};
-
-Welcome.propTypes = propTypes;
-Welcome.displayName = 'Welcome';
-
-export default Welcome;
diff --git a/client/src/components/welcome/welcome.css b/client/src/components/welcome/welcome.css
deleted file mode 100644
index 099a0eada3..0000000000
--- a/client/src/components/welcome/welcome.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.quote-partial .blockquote {
- font-size: 1.3rem;
- border: none;
-}
-
-@media (max-width: 500px) {
- .quote-partial .blockquote {
- font-size: 1.2rem;
- border: none;
- }
-}
-.quote-author {
- font-style: normal;
-}
diff --git a/client/src/pages/learn.js b/client/src/pages/learn.js
index 8f1324b39b..d5646db551 100644
--- a/client/src/pages/learn.js
+++ b/client/src/pages/learn.js
@@ -1,24 +1,21 @@
import React from 'react';
-import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
+import { Grid } from '@freecodecamp/react-bootstrap';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import { graphql } from 'gatsby';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
+import LearnLayout from '../components/layouts/Learn';
+import { dasherize } from '../../../utils/slugs';
+import Map from '../components/Map';
+import Intro from '../components/Intro';
import {
userFetchStateSelector,
isSignedInSelector,
- userSelector
+ userSelector,
+ hardGoTo as navigate
} from '../redux';
-
-import LearnLayout from '../components/layouts/Learn';
-import Login from '../components/Header/components/Login';
-import { Link, Spacer } from '../components/helpers';
-import Map from '../components/Map';
-import Welcome from '../components/welcome';
-import { dasherize } from '../../../utils/slugs';
-
import {
ChallengeNode,
AllChallengeNode,
@@ -50,39 +47,30 @@ const propTypes = {
hash: PropTypes.string,
isSignedIn: PropTypes.bool,
location: PropTypes.object,
+ navigate: PropTypes.func.isRequired,
state: PropTypes.object,
user: PropTypes.shape({
- name: PropTypes.string
+ name: PropTypes.string,
+ username: PropTypes.string
})
};
-const BigCallToAction = isSignedIn => {
- if (!isSignedIn) {
- return (
- <>
-
-
-
- Sign in to save your progress.
-
-
- >
- );
- }
- return '';
-};
-
// choose between the state from landing page and hash from url.
const hashValueSelector = (state, hash) => {
if (state && state.superBlock) return dasherize(state.superBlock);
else if (hash) return hash.substr(1);
else return null;
};
+const mapDispatchToProps = {
+ navigate
+};
export const LearnPage = ({
location: { hash = '', state = '' },
isSignedIn,
- user: { name = '' },
+ navigate,
+ fetchState: { pending, complete },
+ user: { name = '', username = '' },
data: {
challengeNode: {
fields: { slug }
@@ -96,20 +84,19 @@ export const LearnPage = ({
-
-
-
- {BigCallToAction(isSignedIn)}
-
-
- If you are new to coding, we recommend you{' '}
- start at the beginning.
-
-
-
+