Client: login/register
Client: login/register Client: login/register Client: login/register Client: login/register Client: login/register Client: login/register Client: login/register Client: login/register
This commit is contained in:
		@@ -32,3 +32,16 @@ if app.debug:
 | 
			
		||||
                      ).handlers = logging.getLogger('werkzeug').handlers
 | 
			
		||||
    logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
 | 
			
		||||
    appLog.setLevel(logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
if app.debug :
 | 
			
		||||
    # Enable CORS
 | 
			
		||||
    @app.after_request
 | 
			
		||||
    def after_request(response):
 | 
			
		||||
        response.headers.add('Access-Control-Allow-Origin', '*')
 | 
			
		||||
        response.headers.add(
 | 
			
		||||
            'Access-Control-Allow-Headers', 'Content-Type,Authorization'
 | 
			
		||||
        )
 | 
			
		||||
        response.headers.add(
 | 
			
		||||
            'Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS'
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 
 | 
			
		||||
@@ -191,3 +191,42 @@ class TestAuthBlueprint(BaseTestCase):
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                data['message'] == 'Invalid token. Please log in again.')
 | 
			
		||||
            self.assertEqual(response.status_code, 401)
 | 
			
		||||
 | 
			
		||||
    def test_user_profile(self):
 | 
			
		||||
        add_user('test', 'test@test.com', 'test')
 | 
			
		||||
        with self.client:
 | 
			
		||||
            resp_login = self.client.post(
 | 
			
		||||
                '/api/auth/login',
 | 
			
		||||
                data=json.dumps(dict(
 | 
			
		||||
                    email='test@test.com',
 | 
			
		||||
                    password='test'
 | 
			
		||||
                )),
 | 
			
		||||
                content_type='application/json'
 | 
			
		||||
            )
 | 
			
		||||
            response = self.client.get(
 | 
			
		||||
                '/api/auth/profile',
 | 
			
		||||
                headers=dict(
 | 
			
		||||
                    Authorization='Bearer ' + json.loads(
 | 
			
		||||
                        resp_login.data.decode()
 | 
			
		||||
                    )['auth_token']
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            data = json.loads(response.data.decode())
 | 
			
		||||
            self.assertTrue(data['status'] == 'success')
 | 
			
		||||
            self.assertTrue(data['data'] is not None)
 | 
			
		||||
            self.assertTrue(data['data']['username'] == 'test')
 | 
			
		||||
            self.assertTrue(data['data']['email'] == 'test@test.com')
 | 
			
		||||
            self.assertTrue(data['data']['created_at'])
 | 
			
		||||
            self.assertFalse(data['data']['admin'])
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
    def test_invalid_profile(self):
 | 
			
		||||
        with self.client:
 | 
			
		||||
            response = self.client.get(
 | 
			
		||||
                '/api/auth/profile',
 | 
			
		||||
                headers=dict(Authorization='Bearer invalid'))
 | 
			
		||||
            data = json.loads(response.data.decode())
 | 
			
		||||
            self.assertTrue(data['status'] == 'error')
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                data['message'] == 'Invalid token. Please log in again.')
 | 
			
		||||
            self.assertEqual(response.status_code, 401)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ from sqlalchemy import exc, or_
 | 
			
		||||
from mpwo_api import appLog, bcrypt, db
 | 
			
		||||
 | 
			
		||||
from .models import User
 | 
			
		||||
from .utils import authenticate
 | 
			
		||||
 | 
			
		||||
auth_blueprint = Blueprint('auth', __name__)
 | 
			
		||||
 | 
			
		||||
@@ -12,7 +13,9 @@ auth_blueprint = Blueprint('auth', __name__)
 | 
			
		||||
def register_user():
 | 
			
		||||
    # get post data
 | 
			
		||||
    post_data = request.get_json()
 | 
			
		||||
    if not post_data:
 | 
			
		||||
    if not post_data or post_data.get('username') is None \
 | 
			
		||||
            or post_data.get('email') is None \
 | 
			
		||||
            or post_data.get('password') is None:
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Invalid payload.'
 | 
			
		||||
@@ -52,9 +55,10 @@ def register_user():
 | 
			
		||||
    except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
 | 
			
		||||
        db.session.rollback()
 | 
			
		||||
        appLog.error(e)
 | 
			
		||||
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Invalid payload.'
 | 
			
		||||
            'message': 'Error. Please try again or contact the administrator.'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response_object), 400
 | 
			
		||||
 | 
			
		||||
@@ -95,19 +99,20 @@ def login_user():
 | 
			
		||||
        appLog.error(e)
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Try again'
 | 
			
		||||
            'message': 'Error. Please try again or contact the administrator.'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response_object), 500
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/logout', methods=['GET'])
 | 
			
		||||
