fix(client): merge and update the learn map (#36822)
This commit is contained in:
committed by
Ahmad Abdolsaheb
parent
a5b176be88
commit
e54a7fb350
1
client/src/__mocks__/fileMock.js
Normal file
1
client/src/__mocks__/fileMock.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = {};
|
@ -30,8 +30,8 @@ describe('<NavLinks />', () => {
|
|||||||
it('renders to the DOM', () => {
|
it('renders to the DOM', () => {
|
||||||
expect(root).toBeTruthy();
|
expect(root).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('has 2 a tags', () => {
|
it('has 3 a tags', () => {
|
||||||
expect(aTags.length === 2).toBeTruthy();
|
expect(aTags.length === 3).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has link to portfolio', () => {
|
it('has link to portfolio', () => {
|
||||||
|
@ -26,7 +26,7 @@ const createOnClick = (navigate, isSignedIn) => e => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
gtagReportConversion();
|
gtagReportConversion();
|
||||||
if (isSignedIn) {
|
if (isSignedIn) {
|
||||||
return gatsbyNavigate('/');
|
return gatsbyNavigate('/learn');
|
||||||
}
|
}
|
||||||
return navigate(`${apiLocation}/signin`);
|
return navigate(`${apiLocation}/signin`);
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,9 @@ export function NavLinks() {
|
|||||||
return (
|
return (
|
||||||
<div className='main-nav-group'>
|
<div className='main-nav-group'>
|
||||||
<ul className={'nav-list display-flex'} role='menu'>
|
<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'>
|
<li className='nav-theme' role='menuitem'>
|
||||||
<Link to='/'>Light</Link>
|
<Link to='/'>Light</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@ -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);
|
|
@ -6,8 +6,8 @@ import Enzyme, { shallow } from 'enzyme';
|
|||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import { Map } from './Map';
|
import { Map } from './';
|
||||||
import mockNodes from '../../__mocks__/map-nodes';
|
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
|
||||||
import mockIntroNodes from '../../__mocks__/intro-nodes';
|
import mockIntroNodes from '../../__mocks__/intro-nodes';
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
Enzyme.configure({ adapter: new Adapter() });
|
||||||
@ -15,7 +15,7 @@ const renderer = new ShallowRenderer();
|
|||||||
|
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
introNodes: mockIntroNodes,
|
introNodes: mockIntroNodes,
|
||||||
nodes: mockNodes,
|
nodes: mockChallengeNodes,
|
||||||
toggleBlock: () => {},
|
toggleBlock: () => {},
|
||||||
toggleSuperBlock: () => {},
|
toggleSuperBlock: () => {},
|
||||||
resetExpansion: () => {}
|
resetExpansion: () => {}
|
||||||
@ -25,7 +25,7 @@ test('<Map /> snapshot', () => {
|
|||||||
const componentToRender = (
|
const componentToRender = (
|
||||||
<Map
|
<Map
|
||||||
introNodes={mockIntroNodes}
|
introNodes={mockIntroNodes}
|
||||||
nodes={mockNodes}
|
nodes={mockChallengeNodes}
|
||||||
toggleBlock={() => {}}
|
toggleBlock={() => {}}
|
||||||
toggleSuperBlock={() => {}}
|
toggleSuperBlock={() => {}}
|
||||||
/>
|
/>
|
||||||
@ -45,7 +45,7 @@ describe('<Map/>', () => {
|
|||||||
store.clearAll();
|
store.clearAll();
|
||||||
});
|
});
|
||||||
// 7 was chosen because it has a different superblock from the first node.
|
// 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', () => {
|
it('should expand the block with the most recent challenge', () => {
|
||||||
const blockSpy = jest.fn();
|
const blockSpy = jest.fn();
|
||||||
@ -59,10 +59,10 @@ describe('<Map/>', () => {
|
|||||||
const mapToRender = <Map {...props} />;
|
const mapToRender = <Map {...props} />;
|
||||||
shallow(mapToRender);
|
shallow(mapToRender);
|
||||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(blockSpy).toHaveBeenCalledWith(mockNodes[7].block);
|
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[7].block);
|
||||||
|
|
||||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
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', () => {
|
it('should use the currentChallengeId prop if it exists', () => {
|
||||||
@ -85,13 +85,13 @@ describe('<Map/>', () => {
|
|||||||
const mapToRender = <Map {...props} />;
|
const mapToRender = <Map {...props} />;
|
||||||
shallow(mapToRender);
|
shallow(mapToRender);
|
||||||
expect(blockSpy).toHaveBeenCalledTimes(1);
|
expect(blockSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(blockSpy).toHaveBeenCalledWith(mockNodes[0].block);
|
expect(blockSpy).toHaveBeenCalledWith(mockChallengeNodes[0].block);
|
||||||
|
|
||||||
expect(superSpy).toHaveBeenCalledTimes(1);
|
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 expansionSpy = jest.fn();
|
||||||
const props = {
|
const props = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
|
@ -1,321 +1,334 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`<Map /> snapshot: Map 1`] = `
|
exports[`<Map /> snapshot: Map 1`] = `
|
||||||
<div
|
<Row
|
||||||
className="map-ui"
|
bsClass="row"
|
||||||
|
componentClass="div"
|
||||||
>
|
>
|
||||||
<ul>
|
<Col
|
||||||
<Connect(SuperBlock)
|
bsClass="col"
|
||||||
introNodes={
|
componentClass="div"
|
||||||
Array [
|
sm={10}
|
||||||
Object {
|
smOffset={1}
|
||||||
"fields": Object {
|
xs={12}
|
||||||
"slug": "/super-block-one/block-a",
|
>
|
||||||
},
|
<div
|
||||||
"frontmatter": Object {
|
className="map-ui"
|
||||||
"block": "Block A",
|
>
|
||||||
"title": "Introduction to Block A",
|
<ul>
|
||||||
},
|
<Connect(SuperBlock)
|
||||||
},
|
introNodes={
|
||||||
Object {
|
Array [
|
||||||
"fields": Object {
|
Object {
|
||||||
"slug": "/super-block-one/block-b",
|
"fields": Object {
|
||||||
},
|
"slug": "/super-block-one/block-a",
|
||||||
"frontmatter": Object {
|
},
|
||||||
"block": "Block B",
|
"frontmatter": Object {
|
||||||
"title": "Introduction to Block B",
|
"block": "Block A",
|
||||||
},
|
"title": "Introduction to Block A",
|
||||||
},
|
},
|
||||||
Object {
|
},
|
||||||
"fields": Object {
|
Object {
|
||||||
"slug": "/super-block-one/block-c",
|
"fields": Object {
|
||||||
},
|
"slug": "/super-block-one/block-b",
|
||||||
"frontmatter": Object {
|
},
|
||||||
"block": "Block C",
|
"frontmatter": Object {
|
||||||
"title": "Introduction to Block C",
|
"block": "Block B",
|
||||||
},
|
"title": "Introduction to Block B",
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
}
|
Object {
|
||||||
nodes={
|
"fields": Object {
|
||||||
Array [
|
"slug": "/super-block-one/block-c",
|
||||||
Object {
|
},
|
||||||
"block": "block-a",
|
"frontmatter": Object {
|
||||||
"dashedName": "challenge-one",
|
"block": "Block C",
|
||||||
"fields": Object {
|
"title": "Introduction to Block C",
|
||||||
"blockName": "Block A",
|
},
|
||||||
"slug": "/super-block-one/block-a/challenge-one",
|
},
|
||||||
},
|
]
|
||||||
"id": "a",
|
}
|
||||||
"isPrivate": false,
|
nodes={
|
||||||
"isRequired": false,
|
Array [
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-a/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "a",
|
||||||
"slug": "/super-block-one/block-a/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "b",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-a/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "b",
|
||||||
"slug": "/super-block-one/block-b/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "c",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-b/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "c",
|
||||||
"slug": "/super-block-one/block-b/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "d",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-c",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-b/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block C",
|
"id": "d",
|
||||||
"slug": "/super-block-one/block-c/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "e",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": true,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-c",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block C",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-c/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "e",
|
||||||
"slug": "/super-block-one/block-a/challenge-one",
|
"isPrivate": true,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "f",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-a/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "f",
|
||||||
"slug": "/super-block-one/block-a/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "g",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-a/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "g",
|
||||||
"slug": "/super-block-one/block-b/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "h",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-b/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "h",
|
||||||
"slug": "/super-block-one/block-b/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "i",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
]
|
"fields": Object {
|
||||||
}
|
"blockName": "Block B",
|
||||||
superBlock="Super Block One"
|
"slug": "/super-block-one/block-b/challenge-two",
|
||||||
/>
|
},
|
||||||
<Connect(SuperBlock)
|
"id": "i",
|
||||||
introNodes={
|
"isPrivate": false,
|
||||||
Array [
|
"isRequired": false,
|
||||||
Object {
|
"superBlock": "Super Block Two",
|
||||||
"fields": Object {
|
"title": "Challenge Two",
|
||||||
"slug": "/super-block-one/block-a",
|
},
|
||||||
},
|
]
|
||||||
"frontmatter": Object {
|
}
|
||||||
"block": "Block A",
|
superBlock="Super Block One"
|
||||||
"title": "Introduction to Block A",
|
/>
|
||||||
},
|
<Connect(SuperBlock)
|
||||||
},
|
introNodes={
|
||||||
Object {
|
Array [
|
||||||
"fields": Object {
|
Object {
|
||||||
"slug": "/super-block-one/block-b",
|
"fields": Object {
|
||||||
},
|
"slug": "/super-block-one/block-a",
|
||||||
"frontmatter": Object {
|
},
|
||||||
"block": "Block B",
|
"frontmatter": Object {
|
||||||
"title": "Introduction to Block B",
|
"block": "Block A",
|
||||||
},
|
"title": "Introduction to Block A",
|
||||||
},
|
},
|
||||||
Object {
|
},
|
||||||
"fields": Object {
|
Object {
|
||||||
"slug": "/super-block-one/block-c",
|
"fields": Object {
|
||||||
},
|
"slug": "/super-block-one/block-b",
|
||||||
"frontmatter": Object {
|
},
|
||||||
"block": "Block C",
|
"frontmatter": Object {
|
||||||
"title": "Introduction to Block C",
|
"block": "Block B",
|
||||||
},
|
"title": "Introduction to Block B",
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
}
|
Object {
|
||||||
nodes={
|
"fields": Object {
|
||||||
Array [
|
"slug": "/super-block-one/block-c",
|
||||||
Object {
|
},
|
||||||
"block": "block-a",
|
"frontmatter": Object {
|
||||||
"dashedName": "challenge-one",
|
"block": "Block C",
|
||||||
"fields": Object {
|
"title": "Introduction to Block C",
|
||||||
"blockName": "Block A",
|
},
|
||||||
"slug": "/super-block-one/block-a/challenge-one",
|
},
|
||||||
},
|
]
|
||||||
"id": "a",
|
}
|
||||||
"isPrivate": false,
|
nodes={
|
||||||
"isRequired": false,
|
Array [
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-a/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "a",
|
||||||
"slug": "/super-block-one/block-a/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "b",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-a/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "b",
|
||||||
"slug": "/super-block-one/block-b/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "c",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-b/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "c",
|
||||||
"slug": "/super-block-one/block-b/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "d",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-c",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-b/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block C",
|
"id": "d",
|
||||||
"slug": "/super-block-one/block-c/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "e",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": true,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block One",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-c",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block C",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-c/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "e",
|
||||||
"slug": "/super-block-one/block-a/challenge-one",
|
"isPrivate": true,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "f",
|
"superBlock": "Super Block One",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-a",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-a/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block A",
|
"id": "f",
|
||||||
"slug": "/super-block-one/block-a/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "g",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-a",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block A",
|
||||||
"dashedName": "challenge-one",
|
"slug": "/super-block-one/block-a/challenge-two",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "g",
|
||||||
"slug": "/super-block-one/block-b/challenge-one",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "h",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge Two",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge One",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-one",
|
||||||
Object {
|
"fields": Object {
|
||||||
"block": "block-b",
|
"blockName": "Block B",
|
||||||
"dashedName": "challenge-two",
|
"slug": "/super-block-one/block-b/challenge-one",
|
||||||
"fields": Object {
|
},
|
||||||
"blockName": "Block B",
|
"id": "h",
|
||||||
"slug": "/super-block-one/block-b/challenge-two",
|
"isPrivate": false,
|
||||||
},
|
"isRequired": false,
|
||||||
"id": "i",
|
"superBlock": "Super Block Two",
|
||||||
"isPrivate": false,
|
"title": "Challenge One",
|
||||||
"isRequired": false,
|
},
|
||||||
"superBlock": "Super Block Two",
|
Object {
|
||||||
"title": "Challenge Two",
|
"block": "block-b",
|
||||||
},
|
"dashedName": "challenge-two",
|
||||||
]
|
"fields": Object {
|
||||||
}
|
"blockName": "Block B",
|
||||||
superBlock="Super Block Two"
|
"slug": "/super-block-one/block-b/challenge-two",
|
||||||
/>
|
},
|
||||||
<Spacer />
|
"id": "i",
|
||||||
</ul>
|
"isPrivate": false,
|
||||||
</div>
|
"isRequired": false,
|
||||||
|
"superBlock": "Super Block Two",
|
||||||
|
"title": "Challenge Two",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
superBlock="Super Block Two"
|
||||||
|
/>
|
||||||
|
<Spacer />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
`;
|
`;
|
||||||
|
@ -7,7 +7,7 @@ import Adapter from 'enzyme-adapter-react-16';
|
|||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import { Block } from './Block';
|
import { Block } from './Block';
|
||||||
import mockNodes from '../../../__mocks__/map-nodes';
|
import mockChallengeNodes from '../../../__mocks__/challenge-nodes';
|
||||||
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
||||||
import mockCompleted from '../../../__mocks__/completedChallengesMock';
|
import mockCompleted from '../../../__mocks__/completedChallengesMock';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ test('<Block /> not expanded snapshot', () => {
|
|||||||
const componentToRender = (
|
const componentToRender = (
|
||||||
<Block
|
<Block
|
||||||
blockDashedName='block-a'
|
blockDashedName='block-a'
|
||||||
challenges={mockNodes.filter(node => node.block === 'block-a')}
|
challenges={mockChallengeNodes.filter(node => node.block === 'block-a')}
|
||||||
completedChallenges={mockCompleted}
|
completedChallenges={mockCompleted}
|
||||||
intro={mockIntroNodes[0]}
|
intro={mockIntroNodes[0]}
|
||||||
isExpanded={false}
|
isExpanded={false}
|
||||||
@ -39,7 +39,7 @@ test('<Block expanded snapshot', () => {
|
|||||||
const componentToRender = (
|
const componentToRender = (
|
||||||
<Block
|
<Block
|
||||||
blockDashedName='block-a'
|
blockDashedName='block-a'
|
||||||
challenges={mockNodes.filter(node => node.block === 'block-a')}
|
challenges={mockChallengeNodes.filter(node => node.block === 'block-a')}
|
||||||
completedChallenges={mockCompleted}
|
completedChallenges={mockCompleted}
|
||||||
intro={mockIntroNodes[0]}
|
intro={mockIntroNodes[0]}
|
||||||
isExpanded={true}
|
isExpanded={true}
|
||||||
@ -58,7 +58,7 @@ test('<Block /> should handle toggle clicks correctly', () => {
|
|||||||
const toggleMapSpy = sinon.spy();
|
const toggleMapSpy = sinon.spy();
|
||||||
const props = {
|
const props = {
|
||||||
blockDashedName: 'block-a',
|
blockDashedName: 'block-a',
|
||||||
challenges: mockNodes.filter(node => node.block === 'block-a'),
|
challenges: mockChallengeNodes.filter(node => node.block === 'block-a'),
|
||||||
completedChallenges: mockCompleted,
|
completedChallenges: mockCompleted,
|
||||||
intro: mockIntroNodes[0],
|
intro: mockIntroNodes[0],
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
|
@ -7,7 +7,7 @@ import Adapter from 'enzyme-adapter-react-16';
|
|||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import { SuperBlock } from './SuperBlock';
|
import { SuperBlock } from './SuperBlock';
|
||||||
import mockNodes from '../../../__mocks__/map-nodes';
|
import mockChallengeNodes from '../../../__mocks__/challenge-nodes';
|
||||||
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
import mockIntroNodes from '../../../__mocks__/intro-nodes';
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
Enzyme.configure({ adapter: new Adapter() });
|
||||||
@ -18,7 +18,7 @@ test('<SuperBlock /> not expanded snapshot', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
introNodes: mockIntroNodes,
|
introNodes: mockIntroNodes,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
nodes: mockNodes,
|
nodes: mockChallengeNodes,
|
||||||
superBlock: 'Super Block One',
|
superBlock: 'Super Block One',
|
||||||
toggleSuperBlock: toggleSpy
|
toggleSuperBlock: toggleSpy
|
||||||
};
|
};
|
||||||
@ -33,7 +33,7 @@ test('<SuperBlock /> expanded snapshot', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
introNodes: mockIntroNodes,
|
introNodes: mockIntroNodes,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
nodes: mockNodes,
|
nodes: mockChallengeNodes,
|
||||||
superBlock: 'Super Block One',
|
superBlock: 'Super Block One',
|
||||||
toggleSuperBlock: toggleSpy
|
toggleSuperBlock: toggleSpy
|
||||||
};
|
};
|
||||||
@ -48,7 +48,7 @@ test('<SuperBlock should handle toggle clicks correctly', () => {
|
|||||||
const props = {
|
const props = {
|
||||||
introNodes: mockIntroNodes,
|
introNodes: mockIntroNodes,
|
||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
nodes: mockNodes,
|
nodes: mockChallengeNodes,
|
||||||
superBlock: 'Super Block One',
|
superBlock: 'Super Block One',
|
||||||
toggleSuperBlock: toggleSpy
|
toggleSuperBlock: toggleSpy
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
@ -1,27 +1,40 @@
|
|||||||
/* global expect */
|
/* global expect */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||||
|
|
||||||
import 'jest-dom/extend-expect';
|
import 'jest-dom/extend-expect';
|
||||||
import { IndexPage } from '../../pages';
|
import { LearnPage } from '../../pages/learn';
|
||||||
|
|
||||||
import Welcome from './';
|
import Welcome from './';
|
||||||
|
|
||||||
|
import mockChallengeNodes from '../../__mocks__/challenge-nodes';
|
||||||
|
import mockIntroNodes from '../../__mocks__/intro-nodes';
|
||||||
|
|
||||||
describe('<Welcome />', () => {
|
describe('<Welcome />', () => {
|
||||||
it('renders when visiting index page and logged in', () => {
|
it('renders when visiting index page and logged in', () => {
|
||||||
const container = renderer
|
const shallow = new ShallowRenderer();
|
||||||
.create(<IndexPage {...loggedInProps} />)
|
shallow.render(<LearnPage {...loggedInProps} />);
|
||||||
.toTree();
|
const result = shallow.getRenderOutput();
|
||||||
expect(container.rendered.type.displayName === 'Welcome').toBeTruthy();
|
expect(result.type.displayName === 'LearnLayout').toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has four links', () => {
|
it('has a header', () => {
|
||||||
const container = renderer.create(<Welcome name={'developmentuser'} />)
|
const container = renderer.create(<Welcome name={'Development User'} />)
|
||||||
.root;
|
.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 = {
|
const loggedInProps = {
|
||||||
fetchState: {
|
fetchState: {
|
||||||
complete: true,
|
complete: true,
|
||||||
@ -31,7 +44,17 @@ const loggedInProps = {
|
|||||||
},
|
},
|
||||||
isSignedIn: true,
|
isSignedIn: true,
|
||||||
user: {
|
user: {
|
||||||
acceptedPrivacyTerms: true,
|
name: 'Development User'
|
||||||
username: 'developmentuser'
|
},
|
||||||
|
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 }] }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
import { Row, Col } from '@freecodecamp/react-bootstrap';
|
||||||
import Helmet from 'react-helmet';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Spacer, Link } from '../helpers';
|
import { Spacer } from '../helpers';
|
||||||
import { randomQuote } from '../../utils/get-words';
|
import { randomQuote } from '../../utils/get-words';
|
||||||
|
|
||||||
import './welcome.css';
|
import './welcome.css';
|
||||||
@ -12,68 +11,35 @@ function Welcome({ name }) {
|
|||||||
const { quote, author } = randomQuote();
|
const { quote, author } = randomQuote();
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Helmet>
|
<Row>
|
||||||
<title>Welcome | freeCodeCamp.org</title>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
</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>
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Row className='text-center quote-partial'>
|
<h1 className='text-center big-heading'>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
{name ? 'Welcome back ' + name : 'Welcome to freeCodeCamp.org'}
|
||||||
<blockquote className='blockquote'>
|
</h1>
|
||||||
<span>
|
</Col>
|
||||||
<q>{quote}</q>
|
</Row>
|
||||||
<footer className='quote-author blockquote-footer'>
|
<Spacer />
|
||||||
<cite>{author}</cite>
|
<Row className='text-center quote-partial'>
|
||||||
</footer>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
</span>
|
<blockquote className='blockquote'>
|
||||||
</blockquote>
|
<span>
|
||||||
</Col>
|
<q>{quote}</q>
|
||||||
</Row>
|
<footer className='quote-author blockquote-footer'>
|
||||||
<Row>
|
<cite>{author}</cite>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
</footer>
|
||||||
<Spacer />
|
</span>
|
||||||
<h2 className='text-center medium-heading'>
|
</blockquote>
|
||||||
What would you like to do today?
|
</Col>
|
||||||
</h2>
|
</Row>
|
||||||
</Col>
|
<Row>
|
||||||
</Row>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Row>
|
<h2 className='text-center medium-heading'>
|
||||||
<Col sm={10} smOffset={1} xs={12}>
|
What would you like to do today?
|
||||||
<Link className='btn btn-lg btn-primary btn-block' to='/learn'>
|
</h2>
|
||||||
Build Projects and Earn Certifications
|
</Col>
|
||||||
</Link>
|
</Row>
|
||||||
<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>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Loader } from '../components/helpers';
|
import { Loader } from '../components/helpers';
|
||||||
import Welcome from '../components/welcome';
|
|
||||||
import Landing from '../components/landing';
|
import Landing from '../components/landing';
|
||||||
import {
|
import {
|
||||||
userSelector,
|
userSelector,
|
||||||
@ -25,11 +24,12 @@ const mapStateToProps = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
|
const RedirectAcceptPrivacyTerm = createRedirect('/accept-privacy-terms');
|
||||||
|
const RedirectLearn = createRedirect('/learn');
|
||||||
|
|
||||||
export const IndexPage = ({
|
export const IndexPage = ({
|
||||||
fetchState: { pending, complete },
|
fetchState: { pending, complete },
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
user: { acceptedPrivacyTerms, name = '' }
|
user: { acceptedPrivacyTerms }
|
||||||
}) => {
|
}) => {
|
||||||
if (pending && !complete) {
|
if (pending && !complete) {
|
||||||
return <Loader fullScreen={true} />;
|
return <Loader fullScreen={true} />;
|
||||||
@ -40,7 +40,7 @@ export const IndexPage = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSignedIn) {
|
if (isSignedIn) {
|
||||||
return <Welcome name={name} />;
|
return <RedirectLearn />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Landing />;
|
return <Landing />;
|
||||||
@ -54,14 +54,7 @@ const propTypes = {
|
|||||||
}),
|
}),
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
acceptedPrivacyTerms: PropTypes.bool,
|
acceptedPrivacyTerms: PropTypes.bool
|
||||||
completedCertCount: PropTypes.number,
|
|
||||||
completedChallengeCount: PropTypes.number,
|
|
||||||
completedLegacyCertCount: PropTypes.number,
|
|
||||||
completedProjectCount: PropTypes.number,
|
|
||||||
isDonating: PropTypes.bool,
|
|
||||||
name: PropTypes.string,
|
|
||||||
username: PropTypes.string
|
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -1,19 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Grid, Row, Col } from '@freecodecamp/react-bootstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { graphql } from 'gatsby';
|
import { graphql } from 'gatsby';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { connect } from 'react-redux';
|
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 LearnLayout from '../components/layouts/Learn';
|
||||||
import Login from '../components/Header/components/Login';
|
import Login from '../components/Header/components/Login';
|
||||||
import { Link, Spacer, Loader } from '../components/helpers';
|
import { Link, Spacer, Loader } from '../components/helpers';
|
||||||
import Map from '../components/Map';
|
import Map from '../components/Map';
|
||||||
|
import Welcome from '../components/welcome';
|
||||||
import './learn.css';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChallengeNode,
|
ChallengeNode,
|
||||||
@ -24,9 +27,11 @@ import {
|
|||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
(fetchState, isSignedIn) => ({
|
userSelector,
|
||||||
|
(fetchState, isSignedIn, user) => ({
|
||||||
fetchState,
|
fetchState,
|
||||||
isSignedIn
|
isSignedIn,
|
||||||
|
user
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -41,17 +46,20 @@ const propTypes = {
|
|||||||
complete: PropTypes.bool,
|
complete: PropTypes.bool,
|
||||||
errored: PropTypes.bool
|
errored: PropTypes.bool
|
||||||
}),
|
}),
|
||||||
isSignedIn: PropTypes.bool
|
isSignedIn: PropTypes.bool,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
name: PropTypes.string
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const BigCallToAction = isSignedIn => {
|
const BigCallToAction = isSignedIn => {
|
||||||
if (!isSignedIn) {
|
if (!isSignedIn) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Spacer size={2} />
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col sm={8} smOffset={2} xs={12}>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<Login className={'text-center'}>Sign in to save progress.</Login>
|
<Spacer />
|
||||||
|
<Login>Sign in to save your progress.</Login>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
@ -60,9 +68,10 @@ const BigCallToAction = isSignedIn => {
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const IndexPage = ({
|
export const LearnPage = ({
|
||||||
fetchState: { pending, complete },
|
fetchState: { pending, complete },
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
|
user: { name = '' },
|
||||||
data: {
|
data: {
|
||||||
challengeNode: {
|
challengeNode: {
|
||||||
fields: { slug }
|
fields: { slug }
|
||||||
@ -77,44 +86,34 @@ const IndexPage = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<LearnLayout>
|
<LearnLayout>
|
||||||
<div className='learn-page-wrapper'>
|
<Helmet title='Learn | freeCodeCamp.org' />
|
||||||
<Helmet title='Learn | freeCodeCamp.org' />
|
<Grid>
|
||||||
{BigCallToAction(isSignedIn)}
|
<Welcome name={name} />
|
||||||
<Spacer size={2} />
|
<Row className='text-center'>
|
||||||
<h1 className='text-center'>Welcome to the freeCodeCamp curriculum</h1>
|
<Col sm={10} smOffset={1} xs={12}>
|
||||||
<p>
|
{BigCallToAction(isSignedIn)}
|
||||||
We have thousands of coding lessons to help you improve your skills.
|
<Spacer />
|
||||||
</p>
|
<h3>
|
||||||
<p>
|
If you are new to coding, we recommend you{' '}
|
||||||
You can earn each certification by completing its 5 final projects.
|
<Link to={slug}>start at the beginning</Link>.
|
||||||
</p>
|
</h3>
|
||||||
<p>
|
</Col>
|
||||||
And yes - all of this is 100% free, thanks to the thousands of campers
|
</Row>
|
||||||
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>
|
|
||||||
<Map
|
<Map
|
||||||
introNodes={mdEdges.map(({ node }) => node)}
|
introNodes={mdEdges.map(({ node }) => node)}
|
||||||
nodes={edges
|
nodes={edges
|
||||||
.map(({ node }) => node)
|
.map(({ node }) => node)
|
||||||
.filter(({ isPrivate }) => !isPrivate)}
|
.filter(({ isPrivate }) => !isPrivate)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Grid>
|
||||||
</LearnLayout>
|
</LearnLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
IndexPage.displayName = 'IndexPage';
|
LearnPage.displayName = 'LearnPage';
|
||||||
IndexPage.propTypes = propTypes;
|
LearnPage.propTypes = propTypes;
|
||||||
|
|
||||||
export default connect(mapStateToProps)(IndexPage);
|
export default connect(mapStateToProps)(LearnPage);
|
||||||
|
|
||||||
export const query = graphql`
|
export const query = graphql`
|
||||||
query FirstChallenge {
|
query FirstChallenge {
|
||||||
|
Reference in New Issue
Block a user