Make structure changes to hikes
This commit is contained in:
@ -4,46 +4,42 @@ import debugFactory from 'debug';
|
|||||||
const debug = debugFactory('freecc:app:actions');
|
const debug = debugFactory('freecc:app:actions');
|
||||||
|
|
||||||
export default Actions({
|
export default Actions({
|
||||||
|
shouldBindMethods: true,
|
||||||
|
refs: { displayName: 'AppActions' },
|
||||||
|
|
||||||
setTitle(title = 'Learn To Code') {
|
setTitle(title = 'Learn To Code') {
|
||||||
return { title: title + '| Free Code Camp' };
|
return { title: title + ' | Free Code Camp' };
|
||||||
},
|
},
|
||||||
|
|
||||||
setUser({
|
getUser({ isPrimed }) {
|
||||||
username,
|
if (isPrimed) {
|
||||||
picture,
|
return null;
|
||||||
progressTimestamps = [],
|
}
|
||||||
isFrontEndCert,
|
|
||||||
isFullStackCert
|
debug('fetching user data');
|
||||||
}) {
|
return this.readService$('user', null, null)
|
||||||
return {
|
.map(function({
|
||||||
username,
|
username,
|
||||||
picture,
|
picture,
|
||||||
points: progressTimestamps.length,
|
progressTimestamps = [],
|
||||||
isFrontEndCert,
|
isFrontEndCert,
|
||||||
isFullStackCert
|
isFullStackCert
|
||||||
};
|
}) {
|
||||||
|
return {
|
||||||
|
username,
|
||||||
|
picture,
|
||||||
|
points: progressTimestamps.length,
|
||||||
|
isFrontEndCert,
|
||||||
|
isFullStackCert
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getUser: null,
|
|
||||||
updateRoute(route) {
|
updateRoute(route) {
|
||||||
return { route };
|
return { route };
|
||||||
},
|
},
|
||||||
goBack: null
|
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;
|
|
||||||
});
|
|
||||||
|
@ -18,15 +18,15 @@ export default Store({
|
|||||||
value: initValue
|
value: initValue
|
||||||
},
|
},
|
||||||
init({ instance: appStore, args: [cat] }) {
|
init({ instance: appStore, args: [cat] }) {
|
||||||
const { updateRoute, setUser, setTitle } = cat.getActions('appActions');
|
const { updateRoute, getUser, setTitle } = cat.getActions('appActions');
|
||||||
const register = createRegistrar(appStore);
|
const register = createRegistrar(appStore);
|
||||||
let { setHikes } = cat.getActions('hikesActions');
|
const { fetchHikes } = cat.getActions('hikesActions');
|
||||||
|
|
||||||
// app
|
// app
|
||||||
register(setter(fromMany(setUser, setTitle, updateRoute)));
|
register(setter(fromMany(getUser, setTitle, updateRoute)));
|
||||||
|
|
||||||
// hikes
|
// hikes
|
||||||
register(setHikes);
|
register(fetchHikes);
|
||||||
|
|
||||||
return appStore;
|
return appStore;
|
||||||
}
|
}
|
||||||
|
56
common/app/routes/Hikes/components/Hike.jsx
Normal file
56
common/app/routes/Hikes/components/Hike.jsx
Normal file
@ -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 (
|
||||||
|
<Questions hike={ currentHike }/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
challengeSeed: [ id ] = ['1'],
|
||||||
|
description = []
|
||||||
|
} = currentHike;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Lecture
|
||||||
|
description={ description }
|
||||||
|
id={ id } />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { currentHike, showQuestions } = this.props;
|
||||||
|
const { title } = currentHike;
|
||||||
|
|
||||||
|
const videoTitle = <h2>{ title }</h2>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col xs={ 12 }>
|
||||||
|
<Row>
|
||||||
|
<Panel
|
||||||
|
className={ 'text-center' }
|
||||||
|
title={ videoTitle }>
|
||||||
|
{ this.renderBody(showQuestions, currentHike) }
|
||||||
|
</Panel>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -9,7 +9,10 @@ import HikesMap from './Map.jsx';
|
|||||||
|
|
||||||
export default contain(
|
export default contain(
|
||||||
{
|
{
|
||||||
store: 'hikesStore',
|
store: 'appStore',
|
||||||
|
map(state) {
|
||||||
|
return state.hikesApp;
|
||||||
|
},
|
||||||
actions: ['appActions'],
|
actions: ['appActions'],
|
||||||
fetchAction: 'hikesActions.fetchHikes',
|
fetchAction: 'hikesActions.fetchHikes',
|
||||||
getPayload: ({ hikes, params }) => ({
|
getPayload: ({ hikes, params }) => ({
|
||||||
@ -54,8 +57,12 @@ export default contain(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Row style={ preventOverflow }>
|
<Row style={ preventOverflow }>
|
||||||
{ 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)
|
||||||
|
}
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react';
|
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 { History } from 'react-router';
|
||||||
import Vimeo from 'react-vimeo';
|
import Vimeo from 'react-vimeo';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
@ -19,8 +19,6 @@ export default React.createClass({
|
|||||||
|
|
||||||
handleFinish() {
|
handleFinish() {
|
||||||
debug('loading questions');
|
debug('loading questions');
|
||||||
const { dashedName } = this.props.params;
|
|
||||||
this.history.pushState(null, `/hikes/${dashedName}/questions/1`);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderTranscript(transcript, dashedName) {
|
renderTranscript(transcript, dashedName) {
|
||||||
@ -31,7 +29,6 @@ export default React.createClass({
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
title,
|
|
||||||
challengeSeed = ['1'],
|
challengeSeed = ['1'],
|
||||||
description = []
|
description = []
|
||||||
} = this.props.currentHike;
|
} = this.props.currentHike;
|
||||||
@ -39,31 +36,22 @@ export default React.createClass({
|
|||||||
|
|
||||||
const [ id ] = challengeSeed;
|
const [ id ] = challengeSeed;
|
||||||
|
|
||||||
const videoTitle = <h2>{ title }</h2>;
|
|
||||||
return (
|
return (
|
||||||
<Col xs={ 12 }>
|
<Col xs={ 12 }>
|
||||||
<Row>
|
<Row>
|
||||||
<Panel className={ 'text-center' } title={ videoTitle }>
|
<Vimeo
|
||||||
<Vimeo
|
onError={ this.handleError }
|
||||||
onError={ this.handleError }
|
onFinish= { this.handleFinish }
|
||||||
onFinish= { this.handleFinish }
|
videoId={ id } />
|
||||||
videoId={ id } />
|
|
||||||
</Panel>
|
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={ 12 }>
|
{ this.renderTranscript(description, dashedName) }
|
||||||
<Panel>
|
<Button
|
||||||
{ this.renderTranscript(description, dashedName) }
|
block={ true }
|
||||||
</Panel>
|
bsSize='large'
|
||||||
<Panel>
|
onClick={ this.handleFinish }>
|
||||||
<Button
|
Take me to the Questions
|
||||||
block={ true }
|
</Button>
|
||||||
bsSize='large'
|
|
||||||
onClick={ this.handleFinish }>
|
|
||||||
Take me to the Questions
|
|
||||||
</Button>
|
|
||||||
</Panel>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ export default React.createClass({
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
hikes
|
hikes = [{}]
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const vidElements = hikes.map(({ title, dashedName}) => {
|
const vidElements = hikes.map(({ title, dashedName}) => {
|
||||||
|
@ -16,7 +16,7 @@ const debug = debugFactory('freecc:hikes');
|
|||||||
const ANSWER_THRESHOLD = 200;
|
const ANSWER_THRESHOLD = 200;
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'Question',
|
displayName: 'Questions',
|
||||||
|
|
||||||
mixins: [
|
mixins: [
|
||||||
History,
|
History,
|
@ -25,49 +25,41 @@ function getCurrentHike(hikes = [{}], dashedName, currentHike) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Actions({
|
export default Actions({
|
||||||
// start fetching hikes
|
refs: { displayName: 'HikesActions' },
|
||||||
fetchHikes: null,
|
shouldBindMethods: true,
|
||||||
// set hikes on store
|
fetchHikes({ isPrimed, dashedName }) {
|
||||||
setHikes: null
|
if (isPrimed) {
|
||||||
})
|
return {
|
||||||
.refs({ displayName: 'HikesActions' })
|
transform: (state) => {
|
||||||
.init(({ instance: hikeActions, args: [services] }) => {
|
|
||||||
// set up hikes fetching
|
|
||||||
hikeActions.fetchHikes.subscribe(
|
|
||||||
({ isPrimed, dashedName }) => {
|
|
||||||
if (isPrimed) {
|
|
||||||
return hikeActions.setHikes({
|
|
||||||
transform: (state) => {
|
|
||||||
|
|
||||||
const { hikesApp: oldState } = state;
|
const { hikesApp: oldState } = state;
|
||||||
const currentHike = getCurrentHike(
|
const currentHike = getCurrentHike(
|
||||||
oldState.hikes,
|
oldState.hikes,
|
||||||
dashedName,
|
dashedName,
|
||||||
oldState.currentHike
|
oldState.currentHike
|
||||||
);
|
);
|
||||||
|
|
||||||
const hikesApp = { ...oldState, currentHike };
|
const hikesApp = { ...oldState, currentHike };
|
||||||
return Object.assign({}, state, { hikesApp });
|
return Object.assign({}, state, { hikesApp });
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
services.read('hikes', null, null, (err, hikes) => {
|
return this.readService$('hikes', null, null)
|
||||||
if (err) {
|
.map(hikes => {
|
||||||
return console.error(err);
|
const hikesApp = {
|
||||||
|
hikes,
|
||||||
|
currentHike: getCurrentHike(hikes, dashedName)
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
transform(oldState) {
|
||||||
|
return Object.assign({}, oldState, { hikesApp });
|
||||||
}
|
}
|
||||||
|
};
|
||||||
const hikesApp = {
|
})
|
||||||
hikes,
|
.catch(err => {
|
||||||
currentHike: getCurrentHike(hikes, dashedName)
|
console.error(err);
|
||||||
};
|
});
|
||||||
|
}
|
||||||
hikeActions.setHikes({
|
});
|
||||||
transform(oldState) {
|
|
||||||
return Object.assign({}, oldState, { hikesApp });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export { default as HikesActions } from './Actions';
|
export { default as HikesActions } from './Actions';
|
||||||
export { default as HikesStore } from './Store';
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Hikes from './components/Hikes.jsx';
|
import Hikes from './components/Hikes.jsx';
|
||||||
import Lecture from './components/Lecture.jsx';
|
import Hike from './components/Hike.jsx';
|
||||||
import Question from './components/Question.jsx';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* show video /hikes/someVideo
|
* show video /hikes/someVideo
|
||||||
@ -12,9 +11,6 @@ export default {
|
|||||||
component: Hikes,
|
component: Hikes,
|
||||||
childRoutes: [{
|
childRoutes: [{
|
||||||
path: ':dashedName',
|
path: ':dashedName',
|
||||||
component: Lecture
|
component: Hike
|
||||||
}, {
|
|
||||||
path: ':dashedName/questions/:number',
|
|
||||||
component: Question
|
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
|
|
||||||
const trusted = [
|
let trusted = [
|
||||||
"'self'"
|
"'self'"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
trusted.push('ws://localhost:3001');
|
||||||
|
}
|
||||||
|
|
||||||
export default function csp() {
|
export default function csp() {
|
||||||
return helmet.csp({
|
return helmet.csp({
|
||||||
defaultSrc: trusted,
|
defaultSrc: trusted,
|
||||||
|
Reference in New Issue
Block a user