Merge branch 'develop' into forgot_password_captcha

# Conflicts:
#	api/models/authentication/ForgotPasswordForm.php
This commit is contained in:
ErickSkrauch 2017-04-25 21:00:01 +03:00
commit 6dd628cc99
11 changed files with 1348 additions and 933 deletions

2130
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
"start": "npm run clean && npm run build:dll && webpack-dev-server --progress --colors", "start": "npm run clean && npm run build:dll && webpack-dev-server --progress --colors",
"clean": "rm -rf dist/", "clean": "rm -rf dist/",
"up": "npm update", "up": "npm update",
"lockDeps": "npm shrinkwrap --dev",
"test": "npm run build:dll && karma start ./karma.conf.js", "test": "npm run build:dll && karma start ./karma.conf.js",
"lint": "eslint ./src", "lint": "eslint ./src",
"i18n:collect": "babel-node ./scripts/i18n-collect.js", "i18n:collect": "babel-node ./scripts/i18n-collect.js",
@ -33,7 +34,7 @@
"react": "^15.0.0", "react": "^15.0.0",
"react-addons-css-transition-group": "^15.1.0", "react-addons-css-transition-group": "^15.1.0",
"react-dom": "^15.0.0", "react-dom": "^15.0.0",
"react-helmet": "^4.0.0", "react-helmet": "^5.0.0",
"react-intl": "^2.0.0", "react-intl": "^2.0.0",
"react-motion": "^0.4.0", "react-motion": "^0.4.0",
"react-redux": "^5.0.0", "react-redux": "^5.0.0",
@ -61,14 +62,14 @@
"babel-preset-stage-0": "^6.3.13", "babel-preset-stage-0": "^6.3.13",
"babel-runtime": "^6.0.0", "babel-runtime": "^6.0.0",
"bundle-loader": "^0.5.4", "bundle-loader": "^0.5.4",
"circular-dependency-plugin": "^2.0.0", "circular-dependency-plugin": "^3.0.0",
"css-loader": "^0.26.1", "css-loader": "^0.28.0",
"enzyme": "^2.2.0", "enzyme": "^2.2.0",
"eslint": "^3.1.1", "eslint": "^3.1.1",
"eslint-plugin-react": "^6.0.0", "eslint-plugin-react": "^6.0.0",
"exports-loader": "^0.6.3", "exports-loader": "^0.6.3",
"extract-text-webpack-plugin": "^1.0.0", "extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.10.0", "file-loader": "^0.11.0",
"fontgen-loader": "^0.2.1", "fontgen-loader": "^0.2.1",
"html-loader": "^0.4.3", "html-loader": "^0.4.3",
"html-webpack-plugin": "^2.0.0", "html-webpack-plugin": "^2.0.0",
@ -76,13 +77,13 @@
"jsdom": "^9.8.3", "jsdom": "^9.8.3",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"karma": "^1.1.0", "karma": "^1.1.0",
"karma-jsdom-launcher": "^5.0.0", "karma-jsdom-launcher": "^6.0.0",
"karma-mocha": "^1.0.0", "karma-mocha": "^1.0.0",
"karma-nyan-reporter": "^0.2.3", "karma-nyan-reporter": "^0.2.3",
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "*", "karma-sourcemap-loader": "*",
"karma-webpack": "^2.0.0", "karma-webpack": "^2.0.0",
"loader-utils": "^0.2.15", "loader-utils": "^1.0.0",
"mocha": "^3.0.2", "mocha": "^3.0.2",
"node-sass": "^4.0.0", "node-sass": "^4.0.0",
"postcss-import": "^9.0.0", "postcss-import": "^9.0.0",
@ -92,13 +93,14 @@
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react-addons-perf": "^15.3.0", "react-addons-perf": "^15.3.0",
"react-addons-test-utils": "^15.0.2", "react-addons-test-utils": "^15.0.2",
"react-test-renderer": "^15.5.4",
"redux-devtools": "^3.3.1", "redux-devtools": "^3.3.1",
"redux-devtools-dock-monitor": "^1.1.1", "redux-devtools-dock-monitor": "^1.1.1",
"redux-devtools-log-monitor": "^1.0.11", "redux-devtools-log-monitor": "^1.0.11",
"sass-loader": "^4.0.0", "sass-loader": "^4.0.0",
"scripts": "file:./scripts", "scripts": "file:./scripts",
"sinon": "^1.15.3", "sinon": "^2.0.0",
"style-loader": "^0.13.0", "style-loader": "^0.16.0",
"unexpected": "^10.15.0", "unexpected": "^10.15.0",
"unexpected-sinon": "^10.5.1", "unexpected-sinon": "^10.5.1",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",

