mirror of
https://github.com/opensupports/opensupports.git
synced 2025-07-29 16:54:53 +02:00
Ivan - Implement Redux architecture with stores and actions [skip ci]
This commit is contained in:
parent
26ac580ed0
commit
e02844814d
@ -38,7 +38,7 @@ function buildScript(file, watch) {
|
|||||||
bundler.on('update', rebundle);
|
bundler.on('update', rebundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
bundler.transform(babelify);
|
bundler.transform(babelify, {'optional': ['es7.classProperties']});
|
||||||
bundler.transform(debowerify);
|
bundler.transform(debowerify);
|
||||||
|
|
||||||
function rebundle() {
|
function rebundle() {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^5.8.22",
|
"babel-core": "^5.8.22",
|
||||||
|
"babel-plugin-transform-class-properties": "^6.11.5",
|
||||||
"babel-register": "^6.7.2",
|
"babel-register": "^6.7.2",
|
||||||
"babelify": "^6.1.x",
|
"babelify": "^6.1.x",
|
||||||
"browser-sync": "^2.7.13",
|
"browser-sync": "^2.7.13",
|
||||||
@ -63,7 +64,10 @@
|
|||||||
"react-dom": "^15.0.1",
|
"react-dom": "^15.0.1",
|
||||||
"react-google-recaptcha": "^0.5.2",
|
"react-google-recaptcha": "^0.5.2",
|
||||||
"react-motion": "^0.3.0",
|
"react-motion": "^0.3.0",
|
||||||
|
"react-redux": "^4.4.5",
|
||||||
"react-router": "^2.4.0",
|
"react-router": "^2.4.0",
|
||||||
"reflux": "^0.4.1"
|
"react-router-redux": "^4.0.5",
|
||||||
|
"redux": "^3.5.2",
|
||||||
|
"redux-promise-middleware": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import Reflux from 'reflux';
|
|
||||||
|
|
||||||
let CommonActions = Reflux.createActions([
|
|
||||||
'changeLanguage',
|
|
||||||
'logged',
|
|
||||||
'loggedOut'
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default CommonActions;
|
|
8
client/src/actions/config-actions.js
Normal file
8
client/src/actions/config-actions.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
changeLanguage(newLanguage) {
|
||||||
|
return {
|
||||||
|
type: 'CHANGE_LANGUAGE',
|
||||||
|
payload: newLanguage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
63
client/src/actions/session-actions.js
Normal file
63
client/src/actions/session-actions.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import API from 'lib-app/api-call';
|
||||||
|
import sessionStore from 'lib-app/session-store';
|
||||||
|
import store from 'app/store';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
login(loginData) {
|
||||||
|
return {
|
||||||
|
type: 'LOGIN',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/user/login',
|
||||||
|
data: loginData
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
autoLogin() {
|
||||||
|
const rememberData = sessionStore.getRememberData();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'LOGIN_AUTO',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/user/login',
|
||||||
|
data: {
|
||||||
|
userId: rememberData.userId,
|
||||||
|
rememberToken: rememberData.token,
|
||||||
|
isAutomatic: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
return {
|
||||||
|
type: 'LOG_OUT',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/user/logout',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
initSession() {
|
||||||
|
return {
|
||||||
|
type: 'CHECK_SESSION',
|
||||||
|
payload: API.call({
|
||||||
|
path: '/user/check-session',
|
||||||
|
data: {}
|
||||||
|
}).then((result) => {
|
||||||
|
if (!result.data.sessionActive) {
|
||||||
|
if (sessionStore.isRememberDataExpired()) {
|
||||||
|
store.dispatch(this.logout());
|
||||||
|
} else {
|
||||||
|
store.dispatch(this.autoLogin());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SESSION_CHECKED'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -1,12 +0,0 @@
|
|||||||
import Reflux from 'reflux';
|
|
||||||
|
|
||||||
const UserActions = Reflux.createActions([
|
|
||||||
'checkLoginStatus',
|
|
||||||
'login',
|
|
||||||
'logout',
|
|
||||||
'signup',
|
|
||||||
'sendRecoverPassword',
|
|
||||||
'recoverPassword'
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default UserActions;
|
|
@ -1,16 +1,44 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Reflux from 'reflux';
|
import _ from 'lodash';
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import CommonStore from 'stores/common-store';
|
class App extends React.Component {
|
||||||
|
static contextTypes = {
|
||||||
const App = React.createClass({
|
|
||||||
|
|
||||||
contextTypes: {
|
|
||||||
router: React.PropTypes.object,
|
router: React.PropTypes.object,
|
||||||
location: React.PropTypes.object
|
location: React.PropTypes.object
|
||||||
},
|
};
|
||||||
|
|
||||||
mixins: [Reflux.listenTo(CommonStore, 'onCommonStoreChanged')],
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
if (_.includes(props.location.pathname, '/app/dashboard') && !props.config.logged) {
|
||||||
|
context.router.push('/app');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.includes(props.location.pathname, '/app/dashboard') && props.config.logged) {
|
||||||
|
context.router.push('/app/dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const validations = {
|
||||||
|
languageChanged: nextProps.config.language !== this.props.config.language,
|
||||||
|
loggedIn: nextProps.session.logged && !this.props.session.logged,
|
||||||
|
loggedOut: !nextProps.session.logged && this.props.session.logged
|
||||||
|
};
|
||||||
|
|
||||||
|
if (validations.languageChanged) {
|
||||||
|
this.context.router.push(this.props.location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validations.loggedIn) {
|
||||||
|
this.context.router.push('/app/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validations.loggedOut) {
|
||||||
|
this.context.router.push('/app');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -18,19 +46,13 @@ const App = React.createClass({
|
|||||||
{React.cloneElement(this.props.children, {})}
|
{React.cloneElement(this.props.children, {})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
|
||||||
|
|
||||||
onCommonStoreChanged(change) {
|
|
||||||
let handle = {
|
|
||||||
'i18n': () => {this.context.router.push(this.context.location.pathname)},
|
|
||||||
'logged': () => {this.context.router.push('/app/dashboard')},
|
|
||||||
'loggedOut': () => {this.context.router.push('/app')}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (handle[change]) {
|
|
||||||
handle[change]();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default App;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
config: store.config,
|
||||||
|
session: store.session,
|
||||||
|
routing: store.routing
|
||||||
|
};
|
||||||
|
})(App);
|
@ -1,5 +1,8 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
const {Router, Route, IndexRoute, browserHistory} = require('react-router');
|
const {Router, Route, IndexRoute, browserHistory} = require('react-router');
|
||||||
|
import { syncHistoryWithStore } from 'react-router-redux';
|
||||||
|
|
||||||
|
import store from 'app/store';
|
||||||
|
|
||||||
const App = require('app/App');
|
const App = require('app/App');
|
||||||
const DemoPage = require('app/demo/components-demo-page');
|
const DemoPage = require('app/demo/components-demo-page');
|
||||||
@ -20,8 +23,10 @@ const DashboardEditProfilePage = require('app/main/dashboard/dashboard-edit-prof
|
|||||||
const DashboardArticlePage = require('app/main/dashboard/dashboard-article/dashboard-article-page');
|
const DashboardArticlePage = require('app/main/dashboard/dashboard-article/dashboard-article-page');
|
||||||
const DashboardTicketPage = require('app/main/dashboard/dashboard-ticket/dashboard-ticket-page');
|
const DashboardTicketPage = require('app/main/dashboard/dashboard-ticket/dashboard-ticket-page');
|
||||||
|
|
||||||
|
const history = syncHistoryWithStore(browserHistory, store);
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
<Router history={browserHistory}>
|
<Router history={history}>
|
||||||
<Route component={App} path='/'>
|
<Route component={App} path='/'>
|
||||||
<Route path='/app' component={MainLayout}>
|
<Route path='/app' component={MainLayout}>
|
||||||
<IndexRoute component={MainHomePage} />
|
<IndexRoute component={MainHomePage} />
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {render} from 'react-dom'
|
import {render} from 'react-dom'
|
||||||
import Router from 'react-router';
|
import { Provider } from 'react-redux';
|
||||||
import UserStore from 'stores/user-store';
|
|
||||||
|
|
||||||
|
import SessionActions from 'actions/session-actions';
|
||||||
import routes from './Routes';
|
import routes from './Routes';
|
||||||
|
import store from './store';
|
||||||
|
|
||||||
if ( process.env.NODE_ENV !== 'production' ) {
|
if ( process.env.NODE_ENV !== 'production' ) {
|
||||||
// Enable React devtools
|
// Enable React devtools
|
||||||
@ -14,9 +15,17 @@ if (noFixtures === 'disabled') {
|
|||||||
require('lib-app/fixtures-loader');
|
require('lib-app/fixtures-loader');
|
||||||
}
|
}
|
||||||
|
|
||||||
let onSessionInit = function () {
|
let renderApplication = function () {
|
||||||
render(routes, document.getElementById('app'));
|
render(<Provider store={store}>{routes}</Provider>, document.getElementById('app'));
|
||||||
};
|
};
|
||||||
|
|
||||||
UserStore.initSession().then(onSessionInit, onSessionInit);
|
store.dispatch(SessionActions.initSession());
|
||||||
|
|
||||||
|
let unsubscribe = store.subscribe(() => {
|
||||||
|
console.log(store.getState());
|
||||||
|
|
||||||
|
if (store.getState().session.initDone) {
|
||||||
|
unsubscribe();
|
||||||
|
renderApplication();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import UserStore from 'stores/user-store';
|
//import UserStore from 'stores/user-store';
|
||||||
import CommonActions from 'actions/common-actions';
|
//import CommonActions from 'actions/common-actions';
|
||||||
|
|
||||||
import DashboardMenu from 'app/main/dashboard/dashboard-menu';
|
import DashboardMenu from 'app/main/dashboard/dashboard-menu';
|
||||||
|
|
||||||
const DashboardLayout = React.createClass({
|
const DashboardLayout = React.createClass({
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
if (!UserStore.isLoggedIn()) {
|
|
||||||
CommonActions.loggedOut();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (UserStore.isLoggedIn()) ? (
|
return (this.props.session.logged) ? (
|
||||||
<div>
|
<div>
|
||||||
<div><DashboardMenu location={this.props.location} /></div>
|
<div><DashboardMenu location={this.props.location} /></div>
|
||||||
<div>{this.props.children}</div>
|
<div>{this.props.children}</div>
|
||||||
@ -23,4 +18,8 @@ const DashboardLayout = React.createClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default DashboardLayout;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
session: store.session
|
||||||
|
};
|
||||||
|
})(DashboardLayout);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Reflux from 'reflux';
|
import {connect} from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import UserActions from 'actions/user-actions';
|
import SessionActions from 'actions/session-actions';
|
||||||
import UserStore from 'stores/user-store';
|
import API from 'lib-app/api-call';
|
||||||
import focus from 'lib-core/focus';
|
import focus from 'lib-core/focus';
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
|
|
||||||
@ -17,12 +18,12 @@ import Widget from 'core-components/widget';
|
|||||||
import WidgetTransition from 'core-components/widget-transition';
|
import WidgetTransition from 'core-components/widget-transition';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
let MainHomePageLoginWidget = React.createClass({
|
class MainHomePageLoginWidget extends React.Component {
|
||||||
|
|
||||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
|
||||||
|
|
||||||
getInitialState() {
|
constructor(props) {
|
||||||
return {
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
sideToShow: 'front',
|
sideToShow: 'front',
|
||||||
loginFormErrors: {},
|
loginFormErrors: {},
|
||||||
recoverFormErrors: {},
|
recoverFormErrors: {},
|
||||||
@ -30,7 +31,13 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
loadingLogin: false,
|
loadingLogin: false,
|
||||||
loadingRecover: false
|
loadingRecover: false
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!prevProps.session.failed && this.props.session.failed) {
|
||||||
|
this.refs.loginForm.refs.password.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -39,7 +46,7 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
{this.renderPasswordRecovery()}
|
{this.renderPasswordRecovery()}
|
||||||
</WidgetTransition>
|
</WidgetTransition>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderLogin() {
|
renderLogin() {
|
||||||
return (
|
return (
|
||||||
@ -54,12 +61,12 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
<SubmitButton type="primary">LOG IN</SubmitButton>
|
<SubmitButton type="primary">LOG IN</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<Button className="login-widget__forgot-password" type="link" onClick={this.handleForgotPasswordClick} onMouseDown={(event) => {event.preventDefault()}}>
|
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
||||||
{i18n('FORGOT_PASSWORD')}
|
{i18n('FORGOT_PASSWORD')}
|
||||||
</Button>
|
</Button>
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderPasswordRecovery() {
|
renderPasswordRecovery() {
|
||||||
return (
|
return (
|
||||||
@ -72,13 +79,13 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
|
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<Button className="login-widget__forgot-password" type="link" onClick={this.handleBackToLoginClick} onMouseDown={(event) => {event.preventDefault()}}>
|
<Button className="login-widget__forgot-password" type="link" onClick={this.onBackToLoginClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
|
||||||
{i18n('BACK_LOGIN_FORM')}
|
{i18n('BACK_LOGIN_FORM')}
|
||||||
</Button>
|
</Button>
|
||||||
{this.renderRecoverStatus()}
|
{this.renderRecoverStatus()}
|
||||||
</Widget>
|
</Widget>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderRecoverStatus() {
|
renderRecoverStatus() {
|
||||||
let status = null;
|
let status = null;
|
||||||
@ -92,102 +99,94 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
},
|
}
|
||||||
|
|
||||||
getLoginFormProps() {
|
getLoginFormProps() {
|
||||||
return {
|
return {
|
||||||
loading: this.state.loadingLogin,
|
loading: this.props.session.pending,
|
||||||
className: 'login-widget__form',
|
className: 'login-widget__form',
|
||||||
ref: 'loginForm',
|
ref: 'loginForm',
|
||||||
onSubmit:this.handleLoginFormSubmit,
|
onSubmit: this.onLoginFormSubmit.bind(this),
|
||||||
errors: this.state.loginFormErrors,
|
errors: this.getLoginFormErrors(),
|
||||||
onValidateErrors: this.handleLoginFormErrorsValidation
|
onValidateErrors: this.onLoginFormErrorsValidation.bind(this)
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
getRecoverFormProps() {
|
getRecoverFormProps() {
|
||||||
return {
|
return {
|
||||||
loading: this.state.loadingRecover,
|
loading: this.state.loadingRecover,
|
||||||
className: 'login-widget__form',
|
className: 'login-widget__form',
|
||||||
ref: 'recoverForm',
|
ref: 'recoverForm',
|
||||||
onSubmit:this.handleForgotPasswordSubmit,
|
onSubmit: this.onForgotPasswordSubmit.bind(this),
|
||||||
errors: this.state.recoverFormErrors,
|
errors: this.state.recoverFormErrors,
|
||||||
onValidateErrors: this.handleRecoverFormErrorsValidation
|
onValidateErrors: this.onRecoverFormErrorsValidation.bind(this)
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
handleLoginFormSubmit(formState) {
|
getLoginFormErrors() {
|
||||||
UserActions.login(formState);
|
return _.extend({}, this.state.loginFormErrors, {
|
||||||
|
password: (this.props.session.failed) ? i18n('ERROR_PASSWORD') : null
|
||||||
this.setState({
|
|
||||||
loadingLogin: true
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
handleForgotPasswordSubmit(formState) {
|
onLoginFormSubmit(formState) {
|
||||||
UserActions.sendRecoverPassword(formState);
|
this.props.dispatch(SessionActions.login(formState));
|
||||||
|
}
|
||||||
|
|
||||||
|
onForgotPasswordSubmit(formState) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingRecover: true
|
loadingRecover: true,
|
||||||
|
recoverSent: false
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
handleLoginFormErrorsValidation(errors) {
|
API.call({
|
||||||
|
path: '/user/send-recover-password',
|
||||||
|
data: formState
|
||||||
|
}).then(this.onRecoverPasswordSent.bind(this)).catch(this.onRecoverPasswordFail.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginFormErrorsValidation(errors) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loginFormErrors: errors
|
loginFormErrors: errors
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
handleRecoverFormErrorsValidation(errors) {
|
onRecoverFormErrorsValidation(errors) {
|
||||||
this.setState({
|
this.setState({
|
||||||
recoverFormErrors: errors
|
recoverFormErrors: errors
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
handleForgotPasswordClick() {
|
onForgotPasswordClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
sideToShow: 'back'
|
sideToShow: 'back'
|
||||||
}, this.moveFocusToCurrentSide);
|
}, this.moveFocusToCurrentSide);
|
||||||
},
|
}
|
||||||
|
|
||||||
handleBackToLoginClick() {
|
onBackToLoginClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
sideToShow: 'front',
|
sideToShow: 'front',
|
||||||
recoverSent: false
|
recoverSent: false
|
||||||
}, this.moveFocusToCurrentSide);
|
}, this.moveFocusToCurrentSide);
|
||||||
},
|
}
|
||||||
|
|
||||||
onUserStoreChanged(event) {
|
|
||||||
if (event === 'LOGIN_FAIL') {
|
|
||||||
this.setState({
|
|
||||||
loadingLogin: false,
|
|
||||||
loginFormErrors: {
|
|
||||||
password: i18n('ERROR_PASSWORD')
|
|
||||||
}
|
|
||||||
}, function () {
|
|
||||||
this.refs.loginForm.refs.password.focus();
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event === 'SEND_RECOVER_FAIL') {
|
onRecoverPasswordSent() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loadingRecover: false,
|
loadingRecover: false,
|
||||||
recoverFormErrors: {
|
recoverSent: true
|
||||||
email: i18n('EMAIL_NOT_EXIST')
|
});
|
||||||
}
|
}
|
||||||
}, function () {
|
|
||||||
this.refs.recoverForm.refs.email.focus();
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
}
|
onRecoverPasswordFail() {
|
||||||
|
this.setState({
|
||||||
if (event === 'SEND_RECOVER_SUCCESS') {
|
loadingRecover: false,
|
||||||
this.setState({
|
recoverFormErrors: {
|
||||||
loadingRecover: false,
|
email: i18n('EMAIL_NOT_EXIST')
|
||||||
recoverSent: true
|
}
|
||||||
});
|
}, function () {
|
||||||
}
|
this.refs.recoverForm.refs.email.focus();
|
||||||
},
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
moveFocusToCurrentSide() {
|
moveFocusToCurrentSide() {
|
||||||
let currentWidget;
|
let currentWidget;
|
||||||
@ -205,6 +204,11 @@ let MainHomePageLoginWidget = React.createClass({
|
|||||||
focus.focusFirstInput(currentWidget);
|
focus.focusFirstInput(currentWidget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default MainHomePageLoginWidget;
|
|
||||||
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
session: store.session
|
||||||
|
};
|
||||||
|
})(MainHomePageLoginWidget);
|
||||||
|
@ -3,16 +3,7 @@ import React from 'react';
|
|||||||
import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget';
|
import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget';
|
||||||
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
|
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
|
||||||
|
|
||||||
import CommonActions from 'actions/common-actions';
|
|
||||||
import UserStore from 'stores/user-store';
|
|
||||||
|
|
||||||
const MainHomePage = React.createClass({
|
const MainHomePage = React.createClass({
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
if (UserStore.isLoggedIn()) {
|
|
||||||
CommonActions.logged();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import i18n from 'lib-app/i18n';
|
import i18n from 'lib-app/i18n';
|
||||||
import CommonActions from 'actions/common-actions';
|
import SessionActions from 'actions/user-actions';
|
||||||
import UserActions from 'actions/user-actions';
|
import ConfigActions from 'actions/config-actions';
|
||||||
import UserStore from 'stores/user-store';
|
|
||||||
|
|
||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
import DropDown from 'core-components/drop-down';
|
import DropDown from 'core-components/drop-down';
|
||||||
@ -18,23 +18,24 @@ let codeLanguages = {
|
|||||||
'Indian': 'in'
|
'Indian': 'in'
|
||||||
};
|
};
|
||||||
|
|
||||||
let MainLayoutHeader = React.createClass({
|
class MainLayoutHeader extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-layout-header">
|
<div className="main-layout-header">
|
||||||
{this.renderAccessLinks()}
|
{this.renderAccessLinks()}
|
||||||
<DropDown className="main-layout-header--languages" items={this.getLanguageList()} onChange={this.changeLanguage}/>
|
<DropDown className="main-layout-header--languages" items={this.getLanguageList()} onChange={this.changeLanguage.bind(this)}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderAccessLinks() {
|
renderAccessLinks() {
|
||||||
let result;
|
let result;
|
||||||
if (UserStore.isLoggedIn()) {
|
|
||||||
|
if (this.props.session.logged) {
|
||||||
result = (
|
result = (
|
||||||
<div className="main-layout-header--login-links">
|
<div className="main-layout-header--login-links">
|
||||||
Welcome, pepito
|
Welcome, John
|
||||||
<Button type="clean" onClick={this.logout}>(Close Session)</Button>
|
<Button type="clean" onClick={this.logout}>(Close Session)</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -48,7 +49,7 @@ let MainLayoutHeader = React.createClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
}
|
||||||
|
|
||||||
getLanguageList() {
|
getLanguageList() {
|
||||||
return Object.keys(codeLanguages).map((language) => {
|
return Object.keys(codeLanguages).map((language) => {
|
||||||
@ -57,17 +58,22 @@ let MainLayoutHeader = React.createClass({
|
|||||||
icon: codeLanguages[language]
|
icon: codeLanguages[language]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
changeLanguage(event) {
|
changeLanguage(event) {
|
||||||
let language = Object.keys(codeLanguages)[event.index];
|
let language = Object.keys(codeLanguages)[event.index];
|
||||||
|
|
||||||
CommonActions.changeLanguage(codeLanguages[language]);
|
this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language]));
|
||||||
},
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
UserActions.logout();
|
this.props.dispatch(SessionActions.logout());
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default MainLayoutHeader;
|
export default connect((store) => {
|
||||||
|
return {
|
||||||
|
session: store.session,
|
||||||
|
config: store.config
|
||||||
|
};
|
||||||
|
})(MainLayoutHeader);
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Reflux from 'reflux';
|
|
||||||
import _ from 'lodash';
|
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 i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
@ -13,37 +10,27 @@ import Input from 'core-components/input';
|
|||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
|
|
||||||
const MainRecoverPasswordPage = React.createClass({
|
class MainRecoverPasswordPage extends React.Component {
|
||||||
|
|
||||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
location: React.PropTypes.object,
|
location: React.PropTypes.object,
|
||||||
router: React.PropTypes.object
|
router: React.PropTypes.object
|
||||||
},
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
constructor(props) {
|
||||||
if (UserStore.isLoggedIn()) {
|
super(props);
|
||||||
CommonActions.logged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.props.location.query.token || !this.props.location.query.email) {
|
this.state = {
|
||||||
CommonActions.loggedOut();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
|
||||||
return {
|
|
||||||
recoverStatus: 'waiting',
|
recoverStatus: 'waiting',
|
||||||
loading: false
|
loading: false
|
||||||
};
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-recover-password-page">
|
<div className="main-recover-password-page">
|
||||||
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
|
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
|
||||||
<Form className="recover-password__form" onSubmit={this.handleRecoverPasswordSubmit} loading={this.state.loading}>
|
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
|
||||||
<div className="recover-password__inputs">
|
<div className="recover-password__inputs">
|
||||||
<Input placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
|
<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/>
|
<Input placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
|
||||||
@ -56,7 +43,7 @@ const MainRecoverPasswordPage = React.createClass({
|
|||||||
</Widget>
|
</Widget>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderRecoverStatus() {
|
renderRecoverStatus() {
|
||||||
switch (this.state.recoverStatus) {
|
switch (this.state.recoverStatus) {
|
||||||
@ -67,33 +54,39 @@ const MainRecoverPasswordPage = React.createClass({
|
|||||||
case 'waiting':
|
case 'waiting':
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
handleRecoverPasswordSubmit(formState) {
|
onRecoverPasswordSubmit(formState) {
|
||||||
let recoverData = _.clone(formState);
|
let recoverData = _.clone(formState);
|
||||||
recoverData.token = this.props.location.query.token;
|
recoverData.token = this.props.location.query.token;
|
||||||
recoverData.email = this.props.location.query.email;
|
recoverData.email = this.props.location.query.email;
|
||||||
|
|
||||||
UserActions.recoverPassword(recoverData);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
});
|
}, this.callRecoverPassword.bind(this, recoverData));
|
||||||
},
|
|
||||||
|
|
||||||
onUserStoreChanged(event) {
|
|
||||||
if (event === 'VALID_RECOVER') {
|
|
||||||
setTimeout(CommonActions.loggedOut, 2000);
|
|
||||||
this.setState({
|
|
||||||
recoverStatus: 'valid',
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
recoverStatus: 'invalid',
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
callRecoverPassword(recoverData) {
|
||||||
|
API.call({
|
||||||
|
path: '/user/recover-password',
|
||||||
|
data: recoverData
|
||||||
|
}).then(this.onPasswordRecovered.bind(this)).catch(this.onPasswordRecoverFail.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onPasswordRecovered() {
|
||||||
|
setTimeout(() => {this.props.history.push('/app')}, 2000);
|
||||||
|
this.setState({
|
||||||
|
recoverStatus: 'valid',
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPasswordRecoverFail() {
|
||||||
|
this.setState({
|
||||||
|
recoverStatus: 'invalid',
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default MainRecoverPasswordPage;
|
export default MainRecoverPasswordPage;
|
@ -1,11 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Reflux from 'reflux';
|
|
||||||
import ReCAPTCHA from 'react-google-recaptcha';
|
import ReCAPTCHA from 'react-google-recaptcha';
|
||||||
|
|
||||||
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 i18n from 'lib-app/i18n';
|
||||||
|
import API from 'lib-app/api-call';
|
||||||
|
|
||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
@ -14,22 +11,16 @@ import Input from 'core-components/input';
|
|||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
|
|
||||||
|
|
||||||
let MainSignUpPageWidget = React.createClass({
|
class MainSignUpPageWidget extends React.Component {
|
||||||
|
|
||||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
|
||||||
|
|
||||||
componentDidMount() {
|
constructor(props) {
|
||||||
if (UserStore.isLoggedIn()) {
|
super(props);
|
||||||
CommonActions.logged();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
this.state = {
|
||||||
return {
|
|
||||||
loading: false,
|
loading: false,
|
||||||
email: null
|
email: null
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
@ -52,7 +43,7 @@ let MainSignUpPageWidget = React.createClass({
|
|||||||
</Widget>
|
</Widget>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
renderMessage() {
|
renderMessage() {
|
||||||
switch (this.state.message) {
|
switch (this.state.message) {
|
||||||
@ -63,37 +54,47 @@ let MainSignUpPageWidget = React.createClass({
|
|||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
getFormProps() {
|
getFormProps() {
|
||||||
return {
|
return {
|
||||||
loading: this.state.loading,
|
loading: this.state.loading,
|
||||||
className: 'signup-widget__form',
|
className: 'signup-widget__form',
|
||||||
onSubmit: this.handleLoginFormSubmit
|
onSubmit: this.onLoginFormSubmit.bind(this)
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
getInputProps() {
|
getInputProps() {
|
||||||
return {
|
return {
|
||||||
inputType: 'secondary',
|
inputType: 'secondary',
|
||||||
className: 'signup-widget__input'
|
className: 'signup-widget__input'
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
handleLoginFormSubmit(formState) {
|
onLoginFormSubmit(formState) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
UserActions.signup(formState);
|
API.call({
|
||||||
},
|
path: '/user/signup',
|
||||||
|
data: formState
|
||||||
onUserStoreChanged(event) {
|
}).then(this.onSignupSuccess.bind(this)).catch(this.onSignupFail.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSignupSuccess() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
message: (event === 'SIGNUP_FAIL') ? 'fail': 'success'
|
message: 'success'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
onSignupFail() {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
message: 'fail'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default MainSignUpPageWidget;
|
export default MainSignUpPageWidget;
|
5
client/src/app/store.js
Normal file
5
client/src/app/store.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
|
import promise from 'redux-promise-middleware';
|
||||||
|
import reducers from 'reducers/_reducers';
|
||||||
|
|
||||||
|
export default createStore(reducers, applyMiddleware(promise()));
|
@ -1,6 +1,6 @@
|
|||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
{
|
||||||
path: 'user/login',
|
path: '/user/login',
|
||||||
time: 1000,
|
time: 1000,
|
||||||
response: function (data) {
|
response: function (data) {
|
||||||
let response;
|
let response;
|
||||||
@ -26,7 +26,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/logout',
|
path: '/user/logout',
|
||||||
time: 100,
|
time: 100,
|
||||||
response: function () {
|
response: function () {
|
||||||
return {
|
return {
|
||||||
@ -36,7 +36,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/check-session',
|
path: '/user/check-session',
|
||||||
time: 100,
|
time: 100,
|
||||||
response: function () {
|
response: function () {
|
||||||
return {
|
return {
|
||||||
@ -48,7 +48,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/send-recover-password',
|
path: '/user/send-recover-password',
|
||||||
time: 2000,
|
time: 2000,
|
||||||
response: function (data) {
|
response: function (data) {
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/recover-password',
|
path: '/user/recover-password',
|
||||||
time: 1000,
|
time: 1000,
|
||||||
response: function (data) {
|
response: function (data) {
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/signup',
|
path: '/user/signup',
|
||||||
time: 1000,
|
time: 1000,
|
||||||
response: function (data) {
|
response: function (data) {
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ const _ = require('lodash');
|
|||||||
const APIUtils = require('lib-core/APIUtils');
|
const APIUtils = require('lib-core/APIUtils');
|
||||||
const SessionStore = require('lib-app/session-store');
|
const SessionStore = require('lib-app/session-store');
|
||||||
|
|
||||||
const root = 'http://localhost:3000/api/';
|
const root = 'http://localhost:3000/api';
|
||||||
|
|
||||||
function processData (data) {
|
function processData (data) {
|
||||||
return _.extend(SessionStore.getSessionData(), data);
|
return _.extend(SessionStore.getSessionData(), data);
|
||||||
|
@ -21,7 +21,7 @@ fixtures.add(require('data/fixtures/user-fixtures'));
|
|||||||
_.each(fixtures.getAll(), function (fixture) {
|
_.each(fixtures.getAll(), function (fixture) {
|
||||||
mockjax({
|
mockjax({
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
url: 'http://localhost:3000/api/' + fixture.path,
|
url: 'http://localhost:3000/api' + fixture.path,
|
||||||
responseTime: fixture.time || 500,
|
responseTime: fixture.time || 500,
|
||||||
response: function (settings) {
|
response: function (settings) {
|
||||||
this.responseText = fixture.response(settings.data);
|
this.responseText = fixture.response(settings.data);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import MessageFormat from 'messageformat';
|
import MessageFormat from 'messageformat';
|
||||||
|
|
||||||
import CommonStore from 'stores/common-store';
|
import store from 'app/store';
|
||||||
import i18nData from 'data/i18n-data';
|
import i18nData from 'data/i18n-data';
|
||||||
|
|
||||||
let mf = new MessageFormat('en');
|
let mf = new MessageFormat('en');
|
||||||
|
|
||||||
let i18n = function (key, params = null) {
|
let i18n = function (key, params = null) {
|
||||||
let i18nKey = i18nData(key, CommonStore.language);
|
let i18nKey = i18nData(key, store.getState().config.language);
|
||||||
let message = mf.compile(i18nKey);
|
let message = mf.compile(i18nKey);
|
||||||
|
|
||||||
return message(params);
|
return message(params);
|
||||||
|
@ -6,7 +6,7 @@ class SessionStore {
|
|||||||
this.storage = LocalStorage;
|
this.storage = LocalStorage;
|
||||||
|
|
||||||
if (!this.getItem('language')) {
|
if (!this.getItem('language')) {
|
||||||
this.setItem('language', 'english');
|
this.setItem('language', 'us');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
client/src/reducers/_reducers.js
Normal file
11
client/src/reducers/_reducers.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { routerReducer } from 'react-router-redux';
|
||||||
|
|
||||||
|
import sessionReducer from 'reducers/session-reducer';
|
||||||
|
import configReducer from 'reducers/config-reducer';
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
session: sessionReducer,
|
||||||
|
config: configReducer,
|
||||||
|
routing: routerReducer
|
||||||
|
});
|
29
client/src/reducers/config-reducer.js
Normal file
29
client/src/reducers/config-reducer.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import Reducer from 'reducers/reducer';
|
||||||
|
import sessionStore from 'lib-app/session-store';
|
||||||
|
|
||||||
|
class ConfigReducer extends Reducer {
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
language: sessionStore.getItem('language')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeHandlers() {
|
||||||
|
return {
|
||||||
|
'CHANGE_LANGUAGE': this.onLanguageChange
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onLanguageChange(state, payload) {
|
||||||
|
sessionStore.setItem('language', payload);
|
||||||
|
|
||||||
|
return _.extend({}, state, {
|
||||||
|
language: payload
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfigReducer.getInstance();
|
13
client/src/reducers/reducer.js
Normal file
13
client/src/reducers/reducer.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class Reducer {
|
||||||
|
static getInstance() {
|
||||||
|
let reducer = new this();
|
||||||
|
|
||||||
|
return (state = reducer.getInitialState(), action) => {
|
||||||
|
const actionHandler = reducer.getTypeHandlers()[action.type];
|
||||||
|
|
||||||
|
return (actionHandler) ? actionHandler(state, action.payload) : state;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Reducer;
|
90
client/src/reducers/session-reducer.js
Normal file
90
client/src/reducers/session-reducer.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import Reducer from 'reducers/reducer';
|
||||||
|
import sessionStore from 'lib-app/session-store';
|
||||||
|
|
||||||
|
class SessionReducer extends Reducer {
|
||||||
|
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
initDone: false,
|
||||||
|
logged: false,
|
||||||
|
pending: false,
|
||||||
|
failed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeHandlers() {
|
||||||
|
return {
|
||||||
|
'LOGIN_PENDING': this.onLoginPending,
|
||||||
|
'LOGIN_FULFILLED': this.onLoginCompleted,
|
||||||
|
'LOGIN_REJECTED': this.onLoginFailed,
|
||||||
|
'LOGOUT_FULFILLED': this.onLogout,
|
||||||
|
'CHECK_SESSION_REJECTED': (state) => { return _.extend({}, state, {initDone: true})},
|
||||||
|
'SESSION_CHECKED': (state) => { return _.extend({}, state, {initDone: true})},
|
||||||
|
'LOGIN_AUTO_FULFILLED': this.onAutoLogin,
|
||||||
|
'LOGIN_AUTO_REJECTED': this.onAutoLoginFail
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginPending(state) {
|
||||||
|
return _.extend({}, state, {
|
||||||
|
logged: false,
|
||||||
|
pending: true,
|
||||||
|
failed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginCompleted(state, payload) {
|
||||||
|
if (payload.data.rememberToken) {
|
||||||
|
sessionStore.storeRememberData({
|
||||||
|
token: payload.data.rememberToken,
|
||||||
|
userId: payload.data.userId,
|
||||||
|
expiration: payload.data.rememberExpiration
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sessionStore.createSession(payload.data.userId, payload.data.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.extend({}, state, {
|
||||||
|
logged: true,
|
||||||
|
pending: false,
|
||||||
|
failed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginFailed(state) {
|
||||||
|
return _.extend({}, state, {
|
||||||
|
logged: false,
|
||||||
|
pending: false,
|
||||||
|
failed: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLogout(state) {
|
||||||
|
sessionStore.closeSession();
|
||||||
|
sessionStore.clearRememberData();
|
||||||
|
|
||||||
|
return _.extend({}, state, {
|
||||||
|
logged: false,
|
||||||
|
pending: false,
|
||||||
|
failed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAutoLogin() {
|
||||||
|
return _.extend({}, state, {
|
||||||
|
initDone: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAutoLoginFail() {
|
||||||
|
sessionStore.closeSession();
|
||||||
|
sessionStore.clearRememberData();
|
||||||
|
|
||||||
|
return _.extend({}, state, {
|
||||||
|
initDone: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SessionReducer.getInstance();
|
@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
changeLanguage: stub(),
|
|
||||||
logged: stub(),
|
|
||||||
loggedOut: stub(),
|
|
||||||
listen: stub()
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
loginUser: stub(),
|
|
||||||
logoutUser: stub(),
|
|
||||||
isLoggedIn: stub().returns(false),
|
|
||||||
listen: stub()
|
|
||||||
};
|
|
@ -1,289 +0,0 @@
|
|||||||
// MOCKS
|
|
||||||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
|
||||||
const SessionStore = require('lib-app/__mocks__/session-store-mock');
|
|
||||||
const API = require('lib-app/__mocks__/api-call-mock');
|
|
||||||
const UserActions = {
|
|
||||||
checkLoginStatus: {listen: stub()},
|
|
||||||
login: {listen: stub()},
|
|
||||||
logout: {listen: stub()}
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserStore = requireUnit('stores/user-store', {
|
|
||||||
'actions/user-actions': UserActions,
|
|
||||||
'actions/common-actions': CommonActions,
|
|
||||||
'lib-app/session-store': SessionStore,
|
|
||||||
'lib-app/api-call': API
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('UserStore', function () {
|
|
||||||
it ('should inform is the user is logged based on SessionStores\' info', function () {
|
|
||||||
SessionStore.isLoggedIn.returns(true);
|
|
||||||
expect(UserStore.isLoggedIn()).to.equal(true);
|
|
||||||
SessionStore.isLoggedIn.returns(false);
|
|
||||||
expect(UserStore.isLoggedIn()).to.equal(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when login user', function () {
|
|
||||||
it('should call /user/login api path', function () {
|
|
||||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
|
||||||
|
|
||||||
UserStore.loginUser(mockLoginData);
|
|
||||||
expect(API.call).to.have.been.calledWith({
|
|
||||||
path: 'user/login',
|
|
||||||
data: mockLoginData
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create session, trigger success event and inform common action when having a successful login', function () {
|
|
||||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
|
||||||
let mockSuccessData = {
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
userId: 12,
|
|
||||||
token: 'RANDOM_TOKEN'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spy(UserStore, 'trigger');
|
|
||||||
CommonActions.logged.reset();
|
|
||||||
SessionStore.createSession.reset();
|
|
||||||
API.call.returns({
|
|
||||||
then: (resolve) => {resolve(mockSuccessData)}
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.loginUser(mockLoginData);
|
|
||||||
|
|
||||||
expect(SessionStore.storeRememberData).to.have.not.been.called;
|
|
||||||
expect(SessionStore.createSession).to.have.been.calledWith(12, 'RANDOM_TOKEN');
|
|
||||||
expect(UserStore.trigger).to.have.been.calledWith('LOGIN_SUCCESS');
|
|
||||||
expect(CommonActions.logged).to.have.been.called;
|
|
||||||
UserStore.trigger.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trigger fail event if login fails', function () {
|
|
||||||
let mockLoginData = {email: 'mock', password: 'mock'};
|
|
||||||
|
|
||||||
spy(UserStore, 'trigger');
|
|
||||||
API.call.returns({
|
|
||||||
then: (resolve, reject) => {reject()}
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.loginUser(mockLoginData);
|
|
||||||
|
|
||||||
expect(UserStore.trigger).to.have.been.calledWith('LOGIN_FAIL');
|
|
||||||
UserStore.trigger.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should store remember data if remember is true', function () {
|
|
||||||
let mockLoginData = {email: 'mock', password: 'mock', remember: true};
|
|
||||||
let mockSuccessData = {
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
userId: 12,
|
|
||||||
token: 'RANDOM_TOKEN',
|
|
||||||
rememberToken: 'RANDOM_TOKEN_2',
|
|
||||||
rememberExpiration: 20150822
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spy(UserStore, 'trigger');
|
|
||||||
CommonActions.logged.reset();
|
|
||||||
SessionStore.createSession.reset();
|
|
||||||
API.call.returns({
|
|
||||||
then: (resolve) => {resolve(mockSuccessData)}
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.loginUser(mockLoginData);
|
|
||||||
|
|
||||||
expect(SessionStore.storeRememberData).to.have.been.calledWith({
|
|
||||||
token: 'RANDOM_TOKEN_2',
|
|
||||||
userId: 12,
|
|
||||||
expiration: 20150822
|
|
||||||
});
|
|
||||||
expect(SessionStore.createSession).to.have.been.calledWith(12, 'RANDOM_TOKEN');
|
|
||||||
expect(UserStore.trigger).to.have.been.calledWith('LOGIN_SUCCESS');
|
|
||||||
expect(CommonActions.logged).to.have.been.called;
|
|
||||||
UserStore.trigger.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when login out', function () {
|
|
||||||
|
|
||||||
it('should call /user/logout api path', function () {
|
|
||||||
API.call = stub().returns({
|
|
||||||
then: (resolve) => {resolve()}
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.logoutUser();
|
|
||||||
expect(API.call).to.have.been.calledWith({
|
|
||||||
path: 'user/logout'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should delete session, trigger LOGOUT event and inform common action of logout', function () {
|
|
||||||
API.call = stub().returns({
|
|
||||||
then: (resolve) => {resolve()}
|
|
||||||
});
|
|
||||||
spy(UserStore, 'trigger');
|
|
||||||
|
|
||||||
UserStore.logoutUser();
|
|
||||||
expect(SessionStore.closeSession).to.have.been.called;
|
|
||||||
expect(UserStore.trigger).to.have.been.calledWith('LOGOUT');
|
|
||||||
expect(CommonActions.loggedOut).to.have.been.called;
|
|
||||||
UserStore.trigger.restore()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when calling initSession', function () {{
|
|
||||||
|
|
||||||
it('should check if session is active in the API', function () {
|
|
||||||
let mockSuccessData = {
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
sessionActive: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
API.call = stub().returns({
|
|
||||||
then: (resolve) => {resolve(mockSuccessData)}
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.initSession();
|
|
||||||
|
|
||||||
expect(API.call).to.have.been.calledWith({
|
|
||||||
path: 'user/check-session',
|
|
||||||
data: {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and no session is active', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
let mockSuccessData = {
|
|
||||||
status: 'success',
|
|
||||||
data: {
|
|
||||||
sessionActive: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
API.call = stub().returns({
|
|
||||||
then: (resolve) => {resolve(mockSuccessData)}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log out and delete remember data if expired', function () {
|
|
||||||
SessionStore.isRememberDataExpired.returns(true);
|
|
||||||
SessionStore.clearRememberData.reset();
|
|
||||||
|
|
||||||
UserStore.initSession();
|
|
||||||
|
|
||||||
expect(SessionStore.clearRememberData).to.have.been.called;
|
|
||||||
expect(SessionStore.closeSession).to.have.been.called;
|
|
||||||
expect(CommonActions.loggedOut).to.have.been.called;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should login with remember data', function () {
|
|
||||||
SessionStore.isRememberDataExpired.returns(false);
|
|
||||||
SessionStore.getRememberData.returns({
|
|
||||||
userId: 'REMEMBER_USER_ID',
|
|
||||||
token: 'REMEMBER_TOKEN',
|
|
||||||
expiration: 20160721
|
|
||||||
});
|
|
||||||
|
|
||||||
UserStore.initSession();
|
|
||||||
|
|
||||||
expect(API.call).to.have.been.calledWithMatch({
|
|
||||||
path: 'user/login',
|
|
||||||
data: {
|
|
||||||
userId: 'REMEMBER_USER_ID',
|
|
||||||
rememberToken: 'REMEMBER_TOKEN'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}});
|
|
||||||
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,29 +0,0 @@
|
|||||||
import Reflux from 'reflux';
|
|
||||||
|
|
||||||
import CommonActions from 'actions/common-actions';
|
|
||||||
|
|
||||||
let CommonStore = Reflux.createStore({
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.language = 'us';
|
|
||||||
|
|
||||||
this.listenTo(CommonActions.changeLanguage, this.changeLanguage);
|
|
||||||
this.listenTo(CommonActions.logged, this.logged);
|
|
||||||
this.listenTo(CommonActions.loggedOut, this.loggedOut);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeLanguage(lang) {
|
|
||||||
this.language = lang;
|
|
||||||
this.trigger('i18n');
|
|
||||||
},
|
|
||||||
|
|
||||||
logged() {
|
|
||||||
this.trigger('logged');
|
|
||||||
},
|
|
||||||
|
|
||||||
loggedOut() {
|
|
||||||
this.trigger('loggedOut');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default CommonStore;
|
|
@ -1,131 +0,0 @@
|
|||||||
const Reflux = require('reflux');
|
|
||||||
const API = require('lib-app/api-call');
|
|
||||||
const sessionStore = require('lib-app/session-store');
|
|
||||||
|
|
||||||
const UserActions = require('actions/user-actions');
|
|
||||||
const CommonActions = require('actions/common-actions');
|
|
||||||
|
|
||||||
const UserStore = Reflux.createStore({
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.user = null;
|
|
||||||
|
|
||||||
this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus);
|
|
||||||
this.listenTo(UserActions.login, this.loginUser);
|
|
||||||
this.listenTo(UserActions.signup, this.signupUser);
|
|
||||||
this.listenTo(UserActions.logout, this.logoutUser);
|
|
||||||
this.listenTo(UserActions.recoverPassword, this.recoverPassword);
|
|
||||||
this.listenTo(UserActions.sendRecoverPassword, this.sendRecoverPassword);
|
|
||||||
},
|
|
||||||
|
|
||||||
initSession() {
|
|
||||||
return API.call({
|
|
||||||
path: 'user/check-session',
|
|
||||||
data: {}
|
|
||||||
}).then(this.tryLoginIfSessionIsInactive);
|
|
||||||
},
|
|
||||||
|
|
||||||
tryLoginIfSessionIsInactive(result) {
|
|
||||||
if (!result.data.sessionActive) {
|
|
||||||
if (sessionStore.isRememberDataExpired()) {
|
|
||||||
return this.logoutUser();
|
|
||||||
} else {
|
|
||||||
return this.loginWithRememberData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
signupUser(signupData) {
|
|
||||||
return API.call({
|
|
||||||
path: 'user/signup',
|
|
||||||
data: signupData
|
|
||||||
}).then(this.handleSignupSuccess, this.handleSignupFail);
|
|
||||||
},
|
|
||||||
|
|
||||||
loginUser(loginData) {
|
|
||||||
let onSuccessLogin = (loginData.remember) ? this.handleLoginSuccessWithRemember : this.handleLoginSuccess;
|
|
||||||
let onFailedLogin = (loginData.isAutomatic) ? null : this.handleLoginFail;
|
|
||||||
|
|
||||||
return API.call({
|
|
||||||
path: 'user/login',
|
|
||||||
data: loginData
|
|
||||||
}).then(onSuccessLogin, onFailedLogin);
|
|
||||||
},
|
|
||||||
|
|
||||||
logoutUser() {
|
|
||||||
return API.call({
|
|
||||||
path: 'user/logout'
|
|
||||||
}).then(() => {
|
|
||||||
sessionStore.closeSession();
|
|
||||||
sessionStore.clearRememberData();
|
|
||||||
CommonActions.loggedOut();
|
|
||||||
this.trigger('LOGOUT');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
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');
|
|
||||||
}, () => {
|
|
||||||
this.trigger('INVALID_RECOVER')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
isLoggedIn() {
|
|
||||||
return sessionStore.isLoggedIn();
|
|
||||||
},
|
|
||||||
|
|
||||||
loginWithRememberData() {
|
|
||||||
let rememberData = sessionStore.getRememberData();
|
|
||||||
|
|
||||||
return this.loginUser({
|
|
||||||
userId: rememberData.userId,
|
|
||||||
rememberToken: rememberData.token,
|
|
||||||
isAutomatic: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLoginSuccessWithRemember(result) {
|
|
||||||
sessionStore.storeRememberData({
|
|
||||||
token: result.data.rememberToken,
|
|
||||||
userId: result.data.userId,
|
|
||||||
expiration: result.data.rememberExpiration
|
|
||||||
});
|
|
||||||
|
|
||||||
this.handleLoginSuccess(result)
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLoginSuccess(result) {
|
|
||||||
sessionStore.createSession(result.data.userId, result.data.token);
|
|
||||||
CommonActions.logged();
|
|
||||||
this.trigger('LOGIN_SUCCESS');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLoginFail() {
|
|
||||||
this.trigger('LOGIN_FAIL');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSignupSuccess() {
|
|
||||||
this.trigger('SIGNUP_SUCCESS');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSignupFail() {
|
|
||||||
this.trigger('SIGNUP_FAIL');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default UserStore;
|
|
Loading…
x
Reference in New Issue
Block a user