fix(client): merge and update the learn map (#36822)

pull/36835/head
mrugesh 2019-09-21 19:09:48 +05:30 committed by Ahmad Abdolsaheb
parent a5b176be88
commit e54a7fb350
16 changed files with 561 additions and 581 deletions

View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -30,8 +30,8 @@ describe('<NavLinks />', () => {
it('renders to the DOM', () => {
expect(root).toBeTruthy();
});
it('has 2 a tags', () => {
expect(aTags.length === 2).toBeTruthy();
it('has 3 a tags', () => {
expect(aTags.length === 3).toBeTruthy();
});
it('has link to portfolio', () => {

View File

@ -26,7 +26,7 @@ const createOnClick = (navigate, isSignedIn) => e => {
e.preventDefault();
gtagReportConversion();
if (isSignedIn) {
return gatsbyNavigate('/');
return gatsbyNavigate('/learn');
}
return navigate(`${apiLocation}/signin`);
};

View File

@ -5,6 +5,9 @@ export function NavLinks() {
return (
<div className='main-nav-group'>
<ul className={'nav-list display-flex'} role='menu'>
<li className='nav-theme' role='menuitem'>
<Link to='/learn'>Projects</Link>
</li>
<li className='nav-theme' role='menuitem'>
<Link to='/'>Light</Link>
</li>

View File

@ -1,99 +0,0 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import uniq from 'lodash/uniq';
import { createSelector } from 'reselect';
import SuperBlock from './components/SuperBlock';
import Spacer from '../helpers/Spacer';
import './map.css';
import { ChallengeNode } from '../../redux/propTypes';
import { toggleSuperBlock, toggleBlock, resetExpansion } from './redux';
import { currentChallengeIdSelector } from '../../redux';
const propTypes = {
currentChallengeId: PropTypes.string,
introNodes: PropTypes.arrayOf(
PropTypes.shape({
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
frontmatter: PropTypes.shape({
title: PropTypes.string.isRequired,
block: PropTypes.string.isRequired
})
})
),
nodes: PropTypes.arrayOf(ChallengeNode),
resetExpansion: PropTypes.func,
toggleBlock: PropTypes.func.isRequired,
toggleSuperBlock: PropTypes.func.isRequired
};
const mapStateToProps = state => {
return createSelector(
currentChallengeIdSelector,
currentChallengeId => ({
currentChallengeId
})
)(state);
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
resetExpansion,
toggleSuperBlock,
toggleBlock
},
dispatch
);
}
export class Map extends Component {
componentDidMount() {
this.initializeExpandedState(this.props.currentChallengeId);
}
initializeExpandedState(currentChallengeId) {
this.props.resetExpansion();
const { superBlock, block } = currentChallengeId
? this.props.nodes.find(node => node.id === currentChallengeId)
: this.props.nodes[0];
this.props.toggleBlock(block);
this.props.toggleSuperBlock(superBlock);
}
renderSuperBlocks(superBlocks) {
const { nodes, introNodes } = this.props;
return superBlocks.map(superBlock => (
<SuperBlock
introNodes={introNodes}
key={superBlock}
nodes={nodes}
superBlock={superBlock}
/>
));
}
render() {
const { nodes } = this.props;
const superBlocks = uniq(nodes.map(({ superBlock }) => superBlock));
return (
<div className='map-ui'>
<ul>
{this.renderSuperBlocks(superBlocks)}
<Spacer />
</ul>
</div>
);
}
}
Map.displayName = 'Map';
Map.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Map);

View File

@ -6,8 +6,8 @@ import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import store from 'store';
import { Map } from './Map';
import mockNodes from '../../__mocks__/map-nodes';
import { Map } from './';
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
import mockIntroNodes from '../../__mocks__/intro-nodes';
Enzyme.configure({ adapter: new Adapter() });
@ -15,7 +15,7 @@ const renderer = new ShallowRenderer();
const baseProps = {
introNodes: mockIntroNodes,
nodes: mockNodes,
nodes: mockChallengeNodes,
toggleBlock: () => {},
toggleSuperBlock: () => {},
resetExpansion: () => {}
@ -25,7 +25,7 @@ test('<Map /> snapshot', () => {
const componentToRender = (
<Map
introNodes={mockIntroNodes}
nodes={mockNodes}
nodes={mockChallengeNodes}
toggleBlock={() => {}}
toggleSuperBlock={() => {}}
/>
@ -45,7 +45,7 @@ describe('<Map/>', () => {
store.clearAll();
});
// 7 was chosen because it has a different superblock from the first node.
const currentChallengeId = mockNodes[7].id;
const currentChallengeId = mockChallengeNodes[7].id;
it('should expand the block with the most recent challenge', () => {
const blockSpy = jest.fn();
@ -59,10 +59,10 @@ describe('<Map/>', () => {
const mapToRender = <Map {...props} />;
shallow(mapToRender);
expect(blockSpy).toHaveBeenCalledTimes(1);
expect(blockSpy).toHaveBeenCalledWith(mockNodes[7].block);
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[7].block);
expect(superSpy).toHaveBeenCalledTimes(1);
expect(superSpy).toHaveBeenCalledWith(mockNodes[7].superBlock);
expect(superSpy).toHaveBeenCalledWith(mockChallengeNodes[7].superBlock);
});
it('should use the currentChallengeId prop if it exists', () => {
@ -85,13 +85,13 @@ describe('<Map/>', () => {
const mapToRender = <Map {...props} />;
shallow(mapToRender);
expect(blockSpy).toHaveBeenCalledTimes(1);
expect(blockSpy).toHaveBeenCalledWith(mockNodes[0].block);
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[0].block);
expect(superSpy).toHaveBeenCalledTimes(1);
expect(superSpy).toHaveBeenCalledWith(mockNodes[0].superBlock);
expect(superSpy).toHaveBeenCalledWith(mockChallengeNodes[0].superBlock);
});
it('calls resetExpansion when initialising', () => {
it('calls resetExpansion when initializing', () => {
const expansionSpy = jest.fn();
const props = {
...baseProps,

View File

@ -1,321 +1,334 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Map /> snapshot: Map 1`] = `
<div
className="map-ui"
<Row
bsClass="row"
componentClass="div"
>
<ul>
<Connect(SuperBlock)
introNodes={
Array [
Object {
"fields": Object {
"slug": "/super-block-one/block-a",
},
"frontmatter": Object {
"block": "Block A",
"title": "Introduction to Block A",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-b",
},
"frontmatter": Object {
"block": "Block B",
"title": "Introduction to Block B",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-c",
},
"frontmatter": Object {
"block": "Block C",
"title": "Introduction to Block C",
},
},
]
}
nodes={
Array [
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "a",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "b",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "c",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "d",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-c",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block C",
"slug": "/super-block-one/block-c/challenge-one",
},
"id": "e",
"isPrivate": true,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "f",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "g",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "h",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "i",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
]
}
superBlock="Super Block One"
/>
<Connect(SuperBlock)
introNodes={
Array [
Object {
"fields": Object {
"slug": "/super-block-one/block-a",
},
"frontmatter": Object {
"block": "Block A",
"title": "Introduction to Block A",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-b",
},
"frontmatter": Object {
"block": "Block B",
"title": "Introduction to Block B",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-c",
},
"frontmatter": Object {
"block": "Block C",
"title": "Introduction to Block C",
},
},
]
}
nodes={
Array [
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "a",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "b",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "c",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "d",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-c",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block C",
"slug": "/super-block-one/block-c/challenge-one",
},
"id": "e",
"isPrivate": true,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "f",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "g",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "h",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "i",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
]
}
superBlock="Super Block Two"
/>
<Spacer />
</ul>
</div>
<Col
bsClass="col"
componentClass="div"
sm={10}
smOffset={1}
xs={12}
>
<div
className="map-ui"
>
<ul>
<Connect(SuperBlock)
introNodes={
Array [
Object {
"fields": Object {
"slug": "/super-block-one/block-a",
},
"frontmatter": Object {
"block": "Block A",
"title": "Introduction to Block A",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-b",
},
"frontmatter": Object {
"block": "Block B",
"title": "Introduction to Block B",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-c",
},
"frontmatter": Object {
"block": "Block C",
"title": "Introduction to Block C",
},
},
]
}
nodes={
Array [
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "a",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "b",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "c",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "d",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-c",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block C",
"slug": "/super-block-one/block-c/challenge-one",
},
"id": "e",
"isPrivate": true,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "f",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "g",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "h",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "i",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
]
}
superBlock="Super Block One"
/>
<Connect(SuperBlock)
introNodes={
Array [
Object {
"fields": Object {
"slug": "/super-block-one/block-a",
},
"frontmatter": Object {
"block": "Block A",
"title": "Introduction to Block A",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-b",
},
"frontmatter": Object {
"block": "Block B",
"title": "Introduction to Block B",
},
},
Object {
"fields": Object {
"slug": "/super-block-one/block-c",
},
"frontmatter": Object {
"block": "Block C",
"title": "Introduction to Block C",
},
},
]
}
nodes={
Array [
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "a",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "b",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "c",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "d",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge Two",
},
Object {
"block": "block-c",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block C",
"slug": "/super-block-one/block-c/challenge-one",
},
"id": "e",
"isPrivate": true,
"isRequired": false,
"superBlock": "Super Block One",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-one",
},
"id": "f",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-a",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block A",
"slug": "/super-block-one/block-a/challenge-two",
},
"id": "g",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
Object {
"block": "block-b",
"dashedName": "challenge-one",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-one",
},
"id": "h",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge One",
},
Object {
"block": "block-b",
"dashedName": "challenge-two",
"fields": Object {
"blockName": "Block B",
"slug": "/super-block-one/block-b/challenge-two",
},
"id": "i",
"isPrivate": false,
"isRequired": false,
"superBlock": "Super Block Two",
"title": "Challenge Two",
},
]
}
superBlock="Super Block Two"
/>
<Spacer />
</ul>
</div>
</Col>
</Row>
`;

View File

@ -7,7 +7,7 @@ import Adapter from 'enzyme-adapter-react-16';
import sinon from 'sinon';
import { Block } from './Block';
import mockNodes from '../../../__mocks__/map-nodes';
import mockChallengeNodes from '../../../__mocks__/challenge-nodes';
import mockIntroNodes from '../../../__mocks__/intro-nodes';
import mockCompleted from '../../../__mocks__/completedChallengesMock';
@ -20,7 +20,7 @@ test('<Block /> not expanded snapshot', () => {
const componentToRender = (
<Block
blockDashedName='block-a'
challenges={mockNodes.filter(node => node.block === 'block-a')}
challenges={mockChallengeNodes.filter(node => node.block === 'block-a')}
completedChallenges={mockCompleted}
intro={mockIntroNodes[0]}
isExpanded={false}
@ -39,7 +39,7 @@ test('<Block expanded snapshot', () => {
const componentToRender = (
<Block
blockDashedName='block-a'
challenges={mockNodes.filter(node => node.block === 'block-a')}
challenges={mockChallengeNodes.filter(node => node.block === 'block-a')}
completedChallenges={mockCompleted}
intro={mockIntroNodes[0]}
isExpanded={true}
@ -58,7 +58,7 @@ test('<Block /> should handle toggle clicks correctly', () => {
const toggleMapSpy = sinon.spy();
const props = {
blockDashedName: 'block-a',
challenges: mockNodes.filter(node => node.block === 'block-a'),
challenges: mockChallengeNodes.filter(node => node.block === 'block-a'),
completedChallenges: mockCompleted,
intro: mockIntroNodes[0],
isExpanded: false,

View File

@ -7,7 +7,7 @@ import Adapter from 'enzyme-adapter-react-16';
import sinon from 'sinon';
import { SuperBlock } from './SuperBlock';
import mockNodes from '../../../__mocks__/map-nodes';
import mockChallengeNodes from '../../../__mocks__/challenge-nodes';
import mockIntroNodes from '../../../__mocks__/intro-nodes';
Enzyme.configure({ adapter: new Adapter() });
@ -18,7 +18,7 @@ test('<SuperBlock /> not expanded snapshot', () => {
const props = {
introNodes: mockIntroNodes,
isExpanded: false,
nodes: mockNodes,
nodes: mockChallengeNodes,
superBlock: 'Super Block One',
toggleSuperBlock: toggleSpy
};
@ -33,7 +33,7 @@ test('<SuperBlock /> expanded snapshot', () => {
const props = {
introNodes: mockIntroNodes,
isExpanded: true,
nodes: mockNodes,
nodes: mockChallengeNodes,
superBlock: 'Super Block One',
toggleSuperBlock: toggleSpy
};
@ -48,7 +48,7 @@ test('<SuperBlock should handle toggle clicks correctly', () => {
const props = {
introNodes: mockIntroNodes,
isExpanded: false,
nodes: mockNodes,
nodes: mockChallengeNodes,
superBlock: 'Super Block One',
toggleSuperBlock: toggleSpy
};

View File

@ -1 +1,104 @@
export default from './Map.js';
import React, { Component } from 'react';
import { Row, Col } from '@freecodecamp/react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import uniq from 'lodash/uniq';
import { createSelector } from 'reselect';
import SuperBlock from './components/SuperBlock';
import Spacer from '../helpers/Spacer';
import './map.css';
import { ChallengeNode } from '../../redux/propTypes';
import { toggleSuperBlock, toggleBlock, resetExpansion } from './redux';
import { currentChallengeIdSelector } from '../../redux';
const propTypes = {
currentChallengeId: PropTypes.string,
introNodes: PropTypes.arrayOf(
PropTypes.shape({
fields: PropTypes.shape({ slug: PropTypes.string.isRequired }),
frontmatter: PropTypes.shape({
title: PropTypes.string.isRequired,
block: PropTypes.string.isRequired
})
})
),
nodes: PropTypes.arrayOf(ChallengeNode),
resetExpansion: PropTypes.func,
toggleBlock: PropTypes.func.isRequired,
toggleSuperBlock: PropTypes.func.isRequired
};
const mapStateToProps = state => {
return createSelector(
currentChallengeIdSelector,
currentChallengeId => ({
currentChallengeId
})
)(state);
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
resetExpansion,
toggleSuperBlock,
toggleBlock
},
dispatch
);
}
export class Map extends Component {
componentDidMount() {
this.initializeExpandedState(this.props.currentChallengeId);
}
initializeExpandedState(currentChallengeId) {
this.props.resetExpansion();
const { superBlock, block } = currentChallengeId
? this.props.nodes.find(node => node.id === currentChallengeId)
: this.props.nodes[0];
this.props.toggleBlock(block);
this.props.toggleSuperBlock(superBlock);
}
renderSuperBlocks(superBlocks) {
const { nodes, introNodes } = this.props;
return superBlocks.map(superBlock => (
<SuperBlock
introNodes={introNodes}
key={superBlock}
nodes={nodes}
superBlock={superBlock}
/>
));
}
render() {
const { nodes } = this.props;
const superBlocks = uniq(nodes.map(({ superBlock }) => superBlock));
return (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<div className='map-ui'>
<ul>
{this.renderSuperBlocks(superBlocks)}
<Spacer />
</ul>
</div>
</Col>
</Row>
);
}
}
Map.displayName = 'Map';
Map.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Map);

View File

@ -1,27 +1,40 @@
/* 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 { IndexPage } from '../../pages';
import { LearnPage } from '../../pages/learn';
import Welcome from './';
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
import mockIntroNodes from '../../__mocks__/intro-nodes';
describe('<Welcome />', () => {
it('renders when visiting index page and logged in', () => {
const container = renderer
.create(<IndexPage {...loggedInProps} />)
.toTree();
expect(container.rendered.type.displayName === 'Welcome').toBeTruthy();
const shallow = new ShallowRenderer();
shallow.render(<LearnPage {...loggedInProps} />);
const result = shallow.getRenderOutput();
expect(result.type.displayName === 'LearnLayout').toBeTruthy();
});
it('has four links', () => {
const container = renderer.create(<Welcome name={'developmentuser'} />)
it('has a header', () => {
const container = renderer.create(<Welcome name={'Development User'} />)
.root;
expect(container.findAllByType('a').length === 4).toBeTruthy();
expect(container.findAllByType('h1').length === 1).toBeTruthy();
});
it('has a blockquote', () => {
const container = renderer.create(<Welcome name={'Development User'} />)
.root;
expect(container.findAllByType('blockquote').length === 1).toBeTruthy();
});
});
const nodes = mockChallengeNodes.map(node => {
return { node };
});
const loggedInProps = {
fetchState: {
complete: true,
@ -31,7 +44,17 @@ const loggedInProps = {
},
isSignedIn: true,
user: {
acceptedPrivacyTerms: true,
username: 'developmentuser'
name: 'Development User'
},
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 }] }
}
};

View File

@ -1,9 +1,8 @@
import React, { Fragment } from 'react';
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
import Helmet from 'react-helmet';
import { Row, Col } from '@freecodecamp/react-bootstrap';
import PropTypes from 'prop-types';
import { Spacer, Link } from '../helpers';
import { Spacer } from '../helpers';
import { randomQuote } from '../../utils/get-words';
import './welcome.css';
@ -12,68 +11,35 @@ function Welcome({ name }) {
const { quote, author } = randomQuote();
return (
<Fragment>
<Helmet>
<title>Welcome | freeCodeCamp.org</title>
</Helmet>
<main>
<Grid>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<h1 className='text-center big-heading'>
Welcome {name ? name : 'Camper'}!
</h1>
</Col>
</Row>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<Row className='text-center quote-partial'>
<Col sm={10} smOffset={1} xs={12}>
<blockquote className='blockquote'>
<span>
<q>{quote}</q>
<footer className='quote-author blockquote-footer'>
<cite>{author}</cite>
</footer>
</span>
</blockquote>
</Col>
</Row>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<h2 className='text-center medium-heading'>
What would you like to do today?
</h2>
</Col>
</Row>
<h1 className='text-center big-heading'>
{name ? 'Welcome back ' + name : 'Welcome to freeCodeCamp.org'}
</h1>
</Col>
</Row>
<Spacer />
<Row className='text-center quote-partial'>
<Col sm={10} smOffset={1} xs={12}>
<blockquote className='blockquote'>
<span>
<q>{quote}</q>
<footer className='quote-author blockquote-footer'>
<cite>{author}</cite>
</footer>
</span>
</blockquote>
</Col>
</Row>
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<Row>
<Col sm={10} smOffset={1} xs={12}>
<Link className='btn btn-lg btn-primary btn-block' to='/learn'>
Build Projects and Earn Certifications
</Link>
<Link className='btn btn-lg btn-primary btn-block' to='/settings'>
Update Your Developer Portfolio
</Link>
<Link
className='btn btn-lg btn-primary btn-block'
external={true}
to='/news'
>
Read Developer News
</Link>
<Link
className='btn btn-lg btn-primary btn-block'
external={true}
to='/forum'
>
Help Developers on the Forum
</Link>
</Col>
</Row>
<Spacer size={4} />
</Grid>
</main>
<h2 className='text-center medium-heading'>
What would you like to do today?
</h2>
</Col>
</Row>
</Fragment>
);
}

View File

@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Loader } from '../components/helpers';
import Welcome from '../components/welcome';
import Landing from '../components/landing';
import {
userSelector,
@ -25,11 +24,12 @@ const mapStateToProps = createSelector(
);
const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
const RedirectLearn = createRedirect('/learn');
export const IndexPage = ({
fetchState: { pending, complete },
isSignedIn,
user: { acceptedPrivacyTerms, name = '' }
user: { acceptedPrivacyTerms }
}) => {
if (pending && !complete) {
return <Loader fullScreen={true} />;
@ -40,7 +40,7 @@ export const IndexPage = ({
}
if (isSignedIn) {
return <Welcome name={name} />;
return <RedirectLearn />;
}
return <Landing />;
@ -54,14 +54,7 @@ const propTypes = {
}),
isSignedIn: PropTypes.bool,
user: PropTypes.shape({
acceptedPrivacyTerms: PropTypes.bool,
completedCertCount: PropTypes.number,
completedChallengeCount: PropTypes.number,
completedLegacyCertCount: PropTypes.number,
completedProjectCount: PropTypes.number,
isDonating: PropTypes.bool,
name: PropTypes.string,
username: PropTypes.string
acceptedPrivacyTerms: PropTypes.bool
})
};

View File

@ -1,22 +0,0 @@
.learn-page-wrapper {
display: flex;
flex-direction: column;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
top: 38px;
}
.learn-page-wrapper .signup-btn {
width: 100%;
}
@media screen and (max-width: 630px) {
.learn-page-wrapper {
padding: 0 40px;
}
}
.map-ui p {
margin-bottom: 0rem;
}

View File

@ -1,19 +1,22 @@
import React from 'react';
import { Grid, Row, Col } 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 { Row, Col } from '@freecodecamp/react-bootstrap';
import { userFetchStateSelector, isSignedInSelector } from '../redux';
import {
userFetchStateSelector,
isSignedInSelector,
userSelector
} from '../redux';
import LearnLayout from '../components/layouts/Learn';
import Login from '../components/Header/components/Login';
import { Link, Spacer, Loader } from '../components/helpers';
import Map from '../components/Map';
import './learn.css';
import Welcome from '../components/welcome';
import {
ChallengeNode,
@ -24,9 +27,11 @@ import {
const mapStateToProps = createSelector(
userFetchStateSelector,
isSignedInSelector,
(fetchState, isSignedIn) => ({
userSelector,
(fetchState, isSignedIn, user) => ({
fetchState,
isSignedIn
isSignedIn,
user
})
);
@ -41,17 +46,20 @@ const propTypes = {
complete: PropTypes.bool,
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool
isSignedIn: PropTypes.bool,
user: PropTypes.shape({
name: PropTypes.string
})
};
const BigCallToAction = isSignedIn => {
if (!isSignedIn) {
return (
<>
<Spacer size={2} />
<Row>
<Col sm={8} smOffset={2} xs={12}>
<Login className={'text-center'}>Sign in to save progress.</Login>
<Col sm={10} smOffset={1} xs={12}>
<Spacer />
<Login>Sign in to save your progress.</Login>
</Col>
</Row>
</>
@ -60,9 +68,10 @@ const BigCallToAction = isSignedIn => {
return '';
};
const IndexPage = ({
export const LearnPage = ({
fetchState: { pending, complete },
isSignedIn,
user: { name = '' },
data: {
challengeNode: {
fields: { slug }
@ -77,44 +86,34 @@ const IndexPage = ({
return (
<LearnLayout>
<div className='learn-page-wrapper'>
<Helmet title='Learn | freeCodeCamp.org' />
{BigCallToAction(isSignedIn)}
<Spacer size={2} />
<h1 className='text-center'>Welcome to the freeCodeCamp curriculum</h1>
<p>
We have thousands of coding lessons to help you improve your skills.
</p>
<p>
You can earn each certification by completing its 5 final projects.
</p>
<p>
And yes - all of this is 100% free, thanks to the thousands of campers
who{' '}
<Link external={true} to='/donate'>
donate
</Link>{' '}
to our nonprofit.
</p>
<p>
If you are new to coding, we recommend you{' '}
<Link to={slug}>start at the beginning</Link>.
</p>
<Helmet title='Learn | freeCodeCamp.org' />
<Grid>
<Welcome name={name} />
<Row className='text-center'>
<Col sm={10} smOffset={1} xs={12}>
{BigCallToAction(isSignedIn)}
<Spacer />
<h3>
If you are new to coding, we recommend you{' '}
<Link to={slug}>start at the beginning</Link>.
</h3>
</Col>
</Row>
<Map
introNodes={mdEdges.map(({ node }) => node)}
nodes={edges
.map(({ node }) => node)
.filter(({ isPrivate }) => !isPrivate)}
/>
</div>
</Grid>
</LearnLayout>
);
};
IndexPage.displayName = 'IndexPage';
IndexPage.propTypes = propTypes;
LearnPage.displayName = 'LearnPage';
LearnPage.propTypes = propTypes;
export default connect(mapStateToProps)(IndexPage);
export default connect(mapStateToProps)(LearnPage);
export const query = graphql`
query FirstChallenge {