Skip to content

Commit

Permalink
add auth form #67
Browse files Browse the repository at this point in the history
  • Loading branch information
Sigura committed Mar 16, 2020
1 parent 578baed commit e89ca2c
Show file tree
Hide file tree
Showing 19 changed files with 595 additions and 256 deletions.
2 changes: 1 addition & 1 deletion src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Switch,
} from 'react-router-dom';

import WrappedViewport from './components/WrappedViewport';
import WrappedViewport from 'components/WrappedViewport';

const App = ({ store }) => (
<Provider store={store}>
Expand Down
113 changes: 113 additions & 0 deletions src/client/components/AuthForm.js
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);
6 changes: 3 additions & 3 deletions src/client/components/FilterView/OrgField.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const styles = (theme: any) => ({
},
});

const OrgField = withStyles(styles)((props: Props) => {
const OrgField = (props: Props) => {
const {
onChange, value, source: s, fullScreen, classes,
} = props;
Expand Down Expand Up @@ -221,6 +221,6 @@ const OrgField = withStyles(styles)((props: Props) => {
</DialogActions>
</Dialog>,
];
});
};

export default OrgField;
export default withStyles(styles)(OrgField);
5 changes: 3 additions & 2 deletions src/client/components/Viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import TooManyPointsWarning from './TooManyPointsWarning';

type StateProps = {|
isLocationSelected: boolean,
activeTabIndex: 0 | 1,
location: ?Location,
activeTabIndex: 0 | 1,
location: ?Location,
|};
type DispatchProps = {|
onChangeActiveTab: (tab: TabType) => any,
Expand Down Expand Up @@ -113,4 +113,5 @@ const mapStateToProps = (state: GlobalState): StateProps => ({
location: getLocation(state),
});
const mapDispatchToProps: DispatchProps = { onChangeActiveTab: changeActiveTab };

export default connect(mapStateToProps, mapDispatchToProps)(Viewport);
35 changes: 31 additions & 4 deletions src/client/components/WrappedViewport.js
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);
4 changes: 2 additions & 2 deletions src/client/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { render } from 'react-dom';

import App from './client';
import store from './store';
import App from 'client';
import store from 'store';

// Detect users incorrectly hitting /locations/username instead of /username.
// It seems people think because the plugin is POSTing -> /location/username that they must
Expand Down
135 changes: 135 additions & 0 deletions src/client/reducer/auth/index.js
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;
}
}
Loading

0 comments on commit e89ca2c

Please sign in to comment.