Add React Map
This commit is contained in:
parent
844afb6e2f
commit
c909cd032e
@ -125,10 +125,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
#map-filter {
|
||||
.map-filter {
|
||||
background:#fff;
|
||||
border-color: darkgreen;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
width:40px;
|
||||
color: darkgreen;
|
||||
@ -149,22 +150,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mapWrapper {
|
||||
.map-wrapper {
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.map-accordion {
|
||||
width:700px;
|
||||
margin:155px auto 0;
|
||||
position:relative;
|
||||
#nested {
|
||||
margin:0 10px;
|
||||
width: 700px;
|
||||
margin: 180px auto 0;
|
||||
position: relative;
|
||||
|
||||
.map-accordion-panel-nested {
|
||||
margin: 0 20px;
|
||||
@media (max-width: 400px) {
|
||||
margin:0;
|
||||
}
|
||||
}
|
||||
|
||||
.map-accordion-panel-title {
|
||||
padding-bottom: 0px
|
||||
}
|
||||
|
||||
a:focus {
|
||||
text-decoration: none;
|
||||
color:darkgreen;
|
||||
@ -182,13 +190,15 @@
|
||||
padding-right:20px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
a {
|
||||
margin:15px 0;
|
||||
padding:0;
|
||||
|
||||
&:first-child {
|
||||
margin-top:25px
|
||||
}
|
||||
> a {
|
||||
|
||||
> h3 {
|
||||
padding-left: 40px;
|
||||
padding-bottom: 10px;
|
||||
display:block;
|
||||
@ -196,10 +206,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
div.chapterBlock {
|
||||
.map-accordion-block {
|
||||
:before {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
text-indent: -15px;
|
||||
margin-left: 60px;
|
||||
@ -210,7 +221,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.challengeBlockDescription {
|
||||
.challenge-block-description {
|
||||
margin:0;
|
||||
margin-top:-10px;
|
||||
padding:0 15px 23px 30px;
|
||||
@ -223,9 +234,9 @@
|
||||
}
|
||||
div > div:last-child {
|
||||
margin-bottom:30px
|
||||
}
|
||||
}
|
||||
}
|
||||
.challengeBlockTime {
|
||||
.challenge-block-time {
|
||||
font-size: 18px;
|
||||
color: #BBBBBB;
|
||||
display:block;
|
||||
|
@ -2,22 +2,24 @@ import { combineReducers } from 'redux';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
|
||||
import { reducer as app } from './redux';
|
||||
import entitieReducer from './redux/entities-reducer';
|
||||
import entitiesReducer from './redux/entities-reducer';
|
||||
import { reducer as hikesApp } from './routes/Hikes/redux';
|
||||
import { reducer as challengesApp } from './routes/challenges/redux';
|
||||
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({
|
||||
...sideReducers,
|
||||
entities: entitieReducer,
|
||||
entities: entitiesReducer,
|
||||
app,
|
||||
hikesApp,
|
||||
jobsApp,
|
||||
challengesApp,
|
||||
map,
|
||||
form: formReducer.normalize(jobsNormalizer)
|
||||
});
|
||||
}
|
||||
|
@ -8,25 +8,10 @@ export default ({ services }) => ({ dispatch }) => next => {
|
||||
}
|
||||
|
||||
return services.readService$({ service: 'user' })
|
||||
.map(({
|
||||
username,
|
||||
picture,
|
||||
points,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isFullStackCert
|
||||
}) => {
|
||||
.map((user) => {
|
||||
return {
|
||||
type: setUser,
|
||||
payload: {
|
||||
username,
|
||||
picture,
|
||||
points,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isFullStackCert,
|
||||
isSignedIn: true
|
||||
}
|
||||
payload: user
|
||||
};
|
||||
})
|
||||
.catch(error => Observable.just({
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Jobs from './Jobs';
|
||||
import Hikes from './Hikes';
|
||||
import Challenges from './challenges';
|
||||
import challenges from './challenges';
|
||||
import map from './map';
|
||||
import NotFound from '../components/NotFound/index.jsx';
|
||||
|
||||
export default {
|
||||
@ -8,7 +9,8 @@ export default {
|
||||
childRoutes: [
|
||||
Jobs,
|
||||
Hikes,
|
||||
Challenges,
|
||||
challenges,
|
||||
map,
|
||||
{
|
||||
path: '*',
|
||||
component: NotFound
|
||||
|
127
common/app/routes/map/components/Map.jsx
Normal file
127
common/app/routes/map/components/Map.jsx
Normal file
@ -0,0 +1,127 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import FA from 'react-fontawesome';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
Row,
|
||||
Panel
|
||||
} from 'react-bootstrap';
|
||||
|
||||
const challengeClassName = `
|
||||
text-primary
|
||||
padded-ionic-icon
|
||||
negative-15
|
||||
challenge-title
|
||||
ion-checkmark-circled
|
||||
`.replace(/[\n]/g, '');
|
||||
|
||||
export default class ShowMap extends PureComponent {
|
||||
static displayName = 'Map';
|
||||
static propTypes = {
|
||||
superBlocks: PropTypes.array
|
||||
};
|
||||
|
||||
renderChallenges(challenges) {
|
||||
if (!Array.isArray(challenges) || !challenges.length) {
|
||||
return <div>No Challenges Found</div>;
|
||||
}
|
||||
return challenges.map(challenge => {
|
||||
const { title, dashedName } = challenge;
|
||||
return (
|
||||
<p
|
||||
className={ challengeClassName }
|
||||
key={ title }>
|
||||
<a href={ `/challenges/${dashedName}` }>
|
||||
{ title }
|
||||
<span className='sr-only'>complete</span>
|
||||
</a>
|
||||
</p>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderBlocks(blocks) {
|
||||
if (!Array.isArray(blocks) || !blocks.length) {
|
||||
return <div>No Blocks Found</div>;
|
||||
}
|
||||
return blocks.map(block => {
|
||||
const { title, time, challenges } = block;
|
||||
return (
|
||||
<Panel
|
||||
bsClass='map-accordion-panel-nested'
|
||||
collapsible={ true }
|
||||
expanded={ true }
|
||||
header={
|
||||
<div>
|
||||
<h3><FA name='caret-right' />{ title }</h3>
|
||||
<span className='challenge-block-time'>({ time })</span>
|
||||
</div>
|
||||
}
|
||||
id={ title }
|
||||
key={ title }>
|
||||
{ this.renderChallenges(challenges) }
|
||||
</Panel>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderSuperBlocks(superBlocks) {
|
||||
if (!Array.isArray(superBlocks) || !superBlocks.length) {
|
||||
return <div>No Super Blocks</div>;
|
||||
}
|
||||
return superBlocks.map((superBlock) => {
|
||||
const { title, blocks } = superBlock;
|
||||
return (
|
||||
<Panel
|
||||
bsClass='map-accordion-panel'
|
||||
collapsible={ true }
|
||||
expanded={ true }
|
||||
header={ <h2><FA name='caret-right' />{ title }</h2> }
|
||||
id={ title }
|
||||
key={ title }>
|
||||
<div
|
||||
className='map-accordion-block'>
|
||||
{ this.renderBlocks(blocks) }
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { superBlocks } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div className='map-wrapper'>
|
||||
<div
|
||||
className='text-center map-fixed-header'
|
||||
style={{ top: '50px' }}>
|
||||
<p>Challenges required for certifications are marked with a *</p>
|
||||
<Row className='map-buttons'>
|
||||
<Button
|
||||
block={ true }
|
||||
bsStyle='primary'
|
||||
className='active center-block'>
|
||||
Collapse all challenges
|
||||
</Button>
|
||||
</Row>
|
||||
<Row className='map-buttons'>
|
||||
<Input
|
||||
addonAfter={ <span><i className='fa fa-search' /></span> }
|
||||
autocompleted='off'
|
||||
className='map-filter'
|
||||
placeholder='Type a challenge name'
|
||||
type='text' />
|
||||
</Row>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='map-accordion'>
|
||||
{ this.renderSuperBlocks(superBlocks) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
64
common/app/routes/map/components/Show.jsx
Normal file
64
common/app/routes/map/components/Show.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import PureComponent from 'react-pure-render/component';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import Map from './Map.jsx';
|
||||
import contain from '../../../utils/professor-x';
|
||||
import { fetchChallenges } from '../redux/actions';
|
||||
|
||||
const bindableActions = { fetchChallenges };
|
||||
const mapStateToProps = createSelector(
|
||||
state => state.map.superBlocks,
|
||||
state => state.entities.superBlock,
|
||||
state => state.entities.block,
|
||||
state => state.entities.challenge,
|
||||
(superBlocks, superBlockMap, blockMap, challengeMap) => {
|
||||
if (!superBlockMap || !blockMap || !challengeMap) {
|
||||
return {
|
||||
superBlocks: []
|
||||
};
|
||||
}
|
||||
const finalBlocks = superBlocks
|
||||
.map(superBlockName => superBlockMap[superBlockName])
|
||||
.map(superBlock => ({
|
||||
...superBlock,
|
||||
blocks: superBlock.blocks
|
||||
.map(blockName => blockMap[blockName])
|
||||
.map(block => ({
|
||||
...block,
|
||||
challenges: block.challenges
|
||||
.map(dashedName => challengeMap[dashedName])
|
||||
}))
|
||||
}));
|
||||
return {
|
||||
superBlocks: finalBlocks
|
||||
};
|
||||
}
|
||||
);
|
||||
const fetchOptions = {
|
||||
fetchAction: 'fetchChallenges',
|
||||
isPrimed({ superBlocks }) {
|
||||
return Array.isArray(superBlocks) && superBlocks.length > 0;
|
||||
}
|
||||
};
|
||||
|
||||
export class ShowMap extends PureComponent {
|
||||
static displayName = 'ShowMap';
|
||||
static propTypes = {
|
||||
superBlocks: PropTypes.array
|
||||
};
|
||||
|
||||
render() {
|
||||
const { superBlocks } = this.props;
|
||||
return (
|
||||
<Map superBlocks={ superBlocks } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, bindableActions),
|
||||
contain(fetchOptions)
|
||||
)(ShowMap);
|
0
common/app/routes/map/components/Static-Blocks.jsx
Normal file
0
common/app/routes/map/components/Static-Blocks.jsx
Normal file
6
common/app/routes/map/index.js
Normal file
6
common/app/routes/map/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import ShowMap from './components/Show.jsx';
|
||||
|
||||
export default {
|
||||
path: 'map',
|
||||
component: ShowMap
|
||||
};
|
10
common/app/routes/map/redux/actions.js
Normal file
10
common/app/routes/map/redux/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
|
||||
import types from './types';
|
||||
|
||||
export const fetchChallenges = createAction(types.fetchChallenges);
|
||||
export const fetchChallengesCompleted = createAction(
|
||||
types.fetchChallengesCompleted,
|
||||
(_, superBlocks) => superBlocks,
|
||||
entities => ({ entities })
|
||||
);
|
26
common/app/routes/map/redux/fetch-challenges-saga.js
Normal file
26
common/app/routes/map/redux/fetch-challenges-saga.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { Observable } from 'rx';
|
||||
import { fetchChallenges } from './types';
|
||||
import { fetchChallengesCompleted } from './actions';
|
||||
|
||||
import { handleError } from '../../../redux/types';
|
||||
|
||||
export default ({ services }) => ({ dispatch }) => next => {
|
||||
return function fetchChallengesSaga(action) {
|
||||
const result = next(action);
|
||||
if (action.type !== fetchChallenges) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return services.readService$({ service: 'map' })
|
||||
.map(({ entities, result } = {}) => {
|
||||
return fetchChallengesCompleted(entities, result);
|
||||
})
|
||||
.catch(error => {
|
||||
return Observable.just({
|
||||
type: handleError,
|
||||
error
|
||||
});
|
||||
})
|
||||
.doOnNext(dispatch);
|
||||
};
|
||||
};
|
6
common/app/routes/map/redux/index.js
Normal file
6
common/app/routes/map/redux/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
export actions from './actions';
|
||||
export reducer from './reducer';
|
||||
export types from './types';
|
||||
|
||||
import fetchChallengesSaga from './fetch-challenges-saga';
|
||||
export const sagas = [ fetchChallengesSaga ];
|
17
common/app/routes/map/redux/reducer.js
Normal file
17
common/app/routes/map/redux/reducer.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
import types from './types';
|
||||
|
||||
const initialState = {
|
||||
superBlocks: []
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[types.fetchChallengesCompleted]: (state, { payload = [] }) => ({
|
||||
...state,
|
||||
superBlocks: payload
|
||||
})
|
||||
},
|
||||
initialState
|
||||
);
|
6
common/app/routes/map/redux/types.js
Normal file
6
common/app/routes/map/redux/types.js
Normal file
@ -0,0 +1,6 @@
|
||||
import createTypes from '../../../utils/create-types';
|
||||
|
||||
export default createTypes([
|
||||
'fetchChallenges',
|
||||
'fetchChallengesCompleted'
|
||||
], 'map');
|
@ -1,9 +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';
|
||||
|
||||
export default [
|
||||
...appSagas,
|
||||
...hikesSagas,
|
||||
...jobsSagas
|
||||
...jobsSagas,
|
||||
...mapSagas
|
||||
];
|
||||
|
@ -34,6 +34,10 @@
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "The title of this block, suitable for display"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"validations": [],
|
||||
|
@ -39,6 +39,7 @@ Observable.combineLatest(
|
||||
var isComingSoon = !!challengeSpec.isComingSoon;
|
||||
var fileName = challengeSpec.fileName;
|
||||
var helpRoom = challengeSpec.helpRoom || 'Help';
|
||||
var time = challengeSpec.time || 'N/A';
|
||||
|
||||
console.log('parsed %s successfully', blockName);
|
||||
|
||||
@ -53,7 +54,8 @@ Observable.combineLatest(
|
||||
dashedName: dasherize(blockName),
|
||||
superOrder: superOrder,
|
||||
superBlock: superBlock,
|
||||
order: order
|
||||
order: order,
|
||||
time: time
|
||||
};
|
||||
|
||||
return createBlocks(block)
|
||||
|
@ -1,10 +1,8 @@
|
||||
import debugFactory from 'debug';
|
||||
import assign from 'object.assign';
|
||||
|
||||
const censor = '**********************:P********';
|
||||
const debug = debugFactory('fcc:services:user');
|
||||
const protectedUserFields = {
|
||||
id: censor,
|
||||
password: censor,
|
||||
profiles: censor
|
||||
};
|
||||
@ -18,7 +16,13 @@ export default function userServices() {
|
||||
debug('user is signed in');
|
||||
// Zalgo!!!
|
||||
return process.nextTick(() => {
|
||||
cb(null, assign({}, user.toJSON(), protectedUserFields));
|
||||
cb(
|
||||
null,
|
||||
{
|
||||
...user.toJSON(),
|
||||
...protectedUserFields
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
debug('user is not signed in');
|
||||
|
Loading…
x
Reference in New Issue
Block a user