Ivan - Table Component - Add /user/get fixture, retrieve all tickets from api with USER_DATA action, update header styling, add close session to menu, update tests [skip ci]
This commit is contained in:
parent
cfb65f2306
commit
8b6266c6c8
|
@ -11,42 +11,55 @@ const SessionActions = requireUnit('actions/session-actions', {
|
|||
});
|
||||
|
||||
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();
|
||||
it('should return LOGIN with with a result promise', function () {
|
||||
APICallMock.call.returns({
|
||||
then: function (resolve) {
|
||||
resolve({
|
||||
data: {
|
||||
userId: 14
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let loginData = {
|
||||
email: 'SOME_EMAIL',
|
||||
password: 'SOME_PASSWORD',
|
||||
remember: false
|
||||
};
|
||||
|
||||
expect(SessionActions.login(loginData)).to.deep.equal({
|
||||
type: 'LOGIN',
|
||||
payload: 'API_RESULT'
|
||||
});
|
||||
|
||||
expect(SessionActions.login(loginData).type).to.equal('LOGIN');
|
||||
expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/login',
|
||||
data: loginData
|
||||
path: '/user/get',
|
||||
data: {
|
||||
userId: 14
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoLogin action', function () {
|
||||
it('should return LOGIN_AUTO with remember data from sessionStore', function () {
|
||||
APICallMock.call.reset();
|
||||
APICallMock.call.returns({
|
||||
then: function (resolve) {
|
||||
resolve({
|
||||
data: {
|
||||
userId: 14
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
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(SessionActions.autoLogin().type).to.equal('LOGIN_AUTO');
|
||||
expect(storeMock.dispatch).to.have.been.calledWithMatch({type: 'USER_DATA'});
|
||||
expect(APICallMock.call).to.have.been.calledWith({
|
||||
path: '/user/login',
|
||||
data: {
|
||||
|
@ -60,6 +73,7 @@ describe('Session Actions,', function () {
|
|||
|
||||
describe('logout action', function () {
|
||||
it('should return LOGOUT and call /user/logout', function () {
|
||||
APICallMock.call.returns('API_RESULT');
|
||||
APICallMock.call.reset();
|
||||
|
||||
expect(SessionActions.logout()).to.deep.equal({
|
||||
|
|
|
@ -9,6 +9,10 @@ export default {
|
|||
payload: API.call({
|
||||
path: '/user/login',
|
||||
data: loginData
|
||||
}).then((result) => {
|
||||
store.dispatch(this.getUserData(result.data.userId));
|
||||
|
||||
return result;
|
||||
})
|
||||
};
|
||||
},
|
||||
|
@ -25,6 +29,10 @@ export default {
|
|||
rememberToken: rememberData.token,
|
||||
isAutomatic: true
|
||||
}
|
||||
}).then((result) => {
|
||||
store.dispatch(this.getUserData(result.data.userId));
|
||||
|
||||
return result;
|
||||
})
|
||||
};
|
||||
},
|
||||
|
@ -39,6 +47,18 @@ export default {
|
|||
};
|
||||
},
|
||||
|
||||
getUserData(userId) {
|
||||
return {
|
||||
type: 'USER_DATA',
|
||||
payload: API.call({
|
||||
path: '/user/get',
|
||||
data: {
|
||||
userId: userId
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
initSession() {
|
||||
return {
|
||||
type: 'CHECK_SESSION',
|
||||
|
|
|
@ -1,90 +1,16 @@
|
|||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import Table from 'core-components/table';
|
||||
import Button from 'core-components/button';
|
||||
|
||||
let mockTickets = [
|
||||
{
|
||||
ticketNumber: '445441',
|
||||
title: 'Problem with installation',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
department: 'Environment Setup',
|
||||
date: '15 Apr 2016',
|
||||
file: 'http://www.opensupports.com/some_file.zip',
|
||||
language: 'en',
|
||||
unread: true,
|
||||
closed: false,
|
||||
author: {
|
||||
name: 'John Smith',
|
||||
email: 'john@smith.com'
|
||||
},
|
||||
owner: {
|
||||
name: 'Steve Jobs'
|
||||
},
|
||||
comments: [
|
||||
{
|
||||
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||
author: {
|
||||
name: 'Steve Jobs',
|
||||
email: 'jobs@steve.com',
|
||||
staff: true
|
||||
}
|
||||
},
|
||||
{
|
||||
content: 'I have already installed apache, but the problem persists',
|
||||
author: {
|
||||
name: 'John Smith',
|
||||
steve: 'john@smith.com',
|
||||
staff: false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ticketNumber: '87852',
|
||||
title: 'Lorem ipsum door',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
department: 'Environment Setup',
|
||||
date: '15 Apr 2016',
|
||||
file: 'http://www.opensupports.com/some_file.zip',
|
||||
language: 'en',
|
||||
unread: false,
|
||||
closed: false,
|
||||
author: {
|
||||
name: 'John Smith',
|
||||
email: 'john@smith.com'
|
||||
},
|
||||
owner: {
|
||||
name: 'Steve Jobs'
|
||||
},
|
||||
comments: [
|
||||
{
|
||||
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||
author: {
|
||||
name: 'Steve Jobs',
|
||||
email: 'jobs@steve.com',
|
||||
staff: true
|
||||
}
|
||||
},
|
||||
{
|
||||
content: 'I have already installed apache, but the problem persists',
|
||||
author: {
|
||||
name: 'John Smith',
|
||||
steve: 'john@smith.com',
|
||||
staff: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
class DashboardListTicketsPage extends React.Component {
|
||||
static propTypes = {
|
||||
tickets: React.PropTypes.arrayOf(React.PropTypes.object)
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
tickets: mockTickets.concat([mockTickets[1], mockTickets[1]])
|
||||
tickets: []
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -135,11 +61,16 @@ class DashboardListTicketsPage extends React.Component {
|
|||
{titleText}
|
||||
</Button>
|
||||
),
|
||||
department: ticket.department,
|
||||
department: ticket.department.name,
|
||||
date: ticket.date,
|
||||
highlighted: ticket.unread
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardListTicketsPage;
|
||||
|
||||
export default connect((store) => {
|
||||
return {
|
||||
tickets: store.session.userTickets
|
||||
};
|
||||
})(DashboardListTicketsPage);
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Menu from 'core-components/menu';
|
||||
import {dispatch} from 'app/store';
|
||||
import SessionActions from 'actions/session-actions';
|
||||
import i18n from 'lib-app/i18n';
|
||||
|
||||
let dashboardRoutes = [
|
||||
{ path: '/dashboard', text: 'Ticket List', icon: 'file-text-o' },
|
||||
{ path: '/dashboard/create-ticket', text: 'Create Ticket', icon: 'plus' },
|
||||
{ path: '/dashboard/articles', text: 'View Articles', icon: 'book' },
|
||||
{ path: '/dashboard/edit-profile', text: 'Edit Profile', icon: 'pencil' }
|
||||
];
|
||||
import Menu from 'core-components/menu';
|
||||
|
||||
class DashboardMenu extends React.Component {
|
||||
static contextTypes = {
|
||||
|
@ -30,14 +27,18 @@ class DashboardMenu extends React.Component {
|
|||
header: 'Dashboard',
|
||||
items: this.getMenuItems(),
|
||||
selectedIndex: this.getSelectedIndex(),
|
||||
onItemClick: this.goToPathByIndex.bind(this),
|
||||
onItemClick: this.onItemClick.bind(this),
|
||||
tabbable: true,
|
||||
type: 'secondary'
|
||||
};
|
||||
}
|
||||
|
||||
getMenuItems() {
|
||||
return dashboardRoutes.map(this.getMenuItem.bind(this));
|
||||
let items = this.getDashboardRoutes().map(this.getMenuItem.bind(this));
|
||||
|
||||
items.push(this.getCloseSessionItem());
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
getMenuItem(item) {
|
||||
|
@ -47,14 +48,38 @@ class DashboardMenu extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
getCloseSessionItem() {
|
||||
return {
|
||||
content: i18n('CLOSE_SESSION'),
|
||||
icon: 'lock'
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
let pathname = this.props.location.pathname;
|
||||
|
||||
return _.findIndex(dashboardRoutes, {path: pathname});
|
||||
return _.findIndex(this.getDashboardRoutes(), {path: pathname});
|
||||
}
|
||||
|
||||
onItemClick(itemIndex) {
|
||||
if (itemIndex < this.getDashboardRoutes().length) {
|
||||
this.goToPathByIndex(itemIndex)
|
||||
} else {
|
||||
dispatch(SessionActions.logout());
|
||||
}
|
||||
}
|
||||
|
||||
goToPathByIndex(itemIndex) {
|
||||
this.context.router.push(dashboardRoutes[itemIndex].path);
|
||||
this.context.router.push(this.getDashboardRoutes()[itemIndex].path);
|
||||
}
|
||||
|
||||
getDashboardRoutes() {
|
||||
return [
|
||||
{ path: '/dashboard', text: i18n('TICKET_LIST'), icon: 'file-text-o' },
|
||||
{ path: '/dashboard/create-ticket', text: i18n('CREATE_TICKET'), icon: 'plus' },
|
||||
{ path: '/dashboard/articles', text: i18n('VIEW_ARTICLES'), icon: 'book' },
|
||||
{ path: '/dashboard/edit-profile', text: i18n('EDIT_PROFILE'), icon: 'pencil' }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,14 +34,14 @@ class MainLayoutHeader extends React.Component {
|
|||
|
||||
if (this.props.session.logged) {
|
||||
result = (
|
||||
<div className="main-layout-header--login-links">
|
||||
Welcome, John
|
||||
<Button type="clean" onClick={this.logout.bind(this)}>(Close Session)</Button>
|
||||
<div className="main-layout-header__login-links">
|
||||
{i18n('WELCOME')},
|
||||
<span className="main-layout-header__user-name"> {this.props.session.userName}</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
result = (
|
||||
<div className="main-layout-header--login-links">
|
||||
<div className="main-layout-header__login-links">
|
||||
<Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button>
|
||||
<Button type="clean" route={{to:'/signup'}}>Sign up</Button>
|
||||
</div>
|
||||
|
@ -53,7 +53,7 @@ class MainLayoutHeader extends React.Component {
|
|||
|
||||
getLanguageSelectorProps() {
|
||||
return {
|
||||
className: 'main-layout-header--languages',
|
||||
className: 'main-layout-header__languages',
|
||||
items: this.getLanguageList(),
|
||||
selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language),
|
||||
onChange: this.changeLanguage.bind(this)
|
||||
|
@ -74,10 +74,6 @@ class MainLayoutHeader extends React.Component {
|
|||
|
||||
this.props.dispatch(ConfigActions.changeLanguage(codeLanguages[language]));
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.props.dispatch(SessionActions.logout());
|
||||
}
|
||||
}
|
||||
|
||||
export default connect((store) => {
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
height: 32px;
|
||||
width: 100%;
|
||||
|
||||
&--login-links {
|
||||
&__user-name {
|
||||
color: $primary-red;
|
||||
}
|
||||
|
||||
&__login-links {
|
||||
border-top-left-radius: 4px;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
|
@ -14,7 +18,7 @@
|
|||
padding: 5px 20px 0 10px;
|
||||
}
|
||||
|
||||
&--languages {
|
||||
&__languages {
|
||||
float: right;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
|
|
|
@ -15,7 +15,7 @@ class Menu extends React.Component {
|
|||
icon: React.PropTypes.string
|
||||
})).isRequired,
|
||||
selectedIndex: React.PropTypes.number,
|
||||
tabbable: React.PropTypes.boolean
|
||||
tabbable: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -103,5 +103,107 @@ module.exports = [
|
|||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/user/get',
|
||||
time: 100,
|
||||
response: function () {
|
||||
return {
|
||||
status: 'success',
|
||||
data: {
|
||||
name: 'Haskell Curry',
|
||||
email: 'haskell@lambda.com',
|
||||
tickets: [
|
||||
{
|
||||
ticketNumber: '445441',
|
||||
title: 'Problem with installation',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
department: {
|
||||
id: 2,
|
||||
name: 'Environment Setup'
|
||||
},
|
||||
date: '15 Apr 2016',
|
||||
file: 'http://www.opensupports.com/some_file.zip',
|
||||
language: 'en',
|
||||
unread: true,
|
||||
closed: false,
|
||||
author: {
|
||||
id: 12,
|
||||
name: 'Haskell Curry',
|
||||
email: 'haskell@lambda.com'
|
||||
},
|
||||
owner: {
|
||||
id: 15,
|
||||
name: 'Steve Jobs',
|
||||
email: 'steve@jobs.com'
|
||||
},
|
||||
comments: [
|
||||
{
|
||||
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||
author: {
|
||||
id: 15,
|
||||
name: 'Steve Jobs',
|
||||
email: 'jobs@steve.com',
|
||||
staff: true
|
||||
},
|
||||
date: '12 Dec 2016',
|
||||
file: ''
|
||||
},
|
||||
{
|
||||
content: 'I have already installed apache, but the problem persists',
|
||||
author: {
|
||||
id: 12,
|
||||
name: 'Haskell Curry',
|
||||
steve: 'haskell@lambda.com',
|
||||
staff: false
|
||||
},
|
||||
date: '12 Dec 2016',
|
||||
file: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
ticketNumber: '878552',
|
||||
title: 'Lorem ipsum door',
|
||||
content: 'I had a problem with the installation of the php server',
|
||||
department: {
|
||||
id: 2,
|
||||
name: 'Environment Setup'
|
||||
},
|
||||
date: '15 Apr 2016',
|
||||
file: 'http://www.opensupports.com/some_file.zip',
|
||||
language: 'en',
|
||||
unread: false,
|
||||
closed: false,
|
||||
author: {
|
||||
name: 'Haskell Curry',
|
||||
email: 'haskell@lambda.com'
|
||||
},
|
||||
owner: {
|
||||
name: 'Steve Jobs'
|
||||
},
|
||||
comments: [
|
||||
{
|
||||
content: 'Do you have apache installed? It generally happens if you dont have apache.',
|
||||
author: {
|
||||
name: 'Steve Jobs',
|
||||
email: 'jobs@steve.com',
|
||||
staff: true
|
||||
}
|
||||
},
|
||||
{
|
||||
content: 'I have already installed apache, but the problem persists',
|
||||
author: {
|
||||
name: 'Haskell Curry',
|
||||
steve: 'haskell@lambda.com',
|
||||
staff: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
];
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'WELCOME': 'Welcome',
|
||||
'SUBMIT': 'Submit',
|
||||
'LOG_IN': 'Log in',
|
||||
'SIGN_UP': 'Sign up',
|
||||
|
@ -8,6 +9,11 @@ export default {
|
|||
'NEW_PASSWORD': 'New password',
|
||||
'REPEAT_NEW_PASSWORD': 'Repeat new password',
|
||||
'BACK_LOGIN_FORM': 'Back to login form',
|
||||
'TICKET_LIST': 'Ticket List',
|
||||
'CREATE_TICKET': 'Create Ticket',
|
||||
'VIEW_ARTICLES': 'View Articles',
|
||||
'EDIT_PROFILE': 'Edit Profile',
|
||||
'CLOSE_SESSION': 'Close session',
|
||||
|
||||
//ERRORS
|
||||
'EMAIL_NOT_EXIST': 'Email does not exist',
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
export default {
|
||||
call: stub().returns(new Promise(function (resolve) {
|
||||
resolve();
|
||||
resolve({
|
||||
status: 'success',
|
||||
data: {}
|
||||
});
|
||||
}))
|
||||
};
|
|
@ -29,6 +29,17 @@ class SessionStore {
|
|||
closeSession() {
|
||||
this.removeItem('userId');
|
||||
this.removeItem('token');
|
||||
|
||||
this.clearRememberData();
|
||||
this.clearUserData();
|
||||
}
|
||||
|
||||
storeUserData(data) {
|
||||
this.setItem('userData', JSON.stringify(data));
|
||||
}
|
||||
|
||||
getUserData() {
|
||||
return JSON.parse(this.getItem('userData'));
|
||||
}
|
||||
|
||||
storeRememberData({token, userId, expiration}) {
|
||||
|
@ -73,6 +84,10 @@ class SessionStore {
|
|||
this.removeItem('rememberData-expiration');
|
||||
}
|
||||
|
||||
clearUserData() {
|
||||
this.removeItem('userData');
|
||||
}
|
||||
|
||||
getItem(key) {
|
||||
return this.storage.getItem(key);
|
||||
}
|
||||
|
|
|
@ -19,8 +19,9 @@ class SessionReducer extends Reducer {
|
|||
'LOGIN_FULFILLED': this.onLoginCompleted.bind(this),
|
||||
'LOGIN_REJECTED': this.onLoginFailed,
|
||||
'LOGOUT_FULFILLED': this.onLogout,
|
||||
'USER_DATA_FULFILLED': this.onUserDataRetrieved,
|
||||
'CHECK_SESSION_REJECTED': (state) => { return _.extend({}, state, {initDone: true})},
|
||||
'SESSION_CHECKED': (state) => { return _.extend({}, state, {initDone: true, logged: true})},
|
||||
'SESSION_CHECKED': this.onSessionChecked,
|
||||
'LOGIN_AUTO_FULFILLED': this.onAutoLogin.bind(this),
|
||||
'LOGIN_AUTO_REJECTED': this.onAutoLoginFail
|
||||
};
|
||||
|
@ -54,7 +55,6 @@ class SessionReducer extends Reducer {
|
|||
|
||||
onLogout(state) {
|
||||
sessionStore.closeSession();
|
||||
sessionStore.clearRememberData();
|
||||
|
||||
return _.extend({}, state, {
|
||||
initDone: true,
|
||||
|
@ -77,7 +77,6 @@ class SessionReducer extends Reducer {
|
|||
|
||||
onAutoLoginFail(state) {
|
||||
sessionStore.closeSession();
|
||||
sessionStore.clearRememberData();
|
||||
|
||||
return _.extend({}, state, {
|
||||
initDone: true
|
||||
|
@ -95,6 +94,30 @@ class SessionReducer extends Reducer {
|
|||
|
||||
sessionStore.createSession(resultData.userId, resultData.token);
|
||||
}
|
||||
|
||||
onUserDataRetrieved(state, payload) {
|
||||
let userData = payload.data;
|
||||
|
||||
sessionStore.storeUserData(payload.data);
|
||||
|
||||
return _.extend({}, state, {
|
||||
userName: userData.name,
|
||||
userEmail: userData.email,
|
||||
userTickets: userData.tickets
|
||||
});
|
||||
}
|
||||
|
||||
onSessionChecked(state) {
|
||||
let userData = sessionStore.getUserData();
|
||||
|
||||
return _.extend({}, state, {
|
||||
initDone: true,
|
||||
logged: true,
|
||||
userName: userData.name,
|
||||
userEmail: userData.email,
|
||||
userTickets: userData.tickets
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SessionReducer.getInstance();
|
Loading…
Reference in New Issue