Блокировка формы во время обработки запроса

This commit is contained in:
SleepWalker 2016-04-02 13:58:54 +03:00
parent 0162d1270f
commit df9283400d
6 changed files with 81 additions and 14 deletions

View File

@ -25,6 +25,7 @@ class PanelTransition extends Component {
// context props // context props
auth: PropTypes.shape({ auth: PropTypes.shape({
error: PropTypes.string, error: PropTypes.string,
isLoading: PropTypes.bool,
login: PropTypes.shape({ login: PropTypes.shape({
login: PropTypes.string, login: PropTypes.string,
password: PropTypes.string password: PropTypes.string
@ -142,7 +143,12 @@ class PanelTransition extends Component {
}; };
return ( return (
<Form id={panelId} onSubmit={this.onFormSubmit} onInvalid={this.onFormInvalid}> <Form
id={panelId}
onSubmit={this.onFormSubmit}
onInvalid={this.onFormInvalid}
isLoading={this.props.auth.isLoading}
>
<Panel> <Panel>
<PanelHeader> <PanelHeader>
{panels.map((config) => this.getHeader(config))} {panels.map((config) => this.getHeader(config))}

View File

@ -8,7 +8,7 @@ export function login({login = '', password = '', rememberMe = false}) {
const LOGIN_REQUIRED = 'error.login_required'; const LOGIN_REQUIRED = 'error.login_required';
const ACTIVATION_REQUIRED = 'error.account_not_activated'; const ACTIVATION_REQUIRED = 'error.account_not_activated';
return (dispatch) => return wrapInLoader((dispatch) =>
request.post( request.post(
'/api/authentication/login', '/api/authentication/login',
{login, password, rememberMe} {login, password, rememberMe}
@ -43,7 +43,7 @@ export function login({login = '', password = '', rememberMe = false}) {
// TODO: log unexpected errors // TODO: log unexpected errors
}) })
; );
} }
export function changePassword({ export function changePassword({
@ -51,7 +51,7 @@ export function changePassword({
newPassword = '', newPassword = '',
newRePassword = '' newRePassword = ''
}) { }) {
return (dispatch) => return wrapInLoader((dispatch) =>
dispatch(changeUserPassword({password, newPassword, newRePassword})) dispatch(changeUserPassword({password, newPassword, newRePassword}))
.catch((resp) => { .catch((resp) => {
if (resp.errors) { if (resp.errors) {
@ -62,7 +62,7 @@ export function changePassword({
// TODO: log unexpected errors // TODO: log unexpected errors
}) })
; );
} }
export function register({ export function register({
@ -72,7 +72,7 @@ export function register({
rePassword = '', rePassword = '',
rulesAgreement = false rulesAgreement = false
}) { }) {
return (dispatch) => return wrapInLoader((dispatch) =>
request.post( request.post(
'/api/signup', '/api/signup',
{email, username, password, rePassword, rulesAgreement} {email, username, password, rePassword, rulesAgreement}
@ -94,11 +94,11 @@ export function register({
// TODO: log unexpected errors // TODO: log unexpected errors
}) })
; );
} }
export function activate({key = ''}) { export function activate({key = ''}) {
return (dispatch) => return wrapInLoader((dispatch) =>
request.post( request.post(
'/api/signup/confirm', '/api/signup/confirm',
{key} {key}
@ -118,7 +118,7 @@ export function activate({key = ''}) {
// TODO: log unexpected errors // TODO: log unexpected errors
}) })
; );
} }
export const ERROR = 'error'; export const ERROR = 'error';
@ -141,7 +141,7 @@ export function logout() {
// TODO: move to oAuth actions? // TODO: move to oAuth actions?
// test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session // test request: /oauth?client_id=ely&redirect_uri=http%3A%2F%2Fely.by&response_type=code&scope=minecraft_server_session
export function oAuthValidate(oauth) { export function oAuthValidate(oauth) {
return (dispatch) => return wrapInLoader((dispatch) =>
request.get( request.get(
'/api/oauth/validate', '/api/oauth/validate',
getOAuthRequest(oauth) getOAuthRequest(oauth)
@ -156,11 +156,12 @@ export function oAuthValidate(oauth) {
if (resp.statusCode === 401 && resp.error === 'accept_required') { if (resp.statusCode === 401 && resp.error === 'accept_required') {
alert('Accept required.'); alert('Accept required.');
} }
}); })
);
} }
export function oAuthComplete(params = {}) { export function oAuthComplete(params = {}) {
return (dispatch, getState) => { return wrapInLoader((dispatch, getState) => {
const oauth = getState().auth.oauth; const oauth = getState().auth.oauth;
const query = request.buildQuery(getOAuthRequest(oauth)); const query = request.buildQuery(getOAuthRequest(oauth));
@ -205,7 +206,7 @@ export function oAuthComplete(params = {}) {
return resp; return resp;
}); });
}; });
} }
function getOAuthRequest(oauth) { function getOAuthRequest(oauth) {
@ -283,3 +284,29 @@ export function setScopes(scopes) {
payload: scopes payload: scopes
}; };
} }
export const SET_LOADING_STATE = 'set_loading_state';
export function setLoadingState(isLoading) {
return {
type: SET_LOADING_STATE,
payload: isLoading
};
}
function wrapInLoader(fn) {
return (dispatch, getState) => {
dispatch(setLoadingState(true));
const endLoading = () => dispatch(setLoadingState(false));
return Reflect.apply(fn, null, [dispatch, getState]).then((resp) => {
endLoading();
return resp;
}, (resp) => {
endLoading();
return Promise.reject(resp);
});
};
}

View File

@ -1,9 +1,10 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { ERROR, SET_CLIENT, SET_OAUTH, SET_OAUTH_RESULT, SET_SCOPES } from './actions'; import { ERROR, SET_CLIENT, SET_OAUTH, SET_OAUTH_RESULT, SET_SCOPES, SET_LOADING_STATE } from './actions';
export default combineReducers({ export default combineReducers({
error, error,
isLoading,
client, client,
oauth, oauth,
scopes scopes
@ -25,6 +26,20 @@ function error(
} }
} }
function isLoading(
state = false,
{type, payload = null}
) {
switch (type) {
case SET_LOADING_STATE:
return !!payload;
default:
return state;
}
}
function client( function client(
state = null, state = null,
{type, payload = {}} {type, payload = {}}

View File

@ -102,6 +102,7 @@ export class Form extends Component {
static propTypes = { static propTypes = {
id: PropTypes.string, // and id, that uniquely identifies form contents id: PropTypes.string, // and id, that uniquely identifies form contents
isLoading: PropTypes.bool,
onSubmit: PropTypes.func, onSubmit: PropTypes.func,
onInvalid: PropTypes.func, onInvalid: PropTypes.func,
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
@ -112,6 +113,7 @@ export class Form extends Component {
static defaultProps = { static defaultProps = {
id: 'default', id: 'default',
isLoading: false,
onSubmit() {}, onSubmit() {},
onInvalid() {} onInvalid() {}
}; };
@ -129,11 +131,14 @@ export class Form extends Component {
} }
render() { render() {
const {isLoading} = this.props;
return ( return (
<form <form
className={classNames( className={classNames(
styles.form, styles.form,
{ {
[styles.isFormLoading]: isLoading,
[styles.formTouched]: this.state.isTouched [styles.formTouched]: this.state.isTouched
} }
)} )}

View File

@ -234,3 +234,17 @@
border-color: $red; border-color: $red;
} }
} }
.isFormLoading {
// TODO: надо бы разнести from и input на отдельные модули,
// так как в текущем контексте isLoading немного не логичен,
// пришлось юзать isFormLoading
* {
pointer-events: none;
}
button {
background-image: url('images/loader_button.gif');
background-position: center center;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B