-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
595 additions
and
256 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// @flow | ||
|
||
import React, { useState, useCallback } from 'react'; | ||
import { connect } from 'react-redux'; | ||
import Avatar from '@material-ui/core/Avatar'; | ||
import Button from '@material-ui/core/Button'; | ||
import CssBaseline from '@material-ui/core/CssBaseline'; | ||
import TextField from '@material-ui/core/TextField'; | ||
import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; | ||
import Typography from '@material-ui/core/Typography'; | ||
import { makeStyles, type Theme } from '@material-ui/core/styles'; | ||
import Container from '@material-ui/core/Container'; | ||
|
||
import { | ||
checkAuth, | ||
type AuthParams, | ||
} from 'reducer/auth'; | ||
|
||
import { type MaterialInputElement } from 'reducer/dashboard'; | ||
|
||
type DispatchProps = {| | ||
handleSubmit: (params: AuthParams) => any, | ||
|}; | ||
|
||
const useStyles = makeStyles((theme: Theme) => ({ | ||
paper: { | ||
marginTop: theme.spacing(8), | ||
display: 'flex', | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
}, | ||
avatar: { | ||
margin: theme.spacing(1), | ||
backgroundColor: theme.palette.secondary.main, | ||
}, | ||
form: { | ||
width: '100%', // Fix IE 11 issue. | ||
marginTop: theme.spacing(1), | ||
}, | ||
submit: { margin: theme.spacing(3, 0, 2) }, | ||
})); | ||
|
||
const AuthForm = ({ handleCheckAuth }: DispatchProps) => { | ||
const classes = useStyles(); | ||
const [values, setValues] = useState({ login: '', password: '' }); | ||
const handleInputChange = useCallback((event: MaterialInputElement) => { | ||
const { target } = event; | ||
const value = target.type === 'checkbox' ? target.checked : target.value; | ||
const { name } = target; | ||
|
||
setValues({ | ||
...values, | ||
[name]: value, | ||
}); | ||
}); | ||
const handleSubmit = useCallback((event: Event) => { | ||
event.preventDefault(); | ||
handleCheckAuth(values); | ||
}, values); | ||
|
||
return ( | ||
<Container component='main' maxWidth='xs'> | ||
<CssBaseline /> | ||
<div className={classes.paper}> | ||
<Avatar className={classes.avatar}> | ||
<LockOutlinedIcon /> | ||
</Avatar> | ||
<Typography component='h1' variant='h5'> | ||
Sign in | ||
</Typography> | ||
<form className={classes.form} noValidate onSubmit={handleSubmit}> | ||
<TextField | ||
variant='outlined' | ||
margin='normal' | ||
required | ||
fullWidth | ||
id='login' | ||
label='Login' | ||
name='login' | ||
autoComplete='login' | ||
onChange={handleInputChange} | ||
autoFocus | ||
/> | ||
<TextField | ||
variant='outlined' | ||
margin='normal' | ||
required | ||
fullWidth | ||
name='password' | ||
onChange={handleInputChange} | ||
label='Password' | ||
type='password' | ||
id='password' | ||
autoComplete='current-password' | ||
/> | ||
<Button | ||
type='submit' | ||
fullWidth | ||
variant='contained' | ||
color='primary' | ||
className={classes.submit} | ||
> | ||
Sign In | ||
</Button> | ||
</form> | ||
</div> | ||
</Container> | ||
); | ||
}; | ||
|
||
const mapDispatchToProps: DispatchProps = { handleCheckAuth: checkAuth }; | ||
|
||
export default connect(undefined, mapDispatchToProps)(AuthForm); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,42 @@ | ||
// @flow | ||
import React from 'react'; | ||
import { connect } from 'react-redux'; | ||
|
||
import type { GlobalState } from 'reducer/state'; | ||
import { loadInitialData } from 'reducer/dashboard'; | ||
import { showAuthDialog } from 'reducer/auth'; | ||
|
||
import store from '../store'; | ||
|
||
import Viewport from './Viewport'; | ||
import AuthForm from './AuthForm'; | ||
|
||
type StateProps = {| | ||
org: string, | ||
match: { params?: { token: string } }, | ||
|}; | ||
|
||
const WrappedViewport = ({ match }) => { | ||
store.dispatch(loadInitialData(match.params.token)); | ||
return <Viewport />; | ||
const WrappedViewport = ({ | ||
hasData, | ||
match, | ||
org, | ||
}: StateProps) => { | ||
const { token } = match.params; | ||
const hasToken = !!org || !!token || !!process.env.SHARED_DASHBOARD; | ||
const action = !hasToken | ||
? showAuthDialog() | ||
: loadInitialData(token); | ||
|
||
!hasData && store.dispatch(action); | ||
|
||
return hasToken | ||
? <Viewport /> | ||
: <AuthForm />; | ||
}; | ||
|
||
export default WrappedViewport; | ||
const mapStateToProps = (state: GlobalState): StateProps => ({ | ||
org: state.auth.org, | ||
hasData: state.dashboard.hasData, | ||
}); | ||
|
||
export default connect(mapStateToProps)(WrappedViewport); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// @flow | ||
/* eslint-disable no-console */ | ||
import cloneState from 'utils/cloneState'; | ||
|
||
import { | ||
type Dispatch, | ||
type ThunkAction, | ||
} from 'reducer/types'; | ||
import { loadInitialData } from 'reducer/dashboard'; | ||
|
||
import { API_URL } from '../../constants'; | ||
|
||
|
||
type AuthPayload = { accessToken: string, org: string }; | ||
|
||
type SetAccessTokenAction = {| | ||
type: 'auth/SET_ACCESS_TOKEN', | ||
value: AuthPayload, | ||
|}; | ||
|
||
type SetAuthModalOpenAction = {| | ||
type: 'auth/SET_MODAL_OPEN', | ||
value: boolean, | ||
|}; | ||
|
||
type AuthErrorAction = {| | ||
type: 'auth/ERROR', | ||
value: string, | ||
|}; | ||
|
||
// Combining Actions | ||
|
||
type Action = | ||
| SetAccessTokenAction | ||
| CloseAuthModalAction | ||
| AuthErrorAction; | ||
|
||
// ------------------------------------ | ||
// Action Creators | ||
// ------------------------------------ | ||
|
||
export const setAccessToken = (value: AuthPayload): SetAccessTokenAction => ({ type: 'auth/SET_ACCESS_TOKEN', value }); | ||
|
||
export const setAuthModalOpen = (value: boolean): SetAuthModalOpenAction => ({ type: 'auth/SET_MODAL_OPEN', value }); | ||
|
||
export const setAuthError = (value: string): AuthErrorAction => ({ type: 'auth/ERROR', value }); | ||
|
||
// ------------------------------------ | ||
// Thunk Actions | ||
// ------------------------------------ | ||
|
||
export const showAuthDialog = | ||
(): ThunkAction => async (dispatch: Dispatch): Promise<void> => { | ||
await dispatch(setAuthError('')); | ||
await dispatch(setAuthModalOpen(true)); | ||
}; | ||
|
||
export const checkAuth = | ||
({ login, password }: AuthParams): ThunkAction => async (dispatch: Dispatch): Promise<void> => { | ||
try { | ||
const response = await fetch( | ||
`${API_URL}/auth`, | ||
{ | ||
method: 'post', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify({ login, password }), | ||
}, | ||
); | ||
const { | ||
access_token: accessToken, | ||
org, | ||
error, | ||
} = await response.json(); | ||
|
||
if (accessToken) { | ||
await dispatch(setAccessToken({ accessToken, org })); | ||
await dispatch(setAuthModalOpen(false)); | ||
await dispatch(setAuthError('')); | ||
// setAuth({ org, accessToken }); | ||
await dispatch(loadInitialData(org)); | ||
} else { | ||
await dispatch(setAuthError(error)); | ||
} | ||
} catch (e) { | ||
console.error('checkAuth', e); | ||
} | ||
}; | ||
// ------------------------------------ | ||
// Action Handlers | ||
// ------------------------------------ | ||
const setAccessTokenHandler = | ||
(state: AuthState, action: SetAccessTokenAction): AuthState => cloneState( | ||
state, | ||
action.value, | ||
); | ||
|
||
const setAuthModalOpenHandler = | ||
(state: AuthState, action: SetAuthModalOpenAction): AuthState => cloneState( | ||
state, | ||
{ modal: action.value }, | ||
); | ||
|
||
const setAuthErrorHandler = | ||
(state: AuthState, action: AuthErrorAction): AuthState => cloneState( | ||
state, | ||
{ error: action.value }, | ||
); | ||
|
||
// ------------------------------------ | ||
// Initial State | ||
// ------------------------------------ | ||
|
||
const initialState: AuthState = { | ||
org: '', | ||
error: '', | ||
accessToken: '', | ||
}; | ||
|
||
// ------------------------------------ | ||
// Reducer | ||
// ------------------------------------ | ||
export default function spotsReducer (state: AuthState = initialState, action: Action): DashboardState { | ||
switch (action.type) { | ||
case 'auth/SET_ACCESS_TOKEN': | ||
return setAccessTokenHandler(state, action); | ||
case 'auth/SET_MODAL_OPEN': | ||
return setAuthModalOpenHandler(state, action); | ||
case 'auth/ERROR': | ||
return setAuthErrorHandler(state, action); | ||
default: | ||
// eslint-disable-next-line no-unused-expressions | ||
(action: empty); | ||
return state; | ||
} | ||
} |
Oops, something went wrong.