diff --git a/common/app/create-app.jsx b/common/app/create-app.jsx index 5fd9cfd4d2..362f1ea7da 100644 --- a/common/app/create-app.jsx +++ b/common/app/create-app.jsx @@ -10,7 +10,6 @@ import childRoutes from './routes'; // redux import { createEpic } from 'redux-epic'; import createReducer from './create-reducer'; -import middlewares from './middlewares'; import sagas from './sagas'; // general utils @@ -56,7 +55,6 @@ export default function createApp({ ); const enhancers = [ applyMiddleware( - ...middlewares, ...sideMiddlewares, sagaMiddleware ), diff --git a/common/app/create-reducer.js b/common/app/create-reducer.js index a42d4f8aac..c962d6415b 100644 --- a/common/app/create-reducer.js +++ b/common/app/create-reducer.js @@ -9,7 +9,6 @@ import { reducer as jobsApp, formNormalizer as jobsNormalizer } from './routes/Jobs/redux'; -import { reducer as map } from './routes/map/redux'; export default function createReducer(sideReducers = {}) { return combineReducers({ @@ -19,7 +18,6 @@ export default function createReducer(sideReducers = {}) { hikesApp, jobsApp, challengesApp, - map, form: formReducer.normalize(jobsNormalizer) }); } diff --git a/common/app/middlewares.js b/common/app/middlewares.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js index 8c2761068b..b89a2494af 100644 --- a/common/app/redux/actions.js +++ b/common/app/redux/actions.js @@ -45,21 +45,3 @@ export const createErrorObserable = error => Observable.just({ type: types.handleError, error }); - -// challenges -// these need to be used by more than one route so we put them here -export const fetchChallenge = createAction(types.fetchChallenge); -export const fetchChallengeCompleted = createAction( - types.fetchChallengeCompleted, - (_, challenge) => challenge, - entities => ({ entities }) -); - -export const fetchChallenges = createAction(types.fetchChallenges); -export const fetchChallengesCompleted = createAction( - types.fetchChallengesCompleted, - (_, superBlocks) => superBlocks, - entities => ({ entities }) -); - -export const setChallenge = createAction(types.setChallenge); diff --git a/common/app/redux/index.js b/common/app/redux/index.js index 9cdbc9653d..d0e8381658 100644 --- a/common/app/redux/index.js +++ b/common/app/redux/index.js @@ -3,5 +3,4 @@ export { default as actions } from './actions'; export { default as types } from './types'; import fetchUserSaga from './fetch-user-saga'; -import fetchChallengesSaga from './fetch-challenges-saga'; -export const sagas = [ fetchUserSaga, fetchChallengesSaga ]; +export const sagas = [ fetchUserSaga ]; diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index e95901abf5..6ef681c631 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -30,12 +30,6 @@ export default handleActions( [types.updateNavHeight]: (state, { payload: navHeight }) => ({ ...state, navHeight - }), - - // challenges - [types.fetchChallengesCompleted]: (state, { payload = [] }) => ({ - ...state, - superBlocks: payload }) }, { @@ -46,7 +40,6 @@ export default handleActions( isSignedIn: false, csrfToken: '', windowHeight: 0, - navHeight: 0, - superBlocks: [] + navHeight: 0 } ); diff --git a/common/app/redux/types.js b/common/app/redux/types.js index 0bd27ba711..9860e941bc 100644 --- a/common/app/redux/types.js +++ b/common/app/redux/types.js @@ -18,12 +18,5 @@ export default createTypes([ // data handling 'updateChallengesData', 'updateJobsData', - 'updateHikesData', - - // challenges - 'fetchChallenge', - 'fetchChallenges', - 'fetchChallengeCompleted', - 'fetchChallengesCompleted', - 'setChallenge' + 'updateHikesData' ], 'app'); diff --git a/common/app/routes/challenges/components/Challenges.jsx b/common/app/routes/challenges/components/Challenges.jsx index d4aaa384e1..c2914aaefe 100644 --- a/common/app/routes/challenges/components/Challenges.jsx +++ b/common/app/routes/challenges/components/Challenges.jsx @@ -7,7 +7,7 @@ import PureComponent from 'react-pure-render/component'; import Challenge from './Challenge.jsx'; import Step from './step/Step.jsx'; -import { fetchChallenge } from '../../../redux/actions'; +import { fetchChallenge } from '../redux/actions'; import { challengeSelector } from '../redux/selectors'; const bindableActions = { diff --git a/common/app/routes/map/components/Block.jsx b/common/app/routes/challenges/components/map/Block.jsx similarity index 100% rename from common/app/routes/map/components/Block.jsx rename to common/app/routes/challenges/components/map/Block.jsx diff --git a/common/app/routes/map/components/Coding-Prep.jsx b/common/app/routes/challenges/components/map/Coding-Prep.jsx similarity index 100% rename from common/app/routes/map/components/Coding-Prep.jsx rename to common/app/routes/challenges/components/map/Coding-Prep.jsx diff --git a/common/app/routes/map/components/Full-Stack.jsx b/common/app/routes/challenges/components/map/Full-Stack.jsx similarity index 100% rename from common/app/routes/map/components/Full-Stack.jsx rename to common/app/routes/challenges/components/map/Full-Stack.jsx diff --git a/common/app/routes/map/components/Map.jsx b/common/app/routes/challenges/components/map/Map.jsx similarity index 100% rename from common/app/routes/map/components/Map.jsx rename to common/app/routes/challenges/components/map/Map.jsx diff --git a/common/app/routes/map/components/Show.jsx b/common/app/routes/challenges/components/map/Show.jsx similarity index 85% rename from common/app/routes/map/components/Show.jsx rename to common/app/routes/challenges/components/map/Show.jsx index 5fa9ec6e6f..c781bf581a 100644 --- a/common/app/routes/map/components/Show.jsx +++ b/common/app/routes/challenges/components/map/Show.jsx @@ -8,19 +8,20 @@ import { createSelector } from 'reselect'; import Map from './Map.jsx'; import { clearFilter, - updateFilter -} from '../redux/actions'; -import { setChallenge, fetchChallenges } from '../../../redux/actions'; + updateFilter, + updateCurrentChallenge, + fetchChallenges +} from '../../redux/actions'; const bindableActions = { clearFilter, fetchChallenges, updateFilter, - setChallenge + updateCurrentChallenge }; const superBlocksSelector = createSelector( - state => state.app.superBlocks, + state => state.challengesApp.superBlocks, state => state.entities.superBlock, state => state.entities.block, state => state.entities.challenge, @@ -49,7 +50,7 @@ const superBlocksSelector = createSelector( const mapStateToProps = createSelector( superBlocksSelector, - state => state.map.filter, + state => state.challengesApp.filter, ({ superBlocks }, filter) => { return { superBlocks, @@ -61,7 +62,7 @@ const mapStateToProps = createSelector( const fetchOptions = { fetchAction: 'fetchChallenges', isPrimed({ superBlocks }) { - return Array.isArray(superBlocks) && superBlocks.length > 0; + return Array.isArray(superBlocks) && superBlocks.length > 1; } }; @@ -72,7 +73,7 @@ export class ShowMap extends PureComponent { filter: PropTypes.string, superBlocks: PropTypes.array, updateFilter: PropTypes.func, - setChallenge: PropTypes.func + updateCurrentChallenge: PropTypes.func }; render() { diff --git a/common/app/routes/map/components/Super-Block.jsx b/common/app/routes/challenges/components/map/Super-Block.jsx similarity index 100% rename from common/app/routes/map/components/Super-Block.jsx rename to common/app/routes/challenges/components/map/Super-Block.jsx diff --git a/common/app/routes/challenges/index.js b/common/app/routes/challenges/index.js index b430bc2c90..f561670dd3 100644 --- a/common/app/routes/challenges/index.js +++ b/common/app/routes/challenges/index.js @@ -1,6 +1,7 @@ import Challenges from './components/Challenges.jsx'; +import ShowMap from './components/map/Show.jsx'; -export default { +export const challenges = { path: 'challenges(/:dashedName)', component: Challenges, onEnter(nextState, replace) { @@ -10,3 +11,8 @@ export default { } } }; + +export const map = { + path: 'map', + component: ShowMap +}; diff --git a/common/app/routes/challenges/redux/actions.js b/common/app/routes/challenges/redux/actions.js index 20ece869b4..bf1a8793a0 100644 --- a/common/app/routes/challenges/redux/actions.js +++ b/common/app/routes/challenges/redux/actions.js @@ -2,5 +2,33 @@ import { createAction } from 'redux-actions'; import types from './types'; +// step export const goToStep = createAction(types.goToStep); -export const setChallenge = createAction(types.setChallenge); + + +// challenges +export const fetchChallenge = createAction(types.fetchChallenge); +export const fetchChallengeCompleted = createAction( + types.fetchChallengeCompleted, + (_, challenge) => challenge, + entities => ({ entities }) +); + +export const fetchChallenges = createAction(types.fetchChallenges); +export const fetchChallengesCompleted = createAction( + types.fetchChallengesCompleted, + (_, superBlocks) => superBlocks, + entities => ({ entities }) +); + +export const updateCurrentChallenge = createAction( + types.updateCurrentChallenge +); + +// map +export const updateFilter = createAction( + types.updateFilter, + e => e.target.value +); + +export const clearFilter = createAction(types.clearFilter); diff --git a/common/app/redux/fetch-challenges-saga.js b/common/app/routes/challenges/redux/fetch-challenges-saga.js similarity index 91% rename from common/app/redux/fetch-challenges-saga.js rename to common/app/routes/challenges/redux/fetch-challenges-saga.js index 0fee53a2aa..c217f20863 100644 --- a/common/app/redux/fetch-challenges-saga.js +++ b/common/app/routes/challenges/redux/fetch-challenges-saga.js @@ -4,7 +4,7 @@ import { createErrorObserable, fetchChallengeCompleted, fetchChallengesCompleted, - setChallenge + updateCurrentChallenge } from './actions'; export default function fetchChallengesSaga(action$, getState, { services }) { @@ -22,7 +22,7 @@ export default function fetchChallengesSaga(action$, getState, { services }) { if (type === fetchChallenge) { return Observable.of( fetchChallengeCompleted(entities, result), - setChallenge(entities.challenge[result]) + updateCurrentChallenge(entities.challenge[result]) ); } return Observable.just(fetchChallengesCompleted(entities, result)); diff --git a/common/app/routes/challenges/redux/index.js b/common/app/routes/challenges/redux/index.js index 9b248336b8..79b8c842e4 100644 --- a/common/app/routes/challenges/redux/index.js +++ b/common/app/routes/challenges/redux/index.js @@ -1,3 +1,6 @@ export actions from './actions'; export reducer from './reducer'; export types from './types'; + +import fetchChallengesSaga from './fetch-challenges-saga'; +export const sagas = [ fetchChallengesSaga ]; diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js index b791e9b03a..85f06e5c60 100644 --- a/common/app/routes/challenges/redux/reducer.js +++ b/common/app/routes/challenges/redux/reducer.js @@ -7,7 +7,9 @@ const initialState = { challenge: '', currentStep: 0, previousStep: -1, - content: null + filter: '', + content: null, + superBlocks: [] }; function arrayToNewLineString(seedData = []) { @@ -21,12 +23,6 @@ function buildSeed({ challengeSeed = [] } = {}) { export default handleActions( { - [types.resetStep]: () => initialState, - [types.goToStep]: (state, { payload: step = 0 }) => ({ - ...state, - currentStep: step, - previousStep: state.currentStep - }), [fetchChallengeCompleted]: (state, { payload = '' }) => ({ ...state, challenge: payload @@ -35,6 +31,28 @@ export default handleActions( ...state, challenge: challenge.dashedName, content: buildSeed(challenge) + }), + + // map + [types.updateFilter]: (state, { payload = ''}) => ({ + ...state, + filter: payload + }), + [types.clearFilter]: (state) => ({ + ...state, + filter: '' + }), + [types.fetchChallengesCompleted]: (state, { payload = [] }) => ({ + ...state, + superBlocks: payload + }), + + // step + [types.resetStep]: () => initialState, + [types.goToStep]: (state, { payload: step = 0 }) => ({ + ...state, + currentStep: step, + previousStep: state.currentStep }) }, initialState diff --git a/common/app/routes/challenges/redux/types.js b/common/app/routes/challenges/redux/types.js index 2c95ce3180..f0fa935c83 100644 --- a/common/app/routes/challenges/redux/types.js +++ b/common/app/routes/challenges/redux/types.js @@ -3,5 +3,15 @@ import createTypes from '../../../utils/create-types'; export default createTypes([ // step 'goToStep', - 'setChallenge' + + // challenges + 'fetchChallenge', + 'fetchChallenges', + 'fetchChallengeCompleted', + 'fetchChallengesCompleted', + 'updateCurrentChallenge', + + // map + 'updateFilter', + 'clearFilter' ], 'challenges'); diff --git a/common/app/routes/challenges/step/Step.jsx b/common/app/routes/challenges/step/Step.jsx deleted file mode 100644 index 3d22bf6960..0000000000 --- a/common/app/routes/challenges/step/Step.jsx +++ /dev/null @@ -1,274 +0,0 @@ -import React, { PropTypes } from 'react'; -import classnames from 'classnames'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { goToStep } from '../../redux/actions'; -import PureComponent from 'react-pure-render/component'; -import ReactTransitionReplace from 'react-css-transition-replace'; - -import { Button, Col, Image, Row } from 'react-bootstrap'; - -const mapStateToProps = createSelector( - state => state.challengesApp.currentStep, - state => state.challengesApp.previousStep, - (currentStep, previousStep) => ({ - currentStep, - previousStep, - isGoingForward: currentStep > previousStep - }) -); - -const dispatchActions = { - goToStep -}; - -const transitionTimeout = 1000; -/* eslint-disable max-len, quotes */ -const challenge = { - title: "Learn how Free Code Camp Works", - description: [ - [ - "http://i.imgur.com/6ibIavQ.jpg", - "A picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits", - "Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.", - "http://www.foo.com" - ], - [ - "http://i.imgur.com/Elb3dfj.jpg", - "A screenshot of some of our campers coding together in Toronto.", - "Learning to code is hard. To succeed, you'll need lots of practice and support. That's why we've created a rigorous curriculum and supportive community.", - "" - ], - [ - "http://i.imgur.com/D7Y5luw.jpg", - "A graph of the rate of job growth against growth in computer science degree graduates. There are 1.4 million jobs and only 400 thousand people to fill them.", - "There are thousands of coding jobs currently going unfilled, and the demand for coders grows every year.", - "" - ], - [ - "http://i.imgur.com/WD3STY6.jpg", - "Photos of three campers who've gotten jobs after learning to code at Free Code Camp.", - "Free Code Camp is a proven path to your first coding job. In fact, no one has actually completed our entire program, because campers get jobs before they're able to.", - "" - ], - [ - "http://i.imgur.com/vLNso6h.jpg", - "An illustration showing that you will learn HTML5, CSS3, JavaScript, Databases, Git, Node.js, React and D3.", - "We have hundreds of optional coding challenges that will teach you fundamental web development technologies like HTML5, Node.js and databases.", - "" - ], - [ - "http://i.imgur.com/UVB9hxp.jpg", - "An image of a camper at a cafe building projects on Free Code Camp.", - "We believe humans learn best by doing. So you'll spend most of your time actually building projects. We'll give you a list of specifications (agile user stories), and you'll figure out how to build apps that fulfill those specifications.", - "" - ], - [ - "http://i.imgur.com/pbW7K5S.jpg", - "An image of showing our front end, back end, and data visualization certifications (400 hours each), our nonprofit projects (800 hours), and interview prep (80 hours) for a total of 2,080 hours of coding experience.", - "Our curriculum is divided into 4 certifications. These certifications are standardized, and instantly verifiable by your freelance clients and future employers. Like everything else at Free Code Camp, these certifications are free. We recommend doing them in order, but you are free to jump around. The first three certifications take 400 hours each, and the final certification takes 800 hours, and involves building real-life projects for nonprofits.", - "" - ], - [ - "http://i.imgur.com/k8btNUB.jpg", - "A screenshot of our Front End Development Certificate", - "To earn our verified Front End Development Certification, you'll build 10 projects using HTML, CSS, jQuery, and JavaScript.", - "" - ], - [ - "http://i.imgur.com/Et3iD74.jpg", - "A screenshot of our Data Visualization Certificate", - "To earn our Data Visualization Certification, you'll build 10 projects using React, Sass and D3.js.", - "" - ], - [ - "http://i.imgur.com/8v3t84p.jpg", - "A screenshot of our Back End Development Certificate", - "To earn our Back End Development Certification, you'll build 10 projects using Node.js, Express, and MongoDB. You'll use Git and Heroku to deploy them to the cloud.", - "" - ], - [ - "http://i.imgur.com/yXyxbDd.jpg", - "A screen shot of our nonprofit project directory.", - "After you complete all three of these certificates, you'll team up with another camper and use agile software development methodologies to build two real-life projects for nonprofits. You'll also add functionality to two legacy code nonprofit projects. By the time you finish, you'll have a portfolio of real apps that people use every day.", - "" - ], - [ - "http://i.imgur.com/PDGQ9ZM.jpg", - "An image of campers building projects together in a cafe in Seoul.", - "If you complete all 2,080 hours worth of challenges and projects, you'll earn our Full Stack Development Certification. We'll offer you free coding interview practice. We even offer a job board where employers specifically hire campers who've earned Free Code Camp certifications.", - "http://foo.com" - ] - ] -}; -/* eslint-disable max-len, quotes */ - -export class StepChallenge extends PureComponent { - static displayName = 'StepChallenge'; - static defaultProps = { - currentStep: 0, - previousStep: -1 - }; - - static propTypes = { - currentStep: PropTypes.number, - previousStep: PropTypes.number, - isGoingForward: PropTypes.bool, - goToStep: PropTypes.func - }; - - renderActionButton(action) { - if (!action) { - return null; - } - return ( -
- -
-
- ); - } - - renderBackButton(index) { - const { goToStep } = this.props; - if (index === 0) { - return ( - - { ' ' } - - ); - } - return ( - - ); - } - - renderNextButton(hasAction, index, numOfSteps, isCompleted) { - const { goToStep } = this.props; - const isLastStep = index + 1 >= numOfSteps; - const btnClass = classnames({ - 'col-sm-4 col-xs-12': true, - disabled: hasAction && !isCompleted - }); - return ( - - ); - } - - renderStep(step, currentStep, numOfSteps) { - if (!Array.isArray(step)) { - return null; - } - const [imgUrl, imgAlt, info, action] = step; - return ( -
- - { - - -
- -

- - -

-
- { this.renderActionButton(action) } - { this.renderBackButton(currentStep) } - - ( { currentStep + 1 } / { numOfSteps }) - - { - this.renderNextButton( - !!action, - currentStep, - numOfSteps, - true - ) - } -
-
-
- ); - } - - renderImages(steps) { - // will preload all the image - if (!Array.isArray(steps)) { - return null; - } - return steps.map(([imgUrl, imgAlt]) => ( -
- { -
- )); - } - - render() { - const { currentStep, isGoingForward } = this.props; - const numOfSteps = Array.isArray(challenge.description) ? - challenge.description.length : - 0; - const step = challenge.description[currentStep]; - const transitionName = 'challenge-step-' + - (isGoingForward ? 'forward' : 'backward'); - - return ( - - - { this.renderStep(step, currentStep, numOfSteps) } - -
- { this.renderImages(challenge.description) } -
-
- - ); - } -} - -export default connect(mapStateToProps, dispatchActions)(StepChallenge); diff --git a/common/app/routes/index.js b/common/app/routes/index.js index 875ea4d14b..0f678defe6 100644 --- a/common/app/routes/index.js +++ b/common/app/routes/index.js @@ -1,7 +1,6 @@ import Jobs from './Jobs'; import Hikes from './Hikes'; -import challenges from './challenges'; -import map from './map'; +import { map, challenges } from './challenges'; import NotFound from '../components/NotFound/index.jsx'; export default { diff --git a/common/app/routes/map/index.js b/common/app/routes/map/index.js deleted file mode 100644 index f1fbc4bf70..0000000000 --- a/common/app/routes/map/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import ShowMap from './components/Show.jsx'; - -export default { - path: 'map', - component: ShowMap -}; diff --git a/common/app/routes/map/redux/actions.js b/common/app/routes/map/redux/actions.js deleted file mode 100644 index afbce1c479..0000000000 --- a/common/app/routes/map/redux/actions.js +++ /dev/null @@ -1,9 +0,0 @@ -import { createAction } from 'redux-actions'; - -import types from './types'; -export const updateFilter = createAction( - types.updateFilter, - e => e.target.value -); - -export const clearFilter = createAction(types.clearFilter); diff --git a/common/app/routes/map/redux/index.js b/common/app/routes/map/redux/index.js deleted file mode 100644 index df7a505050..0000000000 --- a/common/app/routes/map/redux/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export actions from './actions'; -export reducer from './reducer'; -export types from './types'; - -export const sagas = []; diff --git a/common/app/routes/map/redux/reducer.js b/common/app/routes/map/redux/reducer.js deleted file mode 100644 index 76a973e171..0000000000 --- a/common/app/routes/map/redux/reducer.js +++ /dev/null @@ -1,18 +0,0 @@ -import { handleActions } from 'redux-actions'; - -import types from './types'; - -const initialState = { filter: '' }; -export default handleActions( - { - [types.updateFilter]: (state, { payload = ''}) => ({ - ...state, - filter: payload - }), - [types.clearFilter]: (state) => ({ - ...state, - filter: '' - }) - }, - initialState -); diff --git a/common/app/routes/map/redux/types.js b/common/app/routes/map/redux/types.js deleted file mode 100644 index 86dcb1721b..0000000000 --- a/common/app/routes/map/redux/types.js +++ /dev/null @@ -1,6 +0,0 @@ -import createTypes from '../../../utils/create-types'; - -export default createTypes([ - 'updateFilter', - 'clearFilter' -], 'map'); diff --git a/common/app/sagas.js b/common/app/sagas.js index f1964d402a..18e4f31589 100644 --- a/common/app/sagas.js +++ b/common/app/sagas.js @@ -1,11 +1,11 @@ import { sagas as appSagas } from './redux'; import { sagas as hikesSagas} from './routes/Hikes/redux'; import { sagas as jobsSagas } from './routes/Jobs/redux'; -import { sagas as mapSagas } from './routes/map/redux'; +import { sagas as challengeSagas } from './routes/challenges/redux'; export default [ ...appSagas, ...hikesSagas, ...jobsSagas, - ...mapSagas + ...challengeSagas ];