View File

@ -46,7 +46,10 @@ export default class BaseAuthBody extends Component {
}); });
bindField = this.form.bindField.bind(this.form); bindField = this.form.bindField.bind(this.form);
serialize = this.form.serialize.bind(this.form);
serialize() {
return this.form.serialize();
}
autoFocus() { autoFocus() {
const fieldId = this.autoFocusField; const fieldId = this.autoFocusField;

View File

@ -15,14 +15,13 @@ export default class ForgotPasswordBody extends BaseAuthBody {
static hasGoBack = true; static hasGoBack = true;
state = { state = {
isLoginEdit: !(this.context.user.email || this.context.user.username) isLoginEdit: !this.getLogin()
}; };
autoFocusField = this.state.isLoginEdit ? 'email' : null; autoFocusField = this.state.isLoginEdit ? 'login' : null;
render() { render() {
const { user } = this.context; const login = this.getLogin();
const login = user.email || user.username || '';
const isLoginEditShown = this.state.isLoginEdit; const isLoginEditShown = this.state.isLoginEdit;
return ( return (
@ -38,7 +37,7 @@ export default class ForgotPasswordBody extends BaseAuthBody {
<p className={styles.descriptionText}> <p className={styles.descriptionText}>
<Message {...messages.specifyEmail} /> <Message {...messages.specifyEmail} />
</p> </p>
<Input {...this.bindField('email')} <Input {...this.bindField('login')}
icon="envelope" icon="envelope"
color="lightViolet" color="lightViolet"
required required
@ -63,6 +62,22 @@ export default class ForgotPasswordBody extends BaseAuthBody {
); );
} }
serialize() {
const data = super.serialize();
if (!data.login) {
data.login = this.getLogin();
}
return data;
}
getLogin() {
const { user, auth } = this.context;
return auth.login || user.username || user.email || '';
}
onClickEdit = () => { onClickEdit = () => {
this.setState({ this.setState({
isLoginEdit: true isLoginEdit: true
@ -70,6 +85,6 @@ export default class ForgotPasswordBody extends BaseAuthBody {
this.context.requestRedraw(); this.context.requestRedraw();
// TODO: requestRedraw должен возвращать promise, по которому нужно ставить фокус на поле // TODO: requestRedraw должен возвращать promise, по которому нужно ставить фокус на поле
// иначе же, если фокус ставить сразу, то форма скачет // иначе же, если фокус ставить сразу, то форма скачет
setTimeout(() => {this.form.focus('email');}, 300); setTimeout(() => {this.form.focus('login');}, 300);
}; };
} }

View File

@ -64,7 +64,7 @@ export default class LoggedInPanel extends Component {
onBodyClick = createOnOutsideComponentClickHandler( onBodyClick = createOnOutsideComponentClickHandler(
() => ReactDOM.findDOMNode(this), () => ReactDOM.findDOMNode(this),
() => this.state.isAccountSwitcherActive, () => this.state.isAccountSwitcherActive && this._isMounted,
() => this.toggleAccountSwitcher() () => this.toggleAccountSwitcher()
); );
} }
@ -78,6 +78,7 @@ export default class LoggedInPanel extends Component {
* @param {function} getEl - the function, that returns reference to container el * @param {function} getEl - the function, that returns reference to container el
* @param {function} isActive - whether the component is active and callback may be called * @param {function} isActive - whether the component is active and callback may be called
* @param {function} callback - the callback to call, when there was a click outside el * @param {function} callback - the callback to call, when there was a click outside el
*
* @return {function} * @return {function}
*/ */
function createOnOutsideComponentClickHandler(getEl, isActive, callback) { function createOnOutsideComponentClickHandler(getEl, isActive, callback) {

View File

@ -10,8 +10,11 @@ export default class ForgotPasswordState extends AbstractState {
} }
resolve(context, payload = {}) { resolve(context, payload = {}) {
context.run('forgotPassword', {login: payload.email || this.getLogin(context)}) context.run('forgotPassword', payload)
.then(() => context.setState(new RecoverPasswordState())) .then(() => {
context.run('setLogin', payload.login);
context.setState(new RecoverPasswordState());
})
.catch((err = {}) => .catch((err = {}) =>
err.errors || logger.warn('Error requesting password recoverage', err) err.errors || logger.warn('Error requesting password recoverage', err)
); );
@ -24,10 +27,4 @@ export default class ForgotPasswordState extends AbstractState {
reject(context) { reject(context) {
context.setState(new RecoverPasswordState()); context.setState(new RecoverPasswordState());
} }
getLogin(context) {
const {auth} = context.getState();
return auth.login;
}
} }

View File

@ -6,16 +6,10 @@ import CompleteState from './CompleteState';
export default class RecoverPasswordState extends AbstractState { export default class RecoverPasswordState extends AbstractState {
enter(context) { enter(context) {
const {auth} = context.getState(); const url = context.getRequest().path.includes('/recover-password')
? context.getRequest().path
if (auth.login) { : '/recover-password';
const url = context.getRequest().path.includes('/recover-password') context.navigate(url);
? context.getRequest().path
: '/recover-password';
context.navigate(url);
} else {
context.setState(new CompleteState());
}
} }
resolve(context, payload) { resolve(context, payload) {

View File

@ -38,7 +38,8 @@ const logger = {
} }
logger.info(`Unhandled rejection${message}`, { logger.info(`Unhandled rejection${message}`, {
error error,
event
}); });
}); });
} }

View File

@ -32,13 +32,8 @@ describe('ForgotPasswordState', () => {
}); });
describe('#resolve', () => { describe('#resolve', () => {
it('should call forgotPassword with login', () => { it('should call forgotPassword with email from payload', () => {
const expectedLogin = 'foo@bar.com'; const expectedLogin = 'foo@bar.com';
context.getState.returns({
auth: {
login: expectedLogin
}
});
expectRun( expectRun(
mock, mock,
@ -48,41 +43,30 @@ describe('ForgotPasswordState', () => {
}) })
).returns(Promise.resolve()); ).returns(Promise.resolve());
state.resolve(context, {}); state.resolve(context, {login: expectedLogin});
});
it('should call forgotPassword with email from payload if any', () => {
const expectedLogin = 'foo@bar.com';
context.getState.returns({
auth: {
login: 'should.not@be.used'
}
});
expectRun(
mock,
'forgotPassword',
sinon.match({
login: expectedLogin
})
).returns(Promise.resolve());
state.resolve(context, {email: expectedLogin});
}); });
it('should transition to recoverPassword state on success', () => { it('should transition to recoverPassword state on success', () => {
const promise = Promise.resolve(); const promise = Promise.resolve();
const expectedLogin = 'foo@bar.com'; const expectedLogin = 'foo@bar.com';
context.getState.returns({
auth: {
login: expectedLogin
}
});
mock.expects('run').returns(promise); mock.expects('run').twice().returns(promise);
expectState(mock, RecoverPasswordState); expectState(mock, RecoverPasswordState);
state.resolve(context, {}); state.resolve(context, {login: expectedLogin});
return promise;
});
it('should run setLogin on success', () => {
const promise = Promise.resolve();
const expectedLogin = 'foo@bar.com';
mock.expects('run').withArgs('forgotPassword').returns(promise);
expectState(mock, RecoverPasswordState);
mock.expects('run').withArgs('setLogin', expectedLogin);
state.resolve(context, {login: expectedLogin});
return promise; return promise;
}); });

View File

@ -26,9 +26,6 @@ describe('RecoverPasswordState', () => {
describe('#enter', () => { describe('#enter', () => {
it('should navigate to /recover-password', () => { it('should navigate to /recover-password', () => {
const expectedPath = '/recover-password'; const expectedPath = '/recover-password';
context.getState.returns({
auth: {login: 'foo'}
});
context.getRequest.returns({path: expectedPath}); context.getRequest.returns({path: expectedPath});
@ -39,9 +36,6 @@ describe('RecoverPasswordState', () => {
it('should navigate to /recover-password/key', () => { it('should navigate to /recover-password/key', () => {
const expectedPath = '/recover-password/sasx5AS4d61'; const expectedPath = '/recover-password/sasx5AS4d61';
context.getState.returns({
auth: {login: 'foo'}
});
context.getRequest.returns({path: expectedPath}); context.getRequest.returns({path: expectedPath});
@ -49,16 +43,6 @@ describe('RecoverPasswordState', () => {
state.enter(context); state.enter(context);
}); });
it('should transition to complete if not guest', () => {
context.getState.returns({
auth: {}
});
expectState(mock, CompleteState);
state.enter(context);
});
}); });
describe('#resolve', () => { describe('#resolve', () => {

View File

@ -5,6 +5,6 @@
"keywords": [], "keywords": [],
"author": "", "author": "",
"dependencies": { "dependencies": {
"loader-utils": "^0.2.12" "loader-utils": "^1.0.0"
} }
} }