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
 | 
					                      ).handlers = logging.getLogger('werkzeug').handlers
 | 
				
			||||||
    logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
 | 
					    logging.getLogger('sqlalchemy.orm').setLevel(logging.WARNING)
 | 
				
			||||||
    appLog.setLevel(logging.DEBUG)
 | 
					    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(
 | 
					            self.assertTrue(
 | 
				
			||||||
                data['message'] == 'Invalid token. Please log in again.')
 | 
					                data['message'] == 'Invalid token. Please log in again.')
 | 
				
			||||||
            self.assertEqual(response.status_code, 401)
 | 
					            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 mpwo_api import appLog, bcrypt, db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import User
 | 
					from .models import User
 | 
				
			||||||
 | 
					from .utils import authenticate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
auth_blueprint = Blueprint('auth', __name__)
 | 
					auth_blueprint = Blueprint('auth', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,7 +13,9 @@ auth_blueprint = Blueprint('auth', __name__)
 | 
				
			|||||||
def register_user():
 | 
					def register_user():
 | 
				
			||||||
    # get post data
 | 
					    # get post data
 | 
				
			||||||
    post_data = request.get_json()
 | 
					    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 = {
 | 
					        response_object = {
 | 
				
			||||||
            'status': 'error',
 | 
					            'status': 'error',
 | 
				
			||||||
            'message': 'Invalid payload.'
 | 
					            'message': 'Invalid payload.'
 | 
				
			||||||
@@ -52,9 +55,10 @@ def register_user():
 | 
				
			|||||||
    except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
 | 
					    except (exc.IntegrityError, exc.OperationalError, ValueError) as e:
 | 
				
			||||||
        db.session.rollback()
 | 
					        db.session.rollback()
 | 
				
			||||||
        appLog.error(e)
 | 
					        appLog.error(e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response_object = {
 | 
					        response_object = {
 | 
				
			||||||
            'status': 'error',
 | 
					            'status': 'error',
 | 
				
			||||||
            'message': 'Invalid payload.'
 | 
					            'message': 'Error. Please try again or contact the administrator.'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return jsonify(response_object), 400
 | 
					        return jsonify(response_object), 400
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,19 +99,20 @@ def login_user():
 | 
				
			|||||||
        appLog.error(e)
 | 
					        appLog.error(e)
 | 
				
			||||||
        response_object = {
 | 
					        response_object = {
 | 
				
			||||||
            'status': 'error',
 | 
					            'status': 'error',
 | 
				
			||||||
            'message': 'Try again'
 | 
					            'message': 'Error. Please try again or contact the administrator.'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return jsonify(response_object), 500
 | 
					        return jsonify(response_object), 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@auth_blueprint.route('/auth/logout', methods=['GET'])
 | 
					@auth_blueprint.route('/auth/logout', methods=['GET'])
 | 
				
			||||||
def logout_user():
 | 
					@authenticate
 | 
				
			||||||
 | 
					def logout_user(user_id):
 | 
				
			||||||
    # get auth token
 | 
					    # get auth token
 | 
				
			||||||
    auth_header = request.headers.get('Authorization')
 | 
					    auth_header = request.headers.get('Authorization')
 | 
				
			||||||
    if auth_header:
 | 
					    if auth_header:
 | 
				
			||||||
        auth_token = auth_header.split(" ")[1]
 | 
					        auth_token = auth_header.split(" ")[1]
 | 
				
			||||||
        resp = User.decode_auth_token(auth_token)
 | 
					        resp = User.decode_auth_token(auth_token)
 | 
				
			||||||
        if not isinstance(resp, str):
 | 
					        if not isinstance(user_id, str):
 | 
				
			||||||
            response_object = {
 | 
					            response_object = {
 | 
				
			||||||
                'status': 'success',
 | 
					                'status': 'success',
 | 
				
			||||||
                'message': 'Successfully logged out.'
 | 
					                'message': 'Successfully logged out.'
 | 
				
			||||||
@@ -125,3 +130,20 @@ def logout_user():
 | 
				
			|||||||
            'message': 'Provide a valid auth token.'
 | 
					            'message': 'Provide a valid auth token.'
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return jsonify(response_object), 403
 | 
					        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 React from 'react'
 | 
				
			||||||
 | 
					import { Route, Switch } from 'react-router-dom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import './App.css'
 | 
					import './App.css'
 | 
				
			||||||
 | 
					import Logout from './Logout'
 | 
				
			||||||
import NavBar from './NavBar'
 | 
					import NavBar from './NavBar'
 | 
				
			||||||
 | 
					import UserForm from './User/UserForm'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class App extends React.Component {
 | 
					export default class App extends React.Component {
 | 
				
			||||||
  constructor(props) {
 | 
					  constructor(props) {
 | 
				
			||||||
@@ -13,9 +16,37 @@ export default class App extends React.Component {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="App">
 | 
					      <div className="App">
 | 
				
			||||||
        <NavBar />
 | 
					        <NavBar />
 | 
				
			||||||
        <p className="App-body">
 | 
					        <div className="container">
 | 
				
			||||||
          App in progress
 | 
					          <div className="row">
 | 
				
			||||||
        </p>
 | 
					            <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>
 | 
					      </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 React from 'react'
 | 
				
			||||||
 | 
					import { connect } from 'react-redux'
 | 
				
			||||||
import { Link } from 'react-router-dom'
 | 
					import { Link } from 'react-router-dom'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NavBar extends React.Component {
 | 
					function NavBar (props) {
 | 
				
			||||||
  constructor(props) {
 | 
					  return (
 | 
				
			||||||
    super(props)
 | 
					 | 
				
			||||||
    this.props = props
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render() {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
    <header>
 | 
					    <header>
 | 
				
			||||||
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
 | 
					      <nav className="navbar navbar-expand-lg navbar-light bg-light">
 | 
				
			||||||
        <span className="navbar-brand">mpwo</span>
 | 
					        <span className="navbar-brand">mpwo</span>
 | 
				
			||||||
@@ -36,10 +31,50 @@ export default class NavBar extends React.Component {
 | 
				
			|||||||
                Home
 | 
					                Home
 | 
				
			||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
            </li>
 | 
					            </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>
 | 
					          </ul>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </nav>
 | 
					      </nav>
 | 
				
			||||||
    </header>
 | 
					    </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 Root from './components/Root'
 | 
				
			||||||
import registerServiceWorker from './registerServiceWorker'
 | 
					import registerServiceWorker from './registerServiceWorker'
 | 
				
			||||||
import reducers from './reducers'
 | 
					import reducers from './reducers'
 | 
				
			||||||
 | 
					import { loadProfile } from './actions'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const history = createBrowserHistory()
 | 
					export const history = createBrowserHistory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,6 +24,10 @@ export const store = createStore(
 | 
				
			|||||||
  )
 | 
					  )
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (window.localStorage.getItem('authToken') !== null) {
 | 
				
			||||||
 | 
					  store.dispatch(loadProfile())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ReactDOM.render(
 | 
					ReactDOM.render(
 | 
				
			||||||
  <Root store={store} history={history}>
 | 
					  <Root store={store} history={history}>
 | 
				
			||||||
    <App />
 | 
					    <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 { 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({
 | 
					const reducers = combineReducers({
 | 
				
			||||||
 | 
					  message,
 | 
				
			||||||
  user,
 | 
					  user,
 | 
				
			||||||
 | 
					  formData,
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default reducers
 | 
					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