Skip to content

Commit 392c6ba

Browse files
ning-yremo5000
authored andcommitted
Implement API call to backend for fetchToken (#156)
* Add dotenv field for backend url * Implement api call to backend * Fix some security issues with header * Move redirect to login from academy to application * Move ivle redirect from /academy to /login * Fix flickering spinner
1 parent e85b7d7 commit 392c6ba

File tree

14 files changed

+148
-100
lines changed

14 files changed

+148
-100
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
REACT_APP_IVLE_KEY=your_ivle_key_here
22
REACT_APP_VERSION=$npm_package_version
3+
REACT_APP_BACKEND_URL=http://something.com

src/actions/actionTypes.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@ export const UPDATE_CURRENT_ASSESSMENT_ID = 'UPDATE_CURRENT_ASSESSMENT_ID'
3636

3737
/** Session */
3838
export const FETCH_ANNOUNCEMENTS = 'FETCH_ANNOUNCEMENTS'
39+
export const FETCH_AUTH = 'FETCH_AUTH'
3940
export const FETCH_ASSESSMENT = 'FETCH_ASSESSMENT'
4041
export const FETCH_ASSESSMENT_OVERVIEWS = 'FETCH_ASSESSMENT_OVERVIEWS'
4142
export const FETCH_GRADING = 'FETCH_GRADING'
4243
export const FETCH_GRADING_OVERVIEWS = 'FETCH_GRADING_OVERVIEWS'
43-
export const FETCH_TOKENS = 'FETCH_TOKENS'
44-
export const FETCH_USERNAME = 'FETCH_USERNAME'
4544
export const LOGIN = 'LOGIN'
4645
export const SET_TOKENS = 'SET_TOKENS'
4746
export const SET_USERNAME = 'SET_USERNAME'

src/actions/session.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { Grading, GradingOverview } from '../components/academy/grading/gradingS
44
import { IAssessment, IAssessmentOverview } from '../components/assessment/assessmentShape'
55
import * as actionTypes from './actionTypes'
66

7-
export const fetchTokens: ActionCreator<actionTypes.IAction> = (ivleToken: string) => ({
8-
type: actionTypes.FETCH_TOKENS,
7+
export const fetchAuth: ActionCreator<actionTypes.IAction> = (ivleToken: string) => ({
8+
type: actionTypes.FETCH_AUTH,
99
payload: ivleToken
1010
})
1111

@@ -31,10 +31,6 @@ export const fetchGradingOverviews = () => ({
3131
type: actionTypes.FETCH_GRADING_OVERVIEWS
3232
})
3333

34-
export const fetchUsername = () => ({
35-
type: actionTypes.FETCH_USERNAME
36-
})
37-
3834
export const login = () => ({
3935
type: actionTypes.LOGIN
4036
})

src/components/Application.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export interface IDispatchProps {
2424

2525
const Application: React.SFC<IApplicationProps> = props => {
2626
const redirectToNews = () => <Redirect to="/news" />
27-
const toAcademy = () => <Academy accessToken={props.accessToken} />
2827

2928
parsePlayground(props)
3029

@@ -33,21 +32,35 @@ const Application: React.SFC<IApplicationProps> = props => {
3332
<NavigationBar title={props.title} username={props.username} />
3433
<div className="Application__main">
3534
<Switch>
36-
<Route path="/academy" component={toAcademy} />
35+
<Route path="/academy" component={toAcademy(props)} />
3736
<Route path="/news" component={Announcements} />
3837
<Route path="/material" component={Announcements} />
3938
<Route path="/playground" component={Playground} />
4039
<Route path="/status" component={Announcements} />
41-
<Route path="/login" component={Login} />
42-
<Route exact={true} path="/" component={redirectToNews} />
40+
<Route path="/login" render={toLogin(props)} />
41+
<Route exact={true} path="/" render={redirectToNews} />
4342
<Route component={NotFound} />
4443
</Switch>
4544
</div>
4645
</div>
4746
)
4847
}
4948

50-
export const parsePlayground = (props: IApplicationProps) => {
49+
/**
50+
* A user routes to /academy,
51+
* 1. If the user is logged in, render the Academy component
52+
* 2. If the user is not logged in, redirect to /login
53+
*/
54+
const toAcademy = (props: IApplicationProps) =>
55+
props.accessToken === undefined
56+
? () => <Redirect to="/login" />
57+
: () => <Academy accessToken={props.accessToken} />
58+
59+
const toLogin = (props: IApplicationProps) => () => (
60+
<Login ivleToken={qs.parse(props.location.search).token} />
61+
)
62+
63+
const parsePlayground = (props: IApplicationProps) => {
5164
const prgrm = parsePrgrm(props)
5265
const lib = parseLib(props)
5366
if (prgrm) {

src/components/Login.tsx

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,55 @@
1-
import { Button, ButtonGroup, Card, Icon } from '@blueprintjs/core'
1+
import { Button, ButtonGroup, Card, Icon, NonIdealState, Spinner } from '@blueprintjs/core'
22
import { IconNames } from '@blueprintjs/icons'
33
import * as React from 'react'
44
import { NavLink } from 'react-router-dom'
55

6-
type LoginProps = DispatchProps
6+
type LoginProps = DispatchProps & OwnProps
77

88
export type DispatchProps = {
9+
handleFetchAuth: (ivleToken: string) => void
910
handleLogin: () => void
1011
}
1112

13+
export type OwnProps = {
14+
ivleToken?: string
15+
}
16+
17+
const Login: React.SFC<LoginProps> = props => {
18+
if (props.ivleToken) {
19+
startFetchAuth(props.ivleToken, props.handleFetchAuth)
20+
return (
21+
<div className="Login pt-dark">
22+
<Card className="login-card pt-elevation-4">
23+
<div className="login-body">
24+
<NonIdealState description="Logging In..." visual={<Spinner large={true} />} />
25+
</div>
26+
</Card>
27+
</div>
28+
)
29+
} else {
30+
return (
31+
<div className="Login pt-dark">
32+
<Card className="login-card pt-elevation-4">
33+
<div className="login-header">
34+
<h4>
35+
<Icon icon={IconNames.LOCK} />LOGIN
36+
</h4>
37+
</div>
38+
<div className="login-body">
39+
<ButtonGroup fill={true} vertical={true}>
40+
{loginButton(props.handleLogin)}
41+
{playgroundButton}
42+
</ButtonGroup>
43+
</div>
44+
</Card>
45+
</div>
46+
)
47+
}
48+
}
49+
50+
const startFetchAuth = (ivleToken: string, handleFetchAuth: DispatchProps['handleFetchAuth']) =>
51+
handleFetchAuth(ivleToken)
52+
1253
const loginButton = (handleClick: () => void) => (
1354
<Button className="pt-large" rightIcon="log-in" onClick={handleClick}>
1455
Log in with IVLE
@@ -23,22 +64,4 @@ const playgroundButton = (
2364
</NavLink>
2465
)
2566

26-
const Login: React.SFC<LoginProps> = props => (
27-
<div className="Login pt-dark">
28-
<Card className="login-card pt-elevation-4">
29-
<div className="login-header">
30-
<h4>
31-
<Icon icon={IconNames.LOCK} />LOGIN
32-
</h4>
33-
</div>
34-
<div className="login-body">
35-
<ButtonGroup fill={true} vertical={true}>
36-
{loginButton(() => props.handleLogin())}
37-
{playgroundButton}
38-
</ButtonGroup>
39-
</div>
40-
</Card>
41-
</div>
42-
)
43-
4467
export default Login

src/components/__tests__/Login.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@ import Login from '../Login'
55

66
test('Login renders correctly', () => {
77
const props = {
8-
handleLogin: () => {}
8+
handleLogin: () => {},
9+
handleFetchAuth: (ivleToken: string) => {}
10+
}
11+
const app = <Login {...props} />
12+
const tree = shallow(app)
13+
expect(tree.debug()).toMatchSnapshot()
14+
})
15+
16+
test('Loading login renders correctly', () => {
17+
const props = {
18+
handleLogin: () => {},
19+
handleFetchAuth: (ivleToken: string) => {},
20+
ivleToken: '1VL3 T0K3N'
921
}
1022
const app = <Login {...props} />
1123
const tree = shallow(app)

src/components/__tests__/__snapshots__/Application.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ exports[`Application renders correctly 1`] = `
55
<NavigationBar title=\\"Cadet\\" username={[undefined]} />
66
<div className=\\"Application__main\\">
77
<Switch>
8-
<Route path=\\"/academy\\" component={[Function: toAcademy]} />
8+
<Route path=\\"/academy\\" component={[Function]} />
99
<Route path=\\"/news\\" component={[Function: Connect]} />
1010
<Route path=\\"/material\\" component={[Function: Connect]} />
1111
<Route path=\\"/playground\\" component={[Function: C]} />
1212
<Route path=\\"/status\\" component={[Function: Connect]} />
13-
<Route path=\\"/login\\" component={[Function: Connect]} />
14-
<Route exact={true} path=\\"/\\" component={[Function: redirectToNews]} />
13+
<Route path=\\"/login\\" render={[Function]} />
14+
<Route exact={true} path=\\"/\\" render={[Function: redirectToNews]} />
1515
<Route component={[Function: NotFound]} />
1616
</Switch>
1717
</div>

src/components/__tests__/__snapshots__/Login.tsx.snap

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`Loading login renders correctly 1`] = `
4+
"<div className=\\"Login pt-dark\\">
5+
<Blueprint2.Card className=\\"login-card pt-elevation-4\\" elevation={0} interactive={false}>
6+
<div className=\\"login-body\\">
7+
<NonIdealState description=\\"Logging In...\\" visual={{...}} />
8+
</div>
9+
</Blueprint2.Card>
10+
</div>"
11+
`;
12+
313
exports[`Login renders correctly 1`] = `
414
"<div className=\\"Login pt-dark\\">
515
<Blueprint2.Card className=\\"login-card pt-elevation-4\\" elevation={0} interactive={false}>
@@ -11,7 +21,7 @@ exports[`Login renders correctly 1`] = `
1121
</div>
1222
<div className=\\"login-body\\">
1323
<Blueprint2.ButtonGroup fill={true} vertical={true}>
14-
<Blueprint2.Button className=\\"pt-large\\" rightIcon=\\"log-in\\" onClick={[Function]}>
24+
<Blueprint2.Button className=\\"pt-large\\" rightIcon=\\"log-in\\" onClick={[Function: handleLogin]}>
1525
Log in with IVLE
1626
</Blueprint2.Button>
1727
<NavLink to=\\"/playground\\" activeClassName=\\"active\\" ariaCurrent=\\"true\\">

src/components/academy/index.tsx

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* tslint:disable: jsx-no-lambda */
2-
import * as qs from 'query-string'
31
import * as React from 'react'
42
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router'
53

@@ -12,12 +10,7 @@ import { assessmentCategoryLink } from '../../utils/paramParseHelpers'
1210
import { AssessmentCategories, AssessmentCategory } from '../assessment/assessmentShape'
1311
import AcademyNavigationBar from './NavigationBar'
1412

15-
interface IAcademyProps extends IDispatchProps, IOwnProps, IStateProps, RouteComponentProps<{}> {}
16-
17-
export interface IDispatchProps {
18-
handleFetchTokens: (ivleToken: string) => void
19-
handleFetchUsername: () => void
20-
}
13+
interface IAcademyProps extends IOwnProps, IStateProps, RouteComponentProps<{}> {}
2114

2215
export interface IOwnProps {
2316
accessToken?: string
@@ -38,7 +31,6 @@ export const Academy: React.SFC<IAcademyProps> = props => (
3831
<div className="Academy">
3932
<AcademyNavigationBar />
4033
<Switch>
41-
{checkLoggedIn(props)}
4234
<Route
4335
path={`/academy/${assessmentCategoryLink(
4436
AssessmentCategories.Contest
@@ -69,19 +61,6 @@ export const Academy: React.SFC<IAcademyProps> = props => (
6961
</div>
7062
)
7163

72-
const checkLoggedIn = (props: IAcademyProps) => {
73-
const ivleToken = qs.parse(props.location.search).token
74-
if (ivleToken !== undefined) {
75-
props.handleFetchTokens(ivleToken) // just received a callback from IVLE
76-
props.handleFetchUsername()
77-
return
78-
} else if (props.accessToken === undefined) {
79-
return <Route component={redirectToLogin} />
80-
} else {
81-
return
82-
}
83-
}
84-
8564
/**
8665
* 1. If user is in /academy.*, redirect to game
8766
* 2. If not, redirect to the last /acdaemy.* route the user was in
@@ -99,8 +78,6 @@ const dynamicRedirect = (props: IStateProps) => {
9978

10079
const redirectTo404 = () => <Redirect to="/404" />
10180

102-
const redirectToLogin = () => <Redirect to="/login" />
103-
10481
const redirectToGame = () => <Redirect to="/academy/game" />
10582

10683
export default Academy

src/containers/LoginContainer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { connect, MapDispatchToProps } from 'react-redux'
1+
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'
22
import { bindActionCreators, Dispatch } from 'redux'
33

4-
import { login } from '../actions/session'
5-
import Login, { DispatchProps } from '../components/Login'
4+
import { fetchAuth, login } from '../actions/session'
5+
import Login, { DispatchProps, OwnProps } from '../components/Login'
6+
7+
const mapStateToProps: MapStateToProps<{}, OwnProps, {}> = (_, ownProps) => ownProps
68

79
const mapDispatchToProps: MapDispatchToProps<DispatchProps, {}> = (dispatch: Dispatch<any>) =>
810
bindActionCreators(
911
{
12+
handleFetchAuth: fetchAuth,
1013
handleLogin: login
1114
},
1215
dispatch
1316
)
1417

15-
export default connect(undefined, mapDispatchToProps)(Login)
18+
export default connect(mapStateToProps, mapDispatchToProps)(Login)

0 commit comments

Comments
 (0)