Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
},
"devDependencies": {
"@types/enzyme": "^3.1.15",
"@types/enzyme-adapter-react-16": "^1.0.3",
"@types/jest": "^23.3.13",
"@types/reach__router": "^1.2.3",
"@types/react": "^16.7.20",
"@types/react-dom": "^16.0.11",
"@types/react-test-renderer": "^16.0.3",
"awesome-typescript-loader": "^5.2.1",
"css-loader": "^1.0.1",
"dotenv": "^6.2.0",
Expand Down
34 changes: 19 additions & 15 deletions ui/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Router } from '@reach/router'
import * as Msal from 'msal'
import { UserAgentApplication } from 'msal'
import * as React from 'react'

import './../styles/App.css'
import { AuthProvider } from './AuthContext'
import { AuthResponseBar } from './AuthResponseBar'
import { DefaultComponent } from './DefaultComponent'
Expand All @@ -10,16 +11,14 @@ import { Navbar } from './Navbar'
import { Person } from './Person'
import { Title } from './Title'

import './../styles/App.css'

// Webpack will automatically replace this variable during build time
const appConfig = {
clientID: '', // defaulted to '' when no OAuth client id is passed in
}

// initialize the UserAgentApplication globally so popup and iframe can run in the background
// short circuit userAgentApp. If clientID is null so is userAgentApp
const userAgentApp = appConfig.clientID && new Msal.UserAgentApplication(appConfig.clientID, null, null)
const userAgentApp = appConfig.clientID && new UserAgentApplication(appConfig.clientID, null, () => null)

