Merged in frontend-recover-password (pull request #30)

Frontend recover password
This commit is contained in:
Ivan Diaz 2016-08-02 15:09:46 -03:00
commit 34239afb11
26 changed files with 636 additions and 84 deletions

View File

@ -1,9 +1,11 @@
import Reflux from 'reflux';
let UserActions = Reflux.createActions([
const UserActions = Reflux.createActions([
'checkLoginStatus',
'login',
'logout'
'logout',
'sendRecover',
'recoverPassword'
]);
export default UserActions;

View File

@ -3,10 +3,11 @@ import Reflux from 'reflux';
import CommonStore from 'stores/common-store';
let App = React.createClass({
const App = React.createClass({
contextTypes: {
router: React.PropTypes.object
router: React.PropTypes.object,
location: React.PropTypes.object
},
mixins: [Reflux.listenTo(CommonStore, 'onCommonStoreChanged')],
@ -21,7 +22,7 @@ let App = React.createClass({
onCommonStoreChanged(change) {
let handle = {
'i18n': () => {this.forceUpdate()},
'i18n': () => {this.context.router.push(this.context.location.pathname)},
'logged': () => {this.context.router.push('/app/dashboard')},
'loggedOut': () => {this.context.router.push('/app')}
};

View File

@ -7,6 +7,7 @@ const DemoPage = require('app/demo/components-demo-page');
const MainLayout = require('app/main/main-layout');
const MainHomePage = require('app/main/main-home/main-home-page');
const MainSignUpPage = require('app/main/main-signup/main-signup-page');
const MainRecoverPasswordPage = require('app/main/main-recover-password/main-recover-password-page');
const DashboardLayout = require('app/main/dashboard/dashboard-layout');
@ -25,6 +26,7 @@ export default (
<Route path='/app' component={MainLayout}>
<IndexRoute component={MainHomePage} />
<Route path='signup' component={MainSignUpPage}/>
<Route path='recover-password' component={MainRecoverPasswordPage}/>
<Route path='dashboard' component={DashboardLayout}>
<IndexRoute component={DashboardListTicketsPage} />
<Route path='articles' component={DashboardListArticlesPage}/>

View File

@ -16,6 +16,10 @@ describe('App component', function () {
app.context = {
router: {
push: stub()
},
location: {
pathname: 'MOCK_PATH'
}
};
@ -23,9 +27,10 @@ describe('App component', function () {
});
it('should update with i18n', function () {
app.context.router.push.reset();
app.forceUpdate.reset();
app.onCommonStoreChanged('i18n');
expect(app.forceUpdate).to.have.been.called;
expect(app.context.router.push).to.have.been.calledWith('MOCK_PATH');
});
it('should redirect when logged in', function () {

View File

@ -62,7 +62,7 @@ describe('Login/Recover Widget', function () {
it('should add error if login fails', function () {
component.refs.loginForm.refs.password.focus.reset();
component.onUserStoreChanged('LOGIN_FAIL');
expect(loginForm.props.errors).to.deep.equal({password: 'Password does not match'});
expect(loginForm.props.errors).to.deep.equal({password: 'Invalid password'});
expect(component.refs.loginForm.refs.password.focus).to.have.been.called;
});

View File

@ -1,19 +1,20 @@
const React = require('react');
const ReactDOM = require('react-dom');
const Reflux = require('reflux');
const _ = require('lodash');
const classNames = require('classnames');
import React from 'react';
import ReactDOM from 'react-dom';
import Reflux from 'reflux';
import classNames from 'classnames';
const UserActions = require('actions/user-actions');
const UserStore = require('stores/user-store');
const focus = require('lib-core/focus');
import UserActions from 'actions/user-actions';
import UserStore from 'stores/user-store';
import focus from 'lib-core/focus';
import i18n from 'lib-app/i18n';
const Button = require('core-components/button');
const Form = require('core-components/form');
const Input = require('core-components/input');
const Checkbox = require('core-components/checkbox');
const Widget = require('core-components/widget');
const WidgetTransition = require('core-components/widget-transition');
import Button from 'core-components/button';
import Form from 'core-components/form';
import Input from 'core-components/input';
import Checkbox from 'core-components/checkbox';
import Widget from 'core-components/widget';
import WidgetTransition from 'core-components/widget-transition';
import Message from 'core-components/message';
let MainHomePageLoginWidget = React.createClass({
@ -22,13 +23,15 @@ let MainHomePageLoginWidget = React.createClass({
getInitialState() {
return {
sideToShow: 'front',
loginFormErrors: {}
loginFormErrors: {},
recoverFormErrors: {},
recoverSent: false
};
},
render() {
return (
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('login-widget--container', this.props.className)}>
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('login-widget__container', this.props.className)}>
{this.renderLogin()}
{this.renderPasswordRecovery()}
</WidgetTransition>
@ -37,19 +40,19 @@ let MainHomePageLoginWidget = React.createClass({
renderLogin() {
return (
<Widget className="main-home-page--widget" title="Login" ref="loginWidget">
<Form className="login-widget--form" ref="loginForm" onSubmit={this.handleLoginFormSubmit} errors={this.state.loginFormErrors} onValidateErrors={this.handleLoginFormErrorsValidation}>
<div className="login-widget--inputs">
<Input placeholder="email" name="email" className="login-widget--input" validation="EMAIL" required/>
<Input placeholder="password" name="password" className="login-widget--input" password required/>
<Checkbox name="remember" label="Remember Me" className="login-widget--input"/>
<Widget className="main-home-page__widget" title="Login" ref="loginWidget">
<Form className="login-widget__form" ref="loginForm" onSubmit={this.handleLoginFormSubmit} errors={this.state.loginFormErrors} onValidateErrors={this.handleLoginFormErrorsValidation}>
<div className="login-widget__inputs">
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
<Input placeholder="password" name="password" className="login-widget__input" password required/>
<Checkbox name="remember" label="Remember Me" className="login-widget__input"/>
</div>
<div className="login-widget--submit-button">
<div className="login-widget__submit-button">
<Button type="primary">LOG IN</Button>
</div>
</Form>
<Button className="login-widget--forgot-password" type="link" onClick={this.handleForgotPasswordClick} onMouseDown={(event) => {event.preventDefault()}}>
{'Forgot your password?'}
<Button className="login-widget__forgot-password" type="link" onClick={this.handleForgotPasswordClick} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('FORGOT_PASSWORD')}
</Button>
</Widget>
);
@ -57,28 +60,43 @@ let MainHomePageLoginWidget = React.createClass({
renderPasswordRecovery() {
return (
<Widget className="main-home-page--widget main-home-page--password-widget" title="Password Recovery" ref="recoverWidget">
<Form className="login-widget--form" onSubmit={this.handleForgotPasswordSubmit}>
<div className="login-widget--inputs">
<Input placeholder="email" name="email" className="login-widget--input" validation="EMAIL"/>
<Widget className="main-home-page__widget login-widget_password" title={i18n('RECOVER_PASSWORD')} ref="recoverWidget">
<Form className="login-widget__form" ref="recoverForm" onSubmit={this.handleForgotPasswordSubmit} errors={this.state.recoverFormErrors} onValidateErrors={this.handleRecoverFormErrorsValidation}>
<div className="login-widget__inputs">
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
</div>
<div className="login-widget--submit-button">
<Button type="primary">Recover my password</Button>
<div className="login-widget__submit-button">
<Button type="primary">{i18n('RECOVER_PASSWORD')}</Button>
</div>
</Form>
<Button className="login-widget--forgot-password" type="link" onClick={this.handleBackToLoginClick} onMouseDown={(event) => {event.preventDefault()}}>
{'Back to login form'}
<Button className="login-widget__forgot-password" type="link" onClick={this.handleBackToLoginClick} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('BACK_LOGIN_FORM')}
</Button>
{this.renderRecoverStatus()}
</Widget>
);
},
renderRecoverStatus() {
let status = null;
if (this.state.recoverSent) {
status = (
<Message className="login-widget__message" type="info" leftAligned>
{i18n('RECOVER_SENT')}
</Message>
);
}
return status;
},
handleLoginFormSubmit(formState) {
UserActions.login(formState);
},
handleForgotPasswordSubmit() {
handleForgotPasswordSubmit(formState) {
UserActions.sendRecover(formState);
},
handleLoginFormErrorsValidation(errors) {
@ -87,6 +105,12 @@ let MainHomePageLoginWidget = React.createClass({
});
},
handleRecoverFormErrorsValidation(errors) {
this.setState({
recoverFormErrors: errors
});
},
handleForgotPasswordClick() {
this.setState({
sideToShow: 'back'
@ -103,12 +127,29 @@ let MainHomePageLoginWidget = React.createClass({
if (event === 'LOGIN_FAIL') {
this.setState({
loginFormErrors: {
password: 'Password does not match'
password: i18n('ERROR_PASSWORD')
}
}, function () {
this.refs.loginForm.refs.password.focus()
this.refs.loginForm.refs.password.focus();
}.bind(this));
}
if (event === 'SEND_RECOVER_FAIL') {
this.setState({
recoverFormErrors: {
email: i18n('EMAIL_NOT_EXIST')
}
}, function () {
this.refs.recoverForm.refs.email.focus();
}.bind(this));
}
if (event === 'SEND_RECOVER_SUCCESS') {
this.setState({
recoverSent: true
});
}
},
moveFocusToCurrentSide() {

View File

@ -1,20 +1,28 @@
.login-widget {
&--container {
&__container {
height: 361px;
}
&--input {
&__input {
margin: 10px 0;
}
&--inputs {
&__inputs {
display: inline-block;
margin: 0 auto 20px;
text-align: left;
}
&--forgot-password {
&__forgot-password {
margin-top: 20px;
}
&__message {
margin-top: 18px;
}
&_password {
width: 324px;
}
}

View File

@ -1,10 +1,10 @@
const React = require( 'react');
import React from 'react';
const MainHomePageLoginWidget = require('app/main/main-home/main-home-page-login-widget');
const MainHomePagePortal = require('app/main/main-home/main-home-page-portal');
import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget';
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
const CommonActions = require('actions/common-actions');
const UserStore = require('stores/user-store');
import CommonActions from 'actions/common-actions';
import UserStore from 'stores/user-store';
const MainHomePage = React.createClass({

View File

@ -7,14 +7,13 @@ import UserStore from 'stores/user-store';
import Button from 'core-components/button';
import DropDown from 'core-components/drop-down';
import Icon from 'core-components/icon';
let languageList = ['English', 'Spanish', 'Portuguese', 'German', 'Turkish', 'Indian'];
let codeLanguages = {
'English': 'us',
'Spanish': 'es',
'Portuguese': 'pt',
'German': 'de',
'French': 'fr',
'Chinese': 'cn',
'Turkish': 'tr',
'Indian': 'in'
};
@ -52,7 +51,7 @@ let MainLayoutHeader = React.createClass({
},
getLanguageList() {
return languageList.map((language) => {
return Object.keys(codeLanguages).map((language) => {
return {
content: language,
icon: codeLanguages[language]
@ -61,7 +60,7 @@ let MainLayoutHeader = React.createClass({
},
changeLanguage(event) {
let language = languageList[event.index];
let language = Object.keys(codeLanguages)[event.index];
CommonActions.changeLanguage(codeLanguages[language]);
},

View File

@ -0,0 +1,92 @@
import React from 'react';
import Reflux from 'reflux';
import _ from 'lodash';
import CommonActions from 'actions/common-actions';
import UserActions from 'actions/user-actions';
import UserStore from 'stores/user-store';
import i18n from 'lib-app/i18n';
import Widget from 'core-components/widget';
import Form from 'core-components/form';
import Input from 'core-components/input';
import Button from 'core-components/button';
import Message from 'core-components/message';
const MainRecoverPasswordPage = React.createClass({
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
propTypes: {
location: React.PropTypes.object,
router: React.PropTypes.object
},
componentWillMount() {
if (UserStore.isLoggedIn()) {
CommonActions.logged();
}
if (!this.props.location.query.token || !this.props.location.query.email) {
CommonActions.loggedOut();
}
},
getInitialState() {
return {
recoverStatus: 'waiting'
};
},
render() {
return (
<div className="main-recover-password-page">
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
<Form className="recover-password__form" onSubmit={this.handleRecoverPasswordSubmit}>
<div className="recover-password__inputs">
<Input placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
<Input placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
</div>
<div className="recover-password__submit-button">
<Button type="primary">{i18n('SUBMIT')}</Button>
</div>
{this.renderRecoverStatus()}
</Form>
</Widget>
</div>
);
},
renderRecoverStatus() {
switch (this.state.recoverStatus) {
case 'valid':
return <Message type="success">{i18n('VALID_RECOVER')}</Message>;
case 'invalid':
return <Message type="error">{i18n('INVALID_RECOVER')}</Message>;
case 'waiting':
return null;
}
},
handleRecoverPasswordSubmit(formState) {
let recoverData = _.clone(formState);
recoverData.token = this.props.location.query.token;
recoverData.email = this.props.location.query.email;
UserActions.recoverPassword(formState);
},
onUserStoreChanged(event) {
if (event === 'VALID_RECOVER') {
this.setState({
recoverStatus: 'valid'
});
} else {
this.setState({
recoverStatus: 'invalid'
});
}
}
});
export default MainRecoverPasswordPage;

View File

@ -0,0 +1,23 @@
.recover-password {
&__inputs {
display: inline-block;
margin: 0 auto 20px;
text-align: left;
}
&__input {
margin: 10px 0 0 0;
}
&__submit-button {
margin-bottom: 40px;
}
&__text_valid {
color: green;
}
&__text_invalid {
color: red;
}
}

View File

@ -63,7 +63,8 @@ const DropDown = React.createClass({
let menuProps = {
items: this.props.items,
onItemClick: this.handleItemClick,
onMouseDown: this.handleListMouseDown
onMouseDown: this.handleListMouseDown,
selectedIndex: this.state.selectedIndex
};
return (

View File

@ -5,12 +5,12 @@ const Icon = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
size: React.PropTypes.number
size: React.PropTypes.string
},
getDefaultProps() {
return {
size: 0
size: 'lg'
};
},

View File

@ -0,0 +1,81 @@
import React from 'react';
import classNames from 'classnames';
import {Motion, spring} from 'react-motion';
import Icon from 'core-components/icon';
const Message = React.createClass({
propTypes: {
title: React.PropTypes.string,
children: React.PropTypes.node,
leftAligned: React.PropTypes.bool,
type: React.PropTypes.oneOf(['success', 'error', 'info'])
},
getDefaultProps() {
return {
type: 'info'
};
},
render() {
return (
<Motion {...this.getAnimationProps()}>
{this.renderMessage}
</Motion>
);
},
getAnimationProps() {
return {
defaultStyle: {
opacity: spring(0, [100, 30])
},
style: {
opacity: spring(1, [100, 30])
}
};
},
renderMessage(style) {
return (
<div className={this.getClass()} style={style} aria-live="assertive">
<Icon className="message__icon" name={this.getIconName()} size={this.getIconSize()} />
<div className="message__title">{this.props.title}</div>
<div className="message__content">{this.props.children}</div>
</div>
)
},
getClass() {
let classes = {
'message': true,
'message_success': (this.props.type === 'success'),
'message_error': (this.props.type === 'error'),
'message_info': (this.props.type === 'info'),
'message_with-title': (this.props.title),
'message_left-aligned': (this.props.leftAligned),
[this.props.className]: (this.props.className)
};
return classNames(classes);
},
getIconName() {
let iconNames = {
'success': 'check-circle',
'error': 'exclamation-circle',
'info': 'info-circle'
};
return iconNames[this.props.type];
},
getIconSize() {
return (this.props.title) ? '2x' : 'lg';
}
});
export default Message;

View File

@ -0,0 +1,98 @@
@import "../scss/vars";
.message {
padding: 10px;
text-align: center;
position: relative;
overflow: hidden;
&__icon {
position: absolute;
top: 13px;
left: 13px;
}
&__title {
}
&__content {
color: white;
}
&_success {
background-color: #d8f7b3;
.message__icon {
color: #189e1e;
}
.message__title {
color: $primary-blue;
}
.message__content {
color: $primary-blue;
}
}
&_error {
background-color: #ffb4b4;
.message__icon {
color: red;
}
.message__title {
color: #bb4242;
}
.message__content {
color: white;
}
}
&_info {
background-color: #ceefff;
.message__icon {
color: #4c80ff;
}
.message__title {
color: $primary-blue;
}
.message__content {
color: $primary-blue;
}
}
&_with-title {
text-align: left;
.message__icon {
position: initial;
float: left;
margin-top: 7px;
margin-right: 20px;
}
.message__title {
font-size: $font-size--md;
font-weight: bold;
}
.message__content {
font-size: $font-size--sm;
overflow: auto;
}
}
&_left-aligned {
.message__content {
text-align: left;
padding-left: 28px;
}
}
}

View File

@ -46,5 +46,43 @@ module.exports = [
}
};
}
},
{
path: 'user/send-recover-password',
time: 100,
response: function (data) {
if (data.email.length > 10) {
return {
status: 'success',
data: {}
};
} else {
return {
status: 'fail',
message: 'Email not exists',
data: {}
};
}
}
},
{
path: 'user/recover-password',
time: 100,
response: function (data) {
if (data.password.length > 6) {
return {
status: 'success',
data: {}
};
} else {
return {
status: 'fail',
message: 'Invalid token',
data: {}
};
}
}
}
];

View File

@ -1,9 +1,19 @@
const englishLanguage = require('data/languages/en');
const spanishLanguage = require('data/languages/es');
import englishLanguage from 'data/languages/en';
import spanishLanguage from 'data/languages/en';
import germanLanguage from 'data/languages/en';
import frenchLanguage from 'data/languages/en';
import chineseLanguage from 'data/languages/en';
import turkishLanguage from 'data/languages/en';
import indianLanguage from 'data/languages/en';
const languages = {
'us': englishLanguage,
'es': spanishLanguage
'es': spanishLanguage,
'de': germanLanguage,
'fr': frenchLanguage,
'cn': chineseLanguage,
'tr': turkishLanguage,
'in': indianLanguage
};
const i18nData = function (key, lang) {

View File

@ -2,6 +2,19 @@ export default {
'SUBMIT': 'Submit',
'LOG_IN': 'Log in',
'SIGN_UP': 'Sign up',
'FORGOT_PASSWORD': 'Forgot your password?',
'RECOVER_PASSWORD': 'Recover Password',
'RECOVER_SENT': 'An email with recover instructions has been sent.',
'NEW_PASSWORD': 'New password',
'REPEAT_NEW_PASSWORD': 'Repeat new password',
'VALID_RECOVER': 'Password recovered successfully',
'INVALID_RECOVER': 'Invalid recover data',
'BACK_LOGIN_FORM': 'Back to login form',
'EMAIL_NOT_EXIST': 'Email does not exist',
//ERRORS
'ERROR_EMPTY': 'Invalid value',
'ERROR_EMAIL': 'Invalid email'
'ERROR_PASSWORD': 'Invalid password',
'ERROR_EMAIL': 'Invalid email',
'PASSWORD_NOT_MATCH': 'Password does not match'
};

View File

@ -1,7 +0,0 @@
export default {
'SUBMIT': 'Enviar',
'LOG_IN': 'Ingresar',
'SIGN_UP': 'Registrarse',
'ERROR_EMPTY': 'Valor invalido',
'ERROR_EMAIL': 'Email invalido'
};

View File

@ -1,9 +1,9 @@
const Validator = require('lib-app/validations/validator');
import Validator from 'lib-app/validations/validator';
class EmailValidator extends Validator {
validate(value, form) {
if (!value.length) return this.getError('ERROR_EMPTY');
if (value.length < 6) return this.getError('ERROR_EMAIL');
if (value.indexOf('@') === -1) return this.getError('ERROR_EMAIL');
}
}

View File

@ -0,0 +1,16 @@
import Validator from 'lib-app/validations/validator';
class LengthValidator extends Validator {
constructor(length, errorKey = 'INVALID_VALUE') {
super();
this.minlength = length;
this.errorKey = errorKey;
}
validate(value, form) {
if (value.length < this.minlength) return this.getError(this.errorKey);
}
}
export default LengthValidator;

View File

@ -0,0 +1,10 @@
import Validator from 'lib-app/validations/validator';
class RepeatPasswordValidator extends Validator {
validate(value, form) {
if (value !== form.password) return this.getError('PASSWORD_NOT_MATCH');
}
}
export default RepeatPasswordValidator;

View File

@ -1,9 +1,13 @@
const Validator = require('lib-app/validations/validator');
const EmailValidator = require('lib-app/validations/email-validator');
import Validator from 'lib-app/validations/validator';
import EmailValidator from 'lib-app/validations/email-validator';
import RepeatPasswordValidator from 'lib-app/validations/repeat-password-validator';
import LengthValidator from 'lib-app/validations/length-validator';
let validators = {
'DEFAULT': new Validator(),
'EMAIL': new EmailValidator()
'EMAIL': new EmailValidator(),
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
'REPEAT_PASSWORD': new RepeatPasswordValidator()
};
class ValidatorFactory {

View File

@ -11,11 +11,14 @@ let reactDFS = function (children, visitFunction) {
let element = stack.pop();
let tempChilds = [];
if(element.props && element.props.children) {
React.Children.forEach(element.props.children, child => tempChilds.push(child));
if (element) {
if (element.props && element.props.children) {
React.Children.forEach(element.props.children, child => tempChilds.push(child));
}
visitFunction(element);
}
visitFunction(element);
stack = stack.concat(tempChilds.reverse());
}
};
@ -26,12 +29,12 @@ let renderChildrenWithProps = function(children, mapFunction) {
}
return React.Children.map(children, function (child) {
let props = mapFunction(child);
if (typeof child !== 'object' || child === null) {
return child;
}
let props = mapFunction(child);
if (!_.isEmpty(props)) {
return React.cloneElement(child, props, child.props && child.props.children);
} else {

View File

@ -156,7 +156,7 @@ describe('UserStore', function () {
});
describe('and no session is active', function () {
beforeEach(function() {
beforeEach(function () {
let mockSuccessData = {
status: 'success',
data: {
@ -198,5 +198,92 @@ describe('UserStore', function () {
});
});
});
}})
}});
describe('when recovering password', function () {
beforeEach(function () {
let mockSuccessData = {
status: 'success',
data: {}
};
API.call = stub().returns({
then: (resolve) => {resolve(mockSuccessData)}
});
spy(UserStore, 'trigger');
});
afterEach(function () {
UserStore.trigger.restore();
});
it('should send recover password', function () {
UserStore.sendRecoverPassword({
email: 'SOME_EMAIL'
});
expect(API.call).to.have.been.calledWithMatch({
path: 'user/send-recover-password',
data: {
email: 'SOME_EMAIL'
}
});
expect(UserStore.trigger).to.have.been.calledWith('SEND_RECOVER_SUCCESS');
});
it('should trigger fail if send recover fails', function () {
API.call = stub().returns({
then: (resolve, reject) => {reject({ status: 'fail'})}
});
UserStore.sendRecoverPassword({
email: 'SOME_EMAIL'
});
expect(API.call).to.have.been.calledWithMatch({
path: 'user/send-recover-password',
data: {
email: 'SOME_EMAIL'
}
});
expect(UserStore.trigger).to.have.been.calledWith('SEND_RECOVER_FAIL');
});
it('should recover password', function () {
UserStore.recoverPassword({
email: 'SOME_EMAIL',
token: 'SOME_TOKEN',
password: 'SOME_PASSWORD'
});
expect(API.call).to.have.been.calledWithMatch({
path: 'user/recover-password',
data: {
email: 'SOME_EMAIL',
token: 'SOME_TOKEN',
password: 'SOME_PASSWORD'
}
});
expect(UserStore.trigger).to.have.been.calledWith('VALID_RECOVER');
});
it('should trigger fail if recover password fails', function () {
API.call = stub().returns({
then: (resolve, reject) => {reject({ status: 'fail'})}
});
UserStore.recoverPassword({
email: 'SOME_EMAIL',
token: 'SOME_TOKEN',
password: 'SOME_PASSWORD'
});
expect(API.call).to.have.been.calledWithMatch({
path: 'user/recover-password',
data: {
email: 'SOME_EMAIL',
token: 'SOME_TOKEN',
password: 'SOME_PASSWORD'
}
});
expect(UserStore.trigger).to.have.been.calledWith('INVALID_RECOVER');
});
});
});

View File

@ -13,6 +13,8 @@ const UserStore = Reflux.createStore({
this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus);
this.listenTo(UserActions.login, this.loginUser);
this.listenTo(UserActions.logout, this.logoutUser);
this.listenTo(UserActions.recoverPassword, this.recoverPassword);
this.listenTo(UserActions.sendRecover, this.sendRecoverPassword);
},
initSession() {
@ -53,6 +55,29 @@ const UserStore = Reflux.createStore({
});
},
sendRecoverPassword(recoverData) {
return API.call({
path: 'user/send-recover-password',
data: recoverData
}).then(() => {
this.trigger('SEND_RECOVER_SUCCESS');
}, () => {
this.trigger('SEND_RECOVER_FAIL')
});
},
recoverPassword(recoverData) {
return API.call({
path: 'user/recover-password',
data: recoverData
}).then(() => {
this.trigger('VALID_RECOVER');
setTimeout(CommonActions.loggedOut, 1000);
}, () => {
this.trigger('INVALID_RECOVER')
});
},
isLoggedIn() {
return sessionStore.isLoggedIn();
},