Skip to content

Commit 6ba0885

Browse files
authored
Add navigation between Questions (#124)
* Add routing for questions * Add similar routes to other assessment types * Add utility function for parsing parameters Easy to handle number | null once, DRY. * Rename helper function * Format some files * Add conditionals for Next/Previous buttons * Add redirects for next and previous buttons * Make briefing popup only for question 0 * Format code for links * Add submit button for last question The submit button can potentially be added for any question, but this is controlled before creating the workspace. * Update tests and formatting * Add regexp for assessmentId and questionId * Add helper function for parsing AssessmentCategory * Use assessmentCategoryLink in other files * Use helper function & constants for academy NavBar * Fix minor issue with regexp * Format files * Use jsdocs formatting * Format with prettier
1 parent 1504940 commit 6ba0885

File tree

7 files changed

+120
-53
lines changed

7 files changed

+120
-53
lines changed

src/components/academy/NavigationBar.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import { IconNames } from '@blueprintjs/icons'
33
import * as React from 'react'
44
import { NavLink } from 'react-router-dom'
55

6+
import { assessmentCategoryLink } from '../../utils/paramParseHelpers'
7+
import { AssessmentCategories } from '../assessment/assessmentShape'
8+
69
const NavigationBar: React.SFC<{}> = () => (
710
<Navbar className="NavigationBar secondary-navbar">
811
<NavbarGroup align={Alignment.LEFT}>
912
<NavLink
10-
to="/academy/missions"
13+
to={`/academy/${assessmentCategoryLink(AssessmentCategories.MISSION)}`}
1114
activeClassName="pt-active"
1215
className="NavigationBar__link pt-button pt-minimal"
1316
>
@@ -16,7 +19,7 @@ const NavigationBar: React.SFC<{}> = () => (
1619
</NavLink>
1720

1821
<NavLink
19-
to="/academy/sidequests"
22+
to={`/academy/${assessmentCategoryLink(AssessmentCategories.SIDEQUEST)}`}
2023
activeClassName="pt-active"
2124
className="NavigationBar__link pt-button pt-minimal"
2225
>
@@ -25,7 +28,7 @@ const NavigationBar: React.SFC<{}> = () => (
2528
</NavLink>
2629

2730
<NavLink
28-
to="/academy/paths"
31+
to={`/academy/${assessmentCategoryLink(AssessmentCategories.PATH)}`}
2932
activeClassName="pt-active"
3033
className="NavigationBar__link pt-button pt-minimal"
3134
>
@@ -34,7 +37,7 @@ const NavigationBar: React.SFC<{}> = () => (
3437
</NavLink>
3538

3639
<NavLink
37-
to="/academy/contests"
40+
to={`/academy/${assessmentCategoryLink(AssessmentCategories.CONTEST)}`}
3841
activeClassName="pt-active"
3942
className="NavigationBar__link pt-button pt-minimal"
4043
>

src/components/academy/index.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import AssessmentListingContainer from '../../containers/assessment/AssessmentLi
88
import Game from '../../containers/GameContainer'
99
import { isAcademyRe } from '../../reducers/session'
1010
import { HistoryHelper } from '../../utils/history'
11+
import { assessmentCategoryLink } from '../../utils/paramParseHelpers'
1112
import { AssessmentCategories, AssessmentCategory } from '../assessment/assessmentShape'
1213
import AcademyNavigationBar from './NavigationBar'
1314

@@ -30,31 +31,34 @@ const assessmentListingRenderFactory = (cat: AssessmentCategory) => (
3031
routerProps: RouteComponentProps<any>
3132
) => <AssessmentListingContainer assessmentCategory={cat} />
3233

34+
const assessmentRegExp = ':assessmentId(\\d+)?/:questionId(\\d+)?'
35+
3336
export const Academy: React.SFC<IAcademyProps> = props => (
3437
<div className="Academy">
3538
<AcademyNavigationBar />
3639
<Switch>
3740
{checkLoggedIn(props)}
3841
<Route
39-
path="/academy/contests"
42+
path={`/academy/${assessmentCategoryLink(
43+
AssessmentCategories.CONTEST
44+
)}/${assessmentRegExp}`}
4045
render={assessmentListingRenderFactory(AssessmentCategories.CONTEST)}
4146
/>
4247
<Route path="/academy/game" component={Game} />
4348
<Route
44-
exact={true}
45-
path="/academy/missions"
46-
render={assessmentListingRenderFactory(AssessmentCategories.MISSION)}
47-
/>
48-
<Route
49-
path="/academy/missions/:assessmentId"
49+
path={`/academy/${assessmentCategoryLink(
50+
AssessmentCategories.MISSION
51+
)}/${assessmentRegExp}`}
5052
render={assessmentListingRenderFactory(AssessmentCategories.MISSION)}
5153
/>
5254
<Route
53-
path="/academy/paths"
55+
path={`/academy/${assessmentCategoryLink(AssessmentCategories.PATH)}/${assessmentRegExp}`}
5456
render={assessmentListingRenderFactory(AssessmentCategories.PATH)}
5557
/>
5658
<Route
57-
path="/academy/sidequests"
59+
path={`/academy/${assessmentCategoryLink(
60+
AssessmentCategories.SIDEQUEST
61+
)}/${assessmentRegExp}`}
5862
render={assessmentListingRenderFactory(AssessmentCategories.SIDEQUEST)}
5963
/>
6064
<Route exact={true} path="/academy" component={dynamicRedirect(props)} />

src/components/assessment/AssessmentListing.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { RouteComponentProps } from 'react-router'
55
import { NavLink } from 'react-router-dom'
66

77
import AssessmentContainer from '../../containers/assessment'
8+
import { assessmentCategoryLink, stringParamToInt } from '../../utils/paramParseHelpers'
89
import { OwnProps as AssessmentProps } from '../assessment'
910
import { AssessmentCategory } from '../assessment/assessmentShape'
1011
import { IAssessmentOverview } from '../assessment/assessmentShape'
1112
import ContentDisplay, { IContentDisplayProps } from '../commons/ContentDisplay'
1213

1314
export interface IAssessmentParams {
1415
assessmentId?: string
16+
questionId?: string
1517
}
1618

1719
export interface IAssessmentListingProps extends RouteComponentProps<IAssessmentParams> {
@@ -26,18 +28,19 @@ export type StateProps = Pick<IAssessmentListingProps, 'assessmentOverviews'>
2628

2729
class AssessmentListing extends React.Component<IAssessmentListingProps, {}> {
2830
public render() {
29-
// make assessmentId a number
30-
let assessmentIdParam: number | null =
31-
this.props.match.params.assessmentId === undefined
32-
? NaN
33-
: parseInt(this.props.match.params.assessmentId, 10)
34-
// set as null if the parsing failed
35-
assessmentIdParam = Number.isInteger(assessmentIdParam) ? assessmentIdParam : null
31+
const assessmentIdParam: number | null = stringParamToInt(this.props.match.params.assessmentId)
32+
// default questionId is 0 (the first question)
33+
const questionIdParam: number = stringParamToInt(this.props.match.params.questionId) || 0
3634

3735
// if there is no assessmentId specified, Render only information.
3836
if (assessmentIdParam === null) {
3937
const props: IContentDisplayProps = {
40-
display: <AssessmentOverviewCard assessmentOverviews={this.props.assessmentOverviews} />,
38+
display: (
39+
<AssessmentOverviewCard
40+
assessmentOverviews={this.props.assessmentOverviews}
41+
questionId={questionIdParam}
42+
/>
43+
),
4144
loadContentDispatch: this.props.handleAssessmentOverviewFetch
4245
}
4346
return (
@@ -47,7 +50,8 @@ class AssessmentListing extends React.Component<IAssessmentListingProps, {}> {
4750
)
4851
} else {
4952
const props: AssessmentProps = {
50-
assessmentId: assessmentIdParam
53+
assessmentId: assessmentIdParam,
54+
questionId: questionIdParam
5155
}
5256
return <AssessmentContainer {...props} />
5357
}
@@ -56,9 +60,11 @@ class AssessmentListing extends React.Component<IAssessmentListingProps, {}> {
5660

5761
interface IAssessmentOverviewCardProps {
5862
assessmentOverviews?: IAssessmentOverview[]
63+
questionId: number
5964
}
6065

6166
export const AssessmentOverviewCard: React.SFC<IAssessmentOverviewCardProps> = props => {
67+
const questionId = props.questionId === undefined ? 0 : props.questionId
6268
if (props.assessmentOverviews === undefined) {
6369
return <NonIdealState description="Fetching assessment..." visual={<Spinner />} />
6470
} else if (props.assessmentOverviews.length === 0) {
@@ -86,7 +92,11 @@ export const AssessmentOverviewCard: React.SFC<IAssessmentOverviewCardProps> = p
8692
</Text>
8793
</div>
8894
<div className="col-xs">
89-
<NavLink to={`/academy/missions/${overview.id.toString()}`}>
95+
<NavLink
96+
to={`/academy/${assessmentCategoryLink(
97+
overview.category
98+
)}/${overview.id.toString()}/${questionId.toString()}`}
99+
>
90100
<Button
91101
className="listing-skip-button"
92102
minimal={true}

src/components/assessment/__tests__/__snapshots__/AssessmentListing.tsx.snap

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ exports[`AssessmentListing page "loading" content renders correctly 1`] = `
1010
<div className=\\"col-xs-10 contentdisplay-content-parent\\">
1111
<Blueprint2.Card className=\\"contentdisplay-content\\" elevation={1} interactive={false}>
1212
<div className=\\"pt-card pt-elevation-1 contentdisplay-content\\">
13-
<Component assessmentOverviews={[undefined]}>
13+
<Component assessmentOverviews={[undefined]} questionId={0}>
1414
<NonIdealState description=\\"Fetching assessment...\\" visual={{...}}>
1515
<div className=\\"pt-non-ideal-state\\">
1616
<div className=\\"pt-non-ideal-state-visual\\">
@@ -52,7 +52,7 @@ exports[`AssessmentListing page with 0 missions renders correctly 1`] = `
5252
<div className=\\"col-xs-10 contentdisplay-content-parent\\">
5353
<Blueprint2.Card className=\\"contentdisplay-content\\" elevation={1} interactive={false}>
5454
<div className=\\"pt-card pt-elevation-1 contentdisplay-content\\">
55-
<Component assessmentOverviews={{...}}>
55+
<Component assessmentOverviews={{...}} questionId={0}>
5656
<NonIdealState title=\\"There are no assessments.\\" visual=\\"flame\\">
5757
<div className=\\"pt-non-ideal-state\\">
5858
<div className=\\"pt-non-ideal-state-visual pt-non-ideal-state-icon\\">
@@ -92,7 +92,7 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
9292
<div className=\\"col-xs-10 contentdisplay-content-parent\\">
9393
<Blueprint2.Card className=\\"contentdisplay-content\\" elevation={1} interactive={false}>
9494
<div className=\\"pt-card pt-elevation-1 contentdisplay-content\\">
95-
<Component assessmentOverviews={{...}}>
95+
<Component assessmentOverviews={{...}} questionId={0}>
9696
<div>
9797
<Blueprint2.Card className=\\"row listing\\" elevation={0} interactive={false}>
9898
<div className=\\"pt-card pt-elevation-0 row listing\\">
@@ -132,10 +132,10 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
132132
</Text>
133133
</div>
134134
<div className=\\"col-xs\\">
135-
<NavLink to=\\"/academy/missions/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
136-
<Route path=\\"/academy/missions/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
137-
<Link to=\\"/academy/missions/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
138-
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/0\\">
135+
<NavLink to=\\"/academy/missions/0/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
136+
<Route path=\\"/academy/missions/0/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
137+
<Link to=\\"/academy/missions/0/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
138+
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/0/0\\">
139139
<Blueprint2.Button className=\\"listing-skip-button\\" minimal={true} intent=\\"primary\\" icon=\\"flame\\">
140140
<button type=\\"button\\" className=\\"pt-button pt-minimal pt-intent-primary listing-skip-button\\" disabled={[undefined]} onClick={[undefined]} onKeyDown={[Function]} onKeyUp={[Function]}>
141141
<Blueprint2.Icon icon=\\"flame\\">
@@ -201,10 +201,10 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
201201
</Text>
202202
</div>
203203
<div className=\\"col-xs\\">
204-
<NavLink to=\\"/academy/missions/1\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
205-
<Route path=\\"/academy/missions/1\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
206-
<Link to=\\"/academy/missions/1\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
207-
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/1\\">
204+
<NavLink to=\\"/academy/missions/1/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
205+
<Route path=\\"/academy/missions/1/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
206+
<Link to=\\"/academy/missions/1/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
207+
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/1/0\\">
208208
<Blueprint2.Button className=\\"listing-skip-button\\" minimal={true} intent=\\"primary\\" icon=\\"flame\\">
209209
<button type=\\"button\\" className=\\"pt-button pt-minimal pt-intent-primary listing-skip-button\\" disabled={[undefined]} onClick={[undefined]} onKeyDown={[Function]} onKeyUp={[Function]}>
210210
<Blueprint2.Icon icon=\\"flame\\">
@@ -270,10 +270,10 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
270270
</Text>
271271
</div>
272272
<div className=\\"col-xs\\">
273-
<NavLink to=\\"/academy/missions/2\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
274-
<Route path=\\"/academy/missions/2\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
275-
<Link to=\\"/academy/missions/2\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
276-
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/2\\">
273+
<NavLink to=\\"/academy/sidequests/2/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
274+
<Route path=\\"/academy/sidequests/2/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
275+
<Link to=\\"/academy/sidequests/2/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
276+
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/sidequests/2/0\\">
277277
<Blueprint2.Button className=\\"listing-skip-button\\" minimal={true} intent=\\"primary\\" icon=\\"flame\\">
278278
<button type=\\"button\\" className=\\"pt-button pt-minimal pt-intent-primary listing-skip-button\\" disabled={[undefined]} onClick={[undefined]} onKeyDown={[Function]} onKeyUp={[Function]}>
279279
<Blueprint2.Icon icon=\\"flame\\">
@@ -339,10 +339,10 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
339339
</Text>
340340
</div>
341341
<div className=\\"col-xs\\">
342-
<NavLink to=\\"/academy/missions/3\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
343-
<Route path=\\"/academy/missions/3\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
344-
<Link to=\\"/academy/missions/3\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
345-
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/3\\">
342+
<NavLink to=\\"/academy/missions/3/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
343+
<Route path=\\"/academy/missions/3/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
344+
<Link to=\\"/academy/missions/3/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
345+
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/3/0\\">
346346
<Blueprint2.Button className=\\"listing-skip-button\\" minimal={true} intent=\\"primary\\" icon=\\"flame\\">
347347
<button type=\\"button\\" className=\\"pt-button pt-minimal pt-intent-primary listing-skip-button\\" disabled={[undefined]} onClick={[undefined]} onKeyDown={[Function]} onKeyUp={[Function]}>
348348
<Blueprint2.Icon icon=\\"flame\\">
@@ -408,10 +408,10 @@ exports[`AssessmentListing page with multiple loaded missions renders correctly
408408
</Text>
409409
</div>
410410
<div className=\\"col-xs\\">
411-
<NavLink to=\\"/academy/missions/4\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
412-
<Route path=\\"/academy/missions/4\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
413-
<Link to=\\"/academy/missions/4\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
414-
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/missions/4\\">
411+
<NavLink to=\\"/academy/sidequests/4/0\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">
412+
<Route path=\\"/academy/sidequests/4/0\\" exact={[undefined]} strict={[undefined]} location={[undefined]}>
413+
<Link to=\\"/academy/sidequests/4/0\\" className={[undefined]} style={[undefined]} aria-current={false} replace={false}>
414+
<a className={[undefined]} style={[undefined]} aria-current={false} onClick={[Function]} href=\\"/academy/sidequests/4/0\\">
415415
<Blueprint2.Button className=\\"listing-skip-button\\" minimal={true} intent=\\"primary\\" icon=\\"flame\\">
416416
<button type=\\"button\\" className=\\"pt-button pt-minimal pt-intent-primary listing-skip-button\\" disabled={[undefined]} onClick={[undefined]} onKeyDown={[Function]} onKeyUp={[Function]}>
417417
<Blueprint2.Icon icon=\\"flame\\">

0 commit comments

Comments
 (0)