commit
c13e9a26c7
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"optional": ["es7.classProperties"]
|
||||
}
|
|
@ -38,7 +38,7 @@ function buildScript(file, watch) {
|
|||
bundler.on('update', rebundle);
|
||||
}
|
||||
|
||||
bundler.transform(babelify);
|
||||
bundler.transform(babelify, {'optional': ['es7.classProperties']});
|
||||
bundler.transform(debowerify);
|
||||
|
||||
function rebundle() {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^5.8.22",
|
||||
"babel-plugin-transform-class-properties": "^6.11.5",
|
||||
"babel-register": "^6.7.2",
|
||||
"babelify": "^6.1.x",
|
||||
"browser-sync": "^2.7.13",
|
||||
|
@ -63,7 +64,10 @@
|
|||
"react-dom": "^15.0.1",
|
||||
"react-google-recaptcha": "^0.5.2",
|
||||
"react-motion": "^0.3.0",
|
||||
"react-redux": "^4.4.5",
|
||||
"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,5 +0,0 @@
|
|||
export default {
|
||||
changeLanguage: stub(),
|
||||
logged: stub(),
|
||||
loggedOut: stub()
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
changeLanguage: stub()
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
login: stub(),
|
||||
logout: stub(),
|
||||
initSession: stub()
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
export default {
|
||||
checkLoginStatus: stub(),
|
||||
sendRecoverPassword: stub(),
|
||||
recoverPassword: stub(),
|
||||
login: stub(),
|
||||
logout: stub()
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
const sessionStoreMock = require('lib-app/__mocks__/session-store-mock');
|
||||
const APICallMock = {
|
||||
call: stub().returns('API_RESULT')
|
||||
};
|
||||
|
||||
const ConfigActions = requireUnit('actions/config-actions', {
|
||||
'lib-app/api-call': APICallMock,
|
||||
'lib-app/session-store': sessionStoreMock
|
||||
});
|
||||
|
||||
describe('Config Actions,', function () {
|
||||
|
||||
describe('init action', function () {
|
||||
it('should return INIT_CONFIGS_FULFILLED with configs if it is already retrieved', function () {
|
||||
sessionStoreMock.areConfigsStored.returns(true);
|
||||
sessionStoreMock.getConfigs.returns({
|
||||
config1: 'CONFIG_1',
|
||||
config2: 'CONFIG_2'
|
||||
});
|
||||
|
||||
expect(ConfigActions.init()).to.deep.equal({
|
||||
type: 'INIT_CONFIGS_FULFILLED',
|
||||
payload: {
|
||||
data: {
|
||||
config1: 'CONFIG_1',
|
||||
config2: 'CONFIG_2'
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('should return INIT_CONFIGS with API_RESULT if it is not retrieved', function () {
|
||||
APICallMock.call.reset();
|
||||
sessionStoreMock.areConfigsStored.returns(false);
|
||||
sessionStoreMock.getConfigs.returns({
|
||||
config1: 'CONFIG_1',
|
||||
config2: 'CONFIG_2'
|
||||
});
|
||||
|
||||
expect(ConfigActions.init()).to.deep.equal({
|
||||
type: 'INIT_CONFIGS',
|
||||
payload: 'API_RESULT'
|
||||
});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/system/get-configs',
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeLanguage action', function () {
|
||||
it('should trigger CHANGE_LANGUAGE with new language', function () {
|
||||
expect(ConfigActions.changeLanguage('es')).to.deep.equal({
|
||||
type: 'CHANGE_LANGUAGE',
|
||||
payload: 'es'
|
||||
});
|
||||
|
||||
expect(ConfigActions.changeLanguage('de')).to.deep.equal({
|
||||
type: 'CHANGE_LANGUAGE',
|
||||
payload: 'de'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,144 @@
|
|||
const sessionStoreMock = require('lib-app/__mocks__/session-store-mock');
|
||||
const APICallMock = require('lib-app/__mocks__/api-call-mock');
|
||||
const storeMock = {
|
||||
dispatch: stub()
|
||||
};
|
||||
|
||||
const SessionActions = requireUnit('actions/session-actions', {
|
||||
'lib-app/api-call': APICallMock,
|
||||
'lib-app/session-store': sessionStoreMock,
|
||||
'app/store': storeMock
|
||||
});
|
||||
|
||||
describe('Session Actions,', function () {
|
||||
APICallMock.call.returns('API_RESULT');
|
||||
|
||||
describe('login action', function () {
|
||||
it('should return LOGIN with with API_RESULT promise', function () {
|
||||
APICallMock.call.reset();
|
||||
let loginData = {
|
||||
email: 'SOME_EMAIL',
|
||||
password: 'SOME_PASSWORD',
|
||||
remember: false
|
||||
};
|
||||
|
||||
expect(SessionActions.login(loginData)).to.deep.equal({
|
||||
type: 'LOGIN',
|
||||
payload: 'API_RESULT'
|
||||
});
|
||||
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/login',
|
||||
data: loginData
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoLogin action', function () {
|
||||
it('should return LOGIN_AUTO with remember data from sessionStore', function () {
|
||||
APICallMock.call.reset();
|
||||
sessionStoreMock.getRememberData.returns({
|
||||
token: 'SOME_TOKEN',
|
||||
userId: 'SOME_ID',
|
||||
expiration: 'SOME_EXPIRATION'
|
||||
});
|
||||
|
||||
expect(SessionActions.autoLogin()).to.deep.equal({
|
||||
type: 'LOGIN_AUTO',
|
||||
payload: 'API_RESULT'
|
||||
});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/login',
|
||||
data: {
|
||||
rememberToken: 'SOME_TOKEN',
|
||||
userId: 'SOME_ID',
|
||||
isAutomatic: true
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout action', function () {
|
||||
it('should return LOGOUT and call /user/logout', function () {
|
||||
APICallMock.call.reset();
|
||||
|
||||
expect(SessionActions.logout()).to.deep.equal({
|
||||
type: 'LOGOUT',
|
||||
payload: 'API_RESULT'
|
||||
});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/logout',
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initSession action', function () {
|
||||
beforeEach(function () {
|
||||
APICallMock.call.returns({
|
||||
then: function (resolve) {
|
||||
resolve({
|
||||
data: {
|
||||
sessionActive: false
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
APICallMock.call.reset();
|
||||
storeMock.dispatch.reset();
|
||||
});
|
||||
|
||||
after(function () {
|
||||
APICallMock.call.returns(new Promise(function (resolve) {
|
||||
resolve({
|
||||
data: {
|
||||
sessionActive: true
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return CHECK_SESSION and dispatch SESSION_ACTIVE if session is active', function () {
|
||||
APICallMock.call.returns({
|
||||
then: function (resolve) {
|
||||
resolve({
|
||||
data: {
|
||||
sessionActive: true
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
expect(storeMock.dispatch).to.have.been.calledWith({type: 'SESSION_CHECKED'});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/check-session',
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return CHECK_SESSION and dispatch LOGOUT_FULFILLED if session is not active and no remember data', function () {
|
||||
sessionStoreMock.isRememberDataExpired.returns(true);
|
||||
|
||||
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
expect(storeMock.dispatch).to.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/check-session',
|
||||
data: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return CHECK_SESSION and dispatch LOGIN_AUTO if session is not active but remember data exists', function () {
|
||||
sessionStoreMock.isRememberDataExpired.returns(false);
|
||||
|
||||
expect(SessionActions.initSession().type).to.equal('CHECK_SESSION');
|
||||
expect(storeMock.dispatch).to.not.have.been.calledWith({type: 'LOGOUT_FULFILLED'});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/check-session',
|
||||
data: {}
|
||||
});
|
||||
|
||||
expect(storeMock.dispatch).to.have.been.calledWith(SessionActions.autoLogin());
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
import Reflux from 'reflux';
|
||||
|
||||
let CommonActions = Reflux.createActions([
|
||||
'changeLanguage',
|
||||
'logged',
|
||||
'loggedOut'
|
||||
]);
|
||||
|
||||
export default CommonActions;
|
|
@ -0,0 +1,30 @@
|
|||
import API from 'lib-app/api-call';
|
||||
import sessionStore from 'lib-app/session-store';
|
||||
|
||||
export default {
|
||||
init() {
|
||||
if (sessionStore.areConfigsStored()) {
|
||||
return {
|
||||
type: 'INIT_CONFIGS_FULFILLED',
|
||||
payload: {
|
||||
data: sessionStore.getConfigs()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'INIT_CONFIGS',
|
||||
payload: API.call({
|
||||
path: '/system/get-configs',
|
||||
data: {}
|
||||
})
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
changeLanguage(newLanguage) {
|
||||
return {
|
||||
type: 'CHANGE_LANGUAGE',
|
||||
payload: newLanguage
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
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: 'LOGOUT',
|
||||
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({
|
||||
type: 'LOGOUT_FULFILLED'
|
||||
});
|
||||
} 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,21 @@
|
|||
import React from 'react';
|
||||
import Reflux from 'reflux';
|
||||
import _ from 'lodash';
|
||||
import { connect } from 'react-redux'
|
||||
import { browserHistory } from 'react-router';
|
||||
|
||||
import CommonStore from 'stores/common-store';
|
||||
|
||||
const App = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
class App extends React.Component {
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
};
|
||||
|
||||
mixins: [Reflux.listenTo(CommonStore, 'onCommonStoreChanged')],
|
||||
componentWillMount() {
|
||||
this.redirectIfPathIsNotValid(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.redirectIfPathIsNotValid(nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -18,19 +23,33 @@ const App = React.createClass({
|
|||
{React.cloneElement(this.props.children, {})}
|
||||
</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')}
|
||||
redirectIfPathIsNotValid(props) {
|
||||
const validations = {
|
||||
languageChanged: props.config.language !== this.props.config.language,
|
||||
loggedIn: !_.includes(props.location.pathname, '/app/dashboard') && props.session.logged,
|
||||
loggedOut: _.includes(props.location.pathname, '/app/dashboard') && !props.session.logged
|
||||
};
|
||||
|
||||
if (handle[change]) {
|
||||
handle[change]();
|
||||
if (validations.languageChanged) {
|
||||
browserHistory.push(props.location.pathname);
|
||||
}
|
||||
|
||||
if (validations.loggedOut) {
|
||||
browserHistory.push('/app');
|
||||
}
|
||||
|
||||
if (validations.loggedIn) {
|
||||
browserHistory.push('/app/dashboard');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default connect((store) => {
|
||||
return {
|
||||
config: store.config,
|
||||
session: store.session,
|
||||
routing: store.routing
|
||||
};
|
||||
})(App);
|
|
@ -1,27 +1,32 @@
|
|||
const React = require('react');
|
||||
const {Router, Route, IndexRoute, browserHistory} = require('react-router');
|
||||
import React from 'react';
|
||||
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
|
||||
import { syncHistoryWithStore } from 'react-router-redux';
|
||||
|
||||
const App = require('app/App');
|
||||
const DemoPage = require('app/demo/components-demo-page');
|
||||
import store from 'app/store';
|
||||
|
||||
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');
|
||||
import App from 'app/App';
|
||||
import DemoPage from 'app/demo/components-demo-page';
|
||||
|
||||
const DashboardLayout = require('app/main/dashboard/dashboard-layout');
|
||||
import MainLayout from 'app/main/main-layout';
|
||||
import MainHomePage from 'app/main/main-home/main-home-page';
|
||||
import MainSignUpPage from 'app/main/main-signup/main-signup-page';
|
||||
import MainRecoverPasswordPage from 'app/main/main-recover-password/main-recover-password-page';
|
||||
|
||||
const DashboardListTicketsPage = require('app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page');
|
||||
const DashboardListArticlesPage = require('app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page');
|
||||
import DashboardLayout from 'app/main/dashboard/dashboard-layout';
|
||||
|
||||
const DashboardCreateTicketPage = require('app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page');
|
||||
const DashboardEditProfilePage = require('app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page');
|
||||
import DashboardListTicketsPage from 'app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page';
|
||||
import DashboardListArticlesPage from 'app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page';
|
||||
|
||||
const DashboardArticlePage = require('app/main/dashboard/dashboard-article/dashboard-article-page');
|
||||
const DashboardTicketPage = require('app/main/dashboard/dashboard-ticket/dashboard-ticket-page');
|
||||
import DashboardCreateTicketPage from 'app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page';
|
||||
import DashboardEditProfilePage from 'app/main/dashboard/dashboard-edit-profile/dashboard-edit-profile-page';
|
||||
|
||||
import DashboardArticlePage from 'app/main/dashboard/dashboard-article/dashboard-article-page';
|
||||
import DashboardTicketPage from 'app/main/dashboard/dashboard-ticket/dashboard-ticket-page';
|
||||
|
||||
const history = syncHistoryWithStore(browserHistory, store);
|
||||
|
||||
export default (
|
||||
<Router history={browserHistory}>
|
||||
<Router history={history}>
|
||||
<Route component={App} path='/'>
|
||||
<Route path='/app' component={MainLayout}>
|
||||
<IndexRoute component={MainHomePage} />
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
dispatch: stub(),
|
||||
getState: stub().returns({
|
||||
config: {},
|
||||
session: {},
|
||||
routing: {}
|
||||
})
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
const CommonStore = require('stores/__mocks__/common-store-mock');
|
||||
|
||||
const App = requireUnit('app/App', {
|
||||
'store/common-store': CommonStore
|
||||
});
|
||||
|
||||
describe('App component', function () {
|
||||
describe('when reacting to CommonStore', function () {
|
||||
let app;
|
||||
|
||||
beforeEach(function () {
|
||||
app = TestUtils.renderIntoDocument(
|
||||
<App><span>MOCK_CHILD</span></App>
|
||||
);
|
||||
|
||||
app.context = {
|
||||
router: {
|
||||
push: stub()
|
||||
},
|
||||
|
||||
location: {
|
||||
pathname: 'MOCK_PATH'
|
||||
}
|
||||
};
|
||||
|
||||
spy(app, 'forceUpdate');
|
||||
});
|
||||
|
||||
it('should update with i18n', function () {
|
||||
app.context.router.push.reset();
|
||||
app.forceUpdate.reset();
|
||||
app.onCommonStoreChanged('i18n');
|
||||
expect(app.context.router.push).to.have.been.calledWith('MOCK_PATH');
|
||||
});
|
||||
|
||||
it('should redirect when logged in', function () {
|
||||
app.context.router.push.reset();
|
||||
app.onCommonStoreChanged('logged');
|
||||
expect(app.context.router.push).to.have.been.calledWith('/app/dashboard');
|
||||
});
|
||||
|
||||
it('should redirect when logged out', function () {
|
||||
app.context.router.push.reset();
|
||||
app.onCommonStoreChanged('loggedOut');
|
||||
expect(app.context.router.push).to.have.been.calledWith('/app');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react';
|
||||
import {render} from 'react-dom'
|
||||
import Router from 'react-router';
|
||||
import UserStore from 'stores/user-store';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import SessionActions from 'actions/session-actions';
|
||||
import ConfigActions from 'actions/config-actions';
|
||||
import routes from './Routes';
|
||||
import store from './store';
|
||||
|
||||
if ( process.env.NODE_ENV !== 'production' ) {
|
||||
// Enable React devtools
|
||||
|
@ -14,9 +16,18 @@ if (noFixtures === 'disabled') {
|
|||
require('lib-app/fixtures-loader');
|
||||
}
|
||||
|
||||
let onSessionInit = function () {
|
||||
render(routes, document.getElementById('app'));
|
||||
let renderApplication = function () {
|
||||
render(<Provider store={store}>{routes}</Provider>, document.getElementById('app'));
|
||||
};
|
||||
window.store = store;
|
||||
store.dispatch(ConfigActions.init());
|
||||
store.dispatch(SessionActions.initSession());
|
||||
|
||||
UserStore.initSession().then(onSessionInit, onSessionInit);
|
||||
let unsubscribe = store.subscribe(() => {
|
||||
console.log(store.getState());
|
||||
|
||||
if (store.getState().session.initDone) {
|
||||
unsubscribe();
|
||||
renderApplication();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
|
||||
const DashboardLayout = requireUnit('app/main/dashboard/dashboard-layout', {
|
||||
'actions/common-actions': CommonActions,
|
||||
'stores/user-store': UserStore,
|
||||
'app/main/dashboard/dashboard-menu': ReactMock()
|
||||
});
|
||||
|
||||
|
||||
describe('Dashboard page', function () {
|
||||
|
||||
afterEach(function () {
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
});
|
||||
|
||||
it('should trigger common action if user is not logged', function () {
|
||||
CommonActions.loggedOut.reset();
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
|
||||
TestUtils.renderIntoDocument(<DashboardLayout />);
|
||||
expect(CommonActions.loggedOut).to.have.been.called;
|
||||
});
|
||||
|
||||
it('should not trigger common action user if is logged', function () {
|
||||
CommonActions.loggedOut.reset();
|
||||
UserStore.isLoggedIn.returns(true);
|
||||
|
||||
TestUtils.renderIntoDocument(<DashboardLayout />);
|
||||
expect(CommonActions.loggedOut).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardArticlePage = React.createClass({
|
||||
class DashboardArticlePage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardArticlePage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardArticlePage;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardCreateTicketPage = React.createClass({
|
||||
class DashboardCreateTicketPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardCreateTicketPage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardCreateTicketPage;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardEditProfilePage = React.createClass({
|
||||
class DashboardEditProfilePage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardEditProfilePage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardEditProfilePage;
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
import React from 'react';
|
||||
|
||||
import UserStore from 'stores/user-store';
|
||||
import CommonActions from 'actions/common-actions';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import DashboardMenu from 'app/main/dashboard/dashboard-menu';
|
||||
|
||||
const DashboardLayout = React.createClass({
|
||||
|
||||
componentWillMount() {
|
||||
if (!UserStore.isLoggedIn()) {
|
||||
CommonActions.loggedOut();
|
||||
}
|
||||
},
|
||||
class DashboardLayout extends React.Component {
|
||||
|
||||
render() {
|
||||
return (UserStore.isLoggedIn()) ? (
|
||||
return (this.props.session.logged) ? (
|
||||
<div>
|
||||
<div><DashboardMenu location={this.props.location} /></div>
|
||||
<div>{this.props.children}</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardLayout;
|
||||
export default connect((store) => {
|
||||
return {
|
||||
session: store.session
|
||||
};
|
||||
})(DashboardLayout);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardListArticlesPage = React.createClass({
|
||||
class DashboardListArticlesPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardListArticlesPage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardListArticlesPage;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardListTicketsPage = React.createClass({
|
||||
class DashboardListTicketsPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardListTicketsPage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardListTicketsPage;
|
||||
|
|
|
@ -10,48 +10,48 @@ let dashboardRoutes = [
|
|||
{ path: '/app/dashboard/edit-profile', text: 'Edit Profile' }
|
||||
];
|
||||
|
||||
const DashboardMenu = React.createClass({
|
||||
contextTypes: {
|
||||
class DashboardMenu extends React.Component {
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu {...this.getProps()} />
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
return {
|
||||
items: this.getMenuItems(),
|
||||
selectedIndex: this.getSelectedIndex(),
|
||||
onItemClick: this.goToPathByIndex
|
||||
onItemClick: this.goToPathByIndex.bind(this)
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getMenuItems: function () {
|
||||
return dashboardRoutes.map(this.getMenuItem);
|
||||
},
|
||||
getMenuItems() {
|
||||
return dashboardRoutes.map(this.getMenuItem.bind(this));
|
||||
}
|
||||
|
||||
getMenuItem(item) {
|
||||
return {
|
||||
content: item.text
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
let pathname = this.props.location.pathname;
|
||||
|
||||
return _.findIndex(dashboardRoutes, {path: pathname});
|
||||
},
|
||||
}
|
||||
|
||||
goToPathByIndex(itemIndex) {
|
||||
this.context.router.push(dashboardRoutes[itemIndex].path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardMenu;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
const DashboardTicketPage = React.createClass({
|
||||
class DashboardTicketPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -9,6 +9,6 @@ const DashboardTicketPage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DashboardTicketPage;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const UserActions = require('actions/__mocks__/user-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
const SessionActionsMock = require('actions/__mocks__/session-actions-mock');
|
||||
const APICallMock = require('lib-app/__mocks__/api-call-mock');
|
||||
|
||||
const SubmitButton = ReactMock();
|
||||
const Button = ReactMock();
|
||||
|
@ -11,6 +11,9 @@ const Widget = ReactMock();
|
|||
const WidgetTransition = ReactMock();
|
||||
|
||||
const MainHomePageLoginWidget = requireUnit('app/main/main-home/main-home-page-login-widget', {
|
||||
'react-redux': ReduxMock,
|
||||
'actions/session-actions': SessionActionsMock,
|
||||
'lib-app/api-call': APICallMock,
|
||||
'core-components/submit-button': SubmitButton,
|
||||
'core-components/button': Button,
|
||||
'core-components/input': Input,
|
||||
|
@ -18,9 +21,7 @@ const MainHomePageLoginWidget = requireUnit('app/main/main-home/main-home-page-l
|
|||
'core-components/checkbox': Checkbox,
|
||||
'core-components/message': Message,
|
||||
'core-components/widget': Widget,
|
||||
'core-components/widget-transition': WidgetTransition,
|
||||
'actions/user-actions': UserActions,
|
||||
'stores/user-store': UserStore
|
||||
'core-components/widget-transition': WidgetTransition
|
||||
});
|
||||
|
||||
|
||||
|
@ -29,9 +30,12 @@ describe('Login/Recover Widget', function () {
|
|||
let loginWidget, loginForm, widgetTransition, inputs, checkbox, component,
|
||||
forgotPasswordButton, submitButton;
|
||||
|
||||
beforeEach(function () {
|
||||
component = TestUtils.renderIntoDocument(
|
||||
<MainHomePageLoginWidget />
|
||||
let dispatch = stub();
|
||||
|
||||
function renderComponent(props = {session: {pending: false, failed: false}}) {
|
||||
|
||||
component = reRenderIntoDocument(
|
||||
<MainHomePageLoginWidget dispatch={dispatch} {...props}/>
|
||||
);
|
||||
widgetTransition = TestUtils.scryRenderedComponentsWithType(component, WidgetTransition)[0];
|
||||
loginWidget = TestUtils.scryRenderedComponentsWithType(component, Widget)[0];
|
||||
|
@ -48,7 +52,9 @@ describe('Login/Recover Widget', function () {
|
|||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(renderComponent);
|
||||
|
||||
it('should control form errors by prop', function () {
|
||||
expect(loginForm.props.errors).to.deep.equal({});
|
||||
|
@ -58,25 +64,38 @@ describe('Login/Recover Widget', function () {
|
|||
|
||||
it('should trigger login action when submitted', function () {
|
||||
let mockSubmitData = {email: 'MOCK_VALUE', password: 'MOCK_VALUE'};
|
||||
let actionMock = {};
|
||||
SessionActionsMock.login.returns(actionMock);
|
||||
dispatch.reset();
|
||||
|
||||
UserActions.login.reset();
|
||||
loginForm.props.onSubmit(mockSubmitData);
|
||||
expect(UserActions.login).to.have.been.calledWith(mockSubmitData);
|
||||
expect(SessionActionsMock.login).to.have.been.calledWith(mockSubmitData);
|
||||
expect(dispatch).to.have.been.calledWith(actionMock);
|
||||
});
|
||||
|
||||
it('should set loading true in the form when submitted', function () {
|
||||
let mockSubmitData = {email: 'MOCK_VALUE', password: 'MOCK_VALUE'};
|
||||
|
||||
loginForm.props.onSubmit(mockSubmitData);
|
||||
it('should set loading true if session login is pending', function () {
|
||||
expect(loginForm.props.loading).to.equal(false);
|
||||
renderComponent({
|
||||
session: {
|
||||
pending: true,
|
||||
failed: false
|
||||
}
|
||||
});
|
||||
expect(loginForm.props.loading).to.equal(true);
|
||||
});
|
||||
|
||||
it('should add error and stop loading if login fails', function () {
|
||||
component.refs.loginForm.refs.password.focus.reset();
|
||||
component.onUserStoreChanged('LOGIN_FAIL');
|
||||
component.setState({
|
||||
loginFormErrors: {}
|
||||
});
|
||||
renderComponent({
|
||||
session: {
|
||||
pending: false,
|
||||
failed: true
|
||||
}
|
||||
});
|
||||
expect(loginForm.props.errors).to.deep.equal({password: 'Invalid password'});
|
||||
expect(loginForm.props.loading).to.equal(false);
|
||||
expect(component.refs.loginForm.refs.password.focus).to.have.been.called;
|
||||
});
|
||||
|
||||
it('should show back side if \'Forgot your password?\' link is clicked', function () {
|
||||
|
@ -90,9 +109,11 @@ describe('Login/Recover Widget', function () {
|
|||
let recoverWidget, recoverForm, widgetTransition, emailInput, component,
|
||||
backToLoginButton, submitButton;
|
||||
|
||||
let dispatch = stub();
|
||||
|
||||
beforeEach(function () {
|
||||
component = TestUtils.renderIntoDocument(
|
||||
<MainHomePageLoginWidget />
|
||||
<MainHomePageLoginWidget dispatch={dispatch} session={{pending: false, failed: false}} />
|
||||
);
|
||||
widgetTransition = TestUtils.scryRenderedComponentsWithType(component, WidgetTransition)[0];
|
||||
recoverWidget = TestUtils.scryRenderedComponentsWithType(component, Widget)[1];
|
||||
|
@ -116,12 +137,15 @@ describe('Login/Recover Widget', function () {
|
|||
expect(recoverForm.props.errors).to.deep.equal({email: 'MOCK_ERROR'});
|
||||
});
|
||||
|
||||
it('should trigger sendRecoverPassword action when submitted', function () {
|
||||
it('should call sendRecoverPassword when submitted', function () {
|
||||
let mockSubmitData = {email: 'MOCK_VALUE'};
|
||||
APICallMock.call.reset();
|
||||
|
||||
UserActions.sendRecoverPassword.reset();
|
||||
recoverForm.props.onSubmit(mockSubmitData);
|
||||
expect(UserActions.sendRecoverPassword).to.have.been.calledWith(mockSubmitData);
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/send-recover-password',
|
||||
data: mockSubmitData
|
||||
});
|
||||
});
|
||||
|
||||
it('should set loading true in the form when submitted', function () {
|
||||
|
@ -133,7 +157,8 @@ describe('Login/Recover Widget', function () {
|
|||
|
||||
it('should add error and stop loading when send recover fails', function () {
|
||||
component.refs.recoverForm.refs.email.focus.reset();
|
||||
component.onUserStoreChanged('SEND_RECOVER_FAIL');
|
||||
|
||||
component.onRecoverPasswordFail();
|
||||
expect(recoverForm.props.errors).to.deep.equal({email: 'Email does not exist'});
|
||||
expect(recoverForm.props.loading).to.equal(false);
|
||||
expect(component.refs.recoverForm.refs.email.focus).to.have.been.called;
|
||||
|
@ -143,7 +168,7 @@ describe('Login/Recover Widget', function () {
|
|||
let message = TestUtils.scryRenderedComponentsWithType(component, Message)[0];
|
||||
expect(message).to.equal(undefined);
|
||||
|
||||
component.onUserStoreChanged('SEND_RECOVER_SUCCESS');
|
||||
component.onRecoverPasswordSent();
|
||||
message = TestUtils.scryRenderedComponentsWithType(component, Message)[0];
|
||||
|
||||
expect(recoverForm.props.loading).to.equal(false);
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
|
||||
const MainHomePage = requireUnit('app/main/main-home/main-home-page', {
|
||||
'actions/common-actions': CommonActions,
|
||||
'stores/user-store': UserStore
|
||||
});
|
||||
|
||||
|
||||
describe('Main home page', function () {
|
||||
|
||||
afterEach(function () {
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
});
|
||||
|
||||
it('should trigger common action if user is currently logged', function () {
|
||||
CommonActions.logged.reset();
|
||||
UserStore.isLoggedIn.returns(true);
|
||||
|
||||
TestUtils.renderIntoDocument(<MainHomePage />);
|
||||
expect(CommonActions.logged).to.have.been.called;
|
||||
});
|
||||
|
||||
it('should not trigger common action user if is not logged', function () {
|
||||
CommonActions.logged.reset();
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
|
||||
TestUtils.renderIntoDocument(<MainHomePage />);
|
||||
expect(CommonActions.logged).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Reflux from 'reflux';
|
||||
import {connect} from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import UserActions from 'actions/user-actions';
|
||||
import UserStore from 'stores/user-store';
|
||||
import SessionActions from 'actions/session-actions';
|
||||
import API from 'lib-app/api-call';
|
||||
import focus from 'lib-core/focus';
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
||||
|
@ -17,12 +18,12 @@ import Widget from 'core-components/widget';
|
|||
import WidgetTransition from 'core-components/widget-transition';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
let MainHomePageLoginWidget = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
||||
class MainHomePageLoginWidget extends React.Component {
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
sideToShow: 'front',
|
||||
loginFormErrors: {},
|
||||
recoverFormErrors: {},
|
||||
|
@ -30,7 +31,13 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
loadingLogin: false,
|
||||
loadingRecover: false
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!prevProps.session.failed && this.props.session.failed) {
|
||||
this.refs.loginForm.refs.password.focus();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -39,7 +46,7 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
{this.renderPasswordRecovery()}
|
||||
</WidgetTransition>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderLogin() {
|
||||
return (
|
||||
|
@ -54,12 +61,12 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
<SubmitButton type="primary">LOG IN</SubmitButton>
|
||||
</div>
|
||||
</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')}
|
||||
</Button>
|
||||
</Widget>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderPasswordRecovery() {
|
||||
return (
|
||||
|
@ -72,13 +79,13 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
|
||||
</div>
|
||||
</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')}
|
||||
</Button>
|
||||
{this.renderRecoverStatus()}
|
||||
</Widget>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
let status = null;
|
||||
|
@ -92,102 +99,98 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
}
|
||||
|
||||
return status;
|
||||
},
|
||||
}
|
||||
|
||||
getLoginFormProps() {
|
||||
return {
|
||||
loading: this.state.loadingLogin,
|
||||
loading: this.props.session.pending,
|
||||
className: 'login-widget__form',
|
||||
ref: 'loginForm',
|
||||
onSubmit:this.handleLoginFormSubmit,
|
||||
errors: this.state.loginFormErrors,
|
||||
onValidateErrors: this.handleLoginFormErrorsValidation
|
||||
onSubmit: this.onLoginFormSubmit.bind(this),
|
||||
errors: this.getLoginFormErrors(),
|
||||
onValidateErrors: this.onLoginFormErrorsValidation.bind(this)
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getRecoverFormProps() {
|
||||
return {
|
||||
loading: this.state.loadingRecover,
|
||||
className: 'login-widget__form',
|
||||
ref: 'recoverForm',
|
||||
onSubmit:this.handleForgotPasswordSubmit,
|
||||
onSubmit: this.onForgotPasswordSubmit.bind(this),
|
||||
errors: this.state.recoverFormErrors,
|
||||
onValidateErrors: this.handleRecoverFormErrorsValidation
|
||||
onValidateErrors: this.onRecoverFormErrorsValidation.bind(this)
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
handleLoginFormSubmit(formState) {
|
||||
UserActions.login(formState);
|
||||
getLoginFormErrors() {
|
||||
let errors = _.extend({}, this.state.loginFormErrors);
|
||||
|
||||
if (this.props.session.failed) {
|
||||
errors.password = i18n('ERROR_PASSWORD');
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
onLoginFormSubmit(formState) {
|
||||
this.props.dispatch(SessionActions.login(formState));
|
||||
}
|
||||
|
||||
onForgotPasswordSubmit(formState) {
|
||||
this.setState({
|
||||
loadingLogin: true
|
||||
loadingRecover: true,
|
||||
recoverSent: false
|
||||
});
|
||||
},
|
||||
|
||||
handleForgotPasswordSubmit(formState) {
|
||||
UserActions.sendRecoverPassword(formState);
|
||||
API.call({
|
||||
path: '/user/send-recover-password',
|
||||
data: formState
|
||||
}).then(this.onRecoverPasswordSent.bind(this)).catch(this.onRecoverPasswordFail.bind(this));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
loadingRecover: true
|
||||
});
|
||||
},
|
||||
|
||||
handleLoginFormErrorsValidation(errors) {
|
||||
onLoginFormErrorsValidation(errors) {
|
||||
this.setState({
|
||||
loginFormErrors: errors
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleRecoverFormErrorsValidation(errors) {
|
||||
onRecoverFormErrorsValidation(errors) {
|
||||
this.setState({
|
||||
recoverFormErrors: errors
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleForgotPasswordClick() {
|
||||
onForgotPasswordClick() {
|
||||
this.setState({
|
||||
sideToShow: 'back'
|
||||
}, this.moveFocusToCurrentSide);
|
||||
},
|
||||
}
|
||||
|
||||
handleBackToLoginClick() {
|
||||
onBackToLoginClick() {
|
||||
this.setState({
|
||||
sideToShow: 'front',
|
||||
recoverSent: false
|
||||
}, 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') {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverFormErrors: {
|
||||
email: i18n('EMAIL_NOT_EXIST')
|
||||
}
|
||||
}, function () {
|
||||
this.refs.recoverForm.refs.email.focus();
|
||||
}.bind(this));
|
||||
onRecoverPasswordSent() {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverSent: true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (event === 'SEND_RECOVER_SUCCESS') {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverSent: true
|
||||
});
|
||||
}
|
||||
},
|
||||
onRecoverPasswordFail() {
|
||||
this.setState({
|
||||
loadingRecover: false,
|
||||
recoverFormErrors: {
|
||||
email: i18n('EMAIL_NOT_EXIST')
|
||||
}
|
||||
}, function () {
|
||||
this.refs.recoverForm.refs.email.focus();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
moveFocusToCurrentSide() {
|
||||
let currentWidget;
|
||||
|
@ -205,6 +208,11 @@ let MainHomePageLoginWidget = React.createClass({
|
|||
focus.focusFirstInput(currentWidget);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainHomePageLoginWidget;
|
||||
|
||||
export default connect((store) => {
|
||||
return {
|
||||
session: store.session
|
||||
};
|
||||
})(MainHomePageLoginWidget);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const Widget = require('core-components/widget');
|
||||
import Widget from 'core-components/widget';
|
||||
|
||||
const MainHomePagePortal = React.createClass({
|
||||
class MainHomePagePortal extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Widget className={classNames('main-home-page-portal', this.props.className)}>
|
||||
|
@ -11,6 +11,6 @@ const MainHomePagePortal = React.createClass({
|
|||
</Widget>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainHomePagePortal;
|
|
@ -3,16 +3,7 @@ import React from 'react';
|
|||
import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget';
|
||||
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({
|
||||
|
||||
componentWillMount() {
|
||||
if (UserStore.isLoggedIn()) {
|
||||
CommonActions.logged();
|
||||
}
|
||||
},
|
||||
class MainHomePage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -22,6 +13,6 @@ const MainHomePage = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainHomePage;
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
let MainLayoutFooter = React.createClass({
|
||||
class MainLayoutFooter extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -11,6 +11,6 @@ let MainLayoutFooter = React.createClass({
|
|||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainLayoutFooter;
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import i18n from 'lib-app/i18n';
|
||||
import CommonActions from 'actions/common-actions';
|
||||
import UserActions from 'actions/user-actions';
|
||||
import UserStore from 'stores/user-store';
|
||||
import SessionActions from 'actions/session-actions';
|
||||
import ConfigActions from 'actions/config-actions';
|
||||
|
||||
import Button from 'core-components/button';
|
||||
import DropDown from 'core-components/drop-down';
|
||||
|
@ -18,24 +18,25 @@ let codeLanguages = {
|
|||
'Indian': 'in'
|
||||
};
|
||||
|
||||
let MainLayoutHeader = React.createClass({
|
||||
class MainLayoutHeader extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-layout-header">
|
||||
{this.renderAccessLinks()}
|
||||
<DropDown className="main-layout-header--languages" items={this.getLanguageList()} onChange={this.changeLanguage}/>
|
||||
<DropDown {...this.getLanguageSelectorProps()}/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderAccessLinks() {
|
||||
let result;
|
||||
if (UserStore.isLoggedIn()) {
|
||||
|
||||
if (this.props.session.logged) {
|
||||
result = (
|
||||
<div className="main-layout-header--login-links">
|
||||
Welcome, pepito
|
||||
<Button type="clean" onClick={this.logout}>(Close Session)</Button>
|
||||
Welcome, John
|
||||
<Button type="clean" onClick={this.logout.bind(this)}>(Close Session)</Button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -48,7 +49,16 @@ let MainLayoutHeader = React.createClass({
|
|||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
}
|
||||
|
||||
getLanguageSelectorProps() {
|
||||
return {
|
||||
className: 'main-layout-header--languages',
|
||||
items: this.getLanguageList(),
|
||||
selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language),
|
||||
onChange: this.changeLanguage.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
getLanguageList() {
|
||||
return Object.keys(codeLanguages).map((language) => {
|
||||
|
@ -57,17 +67,22 @@ let MainLayoutHeader = React.createClass({
|
|||
icon: codeLanguages[language]
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
changeLanguage(event) {
|
||||
let language = Object.keys(codeLanguages)[event.index];
|
||||
|
||||
CommonActions.changeLanguage(codeLanguages[language]);
|
||||
},
|
||||
this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language]));
|
||||
}
|
||||
|
||||
logout() {
|
||||
UserActions.logout();
|
||||
this.props.dispatch(SessionActions.logout());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainLayoutHeader;
|
||||
export default connect((store) => {
|
||||
return {
|
||||
session: store.session,
|
||||
config: store.config
|
||||
};
|
||||
})(MainLayoutHeader);
|
||||
|
|
|
@ -3,22 +3,19 @@ import React from 'react';
|
|||
import MainHeader from 'app/main/main-layout-header';
|
||||
import MainFooter from 'app/main/main-layout-footer';
|
||||
|
||||
let MainLayout = React.createClass({
|
||||
class MainLayout extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-layout">
|
||||
|
||||
<MainHeader />
|
||||
|
||||
<div className="main-layout--content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<MainFooter />
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default MainLayout;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const UserActions = require('actions/__mocks__/user-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
const APICallMock = require('lib-app/__mocks__/api-call-mock');
|
||||
|
||||
const SubmitButton = ReactMock();
|
||||
const Button = ReactMock();
|
||||
|
@ -10,15 +8,13 @@ const Message = ReactMock();
|
|||
const Widget = ReactMock();
|
||||
|
||||
const MainRecoverPasswordPage = requireUnit('app/main/main-recover-password/main-recover-password-page', {
|
||||
'lib-app/api-call': APICallMock,
|
||||
'core-components/submit-button': SubmitButton,
|
||||
'core-components/button': Button,
|
||||
'core-components/input': Input,
|
||||
'core-components/form': Form,
|
||||
'core-components/message': Message,
|
||||
'core-components/widget': Widget,
|
||||
'actions/common-actions': CommonActions,
|
||||
'actions/user-actions': UserActions,
|
||||
'stores/user-store': UserStore
|
||||
'core-components/widget': Widget
|
||||
});
|
||||
|
||||
describe('Recover Password form', function () {
|
||||
|
@ -38,12 +34,16 @@ describe('Recover Password form', function () {
|
|||
});
|
||||
|
||||
it('should trigger recoverPassword action when submitted', function () {
|
||||
UserActions.sendRecoverPassword.reset();
|
||||
APICallMock.call.reset();
|
||||
recoverForm.props.onSubmit({password: 'MOCK_VALUE'});
|
||||
expect(UserActions.recoverPassword).to.have.been.calledWith({
|
||||
password: 'MOCK_VALUE',
|
||||
token: 'SOME_TOKEN',
|
||||
email: 'SOME_EMAIL'
|
||||
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/recover-password',
|
||||
data: {
|
||||
password: 'MOCK_VALUE',
|
||||
token: 'SOME_TOKEN',
|
||||
email: 'SOME_EMAIL'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -53,7 +53,7 @@ describe('Recover Password form', function () {
|
|||
});
|
||||
|
||||
it('should show message when recover fails', function () {
|
||||
component.onUserStoreChanged('INVALID_RECOVER');
|
||||
component.onPasswordRecoverFail();
|
||||
expect(recoverForm.props.loading).to.equal(false);
|
||||
|
||||
let message = TestUtils.scryRenderedComponentsWithType(component, Message)[0];
|
||||
|
@ -63,7 +63,7 @@ describe('Recover Password form', function () {
|
|||
});
|
||||
|
||||
it('should show message when recover success', function () {
|
||||
component.onUserStoreChanged('VALID_RECOVER');
|
||||
component.onPasswordRecovered();
|
||||
expect(recoverForm.props.loading).to.equal(false);
|
||||
|
||||
let message = TestUtils.scryRenderedComponentsWithType(component, Message)[0];
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
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 API from 'lib-app/api-call';
|
||||
|
||||
import Widget from 'core-components/widget';
|
||||
import Form from 'core-components/form';
|
||||
|
@ -13,37 +10,27 @@ import Input from 'core-components/input';
|
|||
import SubmitButton from 'core-components/submit-button';
|
||||
import Message from 'core-components/message';
|
||||
|
||||
const MainRecoverPasswordPage = React.createClass({
|
||||
class MainRecoverPasswordPage extends React.Component {
|
||||
|
||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
location: React.PropTypes.object,
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
if (UserStore.isLoggedIn()) {
|
||||
CommonActions.logged();
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (!this.props.location.query.token || !this.props.location.query.email) {
|
||||
CommonActions.loggedOut();
|
||||
}
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
this.state = {
|
||||
recoverStatus: 'waiting',
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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} loading={this.state.loading}>
|
||||
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
|
||||
<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/>
|
||||
|
@ -56,7 +43,7 @@ const MainRecoverPasswordPage = React.createClass({
|
|||
</Widget>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderRecoverStatus() {
|
||||
switch (this.state.recoverStatus) {
|
||||
|
@ -67,33 +54,39 @@ const MainRecoverPasswordPage = React.createClass({
|
|||
case 'waiting':
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleRecoverPasswordSubmit(formState) {
|
||||
onRecoverPasswordSubmit(formState) {
|
||||
let recoverData = _.clone(formState);
|
||||
recoverData.token = this.props.location.query.token;
|
||||
recoverData.email = this.props.location.query.email;
|
||||
|
||||
UserActions.recoverPassword(recoverData);
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
},
|
||||
|
||||
onUserStoreChanged(event) {
|
||||
if (event === 'VALID_RECOVER') {
|
||||
setTimeout(CommonActions.loggedOut, 2000);
|
||||
this.setState({
|
||||
recoverStatus: 'valid',
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
recoverStatus: 'invalid',
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}, this.callRecoverPassword.bind(this, recoverData));
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
|
@ -1,32 +0,0 @@
|
|||
const CommonActions = require('actions/__mocks__/common-actions-mock');
|
||||
const UserStore = require('stores/__mocks__/user-store-mock');
|
||||
|
||||
const MainSignupPage = requireUnit('app/main/main-signup/main-signup-page', {
|
||||
'actions/common-actions': CommonActions,
|
||||
'stores/user-store': UserStore,
|
||||
'react-google-recaptcha': ReactMock()
|
||||
});
|
||||
|
||||
|
||||
describe('Signup page', function () {
|
||||
|
||||
afterEach(function () {
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
});
|
||||
|
||||
it('should trigger common action if user is currently logged', function () {
|
||||
CommonActions.logged.reset();
|
||||
UserStore.isLoggedIn.returns(true);
|
||||
|
||||
TestUtils.renderIntoDocument(<MainSignupPage />);
|
||||
expect(CommonActions.logged).to.have.been.called;
|
||||
});
|
||||
|
||||
it('should not trigger common action user if is not logged', function () {
|
||||
CommonActions.logged.reset();
|
||||
UserStore.isLoggedIn.returns(false);
|
||||
|
||||
TestUtils.renderIntoDocument(<MainSignupPage />);
|
||||
expect(CommonActions.logged).to.not.have.been.called;
|
||||
});
|
||||
});
|
|
@ -1,11 +1,8 @@
|
|||
import React from 'react';
|
||||
import Reflux from 'reflux';
|
||||
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 API from 'lib-app/api-call';
|
||||
|
||||
import SubmitButton from 'core-components/submit-button';
|
||||
import Message from 'core-components/message';
|
||||
|
@ -14,22 +11,16 @@ import Input from 'core-components/input';
|
|||
import Widget from 'core-components/widget';
|
||||
|
||||
|
||||
let MainSignUpPageWidget = React.createClass({
|
||||
|
||||
mixins: [Reflux.listenTo(UserStore, 'onUserStoreChanged')],
|
||||
class MainSignUpPageWidget extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (UserStore.isLoggedIn()) {
|
||||
CommonActions.logged();
|
||||
}
|
||||
},
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
this.state = {
|
||||
loading: false,
|
||||
email: null
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -52,7 +43,7 @@ let MainSignUpPageWidget = React.createClass({
|
|||
</Widget>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
|
@ -63,37 +54,47 @@ let MainSignUpPageWidget = React.createClass({
|
|||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getFormProps() {
|
||||
return {
|
||||
loading: this.state.loading,
|
||||
className: 'signup-widget__form',
|
||||
onSubmit: this.handleLoginFormSubmit
|
||||
onSubmit: this.onLoginFormSubmit.bind(this)
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getInputProps() {
|
||||
return {
|
||||
inputType: 'secondary',
|
||||
className: 'signup-widget__input'
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
handleLoginFormSubmit(formState) {
|
||||
onLoginFormSubmit(formState) {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
UserActions.signup(formState);
|
||||
},
|
||||
|
||||
onUserStoreChanged(event) {
|
||||
API.call({
|
||||
path: '/user/signup',
|
||||
data: formState
|
||||
}).then(this.onSignupSuccess.bind(this)).catch(this.onSignupFail.bind(this));
|
||||
}
|
||||
|
||||
onSignupSuccess() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: (event === 'SIGNUP_FAIL') ? 'fail': 'success'
|
||||
message: 'success'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onSignupFail() {
|
||||
this.setState({
|
||||
loading: false,
|
||||
message: 'fail'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MainSignUpPageWidget;
|
|
@ -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()));
|
|
@ -6,13 +6,13 @@ import classNames from 'classnames';
|
|||
// CORE LIBS
|
||||
import callback from 'lib-core/callback';
|
||||
|
||||
const Button = React.createClass({
|
||||
class Button extends React.Component {
|
||||
|
||||
contextTypes: {
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
type: React.PropTypes.oneOf([
|
||||
'primary',
|
||||
|
@ -24,13 +24,11 @@ const Button = React.createClass({
|
|||
params: React.PropTypes.object,
|
||||
query: React.PropTypes.query
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
type: 'primary'
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
type: 'primary'
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -38,19 +36,19 @@ const Button = React.createClass({
|
|||
{this.props.children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
||||
props.onClick = callback(this.handleClick, this.props.onClick);
|
||||
props.onClick = callback(this.handleClick.bind(this), this.props.onClick);
|
||||
props.className = this.getClass();
|
||||
|
||||
delete props.route;
|
||||
delete props.type;
|
||||
|
||||
return props;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -62,13 +60,13 @@ const Button = React.createClass({
|
|||
classes[this.props.className] = (this.props.className);
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
if (this.props.route) {
|
||||
this.context.router.push(this.props.route.to);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Button;
|
||||
|
|
|
@ -5,25 +5,25 @@ import _ from 'lodash';
|
|||
import callback from 'lib-core/callback';
|
||||
import getIcon from 'lib-core/get-icon';
|
||||
|
||||
let CheckBox = React.createClass({
|
||||
class CheckBox extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
alignment: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
value: React.PropTypes.bool
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
alignment: 'right'
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
alignment: 'right'
|
||||
};
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
checked: false
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -35,7 +35,7 @@ let CheckBox = React.createClass({
|
|||
<input {...this.getProps()}/>
|
||||
</label>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
@ -45,7 +45,7 @@ let CheckBox = React.createClass({
|
|||
props['aria-hidden'] = true;
|
||||
props.className = 'checkbox--box';
|
||||
props.checked = this.getValue();
|
||||
props.onChange = callback(this.handleChange, this.props.onChange);
|
||||
props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
|
||||
|
||||
delete props.alignment;
|
||||
delete props.error;
|
||||
|
@ -53,7 +53,7 @@ let CheckBox = React.createClass({
|
|||
delete props.value;
|
||||
|
||||
return props;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -64,39 +64,39 @@ let CheckBox = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
getIconProps() {
|
||||
return {
|
||||
'aria-checked': this.getValue(),
|
||||
className: 'checkbox--icon',
|
||||
onKeyDown: callback(this.handleIconKeyDown, this.props.onKeyDown),
|
||||
onKeyDown: callback(this.handleIconKeyDown.bind(this), this.props.onKeyDown),
|
||||
role: "checkbox",
|
||||
tabIndex: 0
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return (this.props.value === undefined) ? this.state.checked : this.props.value;
|
||||
},
|
||||
}
|
||||
|
||||
handleChange: function (event) {
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
checked: event.target.checked
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleIconKeyDown: function (event) {
|
||||
handleIconKeyDown(event) {
|
||||
if (event.keyCode == 32) {
|
||||
event.preventDefault();
|
||||
|
||||
callback(this.handleChange, this.props.onChange)({
|
||||
callback(this.handleChange.bind(this), this.props.onChange)({
|
||||
target: {
|
||||
checked: !this.state.checked
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default CheckBox;
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const _ = require('lodash');
|
||||
const {Motion, spring} = require('react-motion');
|
||||
const callback = require('lib-core/callback');
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {Motion, spring} from 'react-motion';
|
||||
|
||||
const Menu = require('core-components/menu');
|
||||
const Icon = require('core-components/icon');
|
||||
import Menu from 'core-components/menu';
|
||||
import Icon from 'core-components/icon';
|
||||
|
||||
const DropDown = React.createClass({
|
||||
class DropDown extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
defaultSelectedIndex: React.PropTypes.number,
|
||||
selectedIndex: React.PropTypes.number,
|
||||
items: Menu.propTypes.items
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
defaultSelectedIndex: 0
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
defaultSelectedIndex: 2
|
||||
};
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedIndex: 0,
|
||||
opened: false
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getAnimationStyles() {
|
||||
let closedStyle = {
|
||||
|
@ -42,7 +40,7 @@ const DropDown = React.createClass({
|
|||
defaultStyle: closedStyle,
|
||||
style: (this.state.opened) ? openedStyle : closedStyle
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
let animation = this.getAnimationStyles();
|
||||
|
@ -52,19 +50,19 @@ const DropDown = React.createClass({
|
|||
<div className={this.getClass()}>
|
||||
{this.renderCurrentItem(selectedItem)}
|
||||
<Motion defaultStyle={animation.defaultStyle} style={animation.style}>
|
||||
{this.renderList}
|
||||
{this.renderList.bind(this)}
|
||||
</Motion>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderList({opacity, translateY}) {
|
||||
let style = { opacity: opacity, transform: `translateY(${translateY}px)`};
|
||||
let menuProps = {
|
||||
items: this.props.items,
|
||||
onItemClick: this.handleItemClick,
|
||||
onMouseDown: this.handleListMouseDown,
|
||||
selectedIndex: this.state.selectedIndex
|
||||
onItemClick: this.handleItemClick.bind(this),
|
||||
onMouseDown: this.handleListMouseDown.bind(this),
|
||||
selectedIndex: this.getSelectedIndex()
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -72,7 +70,7 @@ const DropDown = React.createClass({
|
|||
<Menu {...menuProps} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderCurrentItem(item) {
|
||||
var iconNode = null;
|
||||
|
@ -82,11 +80,11 @@ const DropDown = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="drop-down--current-item" onBlur={this.handleBlur} onClick={this.handleClick} tabIndex="0">
|
||||
<div className="drop-down--current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
|
||||
{iconNode}{item.content}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -97,19 +95,19 @@ const DropDown = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.setState({
|
||||
opened: false
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.setState({
|
||||
opened: !this.state.opened
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
handleItemClick(index) {
|
||||
this.setState({
|
||||
|
@ -122,15 +120,15 @@ const DropDown = React.createClass({
|
|||
index
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleListMouseDown(event) {
|
||||
event.preventDefault();
|
||||
},
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
return (this.props.selectedIndex !== undefined) ? this.props.selectedIndex : this.state.selectedIndex;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default DropDown;
|
||||
|
|
|
@ -1,64 +1,66 @@
|
|||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const classNames = require('classnames');
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const {reactDFS, renderChildrenWithProps} = require('lib-core/react-dfs');
|
||||
const ValidationFactory = require('lib-app/validations/validations-factory');
|
||||
import {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs';
|
||||
import ValidationFactory from 'lib-app/validations/validations-factory';
|
||||
|
||||
const Input = require('core-components/input');
|
||||
const Checkbox = require('core-components/checkbox');
|
||||
import Input from 'core-components/input';
|
||||
import Checkbox from 'core-components/checkbox';
|
||||
|
||||
const Form = React.createClass({
|
||||
class Form extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
loading: React.PropTypes.bool,
|
||||
errors: React.PropTypes.object,
|
||||
onValidateErrors: React.PropTypes.func,
|
||||
onSubmit: React.PropTypes.func
|
||||
},
|
||||
};
|
||||
|
||||
childContextTypes: {
|
||||
static childContextTypes = {
|
||||
loading: React.PropTypes.bool
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
form: {},
|
||||
validations: {},
|
||||
errors: {}
|
||||
};
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
loading: this.props.loading
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
form: {},
|
||||
validations: {},
|
||||
errors: {}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState(this.getInitialFormAndValidations());
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form {...this.getProps()}>
|
||||
{renderChildrenWithProps(this.props.children, this.getFieldProps)}
|
||||
{renderChildrenWithProps(this.props.children, this.getFieldProps.bind(this))}
|
||||
</form>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
||||
props.className = this.getClass();
|
||||
props.onSubmit = this.handleSubmit;
|
||||
props.onSubmit = this.handleSubmit.bind(this);
|
||||
|
||||
delete props.errors;
|
||||
delete props.loading;
|
||||
delete props.onValidateErrors;
|
||||
|
||||
return props;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -68,7 +70,7 @@ const Form = React.createClass({
|
|||
classes[this.props.className] = (this.props.className);
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
getFieldProps({props, type}) {
|
||||
let additionalProps = {};
|
||||
|
@ -86,7 +88,7 @@ const Form = React.createClass({
|
|||
}
|
||||
|
||||
return additionalProps;
|
||||
},
|
||||
}
|
||||
|
||||
getFieldError(fieldName) {
|
||||
let error = this.state.errors[fieldName];
|
||||
|
@ -95,7 +97,7 @@ const Form = React.createClass({
|
|||
error = this.props.errors[fieldName]
|
||||
}
|
||||
return error;
|
||||
},
|
||||
}
|
||||
|
||||
getFirstErrorField() {
|
||||
let fieldName = _.findKey(this.state.errors);
|
||||
|
@ -106,7 +108,7 @@ const Form = React.createClass({
|
|||
}
|
||||
|
||||
return fieldNode;
|
||||
},
|
||||
}
|
||||
|
||||
getAllFieldErrors() {
|
||||
let form = this.state.form;
|
||||
|
@ -118,7 +120,7 @@ const Form = React.createClass({
|
|||
});
|
||||
|
||||
return errors;
|
||||
},
|
||||
}
|
||||
|
||||
getErrorsWithValidatedField(fieldName, form = this.state.form, errors = this.state.errors) {
|
||||
let newErrors = _.clone(errors);
|
||||
|
@ -128,7 +130,7 @@ const Form = React.createClass({
|
|||
}
|
||||
|
||||
return newErrors;
|
||||
},
|
||||
}
|
||||
|
||||
getInitialFormAndValidations() {
|
||||
let form = {};
|
||||
|
@ -154,17 +156,17 @@ const Form = React.createClass({
|
|||
form: form,
|
||||
validations: validations
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.hasFormErrors()) {
|
||||
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField);
|
||||
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this));
|
||||
} else if (this.props.onSubmit) {
|
||||
this.props.onSubmit(this.state.form);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
handleFieldChange(fieldName, type, event) {
|
||||
let form = _.clone(this.state.form);
|
||||
|
@ -178,19 +180,19 @@ const Form = React.createClass({
|
|||
this.setState({
|
||||
form: form
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
isValidFieldType(child) {
|
||||
return child.type === Input || child.type === Checkbox;
|
||||
},
|
||||
}
|
||||
|
||||
hasFormErrors() {
|
||||
return _.some(this.getAllFieldErrors());
|
||||
},
|
||||
}
|
||||
|
||||
validateField(fieldName) {
|
||||
this.updateErrors(this.getErrorsWithValidatedField(fieldName));
|
||||
},
|
||||
}
|
||||
|
||||
updateErrors(errors, callback) {
|
||||
this.setState({
|
||||
|
@ -200,7 +202,7 @@ const Form = React.createClass({
|
|||
if (this.props.onValidateErrors) {
|
||||
this.props.onValidateErrors(errors);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
focusFirstErrorField() {
|
||||
let firstErrorField = this.getFirstErrorField();
|
||||
|
@ -209,6 +211,6 @@ const Form = React.createClass({
|
|||
firstErrorField.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Form;
|
||||
|
|
|
@ -1,34 +1,32 @@
|
|||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const Icon = React.createClass({
|
||||
class Icon extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
name: React.PropTypes.string.isRequired,
|
||||
size: React.PropTypes.string
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
size: 'lg'
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
size: 'lg'
|
||||
};
|
||||
|
||||
render() {
|
||||
return (this.props.name.length > 2) ? this.renderFontIcon() : this.renderFlag();
|
||||
},
|
||||
}
|
||||
|
||||
renderFontIcon() {
|
||||
return (
|
||||
<span className={this.getFontIconClass()} aria-hidden="true" />
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderFlag() {
|
||||
return (
|
||||
<img className={this.props.className} src={`/images/icons/${this.props.name}.png`} aria-hidden="true" />
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getFontIconClass() {
|
||||
let classes = {
|
||||
|
@ -40,6 +38,6 @@ const Icon = React.createClass({
|
|||
|
||||
return classNames(classes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
const React = require('react');
|
||||
const classNames = require('classnames');
|
||||
const _ = require('lodash');
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
const Icon = require('core-components/icon');
|
||||
import Icon from 'core-components/icon';
|
||||
|
||||
const Input = React.createClass({
|
||||
class Input extends React.Component {
|
||||
|
||||
contextTypes: {
|
||||
static contextTypes = {
|
||||
loading: React.PropTypes.bool
|
||||
},
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
value: React.PropTypes.string,
|
||||
validation: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
|
@ -19,13 +19,11 @@ const Input = React.createClass({
|
|||
required: React.PropTypes.bool,
|
||||
icon: React.PropTypes.string,
|
||||
error: React.PropTypes.string
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
static defaultProps = {
|
||||
inputType: 'primary'
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -36,7 +34,7 @@ const Input = React.createClass({
|
|||
{this.renderError()}
|
||||
</label>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderError() {
|
||||
let error = null;
|
||||
|
@ -46,7 +44,7 @@ const Input = React.createClass({
|
|||
}
|
||||
|
||||
return error;
|
||||
},
|
||||
}
|
||||
|
||||
renderIcon() {
|
||||
let icon = null;
|
||||
|
@ -56,7 +54,7 @@ const Input = React.createClass({
|
|||
}
|
||||
|
||||
return icon;
|
||||
},
|
||||
}
|
||||
|
||||
getInputProps() {
|
||||
let props = _.clone(this.props);
|
||||
|
@ -73,7 +71,7 @@ const Input = React.createClass({
|
|||
delete props.password;
|
||||
|
||||
return props;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -86,13 +84,13 @@ const Input = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.refs.nativeInput) {
|
||||
this.refs.nativeInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Input;
|
||||
|
|
|
@ -1,34 +1,32 @@
|
|||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const classNames = require('classnames');
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const Icon = require('core-components/icon');
|
||||
import Icon from 'core-components/icon';
|
||||
|
||||
const Menu = React.createClass({
|
||||
class Menu extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(['primary', 'secondary']),
|
||||
items: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
content: React.PropTypes.string.isRequired,
|
||||
icon: React.PropTypes.string
|
||||
})).isRequired,
|
||||
selectedIndex: React.PropTypes.number
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
type: 'primary',
|
||||
selectedIndex: 0
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
type: 'primary',
|
||||
selectedIndex: 0
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ul {...this.getProps()}>
|
||||
{this.props.items.map(this.renderListItem)}
|
||||
{this.props.items.map(this.renderListItem.bind(this))}
|
||||
</ul>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
renderListItem(item, index) {
|
||||
let iconNode = null;
|
||||
|
@ -42,7 +40,7 @@ const Menu = React.createClass({
|
|||
{iconNode}{item.content}
|
||||
</li>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
var props = _.clone(this.props);
|
||||
|
@ -55,7 +53,7 @@ const Menu = React.createClass({
|
|||
delete props.type;
|
||||
|
||||
return props;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -66,7 +64,7 @@ const Menu = React.createClass({
|
|||
classes[this.props.className] = true;
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
getItemProps(index) {
|
||||
return {
|
||||
|
@ -74,7 +72,7 @@ const Menu = React.createClass({
|
|||
onClick: this.handleItemClick.bind(this, index),
|
||||
key: index
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
getItemClass(index) {
|
||||
let classes = {
|
||||
|
@ -83,13 +81,13 @@ const Menu = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
handleItemClick(index) {
|
||||
if (this.props.onItemClick) {
|
||||
this.props.onItemClick(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Menu;
|
|
@ -4,29 +4,27 @@ import classNames from 'classnames';
|
|||
import {Motion, spring} from 'react-motion';
|
||||
import Icon from 'core-components/icon';
|
||||
|
||||
const Message = React.createClass({
|
||||
class Message extends React.Component {
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
children: React.PropTypes.node,
|
||||
leftAligned: React.PropTypes.bool,
|
||||
type: React.PropTypes.oneOf(['success', 'error', 'info'])
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
type: 'info',
|
||||
leftAligned: false
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
type: 'info',
|
||||
leftAligned: false
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Motion {...this.getAnimationProps()}>
|
||||
{this.renderMessage}
|
||||
{this.renderMessage.bind(this)}
|
||||
</Motion>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getAnimationProps() {
|
||||
return {
|
||||
|
@ -37,7 +35,7 @@ const Message = React.createClass({
|
|||
opacity: spring(1, [100, 30])
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
renderMessage(style) {
|
||||
return (
|
||||
|
@ -47,7 +45,7 @@ const Message = React.createClass({
|
|||
<div className="message__content">{this.props.children}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -62,7 +60,7 @@ const Message = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
getIconName() {
|
||||
let iconNames = {
|
||||
|
@ -72,11 +70,11 @@ const Message = React.createClass({
|
|||
};
|
||||
|
||||
return iconNames[this.props.type];
|
||||
},
|
||||
}
|
||||
|
||||
getIconSize() {
|
||||
return (this.props.title) ? '2x' : 'lg';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Message;
|
||||
|
|
|
@ -6,21 +6,19 @@ import classNames from 'classnames';
|
|||
// CORE LIBS
|
||||
import Button from 'core-components/button';
|
||||
|
||||
const SubmitButton = React.createClass({
|
||||
class SubmitButton extends React.Component {
|
||||
|
||||
contextTypes: {
|
||||
static contextTypes = {
|
||||
loading: React.PropTypes.bool
|
||||
},
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
type: 'primary'
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
type: 'primary'
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -28,20 +26,20 @@ const SubmitButton = React.createClass({
|
|||
{(this.context.loading) ? this.renderLoading() : this.props.children}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderLoading() {
|
||||
return (
|
||||
<div className="submit-button__loader"></div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getProps() {
|
||||
return _.extend({}, this.props, {
|
||||
disabled: this.context.loading,
|
||||
className: this.getClass()
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -53,6 +51,6 @@ const SubmitButton = React.createClass({
|
|||
|
||||
return classNames(classes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default SubmitButton;
|
||||
|
|
|
@ -3,35 +3,31 @@ import classNames from 'classnames';
|
|||
import _ from 'lodash';
|
||||
import {Motion, spring} from 'react-motion';
|
||||
|
||||
import Widget from 'core-components/widget';
|
||||
class WidgetTransition extends React.Component {
|
||||
|
||||
let WidgetTransition = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
sideToShow: React.PropTypes.string
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
sideToShow: 'front'
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
sideToShow: 'front'
|
||||
};
|
||||
|
||||
getDefaultAnimation() {
|
||||
return {
|
||||
rotateY: -90
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Motion defaultStyle={this.getDefaultAnimation()} style={this.getAnimation()}>
|
||||
{this.renderChildren}
|
||||
{this.renderChildren.bind(this)}
|
||||
</Motion>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderChildren: function (animation) {
|
||||
renderChildren(animation) {
|
||||
return (
|
||||
<div className={this.getClass()}>
|
||||
{React.Children.map(this.props.children, function (child, index) {
|
||||
|
@ -57,7 +53,7 @@ let WidgetTransition = React.createClass({
|
|||
})}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -66,13 +62,13 @@ let WidgetTransition = React.createClass({
|
|||
};
|
||||
|
||||
return classNames(classes);
|
||||
},
|
||||
}
|
||||
|
||||
getAnimation() {
|
||||
return {
|
||||
rotateY: (this.props.sideToShow === 'front') ? spring(0, [100, 20]) : spring(180, [100, 20])
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default WidgetTransition;
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
let Widget = React.createClass({
|
||||
propTypes: {
|
||||
class Widget extends React.Component {
|
||||
static propTypes = {
|
||||
title: React.PropTypes.string,
|
||||
children: React.PropTypes.node.isRequired
|
||||
},
|
||||
};
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
title: ''
|
||||
};
|
||||
},
|
||||
static defaultProps = {
|
||||
title: ''
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -20,7 +18,7 @@ let Widget = React.createClass({
|
|||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
let titleNode = null;
|
||||
|
@ -30,7 +28,7 @@ let Widget = React.createClass({
|
|||
}
|
||||
|
||||
return titleNode;
|
||||
},
|
||||
}
|
||||
|
||||
getClass() {
|
||||
let classes = {
|
||||
|
@ -41,6 +39,6 @@ let Widget = React.createClass({
|
|||
|
||||
return classNames(classes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default Widget;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module.exports = [
|
||||
{
|
||||
path: '/system/get-configs',
|
||||
time: 1000,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
'language': 'us',
|
||||
'reCaptchaKey': '6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
|
@ -1,18 +1,18 @@
|
|||
module.exports = [
|
||||
{
|
||||
path: 'user/login',
|
||||
path: '/user/login',
|
||||
time: 1000,
|
||||
response: function (data) {
|
||||
let response;
|
||||
|
||||
if (data.password === 'valid' || (data.rememberToken === 'aa41efe0a1b3eeb9bf303e4561ff8392' && data.userId === 12)) {
|
||||
if (data.password === 'valid' || (data.rememberToken === 'aa41efe0a1b3eeb9bf303e4561ff8392' && data.userId == 12)) {
|
||||
response = {
|
||||
status: 'success',
|
||||
data: {
|
||||
'userId': 12,
|
||||
'token': 'cc6b4921e6733d6aafe284ec0d7be57e',
|
||||
'rememberToken': (data.remember) ? 'aa41efe0a1b3eeb9bf303e4561ff8392' : null,
|
||||
'rememberExpiration': (data.remember) ? 2018 : 0
|
||||
'rememberExpiration': (data.remember) ? 20180806 : 0
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
@ -26,7 +26,7 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'user/logout',
|
||||
path: '/user/logout',
|
||||
time: 100,
|
||||
response: function () {
|
||||
return {
|
||||
|
@ -36,19 +36,19 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'user/check-session',
|
||||
path: '/user/check-session',
|
||||
time: 100,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
sessionActive: true
|
||||
sessionActive: false
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user/send-recover-password',
|
||||
path: '/user/send-recover-password',
|
||||
time: 2000,
|
||||
response: function (data) {
|
||||
|
||||
|
@ -67,7 +67,7 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'user/recover-password',
|
||||
path: '/user/recover-password',
|
||||
time: 1000,
|
||||
response: function (data) {
|
||||
|
||||
|
@ -86,7 +86,7 @@ module.exports = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'user/signup',
|
||||
path: '/user/signup',
|
||||
time: 1000,
|
||||
response: function (data) {
|
||||
|
||||
|
|
|
@ -6,5 +6,8 @@ export default {
|
|||
getRememberData: stub(),
|
||||
isRememberDataExpired: stub().returns(false),
|
||||
isLoggedIn: stub().returns(false),
|
||||
setConfigs: stub().returns(false),
|
||||
getConfigs: stub().returns({}),
|
||||
areConfigsStored: stub().returns(false),
|
||||
closeSession: stub()
|
||||
};
|
|
@ -2,7 +2,7 @@ const _ = require('lodash');
|
|||
const APIUtils = require('lib-core/APIUtils');
|
||||
const SessionStore = require('lib-app/session-store');
|
||||
|
||||
const root = 'http://localhost:3000/api/';
|
||||
const root = 'http://localhost:3000/api';
|
||||
|
||||
function processData (data) {
|
||||
return _.extend(SessionStore.getSessionData(), data);
|
||||
|
|
|
@ -17,11 +17,12 @@ let fixtures = (function () {
|
|||
|
||||
// FIXTURES
|
||||
fixtures.add(require('data/fixtures/user-fixtures'));
|
||||
fixtures.add(require('data/fixtures/system-fixtures'));
|
||||
|
||||
_.each(fixtures.getAll(), function (fixture) {
|
||||
mockjax({
|
||||
contentType: 'application/json',
|
||||
url: 'http://localhost:3000/api/' + fixture.path,
|
||||
url: 'http://localhost:3000/api' + fixture.path,
|
||||
responseTime: fixture.time || 500,
|
||||
response: function (settings) {
|
||||
this.responseText = fixture.response(settings.data);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import MessageFormat from 'messageformat';
|
||||
|
||||
import CommonStore from 'stores/common-store';
|
||||
import store from 'app/store';
|
||||
import i18nData from 'data/i18n-data';
|
||||
|
||||
let mf = new MessageFormat('en');
|
||||
|
||||
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);
|
||||
|
||||
return message(params);
|
||||
|
|
|
@ -6,7 +6,7 @@ class SessionStore {
|
|||
this.storage = LocalStorage;
|
||||
|
||||
if (!this.getItem('language')) {
|
||||
this.setItem('language', 'english');
|
||||
this.setItem('language', 'us');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,22 @@ class SessionStore {
|
|||
this.setItem('rememberData-expiration', expiration);
|
||||
}
|
||||
|
||||
storeConfigs(configs) {
|
||||
this.setItem('language', configs.language);
|
||||
this.setItem('reCaptchaKey', configs.reCaptchaKey);
|
||||
}
|
||||
|
||||
getConfigs() {
|
||||
return {
|
||||
language: this.getItem('language'),
|
||||
reCaptchaKey: this.getItem('reCaptchaKey')
|
||||
};
|
||||
}
|
||||
|
||||
areConfigsStored() {
|
||||
return !!this.getItem('reCaptchaKey');
|
||||
}
|
||||
|
||||
isRememberDataExpired() {
|
||||
let rememberData = this.getRememberData();
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
var jsdom = require('jsdom').jsdom;
|
||||
|
||||
global.document = jsdom('<html><body></body></html>');
|
||||
|
@ -30,3 +32,6 @@ global.reRenderIntoDocument = (function () {
|
|||
return ReactDOM.render(jsx, div);
|
||||
}
|
||||
})();
|
||||
global.ReduxMock = {
|
||||
connect: stub().returns(stub().returnsArg(0))
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
const Reducer = require('reducers/reducer');
|
||||
|
||||
|
||||
class SomeReducer extends Reducer {
|
||||
getTypeHandlers() {
|
||||
return {
|
||||
'ACTION_1': this.handleAction1
|
||||
};
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
prop1: 0,
|
||||
prop2: '',
|
||||
prop3: false
|
||||
};
|
||||
}
|
||||
|
||||
handleAction1(state, payload) {
|
||||
return {
|
||||
state: state,
|
||||
payload: payload,
|
||||
prop1: 5,
|
||||
prop2: 'hello',
|
||||
prop3: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('Reducer class', function () {
|
||||
let reducer;
|
||||
|
||||
before(function () {
|
||||
reducer = SomeReducer.getInstance();
|
||||
});
|
||||
|
||||
it('should call correct handlers for each type', function () {
|
||||
let result = reducer(undefined, {});
|
||||
expect(result).to.deep.equal({
|
||||
prop1: 0,
|
||||
prop2: '',
|
||||
prop3: false
|
||||
});
|
||||
|
||||
result = reducer({prop4: true}, {type: 'ACTION_1', payload: 'PAYLOAD'});
|
||||
expect(result).to.deep.equal({
|
||||
state: {
|
||||
prop4: true
|
||||
},
|
||||
payload: 'PAYLOAD',
|
||||
prop1: 5,
|
||||
prop2: 'hello',
|
||||
prop3: true
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
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,
|
||||
'INIT_CONFIGS_FULFILLED': this.onInitConfigs
|
||||
};
|
||||
}
|
||||
|
||||
onLanguageChange(state, payload) {
|
||||
sessionStore.setItem('language', payload);
|
||||
|
||||
return _.extend({}, state, {
|
||||
language: payload
|
||||
});
|
||||
}
|
||||
|
||||
onInitConfigs(state, payload) {
|
||||
sessionStore.storeConfigs(payload.data);
|
||||
|
||||
return _.extend({}, state, payload.data);
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigReducer.getInstance();
|
|
@ -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;
|
|
@ -0,0 +1,100 @@
|
|||
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.bind(this),
|
||||
'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, logged: true})},
|
||||
'LOGIN_AUTO_FULFILLED': this.onAutoLogin.bind(this),
|
||||
'LOGIN_AUTO_REJECTED': this.onAutoLoginFail
|
||||
};
|
||||
}
|
||||
|
||||
onLoginPending(state) {
|
||||
return _.extend({}, state, {
|
||||
logged: false,
|
||||
pending: true,
|
||||
failed: false
|
||||
});
|
||||
}
|
||||
|
||||
onLoginCompleted(state, payload) {
|
||||
this.storeLoginResultData(payload.data);
|
||||
|
||||
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, {
|
||||
initDone: true,
|
||||
logged: false,
|
||||
pending: false,
|
||||
failed: false
|
||||
});
|
||||
}
|
||||
|
||||
onAutoLogin(state, payload) {
|
||||
this.storeLoginResultData(payload.data);
|
||||
|
||||
return _.extend({}, state, {
|
||||
initDone: true,
|
||||
logged: true,
|
||||
pending: false,
|
||||
failed: false
|
||||
});
|
||||
}
|
||||
|
||||
onAutoLoginFail(state) {
|
||||
sessionStore.closeSession();
|
||||
sessionStore.clearRememberData();
|
||||
|
||||
return _.extend({}, state, {
|
||||
initDone: true
|
||||
});
|
||||
}
|
||||
|
||||
storeLoginResultData(resultData) {
|
||||
if (resultData.rememberToken) {
|
||||
sessionStore.storeRememberData({
|
||||
token: resultData.rememberToken,
|
||||
userId: resultData.userId,
|
||||
expiration: resultData.rememberExpiration
|
||||
});
|
||||
}
|
||||
|
||||
sessionStore.createSession(resultData.userId, resultData.token);
|
||||
}
|
||||
}
|
||||
|
||||
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…
Reference in New Issue