Merged in os-70-create-admin-dashboard-architecture (pull request #53)

Create admin dashboard architecture
This commit is contained in:
Ivan Diaz 2016-09-26 13:48:13 -03:00
commit 3dc9e6ca05
58 changed files with 1061 additions and 73 deletions

View File

@ -12,7 +12,7 @@ export default {
path: '/user/login',
data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token));
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
return result;
})
@ -49,7 +49,7 @@ export default {
};
},
getUserData(userId, token) {
getUserData(userId, token, staff) {
let data = {};
if (userId && token) {
@ -62,7 +62,7 @@ export default {
return {
type: 'USER_DATA',
payload: API.call({
path: '/user/get',
path: (staff) ? '/staff/get' : '/user/get',
data: data
})
}

View File

@ -44,7 +44,9 @@ class App extends React.Component {
const validations = {
languageChanged: props.config.language !== this.props.config.language,
loggedIn: !_.includes(props.location.pathname, '/dashboard') && props.session.logged,
loggedOut: _.includes(props.location.pathname, '/dashboard') && !props.session.logged
loggedOut: _.includes(props.location.pathname, '/dashboard') && !props.session.logged,
loggedInStaff: !_.includes(props.location.pathname, '/admin/panel') && props.session.logged && props.session.staff,
loggedOutStaff: _.includes(props.location.pathname, '/admin/panel') && !props.session.logged
};
if (validations.languageChanged) {
@ -55,8 +57,14 @@ class App extends React.Component {
browserHistory.push('/');
}
if (validations.loggedIn) {
if (validations.loggedOutStaff) {
browserHistory.push('/admin');
}
if (validations.loggedIn && !props.session.staff) {
browserHistory.push('/dashboard');
} else if(validations.loggedInStaff) {
browserHistory.push('/admin/panel');
}
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import store from 'app/store';
@ -20,8 +20,35 @@ import DashboardEditProfilePage from 'app/main/dashboard/dashboard-edit-profile/
import DashboardArticlePage from 'app/main/dashboard/dashboard-article/dashboard-article-page';
import DashboardTicketPage from 'app/main/dashboard/dashboard-ticket/dashboard-ticket-page';
// ADMIN PANEL
import AdminLoginPage from 'app/admin/admin-login-page';
import AdminPanel from 'app/admin/panel/admin-panel';
import AdminPanelLayout from 'app/admin/panel/admin-panel-layout';
import AdminPanelStats from 'app/admin/panel/dashboard/admin-panel-stats';
import AdminPanelActivity from 'app/admin/panel/dashboard/admin-panel-activity';
import AdminPanelMyAccount from 'app/admin/panel/dashboard/admin-panel-my-account';
import AdminPanelMyTickets from 'app/admin/panel/tickets/admin-panel-my-tickets';
import AdminPanelNewTickets from 'app/admin/panel/tickets/admin-panel-new-tickets';
import AdminPanelAllTickets from 'app/admin/panel/tickets/admin-panel-all-tickets';
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket';
import AdminPanelCustomResponses from 'app/admin/panel/tickets/admin-panel-custom-responses';
import AdminPanelListUsers from 'app/admin/panel/users/admin-panel-list-users';
import AdminPanelViewUser from 'app/admin/panel/users/admin-panel-view-user';
import AdminPanelBanUsers from 'app/admin/panel/users/admin-panel-ban-users';
import AdminPanelListArticles from 'app/admin/panel/articles/admin-panel-list-articles';
import AdminPanelViewArticle from 'app/admin/panel/articles/admin-panel-view-article';
import AdminPanelStaffMembers from 'app/admin/panel/staff/admin-panel-staff-members';
import AdminPanelDepartments from 'app/admin/panel/staff/admin-panel-departments';
import AdminPanelViewStaff from 'app/admin/panel/staff/admin-panel-view-staff';
import AdminPanelSystemPreferences from 'app/admin/panel/settings/admin-panel-system-preferences';
import AdminPanelUserSystem from 'app/admin/panel/settings/admin-panel-user-system';
import AdminPanelEmailTemplates from 'app/admin/panel/settings/admin-panel-email-templates';
import AdminPanelCustomFields from 'app/admin/panel/settings/admin-panel-custom-fields';
const history = syncHistoryWithStore(browserHistory, store);
@ -43,10 +70,50 @@ export default (
<Route path='ticket/:ticketNumber' component={DashboardTicketPage}/>
</Route>
</Route>
<Route path='admin'>
<Route path="admin">
<IndexRoute component={AdminLoginPage} />
<Route path='panel' component={MainLayout}>
<IndexRoute component={AdminPanel} />
<Route path="panel" component={AdminPanelLayout}>
<IndexRedirect to="stats" />
<Route path="stats" component={AdminPanelStats} />
<Route path="activity" component={AdminPanelActivity} />
<Route path="my-account" component={AdminPanelMyAccount} />
<Route path="tickets">
<IndexRedirect to="my-tickets" />
<Route path="my-tickets" component={AdminPanelMyTickets} />
<Route path="new-tickets" component={AdminPanelNewTickets} />
<Route path="all-tickets" component={AdminPanelAllTickets} />
<Route path="custom-responses" component={AdminPanelCustomResponses} />
<Route path="view-ticket" component={AdminPanelViewTicket} />
</Route>
<Route path="users">
<IndexRedirect to="list-users" />
<Route path="list-users" component={AdminPanelListUsers} />
<Route path="view-user" component={AdminPanelViewUser} />
<Route path="ban-users" component={AdminPanelBanUsers} />
</Route>
<Route path="articles">
<IndexRedirect to="list-articles" />
<Route path="list-articles" component={AdminPanelListArticles} />
<Route path="view-article" component={AdminPanelViewArticle} />
</Route>
<Route path="staff">
<IndexRedirect to="staff-members" />
<Route path="staff-members" component={AdminPanelStaffMembers} />
<Route path="view-staff" component={AdminPanelViewStaff} />
<Route path="departments" component={AdminPanelDepartments} />
</Route>
<Route path="settings">
<IndexRedirect to="system-preferences" />
<Route path="system-preferences" component={AdminPanelSystemPreferences} />
<Route path="user-system" component={AdminPanelUserSystem} />
<Route path="email-templates" component={AdminPanelEmailTemplates} />
<Route path="custom-fields" component={AdminPanelCustomFields} />
</Route>
</Route>
</Route>

View File

@ -1,33 +1,58 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Widget from 'core-components/widget';
class AdminLoginPage extends React.Component {
render(){
render() {
return (
<div className="admin-login-page">
<div className="admin-login-page__content">
<Widget className="admin-login-page__content">
<div className="admin-login-page__image"><img width="100%" src="/images/logo.png" alt="OpenSupports Admin Panel"/></div>
<div className="admin-login-page__login-form">
<Form onSubmit={this.onSubmit.bind(this)}>
<Form onSubmit={this.onSubmit.bind(this)} loading={this.props.session.pending}>
<FormField name="email" label={i18n('EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required />
<FormField name="password" label={i18n('PASSWORD')} field="input" validation="PASSWORD" fieldProps={{password:true, size:'large'}} required />
<SubmitButton> {i18n('LOG_IN')} </SubmitButton>
<FormField name="password" label={i18n('PASSWORD')} field="input" fieldProps={{password:true, size:'large'}} />
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
</Form>
</div>
</div>
{this.renderMessage()}
</Widget>
</div>
);
}
renderMessage() {
let message = null;
if(this.props.session.failed) {
message = (
<Message className="admin-login-page__error" type="error">
{i18n('EMAIL_OR_PASSWORD')}
</Message>
);
}
return message;
}
onSubmit(formState) {
this.props.dispatch(SessionActions.login(_.extend({}, formState, {
staff: true
})));
}
}
export default AdminLoginPage;
export default connect((store) => {
return {
session: store.session
};
})(AdminLoginPage);

View File

@ -5,11 +5,7 @@
&__content {
margin: 0 auto;
display: inline-block;
background-color: white;
padding: 40px;
border-radius: 4px;
text-align: center;
}
&__image {
@ -21,4 +17,8 @@
margin: 0 auto;
display: inline-block;
}
&__error {
margin-top: 30px;
}
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import MainLayout from 'app/main/main-layout';
import AdminPanelStaffWidget from 'app/admin/panel/admin-panel-staff-widget';
import AdminPanelMenu from 'app/admin/panel/admin-panel-menu';
import Widget from 'core-components/widget';
class AdminPanel extends React.Component {
render() {
return (
<MainLayout>
<div className="admin-panel-layout">
<div className="row admin-panel-layout__header">
<div className="col-md-3">
<AdminPanelStaffWidget />
</div>
<div className="col-md-9">
<AdminPanelMenu location={this.props.location} />
</div>
</div>
<div className="row">
<div className="col-md-12 admin-panel-layout__content">
<Widget>
{this.props.children}
</Widget>
</div>
</div>
</div>
</MainLayout>
);
}
}
export default AdminPanel;

View File

@ -0,0 +1,7 @@
.admin-panel-layout {
padding: 0 10px;
&__header {
margin-bottom: 20px;
}
}

View File

@ -0,0 +1,202 @@
import React from 'react';
import _ from 'lodash';
import {dispatch} from 'app/store';
import i18n from 'lib-app/i18n';
import Menu from 'core-components/menu';
class AdminPanelMenu extends React.Component {
static contextTypes = {
router: React.PropTypes.object
};
static propTypes = {
location: React.PropTypes.object
};
render() {
return (
<div className="admin-panel-menu">
<Menu {...this.getGroupsMenuProps()} />
<Menu {...this.getGroupMenuProps()} />
</div>
);
}
getGroupsMenuProps() {
return {
items: this.getGroups(),
selectedIndex: this.getGroupIndex(),
onItemClick: this.onGroupClick.bind(this),
tabbable: true,
type: 'horizontal'
};
}
getGroupMenuProps() {
return {
items: this.getGroupItems(),
selectedIndex: this.getGroupItemIndex(),
onItemClick: this.onGroupItemClick.bind(this),
tabbable: true,
type: 'horizontal-list'
};
}
getGroups() {
return this.getRoutes().map((group) => {
return {
content: group.groupName,
icon: group.icon
};
});
}
getGroupItems() {
const group = this.getRoutes()[this.getGroupIndex()];
return group.items.map((item) => {
return {
content: item.name
};
});
}
onGroupClick(index) {
this.context.router.push(this.getRoutes()[index].path);
}
onGroupItemClick(index) {
const group = this.getRoutes()[this.getGroupIndex()];
this.context.router.push(group.items[index].path);
}
getGroupItemIndex() {
const group = this.getRoutes()[this.getGroupIndex()];
const pathname = this.props.location.pathname;
return _.findIndex(group.items, {path: pathname});
}
getGroupIndex() {
const pathname = this.props.location.pathname;
const groupIndex = _.findLastIndex(this.getRoutes(), (group) => {
return _.includes(pathname, group.path);
});
return (groupIndex === -1) ? 0 : groupIndex;
}
getRoutes() {
return [
{
groupName: i18n('DASHBOARD'),
path: '/admin/panel',
icon: 'tachometer',
items: [
{
name: i18n('TICKET_STATS'),
path: '/admin/panel/stats'
},
{
name: i18n('LAST_ACTIVITY'),
path: '/admin/panel/activity'
}
]
},
{
groupName: i18n('TICKETS'),
path: '/admin/panel/tickets',
icon: 'ticket',
items: [
{
name: i18n('MY_TICKETS'),
path: '/admin/panel/tickets/my-tickets'
},
{
name: i18n('NEW_TICKETS'),
path: '/admin/panel/tickets/new-tickets'
},
{
name: i18n('ALL_TICKETS'),
path: '/admin/panel/tickets/all-tickets'
},
{
name: i18n('CUSTOM_RESPONSES'),
path: '/admin/panel/tickets/custom-responses'
}
]
},
{
groupName: i18n('USERS'),
path: '/admin/panel/users',
icon: 'user',
items: [
{
name: i18n('LIST_USERS'),
path: '/admin/panel/users/list-users'
},
{
name: i18n('BAN_USERS'),
path: '/admin/panel/users/ban-users'
}
]
},
{
groupName: i18n('ARTICLES'),
path: '/admin/panel/articles',
icon: 'book',
items: [
{
name: i18n('LIST_ARTICLES'),
path: '/admin/panel/articles/list-articles'
}
]
},
{
groupName: i18n('STAFF'),
path: '/admin/panel/staff',
icon: 'users',
items: [
{
name: i18n('STAFF_MEMBERS'),
path: '/admin/panel/staff/staff-members'
},
{
name: i18n('DEPARTMENTS'),
path: '/admin/panel/staff/departments'
}
]
},
{
groupName: i18n('SETTINGS'),
path: '/admin/panel/settings',
icon: 'cogs',
items: [
{
name: i18n('SYSTEM_PREFERENCES'),
path: '/admin/panel/settings/system-preferences'
},
{
name: i18n('USER_SYSTEM'),
path: '/admin/panel/settings/user-system'
},
{
name: i18n('EMAIL_TEMPLATES'),
path: '/admin/panel/settings/email-templates'
},
{
name: i18n('FILTERS_CUSTOM_FIELDS'),
path: '/admin/panel/settings/custom-fields'
}
]
}
];
}
}
export default AdminPanelMenu;

View File

@ -0,0 +1,3 @@
.admin-panel-menu {
}

View File

@ -0,0 +1,52 @@
import React from 'react';
import classNames from 'classnames';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import Button from 'core-components/button';
import SessionActions from 'actions/session-actions';
class AdminPanelStaffWidget extends React.Component {
render() {
return (
<div className={this.getClass()}>
<div className="admin-panel-staff-widget__user-data">
<div className="admin-panel-staff-widget__name">{this.props.session.userName}</div>
<div className="admin-panel-staff-widget__actions">
<Button className="admin-panel-staff-widget__action" type="link" route={{to:'/admin/panel/my-account'}}>
{i18n('MY_ACCOUNT')}
</Button>
<span className="admin-panel-staff-widget__action-separator">|</span>
<Button className="admin-panel-staff-widget__action" type="link" onClick={this.closeSession.bind(this)}>
{i18n('CLOSE_SESSION')}
</Button>
</div>
</div>
<div className="admin-panel-staff-widget__profile-pic-wrapper">
<img className="admin-panel-staff-widget__profile-pic" src={this.props.session.userProfilePic} />
</div>
</div>
);
}
getClass() {
let classes = {
'admin-panel-staff-widget': true
};
classes[this.props.className] = (this.props.className);
return classNames(classes);
}
closeSession() {
this.props.dispatch(SessionActions.logout());
}
}
export default connect((store) => {
return {
session: store.session
};
})(AdminPanelStaffWidget);

View File

@ -0,0 +1,47 @@
@import '../../../scss/vars';
.admin-panel-staff-widget {
background-color: $secondary-blue;
position: relative;
width: 100%;
height: 165px;
text-align: center;
&__profile-pic-wrapper {
position: absolute;
top: 8px;
border: 4px solid $grey;
border-radius: 50%;
width: 90px;
height: 90px;
overflow: hidden;
text-align: center;
left: 50%;
transform: translate(-50%, 0);
}
&__profile-pic {
height: 100%;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
}
&__user-data {
position: absolute;
background-color: white;
bottom: 0;
width: 100%;
height: 110px;
padding-top: 48px;
}
&__name {
font-size: $font-size--md;
}
&__actions {
font-size: $font-size--xs;
margin-top: 10px;
}
}

View File

@ -1,13 +0,0 @@
import React from 'react';
class AdminPanel extends React.Component {
render(){
return (
<div>
Admin panel...
</div>
);
}
}
export default AdminPanel;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelListArticles extends React.Component {
render() {
return (
<div>
/admin/panel/articles/list-articles
</div>
);
}
}
export default AdminPanelListArticles;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelViewArticle extends React.Component {
render() {
return (
<div>
/admin/panel/articles/view-article
</div>
);
}
}
export default AdminPanelViewArticle;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelActivity extends React.Component {
render() {
return (
<div>
/admin/panel/activity
</div>
);
}
}
export default AdminPanelActivity;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelMyAccount extends React.Component {
render() {
return (
<div>
/admin/panel/my-account
</div>
);
}
}
export default AdminPanelMyAccount;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelStats extends React.Component {
render() {
return (
<div>
/admin/panel/stats
</div>
);
}
}
export default AdminPanelStats;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelCustomFields extends React.Component {
render() {
return (
<div>
/admin/panel/settings/custom-fields
</div>
);
}
}
export default AdminPanelCustomFields;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelEmailTemplates extends React.Component {
render() {
return (
<div>
/admin/panel/settings/email-templates
</div>
);
}
}
export default AdminPanelEmailTemplates;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelSystemPreferences extends React.Component {
render() {
return (
<div>
/admin/panel/settings/system-preferences
</div>
);
}
}
export default AdminPanelSystemPreferences;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelUserSystem extends React.Component {
render() {
return (
<div>
/admin/panel/settings/user-system
</div>
);
}
}
export default AdminPanelUserSystem;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelDepartments extends React.Component {
render() {
return (
<div>
/admin/panel/staff/departments
</div>
);
}
}
export default AdminPanelDepartments;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelStaffMembers extends React.Component {
render() {
return (
<div>
/admin/panel/staff/staff-members
</div>
);
}
}
export default AdminPanelStaffMembers;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelViewStaff extends React.Component {
render() {
return (
<div>
/admin/panel/staff/view-staff
</div>
);
}
}
export default AdminPanelViewStaff;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelAllTickets extends React.Component {
render() {
return (
<div>
/admin/panel/tickets/all-tickets
</div>
);
}
}
export default AdminPanelAllTickets;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelCustomResponses extends React.Component {
render() {
return (
<div>
/admin/panel/tickets/custom-responses
</div>
);
}
}
export default AdminPanelCustomResponses;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelMyTickets extends React.Component {
render() {
return (
<div>
/admin/panel/tickets/my-tickets
</div>
);
}
}
export default AdminPanelMyTickets;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelNewTickets extends React.Component {
render() {
return (
<div>
/admin/panel/tickets/new-tickets
</div>
);
}
}
export default AdminPanelNewTickets;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelViewTicket extends React.Component {
render() {
return (
<div>
/admin/panel/tickets/view-ticket
</div>
);
}
}
export default AdminPanelViewTicket;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelBanUsers extends React.Component {
render() {
return (
<div>
/admin/panel/users/ban-users
</div>
);
}
}
export default AdminPanelBanUsers;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelListUsers extends React.Component {
render() {
return (
<div>
/admin/panel/users/list-users
</div>
);
}
}
export default AdminPanelListUsers;

View File

@ -0,0 +1,14 @@
import React from 'react';
class AdminPanelViewUser extends React.Component {
render() {
return (
<div>
/admin/panel/users/view-user
</div>
);
}
}
export default AdminPanelViewUser;

View File

@ -65,12 +65,6 @@ let DemoPage = React.createClass({
</Widget>
)
},
{
title: 'DropDown',
render: (
<DropDown items={dropDownItems} onChange={function (index) { console.log('changed to ' + index); }} />
)
},
{
title: 'Primary Menu',
render: (
@ -83,6 +77,30 @@ let DemoPage = React.createClass({
<Menu items={secondaryMenuItems} type="secondary"/>
)
},
{
title: 'Navigation Menu',
render: (
<Menu items={secondaryMenuItems} type="navigation"/>
)
},
{
title: 'Horizontal Menu',
render: (
<Menu items={secondaryMenuItems.slice(0, 3)} type="horizontal"/>
)
},
{
title: 'HorizontalList Menu',
render: (
<Menu items={dropDownItems.slice(0, 3)} type="horizontal-list"/>
)
},
{
title: 'DropDown',
render: (
<DropDown items={dropDownItems} onChange={function (index) { console.log('changed to ' + index); }} />
)
},
{
title: 'Tooltip',
render: (

View File

@ -20,7 +20,6 @@ class DashboardEditProfilePage extends React.Component {
messagePass:''
};
render() {
return (
<div className="edit-profile-page">

View File

@ -84,7 +84,9 @@ class Menu extends React.Component {
let classes = {
'menu': true,
'menu_secondary': (this.props.type === 'secondary'),
'menu_navigation': (this.props.type === 'navigation')
'menu_navigation': (this.props.type === 'navigation'),
'menu_horizontal': (this.props.type === 'horizontal'),
'menu_horizontal-list': (this.props.type === 'horizontal-list')
};
classes[this.props.className] = true;

View File

@ -1,5 +1,7 @@
@import "../scss/vars";
$transition: background-color 0.3s ease, color 0.3s ease;
.menu {
&__list {
@ -13,7 +15,7 @@
&__list-item {
padding: 8px;
transition: background-color 0.3s ease, color 0.3s ease;
transition: $transition;
&_selected,
&:hover {
@ -78,4 +80,93 @@
color: $primary-blue;
}
}
&_horizontal {
text-align: center;
.menu__list {
background-color: transparent;
font-size: $font-size--md;
}
.menu__icon {
display: block;
margin-right: 0;
margin-bottom: 20px;
margin-top: 20px;
font-size: $font-size--xl;
}
.menu__list-item {
background-color: white;
color: $primary-black;
cursor: pointer;
display: inline-block;
height: 120px;
width: 16.6667%;
@media (max-width: 667px) {
width: 33.3333%;
}
}
.menu__list-item_selected {
background-color: $secondary-blue;
color: white;
}
.menu__list-item:hover,
.menu__list-item:focus {
color: $dark-grey;
outline: none;
}
.menu__list-item_selected:focus,
.menu__list-item_selected:hover {
color: white;
}
}
&_horizontal-list {
text-align: left;
background-color: $secondary-blue;
min-height: 45px;
.menu__list {
background-color: transparent;
font-size: $font-size--sm;
}
.menu__list-item {
transition: none;
background-color: transparent;
color: white;
cursor: pointer;
display: inline-block;
margin-top: 11px;
margin-bottom: 10px;
margin-left: 20px;
padding: 2px 13px;
}
.menu__list-item:hover {
transition: $transition;
background-color: transparent;
color: $primary-black;
}
.menu__list-item:focus {
color: $grey;
outline: none;
}
.menu__list-item_selected,
.menu__list-item_selected:hover {
transition: $transition;
padding: 2px 13px;
border-radius: 30px;
background-color: $primary-blue;
color: white;
}
}
}

View File

@ -0,0 +1,23 @@
module.exports = [
{
path: '/staff/get',
time: 100,
response: function () {
return {
status: 'success',
data: {
name: 'Emilia Clarke',
email: 'staff@opensupports.com',
profilePic: 'http://i65.tinypic.com/9bep95.jpg',
level: 1,
staff: true,
departments: [
{id: 1, name: 'Sales Support'},
{id: 2, name: 'Technical Issues'},
{id: 3, name: 'System and Administration'}
]
}
};
}
}
];

View File

@ -9,6 +9,7 @@ module.exports = [
response = {
status: 'success',
data: {
'staff': data.staff,
'userId': 12,
'token': 'cc6b4921e6733d6aafe284ec0d7be57e',
'rememberToken': (data.remember) ? 'aa41efe0a1b3eeb9bf303e4561ff8392' : null,

View File

@ -36,8 +36,28 @@ export default {
'CUSTOMER': 'Customer',
'YES': 'Yes',
'CANCEL': 'Cancel',
'MY_ACCOUNT': 'My Account',
'DASHBOARD': 'Dashboard',
'USERS': 'Users',
'SETTINGS': 'Settings',
'TICKET_STATS': 'Ticket Stats',
'LAST_ACTIVITY': 'Last Activity',
'MY_TICKETS': 'My Tickets',
'NEW_TICKETS': 'New Tickets',
'ALL_TICKETS': 'All Tickets',
'CUSTOM_RESPONSES': 'Custom Responses',
'LIST_USERS': 'List Users',
'BAN_USERS': 'Ban Users',
'LIST_ARTICLES': 'List Articles',
'STAFF_MEMBERS': 'Staff Members',
'DEPARTMENTS': 'Departments',
'SYSTEM_PREFERENCES': 'System Preferences',
'USER_SYSTEM': 'User System',
'EMAIL_TEMPLATES': 'Email Templates',
'FILTERS_CUSTOM_FIELDS': 'Filters and Custom Fields',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
'EMAIL_NOT_EXIST': 'Email does not exist',
'ERROR_EMPTY': 'Invalid value',
'ERROR_PASSWORD': 'Invalid password',

View File

@ -17,6 +17,7 @@ let fixtures = (function () {
// FIXTURES
fixtures.add(require('data/fixtures/user-fixtures'));
fixtures.add(require('data/fixtures/staff-fixtures'));
fixtures.add(require('data/fixtures/ticket-fixtures'));
fixtures.add(require('data/fixtures/system-fixtures'));

View File

@ -41,7 +41,8 @@ class SessionReducer extends Reducer {
return _.extend({}, state, {
logged: true,
pending: false,
failed: false
failed: false,
staff: payload.data.staff
});
}
@ -101,8 +102,12 @@ class SessionReducer extends Reducer {
sessionStore.storeUserData(payload.data);
return _.extend({}, state, {
userName: userData.name,
userEmail: userData.email,
staff: userData.staff,
userName: userData.name,
userEmail: userData.email,
userProfilePic: userData.profilePic,
userLevel: userData.level,
userDepartments: userData.departments,
userTickets: userData.tickets
});
}
@ -113,8 +118,12 @@ class SessionReducer extends Reducer {
return _.extend({}, state, {
initDone: true,
logged: true,
staff: userData.staff,
userName: userData.name,
userEmail: userData.email,
userProfilePic: userData.profilePic,
userLevel: userData.level,
userDepartments: userData.departments,
userTickets: userData.tickets
});
}

View File

@ -0,0 +1,9 @@
<?php
require_once 'staff/get.php';
$systemControllerGroup = new ControllerGroup();
$systemControllerGroup->setGroupPath('/staff');
$systemControllerGroup->addController(new GetStaffController);
$systemControllerGroup->finalize();

View File

@ -0,0 +1,36 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
class GetStaffController extends Controller {
const PATH = '/get';
public function validations() {
return [
'permission' => 'staff_1',
'requestData' => []
];
}
public function handler() {
$user = Controller::getLoggedUser();
$parsedDepartmentList = [];
$departmentList = $user->sharedDepartmentList;
foreach($departmentList as $department) {
$parsedDepartmentList[] = [
'id' => $department->id,
'name' => $department->name
];
}
Response::respondSuccess([
'name' => $user->name,
'email' => $user->email,
'profilePic' => $user->profilePic,
'level' => $user->level,
'staff' => true,
'departments' => $parsedDepartmentList
]);
}
}

View File

@ -13,6 +13,11 @@ class GetUserController extends Controller {
}
public function handler() {
if (Controller::isStaffLogged()) {
Response::respondError(ERRORS::INVALID_CREDENTIALS);
return;
}
$user = Controller::getLoggedUser();
$parsedTicketList = [];
$ticketList = $user->sharedTicketList;

View File

@ -46,7 +46,7 @@ class LoginController extends Controller {
}
private function createUserSession() {
Session::getInstance()->createSession($this->userInstance->id);
Session::getInstance()->createSession($this->userInstance->id, Controller::request('staff'));
}
private function getUserData() {
@ -55,6 +55,7 @@ class LoginController extends Controller {
return array(
'userId' => $userInstance->id,
'userEmail' => $userInstance->email,
'staff' => Controller::request('staff'),
'token' => Session::getInstance()->getToken(),
'rememberToken' => $this->rememberToken
);
@ -64,7 +65,11 @@ class LoginController extends Controller {
$email = Controller::request('email');
$password = Controller::request('password');
return User::authenticate($email, $password);
if(Controller::request('staff')) {
return Staff::authenticate($email, $password);
} else {
return User::authenticate($email, $password);
}
}
private function getUserByRememberToken() {

View File

@ -36,7 +36,13 @@ abstract class Controller {
}
public static function getLoggedUser() {
return User::getUser((int)self::request('csrf_userid'));
$session = Session::getInstance();
if ($session->isStaffLogged()) {
return Staff::getUser((int)self::request('csrf_userid'));
} else {
return User::getUser((int)self::request('csrf_userid'));
}
}
public static function isUserLogged() {
@ -48,12 +54,8 @@ abstract class Controller {
));
}
public static function isStaffLogged() {
return Controller::isUserLogged() && (Controller::getLoggedUser()->admin === 1);
}
public static function isAdminLogged() {
return Controller::isUserLogged() && (Controller::getLoggedUser()->admin === 2);
public static function isStaffLogged($level = 1) {
return Controller::isUserLogged() && (Controller::getLoggedUser()->level >= $level);
}
public static function getAppInstance() {

View File

@ -16,8 +16,9 @@ class Validator {
$permissions = [
'any' => true,
'user' => Controller::isUserLogged(),
'staff' => Controller::isStaffLogged(),
'admin' => Controller::isAdminLogged()
'staff_1' => Controller::isStaffLogged(1),
'staff_2' => Controller::isStaffLogged(2),
'staff_3' => Controller::isStaffLogged(3)
];
if (!$permissions[$permission]) {

View File

@ -24,8 +24,9 @@ class Session {
return self::$instance;
}
public function createSession($userId) {
public function createSession($userId, $staff = false) {
$this->store('userId', $userId);
$this->store('staff', $staff);
$this->store('token', Hashing::generateRandomToken());
}
@ -37,6 +38,10 @@ class Session {
return !!$this->getToken();
}
public function isStaffLogged() {
return $this->getStoredData('staff');
}
public function checkAuthentication($data) {
$userId = $this->getStoredData('userId');
$token = $this->getStoredData('token');

View File

@ -3,6 +3,11 @@
class Staff extends DataStore {
const TABLE = 'staff';
public static function authenticate($userEmail, $userPassword) {
$user = Staff::getUser($userEmail, 'email');
return ($user && Hashing::verifyPassword($userPassword, $user->password)) ? $user : new NullDataStore();
}
public static function getProps() {
return [

View File

@ -15,16 +15,13 @@ class User extends DataStore {
'email',
'password',
'name',
'admin',
'sharedTicketList',
'verificationToken',
];
}
public function getDefaultProps() {
return [
'admin' => 0
];
return [];
}
public static function getUser($value, $property = 'id') {

View File

@ -35,10 +35,11 @@ class LoginControllerTest extends PHPUnit_Framework_TestCase {
$this->loginController->handler();
$this->assertTrue(Session::getInstance()->createSession->hasBeenCalledWithArgs('MOCK_ID'));
$this->assertTrue(Session::getInstance()->createSession->hasBeenCalledWithArgs('MOCK_ID', null));
$this->assertTrue(Response::get('respondSuccess')->hasBeenCalledWithArgs(array(
'userId' => 'MOCK_ID',
'userEmail' => 'MOCK_EMAIL',
'staff' => null,
'token' => 'TEST_TOKEN',
'rememberToken' => null
)));

View File

@ -21,3 +21,4 @@ require './user/get.rb'
require './ticket/create.rb'
require './ticket/comment.rb'
require './ticket/get.rb'
require './staff/get.rb'

View File

@ -26,3 +26,8 @@ class Database
end
$database = Database.new
$staff = {
:email => 'staff@opensupports.com',
:password => 'staff'
}

View File

@ -11,11 +11,12 @@ class Scripts
end
end
def self.login(email = 'steve@jobs.com', password = 'custompassword')
def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false)
request('/user/logout')
response = request('/user/login', {
:email => email,
:password => password
:password => password,
:staff => staff
})
if response['data'].any?

15
tests/staff/get.rb Normal file
View File

@ -0,0 +1,15 @@
describe '/staff/get/' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should return staff member data' do
result = request('/staff/get', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
(result['data']['name']).should.equal('Emilia Clarke')
(result['data']['staff']).should.equal(true)
end
end

View File

@ -13,13 +13,36 @@ describe '/user/login' do
(result['status']).should.equal('fail')
end
# it 'should login correctly' do
it 'should login correctly' do
result = request('/user/login', {
email: @loginEmail,
password: @loginPass
})
# end
(result['status']).should.equal('success')
end
# it 'should fail if already logged in' do
it 'should fail if already logged in' do
result = request('/user/login', {
email: @loginEmail,
password: @loginPass
})
# end
(result['status']).should.equal('fail')
(result['message']).should.equal('User is already logged in')
end
it 'should login staff member' do
request('/user/logout', {})
result = request('/user/login', {
email: $staff[:email],
password: $staff[:password],
staff: true
})
(result['status']).should.equal('success')
(result['data']['staff']).should.equal('true')
end
it 'should return remember token' do
request('/user/logout', {})
@ -31,7 +54,7 @@ describe '/user/login' do
(result['status']).should.equal('success')
@rememberToken = result['data']['rememberToken']# falta comproversion
@rememberToken = result['data']['rememberToken']
@userid = result['data']['userId']
end

View File

@ -10,7 +10,6 @@ describe '/user/signup' do
(userRow['email']).should.equal('steve@jobs.com')
(userRow['name']).should.equal('Steve Jobs')
(userRow['admin']).should.equal('0')
end
it 'should fail if name is invalid' do