Skip to content

Commit

Permalink
Merge pull request mxstbr#30 from areiterer/master
Browse files Browse the repository at this point in the history
Display error messages with separate component
  • Loading branch information
mxstbr authored Feb 12, 2017
2 parents 8c093f8 + ae29b8b commit 44b3488
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 89 deletions.
18 changes: 18 additions & 0 deletions css/components/_error-message.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.error-wrapper {
display: flex;
justify-content: center;
max-width: calc(100% - 2em);
margin: 0 auto;
margin-bottom: 1em;
}

.error {
display: inline-block;
background-color: $error-color;
color: white;
margin: 0;
padding: 0.5em 1em;
font-size: 0.8em;
font-family: $text-font-stack;
user-select: none;
}
30 changes: 0 additions & 30 deletions css/components/_form.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,36 +73,6 @@
animation: shake 150ms ease-in-out;
}

.form__error-wrapper {
display: none;
justify-content: center;
max-width: calc(100% - 2em);
margin: 0 auto;
margin-bottom: 1em;
}

.form__error {
display: none;
background-color: $error-color;
color: white;
margin: 0;
padding: 0.5em 1em;
font-size: 0.8em;
font-family: $text-font-stack;
user-select: none;
}

.js-form__err .form__error-wrapper {
display: flex;
}

.js-form__err--user-doesnt-exist .form__error--username-not-registered,
.js-form__err--username-exists .form__error--username-taken,
.js-form__err--password-wrong .form__error--wrong-password,
.js-form__err--field-missing .form__error--field-missing {
display: inline-block;
}

@keyframes shake {
0% {
transform: translateX(0);
Expand Down
1 change: 1 addition & 0 deletions css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
@import 'components/_form';
@import 'components/_nav';
@import 'components/_loading-indicator';
@import 'components/_error-message';
102 changes: 53 additions & 49 deletions js/actions/AppActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
*/

import bcrypt from 'bcryptjs';
import { SET_AUTH, CHANGE_FORM, SENDING_REQUEST } from '../constants/AppConstants';
import { SET_AUTH, CHANGE_FORM, SENDING_REQUEST, SET_ERROR_MESSAGE } from '../constants/AppConstants';
import * as errorMessages from '../constants/MessageConstants';
import auth from '../utils/auth';
import genSalt from '../utils/salt';
import { browserHistory } from 'react-router';
Expand All @@ -38,12 +39,9 @@ export function login(username, password) {
return (dispatch) => {
// Show the loading indicator, hide the last error
dispatch(sendingRequest(true));
removeLastFormError();
// If no username or password was specified, throw a field-missing error
if (anyElementsEmpty({ username, password })) {
requestFailed({
type: "field-missing"
});
dispatch(setErrorMessage(errorMessages.FIELD_MISSING));
dispatch(sendingRequest(false));
return;
}
Expand All @@ -53,9 +51,7 @@ export function login(username, password) {
bcrypt.hash(password, salt, (err, hash) => {
// Something wrong while hashing
if (err) {
requestFailed({
type: 'failed'
});
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
return;
}
// Use auth.js to fake a request
Expand All @@ -71,7 +67,17 @@ export function login(username, password) {
password: ""
}));
} else {
requestFailed(err);
switch (err.type) {
case 'user-doesnt-exist':
dispatch(setErrorMessage(errorMessages.USER_NOT_FOUND));
return;
case 'password-wrong':
dispatch(setErrorMessage(errorMessages.WRONG_PASSWORD));
return;
default:
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
return;
}
}
});
});
Expand All @@ -86,11 +92,11 @@ export function logout() {
dispatch(sendingRequest(true));
auth.logout((success, err) => {
if (success === true) {
dispatch(sendingRequest(false));
dispatch(sendingRequest(false))
dispatch(setAuthState(false));
browserHistory.replace(null, '/');
} else {
requestFailed(err);
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
}
});
}
Expand All @@ -105,12 +111,9 @@ export function register(username, password) {
return (dispatch) => {
// Show the loading indicator, hide the last error
dispatch(sendingRequest(true));
removeLastFormError();
// If no username or password was specified, throw a field-missing error
if (anyElementsEmpty({ username, password })) {
requestFailed({
type: "field-missing"
});
dispatch(setErrorMessage(errorMessages.FIELD_MISSING));
dispatch(sendingRequest(false));
return;
}
Expand All @@ -120,9 +123,7 @@ export function register(username, password) {
bcrypt.hash(password, salt, (err, hash) => {
// Something wrong while hashing
if (err) {
requestFailed({
type: 'failed'
});
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
return;
}
// Use auth.js to fake a request
Expand All @@ -138,7 +139,14 @@ export function register(username, password) {
password: ""
}));
} else {
requestFailed(err);
switch (err.type) {
case 'username-exists':
dispatch(setErrorMessage(errorMessages.USERNAME_TAKEN));
return;
default:
dispatch(setErrorMessage(errorMessages.GENERAL_ERROR));
return;
}
}
});
});
Expand Down Expand Up @@ -173,46 +181,42 @@ export function sendingRequest(sending) {
return { type: SENDING_REQUEST, sending };
}


