diff --git a/common/app/flux/Actions.js b/common/app/flux/Actions.js index 52334c4876..83196b90f0 100644 --- a/common/app/flux/Actions.js +++ b/common/app/flux/Actions.js @@ -4,46 +4,42 @@ import debugFactory from 'debug'; const debug = debugFactory('freecc:app:actions'); export default Actions({ + shouldBindMethods: true, + refs: { displayName: 'AppActions' }, + setTitle(title = 'Learn To Code') { - return { title: title + '| Free Code Camp' }; + return { title: title + ' | Free Code Camp' }; }, - setUser({ - username, - picture, - progressTimestamps = [], - isFrontEndCert, - isFullStackCert - }) { - return { - username, - picture, - points: progressTimestamps.length, - isFrontEndCert, - isFullStackCert - }; + getUser({ isPrimed }) { + if (isPrimed) { + return null; + } + + debug('fetching user data'); + return this.readService$('user', null, null) + .map(function({ + username, + picture, + progressTimestamps = [], + isFrontEndCert, + isFullStackCert + }) { + return { + username, + picture, + points: progressTimestamps.length, + isFrontEndCert, + isFullStackCert + }; + }) + .catch(err => { + console.error(err); + }); }, - getUser: null, updateRoute(route) { return { route }; }, goBack: null -}) - .refs({ displayName: 'AppActions' }) - .init(({ instance: appActions, args: [services] }) => { - appActions.getUser.subscribe(({ isPrimed }) => { - if (isPrimed) { - debug('isPrimed'); - return; - } - services.read('user', null, null, (err, user) => { - if (err) { - return debug('user service error'); - } - debug('user service returned successful'); - return appActions.setUser(user); - }); - }); - return appActions; - }); +}); diff --git a/common/app/flux/Store.js b/common/app/flux/Store.js index f174314f5a..59f2f7e959 100644 --- a/common/app/flux/Store.js +++ b/common/app/flux/Store.js @@ -18,15 +18,15 @@ export default Store({ value: initValue }, init({ instance: appStore, args: [cat] }) { - const { updateRoute, setUser, setTitle } = cat.getActions('appActions'); + const { updateRoute, getUser, setTitle } = cat.getActions('appActions'); const register = createRegistrar(appStore); - let { setHikes } = cat.getActions('hikesActions'); + const { fetchHikes } = cat.getActions('hikesActions'); // app - register(setter(fromMany(setUser, setTitle, updateRoute))); + register(setter(fromMany(getUser, setTitle, updateRoute))); // hikes - register(setHikes); + register(fetchHikes); return appStore; } diff --git a/common/app/routes/Hikes/components/Hike.jsx b/common/app/routes/Hikes/components/Hike.jsx new file mode 100644 index 0000000000..dbf00314d8 --- /dev/null +++ b/common/app/routes/Hikes/components/Hike.jsx @@ -0,0 +1,56 @@ +import React, { PropTypes } from 'react'; +import { + Col, + Panel, + Row +} from 'react-bootstrap'; + +import Lecture from './Lecture.jsx'; +import Questions from './Questions.jsx'; + +export default React.createClass({ + displayName: 'Hike', + + propTypes: { + showQuestions: PropTypes.bool, + currentHike: PropTypes.object + }, + + renderBody(showQuestions, currentHike) { + if (showQuestions) { + return ( + + ); + } + + const { + challengeSeed: [ id ] = ['1'], + description = [] + } = currentHike; + + return ( + + ); + }, + + render() { + const { currentHike, showQuestions } = this.props; + const { title } = currentHike; + + const videoTitle =

{ title }

; + + return ( + + + + { this.renderBody(showQuestions, currentHike) } + + + + ); + } +}); diff --git a/common/app/routes/Hikes/components/Hikes.jsx b/common/app/routes/Hikes/components/Hikes.jsx index 76632a1456..7943b49320 100644 --- a/common/app/routes/Hikes/components/Hikes.jsx +++ b/common/app/routes/Hikes/components/Hikes.jsx @@ -9,7 +9,10 @@ import HikesMap from './Map.jsx'; export default contain( { - store: 'hikesStore', + store: 'appStore', + map(state) { + return state.hikesApp; + }, actions: ['appActions'], fetchAction: 'hikesActions.fetchHikes', getPayload: ({ hikes, params }) => ({ @@ -54,8 +57,12 @@ export default contain( return (
- { this.renderChild(children, hikes, currentHike) || - this.renderMap(hikes) } + { + // render sub-route + this.renderChild(children, hikes, currentHike) || + // if no sub-route render hikes map + this.renderMap(hikes) + }
); diff --git a/common/app/routes/Hikes/components/Lecture.jsx b/common/app/routes/Hikes/components/Lecture.jsx index e47dc0fc98..b70c50d250 100644 --- a/common/app/routes/Hikes/components/Lecture.jsx +++ b/common/app/routes/Hikes/components/Lecture.jsx @@ -1,5 +1,5 @@ import React, { PropTypes } from 'react'; -import { Button, Col, Row, Panel } from 'react-bootstrap'; +import { Button, Col, Row } from 'react-bootstrap'; import { History } from 'react-router'; import Vimeo from 'react-vimeo'; import debugFactory from 'debug'; @@ -19,8 +19,6 @@ export default React.createClass({ handleFinish() { debug('loading questions'); - const { dashedName } = this.props.params; - this.history.pushState(null, `/hikes/${dashedName}/questions/1`); }, renderTranscript(transcript, dashedName) { @@ -31,7 +29,6 @@ export default React.createClass({ render() { const { - title, challengeSeed = ['1'], description = [] } = this.props.currentHike; @@ -39,31 +36,22 @@ export default React.createClass({ const [ id ] = challengeSeed; - const videoTitle =

{ title }

; return ( - - - + - - - { this.renderTranscript(description, dashedName) } - - - - - + { this.renderTranscript(description, dashedName) } + ); diff --git a/common/app/routes/Hikes/components/Map.jsx b/common/app/routes/Hikes/components/Map.jsx index 830f74a7ef..64e837603b 100644 --- a/common/app/routes/Hikes/components/Map.jsx +++ b/common/app/routes/Hikes/components/Map.jsx @@ -11,7 +11,7 @@ export default React.createClass({ render() { const { - hikes + hikes = [{}] } = this.props; const vidElements = hikes.map(({ title, dashedName}) => { diff --git a/common/app/routes/Hikes/components/Question.jsx b/common/app/routes/Hikes/components/Questions.jsx similarity index 99% rename from common/app/routes/Hikes/components/Question.jsx rename to common/app/routes/Hikes/components/Questions.jsx index 63dd15407d..d426b14916 100644 --- a/common/app/routes/Hikes/components/Question.jsx +++ b/common/app/routes/Hikes/components/Questions.jsx @@ -16,7 +16,7 @@ const debug = debugFactory('freecc:hikes'); const ANSWER_THRESHOLD = 200; export default React.createClass({ - displayName: 'Question', + displayName: 'Questions', mixins: [ History, diff --git a/common/app/routes/Hikes/flux/Actions.js b/common/app/routes/Hikes/flux/Actions.js index 3eed55159f..3f5e262f31 100644 --- a/common/app/routes/Hikes/flux/Actions.js +++ b/common/app/routes/Hikes/flux/Actions.js @@ -25,49 +25,41 @@ function getCurrentHike(hikes = [{}], dashedName, currentHike) { } export default Actions({ - // start fetching hikes - fetchHikes: null, - // set hikes on store - setHikes: null -}) - .refs({ displayName: 'HikesActions' }) - .init(({ instance: hikeActions, args: [services] }) => { - // set up hikes fetching - hikeActions.fetchHikes.subscribe( - ({ isPrimed, dashedName }) => { - if (isPrimed) { - return hikeActions.setHikes({ - transform: (state) => { + refs: { displayName: 'HikesActions' }, + shouldBindMethods: true, + fetchHikes({ isPrimed, dashedName }) { + if (isPrimed) { + return { + transform: (state) => { - const { hikesApp: oldState } = state; - const currentHike = getCurrentHike( - oldState.hikes, - dashedName, - oldState.currentHike - ); + const { hikesApp: oldState } = state; + const currentHike = getCurrentHike( + oldState.hikes, + dashedName, + oldState.currentHike + ); - const hikesApp = { ...oldState, currentHike }; - return Object.assign({}, state, { hikesApp }); - } - }); + const hikesApp = { ...oldState, currentHike }; + return Object.assign({}, state, { hikesApp }); } + }; + } - services.read('hikes', null, null, (err, hikes) => { - if (err) { - return console.error(err); + return this.readService$('hikes', null, null) + .map(hikes => { + const hikesApp = { + hikes, + currentHike: getCurrentHike(hikes, dashedName) + }; + + return { + transform(oldState) { + return Object.assign({}, oldState, { hikesApp }); } - - const hikesApp = { - hikes, - currentHike: getCurrentHike(hikes, dashedName) - }; - - hikeActions.setHikes({ - transform(oldState) { - return Object.assign({}, oldState, { hikesApp }); - } - }); - }); - } - ); - }); + }; + }) + .catch(err => { + console.error(err); + }); + } +}); diff --git a/common/app/routes/Hikes/flux/index.js b/common/app/routes/Hikes/flux/index.js index 05980cb3ff..336f72d297 100644 --- a/common/app/routes/Hikes/flux/index.js +++ b/common/app/routes/Hikes/flux/index.js @@ -1,2 +1 @@ export { default as HikesActions } from './Actions'; -export { default as HikesStore } from './Store'; diff --git a/common/app/routes/Hikes/index.js b/common/app/routes/Hikes/index.js index 203380153e..a53ac3193d 100644 --- a/common/app/routes/Hikes/index.js +++ b/common/app/routes/Hikes/index.js @@ -1,6 +1,5 @@ import Hikes from './components/Hikes.jsx'; -import Lecture from './components/Lecture.jsx'; -import Question from './components/Question.jsx'; +import Hike from './components/Hike.jsx'; /* * show video /hikes/someVideo @@ -12,9 +11,6 @@ export default { component: Hikes, childRoutes: [{ path: ':dashedName', - component: Lecture - }, { - path: ':dashedName/questions/:number', - component: Question + component: Hike }] }; diff --git a/server/middlewares/csp.js b/server/middlewares/csp.js index 80b41e5fbe..21e542dd01 100644 --- a/server/middlewares/csp.js +++ b/server/middlewares/csp.js @@ -1,9 +1,13 @@ import helmet from 'helmet'; -const trusted = [ +let trusted = [ "'self'" ]; +if (process.env.NODE_ENV !== 'production') { + trusted.push('ws://localhost:3001'); +} + export default function csp() { return helmet.csp({ defaultSrc: trusted,