Add block scoping to challenges url
This commit is contained in:
@@ -28,6 +28,8 @@ export const setUser = createAction(types.setUser);
|
||||
|
||||
// updatePoints(points: Number) => Action
|
||||
export const updatePoints = createAction(types.updatePoints);
|
||||
// used when server needs client to redirect
|
||||
export const delayedRedirect = createAction(types.delayedRedirect);
|
||||
|
||||
// hardGoTo(path: String) => Action
|
||||
export const hardGoTo = createAction(types.hardGoTo);
|
||||
|
@@ -55,6 +55,10 @@ export default handleActions(
|
||||
[types.toggleMainChat]: state => ({
|
||||
...state,
|
||||
isMainChatOpen: !state.isMainChatOpen
|
||||
}),
|
||||
[types.delayedRedirect]: (state, { payload }) => ({
|
||||
...state,
|
||||
delayedRedirect: payload
|
||||
})
|
||||
},
|
||||
initialState
|
||||
|
@@ -11,6 +11,7 @@ export default createTypes([
|
||||
'handleError',
|
||||
// used to hit the server
|
||||
'hardGoTo',
|
||||
'delayedRedirect',
|
||||
|
||||
'initWindowHeight',
|
||||
'updateWindowHeight',
|
||||
|
@@ -35,8 +35,8 @@ const mapStateToProps = createSelector(
|
||||
|
||||
const fetchOptions = {
|
||||
fetchAction: 'fetchChallenge',
|
||||
getActionArgs({ params: { dashedName } }) {
|
||||
return [ dashedName ];
|
||||
getActionArgs({ params: { block, dashedName } }) {
|
||||
return [ dashedName, block ];
|
||||
},
|
||||
isPrimed({ challenge }) {
|
||||
return !!challenge;
|
||||
|
@@ -13,12 +13,13 @@ export class Block extends PureComponent {
|
||||
static displayName = 'Block';
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
dashedName: PropTypes.string,
|
||||
time: PropTypes.string,
|
||||
challenges: PropTypes.array,
|
||||
updateCurrentChallenge: PropTypes.func
|
||||
};
|
||||
|
||||
renderChallenges(challenges, updateCurrentChallenge) {
|
||||
renderChallenges(blockName, challenges, updateCurrentChallenge) {
|
||||
if (!Array.isArray(challenges) || !challenges.length) {
|
||||
return <div>No Challenges Found</div>;
|
||||
}
|
||||
@@ -37,7 +38,8 @@ export class Block extends PureComponent {
|
||||
return (
|
||||
<p
|
||||
className={ challengeClassName }
|
||||
key={ title }>
|
||||
key={ title }
|
||||
>
|
||||
{ title }
|
||||
{
|
||||
isRequired ?
|
||||
@@ -50,10 +52,12 @@ export class Block extends PureComponent {
|
||||
return (
|
||||
<p
|
||||
className={ challengeClassName }
|
||||
key={ title }>
|
||||
<Link to={ `/challenges/${dashedName}` }>
|
||||
key={ title }
|
||||
>
|
||||
<Link to={ `/challenges/${blockName}/${dashedName}` }>
|
||||
<span
|
||||
onClick={ () => updateCurrentChallenge(challenge) }>
|
||||
onClick={ () => updateCurrentChallenge(challenge) }
|
||||
>
|
||||
{ title }
|
||||
<span className='sr-only'>complete</span>
|
||||
{
|
||||
@@ -69,7 +73,13 @@ export class Block extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, time, challenges, updateCurrentChallenge } = this.props;
|
||||
const {
|
||||
title,
|
||||
time,
|
||||
challenges,
|
||||
updateCurrentChallenge,
|
||||
dashedName
|
||||
} = this.props;
|
||||
return (
|
||||
<Panel
|
||||
bsClass='map-accordion-panel-nested'
|
||||
@@ -82,8 +92,11 @@ export class Block extends PureComponent {
|
||||
</div>
|
||||
}
|
||||
id={ title }
|
||||
key={ title }>
|
||||
{ this.renderChallenges(challenges, updateCurrentChallenge) }
|
||||
key={ title }
|
||||
>
|
||||
{
|
||||
this.renderChallenges(dashedName, challenges, updateCurrentChallenge)
|
||||
}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
@@ -21,7 +21,8 @@ export default class SuperBlock extends PureComponent {
|
||||
return (
|
||||
<Block
|
||||
key={ block.title }
|
||||
{ ...block } />
|
||||
{ ...block }
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -35,7 +36,8 @@ export default class SuperBlock extends PureComponent {
|
||||
expanded={ true }
|
||||
header={ <h2><FA name='caret-right' />{ title }</h2> }
|
||||
id={ title }
|
||||
key={ title }>
|
||||
key={ title }
|
||||
>
|
||||
{
|
||||
message ?
|
||||
<div className='challenge-block-description'>
|
||||
@@ -44,7 +46,8 @@ export default class SuperBlock extends PureComponent {
|
||||
''
|
||||
}
|
||||
<div
|
||||
className='map-accordion-block'>
|
||||
className='map-accordion-block'
|
||||
>
|
||||
{ this.renderBlocks(blocks) }
|
||||
</div>
|
||||
</Panel>
|
||||
|
@@ -12,6 +12,11 @@ export const challenges = {
|
||||
}
|
||||
};
|
||||
|
||||
export const modernChallenges = {
|
||||
path: 'challenges/:block/:dashedName',
|
||||
component: Show
|
||||
};
|
||||
|
||||
export const map = {
|
||||
path: 'map',
|
||||
component: ShowMap
|
||||
|
@@ -9,7 +9,10 @@ export const goToStep = createAction(types.goToStep);
|
||||
export const completeAction = createAction(types.completeAction);
|
||||
|
||||
// challenges
|
||||
export const fetchChallenge = createAction(types.fetchChallenge);
|
||||
export const fetchChallenge = createAction(
|
||||
types.fetchChallenge,
|
||||
(dashedName, block) => ({ dashedName, block })
|
||||
);
|
||||
export const fetchChallengeCompleted = createAction(
|
||||
types.fetchChallengeCompleted,
|
||||
(_, challenge) => challenge,
|
||||
|
@@ -1,20 +1,13 @@
|
||||
import { Observable } from 'rx';
|
||||
import { push } from 'react-router-redux';
|
||||
import types from './types';
|
||||
import {
|
||||
showChallengeComplete,
|
||||
moveToNextChallenge,
|
||||
updateCurrentChallenge
|
||||
} from './actions';
|
||||
import { showChallengeComplete, moveToNextChallenge } from './actions';
|
||||
import {
|
||||
createErrorObservable,
|
||||
makeToast,
|
||||
updatePoints
|
||||
} from '../../../redux/actions';
|
||||
|
||||
import { getNextChallenge } from '../utils';
|
||||
import { challengeSelector } from './selectors';
|
||||
|
||||
import { backEndProject } from '../../../utils/challengeTypes';
|
||||
import { randomCompliment } from '../../../utils/get-words';
|
||||
import { postJSON$ } from '../../../../utils/ajax-stream';
|
||||
@@ -179,25 +172,13 @@ export default function completionSaga(actions$, getState) {
|
||||
return actions$
|
||||
.filter(({ type }) => (
|
||||
type === types.checkChallenge ||
|
||||
type === types.submitChallenge ||
|
||||
type === types.moveToNextChallenge
|
||||
type === types.submitChallenge
|
||||
))
|
||||
.flatMap(({ type, payload }) => {
|
||||
const state = getState();
|
||||
const { submitType } = challengeSelector(state);
|
||||
const submitter = submitTypes[submitType] ||
|
||||
(() => Observable.just(null));
|
||||
if (type === types.moveToNextChallenge) {
|
||||
const nextChallenge = getNextChallenge(
|
||||
state.challengesApp.challenge,
|
||||
state.entities,
|
||||
state.challengesApp.superBlocks
|
||||
);
|
||||
return Observable.of(
|
||||
updateCurrentChallenge(nextChallenge),
|
||||
push(`/challenges/${nextChallenge.dashedName}`)
|
||||
);
|
||||
}
|
||||
return submitter(type, state, payload);
|
||||
});
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { Observable } from 'rx';
|
||||
import { fetchChallenge, fetchChallenges } from './types';
|
||||
import {
|
||||
createErrorObserable,
|
||||
delayedRedirect,
|
||||
createErrorObserable
|
||||
} from '../../../redux/actions';
|
||||
import {
|
||||
fetchChallengeCompleted,
|
||||
fetchChallengesCompleted,
|
||||
updateCurrentChallenge
|
||||
@@ -13,17 +16,18 @@ export default function fetchChallengesSaga(action$, getState, { services }) {
|
||||
type === fetchChallenges ||
|
||||
type === fetchChallenge
|
||||
))
|
||||
.flatMap(({ type, payload })=> {
|
||||
.flatMap(({ type, payload: { dashedName, block } = {} }) => {
|
||||
const options = { service: 'map' };
|
||||
if (type === fetchChallenge) {
|
||||
options.params = { dashedName: payload };
|
||||
options.params = { dashedName, block };
|
||||
}
|
||||
return services.readService$(options)
|
||||
.flatMap(({ entities, result } = {}) => {
|
||||
.flatMap(({ entities, result, redirect } = {}) => {
|
||||
if (type === fetchChallenge) {
|
||||
return Observable.of(
|
||||
fetchChallengeCompleted(entities, result),
|
||||
updateCurrentChallenge(entities.challenge[result])
|
||||
updateCurrentChallenge(entities.challenge[result.challenge]),
|
||||
redirect ? delayedRedirect(redirect) : null
|
||||
);
|
||||
}
|
||||
return Observable.just(fetchChallengesCompleted(entities, result));
|
||||
|
23
common/app/routes/challenges/redux/next-challenge-saga.js
Normal file
23
common/app/routes/challenges/redux/next-challenge-saga.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Observable } from 'rx';
|
||||
import { push } from 'react-router-redux';
|
||||
import { moveToNextChallenge } from './types';
|
||||
import { getNextChallenge } from '../utils';
|
||||
import { updateCurrentChallenge } from './actions';
|
||||
// import { createErrorObservable, makeToast } from '../../../redux/actions';
|
||||
|
||||
export default function nextChallengeSaga(actions$, getState) {
|
||||
return actions$
|
||||
.filter(({ type }) => type === moveToNextChallenge)
|
||||
.flatMap(() => {
|
||||
const state = getState();
|
||||
const nextChallenge = getNextChallenge(
|
||||
state.challengesApp.challenge,
|
||||
state.entities,
|
||||
state.challengesApp.superBlocks
|
||||
);
|
||||
return Observable.of(
|
||||
updateCurrentChallenge(nextChallenge),
|
||||
push(`/challenges/${nextChallenge.dashedName}`)
|
||||
);
|
||||
});
|
||||
}
|
@@ -61,7 +61,7 @@ export function getFileKey({ challengeType }) {
|
||||
export function createTests({ tests = [] }) {
|
||||
return tests
|
||||
.map(test => ({
|
||||
text: test.split('message: ').pop().replace(/\'\);/g, ''),
|
||||
text: ('' + test).split('message: ').pop().replace(/\'\);/g, ''),
|
||||
testString: test
|
||||
}));
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import Jobs from './Jobs';
|
||||
import Hikes from './Hikes';
|
||||
import { map, challenges } from './challenges';
|
||||
import { modernChallenges, map, challenges } from './challenges';
|
||||
import NotFound from '../components/NotFound/index.jsx';
|
||||
|
||||
export default {
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
Jobs,
|
||||
Hikes,
|
||||
challenges,
|
||||
modernChallenges,
|
||||
map,
|
||||
{
|
||||
path: '*',
|
||||
|
Reference in New Issue
Block a user