/**
* Forwards the user
* @param {string} location The route the user should be forwarded to
* Sets the errorMessage state, which displays the ErrorMessage component when it is not empty
* @param message
*/
function forwardTo(location) {
console.log('forwardTo(' + location + ')');
browserHistory.push(location);
}
function setErrorMessage(message) {
return (dispatch) => {
dispatch({ type: SET_ERROR_MESSAGE, message });

let lastErrType = "";
const form = document.querySelector('.form-page__form-wrapper');
if (form) {
form.classList.add('js-form__err-animation');
// Remove the animation class after the animation is finished, so it
// can play again on the next error
setTimeout(() => {
form.classList.remove('js-form__err-animation');
}, 150);

/**
* Called when a request failes
* @param {object} err An object containing information about the error
* @param {string} err.type The js-form__err + err.type class will be set on the form
*/
function requestFailed(err) {
// Remove the class of the last error so there can only ever be one
removeLastFormError();
const form = document.querySelector('.form-page__form-wrapper');
// And add the respective classes
form.classList.add('js-form__err');
form.classList.add('js-form__err-animation');
form.classList.add('js-form__err--' + err.type);
lastErrType = err.type;
// Remove the animation class after the animation is finished, so it
// can play again on the next error
setTimeout(() => {
form.classList.remove('js-form__err-animation');
}, 150);
// Remove the error message after 3 seconds
setTimeout(() => {
dispatch({ type: SET_ERROR_MESSAGE, message: '' });
}, 3000);
}
}
}

/**
* Removes the last error from the form
* Forwards the user
* @param {string} location The route the user should be forwarded to
*/
function removeLastFormError() {
const form = document.querySelector('.form-page__form-wrapper');
form.classList.remove('js-form__err--' + lastErrType);
function forwardTo(location) {
console.log('forwardTo(' + location + ')');
browserHistory.push(location);
}


/**
* Checks if any elements of a JSON object are empty
* @param {object} elements The object that should be checked
Expand Down
24 changes: 24 additions & 0 deletions js/components/ErrorMessage.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';

let ErrorMessage = (props) => {
return (
props.errorMessage ?
<div className="error-wrapper">
<p className="error">{props.errorMessage}</p>
</div>
:<div></div>
);
};

ErrorMessage.propTypes = {
errorMessage: PropTypes.string
};

const mapStateToProps = (state) => ({
errorMessage: state.errorMessage
});

ErrorMessage = connect(mapStateToProps)(ErrorMessage);

export default ErrorMessage;
10 changes: 2 additions & 8 deletions js/components/Form.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import React, { Component } from 'react';
import { changeForm } from '../actions/AppActions';
import { History } from 'react-router';
import LoadingButton from './LoadingButton.react';
import ErrorMessage from './ErrorMessage.react';
// Object.assign is not yet fully supported in all browsers, so we fallback to
// a polyfill
const assign = Object.assign || require('object.assign');
Expand All @@ -18,13 +18,7 @@ class LoginForm extends Component {
render() {
return(
<form className="form" onSubmit={this._onSubmit.bind(this)}>
<div className="form__error-wrapper">
<p className="form__error form__error--username-taken">Sorry, but this username is already taken.</p>
<p className="form__error form__error--username-not-registered">This username does not exist.</p>
<p className="form__error form__error--wrong-password">Wrong password.</p>
<p className="form__error form__error--field-missing">Please fill out the entire form.</p>
<p className="form__error form__error--failed">Something went wrong, please try again!</p>
</div>
<ErrorMessage />
<div className="form__field-wrapper">
<input className="form__field-input" type="text" id="username" value={this.props.data.username} placeholder="frank.underwood" onChange={this._changeUsername.bind(this)} autoCorrect="off" autoCapitalize="off" spellCheck="false" />
<label className="form__field-label" htmlFor="username">Username</label>
Expand Down
1 change: 1 addition & 0 deletions js/constants/AppConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
export const CHANGE_FORM = 'CHANGE_FORM';
export const SET_AUTH = 'SET_AUTH';
export const SENDING_REQUEST = 'SENDING_REQUEST';
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
12 changes: 12 additions & 0 deletions js/constants/MessageConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* MessageConstants
* These are the variables that contain the text which is displayed on certain errors
*
* Follow this format:
* export const YOUR_CONSTANT = 'Your message text';
*/
export const FIELD_MISSING = 'Please fill out the entire form.';
export const WRONG_PASSWORD = 'Wrong password.';
export const USER_NOT_FOUND = 'This username does not exist.';
export const USERNAME_TAKEN = 'Sorry, but this username is already taken';
export const GENERAL_ERROR = 'Something went wrong, please try again';
9 changes: 7 additions & 2 deletions js/reducers/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* });
*/

import { CHANGE_FORM, SET_AUTH, SENDING_REQUEST } from '../constants/AppConstants';
import { CHANGE_FORM, SET_AUTH, SENDING_REQUEST, SET_ERROR_MESSAGE } from '../constants/AppConstants';
// Object.assign is not yet fully supported in all browsers, so we fallback to
// a polyfill
const assign = Object.assign || require('object.assign');
Expand All @@ -23,7 +23,8 @@ const initialState = {
password: ''
},
currentlySending: false,
loggedIn: auth.loggedIn()
loggedIn: auth.loggedIn(),
errorMessage: ''
};

// Takes care of changing the application state
Expand All @@ -44,6 +45,10 @@ export function homeReducer(state = initialState, action) {
currentlySending: action.sending
});
break;
case SET_ERROR_MESSAGE:
return assign({}, state, {
errorMessage: action.message
});
default:
return state;
}
Expand Down

0 comments on commit 44b3488

Please sign in to comment.