Skip to content

Commit 5ebb46a

Browse files
committed
Authentication functionality
1 parent 0b6fb9d commit 5ebb46a

File tree

12 files changed

+251
-15
lines changed

12 files changed

+251
-15
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
"react-apexcharts": "^1.3.0",
1515
"react-dom": "^16.8.2",
1616
"react-google-maps": "^9.4.5",
17+
"react-redux": "^6.0.1",
1718
"react-router": "^4.3.1",
1819
"react-router-dom": "^4.3.1",
1920
"react-scripts": "2.1.5",
2021
"react-syntax-highlighter": "^10.2.0",
2122
"react-toastify": "^4.5.2",
2223
"recharts": "^1.5.0",
23-
"recompose": "^0.30.0"
24+
"recompose": "^0.30.0",
25+
"redux": "^4.0.1",
26+
"redux-thunk": "^2.3.0"
2427
},
2528
"scripts": {
2629
"start": "react-scripts start",

src/components/App.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,51 @@ import Login from '../pages/login';
99

1010
const theme = createMuiTheme(themes.default.theme);
1111

12+
const PrivateRoute = ({ component, ...rest }) => {
13+
return (
14+
<Route
15+
{...rest} render={props => (
16+
localStorage.getItem('id_token') ? (
17+
React.createElement(component, props)
18+
) : (
19+
<Redirect
20+
to={{
21+
pathname: '/login',
22+
state: { from: props.location },
23+
}}
24+
/>
25+
)
26+
)}
27+
/>
28+
);
29+
};
30+
31+
const PublicRoute = ({ component, ...rest }) => {
32+
return (
33+
<Route
34+
{...rest} render={props => (
35+
localStorage.getItem('id_token') ? (
36+
<Redirect
37+
to={{
38+
pathname: '/',
39+
}}
40+
/>
41+
) : (
42+
React.createElement(component, props)
43+
)
44+
)}
45+
/>
46+
);
47+
};
48+
1249
const App = () => (
1350
<MuiThemeProvider theme={theme}>
1451
<BrowserRouter>
1552
<Switch>
1653
<Route exact path="/" render={() => <Redirect to="/app/dashboard" />} />
1754
<Route exact path="/app" render={() => <Redirect to="/app/dashboard" />} />
18-
<Route path="/app" component={Layout} />
19-
<Route path="/login" component={Login} />
55+
<PrivateRoute path="/app" component={Layout} />
56+
<PublicRoute path="/login" component={Login} />
2057
<Route component={Error} />
2158
</Switch>
2259
</BrowserRouter>

src/components/AppContainer.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { compose } from 'recompose';
2+
import { connect } from 'react-redux';
3+
4+
import AppView from './App';
5+
6+
export default compose(
7+
connect(
8+
state => ({
9+
isAuthenticated: state.login.isAuthenticated,
10+
})
11+
)
12+
)(AppView);

src/components/Header/HeaderContainer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { compose, withState, withHandlers } from 'recompose';
2+
import { connect } from 'react-redux';
3+
24
import HeaderView from './HeaderView';
5+
import { signOut } from '../../pages/login/LoginState';
36

47
export default compose(
8+
connect(
9+
null,
10+
{ signOut },
11+
),
512
withState('mailMenu', 'setMailMenu', null),
613
withState('isMailsUnread', 'setIsMailsUnread', true),
714
withState('notificationsMenu', 'setNotificationsMenu', null),

src/components/Header/HeaderView.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
AppBar,
44
Toolbar,
55
IconButton,
6+
Button,
67
InputBase,
78
Menu,
89
MenuItem,
@@ -184,7 +185,7 @@ const Header = ({ classes, isSidebarOpened, toggleSidebar, ...props }) => (
184185
<MenuItem className={classNames(classes.profileMenuItem, classes.headerMenuItem)}><AccountIcon className={classes.profileMenuIcon}/> Tasks</MenuItem>
185186
<MenuItem className={classNames(classes.profileMenuItem, classes.headerMenuItem)}><AccountIcon className={classes.profileMenuIcon}/> Messages</MenuItem>
186187
<div className={classes.profileMenuUser}>
187-
<Typography className={classes.profileMenuLink} component="a" color="primary">Sign Out</Typography>
188+
<Typography className={classes.profileMenuLink} color="primary" onClick={props.signOut}>Sign Out</Typography>
188189
</div>
189190
</Menu>
190191
</Toolbar>
@@ -315,6 +316,9 @@ const styles = theme => ({
315316
profileMenuLink: {
316317
fontSize: 16,
317318
textDecoration: 'none',
319+
'&:hover': {
320+
cursor: 'pointer',
321+
},
318322
}
319323
});
320324

src/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
3-
import App from './components/App';
3+
import App from './components/AppContainer';
4+
import { Provider } from 'react-redux'
45
import * as serviceWorker from './serviceWorker';
56

7+
import store from './store';
8+
69
ReactDOM.render(
7-
<App />,
10+
<Provider store={store}>
11+
<App />
12+
</Provider>,
813
document.getElementById('root'),
914
);
1015

src/pages/login/LoginContainer.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
import { compose, withState, withHandlers } from 'recompose';
1+
import { compose, withState, withHandlers, lifecycle } from 'recompose';
2+
import { withRouter } from 'react-router-dom';
3+
import { connect } from 'react-redux';
24

35
import LoginView from './LoginView';
6+
import { loginUser, resetError } from './LoginState';
47

58
export default compose(
9+
connect(
10+
state => ({
11+
isLoading: state.login.isLoading,
12+
isAuthenticated: state.login.isAuthenticated,
13+
error: state.login.error,
14+
}),
15+
{ loginUser, resetError },
16+
),
17+
withRouter,
618
withState('activeTabId', 'setActiveTabId', 0),
719
withState('loginValue', 'setLoginValue', ''),
820
withState('passwordValue', 'setPasswordValue', ''),
@@ -11,11 +23,25 @@ export default compose(
1123
props.setActiveTabId(id);
1224
},
1325
handleInput: props => (e, input = 'login') => {
26+
if (props.error) {
27+
props.resetError();
28+
}
29+
1430
if (input === 'login') {
1531
props.setLoginValue(e.target.value);
1632
} else {
1733
props.setPasswordValue(e.target.value);
1834
}
19-
}
35+
},
36+
handleLoginButtonClick: props => () => {
37+
props.loginUser(props.loginValue, props.passwordValue);
38+
},
2039
}),
40+
lifecycle({
41+
componentWillReceiveProps(nextProps) {
42+
if (!this.props.error && nextProps.error) {
43+
this.props.setPasswordValue('');
44+
}
45+
}
46+
})
2147
)(LoginView);

src/pages/login/LoginState.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
export const initialState = {
2+
isLoading: false,
3+
isAuthenticated: false,
4+
error: null,
5+
};
6+
7+
export const START_LOGIN = 'Login/START_LOGIN';
8+
export const LOGIN_SUCCESS = 'Login/LOGIN_SUCCESS';
9+
export const LOGIN_FAILURE = 'Login/LOGIN_FAILURE';
10+
export const RESET_ERROR = 'Login/RESET_ERROR';
11+
export const LOGIN_USER = 'Login/LOGIN_USER';
12+
export const SIGN_OUT_SUCCESS = 'Login/SIGN_OUT_SUCCESS';
13+
14+
export const startLogin = () => ({
15+
type: START_LOGIN,
16+
});
17+
18+
export const loginSuccess = () => ({
19+
type: LOGIN_SUCCESS,
20+
});
21+
22+
export const loginFailure = () => ({
23+
type: LOGIN_FAILURE,
24+
});
25+
26+
export const resetError = () => ({
27+
type: RESET_ERROR,
28+
})
29+
30+
export const loginUser = (login, password) => dispatch => {
31+
dispatch(startLogin());
32+
33+
if (!!login && !!password) {
34+
setTimeout(() => {
35+
localStorage.setItem('id_token', '1');
36+
dispatch(loginSuccess());
37+
}, 2000);
38+
} else {
39+
dispatch(loginFailure());
40+
}
41+
};
42+
43+
export const signOutSuccess = () => ({
44+
type: SIGN_OUT_SUCCESS,
45+
});
46+
47+
export const signOut = () => dispatch => {
48+
localStorage.removeItem('id_token');
49+
dispatch(signOutSuccess());
50+
};
51+
52+
export default function LoginReducer(state = initialState, { type, payload }) {
53+
switch (type) {
54+
case START_LOGIN:
55+
return {
56+
...state,
57+
isLoading: true,
58+
};
59+
case LOGIN_SUCCESS:
60+
return {
61+
...state,
62+
isLoading: false,
63+
isAuthenticated: true,
64+
error: null,
65+
};
66+
case LOGIN_FAILURE:
67+
return {
68+
...state,
69+
isLoading: false,
70+
error: true,
71+
};
72+
case RESET_ERROR:
73+
return {
74+
error: false,
75+
};
76+
case SIGN_OUT_SUCCESS:
77+
debugger;
78+
return {
79+
...state,
80+
isAuthenticated: false,
81+
};
82+
default:
83+
return state;
84+
}
85+
}

src/pages/login/LoginView.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from 'react';
2-
import { Link } from 'react-router-dom';
3-
import { Grid, Paper, Typography, withStyles, Button, Tabs, Tab, TextField } from '@material-ui/core';
2+
import { Grid, CircularProgress, Typography, withStyles, Button, Tabs, Tab, TextField, Fade } from '@material-ui/core';
43
import classnames from 'classnames';
54

65
import logo from './logo.svg';
@@ -31,6 +30,7 @@ const Login = ({ classes, ...props }) => (
3130
<Typography className={classes.formDividerWord}>or</Typography>
3231
<div className={classes.formDivider} />
3332
</div>
33+
<Fade in={props.error}><Typography color="secondary" className={classes.errorMessage}>Something is wrong with your login or password :(</Typography></Fade>
3434
<TextField
3535
id="email"
3636
InputProps={{ classes: { underline: classes.textFieldUnderline, input: classes.textField }}}
@@ -52,8 +52,11 @@ const Login = ({ classes, ...props }) => (
5252
fullWidth
5353
/>
5454
<div className={classes.formButtons}>
55-
<Button disabled={props.loginValue.length === 0 || props.passwordValue.length === 0} variant="contained" color="primary" size="large">Login</Button>
56-
<Typography color="primary">Forget Password</Typography>
55+
{props.isLoading
56+
? <CircularProgress size={26} className={classes.loginLoader}/>
57+
: <Button disabled={props.loginValue.length === 0 || props.passwordValue.length === 0} onClick={props.handleLoginButtonClick} variant="contained" color="primary" size="large">Login</Button>
58+
}
59+
<Button color="primary" size="large" className={classes.forgetButton}>Forget Password</Button>
5760
</div>
5861
</div>
5962
<Typography color="primary" className={classes.copyright}>
@@ -150,6 +153,9 @@ const styles = theme => ({
150153
height: 1,
151154
backgroundColor: theme.palette.text.hint + '40',
152155
},
156+
errorMessage: {
157+
textAlign: 'center',
158+
},
153159
textFieldUnderline: {
154160
'&:before': {
155161
borderBottomColor: theme.palette.primary.light,
@@ -171,6 +177,13 @@ const styles = theme => ({
171177
justifyContent: 'space-between',
172178
alignItems: 'center',
173179
},
180+
forgetButton: {
181+
textTransform: 'none',
182+
fontWeight: 400,
183+
},
184+
loginLoader: {
185+
marginLeft: theme.spacing.unit * 4,
186+
},
174187
copyright: {
175188
position: 'absolute',
176189
bottom: theme.spacing.unit * 2,

src/store/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { createStore, applyMiddleware } from 'redux';
2+
import ReduxThunk from 'redux-thunk'
3+
4+
import reducers from './reducers';
5+
6+
const store = createStore(
7+
reducers,
8+
applyMiddleware(ReduxThunk)
9+
);
10+
11+
export default store;

0 commit comments

Comments
 (0)