def logout_user():
 | 
			
		||||
@authenticate
 | 
			
		||||
def logout_user(user_id):
 | 
			
		||||
    # get auth token
 | 
			
		||||
    auth_header = request.headers.get('Authorization')
 | 
			
		||||
    if auth_header:
 | 
			
		||||
        auth_token = auth_header.split(" ")[1]
 | 
			
		||||
        resp = User.decode_auth_token(auth_token)
 | 
			
		||||
        if not isinstance(resp, str):
 | 
			
		||||
        if not isinstance(user_id, str):
 | 
			
		||||
            response_object = {
 | 
			
		||||
                'status': 'success',
 | 
			
		||||
                'message': 'Successfully logged out.'
 | 
			
		||||
@@ -125,3 +130,20 @@ def logout_user():
 | 
			
		||||
            'message': 'Provide a valid auth token.'
 | 
			
		||||
        }
 | 
			
		||||
        return jsonify(response_object), 403
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@auth_blueprint.route('/auth/profile', methods=['GET'])
 | 
			
		||||
@authenticate
 | 
			
		||||
def get_user_status(user_id):
 | 
			
		||||
    user = User.query.filter_by(id=user_id).first()
 | 
			
		||||
    response_object = {
 | 
			
		||||
        'status': 'success',
 | 
			
		||||
        'data': {
 | 
			
		||||
            'id': user.id,
 | 
			
		||||
            'username': user.username,
 | 
			
		||||
            'email': user.email,
 | 
			
		||||
            'created_at': user.created_at,
 | 
			
		||||
            'admin': user.admin,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return jsonify(response_object), 200
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								mpwo_api/mpwo_api/users/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								mpwo_api/mpwo_api/users/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
from functools import wraps
 | 
			
		||||
 | 
			
		||||
from flask import request, jsonify
 | 
			
		||||
 | 
			
		||||
from .models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def authenticate(f):
 | 
			
		||||
    @wraps(f)
 | 
			
		||||
    def decorated_function(*args, **kwargs):
 | 
			
		||||
        response_object = {
 | 
			
		||||
            'status': 'error',
 | 
			
		||||
            'message': 'Something went wrong. Please contact us.'
 | 
			
		||||
        }
 | 
			
		||||
        code = 401
 | 
			
		||||
        auth_header = request.headers.get('Authorization')
 | 
			
		||||
        if not auth_header:
 | 
			
		||||
            response_object['message'] = 'Provide a valid auth token.'
 | 
			
		||||
            code = 403
 | 
			
		||||
            return jsonify(response_object), code
 | 
			
		||||
        auth_token = auth_header.split(" ")[1]
 | 
			
		||||
        resp = User.decode_auth_token(auth_token)
 | 
			
		||||
        if isinstance(resp, str):
 | 
			
		||||
            response_object['message'] = resp
 | 
			
		||||
            return jsonify(response_object), code
 | 
			
		||||
        user = User.query.filter_by(id=resp).first()
 | 
			
		||||
        if not user:
 | 
			
		||||
            return jsonify(response_object), code
 | 
			
		||||
        return f(resp, *args, **kwargs)
 | 
			
		||||
    return decorated_function
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_admin(user_id):
 | 
			
		||||
    user = User.query.filter_by(id=user_id).first()
 | 
			
		||||
    return user.admin
 | 
			
		||||
@@ -0,0 +1,117 @@
 | 
			
		||||
import mpwoApi from '../mpwoApi'
 | 
			
		||||
 | 
			
		||||
function AuthError(message) {
 | 
			
		||||
  return { type: 'AUTH_ERROR', message }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ProfileSuccess(message) {
 | 
			
		||||
  return { type: 'PROFILE_SUCCESS', message }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ProfileError(message) {
 | 
			
		||||
  return { type: 'PROFILE_ERROR', message }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const updateFormDataEmail = value => ({
 | 
			
		||||
  type: 'UPDATE_FORMDATA_EMAIL',
 | 
			
		||||
  email: value,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const updateFormDataUsername = value => ({
 | 
			
		||||
  type: 'UPDATE_FORMDATA_USERNAME',
 | 
			
		||||
  username: value,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const updateFormDataPassword = value => ({
 | 
			
		||||
  type: 'UPDATE_FORMDATA_PASSWORD',
 | 
			
		||||
  password: value,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export function getProfile(dispatch) {
 | 
			
		||||
  return mpwoApi
 | 
			
		||||
    .getProfile()
 | 
			
		||||
    .then(ret => {
 | 
			
		||||
      if (ret.status === 'success') {
 | 
			
		||||
        dispatch(ProfileSuccess(ret))
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(ProfileError(ret.message))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    .catch(error => {
 | 
			
		||||
      throw error
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function register(formData) {
 | 
			
		||||
  return function(dispatch) {
 | 
			
		||||
    return mpwoApi
 | 
			
		||||
      .register(formData.username, formData.email, formData.password)
 | 
			
		||||
      .then(ret => {
 | 
			
		||||
        if (ret.status === 'success') {
 | 
			
		||||
          window.localStorage.setItem('authToken', ret.auth_token)
 | 
			
		||||
          getProfile(dispatch)
 | 
			
		||||
        } else {
 | 
			
		||||
          dispatch(AuthError(ret.message))
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch(error => {
 | 
			
		||||
        throw error
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function login(formData) {
 | 
			
		||||
  return function(dispatch) {
 | 
			
		||||
    return mpwoApi
 | 
			
		||||
      .login(formData.email, formData.password)
 | 
			
		||||
      .then(ret => {
 | 
			
		||||
        if (ret.status === 'success') {
 | 
			
		||||
          window.localStorage.setItem('authToken', ret.auth_token)
 | 
			
		||||
          getProfile(dispatch)
 | 
			
		||||
        } else {
 | 
			
		||||
          dispatch(AuthError(ret.message))
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch(error => {
 | 
			
		||||
        throw error
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function loadProfile() {
 | 
			
		||||
  if (window.localStorage.getItem('authToken')) {
 | 
			
		||||
    return function(dispatch) {
 | 
			
		||||
      getProfile(dispatch)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return { type: 'LOGOUT' }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function logout() {
 | 
			
		||||
  return { type: 'LOGOUT' }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function handleUserFormSubmit(event, formType) {
 | 
			
		||||
  event.preventDefault()
 | 
			
		||||
  return (dispatch, getState) => {
 | 
			
		||||
    const state = getState()
 | 
			
		||||
    let { formData } = state.formData
 | 
			
		||||
    formData.formData = state.formData.formData
 | 
			
		||||
    if (formType === 'Login') {
 | 
			
		||||
      dispatch(login(formData.formData))
 | 
			
		||||
    } else { // formType === 'Register'
 | 
			
		||||
      dispatch(register(formData.formData))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const handleFormChange = event => dispatch => {
 | 
			
		||||
  switch (event.target.name) {
 | 
			
		||||
    case 'email':
 | 
			
		||||
      return dispatch(updateFormDataEmail(event.target.value))
 | 
			
		||||
    case 'username':
 | 
			
		||||
      return dispatch(updateFormDataUsername(event.target.value))
 | 
			
		||||
    default: // case 'password':
 | 
			
		||||
      return dispatch(updateFormDataPassword(event.target.value))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { Route, Switch } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import './App.css'
 | 
			
		||||
import Logout from './Logout'
 | 
			
		||||
import NavBar from './NavBar'
 | 
			
		||||
import UserForm from './User/UserForm'
 | 
			
		||||
 | 
			
		||||
export default class App extends React.Component {
 | 
			
		||||
  constructor(props) {
 | 
			
		||||
@@ -13,9 +16,37 @@ export default class App extends React.Component {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className="App">
 | 
			
		||||
        <NavBar />
 | 
			
		||||
        <p className="App-body">
 | 
			
		||||
          App in progress
 | 
			
		||||
        </p>
 | 
			
		||||
        <div className="container">
 | 
			
		||||
          <div className="row">
 | 
			
		||||
            <div className="col-md-6">
 | 
			
		||||
              <br />
 | 
			
		||||
              <Switch>
 | 
			
		||||
                <Route
 | 
			
		||||
                  exact path="/register"
 | 
			
		||||
                  render={() => (
 | 
			
		||||
                    <UserForm
 | 
			
		||||
                      formType={'Register'}
 | 
			
		||||
                    />
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <Route
 | 
			
		||||
                  exact path="/login"
 | 
			
		||||
                  render={() => (
 | 
			
		||||
                    <UserForm
 | 
			
		||||
                      formType={'Login'}
 | 
			
		||||
                    />
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
                <Route
 | 
			
		||||
                  exact path="/logout"
 | 
			
		||||
                  render={() => (
 | 
			
		||||
                    <Logout />
 | 
			
		||||
                  )}
 | 
			
		||||
                />
 | 
			
		||||
              </Switch>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								mpwo_client/src/components/Logout.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								mpwo_client/src/components/Logout.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import { logout } from '../actions'
 | 
			
		||||
 | 
			
		||||
class Logout extends React.Component {
 | 
			
		||||
  componentDidMount() {
 | 
			
		||||
    this.props.UserLogout()
 | 
			
		||||
  }
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
      <div>
 | 
			
		||||
        <p>
 | 
			
		||||
          You are now logged out.
 | 
			
		||||
          Click <Link to="/login">here</Link> to log back in.</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default connect(
 | 
			
		||||
  state => ({
 | 
			
		||||
    user: state.user,
 | 
			
		||||
  }),
 | 
			
		||||
  dispatch => ({
 | 
			
		||||
    UserLogout: () => {
 | 
			
		||||
      dispatch(logout())
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
)(Logout)
 | 
			
		||||
@@ -1,14 +1,9 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
export default class NavBar extends React.Component {
 | 
			
		||||
  constructor(props) {
 | 
			
		||||
    super(props)
 | 
			
		||||
    this.props = props
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return (
 | 
			
		||||
function NavBar (props) {
 | 
			
		||||
  return (
 | 
			
		||||
    <header>
 | 
			
		||||
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
 | 
			
		||||
        <span className="navbar-brand">mpwo</span>
 | 
			
		||||
@@ -36,10 +31,50 @@ export default class NavBar extends React.Component {
 | 
			
		||||
                Home
 | 
			
		||||
              </Link>
 | 
			
		||||
            </li>
 | 
			
		||||
            {!props.user.isAuthenticated && (
 | 
			
		||||
              <li className="nav-item">
 | 
			
		||||
                <Link
 | 
			
		||||
                  className="nav-link"
 | 
			
		||||
                  to={{
 | 
			
		||||
                    pathname: '/login',
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  Login
 | 
			
		||||
                </Link>
 | 
			
		||||
              </li>
 | 
			
		||||
            )}
 | 
			
		||||
            {!props.user.isAuthenticated && (
 | 
			
		||||
              <li className="nav-item">
 | 
			
		||||
                <Link
 | 
			
		||||
                  className="nav-link"
 | 
			
		||||
                  to={{
 | 
			
		||||
                    pathname: '/register',
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  Register
 | 
			
		||||
                </Link>
 | 
			
		||||
              </li>
 | 
			
		||||
            )}
 | 
			
		||||
            {props.user.isAuthenticated && (
 | 
			
		||||
            <li className="nav-item">
 | 
			
		||||
              <Link
 | 
			
		||||
                className="nav-link"
 | 
			
		||||
                to={{
 | 
			
		||||
                  pathname: '/logout',
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                Logout
 | 
			
		||||
              </Link>
 | 
			
		||||
            </li>
 | 
			
		||||
            )}
 | 
			
		||||
          </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
      </nav>
 | 
			
		||||
    </header>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
export default connect(
 | 
			
		||||
  state => ({
 | 
			
		||||
    user: state.user,
 | 
			
		||||
  })
 | 
			
		||||
)(NavBar)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								mpwo_client/src/components/User/Form.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								mpwo_client/src/components/User/Form.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default function Form (props) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h1>{props.formType}</h1>
 | 
			
		||||
      <hr /><br />
 | 
			
		||||
      <form onSubmit={event =>
 | 
			
		||||
          props.handleUserFormSubmit(event, props.formType)}
 | 
			
		||||
      >
 | 
			
		||||
        {props.formType === 'Register' &&
 | 
			
		||||
          <div className="form-group">
 | 
			
		||||
            <input
 | 
			
		||||
              name="username"
 | 
			
		||||
              className="form-control input-lg"
 | 
			
		||||
              type="text"
 | 
			
		||||
              placeholder="Enter a username"
 | 
			
		||||
              required
 | 
			
		||||
              onChange={props.onHandleFormChange}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        }
 | 
			
		||||
        <div className="form-group">
 | 
			
		||||
          <input
 | 
			
		||||
            name="email"
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            type="email"
 | 
			
		||||
            placeholder="Enter an email address"
 | 
			
		||||
            required
 | 
			
		||||
            onChange={props.onHandleFormChange}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="form-group">
 | 
			
		||||
          <input
 | 
			
		||||
            name="password"
 | 
			
		||||
            className="form-control input-lg"
 | 
			
		||||
            type="password"
 | 
			
		||||
            placeholder="Enter a password"
 | 
			
		||||
            required
 | 
			
		||||
            onChange={props.onHandleFormChange}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <input
 | 
			
		||||
          type="submit"
 | 
			
		||||
          className="btn btn-primary btn-lg btn-block"
 | 
			
		||||
          value="Submit"
 | 
			
		||||
        />
 | 
			
		||||
      </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								mpwo_client/src/components/User/UserForm.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								mpwo_client/src/components/User/UserForm.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
 | 
			
		||||
import Form from './Form'
 | 
			
		||||
import { handleFormChange, handleUserFormSubmit } from '../../actions'
 | 
			
		||||
 | 
			
		||||
function UserForm(props) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
     {props.message !== '' && (
 | 
			
		||||
       <code>{props.message}</code>
 | 
			
		||||
     )}
 | 
			
		||||
     <Form
 | 
			
		||||
        formType={props.formType}
 | 
			
		||||
        userForm={props.formData}
 | 
			
		||||
        onHandleFormChange={event => props.onHandleFormChange(event)}
 | 
			
		||||
        handleUserFormSubmit={(event, formType) =>
 | 
			
		||||
            props.onHandleUserFormSubmit(event, formType)}
 | 
			
		||||
     />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
export default connect(
 | 
			
		||||
  state => ({
 | 
			
		||||
    formData: state.formData,
 | 
			
		||||
    message: state.message,
 | 
			
		||||
  }),
 | 
			
		||||
  dispatch => ({
 | 
			
		||||
    onHandleFormChange: event => {
 | 
			
		||||
      dispatch(handleFormChange(event))
 | 
			
		||||
    },
 | 
			
		||||
    onHandleUserFormSubmit: (event, formType) => {
 | 
			
		||||
      dispatch(handleUserFormSubmit(event, formType))
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
)(UserForm)
 | 
			
		||||
@@ -10,7 +10,7 @@ import App from './components/App'
 | 
			
		||||
import Root from './components/Root'
 | 
			
		||||
import registerServiceWorker from './registerServiceWorker'
 | 
			
		||||
import reducers from './reducers'
 | 
			
		||||
 | 
			
		||||
import { loadProfile } from './actions'
 | 
			
		||||
 | 
			
		||||
export const history = createBrowserHistory()
 | 
			
		||||
 | 
			
		||||
@@ -24,6 +24,10 @@ export const store = createStore(
 | 
			
		||||
  )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if (window.localStorage.getItem('authToken') !== null) {
 | 
			
		||||
  store.dispatch(loadProfile())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(
 | 
			
		||||
  <Root store={store} history={history}>
 | 
			
		||||
    <App />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								mpwo_client/src/mpwoApi.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								mpwo_client/src/mpwoApi.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
const apiUrl = `${process.env.REACT_APP_API_URL}`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default class MpwoApi {
 | 
			
		||||
  static login(email, password) {
 | 
			
		||||
    const request = new Request(`${apiUrl}auth/login`, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: new Headers({
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      }),
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        email: email,
 | 
			
		||||
        password: password,
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
    return fetch(request)
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .catch(error => error)
 | 
			
		||||
  }
 | 
			
		||||
  static register(username, email, password) {
 | 
			
		||||
    const request = new Request(`${apiUrl}auth/register`, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: new Headers({
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      }),
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        username: username,
 | 
			
		||||
        email: email,
 | 
			
		||||
        password: password,
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
    return fetch(request)
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .catch(error => error)
 | 
			
		||||
  }
 | 
			
		||||
  static getProfile() {
 | 
			
		||||
    const request = new Request(`${apiUrl}auth/profile`, {
 | 
			
		||||
      method: 'GET',
 | 
			
		||||
      headers: new Headers({
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
        Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
    return fetch(request)
 | 
			
		||||
      .then(response => response.json())
 | 
			
		||||
      .catch(error => error)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,76 @@
 | 
			
		||||
import { combineReducers } from 'redux'
 | 
			
		||||
 | 
			
		||||
import user from './user'
 | 
			
		||||
import initial from './initial'
 | 
			
		||||
 | 
			
		||||
const message = (state = initial.message, action) => {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case 'AUTH_ERROR':
 | 
			
		||||
    case 'PROFILE_ERROR':
 | 
			
		||||
      return action.message
 | 
			
		||||
    case 'LOGOUT':
 | 
			
		||||
      return ''
 | 
			
		||||
    case 'PROFILE_SUCCESS':
 | 
			
		||||
      return ''
 | 
			
		||||
    default:
 | 
			
		||||
      return state
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const user = (state = initial.user, action) => {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case 'AUTH_ERROR':
 | 
			
		||||
    case 'PROFILE_ERROR':
 | 
			
		||||
    case 'LOGOUT':
 | 
			
		||||
      window.localStorage.removeItem('authToken')
 | 
			
		||||
      return initial.user
 | 
			
		||||
    case 'PROFILE_SUCCESS':
 | 
			
		||||
      return {
 | 
			
		||||
        id: action.message.data.id,
 | 
			
		||||
        username: action.message.data.username,
 | 
			
		||||
        email: action.message.data.email,
 | 
			
		||||
        isAdmin: action.message.data.admin,
 | 
			
		||||
        createdAt: action.message.data.created_at,
 | 
			
		||||
        isAuthenticated: true
 | 
			
		||||
      }
 | 
			
		||||
    default:
 | 
			
		||||
      return state
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const formData = (state = initial.formData, action) => {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case 'UPDATE_FORMDATA_EMAIL':
 | 
			
		||||
      return {
 | 
			
		||||
        formData: {
 | 
			
		||||
          ...state.formData,
 | 
			
		||||
          email: action.email
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    case 'UPDATE_FORMDATA_USERNAME':
 | 
			
		||||
      return {
 | 
			
		||||
        formData: {
 | 
			
		||||
          ...state.formData,
 | 
			
		||||
          username: action.username
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    case 'UPDATE_FORMDATA_PASSWORD':
 | 
			
		||||
      return {
 | 
			
		||||
        formData: {
 | 
			
		||||
          ...state.formData,
 | 
			
		||||
          password: action.password
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    case 'PROFILE_SUCCESS':
 | 
			
		||||
      return initial.formData
 | 
			
		||||
    default:
 | 
			
		||||
      return state
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reducers = combineReducers({
 | 
			
		||||
  message,
 | 
			
		||||
  user,
 | 
			
		||||
  formData,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export default reducers
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								mpwo_client/src/reducers/initial.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mpwo_client/src/reducers/initial.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
export default {
 | 
			
		||||
  message: '',
 | 
			
		||||
  user: {
 | 
			
		||||
    id: '',
 | 
			
		||||
    username: '',
 | 
			
		||||
    email: '',
 | 
			
		||||
    createdAt: '',
 | 
			
		||||
    isAdmin: false,
 | 
			
		||||
    isAuthenticated: false,
 | 
			
		||||
  },
 | 
			
		||||
  formData: {
 | 
			
		||||
    formData: {
 | 
			
		||||
    username: '',
 | 
			
		||||
    email: '',
 | 
			
		||||
    password: ''
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
const user = (state = null, action) => {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case 'AUTH_ERROR':
 | 
			
		||||
    case 'PROFILE_ERROR':
 | 
			
		||||
    case 'LOGOUT':
 | 
			
		||||
      window.localStorage.removeItem('authToken')
 | 
			
		||||
      return null
 | 
			
		||||
    case 'PROFILE_SUCCESS':
 | 
			
		||||
      return {
 | 
			
		||||
        id: action.message.data.id,
 | 
			
		||||
        username: action.message.data.username,
 | 
			
		||||
        email: action.message.data.email,
 | 
			
		||||
        isAdmin: action.message.data.is_admin,
 | 
			
		||||
        createdAt: action.message.data.created_at,
 | 
			
		||||
      }
 | 
			
		||||
    default:
 | 
			
		||||
      return state
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default user
 | 
			
		||||
		Reference in New Issue
	
	Block a user