From 47698544e2e54ed71b434249453c3e7505acd619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysiek=20Gre=C5=84?= Date: Wed, 19 Apr 2017 14:34:12 +0200 Subject: [PATCH] added login logout, passing user object to store & localStorage --- client/src/components/App.js | 45 ++++++++++++++++++------- client/src/components/Auth/Login.js | 26 ++++++++++++++ client/src/components/Auth/LoginForm.js | 37 ++++++++++++++++++++ client/src/components/Auth/index.js | 1 + client/src/components/Routes.js | 2 ++ client/src/store/api/actions.js | 4 +++ client/src/store/api/client.js | 18 ++++++++++ client/src/store/api/reducer.js | 16 ++++++++- client/src/store/api/selectors.js | 2 ++ 9 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 client/src/components/Auth/Login.js create mode 100644 client/src/components/Auth/LoginForm.js create mode 100644 client/src/components/Auth/index.js diff --git a/client/src/components/App.js b/client/src/components/App.js index 71e15db..eb779a1 100644 --- a/client/src/components/App.js +++ b/client/src/components/App.js @@ -1,16 +1,35 @@ -import React from 'react'; +import React, { Component } from 'react'; import { Link } from 'react-router'; +import { connect } from 'react-redux'; -const App = (props) => ( -
-

- Posts |  - Categories |  - Users -

-
- {props.children} -
-); +import { getUser, logout } from '../store/api'; -export default App; +export class App extends Component { + logout = () => this.props.logout(this.props.user); + + render() { + return ( +
+

+ Posts |  + Categories |  + Users |  + Log in |  + Logout +

+
+ {this.props.children} +
+ ) + } +} + +export const mapStateToProps = (state) => ({ + user: getUser(state), +}); + +export const mapDispatchToProps = (dispatch) => ({ + logout: (payload) => dispatch(logout('auth', payload)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(App); diff --git a/client/src/components/Auth/Login.js b/client/src/components/Auth/Login.js new file mode 100644 index 0000000..8d4b631 --- /dev/null +++ b/client/src/components/Auth/Login.js @@ -0,0 +1,26 @@ +import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; + +import LoginForm from './LoginForm'; +import { login } from '../../store/api'; + +export class Login extends Component { + onSubmit = (values) => this.props.login(values); + + render() { + return ( +
+ +
+ ) + } +}; + +export const mapStateToProps = (state) => ({ +}); + +export const mapDispatchToProps = (dispatch) => ({ + login: (payload) => dispatch(login('auth', payload)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/client/src/components/Auth/LoginForm.js b/client/src/components/Auth/LoginForm.js new file mode 100644 index 0000000..22ed7e7 --- /dev/null +++ b/client/src/components/Auth/LoginForm.js @@ -0,0 +1,37 @@ +import React, { Component, PropTypes } from 'react'; +import { isEmpty } from 'lodash'; +import { Field, reduxForm } from 'redux-form'; + +import { InputField, required } from '../Forms'; + +class LoginForm extends Component { + render() { + const { handleSubmit, pristine, reset, submitting } = this.props; + + return ( +
+
+ + +
+
+ +
+
+ ); + } +} + +const validate = values => { + const errors = required(values, + 'email', + 'password', + ); + return errors; +}; + +export default reduxForm({ + enableReinitialize: true, + form: 'login', + validate, +})(LoginForm); diff --git a/client/src/components/Auth/index.js b/client/src/components/Auth/index.js new file mode 100644 index 0000000..f8e93db --- /dev/null +++ b/client/src/components/Auth/index.js @@ -0,0 +1 @@ +export Login from './Login'; diff --git a/client/src/components/Routes.js b/client/src/components/Routes.js index 94d2e15..eea35a0 100644 --- a/client/src/components/Routes.js +++ b/client/src/components/Routes.js @@ -5,6 +5,7 @@ import App from './App'; import { PostList, PostEdit } from './Posts'; import { CategoryList, CategoryEdit } from './Categories'; import { UserList, UserEdit } from './Users'; +import { Login } from './Auth'; export default class Routes extends PureComponent { static propTypes = { @@ -25,6 +26,7 @@ export default class Routes extends PureComponent { + ); diff --git a/client/src/store/api/actions.js b/client/src/store/api/actions.js index fff889f..13ce014 100644 --- a/client/src/store/api/actions.js +++ b/client/src/store/api/actions.js @@ -5,6 +5,8 @@ import client, { CREATE, UPDATE, DELETE, + AUTH_LOGIN, + AUTH_LOGOUT, } from './client'; export const STARTED = 'STARTED'; @@ -39,3 +41,5 @@ export const fetchMany = createAsyncAction(GET_MANY); export const createResource = createAsyncAction(CREATE); export const updateResource = createAsyncAction(UPDATE); export const deleteResource = createAsyncAction(DELETE); +export const login = createAsyncAction(AUTH_LOGIN); +export const logout = createAsyncAction(AUTH_LOGOUT); diff --git a/client/src/store/api/client.js b/client/src/store/api/client.js index 6adecc4..37b6720 100644 --- a/client/src/store/api/client.js +++ b/client/src/store/api/client.js @@ -9,6 +9,7 @@ import { values, keys, zipObject, + pick, } from 'lodash'; import { denormalize, normalize } from './normalize'; @@ -19,6 +20,8 @@ export const GET_MANY = 'GET_MANY'; export const CREATE = 'CREATE'; export const UPDATE = 'UPDATE'; export const DELETE = 'DELETE'; +export const AUTH_LOGIN = 'AUTH_LOGIN'; +export const AUTH_LOGOUT = 'AUTH_LOGOUT'; const client = axios.create({ baseURL: '/', @@ -91,6 +94,21 @@ export default (request, payload, meta) => { url: withParams(`${url}/${payload.id}`), method: 'DELETE', }).then(response => normalizeResponse({ data: payload })); + case AUTH_LOGIN: + return client({ + url: 'auth/sign_in', + method: 'POST', + data: payload, + }).then(response => ({ + ...response.data.data, + ...pick(response.headers, ['access-token', 'client']), + })); + case AUTH_LOGOUT: + return client({ + url: 'auth/sign_out', + method: 'DELETE', + data: payload, + }); default: throw `No client handler for ${request}`; } diff --git a/client/src/store/api/reducer.js b/client/src/store/api/reducer.js index 6b257a6..b945ed5 100644 --- a/client/src/store/api/reducer.js +++ b/client/src/store/api/reducer.js @@ -15,6 +15,8 @@ import { CREATE, UPDATE, DELETE, + AUTH_LOGIN, + AUTH_LOGOUT, } from './client'; import { @@ -24,7 +26,9 @@ import { actionType, } from './actions'; -const initialState = {}; +const initialState = { + user: JSON.parse(localStorage.getItem('user') || '{}'), +}; const addNormalized = (newState, payload) => { keys(payload.normalized).forEach(key => { @@ -67,6 +71,16 @@ export default (state = initialState, action) => { ); return newState; } + case actionType(AUTH_LOGIN, SUCCESS): { + localStorage.setItem('user', JSON.stringify(payload)); + newState = imm.set(newState, ['user'], payload); + return newState; + } + case actionType(AUTH_LOGOUT, SUCCESS): { + localStorage.removeItem('user'); + newState = imm.set(newState, ['user'], {}); + return newState; + } default: return state } diff --git a/client/src/store/api/selectors.js b/client/src/store/api/selectors.js index 256dd76..814de26 100644 --- a/client/src/store/api/selectors.js +++ b/client/src/store/api/selectors.js @@ -1,5 +1,7 @@ import { get, isEmpty } from 'lodash'; +export const getUser = (state) => get(state, ['api', 'user']) || {}; + export const getOne = (state, key, id) => get(state, ['api', key, 'byId', id]) || {}; export const getMap = (state, key) => get(state, ['api', key, 'byId']) || {};