diff --git a/client/sagas/index.js b/client/sagas/index.js
index 24a84a73e6..54cd65a21b 100644
--- a/client/sagas/index.js
+++ b/client/sagas/index.js
@@ -1,6 +1,5 @@
import errSaga from './err-saga';
import titleSaga from './title-saga';
-import localStorageSaga from './local-storage-saga';
import hardGoToSaga from './hard-go-to-saga';
import windowSaga from './window-saga';
import executeChallengeSaga from './execute-challenge-saga';
@@ -11,7 +10,6 @@ import gitterSaga from './gitter-saga';
export default [
errSaga,
titleSaga,
- localStorageSaga,
hardGoToSaga,
windowSaga,
executeChallengeSaga,
diff --git a/client/sagas/local-storage-saga.js b/client/sagas/local-storage-saga.js
deleted file mode 100644
index d6e60dc20e..0000000000
--- a/client/sagas/local-storage-saga.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import store from 'store';
-import {
- saveForm,
- clearForm,
- loadSavedForm
-} from '../../common/app/routes/Jobs/redux/types';
-
-import {
- saveCompleted,
- loadSavedFormCompleted
-} from '../../common/app/routes/Jobs/redux/actions';
-
-const formKey = 'newJob';
-
-export default function localStorageSaga(action$) {
- return action$
- .filter(action => {
- return action.type === saveForm ||
- action.type === clearForm ||
- action.type === loadSavedForm;
- })
- .map(action => {
- if (action.type === saveForm) {
- const form = action.payload;
- try {
- store.setItem(formKey, form);
- return saveCompleted(form);
- } catch (error) {
- return {
- type: 'app.handleError',
- error
- };
- }
- }
-
- if (action.type === clearForm) {
- store.removeItem(formKey);
- return null;
- }
-
- return loadSavedFormCompleted(store.getItem(formKey));
- });
-}
diff --git a/common/app/create-reducer.js b/common/app/create-reducer.js
index e91c704a8e..f7edc27f93 100644
--- a/common/app/create-reducer.js
+++ b/common/app/create-reducer.js
@@ -3,27 +3,17 @@ import { reducer as formReducer } from 'redux-form';
import { reducer as app } from './redux';
import entitiesReducer from './redux/entities-reducer';
-import { reducer as hikesApp } from './routes/Hikes/redux';
import {
reducer as challengesApp,
projectNormalizer
} from './routes/challenges/redux';
-import {
- reducer as jobsApp,
- formNormalizer as jobsNormalizer
-} from './routes/Jobs/redux';
export default function createReducer(sideReducers = {}) {
return combineReducers({
...sideReducers,
entities: entitiesReducer,
app,
- hikesApp,
- jobsApp,
challengesApp,
- form: formReducer.normalize({
- ...jobsNormalizer,
- ...projectNormalizer
- })
+ form: formReducer.normalize({ ...projectNormalizer })
});
}
diff --git a/common/app/routes/Hikes/components/Hikes.jsx b/common/app/routes/Hikes/components/Hikes.jsx
deleted file mode 100644
index ccc7a68df0..0000000000
--- a/common/app/routes/Hikes/components/Hikes.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import React, { PropTypes } from 'react';
-import { compose } from 'redux';
-import { contain } from 'redux-epic';
-import { connect } from 'react-redux';
-import PureComponent from 'react-pure-render/component';
-import { createSelector } from 'reselect';
-// import debug from 'debug';
-
-import HikesMap from './Map.jsx';
-import { fetchHikes } from '../redux/actions';
-
-
-// const log = debug('fcc:hikes');
-
-const mapStateToProps = createSelector(
- state => state.entities.hike,
- state => state.hikesApp.hikes,
- (hikesMap, hikesByDashedName) => {
- if (!hikesMap || !hikesByDashedName) {
- return { hikes: [] };
- }
- return {
- hikes: hikesByDashedName.map(dashedName => hikesMap[dashedName])
- };
- }
-);
-
-const fetchOptions = {
- fetchAction: 'fetchHikes',
- isPrimed: ({ hikes }) => hikes && !!hikes.length,
- getActionArgs: ({ params: { dashedName } }) => [ dashedName ],
- shouldContainerFetch(props, nextProps) {
- return props.params.dashedName !== nextProps.params.dashedName;
- }
-};
-
-export class Hikes extends PureComponent {
- static displayName = 'Hikes';
-
- static propTypes = {
- children: PropTypes.element,
- hikes: PropTypes.array,
- params: PropTypes.object
- };
-
- renderMap(hikes) {
- return (
-
- );
- }
-
- render() {
- const { hikes } = this.props;
- return (
-
- {
- // render sub-route
- this.props.children ||
- // if no sub-route render hikes map
- this.renderMap(hikes)
- }
-
- );
- }
-}
-
-// export redux and fetch aware component
-export default compose(
- connect(mapStateToProps, { fetchHikes }),
- contain(fetchOptions)
-)(Hikes);
diff --git a/common/app/routes/Hikes/components/Lecture.jsx b/common/app/routes/Hikes/components/Lecture.jsx
deleted file mode 100644
index e6d85643ed..0000000000
--- a/common/app/routes/Hikes/components/Lecture.jsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import React, { PropTypes } from 'react';
-import { connect } from 'react-redux';
-import { Button, Col, Row } from 'react-bootstrap';
-import Youtube from 'react-youtube';
-import { createSelector } from 'reselect';
-import debug from 'debug';
-
-import { hardGoTo } from '../../../redux/actions';
-import { toggleQuestionView } from '../redux/actions';
-import { getCurrentHike } from '../redux/selectors';
-
-const log = debug('fcc:hikes');
-
-const mapStateToProps = createSelector(
- getCurrentHike,
- (currentHike) => {
- const {
- dashedName,
- description,
- challengeSeed: [id] = [0]
- } = currentHike;
- return {
- id,
- dashedName,
- description
- };
- }
-);
-
-export class Lecture extends React.Component {
- static displayName = 'Lecture';
-
- static propTypes = {
- // actions
- toggleQuestionView: PropTypes.func,
- // ui
- id: PropTypes.string,
- description: PropTypes.array,
- dashedName: PropTypes.string,
- hardGoTo: PropTypes.func
- };
-
- componentWillMount() {
- if (!this.props.id) {
- // this.props.hardGoTo('/map');
- }
- }
-
- shouldComponentUpdate(nextProps) {
- const { props } = this;
- return nextProps.id !== props.id;
- }
-
- handleError: log;
-
- renderTranscript(transcript, dashedName) {
- return transcript.map((line, index) => (
-
- ));
- }
-
- render() {
- const {
- id = '1',
- description = [],
- toggleQuestionView
- } = this.props;
-
- const dashedName = 'foo';
-
- return (
-
-
-
-
-
-
- { this.renderTranscript(description, dashedName) }
-
-
- Take me to the Questions
-
-
-
- );
- }
-}
-
-export default connect(
- mapStateToProps,
- { hardGoTo, toggleQuestionView }
-)(Lecture);
diff --git a/common/app/routes/Hikes/components/Map.jsx b/common/app/routes/Hikes/components/Map.jsx
deleted file mode 100644
index 391c68693b..0000000000
--- a/common/app/routes/Hikes/components/Map.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { PropTypes } from 'react';
-import { Link } from 'react-router';
-import { ListGroup, ListGroupItem } from 'react-bootstrap';
-
-export default React.createClass({
- displayName: 'HikesMap',
-
- propTypes: {
- hikes: PropTypes.array
- },
-
- render() {
- const {
- hikes = [{}]
- } = this.props;
-
- const vidElements = hikes.map(({ title, dashedName }) => {
- return (
-
-
- { title }
-
-
- );
- });
-
- return (
-
-
-
Welcome To Hikes!
-
-
-
- { vidElements }
-
-
- );
- }
-});
diff --git a/common/app/routes/Hikes/index.js b/common/app/routes/Hikes/index.js
deleted file mode 100644
index 206bf97b4d..0000000000
--- a/common/app/routes/Hikes/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-export default {
- path: 'videos',
- getComponent(_, cb) {
- require.ensure(
- [ './components/Hikes.jsx' ],
- require => {
- cb(null, require('./components/Hikes.jsx').default);
- },
- 'hikes'
- );
- },
- getChildRoutes(_, cb) {
- require.ensure(
- [ './components/Hike.jsx' ],
- require => {
- cb(null, [{
- path: ':dashedName',
- component: require('./components/Hike.jsx').default
- }]);
- },
- 'hikes'
- );
- }
-};
diff --git a/common/app/routes/Hikes/redux/actions.js b/common/app/routes/Hikes/redux/actions.js
deleted file mode 100644
index 10d434f068..0000000000
--- a/common/app/routes/Hikes/redux/actions.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { createAction } from 'redux-actions';
-
-import types from './types';
-import { getMouse } from './utils';
-
-
-// fetchHikes(dashedName?: String) => Action
-// used with fetchHikesSaga
-export const fetchHikes = createAction(types.fetchHikes);
-
-// fetchHikesCompleted(hikes: Object) => Action
-// hikes is a normalized response from server
-// called within fetchHikesSaga
-export const fetchHikesCompleted = createAction(
- types.fetchHikesCompleted,
- (entities, hikes, currentHike) => ({ hikes, currentHike }),
- entities => ({ entities })
-);
-
-export const resetHike = createAction(types.resetHike);
-
-export const toggleQuestionView = createAction(types.toggleQuestionView);
-
-export const grabQuestion = createAction(types.grabQuestion, e => {
- let { pageX, pageY, touches } = e;
- if (touches) {
- e.preventDefault();
- // these re-assigns the values of pageX, pageY from touches
- ({ pageX, pageY } = touches[0]);
- }
- const delta = [pageX, pageY];
- const mouse = [0, 0];
-
- return { delta, mouse };
-});
-
-export const releaseQuestion = createAction(types.releaseQuestion);
-export const moveQuestion = createAction(
- types.moveQuestion,
- ({ e, delta }) => getMouse(e, delta)
-);
-
-// answer({
-// e: Event,
-// answer: Boolean,
-// userAnswer: Boolean,
-// info: String,
-// threshold: Number
-// }) => Action
-export const answerQuestion = createAction(types.answerQuestion);
-
-export const startShake = createAction(types.startShake);
-export const endShake = createAction(types.primeNextQuestion);
-
-export const goToNextQuestion = createAction(types.goToNextQuestion);
-
-export const hikeCompleted = createAction(types.hikeCompleted);
-export const goToNextHike = createAction(types.goToNextHike);
diff --git a/common/app/routes/Hikes/redux/answer-saga.js b/common/app/routes/Hikes/redux/answer-saga.js
deleted file mode 100644
index 3b2ee5ad6a..0000000000
--- a/common/app/routes/Hikes/redux/answer-saga.js
+++ /dev/null
@@ -1,138 +0,0 @@
-import { Observable } from 'rx';
-import { push } from 'react-router-redux';
-
-import types from './types';
-import { getMouse } from './utils';
-
-import {
- createErrorObservable,
- makeToast,
- updatePoints
-} from '../../../redux/actions';
-import { hikeCompleted, goToNextHike } from './actions';
-import { postJSON$ } from '../../../../utils/ajax-stream';
-import { getCurrentHike } from './selectors';
-
-function handleAnswer(action, getState) {
- const {
- e,
- answer,
- userAnswer,
- info,
- threshold
- } = action.payload;
-
- const state = getState();
- const { id, name, challengeType, tests } = getCurrentHike(state);
- const {
- app: { isSignedIn, csrfToken },
- hikesApp: {
- currentQuestion,
- delta = [ 0, 0 ]
- }
- } = state;
-
- let finalAnswer;
- // drag answer, compute response
- if (typeof userAnswer === 'undefined') {
- const [positionX] = getMouse(e, delta);
-
- // question released under threshold
- if (Math.abs(positionX) < threshold) {
- return Observable.just(null);
- }
-
- if (positionX >= threshold) {
- finalAnswer = true;
- }
-
- if (positionX <= -threshold) {
- finalAnswer = false;
- }
- } else {
- finalAnswer = userAnswer;
- }
-
- // incorrect question
- if (answer !== finalAnswer) {
- let infoAction;
- if (info) {
- infoAction = makeToast({
- title: 'Hint',
- message: info,
- type: 'info'
- });
- }
-
- return Observable
- .just({ type: types.endShake })
- .delay(500)
- .startWith(infoAction, { type: types.startShake });
- }
-
- if (tests[currentQuestion]) {
- return Observable
- .just({ type: types.goToNextQuestion })
- .delay(300)
- .startWith({ type: types.primeNextQuestion });
- }
-
- let updateUser$;
- if (isSignedIn) {
- const body = { id, name, challengeType: +challengeType, _csrf: csrfToken };
- updateUser$ = postJSON$('/completed-challenge', body)
- // if post fails, will retry once
- .retry(3)
- .flatMap(({ alreadyCompleted, points }) => {
- return Observable.of(
- makeToast({
- message:
- 'Challenge saved.' +
- (alreadyCompleted ? '' : ' First time Completed!'),
- title: 'Saved',
- type: 'info'
- }),
- updatePoints(points)
- );
- })
- .catch(createErrorObservable);
- } else {
- updateUser$ = Observable.empty();
- }
-
- const challengeCompleted$ = Observable.of(
- goToNextHike(),
- makeToast({
- title: 'Congratulations!',
- message: 'Hike completed.' + (isSignedIn ? ' Saving...' : ''),
- type: 'success'
- })
- );
-
- return Observable.merge(challengeCompleted$, updateUser$)
- .delay(300)
- .startWith(hikeCompleted(finalAnswer))
- .catch(createErrorObservable)
- // end with action so we know it is ok to transition
- .concat(Observable.just({ type: types.transitionHike }));
-}
-
-export default function answerSaga(action$, getState) {
- return action$
- .filter(action => {
- return action.type === types.answerQuestion ||
- action.type === types.transitionHike;
- })
- .flatMap(action => {
- if (action.type === types.answerQuestion) {
- return handleAnswer(action, getState);
- }
-
- const { hikesApp: { currentHike } } = getState();
- // if no next hike currentHike will equal '' which is falsy
- if (currentHike) {
- return Observable.just(push(`/videos/${currentHike}`));
- }
- return Observable.just(push('/map'));
- });
-}
diff --git a/common/app/routes/Hikes/redux/fetch-hikes-saga.js b/common/app/routes/Hikes/redux/fetch-hikes-saga.js
deleted file mode 100644
index 748f8c2961..0000000000
--- a/common/app/routes/Hikes/redux/fetch-hikes-saga.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { normalize, Schema, arrayOf } from 'normalizr';
-
-import types from './types';
-import { fetchHikesCompleted } from './actions';
-import { createErrorObserable } from '../../../redux/actions';
-
-import { findCurrentHike } from './utils';
-
-// const log = debug('fcc:fetch-hikes-saga');
-const hike = new Schema('hike', { idAttribute: 'dashedName' });
-
-export default function fetchHikesSaga(action$, getState, { services }) {
- return action$
- .filter(action => action.type === types.fetchHikes)
- .flatMap(action => {
- const dashedName = action.payload;
- return services.readService$({ service: 'hikes' })
- .map(hikes => {
- const { entities, result } = normalize(
- { hikes },
- { hikes: arrayOf(hike) }
- );
- const currentHike = findCurrentHike(result.hikes, dashedName);
- return fetchHikesCompleted(entities, result.hikes, currentHike);
- })
- .catch(createErrorObserable);
- });
-}
diff --git a/common/app/routes/Hikes/redux/index.js b/common/app/routes/Hikes/redux/index.js
deleted file mode 100644
index 8d94299b34..0000000000
--- a/common/app/routes/Hikes/redux/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export actions from './actions';
-export reducer from './reducer';
-export types from './types';
-
-import answerSaga from './answer-saga';
-import fetchHikesSaga from './fetch-hikes-saga';
-
-export const sagas = [ answerSaga, fetchHikesSaga ];
diff --git a/common/app/routes/Hikes/redux/reducer.js b/common/app/routes/Hikes/redux/reducer.js
deleted file mode 100644
index 5a850643c6..0000000000
--- a/common/app/routes/Hikes/redux/reducer.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import { handleActions } from 'redux-actions';
-import types from './types';
-import { findNextHikeName } from './utils';
-
-const initialState = {
- hikes: [],
- // ui
- // hike dashedName
- currentHike: '',
- // 1 indexed
- currentQuestion: 1,
- // [ xPosition, yPosition ]
- mouse: [ 0, 0 ],
- // change in mouse position since pressed
- // [ xDelta, yDelta ]
- delta: [ 0, 0 ],
- isPressed: false,
- isCorrect: false,
- shouldShakeQuestion: false,
- shouldShowQuestions: false
-};
-
-export default handleActions(
- {
- [types.toggleQuestionView]: state => ({
- ...state,
- shouldShowQuestions: !state.shouldShowQuestions,
- currentQuestion: 1
- }),
-
- [types.grabQuestion]: (state, { payload: { delta, mouse } }) => ({
- ...state,
- isPressed: true,
- delta,
- mouse
- }),
-
- [types.releaseQuestion]: state => ({
- ...state,
- isPressed: false,
- mouse: [ 0, 0 ]
- }),
-
- [types.moveQuestion]: (state, { payload: mouse }) => ({ ...state, mouse }),
-
- [types.resetHike]: state => ({
- ...state,
- currentQuestion: 1,
- shouldShowQuestions: false,
- mouse: [0, 0],
- delta: [0, 0]
- }),
-
- [types.startShake]: state => ({ ...state, shouldShakeQuestion: true }),
- [types.endShake]: state => ({ ...state, shouldShakeQuestion: false }),
-
- [types.primeNextQuestion]: (state, { payload: userAnswer }) => ({
- ...state,
- currentQuestion: state.currentQuestion + 1,
- mouse: [ userAnswer ? 1000 : -1000, 0],
- isPressed: false
- }),
-
- [types.goToNextQuestion]: state => ({
- ...state,
- mouse: [ 0, 0 ]
- }),
-
- [types.hikeCompleted]: (state, { payload: userAnswer } ) => ({
- ...state,
- isCorrect: true,
- isPressed: false,
- delta: [ 0, 0 ],
- mouse: [ userAnswer ? 1000 : -1000, 0]
- }),
-
- [types.goToNextHike]: state => ({
- ...state,
- currentHike: findNextHikeName(state.hikes, state.currentHike),
- mouse: [ 0, 0 ]
- }),
-
- [types.transitionHike]: state => ({
- ...state,
- showQuestions: false,
- currentQuestion: 1
- }),
-
- [types.fetchHikesCompleted]: (state, { payload }) => {
- const { hikes, currentHike } = payload;
- return {
- ...state,
- hikes,
- currentHike
- };
- }
- },
- initialState
-);
diff --git a/common/app/routes/Hikes/redux/selectors.js b/common/app/routes/Hikes/redux/selectors.js
deleted file mode 100644
index 507a90e1c2..0000000000
--- a/common/app/routes/Hikes/redux/selectors.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// use this file for common selectors
-import { createSelector } from 'reselect';
-
-export const getCurrentHike = createSelector(
- state => state.entities.hike,
- state => state.hikesApp.currentHike,
- (hikesMap, currentHikeDashedName) => (hikesMap[currentHikeDashedName] || {})
-);
diff --git a/common/app/routes/Hikes/redux/types.js b/common/app/routes/Hikes/redux/types.js
deleted file mode 100644
index 26e547dce0..0000000000
--- a/common/app/routes/Hikes/redux/types.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import createTypes from '../../../utils/create-types';
-
-export default createTypes([
- 'fetchHikes',
- 'fetchHikesCompleted',
- 'resetHike',
-
- 'toggleQuestionView',
- 'grabQuestion',
- 'releaseQuestion',
- 'moveQuestion',
-
- 'answerQuestion',
-
- 'startShake',
- 'endShake',
-
- 'primeNextQuestion',
- 'goToNextQuestion',
- 'transitionHike',
-
- 'hikeCompleted',
- 'goToNextHike'
-], 'videos');
diff --git a/common/app/routes/Hikes/redux/utils.js b/common/app/routes/Hikes/redux/utils.js
deleted file mode 100644
index 6b3f6f0fbe..0000000000
--- a/common/app/routes/Hikes/redux/utils.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import debug from 'debug';
-import _ from 'lodash';
-
-const log = debug('fcc:hikes:utils');
-
-function getFirstHike(hikes) {
- return hikes[0];
-}
-
-// interface Hikes {
-// results: String[],
-// entities: {
-// hikeId: Challenge
-// }
-// }
-//
-// findCurrentHike({
-// hikes: Hikes,
-// dashedName: String
-// }) => String
-export function findCurrentHike(hikes, dashedName) {
- if (!dashedName) {
- return getFirstHike(hikes) || {};
- }
-
- const filterRegex = new RegExp(dashedName, 'i');
-
- return hikes
- .filter(dashedName => {
- return filterRegex.test(dashedName);
- })
- .reduce((throwAway, hike) => {
- return hike;
- }, '');
-}
-
-export function getCurrentHike(hikes = {}, dashedName) {
- if (!dashedName) {
- return getFirstHike(hikes) || {};
- }
- return hikes.entities[dashedName];
-}
-
-// findNextHikeName(
-// hikes: String[],
-// dashedName: String
-// ) => String
-export function findNextHikeName(hikes, dashedName) {
- if (!dashedName) {
- log('find next hike no dashedName provided');
- return hikes[0];
- }
- const currentIndex = _.findIndex(
- hikes,
- _dashedName => _dashedName === dashedName
- );
-
- if (currentIndex >= hikes.length) {
- return '';
- }
- return hikes[currentIndex + 1];
-}
-
-
-export function getMouse(e, [dx, dy]) {
- let { pageX, pageY, touches, changedTouches } = e;
-
- // touches can be empty on touchend
- if (touches || changedTouches) {
- e.preventDefault();
- // these re-assigns the values of pageX, pageY from touches
- ({ pageX, pageY } = touches[0] || changedTouches[0]);
- }
-
- return [pageX - dx, pageY - dy];
-}
diff --git a/common/app/routes/Jobs/README.md b/common/app/routes/Jobs/README.md
deleted file mode 100644
index 5dde417dab..0000000000
--- a/common/app/routes/Jobs/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This folder contains everything relative to Jobs board
diff --git a/common/app/routes/Jobs/components/JobNotFound.jsx b/common/app/routes/Jobs/components/JobNotFound.jsx
deleted file mode 100644
index e05a886694..0000000000
--- a/common/app/routes/Jobs/components/JobNotFound.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { LinkContainer } from 'react-router-bootstrap';
-import { Button, Row, Col } from 'react-bootstrap';
-
-export default class extends React.Component {
- static displayName = 'NoJobFound';
-
- shouldComponentUpdate() {
- return false;
- }
-
- render() {
- return (
-
-
-
-
- No job found...
-
-
- Go to the job board
-
-
-
-
-
-
- );
- }
-}
diff --git a/common/app/routes/Jobs/components/JobTotal.jsx b/common/app/routes/Jobs/components/JobTotal.jsx
deleted file mode 100644
index df540408db..0000000000
--- a/common/app/routes/Jobs/components/JobTotal.jsx
+++ /dev/null
@@ -1,306 +0,0 @@
-import { CompositeDisposable } from 'rx';
-import React, { PropTypes } from 'react';
-import { Button, Input, Col, Row, Well } from 'react-bootstrap';
-import { connect } from 'react-redux';
-import { push } from 'react-router-redux';
-import PureComponent from 'react-pure-render/component';
-import { createSelector } from 'reselect';
-
-import {
- applyPromo,
- clearPromo,
- updatePromo
-} from '../redux/actions';
-
-// real paypal buttons
-// will take your money
-const paypalIds = {
- regular: 'Q8Z82ZLAX3Q8N',
- highlighted: 'VC8QPSKCYMZLN'
-};
-
-const bindableActions = {
- applyPromo,
- clearPromo,
- push,
- updatePromo
-};
-
-const mapStateToProps = createSelector(
- state => state.jobsApp.newJob,
- state => state.jobsApp,
- (
- { id, isHighlighted } = {},
- {
- buttonId,
- price = 1000,
- discountAmount = 0,
- promoCode = '',
- promoApplied = false,
- promoName = ''
- }
- ) => {
- if (!buttonId) {
- buttonId = isHighlighted ?
- paypalIds.highlighted :
- paypalIds.regular;
- }
- return {
- id,
- isHighlighted,
- price,
- discountAmount,
- promoName,
- promoCode,
- promoApplied
- };
- }
-);
-
-export class JobTotal extends PureComponent {
- constructor(...args) {
- super(...args);
- this._subscriptions = new CompositeDisposable();
- }
-
- static displayName = 'JobTotal';
-
- static propTypes = {
- id: PropTypes.string,
- isHighlighted: PropTypes.bool,
- buttonId: PropTypes.string,
- price: PropTypes.number,
- discountAmount: PropTypes.number,
- promoName: PropTypes.string,
- promoCode: PropTypes.string,
- promoApplied: PropTypes.bool
- };
-
- componentWillMount() {
- if (!this.props.id) {
- this.props.push('/jobs');
- }
-
- this.props.clearPromo();
- }
-
- componentWillUnmount() {
- this._subscriptions.dispose();
- }
-
- renderDiscount(discountAmount) {
- if (!discountAmount) {
- return null;
- }
- return (
-
-
- Promo Discount
-
-
- -{ discountAmount }
-
-
- );
- }
-
- renderHighlightPrice(isHighlighted) {
- if (!isHighlighted) {
- return null;
- }
- return (
-
-
- Highlighting
-
-
- + 250
-
-
- );
- }
-
- renderPromo() {
- const {
- id,
- promoApplied,
- promoCode,
- promoName,
- isHighlighted,
- applyPromo,
- updatePromo
- } = this.props;
-
- if (promoApplied) {
- return (
-
-
-
-
- { promoName } applied
-
-
-
- );
- }
-
- return (
-
-
-
-
- Have a promo code?
-
-
-
-
-
-
-
- {
- const subscription = applyPromo({
- id,
- code: promoCode,
- type: isHighlighted ? 'isHighlighted' : null
- }).subscribe();
- this._subscriptions.add(subscription);
- }}>
- Apply Promo Code
-
-
-
-
- );
- }
-
- render() {
- const {
- id,
- isHighlighted,
- buttonId,
- price,
- discountAmount,
- push
- } = this.props;
-
- return (
-
-
-
-
-
-
-
- One more step
-
-
- You're Awesome! just one more step to go.
- Clicking on the link below will redirect to paypal.
-
-
-
-
-
-
- Job Posting
-
-
- + { price }
-
-
- { this.renderHighlightPrice(isHighlighted) }
- { this.renderDiscount(discountAmount) }
-
-
- Total
-
-
- ${
- price - discountAmount + (isHighlighted ? 250 : 0)
- }
-
-
-
- { this.renderPromo() }
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default connect(mapStateToProps, bindableActions)(JobTotal);
diff --git a/common/app/routes/Jobs/components/Jobs.jsx b/common/app/routes/Jobs/components/Jobs.jsx
deleted file mode 100644
index d0a7102fe8..0000000000
--- a/common/app/routes/Jobs/components/Jobs.jsx
+++ /dev/null
@@ -1,149 +0,0 @@
-import React, { cloneElement, PropTypes } from 'react';
-import { compose } from 'redux';
-import { contain } from 'redux-epic';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import { LinkContainer } from 'react-router-bootstrap';
-
-import PureComponent from 'react-pure-render/component';
-import { Button, Row, Col } from 'react-bootstrap';
-
-import ListJobs from './List.jsx';
-
-import {
- findJob,
- fetchJobs
-} from '../redux/actions';
-
-const mapStateToProps = createSelector(
- state => state.entities.job,
- state => state.jobsApp.jobs,
- (jobsMap, jobsById) => ({
- jobs: jobsById.map(id => jobsMap[id])
- })
-);
-
-const bindableActions = {
- findJob,
- fetchJobs
-};
-
-const fetchOptions = {
- fetchAction: 'fetchJobs',
- isPrimed({ jobs }) {
- return jobs.length > 1;
- }
-};
-
-export class Jobs extends PureComponent {
- static displayName = 'Jobs';
-
- static propTypes = {
- push: PropTypes.func,
- findJob: PropTypes.func,
- fetchJobs: PropTypes.func,
- children: PropTypes.element,
- jobs: PropTypes.array,
- showModal: PropTypes.bool
- };
-
- createJobClickHandler() {
- const { findJob } = this.props;
-
- return (id) => {
- findJob(id);
- };
- }
-
- renderList(handleJobClick, jobs) {
- return (
-
- );
- }
-
- renderChild(child, jobs) {
- if (!child) {
- return null;
- }
- return cloneElement(
- child,
- { jobs }
- );
- }
-
- render() {
- const {
- children,
- jobs
- } = this.props;
-
- return (
-
-
-
- Hire a JavaScript engineer who's experienced in HTML5,
- Node.js, MongoDB, and Agile Development.
-
-
-
-
-
-
- Post a job: $1,000
-
-
-
-
-
-
-
-
-
-
-
-
-
- We hired our last developer out of Free Code Camp
- and couldn't be happier. Free Code Camp is now
- our go-to way to bring on pre-screened candidates
- who are enthusiastic about learning quickly and
- becoming immediately productive in their new career.
-
-
- Michael Gai, CEO at CoNarrative
-
-
-
-
-
- { this.renderChild(children, jobs) ||
- this.renderList(this.createJobClickHandler(), jobs) }
-
-
-
- );
- }
-}
-
-export default compose(
- connect(mapStateToProps, bindableActions),
- contain(fetchOptions)
-)(Jobs);
diff --git a/common/app/routes/Jobs/components/List.jsx b/common/app/routes/Jobs/components/List.jsx
deleted file mode 100644
index daebd1d999..0000000000
--- a/common/app/routes/Jobs/components/List.jsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import React, { PropTypes } from 'react';
-import classnames from 'classnames';
-import { LinkContainer } from 'react-router-bootstrap';
-import { ListGroup, ListGroupItem } from 'react-bootstrap';
-import PureComponent from 'react-pure-render/component';
-
-export default class ListJobs extends PureComponent {
- static displayName = 'ListJobs';
-
- static propTypes = {
- handleClick: PropTypes.func,
- jobs: PropTypes.array
- };
-
- addLocation(locale) {
- if (!locale) {
- return null;
- }
- return (
-
- { locale }
-
- );
- }
-
- renderJobs(handleClick, jobs = []) {
- return jobs
- .filter(({ isPaid, isApproved, isFilled }) => {
- return isPaid && isApproved && !isFilled;
- })
- .map(({
- id,
- company,
- position,
- isHighlighted,
- locale
- }) => {
-
- const className = classnames({
- 'jobs-list': true,
- 'col-xs-12': true,
- 'jobs-list-highlight': isHighlighted
- });
-
- const to = `/jobs/${id}`;
-
- return (
-
- handleClick(id) }>
-
-
- { company }
- {' '}
-
- - { position }
-
-
-
- { this.addLocation(locale) }
-
-
-
-
- );
- });
- }
-
- render() {
- const {
- handleClick,
- jobs
- } = this.props;
-
- return (
-
- { this.renderJobs(handleClick, jobs) }
-
- );
- }
-}
diff --git a/common/app/routes/Jobs/components/NewJob.jsx b/common/app/routes/Jobs/components/NewJob.jsx
deleted file mode 100644
index 1bab05eb77..0000000000
--- a/common/app/routes/Jobs/components/NewJob.jsx
+++ /dev/null
@@ -1,361 +0,0 @@
-import { helpers } from 'rx';
-import React, { PropTypes } from 'react';
-import PureComponent from 'react-pure-render/component';
-import { push } from 'react-router-redux';
-import { reduxForm } from 'redux-form';
-// import debug from 'debug';
-import dedent from 'dedent';
-import { isAscii, isEmail } from 'validator';
-
-import {
- Button,
- Col,
- Input,
- Row
-} from 'react-bootstrap';
-
-import {
- isValidURL,
- makeOptional,
- makeRequired,
- createFormValidator,
- getValidationState
-} from '../../../utils/form';
-import { saveForm, loadSavedForm } from '../redux/actions';
-
-// const log = debug('fcc:jobs:newForm');
-
-const hightlightCopy = `
-Highlight my post to make it stand out. (+$250)
-`;
-
-const isRemoteCopy = `
-This job can be performed remotely.
-`;
-
-const howToApplyCopy = dedent`
- Examples: click here to apply yourcompany.com/jobs/33
- Or email jobs@yourcompany.com
-`;
-
-const checkboxClass = dedent`
- text-left
- jobs-checkbox-spacer
- col-sm-offset-2
- col-sm-6 col-md-offset-3
-`;
-
-const certTypes = {
- isFrontEndCert: 'isFrontEndCert',
- isBackEndCert: 'isBackEndCert'
-};
-
-const fields = [
- 'position',
- 'locale',
- 'description',
- 'email',
- 'url',
- 'logo',
- 'company',
- 'isHighlighted',
- 'isRemoteOk',
- 'isFrontEndCert',
- 'isBackEndCert',
- 'howToApply'
-];
-
-const fieldValidators = {
- position: makeRequired(isAscii),
- locale: makeRequired(isAscii),
- description: makeRequired(helpers.identity),
- email: makeRequired(isEmail),
- url: makeRequired(isValidURL),
- logo: makeOptional(isValidURL),
- company: makeRequired(isAscii),
- howToApply: makeRequired(isAscii)
-};
-
-export class NewJob extends PureComponent {
- static displayName = 'NewJob';
-
- static propTypes = {
- fields: PropTypes.object,
- handleSubmit: PropTypes.func,
- loadSavedForm: PropTypes.func,
- push: PropTypes.func,
- saveForm: PropTypes.func
- };
-
- componentDidMount() {
- this.props.loadSavedForm();
- }
-
- handleSubmit(job) {
- this.props.saveForm(job);
- this.props.push('/jobs/new/preview');
- }
-
- handleCertClick(name) {
- const { fields } = this.props;
- Object.keys(certTypes).forEach(certType => {
- if (certType === name) {
- return fields[certType].onChange(true);
- }
- return fields[certType].onChange(false);
- });
- }
-
- render() {
- const {
- fields: {
- position,
- locale,
- description,
- email,
- url,
- logo,
- company,
- isHighlighted,
- isRemoteOk,
- howToApply,
- isFrontEndCert,
- isBackEndCert
- },
- handleSubmit
- } = this.props;
-
- const { handleChange } = this;
- const labelClass = 'col-sm-offset-1 col-sm-2';
- const inputClass = 'col-sm-6';
-
- return (
-
- );
- }
-}
-
-export default reduxForm(
- {
- form: 'NewJob',
- fields,
- validate: createFormValidator(fieldValidators)
- },
- state => ({ initialValues: state.jobsApp.initialValues }),
- {
- loadSavedForm,
- push,
- saveForm
- }
-)(NewJob);
diff --git a/common/app/routes/Jobs/components/NewJobCompleted.jsx b/common/app/routes/Jobs/components/NewJobCompleted.jsx
deleted file mode 100644
index 7d40199844..0000000000
--- a/common/app/routes/Jobs/components/NewJobCompleted.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import { LinkContainer } from 'react-router-bootstrap';
-import { Button, Col, Row } from 'react-bootstrap';
-
-export default class extends React.createClass {
- static displayName = 'NewJobCompleted';
-
- shouldComponentUpdate() {
- return false;
- }
-
- render() {
- return (
-
-
-
-
- Your Position has Been Submitted
-
-
-
-
- We’ll review your listing and email you when it’s live.
-
- Thank you for listing this job with Free Code Camp.
-
-
-
-
-
- Go to the job board
-
-
-
-
- );
- }
-}
diff --git a/common/app/routes/Jobs/components/Preview.jsx b/common/app/routes/Jobs/components/Preview.jsx
deleted file mode 100644
index 7509408e50..0000000000
--- a/common/app/routes/Jobs/components/Preview.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { CompositeDisposable } from 'rx';
-import React, { PropTypes } from 'react';
-import { Button, Row, Col } from 'react-bootstrap';
-import { connect } from 'react-redux';
-import PureComponent from 'react-pure-render/component';
-import { goBack, push } from 'react-router-redux';
-
-import ShowJob from './ShowJob.jsx';
-import JobNotFound from './JobNotFound.jsx';
-
-import { clearForm, saveJob } from '../redux/actions';
-
-const mapStateToProps = state => ({ job: state.jobsApp.newJob });
-
-const bindableActions = {
- goBack,
- push,
- clearForm,
- saveJob
-};
-
-export class JobPreview extends PureComponent {
- constructor(...args) {
- super(...args);
- this._subscriptions = new CompositeDisposable();
- }
-
- static displayName = 'Preview';
-
- static propTypes = {
- job: PropTypes.object,
- saveJob: PropTypes.func,
- clearForm: PropTypes.func,
- push: PropTypes.func
- };
-
- componentWillMount() {
- const { push, job } = this.props;
- // redirect user in client
- if (!job || !job.position || !job.description) {
- push('/jobs/new');
- }
- }
-
- componentWillUnmount() {
- this._subscriptions.dispose();
- }
-
- handleJobSubmit() {
- const { clearForm, saveJob, job } = this.props;
- clearForm();
- const subscription = saveJob(job).subscribe();
- this._subscriptions.add(subscription);
- }
-
- render() {
- const { job, goBack } = this.props;
-
- if (!job || !job.position || !job.description) {
- return ;
- }
-
- return (
-
-
-
-
-
-
-
- this.handleJobSubmit() }>
-
- Looks great! Let's Check Out
-
-
- Head back and make edits
-
-
-
-
-
- );
- }
-}
-
-export default connect(mapStateToProps, bindableActions)(JobPreview);
diff --git a/common/app/routes/Jobs/components/Show.jsx b/common/app/routes/Jobs/components/Show.jsx
deleted file mode 100644
index 5a1f9fa3cf..0000000000
--- a/common/app/routes/Jobs/components/Show.jsx
+++ /dev/null
@@ -1,146 +0,0 @@
-import React, { PropTypes } from 'react';
-import { compose } from 'redux';
-import { contain } from 'redux-epic';
-import { connect } from 'react-redux';
-import { push } from 'react-router-redux';
-import PureComponent from 'react-pure-render/component';
-import { createSelector } from 'reselect';
-
-import { fetchJobs } from '../redux/actions';
-
-import ShowJob from './ShowJob.jsx';
-import JobNotFound from './JobNotFound.jsx';
-import { isJobValid } from '../utils';
-
-function shouldShowApply(
- {
- isFrontEndCert: isFrontEndCertReq = false,
- isBackEndCert: isBackEndCertReq = false
- }, {
- isFrontEndCert = false,
- isBackEndCert = false
- }
-) {
- return (!isFrontEndCertReq && !isBackEndCertReq) ||
- (isBackEndCertReq && isBackEndCert) ||
- (isFrontEndCertReq && isFrontEndCert);
-}
-
-function generateMessage(
- {
- isFrontEndCert: isFrontEndCertReq = false,
- isBackEndCert: isBackEndCertReq = false
- },
- {
- isFrontEndCert = false,
- isBackEndCert = false,
- isSignedIn = false
- }
-) {
-
- if (!isSignedIn) {
- return 'Must be signed in to apply';
- }
- if (isFrontEndCertReq && !isFrontEndCert) {
- return 'This employer requires Free Code Camp’s Front ' +
- 'End Development Certification in order to apply';
- }
- if (isBackEndCertReq && !isBackEndCert) {
- return 'This employer requires Free Code Camp’s Back ' +
- 'End Development Certification in order to apply';
- }
- if (isFrontEndCertReq && isFrontEndCertReq) {
- return 'This employer requires the Front End Development Certification. ' +
- "You've earned it, so feel free to apply.";
- }
- return 'This employer requires the Back End Development Certification. ' +
- "You've earned it, so feel free to apply.";
-}
-
-const mapStateToProps = createSelector(
- state => state.app,
- state => state.jobsApp.currentJob,
- state => state.entities.job,
- ({ username, isFrontEndCert, isBackEndCert }, currentJob, jobMap) => ({
- username,
- isFrontEndCert,
- isBackEndCert,
- job: jobMap[currentJob] || {}
- })
-);
-
-const bindableActions = {
- push,
- fetchJobs
-};
-
-const fetchOptions = {
- fetchAction: 'fetchJobs',
- getActionArgs({ params: { id } }) {
- return [ id ];
- },
- isPrimed({ params: { id } = {}, job = {} }) {
- return job.id === id;
- },
- // using es6 destructuring
- shouldRefetch({ job }, { params: { id } }) {
- return job.id !== id;
- }
-};
-
-export class Show extends PureComponent {
- static displayName = 'Show';
-
- static propTypes = {
- job: PropTypes.object,
- isBackEndCert: PropTypes.bool,
- isFrontEndCert: PropTypes.bool,
- username: PropTypes.string
- };
-
- componentDidMount() {
- const { job, push } = this.props;
- // redirect user in client
- if (!isJobValid(job)) {
- push('/jobs');
- }
- }
-
- render() {
- const {
- isBackEndCert,
- isFrontEndCert,
- job,
- username
- } = this.props;
-
- if (!isJobValid(job)) {
- return ;
- }
-
- const isSignedIn = !!username;
-
- const showApply = shouldShowApply(
- job,
- { isFrontEndCert, isBackEndCert }
- );
-
- const message = generateMessage(
- job,
- { isFrontEndCert, isBackEndCert, isSignedIn }
- );
-
- return (
-
- );
- }
-}
-
-export default compose(
- connect(mapStateToProps, bindableActions),
- contain(fetchOptions)
-)(Show);
diff --git a/common/app/routes/Jobs/components/ShowJob.jsx b/common/app/routes/Jobs/components/ShowJob.jsx
deleted file mode 100644
index b6bdc2030c..0000000000
--- a/common/app/routes/Jobs/components/ShowJob.jsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React, { PropTypes } from 'react';
-import { Row, Col, Thumbnail } from 'react-bootstrap';
-import PureComponent from 'react-pure-render/component';
-import urlRegexFactory from 'url-regex';
-
-const urlRegex = urlRegexFactory();
-const defaultImage =
- 'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png';
-
-const thumbnailStyle = {
- backgroundColor: 'white',
- maxHeight: '100px',
- maxWidth: '100px'
-};
-
-function addATags(text) {
- return text.replace(urlRegex, function(match) {
- return `${match} `;
- });
-}
-
-export default class extends PureComponent {
- static displayName = 'ShowJob';
-
- static propTypes = {
- job: PropTypes.object,
- params: PropTypes.object,
- showApply: PropTypes.bool,
- preview: PropTypes.bool,
- message: PropTypes.string
- };
-
- renderHeader({ company, position }) {
- return (
-
-
{ company }
-
- { position }
-
-
- );
- }
-
- renderHowToApply(showApply, preview, message, howToApply) {
- if (!showApply) {
- return (
-
-
- { message }
-
-
- );
- }
-
- return (
-
-
-
-
- { preview ? 'How do I apply?' : message }
-
-
-
-
-
-
- );
- }
-
- render() {
- const {
- showApply = true,
- message,
- preview = true,
- job = {}
- } = this.props;
-
- const {
- logo,
- position,
- city,
- company,
- state,
- locale,
- description,
- howToApply
- } = job;
-
- return (
-
-
-
-
-
-
- { company }
-
-
-
-
-
-
-
-
-
- Position: { position || 'N/A' }
-
- Location:
- { locale ? locale : `${city}, ${state}` }
-
-
-
-
-
-
- { description }
-
-
- { this.renderHowToApply(showApply, preview, message, howToApply) }
-
-
-
-
- );
- }
-}
diff --git a/common/app/routes/Jobs/index.js b/common/app/routes/Jobs/index.js
deleted file mode 100644
index 1c7dfa85ed..0000000000
--- a/common/app/routes/Jobs/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-export default {
- getChildRoutes: (_, cb) => {
- require.ensure(
- [
- './components/Jobs.jsx',
- './components/NewJob.jsx',
- './components/Preview.jsx',
- './components/JobTotal.jsx',
- './components/NewJobCompleted.jsx',
- './components/Show.jsx'
- ],
- require => {
- cb(null, [{
- path: '/jobs',
- component: require('./components/Jobs.jsx').default
- }, {
- path: 'jobs/new',
- component: require('./components/NewJob.jsx').default
- }, {
- path: 'jobs/new/preview',
- component: require('./components/Preview.jsx').default
- }, {
- path: 'jobs/new/check-out',
- component: require('./components/JobTotal.jsx').default
- }, {
- path: 'jobs/new/completed',
- component: require('./components/NewJobCompleted.jsx').default
- }, {
- path: 'jobs/:id',
- component: require('./components/Show.jsx').default
- }]);
- },
- 'jobs'
- );
- }
-};
diff --git a/common/app/routes/Jobs/redux/actions.js b/common/app/routes/Jobs/redux/actions.js
deleted file mode 100644
index 40a328f757..0000000000
--- a/common/app/routes/Jobs/redux/actions.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { createAction } from 'redux-actions';
-
-import types from './types';
-
-export const fetchJobs = createAction(types.fetchJobs);
-export const fetchJobsCompleted = createAction(
- types.fetchJobsCompleted,
- (_, currentJob, jobs) => ({ currentJob, jobs }),
- entities => ({ entities })
-);
-
-export const findJob = createAction(types.findJob);
-
-// saves to database
-export const saveJob = createAction(types.saveJob);
-// saves to localStorage
-export const saveForm = createAction(types.saveForm);
-
-export const saveCompleted = createAction(types.saveCompleted);
-
-export const clearForm = createAction(types.clearForm);
-
-export const loadSavedForm = createAction(types.loadSavedForm);
-export const loadSavedFormCompleted = createAction(
- types.loadSavedFormCompleted
-);
-
-export const clearPromo = createAction(types.clearPromo);
-export const updatePromo = createAction(
- types.updatePromo,
- ({ target: { value = '' } = {} } = {}) => value
-);
-
-export const applyPromo = createAction(types.applyPromo);
-export const applyPromoCompleted = createAction(types.applyPromoCompleted);
diff --git a/common/app/routes/Jobs/redux/apply-promo-saga.js b/common/app/routes/Jobs/redux/apply-promo-saga.js
deleted file mode 100644
index 102dff7bcc..0000000000
--- a/common/app/routes/Jobs/redux/apply-promo-saga.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Observable } from 'rx';
-
-import { applyPromo } from './types';
-import { applyPromoCompleted } from './actions';
-import { postJSON$ } from '../../../../utils/ajax-stream';
-
-export default function applyPromoSaga(action$) {
- return action$
- .filter(action => action.type === applyPromo)
- .flatMap(action => {
- const { id, code = '', type = null } = action.payload;
- const body = {
- id,
- code: code.replace(/[^\d\w\s]/, '')
- };
- if (type) {
- body.type = type;
- }
- return postJSON$('/api/promos/getButton', body)
- .retry(3)
- .map(({ promo }) => {
- if (!promo || !promo.buttonId) {
- throw new Error('No promo returned by server');
- }
-
- return applyPromoCompleted(promo);
- })
- .catch(error => Observable.just({
- type: 'app.handleError',
- error
- }));
- });
-}
diff --git a/common/app/routes/Jobs/redux/fetch-jobs-saga.js b/common/app/routes/Jobs/redux/fetch-jobs-saga.js
deleted file mode 100644
index 1d8c4244dc..0000000000
--- a/common/app/routes/Jobs/redux/fetch-jobs-saga.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Observable } from 'rx';
-import { normalize, Schema, arrayOf } from 'normalizr';
-
-import { fetchJobsCompleted } from './actions';
-import { fetchJobs } from './types';
-import { handleError } from '../../../redux/types';
-
-const job = new Schema('job', { idAttribute: 'id' });
-
-export default function fetchJobsSaga(action$, getState, { services }) {
- return action$
- .filter(action => action.type === fetchJobs)
- .flatMap(action => {
- const { payload: id } = action;
- const data = { service: 'jobs' };
- if (id) {
- data.params = { id };
- }
- return services.readService$(data)
- .map(jobs => {
- if (!Array.isArray(jobs)) {
- jobs = [jobs];
- }
- const { entities, result } = normalize(
- { jobs },
- { jobs: arrayOf(job) }
- );
- return fetchJobsCompleted(
- entities,
- result.jobs[0],
- result.jobs
- );
- })
- .catch(error => Observable.just({ type: handleError, error }));
- });
-}
diff --git a/common/app/routes/Jobs/redux/index.js b/common/app/routes/Jobs/redux/index.js
deleted file mode 100644
index fe3f432b3e..0000000000
--- a/common/app/routes/Jobs/redux/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export actions from './actions';
-export reducer from './reducer';
-export types from './types';
-
-import fetchJobsSaga from './fetch-jobs-saga';
-import saveJobSaga from './save-job-saga';
-import applyPromoSaga from './apply-promo-saga';
-
-export formNormalizer from './jobs-form-normalizer';
-
-export const sagas = [ fetchJobsSaga, saveJobSaga, applyPromoSaga ];
diff --git a/common/app/routes/Jobs/redux/jobs-form-normalizer.js b/common/app/routes/Jobs/redux/jobs-form-normalizer.js
deleted file mode 100644
index df7baa8507..0000000000
--- a/common/app/routes/Jobs/redux/jobs-form-normalizer.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import {
- inHTMLData,
- uriInSingleQuotedAttr
-} from 'xss-filters';
-
-import { callIfDefined, formatUrl } from '../../../utils/form';
-
-export default {
- NewJob: {
- position: callIfDefined(inHTMLData),
- locale: callIfDefined(inHTMLData),
- description: callIfDefined(inHTMLData),
- email: callIfDefined(inHTMLData),
- url: callIfDefined(value => formatUrl(uriInSingleQuotedAttr(value))),
- logo: callIfDefined(value => formatUrl(uriInSingleQuotedAttr(value))),
- company: callIfDefined(inHTMLData),
- howToApply: callIfDefined(inHTMLData)
- }
-};
diff --git a/common/app/routes/Jobs/redux/reducer.js b/common/app/routes/Jobs/redux/reducer.js
deleted file mode 100644
index 2a270b777b..0000000000
--- a/common/app/routes/Jobs/redux/reducer.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { handleActions } from 'redux-actions';
-
-import types from './types';
-
-const replaceMethod = ''.replace;
-function replace(str) {
- if (!str) { return ''; }
- return replaceMethod.call(str, /[^\d\w\s]/, '');
-}
-
-const initialState = {
- // used by NewJob form
- initialValues: {},
- currentJob: '',
- newJob: {},
- jobs: []
-};
-
-export default handleActions(
- {
- [types.findJob]: (state, { payload: id }) => {
- return {
- ...state,
- currentJob: id
- };
- },
- [types.fetchJobsCompleted]: (state, { payload: { jobs, currentJob } }) => ({
- ...state,
- currentJob,
- jobs
- }),
- [types.updatePromo]: (state, { payload }) => ({
- ...state,
- promoCode: replace(payload)
- }),
- [types.saveCompleted]: (state, { payload: newJob }) => {
- return {
- ...state,
- newJob
- };
- },
- [types.loadSavedFormCompleted]: (state, { payload: initialValues }) => ({
- ...state,
- initialValues
- }),
- [types.applyPromoCompleted]: (state, { payload: promo }) => {
-
- const {
- fullPrice: price,
- buttonId,
- discountAmount,
- code: promoCode,
- name: promoName
- } = promo;
-
- return {
- ...state,
- price,
- buttonId,
- discountAmount,
- promoCode,
- promoApplied: true,
- promoName
- };
- },
- [types.clearPromo]: state => ({
- /* eslint-disable no-undefined */
- ...state,
- price: undefined,
- buttonId: undefined,
- discountAmount: undefined,
- promoCode: undefined,
- promoApplied: false,
- promoName: undefined
- /* eslint-enable no-undefined */
- })
- },
- initialState
-);
diff --git a/common/app/routes/Jobs/redux/save-job-saga.js b/common/app/routes/Jobs/redux/save-job-saga.js
deleted file mode 100644
index f9158b1245..0000000000
--- a/common/app/routes/Jobs/redux/save-job-saga.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { push } from 'react-router-redux';
-import { Observable } from 'rx';
-
-import { saveCompleted } from './actions';
-import { saveJob } from './types';
-
-import { handleError } from '../../../redux/types';
-
-export default function saveJobSaga(action$, getState, { services }) {
- return action$
- .filter(action => action.type === saveJob)
- .flatMap(action => {
- const { payload: job } = action;
- return services.createService$({ service: 'jobs', params: { job } })
- .retry(3)
- .flatMap(job => Observable.of(
- saveCompleted(job),
- push('/jobs/new/check-out')
- ))
- .catch(error => Observable.just({
- type: handleError,
- error
- }));
- });
-}
diff --git a/common/app/routes/Jobs/redux/types.js b/common/app/routes/Jobs/redux/types.js
deleted file mode 100644
index 9a387f58c7..0000000000
--- a/common/app/routes/Jobs/redux/types.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import createTypes from '../../../utils/create-types';
-
-export default createTypes([
- 'fetchJobs',
- 'fetchJobsCompleted',
-
- 'findJob',
- 'saveJob',
- 'saveForm',
-
- 'saveCompleted',
-
- 'clearForm',
-
- 'loadSavedForm',
- 'loadSavedFormCompleted',
-
- 'clearPromo',
- 'updatePromo',
- 'applyPromo',
- 'applyPromoCompleted'
-], 'jobs');
diff --git a/common/app/routes/Jobs/utils.js b/common/app/routes/Jobs/utils.js
deleted file mode 100644
index 6a8f1705b7..0000000000
--- a/common/app/routes/Jobs/utils.js
+++ /dev/null
@@ -1,29 +0,0 @@
-const defaults = {
- string: {
- value: '',
- valid: false,
- pristine: true,
- type: 'string'
- },
- bool: {
- value: false,
- type: 'boolean'
- }
-};
-
-export function getDefaults(type, value) {
- if (!type) {
- return defaults['string'];
- }
- if (value) {
- return Object.assign({}, defaults[type], { value });
- }
- return Object.assign({}, defaults[type]);
-}
-
-export function isJobValid(job) {
- return job &&
- !job.isFilled &&
- job.isApproved &&
- job.isPaid;
-}
diff --git a/common/app/routes/challenges/components/Show.jsx b/common/app/routes/challenges/components/Show.jsx
index badc1f9477..07fb075d6a 100644
--- a/common/app/routes/challenges/components/Show.jsx
+++ b/common/app/routes/challenges/components/Show.jsx
@@ -8,6 +8,7 @@ import PureComponent from 'react-pure-render/component';
import Classic from './classic/Classic.jsx';
import Step from './step/Step.jsx';
import Project from './project/Project.jsx';
+import Video from './video/Video.jsx';
import { fetchChallenge, fetchChallenges } from '../redux/actions';
import { challengeSelector } from '../redux/selectors';
@@ -16,7 +17,8 @@ const views = {
step: Step,
classic: Classic,
project: Project,
- simple: Project
+ simple: Project,
+ video: Video
};
const bindableActions = {
diff --git a/common/app/routes/challenges/components/video/Lecture.jsx b/common/app/routes/challenges/components/video/Lecture.jsx
new file mode 100644
index 0000000000..0f4cd42970
--- /dev/null
+++ b/common/app/routes/challenges/components/video/Lecture.jsx
@@ -0,0 +1,110 @@
+import React, { PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { Button, Col, Row } from 'react-bootstrap';
+import Youtube from 'react-youtube';
+import { createSelector } from 'reselect';
+import debug from 'debug';
+
+import { toggleQuestionView } from '../../redux/actions';
+import { challengeSelector } from '../../redux/selectors';
+
+const log = debug('fcc:videos');
+
+const mapStateToProps = createSelector(
+ challengeSelector,
+ ({
+ challenge: {
+ id = 'foo',
+ dashedName,
+ description,
+ challengeSeed: [ videoId ] = [ '1' ]
+ }
+ }) => ({
+ id,
+ videoId,
+ dashedName,
+ description
+ })
+);
+
+export class Lecture extends React.Component {
+ static displayName = 'Lecture';
+
+ static propTypes = {
+ // actions
+ toggleQuestionView: PropTypes.func,
+ // ui
+ id: PropTypes.string,
+ videoId: PropTypes.string,
+ description: PropTypes.array,
+ dashedName: PropTypes.string
+ };
+
+ shouldComponentUpdate(nextProps) {
+ const { props } = this;
+ return nextProps.id !== props.id;
+ }
+
+ handleError: log;
+
+ renderTranscript(transcript, dashedName) {
+ return transcript.map((line, index) => (
+
+ ));
+ }
+
+ render() {
+ const {
+ id,
+ videoId,
+ description = [],
+ toggleQuestionView
+ } = this.props;
+
+ const dashedName = 'foo';
+
+ return (
+
+
+
+
+
+
+
+
+
+ { this.renderTranscript(description, dashedName) }
+
+
+ Take me to the Questions
+
+
+
+
+
+ );
+ }
+}
+
+export default connect(
+ mapStateToProps,
+ { toggleQuestionView }
+)(Lecture);
diff --git a/common/app/routes/Hikes/components/Questions.jsx b/common/app/routes/challenges/components/video/Questions.jsx
similarity index 87%
rename from common/app/routes/Hikes/components/Questions.jsx
rename to common/app/routes/challenges/components/video/Questions.jsx
index 55911b6d02..e753b7fbe1 100644
--- a/common/app/routes/Hikes/components/Questions.jsx
+++ b/common/app/routes/challenges/components/video/Questions.jsx
@@ -9,8 +9,8 @@ import {
moveQuestion,
releaseQuestion,
grabQuestion
-} from '../redux/actions';
-import { getCurrentHike } from '../redux/selectors';
+} from '../../redux/actions';
+import { challengeSelector } from '../../redux/selectors';
const answerThreshold = 100;
const springProperties = { stiffness: 120, damping: 10 };
@@ -22,34 +22,30 @@ const actionsToBind = {
};
const mapStateToProps = createSelector(
- getCurrentHike,
- state => state.hikesApp,
+ challengeSelector,
+ state => state.challengesApp,
state => state.app.isSignedIn,
- (currentHike, ui, isSignedIn) => {
- const {
+ (
+ { challenge: { tests = [ ] }},
+ {
currentQuestion = 1,
mouse = [ 0, 0 ],
delta = [ 0, 0 ],
isCorrect = false,
isPressed = false,
shouldShakeQuestion = false
- } = ui;
-
- const {
- tests = []
- } = currentHike;
-
- return {
- tests,
- currentQuestion,
- isCorrect,
- mouse,
- delta,
- isPressed,
- shouldShakeQuestion,
- isSignedIn
- };
- }
+ },
+ isSignedIn
+ ) => ({
+ tests,
+ currentQuestion,
+ isCorrect,
+ mouse,
+ delta,
+ isPressed,
+ shouldShakeQuestion,
+ isSignedIn
+ })
);
class Question extends React.Component {
@@ -133,7 +129,8 @@ class Question extends React.Component {
onTouchEnd={ mouseUp }
onTouchMove={ this.handleMouseMove(isPressed, this.props) }
onTouchStart={ grabQuestion }
- style={ style }>
+ style={ style }
+ >
Question { number }
{ question }
@@ -162,7 +159,8 @@ class Question extends React.Component {
this.handleMouseUp(e, answer, info) }
xs={ 8 }
- xsOffset={ 2 }>
+ xsOffset={ 2 }
+ >
{ questionElement }
@@ -174,14 +172,16 @@ class Question extends React.Component {
bsSize='large'
bsStyle='primary'
className='pull-left'
- onClick={ this.onAnswer(answer, false, info) }>
+ onClick={ this.onAnswer(answer, false, info) }
+ >
false
+ onClick={ this.onAnswer(answer, true, info) }
+ >
true
diff --git a/common/app/routes/Hikes/components/Hike.jsx b/common/app/routes/challenges/components/video/Video.jsx
similarity index 64%
rename from common/app/routes/Hikes/components/Hike.jsx
rename to common/app/routes/challenges/components/video/Video.jsx
index ed42875864..3937ca0b40 100644
--- a/common/app/routes/Hikes/components/Hike.jsx
+++ b/common/app/routes/challenges/components/video/Video.jsx
@@ -5,26 +5,27 @@ import { createSelector } from 'reselect';
import Lecture from './Lecture.jsx';
import Questions from './Questions.jsx';
-import { resetHike } from '../redux/actions';
-import { updateTitle } from '../../../redux/actions';
-import { getCurrentHike } from '../redux/selectors';
+import { resetUi } from '../../redux/actions';
+import { updateTitle } from '../../../../redux/actions';
+import { challengeSelector } from '../../redux/selectors';
+const bindableActions = { resetUi, updateTitle };
const mapStateToProps = createSelector(
- getCurrentHike,
- state => state.hikesApp.shouldShowQuestions,
- (currentHike, shouldShowQuestions) => ({
- title: currentHike ? currentHike.title : '',
+ challengeSelector,
+ state => state.challengesApp.shouldShowQuestions,
+ ({ challenge: { title } }, shouldShowQuestions) => ({
+ title,
shouldShowQuestions
})
);
// export plain component for testing
-export class Hike extends React.Component {
- static displayName = 'Hike';
+export class Video extends React.Component {
+ static displayName = 'Video';
static propTypes = {
// actions
- resetHike: PropTypes.func,
+ resetUi: PropTypes.func,
// ui
title: PropTypes.string,
params: PropTypes.object,
@@ -38,12 +39,12 @@ export class Hike extends React.Component {
}
componentWillUnmount() {
- this.props.resetHike();
+ this.props.resetUi();
}
- componentWillReceiveProps({ params: { dashedName } }) {
- if (this.props.params.dashedName !== dashedName) {
- this.props.resetHike();
+ componentWillReceiveProps({ title }) {
+ if (this.props.title !== title) {
+ this.props.resetUi();
}
}
@@ -69,7 +70,8 @@ export class Hike extends React.Component {
+ title={ title }
+ >
{ this.renderBody(shouldShowQuestions) }
@@ -78,4 +80,7 @@ export class Hike extends React.Component {
}
// export redux aware component
-export default connect(mapStateToProps, { resetHike, updateTitle })(Hike);
+export default connect(
+ mapStateToProps,
+ bindableActions
+)(Video);
diff --git a/common/app/routes/challenges/redux/actions.js b/common/app/routes/challenges/redux/actions.js
index bae870b397..de904ac50d 100644
--- a/common/app/routes/challenges/redux/actions.js
+++ b/common/app/routes/challenges/redux/actions.js
@@ -1,6 +1,6 @@
import { createAction } from 'redux-actions';
import { updateContents } from '../../../../utils/polyvinyl';
-import { loggerToStr } from '../utils';
+import { getMouse, loggerToStr } from '../utils';
import types from './types';
@@ -79,3 +79,40 @@ export const moveToNextChallenge = createAction(types.moveToNextChallenge);
export const saveCode = createAction(types.saveCode);
export const loadCode = createAction(types.loadCode);
export const savedCodeFound = createAction(types.savedCodeFound);
+
+
+// video challenges
+export const toggleQuestionView = createAction(types.toggleQuestionView);
+export const grabQuestion = createAction(types.grabQuestion, e => {
+ let { pageX, pageY, touches } = e;
+ if (touches) {
+ e.preventDefault();
+ // these re-assigns the values of pageX, pageY from touches
+ ({ pageX, pageY } = touches[0]);
+ }
+ const delta = [pageX, pageY];
+ const mouse = [0, 0];
+
+ return { delta, mouse };
+});
+
+export const releaseQuestion = createAction(types.releaseQuestion);
+export const moveQuestion = createAction(
+ types.moveQuestion,
+ ({ e, delta }) => getMouse(e, delta)
+);
+
+// answer({
+// e: Event,
+// answer: Boolean,
+// userAnswer: Boolean,
+// info: String,
+// threshold: Number
+// }) => Action
+export const answerQuestion = createAction(types.answerQuestion);
+
+export const startShake = createAction(types.startShake);
+export const endShake = createAction(types.primeNextQuestion);
+
+export const goToNextQuestion = createAction(types.goToNextQuestion);
+export const videoCompleted = createAction(types.videoCompleted);
diff --git a/common/app/routes/challenges/redux/answer-saga.js b/common/app/routes/challenges/redux/answer-saga.js
new file mode 100644
index 0000000000..b9ccedf028
--- /dev/null
+++ b/common/app/routes/challenges/redux/answer-saga.js
@@ -0,0 +1,86 @@
+import { Observable } from 'rx';
+import types from './types';
+import { getMouse } from '../utils';
+
+import { submitChallenge, videoCompleted } from './actions';
+import { createErrorObservable, makeToast } from '../../../redux/actions';
+import { challengeSelector } from './selectors';
+
+export default function answerSaga(action$, getState) {
+ return action$
+ .filter(action => action.type === types.answerQuestion)
+ .flatMap(({
+ payload: {
+ e,
+ answer,
+ userAnswer,
+ info,
+ threshold
+ }
+ }) => {
+ const state = getState();
+ const {
+ challenge: { tests }
+ } = challengeSelector(state);
+ const {
+ challengesApp: {
+ currentQuestion,
+ delta = [ 0, 0 ]
+ }
+ } = state;
+
+ let finalAnswer;
+ // drag answer, compute response
+ if (typeof userAnswer === 'undefined') {
+ const [positionX] = getMouse(e, delta);
+
+ // question released under threshold
+ if (Math.abs(positionX) < threshold) {
+ return Observable.just(null);
+ }
+
+ if (positionX >= threshold) {
+ finalAnswer = true;
+ }
+
+ if (positionX <= -threshold) {
+ finalAnswer = false;
+ }
+ } else {
+ finalAnswer = userAnswer;
+ }
+
+ // incorrect question
+ if (answer !== finalAnswer) {
+ let infoAction;
+ if (info) {
+ infoAction = makeToast({
+ title: 'Have a hint',
+ message: info,
+ type: 'info'
+ });
+ }
+
+ return Observable
+ .just({ type: types.endShake })
+ .delay(500)
+ .startWith(infoAction, { type: types.startShake });
+ }
+
+ if (tests[currentQuestion]) {
+ return Observable
+ .just({ type: types.goToNextQuestion })
+ .delay(300)
+ .startWith({ type: types.primeNextQuestion });
+ }
+
+
+ return Observable.just(submitChallenge())
+ .delay(300)
+ // moves question to the appropriate side of the screen
+ .startWith(videoCompleted(finalAnswer))
+ // end with action so we know it is ok to transition
+ .concat(Observable.just({ type: types.transitionHike }))
+ .catch(createErrorObservable);
+ });
+}
diff --git a/common/app/routes/challenges/redux/completion-saga.js b/common/app/routes/challenges/redux/completion-saga.js
index 502548c0c2..870698b66a 100644
--- a/common/app/routes/challenges/redux/completion-saga.js
+++ b/common/app/routes/challenges/redux/completion-saga.js
@@ -12,6 +12,9 @@ import { backEndProject } from '../../../utils/challengeTypes';
import { randomCompliment } from '../../../utils/get-words';
import { postJSON$ } from '../../../../utils/ajax-stream';
+// NOTE(@BerkeleyTrue): this file could benefit from some refactoring.
+// lots of repeat code
+
function completedChallenge(state) {
let body;
let isSignedIn = false;
@@ -163,6 +166,7 @@ function submitSimpleChallenge(type, state) {
const submitTypes = {
tests: submitModern,
step: submitSimpleChallenge,
+ video: submitSimpleChallenge,
'project.frontEnd': submitProject,
'project.backEnd': submitProject,
'project.simple': submitSimpleChallenge
diff --git a/common/app/routes/challenges/redux/index.js b/common/app/routes/challenges/redux/index.js
index a7dec45500..5d0fc7cc67 100644
--- a/common/app/routes/challenges/redux/index.js
+++ b/common/app/routes/challenges/redux/index.js
@@ -5,11 +5,13 @@ export types from './types';
import fetchChallengesSaga from './fetch-challenges-saga';
import completionSaga from './completion-saga';
import nextChallengeSaga from './next-challenge-saga';
+import answerSaga from './answer-saga';
export projectNormalizer from './project-normalizer';
export const sagas = [
fetchChallengesSaga,
completionSaga,
- nextChallengeSaga
+ nextChallengeSaga,
+ answerSaga
];
diff --git a/common/app/routes/challenges/redux/next-challenge-saga.js b/common/app/routes/challenges/redux/next-challenge-saga.js
index 06a15c11d8..bb2daacd9b 100644
--- a/common/app/routes/challenges/redux/next-challenge-saga.js
+++ b/common/app/routes/challenges/redux/next-challenge-saga.js
@@ -8,7 +8,7 @@ import {
getFirstChallengeOfNextBlock,
getFirstChallengeOfNextSuperBlock
} from '../utils';
-import { getRandomVerb } from '../../../utils/get-words';
+import { randomVerb } from '../../../utils/get-words';
export default function nextChallengeSaga(actions$, getState) {
return actions$
@@ -48,7 +48,7 @@ export default function nextChallengeSaga(actions$, getState) {
}
message += ' Your next challenge has arrived.';
const toast = {
- // title: isNewSuperBlock || isNewBlock ? getRandomVerb() : null,
+ // title: isNewSuperBlock || isNewBlock ? randomVerb() : null,
message
};
*/
@@ -56,7 +56,7 @@ export default function nextChallengeSaga(actions$, getState) {
updateCurrentChallenge(nextChallenge),
resetUi(),
makeToast({
- title: getRandomVerb(),
+ title: randomVerb(),
message: 'Your next challenge has arrived.'
}),
push(`/challenges/${nextChallenge.block}/${nextChallenge.dashedName}`)
diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js
index f224ec9801..c56b4828ef 100644
--- a/common/app/routes/challenges/redux/reducer.js
+++ b/common/app/routes/challenges/redux/reducer.js
@@ -12,15 +12,30 @@ import {
} from '../utils';
const initialUiState = {
+ // step index tracing
currentIndex: 0,
previousIndex: -1,
+ // step action
isActionCompleted: false,
- isSubmitting: true,
+ // project is ready to submit
+ isSubmitting: false,
output: `/**
* Any console.log()
* statements will appear in
* here console.
- */`
+ */`,
+ // video
+ // 1 indexed
+ currentQuestion: 1,
+ // [ xPosition, yPosition ]
+ mouse: [ 0, 0 ],
+ // change in mouse position since pressed
+ // [ xDelta, yDelta ]
+ delta: [ 0, 0 ],
+ isPressed: false,
+ isCorrect: false,
+ shouldShakeQuestion: false,
+ shouldShowQuestions: false
};
const initialState = {
id: '',
@@ -107,6 +122,49 @@ const mainReducer = handleActions(
[types.updateOutput]: (state, { payload: output }) => ({
...state,
output: (state.output || '') + output
+ }),
+ // video
+ [types.toggleQuestionView]: state => ({
+ ...state,
+ shouldShowQuestions: !state.shouldShowQuestions,
+ currentQuestion: 1
+ }),
+
+ [types.grabQuestion]: (state, { payload: { delta, mouse } }) => ({
+ ...state,
+ isPressed: true,
+ delta,
+ mouse
+ }),
+
+ [types.releaseQuestion]: state => ({
+ ...state,
+ isPressed: false,
+ mouse: [ 0, 0 ]
+ }),
+
+ [types.moveQuestion]: (state, { payload: mouse }) => ({ ...state, mouse }),
+ [types.startShake]: state => ({ ...state, shouldShakeQuestion: true }),
+ [types.endShake]: state => ({ ...state, shouldShakeQuestion: false }),
+
+ [types.primeNextQuestion]: (state, { payload: userAnswer }) => ({
+ ...state,
+ currentQuestion: state.currentQuestion + 1,
+ mouse: [ userAnswer ? 1000 : -1000, 0],
+ isPressed: false
+ }),
+
+ [types.goToNextQuestion]: state => ({
+ ...state,
+ mouse: [ 0, 0 ]
+ }),
+
+ [types.videoCompleted]: (state, { payload: userAnswer } ) => ({
+ ...state,
+ isCorrect: true,
+ isPressed: false,
+ delta: [ 0, 0 ],
+ mouse: [ userAnswer ? 1000 : -1000, 0]
})
},
initialState
diff --git a/common/app/routes/challenges/redux/types.js b/common/app/routes/challenges/redux/types.js
index b29c85d297..87e34beadf 100644
--- a/common/app/routes/challenges/redux/types.js
+++ b/common/app/routes/challenges/redux/types.js
@@ -39,5 +39,21 @@ export default createTypes([
// code storage
'saveCode',
'loadCode',
- 'savedCodeFound'
+ 'savedCodeFound',
+
+ // video challenges
+ 'toggleQuestionView',
+ 'grabQuestion',
+ 'releaseQuestion',
+ 'moveQuestion',
+
+ 'answerQuestion',
+
+ 'startShake',
+ 'endShake',
+
+ 'primeNextQuestion',
+ 'goToNextQuestion',
+ 'transitionVideo',
+ 'videoCompleted'
], 'challenges');
diff --git a/common/app/routes/challenges/utils.js b/common/app/routes/challenges/utils.js
index dff8cfffdd..12fab290e6 100644
--- a/common/app/routes/challenges/utils.js
+++ b/common/app/routes/challenges/utils.js
@@ -168,3 +168,21 @@ export function getCurrentSuperBlockName(current, entities) {
const block = blockMap[challenge.block];
return block.superBlock;
}
+
+// gets new mouse position
+// getMouse(
+// e: MouseEvent|TouchEvent,
+// [ dx: Number, dy: Number ]
+// ) => [ Number, Number ]
+export function getMouse(e, [dx, dy]) {
+ let { pageX, pageY, touches, changedTouches } = e;
+
+ // touches can be empty on touchend
+ if (touches || changedTouches) {
+ e.preventDefault();
+ // these re-assigns the values of pageX, pageY from touches
+ ({ pageX, pageY } = touches[0] || changedTouches[0]);
+ }
+
+ return [pageX - dx, pageY - dy];
+}
diff --git a/common/app/routes/index.js b/common/app/routes/index.js
index bbb70e7438..b2ea7c0155 100644
--- a/common/app/routes/index.js
+++ b/common/app/routes/index.js
@@ -1,13 +1,9 @@
-import Jobs from './Jobs';
-import Hikes from './Hikes';
import { modernChallenges, map, challenges } from './challenges';
import NotFound from '../components/NotFound/index.jsx';
export default {
path: '/',
childRoutes: [
- Jobs,
- Hikes,
challenges,
modernChallenges,
map,
diff --git a/common/app/sagas.js b/common/app/sagas.js
index 18e4f31589..3b668202f9 100644
--- a/common/app/sagas.js
+++ b/common/app/sagas.js
@@ -1,11 +1,7 @@
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 challengeSagas } from './routes/challenges/redux';
export default [
...appSagas,
- ...hikesSagas,
- ...jobsSagas,
...challengeSagas
];