// webpack replaces this variable on build time
let basepath = process.env.WEBPACK_PROP_UI_BASEPATH || '' // default to empty string for testing
Expand All @@ -45,18 +44,28 @@ export class App extends React.Component {
console.warn('AAD Client ID has not been configured. If you are currently in production mode, see the \'deploy\' documentation for details on how to fix this.')
this.setState({ accessToken: '' })
}
} else { // normal or 'production' auth flow
} else if (userAgentApp === '') { // normal or 'production' auth flow
alert('userAgentApp not initialized')
} else {
let accessToken = null
try {
if (this.state.accessToken !== null) {
// log out
await userAgentApp.logout()
if (userAgentApp) {
await userAgentApp.logout()
} else {
alert('userAgentApp not initialized')
}
} else {
// log in
const graphScopes = [appConfig.clientID]
await userAgentApp.loginPopup(graphScopes)
accessToken = await userAgentApp.acquireTokenSilent(graphScopes,
'https://login.microsoftonline.com/microsoft.onmicrosoft.com')
if (userAgentApp) {
await userAgentApp.loginPopup(graphScopes)
accessToken = await userAgentApp.acquireTokenSilent(graphScopes,
'https://login.microsoftonline.com/microsoft.onmicrosoft.com')
} else {
alert('userAgentApp not initialized')
}
}
this.setState({ accessToken })
} catch (err) {
Expand All @@ -75,12 +84,7 @@ export class App extends React.Component {
// by linking the accessToken to the app state we can be certain the
// context will always update and propogate the value to subscribed nodes
return (
<AuthProvider value={{
accessToken: this.state.accessToken,
authResponse: this.state.authResponse,
handleAuth: this.handleAuth,
setAuthResponse: this.setAuthResponse,
}}>
<AuthProvider value={{handleAuth: this.handleAuth}}>
<div className='app-container'>
<Navbar basepath={basepath} />
<AuthResponseBar />
Expand Down
15 changes: 5 additions & 10 deletions ui/src/components/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { createContext } from 'react'

export interface IAuthContextValues {
accessToken: string,
authResponse: string,
handleAuth: () => void,
setAuthResponse: (msg?: string) => void,
accessToken?: string,
authResponse?: string,
handleAuth?: () => any,
setAuthResponse?: (msg?: string) => any,
}

// Default values and enforce use of interface
const defaultAuthContextValue: IAuthContextValues = {
accessToken: null,
authResponse: null,
handleAuth: () => null,
setAuthResponse: () => null,
}
const defaultAuthContextValue: IAuthContextValues = {}

export const AuthContext = createContext<IAuthContextValues>(defaultAuthContextValue)
// exporting the Provider and Consumer components for more specific imports
Expand Down
5 changes: 2 additions & 3 deletions ui/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Link } from '@reach/router'
import * as React from 'react'

import { AuthButton } from './AuthButton'

import './../styles/Navbar.css'
import { AuthButton } from './AuthButton'

const isActive = ({ isCurrent }) => {
const isActive = ({ isCurrent }: {isCurrent: boolean}) => {
return {
className: isCurrent ? 'nav-link active' : 'nav-link',
}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/PageForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as React from 'react'
export interface IPageFormProps {
inputTitle: string,
inputPlaceholder: string,
onInputChange: (event) => void,
onSubmitClick: (event) => void,
onInputChange: (event: React.ChangeEvent<HTMLInputElement>) => any,
onSubmitClick: (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => any,
}

export function PageForm(props: IPageFormProps) {
Expand Down
15 changes: 6 additions & 9 deletions ui/src/components/Person.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PageForm } from './PageForm'

export interface IPersonProps { path: string }

export interface IPersonState { loading: boolean, personId: string, result: object }
export interface IPersonState { loading: boolean, personId?: string, result?: object }

export interface IPersonResponse extends Response {
_embedded?: { persons: Array<{ nconst: string }> },
Expand All @@ -18,10 +18,8 @@ export interface IPersonResult { nconst: string }
export class Person extends React.Component<IPersonProps, IPersonState> {
public static contextType = AuthContext

public state = {
public state: IPersonState = {
loading: false,
personId: null,
result: null,
}

public render() {
Expand All @@ -47,12 +45,11 @@ export class Person extends React.Component<IPersonProps, IPersonState> {
)
}

private handleNameInputChange = (event) => this.setState({
private handleNameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => this.setState({
personId: event.target.value,
result: null,
})

private handleFormSubmit = async (event) => {
private handleFormSubmit = async (event: React.MouseEvent<HTMLInputElement>) => {
event.preventDefault()

this.setState({ loading: true })
Expand All @@ -75,11 +72,11 @@ export class Person extends React.Component<IPersonProps, IPersonState> {
const response = await fetch(endpoint, options)
const resOut: IPersonResponse = await response.json()

if (resOut.hasOwnProperty('error')) {
if (resOut.error) {
throw new Error(`Error: ${resOut.error}, ${resOut.error_description}`)
} else if (id) {
this.setState({ result: resOut })
} else {
} else if (resOut._embedded) {
const persons = resOut._embedded.persons
const result: IPersonResult = persons[Math.floor(Math.random() * persons.length)]
this.setState({ result, personId: result.nconst })
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/PrivateRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class PrivateRoute extends React.Component<IPrivateRouteProps> {
}
}

public componentDidUpdate(prevProps, prevState, snapshot) {
public componentDidUpdate(prevProps: IPrivateRouteProps) {
if (this.context.accessToken === null && this.props.path !== prevProps.path) {
this.context.setAuthResponse(`Please log in to access: ${this.props.path}`)
} else if (this.context.authResponse !== null && this.context.accessToken !== null) {
Expand Down
22 changes: 12 additions & 10 deletions ui/src/components/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PageForm } from './PageForm'

export interface ITitleProps { path: string }

export interface ITitleState { loading: boolean, titleId: string, result: object }
export interface ITitleState { loading: boolean, titleId?: string, result?: object }

export interface ITitleResponse extends Response {
_embedded?: { titles: Array<{ tconst: string }> },
Expand All @@ -18,10 +18,8 @@ export interface ITitleResult { tconst: string }
export class Title extends React.Component<ITitleProps, ITitleState> {
public static contextType = AuthContext

public state = {
public state: ITitleState = {
loading: false,
result: null,
titleId: null,
}

public render() {
Expand All @@ -47,12 +45,13 @@ export class Title extends React.Component<ITitleProps, ITitleState> {
)
}

private handleNameInputChange = (event) => this.setState({
result: null,
titleId: event.target.value,
})
private handleNameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
titleId: event.target.value,
})
}

private handleFormSubmit = async (event) => {
private handleFormSubmit = async (event: React.MouseEvent<HTMLInputElement>) => {
event.preventDefault()

this.setState({ loading: true })
Expand All @@ -79,10 +78,13 @@ export class Title extends React.Component<ITitleProps, ITitleState> {
throw new Error(`Error: ${resOut.error}, ${resOut.error_description}`)
} else if (id) {
this.setState({ result: resOut })
} else {
} else if (resOut._embedded) {
const titles = resOut._embedded.titles
const result: ITitleResult = titles[Math.floor(Math.random() * titles.length)]
this.setState({ result, titleId: result.tconst })
} else {
throw new Error('Response does not match expected format. _embedded not found\n' +
'Received: ${JSON.stringify(resOut)}')
}
} catch (err) {
this.setState({ result: { error: err.message } })
Expand Down
18 changes: 6 additions & 12 deletions ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@
"outDir": "./dist/",
"module": "commonjs",
"target": "es5",
"strict": true,
"jsx": "react",
"lib": [ "es2015", "dom" ],
"lib": ["es2015", "dom"],
"allowSyntheticDefaultImports": true,
"typeRoots": [
"./node_modules/@types",
"./types"
]
"typeRoots": ["./node_modules/@types", "./types"]
},
"exclude": [
"./node_modules/**/*"
],
"include": [
"./src/**/*"
]
}
"exclude": ["./node_modules/**/*"],
"include": ["./src/**/*"]
}
3 changes: 2 additions & 1 deletion ui/tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
],
"jsRules": {},
"rules": {
"no-console": [false],
"quotemark": [true, "single"],
"semicolon": [true, "never"],
"indent": [true, "spaces", 4],
Expand All @@ -16,4 +17,4 @@
]
},
"rulesDirectory": []
}
}