Merge branch 'master' into OS141create-Log-Architectur

Conflicts:
	server/controllers/staff/delete.php
	tests/system/edit-settings.rb
This commit is contained in:
AntonyAntonio 2016-12-29 18:54:17 -03:00
commit e317bbea38
53 changed files with 592 additions and 244 deletions

View File

@ -19,13 +19,13 @@ class ArticleAddModal extends React.Component {
render() { render() {
return ( return (
<div className="add-article-modal"> <div className="article-add-modal">
<Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} /> <Header title={i18n('ADD_ARTICLE')} description={i18n('ADD_ARTICLE_DESCRIPTION', {category: this.props.topicName})} />
<Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}> <Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}>
<FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/> <FormField name="title" label={i18n('TITLE')} field="input" fieldProps={{size: 'large'}} validation="TITLE" required/>
<FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/> <FormField name="content" label={i18n('CONTENT')} field="textarea" validation="TEXT_AREA" required/>
<SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton> <SubmitButton type="secondary">{i18n('ADD_ARTICLE')}</SubmitButton>
<Button className="add-article-modal__cancel-button" type="link" onClick={(event) => { <Button className="article-add-modal__cancel-button" type="link" onClick={(event) => {
event.preventDefault(); event.preventDefault();
ModalContainer.closeModal(); ModalContainer.closeModal();
}}>{i18n('CANCEL')}</Button> }}>{i18n('CANCEL')}</Button>

View File

@ -1,4 +1,5 @@
.article-add-article { .article-add-modal {
width: 800px;
&__cancel-button { &__cancel-button {
float: right; float: right;

View File

@ -11,6 +11,7 @@ import TopicEditModal from 'app-components/topic-edit-modal';
import Loading from 'core-components/loading'; import Loading from 'core-components/loading';
import Button from 'core-components/button'; import Button from 'core-components/button';
import Icon from 'core-components/icon'; import Icon from 'core-components/icon';
import Message from 'core-components/message';
class ArticlesList extends React.Component { class ArticlesList extends React.Component {
@ -18,6 +19,7 @@ class ArticlesList extends React.Component {
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
articlePath: React.PropTypes.string, articlePath: React.PropTypes.string,
loading: React.PropTypes.bool, loading: React.PropTypes.bool,
errored: React.PropTypes.bool,
topics: React.PropTypes.array, topics: React.PropTypes.array,
retrieveOnMount: React.PropTypes.bool retrieveOnMount: React.PropTypes.bool
}; };
@ -34,6 +36,10 @@ class ArticlesList extends React.Component {
} }
render() { render() {
if(this.props.errored) {
return <Message type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>;
}
return (this.props.loading) ? <Loading /> : this.renderContent(); return (this.props.loading) ? <Loading /> : this.renderContent();
} }
@ -84,6 +90,7 @@ class ArticlesList extends React.Component {
export default connect((store) => { export default connect((store) => {
return { return {
topics: store.articles.topics, topics: store.articles.topics,
errored: store.articles.errored,
loading: store.articles.loading loading: store.articles.loading
}; };
})(ArticlesList); })(ArticlesList);

View File

@ -10,7 +10,7 @@ class PeopleList extends React.Component {
static propTypes = { static propTypes = {
list: React.PropTypes.arrayOf(React.PropTypes.shape({ list: React.PropTypes.arrayOf(React.PropTypes.shape({
profilePic: React.PropTypes.string, profilePic: React.PropTypes.string,
name: React.PropTypes.string, name: React.PropTypes.node,
assignedTickets: React.PropTypes.number, assignedTickets: React.PropTypes.number,
closedTickets: React.PropTypes.number, closedTickets: React.PropTypes.number,
lastLogin: React.PropTypes.number lastLogin: React.PropTypes.number

View File

@ -47,6 +47,7 @@
&__comment { &__comment {
position: relative; position: relative;
word-break: break-all;
&-pointer { &-pointer {
right: 100%; right: 100%;

View File

@ -1,4 +1,6 @@
import React from 'react'; import React from 'react';
import _ from 'lodash';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
class TicketInfo extends React.Component { class TicketInfo extends React.Component {
@ -16,12 +18,12 @@ class TicketInfo extends React.Component {
{this.props.ticket.content} {this.props.ticket.content}
</div> </div>
<div className="ticket-info__author"> <div className="ticket-info__author">
Author: {this.props.ticket.author.name} {i18n('AUTHOR')}: {this.props.ticket.author.name}
</div> </div>
<div className="ticket-info__properties"> <div className="ticket-info__properties">
<div className="ticket-info__properties__status"> <div className="ticket-info__properties__status">
<span className="ticket-info__properties__label"> <span className="ticket-info__properties__label">
Status: {i18n('STATUS')}:
</span> </span>
<span className={this.getStatusClass()}> <span className={this.getStatusClass()}>
{(this.props.ticket.closed) ? 'closed' : 'open'} {(this.props.ticket.closed) ? 'closed' : 'open'}
@ -29,7 +31,7 @@ class TicketInfo extends React.Component {
</div> </div>
<div className="ticket-info__properties__priority"> <div className="ticket-info__properties__priority">
<span className="ticket-info__properties__label"> <span className="ticket-info__properties__label">
Priority: {i18n('PRIORITY')}:
</span> </span>
<span className={this.getPriorityClass()}> <span className={this.getPriorityClass()}>
{this.props.ticket.priority} {this.props.ticket.priority}
@ -37,24 +39,25 @@ class TicketInfo extends React.Component {
</div> </div>
<div className="ticket-info__properties__owner"> <div className="ticket-info__properties__owner">
<span className="ticket-info__properties__label"> <span className="ticket-info__properties__label">
Owned: {i18n('OWNED')}:
</span> </span>
<span className="ticket-info__properties__badge-red"> <span className={this.getOwnedClass()}>
none {(this.props.ticket.owner) ? i18n('YES') : i18n('NO')}
</span> </span>
</div> </div>
<div className="ticket-info__properties__comments"> <div className="ticket-info__properties__comments">
<span className="ticket-info__properties__label"> <span className="ticket-info__properties__label">
Comments: {i18n('COMMENTS')}:
</span> </span>
<span className="ticket-info__properties__badge-blue"> <span className="ticket-info__properties__badge-blue">
21 {_.filter(this.props.ticket.events, event => event.type === 'COMMENT').length}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
); );
} }
getStatusClass() { getStatusClass() {
if(this.props.ticket.closed) { if(this.props.ticket.closed) {
return 'ticket-info__properties__badge-red'; return 'ticket-info__properties__badge-red';
@ -63,6 +66,14 @@ class TicketInfo extends React.Component {
} }
} }
getOwnedClass() {
if(this.props.ticket.owner) {
return 'ticket-info__properties__badge-green';
} else {
return 'ticket-info__properties__badge-red';
}
}
getPriorityClass() { getPriorityClass() {
let priorityClasses = { let priorityClasses = {
'low': 'ticket-info__properties__badge-green', 'low': 'ticket-info__properties__badge-green',

View File

@ -3,19 +3,28 @@
.ticket-info { .ticket-info {
width: 300px; width: 300px;
font-weight: normal; font-weight: normal;
&__title{
&__title {
color: $primary-black; color: $primary-black;
font-variant: small-caps; font-size: $font-size--md;
} }
&__description{
color: $grey; &__description {
margin-top: 5px;
font-size: small; font-size: small;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
} }
&__author{
&__author {
color: $primary-blue; color: $primary-blue;
margin-bottom: 12px; font-weight: bold;
margin-top: 10px;
margin-bottom: 5px;
} }
&__properties{
&__properties {
background-color: $grey; background-color: $grey;
padding: 10px; padding: 10px;
font-variant: small-caps; font-variant: small-caps;
@ -35,7 +44,7 @@
&__badge-green, &__badge-green,
&__badge-blue, &__badge-blue,
&__badge-red{ &__badge-red {
color: white; color: white;
border-radius: 7px; border-radius: 7px;
display: inline-block; display: inline-block;
@ -44,19 +53,19 @@
margin-left: 5px; margin-left: 5px;
} }
&__badge-green{ &__badge-green {
background-color: $primary-green; background-color: $primary-green;
} }
&__badge-blue{ &__badge-blue {
background-color: $secondary-blue; background-color: $secondary-blue;
} }
&__badge-red{ &__badge-red {
background-color: $primary-red; background-color: $primary-red;
} }
&__label{ &__label {
text-align: right; text-align: right;
width: 71px; width: 71px;
display: inline-block; display: inline-block;
@ -64,10 +73,11 @@
} }
&__status, &__status,
&__owner{ &__owner {
margin-right: 10px; margin-right: 10px;
width: 125px; width: 125px;
.ticket-info__properties__label{
.ticket-info__properties__label {
width: 48px; width: 48px;
} }
} }

View File

@ -14,6 +14,7 @@ import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button'; import SubmitButton from 'core-components/submit-button';
import DropDown from 'core-components/drop-down'; import DropDown from 'core-components/drop-down';
import Button from 'core-components/button'; import Button from 'core-components/button';
import Message from 'core-components/message';
class TicketViewer extends React.Component { class TicketViewer extends React.Component {
static propTypes = { static propTypes = {
@ -188,6 +189,7 @@ class TicketViewer extends React.Component {
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton> <SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
</Form> </Form>
</div> </div>
{(this.state.commentError) ? this.renderCommentError() : null}
</div> </div>
); );
} }
@ -216,6 +218,12 @@ class TicketViewer extends React.Component {
return customResponsesNode; return customResponsesNode;
} }
renderCommentError() {
return (
<Message className="ticket-viewer__message" type="error">{i18n('TICKET_COMMENT_ERROR')}</Message>
);
}
getCommentFormProps() { getCommentFormProps() {
return { return {
onSubmit: this.onSubmit.bind(this), onSubmit: this.onSubmit.bind(this),
@ -316,7 +324,8 @@ class TicketViewer extends React.Component {
onCommentSuccess() { onCommentSuccess() {
this.setState({ this.setState({
loading: false loading: false,
commentError: false
}); });
this.onTicketModification(); this.onTicketModification();
@ -324,7 +333,8 @@ class TicketViewer extends React.Component {
onCommentFail() { onCommentFail() {
this.setState({ this.setState({
loading: false loading: false,
commentError: true
}); });
} }

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash'; import _ from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import {Link} from 'react-router'; import {Link} from 'react-router';
@ -78,11 +79,33 @@ class TopicViewer extends React.Component {
} }
renderArticleItem(article, index) { renderArticleItem(article, index) {
let props = {
className: 'topic-viewer__list-item',
key: index,
draggable: true
};
if(this.props.editable) {
_.extend(props, {
onDragOver: (this.state.currentDraggedId) ? this.onItemDragOver.bind(this, article, index) : null,
onDrop: (this.state.currentDraggedId) ? this.onItemDrop.bind(this, article, index) : null,
onDragStart: () => {
this.setState({currentDraggedId: article.id})
},
onDragEnd: () => {
if(this.state.currentDraggedId) {
this.setState({articles: this.props.articles, currentDraggedId: 0});
}
}
});
}
return ( return (
<li className="topic-viewer__list-item" key={index}> <li {...props}>
<Link {...this.getArticleLinkProps(article, index)}> <Link {...this.getArticleLinkProps(article, index)}>
{article.title} {article.title}
</Link> </Link>
<Icon className="topic-viewer__grab-icon" name="arrows" ref={'grab-' + index}/>
</li> </li>
); );
} }
@ -126,31 +149,16 @@ class TopicViewer extends React.Component {
); );
} }
getArticleLinkProps(article, index) { getArticleLinkProps(article) {
let classes = { let classes = {
'topic-viewer__list-item-button': true, 'topic-viewer__list-item-button': true,
'topic-viewer__list-item-hidden': article.hidden 'topic-viewer__list-item-hidden': article.hidden
}; };
let props = { return {
className: classNames(classes), className: classNames(classes),
to: this.props.articlePath + article.id to: this.props.articlePath + article.id
}; };
if(this.props.editable) {
_.extend(props, {
onDragOver: this.onItemDragOver.bind(this, article, index),
onDrop: this.onItemDrop.bind(this, article, index),
onDragStart: () => this.setState({currentDraggedId: article.id}),
onDragEnd: () => {
if(this.state.currentDraggedId) {
this.setState({articles: this.props.articles});
}
}
});
}
return props;
} }
onDeleteClick() { onDeleteClick() {

View File

@ -38,12 +38,22 @@
width: 50%; width: 50%;
color: $secondary-blue; color: $secondary-blue;
margin-bottom: 10px; margin-bottom: 10px;
user-select: none;
&-hidden { &-hidden {
width: 80%;
display: inline-block;
opacity: 0; opacity: 0;
} }
&:hover {
.topic-viewer__grab-icon {
display: inline-block;
left: 0;
}
}
&-button {
user-select: none;
}
} }
&-item:before { &-item:before {
@ -52,10 +62,18 @@
} }
&-item-button { &-item-button {
display: inline-block;
color: $secondary-blue; color: $secondary-blue;
} }
} }
&__grab-icon {
color: $grey;
cursor: move;
margin-left: 10px;
display: none;
}
&__add-item { &__add-item {
color: $dark-grey; color: $dark-grey;
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {browserHistory} from 'react-router';
import RichTextEditor from 'react-rte-browserify'; import RichTextEditor from 'react-rte-browserify';
import ArticlesActions from 'actions/articles-actions'; import ArticlesActions from 'actions/articles-actions';
@ -9,6 +10,7 @@ import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
import DateTransformer from 'lib-core/date-transformer'; import DateTransformer from 'lib-core/date-transformer';
import AreYouSure from 'app-components/are-you-sure';
import Header from 'core-components/header'; import Header from 'core-components/header';
import Loading from 'core-components/loading'; import Loading from 'core-components/loading';
import Button from 'core-components/button'; import Button from 'core-components/button';
@ -61,10 +63,14 @@ class AdminPanelViewArticle extends React.Component {
renderArticlePreview(article) { renderArticlePreview(article) {
return ( return (
<div className="admin-panel-view-article__content"> <div className="admin-panel-view-article__content">
<div className="admin-panel-view-article__edit-button"> <div className="admin-panel-view-article__edit-buttons">
<Button size="medium" onClick={this.onEditClick.bind(this, article)}>{i18n('EDIT')}</Button> <Button className="admin-panel-view-article__edit-button" size="medium" onClick={this.onEditClick.bind(this, article)} type="tertiary">
{i18n('EDIT')}
</Button>
<Button size="medium" onClick={this.onDeleteClick.bind(this, article)}>
{i18n('DELETE')}
</Button>
</div> </div>
<div className="admin-panel-view-article__article"> <div className="admin-panel-view-article__article">
<Header title={article.title}/> <Header title={article.title}/>
@ -116,6 +122,10 @@ class AdminPanelViewArticle extends React.Component {
}); });
} }
onDeleteClick(article) {
AreYouSure.openModal(i18n('DELETE_ARTICLE_DESCRIPTION'), this.onArticleDeleted.bind(this, article));
}
onFormSubmit(form) { onFormSubmit(form) {
API.call({ API.call({
path: '/article/edit', path: '/article/edit',
@ -139,6 +149,15 @@ class AdminPanelViewArticle extends React.Component {
editable: false editable: false
}); });
} }
onArticleDeleted(article) {
API.call({
path: '/article/delete',
data: {
articleId: article.id
}
}).then(() => browserHistory.push('/admin/panel/articles/list-articles'));
}
} }
export default connect((store) => { export default connect((store) => {

View File

@ -1,10 +1,14 @@
.admin-panel-view-article { .admin-panel-view-article {
&__edit-button { &__edit-buttons {
text-align: left; text-align: left;
margin-bottom: 20px; margin-bottom: 20px;
} }
&__edit-button {
margin-right: 20px;
}
&__last-edited { &__last-edited {
font-style: italic; font-style: italic;
text-align: right; text-align: right;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import {connect} from 'react-redux'; import {browserHistory} from 'react-router';
import _ from 'lodash'; import _ from 'lodash';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
@ -36,7 +36,8 @@ class AdminPanelViewStaff extends React.Component {
getProps() { getProps() {
return _.extend({}, this.state.userData, { return _.extend({}, this.state.userData, {
staffId: this.props.params.staffId * 1 staffId: this.props.params.staffId * 1,
onDelete: this.onDelete.bind(this)
}); });
} }
@ -46,6 +47,10 @@ class AdminPanelViewStaff extends React.Component {
userData: result.data userData: result.data
}); });
} }
onDelete() {
browserHistory.push('/admin/panel/staff/staff-members');
}
} }
export default AdminPanelViewStaff; export default AdminPanelViewStaff;

View File

@ -5,10 +5,13 @@ import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
import SessionStore from 'lib-app/session-store'; import SessionStore from 'lib-app/session-store';
import TicketList from 'app-components/ticket-list'; import TicketList from 'app-components/ticket-list';
import AreYouSure from 'app-components/are-you-sure';
import Form from 'core-components/form'; import Form from 'core-components/form';
import FormField from 'core-components/form-field'; import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button'; import SubmitButton from 'core-components/submit-button';
import Message from 'core-components/message';
import Button from 'core-components/button';
class StaffEditor extends React.Component { class StaffEditor extends React.Component {
static propTypes = { static propTypes = {
@ -20,18 +23,21 @@ class StaffEditor extends React.Component {
level: React.PropTypes.number.isRequired, level: React.PropTypes.number.isRequired,
tickets: React.PropTypes.array.isRequired, tickets: React.PropTypes.array.isRequired,
departments: React.PropTypes.array.isRequired, departments: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func onChange: React.PropTypes.func,
onDelete: React.PropTypes.func
}; };
state = { state = {
email: this.props.email, email: this.props.email,
level: this.props.level - 1, level: this.props.level - 1,
message: null,
departments: this.getUserDepartments() departments: this.getUserDepartments()
}; };
render() { render() {
return ( return (
<div className="staff-editor"> <div className="staff-editor">
{(this.state.message) ? this.renderMessage() : null}
<div className="row"> <div className="row">
<div className="col-md-4"> <div className="col-md-4">
<div className="staff-editor__card"> <div className="staff-editor__card">
@ -67,12 +73,12 @@ class StaffEditor extends React.Component {
</div> </div>
<div className="col-md-8"> <div className="col-md-8">
<div className="staff-editor__form"> <div className="staff-editor__form">
<Form className="staff-editor__update-email" values={{email: this.state.email}} onChange={form => this.setState({email: form.email})} onSubmit={this.onSubmit.bind(this)}> <Form className="staff-editor__update-email" values={{email: this.state.email}} onChange={form => this.setState({email: form.email})} onSubmit={this.onSubmit.bind(this, 'EMAIL')}>
<FormField name="email" validation="EMAIL" required label={i18n('EMAIL')} fieldProps={{size: 'large'}}/> <FormField name="email" validation="EMAIL" required label={i18n('EMAIL')} fieldProps={{size: 'large'}}/>
<SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_EMAIL')}</SubmitButton> <SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_EMAIL')}</SubmitButton>
</Form> </Form>
<span className="separator staff-editor__separator" /> <span className="separator staff-editor__separator" />
<Form className="staff-editor__update-password" onSubmit={this.onSubmit.bind(this)}> <Form className="staff-editor__update-password" onSubmit={this.onSubmit.bind(this, 'PASSWORD')}>
<FormField name="password" validation="PASSWORD" required label={i18n('PASSWORD')} fieldProps={{size: 'large', password: true}}/> <FormField name="password" validation="PASSWORD" required label={i18n('PASSWORD')} fieldProps={{size: 'large', password: true}}/>
<FormField name="rpassword" validation="REPEAT_PASSWORD" required label={i18n('REPEAT_PASSWORD')} fieldProps={{size: 'large', password: true}}/> <FormField name="rpassword" validation="REPEAT_PASSWORD" required label={i18n('REPEAT_PASSWORD')} fieldProps={{size: 'large', password: true}}/>
<SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_PASSWORD')}</SubmitButton> <SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_PASSWORD')}</SubmitButton>
@ -96,15 +102,41 @@ class StaffEditor extends React.Component {
</div> </div>
</div> </div>
{(this.props.tickets) ? this.renderTickets() : null} {(this.props.tickets) ? this.renderTickets() : null}
{(!this.props.myAccount) ? this.renderDelete() : null}
</div> </div>
); );
} }
renderMessage() {
let messageType = (this.state.message === 'FAIL') ? 'error' : 'success';
let message = null;
switch (this.state.message) {
case 'EMAIL':
message = 'EMAIL_CHANGED';
break;
case 'PASSWORD':
message = 'PASSWORD_CHANGED';
break;
case 'LEVEL':
message = 'LEVEL_UPDATED';
break;
case 'DEPARTMENTS':
message = 'DEPARTMENTS_UPDATED';
break;
case 'FAIL':
message = 'FAILED_EDIT_STAFF';
break;
}
return <Message className="staff-editor__message" type={messageType}>{i18n(message)}</Message>;
}
renderLevelForm() { renderLevelForm() {
return ( return (
<div> <div>
<span className="separator staff-editor__separator"/> <span className="separator staff-editor__separator"/>
<Form className="staff-editor__update-level" values={{level: this.state.level}} onChange={form => this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this)}> <Form className="staff-editor__update-level" values={{level: this.state.level}} onChange={form => this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this, 'LEVEL')}>
<FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{ <FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{
items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}], items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}],
size: 'large' size: 'large'
@ -117,7 +149,7 @@ class StaffEditor extends React.Component {
renderDepartmentsForm() { renderDepartmentsForm() {
return ( return (
<Form values={{departments: this.state.departments}} onChange={form => this.setState({departments: form.departments})} onSubmit={this.onSubmit.bind(this)}> <Form values={{departments: this.state.departments}} onChange={form => this.setState({departments: form.departments})} onSubmit={this.onSubmit.bind(this, 'DEPARTMENTS')}>
<FormField name="departments" field="checkbox-group" fieldProps={{items: this.getDepartments()}} /> <FormField name="departments" field="checkbox-group" fieldProps={{items: this.getDepartments()}} />
<SubmitButton size="medium">{i18n('UPDATE_DEPARTMENTS')}</SubmitButton> <SubmitButton size="medium">{i18n('UPDATE_DEPARTMENTS')}</SubmitButton>
</Form> </Form>
@ -144,6 +176,22 @@ class StaffEditor extends React.Component {
</div> </div>
); );
} }
renderDelete() {
return (
<div>
<span className="separator"/>
<div className="staff-editor__delete">
<div className="staff-editor__delete-title">
{i18n('DELETE_STAFF_MEMBER')}
</div>
<Button onClick={AreYouSure.openModal.bind(this, i18n('WILL_DELETE_STAFF'), this.onDeleteClick.bind(this))}>
{i18n('DELETE_STAFF_MEMBER')}
</Button>
</div>
</div>
);
}
getTicketListProps() { getTicketListProps() {
return { return {
@ -171,7 +219,7 @@ class StaffEditor extends React.Component {
return SessionStore.getDepartments().map(department => department.name); return SessionStore.getDepartments().map(department => department.name);
} }
onSubmit(form) { onSubmit(eventType, form) {
let departments; let departments;
if(form.departments) { if(form.departments) {
@ -189,7 +237,29 @@ class StaffEditor extends React.Component {
level: (form.level !== undefined) ? form.level + 1 : null, level: (form.level !== undefined) ? form.level + 1 : null,
departments: departments && JSON.stringify(departments) departments: departments && JSON.stringify(departments)
} }
}).then(this.props.onChange); }).then(() => {
window.scrollTo(0,0);
this.setState({message: eventType});
if(this.props.onChange) {
this.props.onChange();
}
}).catch(() => {
window.scrollTo(0,0);
this.setState({message: 'FAIL'});
});
}
onDeleteClick() {
API.call({
path: '/staff/delete',
data: {
staffId: this.props.staffId
}
}).then(this.props.onDelete).catch(() => {
window.scrollTo(0,0);
this.setState({message: 'FAIL'});
});
} }
} }

View File

@ -122,4 +122,21 @@
&__separator { &__separator {
margin: 3px 0; margin: 3px 0;
} }
&__message {
margin-bottom: 20px;
}
&__delete {
border: 1px solid $grey;
padding: 20px 50px;
text-align: right;
}
&__delete-title {
font-size: $font-size--md;
text-align: center;
float: left;
margin-top: 11px;
}
} }

View File

@ -4,9 +4,11 @@ import {connect} from 'react-redux';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions'; import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list'; import TicketList from 'app-components/ticket-list';
import Header from 'core-components/header';
import SearchBox from 'core-components/search-box'; import SearchBox from 'core-components/search-box';
import Message from 'core-components/message';
class AdminPanelAllTickets extends React.Component { class AdminPanelAllTickets extends React.Component {
@ -31,7 +33,7 @@ class AdminPanelAllTickets extends React.Component {
<div className="admin-panel-my-tickets__search-box"> <div className="admin-panel-my-tickets__search-box">
<SearchBox onSearch={this.onSearch.bind(this)} /> <SearchBox onSearch={this.onSearch.bind(this)} />
</div> </div>
<TicketList {...this.getTicketListProps()}/> {(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getTicketListProps()}/>}
</div> </div>
); );
} }
@ -52,7 +54,8 @@ class AdminPanelAllTickets extends React.Component {
onSearch(query) { onSearch(query) {
this.setState({query, page: 1}); this.setState({query, page: 1});
if (query) {
if(query) {
this.props.dispatch(AdminDataAction.searchTickets(query)); this.props.dispatch(AdminDataAction.searchTickets(query));
} else { } else {
this.props.dispatch(AdminDataAction.retrieveAllTickets()); this.props.dispatch(AdminDataAction.retrieveAllTickets());
@ -60,9 +63,7 @@ class AdminPanelAllTickets extends React.Component {
} }
onPageChange(event) { onPageChange(event) {
this.setState({ this.setState({page: event.target.value});
page: event.target.value
});
if(this.state.query) { if(this.state.query) {
this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value)); this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value));
@ -77,6 +78,7 @@ export default connect((store) => {
departments: store.session.userDepartments, departments: store.session.userDepartments,
tickets: store.adminData.allTickets, tickets: store.adminData.allTickets,
pages: store.adminData.allTicketsPages, pages: store.adminData.allTicketsPages,
loading: !store.adminData.allTicketsLoaded loading: !store.adminData.allTicketsLoaded,
error: store.adminData.allTicketsError
}; };
})(AdminPanelAllTickets); })(AdminPanelAllTickets);

View File

@ -4,9 +4,11 @@ import {connect} from 'react-redux';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions'; import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list'; import TicketList from 'app-components/ticket-list';
import Header from 'core-components/header';
import Message from 'core-components/message';
class AdminPanelMyTickets extends React.Component { class AdminPanelMyTickets extends React.Component {
static defaultProps = { static defaultProps = {
@ -22,7 +24,7 @@ class AdminPanelMyTickets extends React.Component {
return ( return (
<div className="admin-panel-my-tickets"> <div className="admin-panel-my-tickets">
<Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} /> <Header title={i18n('MY_TICKETS')} description={i18n('MY_TICKETS_DESCRIPTION')} />
<TicketList {...this.getProps()}/> {(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()}/>}
</div> </div>
); );
} }
@ -42,6 +44,7 @@ export default connect((store) => {
return { return {
departments: store.session.userDepartments, departments: store.session.userDepartments,
tickets: store.adminData.myTickets, tickets: store.adminData.myTickets,
loading: !store.adminData.myTicketsLoaded loading: !store.adminData.myTicketsLoaded,
error: store.adminData.myTicketsError
}; };
})(AdminPanelMyTickets); })(AdminPanelMyTickets);

View File

@ -4,9 +4,11 @@ import {connect} from 'react-redux';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import AdminDataAction from 'actions/admin-data-actions'; import AdminDataAction from 'actions/admin-data-actions';
import Header from 'core-components/header';
import TicketList from 'app-components/ticket-list'; import TicketList from 'app-components/ticket-list';
import Header from 'core-components/header';
import Message from 'core-components/message';
class AdminPanelNewTickets extends React.Component { class AdminPanelNewTickets extends React.Component {
static defaultProps = { static defaultProps = {
@ -22,7 +24,7 @@ class AdminPanelNewTickets extends React.Component {
return ( return (
<div className="admin-panel-my-tickets"> <div className="admin-panel-my-tickets">
<Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} /> <Header title={i18n('NEW_TICKETS')} description={i18n('NEW_TICKETS_DESCRIPTION')} />
<TicketList {...this.getProps()}/> {(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getProps()}/>}
</div> </div>
); );
} }
@ -42,6 +44,7 @@ export default connect((store) => {
return { return {
departments: store.session.userDepartments, departments: store.session.userDepartments,
tickets: store.adminData.newTickets, tickets: store.adminData.newTickets,
loading: !store.adminData.newTicketsLoaded loading: !store.adminData.newTicketsLoaded,
error: store.adminData.newTicketsError
}; };
})(AdminPanelNewTickets); })(AdminPanelNewTickets);

View File

@ -11,24 +11,35 @@ import Button from 'core-components/button';
import SubmitButton from 'core-components/submit-button'; import SubmitButton from 'core-components/submit-button';
import Form from 'core-components/form'; import Form from 'core-components/form';
import FormField from 'core-components/form-field'; import FormField from 'core-components/form-field';
import Message from 'core-components/message';
class AdminPanelBanUsers extends React.Component { class AdminPanelBanUsers extends React.Component {
state = { state = {
loadingList: true, loadingList: true,
loadingForm: false, loadingForm: false,
listError: false,
addBanStatus: 'none',
emails: [], emails: [],
filteredEmails: [] filteredEmails: []
}; };
componentDidMount() { componentDidMount() {
this.retrieveEmails() this.retrieveEmails();
} }
render() { render() {
return ( return (
<div className="admin-panel-ban-users row"> <div className="admin-panel-ban-users row">
<Header title={i18n('BAN_USERS')} description={i18n('BAN_USERS_DESCRIPTION')} /> <Header title={i18n('BAN_USERS')} description={i18n('BAN_USERS_DESCRIPTION')} />
{(this.state.listError) ? <Message type="error">{i18n('ERROR_RETRIEVING_BAN_LIST')}</Message> : this.renderContent()}
</div>
);
}
renderContent() {
return (
<div>
<div className="admin-panel-ban-users__email-list col-md-6"> <div className="admin-panel-ban-users__email-list col-md-6">
<SearchBox className="admin-panel-ban-users__search" onSearch={this.onSearch.bind(this)} searchOnType placeholder={i18n('SEARCH_EMAIL')}/> <SearchBox className="admin-panel-ban-users__search" onSearch={this.onSearch.bind(this)} searchOnType placeholder={i18n('SEARCH_EMAIL')}/>
<Table {...this.getTableProps()} /> <Table {...this.getTableProps()} />
@ -41,11 +52,23 @@ class AdminPanelBanUsers extends React.Component {
<FormField className="admin-panel-ban-users__input" placeholder="email" name="email" validation="EMAIL" required fieldProps={{size: 'large'}}/> <FormField className="admin-panel-ban-users__input" placeholder="email" name="email" validation="EMAIL" required fieldProps={{size: 'large'}}/>
<SubmitButton>{i18n('BAN_EMAIL')}</SubmitButton> <SubmitButton>{i18n('BAN_EMAIL')}</SubmitButton>
</Form> </Form>
{this.renderMessage()}
</div> </div>
</div> </div>
); );
} }
renderMessage() {
switch (this.state.addBanStatus) {
case 'success':
return <Message className="admin-panel-ban-users__form-message" type="success">{i18n('EMAIL_BANNED_SUCCESSFULLY')}</Message>;
case 'fail':
return <Message className="admin-panel-ban-users__form-message" type="error">{i18n('ERROR_BANNING_EMAIL')}</Message>;
default:
return null;
}
}
getTableProps() { getTableProps() {
return { return {
loading: this.state.loadingList, loading: this.state.loadingList,
@ -94,7 +117,12 @@ class AdminPanelBanUsers extends React.Component {
data: { data: {
email: form.email email: form.email
} }
}).then(this.retrieveEmails.bind(this)); }).then(() => {
this.setState({
addBanStatus: 'success'
});
this.retrieveEmails();
}).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false}));
} }
onUnBanClick(email) { onUnBanClick(email) {
@ -114,14 +142,17 @@ class AdminPanelBanUsers extends React.Component {
API.call({ API.call({
path: '/user/list-ban', path: '/user/list-ban',
data: {} data: {}
}).then((result) => { }).then(result => this.setState({
this.setState({ listError: false,
loadingList: false, loadingList: false,
loadingForm: false, loadingForm: false,
emails: result.data, emails: result.data,
filteredEmails: result.data filteredEmails: result.data
}); })).catch(() => this.setState({
}); listError: true,
loadingList: false,
loadingForm: false
}));
} }
} }

View File

@ -3,10 +3,6 @@
.admin-panel-ban-users { .admin-panel-ban-users {
padding: 0 20px; padding: 0 20px;
&__email-list {
}
&__search { &__search {
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -33,4 +29,8 @@
&__input { &__input {
display: inline-block; display: inline-block;
} }
&__form-message {
margin-top: 20px;
}
} }

View File

@ -8,6 +8,7 @@ import Header from 'core-components/header';
import Table from 'core-components/table'; import Table from 'core-components/table';
import SearchBox from 'core-components/search-box'; import SearchBox from 'core-components/search-box';
import Button from 'core-components/button'; import Button from 'core-components/button';
import Message from 'core-components/message';
class AdminPanelListUsers extends React.Component { class AdminPanelListUsers extends React.Component {
@ -16,6 +17,7 @@ class AdminPanelListUsers extends React.Component {
users: [], users: [],
orderBy: 'id', orderBy: 'id',
desc: true, desc: true,
error: false,
page: 1, page: 1,
pages: 1 pages: 1
}; };
@ -34,7 +36,7 @@ class AdminPanelListUsers extends React.Component {
<div className="admin-panel-list-users"> <div className="admin-panel-list-users">
<Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} /> <Header title={i18n('LIST_USERS')} description={i18n('LIST_USERS_DESCRIPTION')} />
<SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} /> <SearchBox className="admin-panel-list-users__search-box" placeholder={i18n('SEARCH_USERS')} onSearch={this.onSearch.bind(this)} />
<Table {...this.getTableProps()} /> {(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : <Table {...this.getTableProps()}/>}
</div> </div>
); );
} }
@ -144,7 +146,7 @@ class AdminPanelListUsers extends React.Component {
API.call({ API.call({
path: '/user/get-users', path: '/user/get-users',
data: data data: data
}).then(this.onUsersRetrieved.bind(this)); }).then(this.onUsersRetrieved.bind(this)).catch(this.onUsersRejected.bind(this));
} }
onUsersRetrieved(result) { onUsersRetrieved(result) {
@ -154,6 +156,14 @@ class AdminPanelListUsers extends React.Component {
users: result.data.users, users: result.data.users,
orderBy: result.data.orderBy, orderBy: result.data.orderBy,
desc: (result.data.desc === '1'), desc: (result.data.desc === '1'),
error: false,
loading: false
});
}
onUsersRejected() {
this.setState({
error: true,
loading: false loading: false
}); });
} }

View File

@ -5,11 +5,13 @@ import {browserHistory} from 'react-router';
import i18n from 'lib-app/i18n'; import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call'; import API from 'lib-app/api-call';
import Header from 'core-components/header';
import Button from 'core-components/button';
import TicketList from 'app-components/ticket-list'; import TicketList from 'app-components/ticket-list';
import AreYouSure from 'app-components/are-you-sure'; import AreYouSure from 'app-components/are-you-sure';
import Header from 'core-components/header';
import Button from 'core-components/button';
import Message from 'core-components/message';
class AdminPanelViewUser extends React.Component { class AdminPanelViewUser extends React.Component {
state = { state = {
@ -43,7 +45,7 @@ class AdminPanelViewUser extends React.Component {
renderInvalid() { renderInvalid() {
return ( return (
<div className="admin-panel-view-user__invalid"> <div className="admin-panel-view-user__invalid">
{i18n('INVALID_USER')} <Message type="error">{i18n('INVALID_USER')}</Message>
</div> </div>
); );
} }

View File

@ -23,7 +23,7 @@ class DashboardListArticlesPage extends React.Component {
render() { render() {
return ( return (
<div classnames="dashboard-list-articles-page"> <div className="dashboard-list-articles-page">
<Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/> <Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/>
<SearchBox className="dashboard-list-articles-page__search-box" onSearch={this.onSearch.bind(this)} searchOnType /> <SearchBox className="dashboard-list-articles-page__search-box" onSearch={this.onSearch.bind(this)} searchOnType />
{(!this.state.showSearchResults) ? this.renderArticleList() : this.renderSearchResults()} {(!this.state.showSearchResults) ? this.renderArticleList() : this.renderSearchResults()}

View File

@ -19,7 +19,7 @@ class Icon extends React.Component {
renderFontIcon() { renderFontIcon() {
return ( return (
<span className={this.getFontIconClass()} aria-hidden="true" style={{color: this.props.color}}/> <span onClick={this.props.onClick} className={this.getFontIconClass()} aria-hidden="true" style={{color: this.props.color}}/>
); );
} }

View File

@ -29,7 +29,7 @@ class Message extends React.Component {
getAnimationProps() { getAnimationProps() {
return { return {
defaultStyle: { defaultStyle: {
opacity: spring(0, [100, 30]) opacity: 0
}, },
style: { style: {
opacity: spring(1, [100, 30]) opacity: spring(1, [100, 30])

View File

@ -28,7 +28,7 @@ class Tooltip extends React.Component {
renderAnimatedMessage() { renderAnimatedMessage() {
return ( return (
<Motion defaultStyle={{opacity: 0}} style={{opacity: spring(1)}}> <Motion defaultStyle={{opacity: 0, top: -30}} style={{opacity: spring(1), top: spring(0)}}>
{this.renderMessage.bind(this)} {this.renderMessage.bind(this)}
</Motion> </Motion>
) )
@ -36,7 +36,7 @@ class Tooltip extends React.Component {
renderMessage(animation) { renderMessage(animation) {
return ( return (
<div style={animation}> <div className="tooltip__animated-container" style={animation}>
<span className="tooltip__pointer-shadow"/> <span className="tooltip__pointer-shadow"/>
<span className="tooltip__pointer"/> <span className="tooltip__pointer"/>
<div className="tooltip__message"> <div className="tooltip__message">

View File

@ -8,12 +8,15 @@
cursor: default; cursor: default;
} }
&__animated-container {
position: relative;
}
&__message { &__message {
position: absolute; position: absolute;
bottom: 100%; bottom: 100%;
left: -25%;
margin-bottom: 15px; margin-bottom: 15px;
margin-left: -10px; margin-left: -15px;
border: 0 solid rgba(0, 0, 0, 0.247059); border: 0 solid rgba(0, 0, 0, 0.247059);
box-shadow: rgba(0, 0, 0, 0.247059) 0 -1px 4px; box-shadow: rgba(0, 0, 0, 0.247059) 0 -1px 4px;
border-radius: 4px; border-radius: 4px;

View File

@ -1047,5 +1047,15 @@ module.exports = [
data: {} data: {}
}; };
} }
},
{
path: '/staff/delete',
time: 100,
response: function () {
return {
status: 'success',
data: {}
};
}
} }
]; ];

View File

@ -147,7 +147,7 @@ module.exports = [
email: 'kurt@currycurrylady.hs', email: 'kurt@currycurrylady.hs',
tickets: _.times(13).map(() => { tickets: _.times(13).map(() => {
return { return {
ticketNumber: '1185510000', ticketNumber: '118551',
title: 'Lorem ipsum door', title: 'Lorem ipsum door',
content: 'I had a problem with the installation of the php server', content: 'I had a problem with the installation of the php server',
department: { department: {
@ -368,7 +368,7 @@ module.exports = [
{ {
ticketNumber: '445441', ticketNumber: '445441',
title: 'Problem with installation', title: 'Problem with installation',
content: 'I had a problem with the installation of the php server', content: 'In varius, tellus ut luctus vestibulum, orci erat commodo ligula, sit amet bibendum arcu libero sed magna. Suspendisse in ligula vitae ante placerat varius id in eros. Etiam commodo viverra nisi in ornare. Donec ullamcorper felis sapien, eu laoreet dolor tincidunt nec. Aliquam erat volutpat. Proin semper viverra purus eget facilisis. Proin fermentum et odio in elementum. Maecenas lacinia, massa consectetur gravida lacinia, nisl lectus tincidunt diam, ut viverra ipsum ex sit amet diam. Mauris ac massa turpis. Fusce ultrices venenatis vestibulum. In et nulla purus. Nullam porta vestibulum leo in dignissim. Duis id ullamcorper odio. Ut purus nulla, consequat lobortis volutpat quis, consequat et libero. Maecenas sit amet libero laoreet, dictum sapien at, hendrerit sapien.',
department: { department: {
id: 2, id: 2,
name: 'Technical Issues' name: 'Technical Issues'
@ -502,9 +502,7 @@ module.exports = [
name: 'Haskell Curry', name: 'Haskell Curry',
email: 'haskell@lambda.com' email: 'haskell@lambda.com'
}, },
owner: { owner: null,
name: 'Steve Jobs'
},
events: [ events: [
{ {
type: 'ASSIGN', type: 'ASSIGN',
@ -516,17 +514,6 @@ module.exports = [
staff: true staff: true
} }
}, },
{
type: 'COMMENT',
date: '201504090912',
content: 'Do you have apache installed? It generally happens if you dont have apache.',
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
staff: true
}
},
{ {
type: 'UN_ASSIGN', type: 'UN_ASSIGN',
date: '201504100924', date: '201504100924',

View File

@ -30,6 +30,7 @@ export default {
'STAFF': 'Staff', 'STAFF': 'Staff',
'CUSTOMER': 'Customer', 'CUSTOMER': 'Customer',
'YES': 'Yes', 'YES': 'Yes',
'NO': 'No',
'CANCEL': 'Cancel', 'CANCEL': 'Cancel',
'MY_ACCOUNT': 'My Account', 'MY_ACCOUNT': 'My Account',
'DASHBOARD': 'Dashboard', 'DASHBOARD': 'Dashboard',
@ -114,6 +115,8 @@ export default {
'ADD_DEPARTMENT': 'Add department', 'ADD_DEPARTMENT': 'Add department',
'UPDATE_DEPARTMENT': 'Update department', 'UPDATE_DEPARTMENT': 'Update department',
'TRANSFER_TICKETS_TO': 'Transfer tickets to', 'TRANSFER_TICKETS_TO': 'Transfer tickets to',
'COMMENTS': 'Comments',
'DELETE_STAFF_MEMBER': 'Delete staff member',
//VIEW DESCRIPTIONS //VIEW DESCRIPTIONS
'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.', 'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.',
@ -136,6 +139,7 @@ export default {
'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.', 'ADD_ARTICLE_DESCRIPTION': 'Here you can add an article that will be available for every user. It will be added inside the category {category}.',
'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.', 'LIST_ARTICLES_DESCRIPTION': 'This is a list of articles that includes information about our services.',
'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.', 'ADD_TOPIC_DESCRIPTION': 'Here you can add a topic that works as a category for articles.',
'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article for ever.',
'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.', 'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.',
'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.', 'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.',
'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.', 'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.',
@ -154,8 +158,14 @@ export default {
'PASSWORD_NOT_MATCH': 'Password does not match', 'PASSWORD_NOT_MATCH': 'Password does not match',
'INVALID_RECOVER': 'Invalid recover data', 'INVALID_RECOVER': 'Invalid recover data',
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.', 'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
'TICKET_COMMENT_ERROR': 'An error occurred while trying to add the comment.',
'NO_PERMISSION': 'You\'ve no permission to access to this page.', 'NO_PERMISSION': 'You\'ve no permission to access to this page.',
'INVALID_USER': 'User id is invalid', 'INVALID_USER': 'User id is invalid',
'ERROR_RETRIEVING_TICKETS': 'An error occurred while trying to retrieve tickets.',
'ERROR_RETRIEVING_USERS': 'An error occurred while trying to retrieve users.',
'ERROR_RETRIEVING_BAN_LIST': 'An error occurred while trying to retrieve the list of banned emails.',
'ERROR_BANNING_EMAIL': 'An error occurred while trying to ban the email.',
'ERROR_RETRIEVING_ARTICLES': 'An error occurred while trying to retrieve articles.',
//MESSAGES //MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
@ -169,5 +179,10 @@ export default {
'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.', 'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.',
'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.', 'WILL_DELETE_CUSTOM_RESPONSE': 'The custom response will be deleted.',
'WILL_DELETE_DEPARTMENT': 'The department will be deleted. All the tickets will be transfer to the department selected.', 'WILL_DELETE_DEPARTMENT': 'The department will be deleted. All the tickets will be transfer to the department selected.',
'NO_STAFF_ASSIGNED': 'No staff member is assigned to this department.' 'NO_STAFF_ASSIGNED': 'No staff member is assigned to this department.',
'LEVEL_UPDATED': 'Level has been updated successfully.',
'DEPARTMENTS_UPDATED': 'Departments have been updated successfully.',
'FAILED_EDIT_STAFF': 'An error occurred while trying to edit staff member.',
'EMAIL_BANNED_SUCCESSFULLY': 'Email has been banned successfully',
'WILL_DELETE_STAFF': 'This staff member will be deleted and all its tickets will be unassigned.'
}; };

View File

@ -11,10 +11,15 @@ class AdminDataReducer extends Reducer {
customResponsesLoaded: false, customResponsesLoaded: false,
myTickets: [], myTickets: [],
myTicketsLoaded: false, myTicketsLoaded: false,
myTicketsError: false,
newTickets: [], newTickets: [],
newTicketsLoaded: false, newTicketsLoaded: false,
newTicketsError: false,
allTickets: [], allTickets: [],
allTicketsLoaded: false allTicketsLoaded: false,
allTicketsError: false
}; };
} }
@ -22,11 +27,17 @@ class AdminDataReducer extends Reducer {
return { return {
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses, 'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
'SESSION_CHECKED': this.onSessionChecked, 'SESSION_CHECKED': this.onSessionChecked,
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved, 'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
'MY_TICKETS_REJECTED': this.onMyTicketsRejected,
'MY_TICKETS_PENDING': this.onMyTicketsPending, 'MY_TICKETS_PENDING': this.onMyTicketsPending,
'NEW_TICKETS_FULFILLED': this.onNewTicketsRetrieved, 'NEW_TICKETS_FULFILLED': this.onNewTicketsRetrieved,
'NEW_TICKETS_REJECTED': this.onNewTicketsRejected,
'NEW_TICKETS_PENDING': this.onNewTicketsPending, 'NEW_TICKETS_PENDING': this.onNewTicketsPending,
'ALL_TICKETS_FULFILLED': this.onAllTicketsRetrieved, 'ALL_TICKETS_FULFILLED': this.onAllTicketsRetrieved,
'ALL_TICKETS_REJECTED': this.onAllTicketsRejected,
'ALL_TICKETS_PENDING': this.onAllTicketsPending 'ALL_TICKETS_PENDING': this.onAllTicketsPending
}; };
} }
@ -53,26 +64,42 @@ class AdminDataReducer extends Reducer {
return _.extend({}, state, { return _.extend({}, state, {
myTickets: payload.data, myTickets: payload.data,
myTicketsLoaded: true myTicketsLoaded: true
});
}
onMyTicketsRejected(state) {
return _.extend({}, state, {
myTicketsError: true,
myTicketsLoaded: true
}) })
} }
onMyTicketsPending(state) { onMyTicketsPending(state) {
return _.extend({}, state, { return _.extend({}, state, {
myTicketsError: false,
myTicketsLoaded: false myTicketsLoaded: false
}) });
} }
onNewTicketsRetrieved(state, payload) { onNewTicketsRetrieved(state, payload) {
return _.extend({}, state, { return _.extend({}, state, {
newTickets: payload.data, newTickets: payload.data,
newTicketsLoaded: true newTicketsLoaded: true
}) });
}
onNewTicketsRejected(state) {
return _.extend({}, state, {
newTicketsError: true,
newTicketsLoaded: false
});
} }
onNewTicketsPending(state) { onNewTicketsPending(state) {
return _.extend({}, state, { return _.extend({}, state, {
newTicketsError: false,
newTicketsLoaded: false newTicketsLoaded: false
}) });
} }
onAllTicketsRetrieved(state, payload) { onAllTicketsRetrieved(state, payload) {
@ -80,13 +107,21 @@ class AdminDataReducer extends Reducer {
allTickets: payload.data.tickets, allTickets: payload.data.tickets,
allTicketsPages: payload.data.pages, allTicketsPages: payload.data.pages,
allTicketsLoaded: true allTicketsLoaded: true
}) });
}
onAllTicketsRejected(state) {
return _.extend({}, state, {
allTicketsError: false,
allTicketsLoaded: false
});
} }
onAllTicketsPending(state) { onAllTicketsPending(state) {
return _.extend({}, state, { return _.extend({}, state, {
allTicketsError: false,
allTicketsLoaded: false allTicketsLoaded: false
}) });
} }
} }

View File

@ -9,6 +9,7 @@ class ArticlesReducer extends Reducer {
return { return {
retrieved: false, retrieved: false,
loading: true, loading: true,
errored: false,
topics: [] topics: []
}; };
} }
@ -16,6 +17,7 @@ class ArticlesReducer extends Reducer {
getTypeHandlers() { getTypeHandlers() {
return { return {
'GET_ARTICLES_FULFILLED': this.onArticlesRetrieved, 'GET_ARTICLES_FULFILLED': this.onArticlesRetrieved,
'GET_ARTICLES_REJECTED': this.onArticlesRejected,
'INIT_ARTICLES': this.onInitArticles 'INIT_ARTICLES': this.onInitArticles
}; };
} }
@ -26,10 +28,19 @@ class ArticlesReducer extends Reducer {
return _.extend({}, state, { return _.extend({}, state, {
retrieved: true, retrieved: true,
loading: false, loading: false,
errored: false,
topics: payload.data topics: payload.data
}); });
} }
onArticlesRejected(state) {
return _.extend({}, state, {
retrieved: true,
loading: false,
errored: true
});
}
onInitArticles(state) { onInitArticles(state) {
let topics = SessionStore.getItem('topics'); let topics = SessionStore.getItem('topics');

View File

@ -1,10 +1,10 @@
{ {
"require": { "require": {
"slim/slim": "~2.0", "slim/slim": "~2.0",
"gabordemooij/redbean": "~4.2",
"respect/validation": "^1.1", "respect/validation": "^1.1",
"phpmailer/phpmailer": "^5.2", "phpmailer/phpmailer": "^5.2",
"google/recaptcha": "~1.1" "google/recaptcha": "~1.1",
"gabordemooij/redbean": "^4.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "5.0.*" "phpunit/phpunit": "5.0.*"

View File

@ -23,6 +23,11 @@ class DeleteStaffController extends Controller {
$staffId = Controller::request('staffId'); $staffId = Controller::request('staffId');
$staff = Staff::getDataStore($staffId); $staff = Staff::getDataStore($staffId);
if($staffId === Controller::getLoggedUser()->id) {
Response::respondError(ERRORS::INVALID_STAFF);
return;
}
foreach($staff->sharedTicketList as $ticket) { foreach($staff->sharedTicketList as $ticket) {
$ticket->owner = null; $ticket->owner = null;
$ticket->true = true; $ticket->true = true;

View File

@ -4,8 +4,7 @@ use Respect\Validation\Validator as DataValidator;
class EditStaffController extends Controller { class EditStaffController extends Controller {
const PATH = '/edit'; const PATH = '/edit';
private $staffRow; private $staffInstance;
private $staffId;
public function validations() { public function validations() {
return [ return [
@ -15,14 +14,14 @@ class EditStaffController extends Controller {
} }
public function handler() { public function handler() {
$this->staffId = Controller::request('staffId'); $staffId = Controller::request('staffId');
if(!$this->staffId) { if(!$staffId) {
$this->staffRow = Controller::getLoggedUser(); $this->staffInstance = Controller::getLoggedUser();
} else if(Controller::isStaffLogged(3)) { } else if(Controller::isStaffLogged(3)) {
$this->staffRow = Staff::getDataStore($this->staffId, 'id'); $this->staffInstance = Staff::getDataStore($staffId, 'id');
if($this->staffRow->isNull()) { if($this->staffInstance->isNull()) {
Response::respondError(ERRORS::INVALID_STAFF); Response::respondError(ERRORS::INVALID_STAFF);
return; return;
} }
@ -39,29 +38,29 @@ class EditStaffController extends Controller {
Response::respondSuccess(); Response::respondSuccess();
} }
public function editInformation() { private function editInformation() {
if(Controller::request('email')) { if(Controller::request('email')) {
$this->staffRow->email = Controller::request('email'); $this->staffInstance->email = Controller::request('email');
} }
if(Controller::request('password')) { if(Controller::request('password')) {
$this->staffRow->password = Hashing::hashPassword(Controller::request('password')); $this->staffInstance->password = Hashing::hashPassword(Controller::request('password'));
} }
if(Controller::request('level') && Controller::isStaffLogged(3)) { if(Controller::request('level') && Controller::isStaffLogged(3) && Controller::request('staffId') !== Controller::getLoggedUser()->id) {
$this->staffRow->level = Controller::request('level'); $this->staffInstance->level = Controller::request('level');
} }
if(Controller::request('departments') && Controller::isStaffLogged(3)) { if(Controller::request('departments') && Controller::isStaffLogged(3)) {
$this->staffRow->sharedDepartmentList = $this->getDepartmentList(); $this->staffInstance->sharedDepartmentList = $this->getDepartmentList();
} }
$this->staffRow->store(); $this->staffInstance->store();
} }
public function getDepartmentList() { private function getDepartmentList() {
$listDepartments = new DataStoreList(); $listDepartments = new DataStoreList();
$departmentIds = json_decode(Controller::request('departments')); $departmentIds = json_decode(Controller::request('departments'));
@ -73,8 +72,8 @@ class EditStaffController extends Controller {
return $listDepartments; return $listDepartments;
} }
public function updateDepartmentsOwners() { private function updateDepartmentsOwners() {
$list1 = $this->staffRow->sharedDepartmentList; $list1 = $this->staffInstance->sharedDepartmentList;
$list2 = $this->getDepartmentList(); $list2 = $this->getDepartmentList();
foreach ($list1 as $department1) { foreach ($list1 as $department1) {

View File

@ -13,7 +13,7 @@ class CommentController extends Controller {
'permission' => 'user', 'permission' => 'user',
'requestData' => [ 'requestData' => [
'content' => [ 'content' => [
'validation' => DataValidator::length(20, 500), 'validation' => DataValidator::length(20, 5000),
'error' => ERRORS::INVALID_CONTENT 'error' => ERRORS::INVALID_CONTENT
], ],
'ticketNumber' => [ 'ticketNumber' => [

View File

@ -20,7 +20,7 @@ class CreateController extends Controller {
'error' => ERRORS::INVALID_TITLE 'error' => ERRORS::INVALID_TITLE
], ],
'content' => [ 'content' => [
'validation' => DataValidator::length(10, 500), 'validation' => DataValidator::length(10, 5000),
'error' => ERRORS::INVALID_CONTENT 'error' => ERRORS::INVALID_CONTENT
], ],
'departmentId' => [ 'departmentId' => [

View File

@ -14,6 +14,7 @@ include 'user/delete.php';
include 'user/ban.php'; include 'user/ban.php';
include 'user/un-ban.php'; include 'user/un-ban.php';
include 'user/list-ban.php'; include 'user/list-ban.php';
include 'user/verify.php';
$userControllers = new ControllerGroup(); $userControllers = new ControllerGroup();
$userControllers->setGroupPath('/user'); $userControllers->setGroupPath('/user');
@ -33,4 +34,5 @@ $userControllers->addController(new DeleteUserController);
$userControllers->addController(new BanUserController); $userControllers->addController(new BanUserController);
$userControllers->addController(new UnBanUserController); $userControllers->addController(new UnBanUserController);
$userControllers->addController(new ListBanUserController); $userControllers->addController(new ListBanUserController);
$userControllers->addController(new VerifyController);
$userControllers->finalize(); $userControllers->finalize();

View File

@ -34,7 +34,8 @@ class GetUserByIdController extends Controller {
'name' => $user->name, 'name' => $user->name,
'email' => $user->email, 'email' => $user->email,
'signupDate' => $user->signupDate, 'signupDate' => $user->signupDate,
'tickets' => $tickets->toArray() 'tickets' => $tickets->toArray(),
'verified' => !$user->verificationToken
]); ]);
} }
} }

View File

@ -27,6 +27,14 @@ class LoginController extends Controller {
$this->userInstance->store(); $this->userInstance->store();
} }
$email = Controller::request('email');
$userRow = User::getDataStore($email, 'email');
if($userRow->verificationToken !== null) {
Response::respondError(ERRORS::UNVERIFIED_USER);
return;
}
Response::respondSuccess($this->getUserData()); Response::respondSuccess($this->getUserData());
} else { } else {
Response::respondError(ERRORS::INVALID_CREDENTIALS); Response::respondError(ERRORS::INVALID_CREDENTIALS);

View File

@ -9,6 +9,7 @@ class SignUpController extends Controller {
private $userEmail; private $userEmail;
private $userName; private $userName;
private $userPassword; private $userPassword;
private $verificationToken;
public function validations() { public function validations() {
return [ return [
@ -65,17 +66,19 @@ class SignUpController extends Controller {
$this->userName = Controller::request('name'); $this->userName = Controller::request('name');
$this->userEmail = Controller::request('email'); $this->userEmail = Controller::request('email');
$this->userPassword = Controller::request('password'); $this->userPassword = Controller::request('password');
$this->verificationToken = Hashing::generateRandomToken();
} }
public function createNewUserAndRetrieveId() { public function createNewUserAndRetrieveId() {
$userInstance = new User(); $userInstance = new User();
$userInstance->setProperties([ $userInstance->setProperties([
'name' => $this->userName, 'name' => $this->userName,
'signupDate' => Date::getCurrentDate(), 'signupDate' => Date::getCurrentDate(),
'tickets' => 0, 'tickets' => 0,
'email' => $this->userEmail, 'email' => $this->userEmail,
'password' => Hashing::hashPassword($this->userPassword) 'password' => Hashing::hashPassword($this->userPassword),
'verificationToken' => $this->verificationToken
]); ]);
return $userInstance->store(); return $userInstance->store();
@ -86,7 +89,8 @@ class SignUpController extends Controller {
$mailSender->setTemplate(MailTemplate::USER_SIGNUP, [ $mailSender->setTemplate(MailTemplate::USER_SIGNUP, [
'to' => $this->userEmail, 'to' => $this->userEmail,
'name' => $this->userName 'name' => $this->userName,
'verificationToken' => $this->verificationToken
]); ]);
$mailSender->send(); $mailSender->send();

View File

@ -0,0 +1,38 @@
<?php
use Respect\Validation\Validator as DataValidator;
class VerifyController extends Controller{
const PATH = '/verify';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
]
]
];
}
public function handler() {
$email = Controller::request('email');
$token = Controller::request('token');
$userRow = User::getDataStore($email, 'email');
if(!$userRow) {
Response::respondError(ERRORS::INVALID_EMAIL);
return;
}
if($userRow->verificationToken !== $token) {
Response::respondError(ERRORS::INVALID_TOKEN);
return;
}
$userRow->verificationToken = null;
$userRow->store();
Response::respondSuccess();
}
}

View File

@ -30,4 +30,6 @@ class ERRORS {
const ALREADY_A_STAFF = 'ALREADY_A_STAFF'; const ALREADY_A_STAFF = 'ALREADY_A_STAFF';
const INVALID_STAFF = 'INVALID_STAFF'; const INVALID_STAFF = 'INVALID_STAFF';
const SAME_DEPARTMENT = 'SAME_DEPARTMENT'; const SAME_DEPARTMENT = 'SAME_DEPARTMENT';
const INVALID_TOKEN = 'INVALID_TOKEN';
const UNVERIFIED_USER = 'UNVERIFIED_USER';
} }

View File

@ -1,4 +1,5 @@
<div> <div>
Welcome, {{name}} to our support center, Welcome, {{name}} to our support center,
your email is {{to}} your email is {{to}},
your token is {{verificationToken}}
</div> </div>

View File

@ -1,4 +1,5 @@
<div> <div>
Bienvenido, {{name}} a nuestro centro de soporte, Bienvenido, {{name}} a nuestro centro de soporte,
tu email es {{to}} tu email es {{to}},
tu codigo de verificacion es {{verificationToken}}
</div> </div>

View File

@ -17,7 +17,8 @@ class User extends DataStore {
'name', 'name',
'signupDate', 'signupDate',
'tickets', 'tickets',
'sharedTicketList' 'sharedTicketList',
'verificationToken'
]; ];
} }

View File

@ -9,6 +9,11 @@ class Scripts
if response['status'] === 'fail' if response['status'] === 'fail'
raise response['message'] raise response['message']
end end
userRow = $database.getRow('user', email, 'email')
response = request('/user/verify', {
:email => email,
:token => userRow['verification_token']
})
end end
def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false) def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false)

View File

@ -1,104 +1,85 @@
describe'system/edit-settings' do describe'system/edit-settings' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should edit settings' do
result= request('/system/edit-settings', {
"csrf_userid" => $csrf_userid,
"csrf_token" => $csrf_token,
"maintenance-mode" => 1,
"time-zone" => -3,
"layout" => 'full-width',
"allow-attachments" => 1,
"max-size" => 2,
"language" => 'es',
"no-reply-email" => 'testemail@hotmail.com'
})
(result['status']).should.equal('success')
row = $database.getRow('setting', 'maintenance-mode', 'name')
(row['value']).should.equal('1')
row = $database.getRow('setting', 'time-zone', 'name')
(row['value']).should.equal('-3')
row = $database.getRow('setting', 'layout', 'name')
(row['value']).should.equal('full-width')
row = $database.getRow('setting', 'max-size', 'name')
(row['value']).should.equal('2')
row = $database.getRow('setting', 'language', 'name')
(row['value']).should.equal('es')
row = $database.getRow('setting', 'no-reply-email', 'name')
(row['value']).should.equal('testemail@hotmail.com')
request('/user/logout')
end
it 'should change allowed and supported languages' do
request('/user/logout') request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true) Scripts.login($staff[:email], $staff[:password], true)
it 'should edit settings' do result= request('/system/edit-settings', {
result= request('/system/edit-settings', { "csrf_userid" => $csrf_userid,
"csrf_userid" => $csrf_userid, "csrf_token" => $csrf_token,
"csrf_token" => $csrf_token, "supportedLanguages" => '["en", "pr", "jp", "ru"]',
"maintenance-mode" => 1, "allowedLanguages" => '["en","pr", "jp", "ru", "de"]'
"time-zone" => -3, })
"layout" => 'full-width',
"allow-attachments" => 1,
"max-size" => 2,
"language" => 'es',
"no-reply-email" => 'testemail@hotmail.com',
"smtp-host" => 'www.opensupports.com',
"smtp-port" => 18,
"smtp-user" => 'admin',
"smtp-pass" => 'pass1234',
})
(result['status']).should.equal('success') (result['status']).should.equal('success')
row = $database.getRow('setting', 'maintenance-mode', 'name') row = $database.getRow('language', 'en', 'code')
(row['value']).should.equal('1') (row['supported']).should.equal('1')
row = $database.getRow('setting', 'time-zone', 'name') row = $database.getRow('language', 'pr', 'code')
(row['value']).should.equal('-3') (row['supported']).should.equal('1')
row = $database.getRow('setting', 'layout', 'name') row = $database.getRow('language', 'jp', 'code')
(row['value']).should.equal('full-width') (row['supported']).should.equal('1')
row = $database.getRow('setting', 'max-size', 'name') row = $database.getRow('language', 'ru', 'code')
(row['value']).should.equal('2') (row['supported']).should.equal('1')
row = $database.getRow('setting', 'language', 'name') row = $database.getRow('language', 'en', 'code')
(row['value']).should.equal('es') (row['allowed']).should.equal('1')
row = $database.getRow('setting', 'no-reply-email', 'name') row = $database.getRow('language', 'pr', 'code')
(row['value']).should.equal('testemail@hotmail.com') (row['allowed']).should.equal('1')
row = $database.getRow('setting', 'smtp-host', 'name') row = $database.getRow('language', 'jp', 'code')
(row['value']).should.equal('www.opensupports.com') (row['allowed']).should.equal('1')
row = $database.getRow('setting', 'smtp-port', 'name') row = $database.getRow('language', 'ru', 'code')
(row['value']).should.equal('18') (row['allowed']).should.equal('1')
row = $database.getRow('setting', 'smtp-user', 'name') row = $database.getRow('language', 'de', 'code')
(row['value']).should.equal('admin') (row['allowed']).should.equal('1')
row = $database.getRow('setting', 'smtp-pass', 'name') lastLog = $database.getLastRow('log')
(row['value']).should.equal('pass1234') (lastLog['type']).should.equal('EDIT_SETTINGS')
request('/user/logout') request('/user/logout')
end
lastLog = $database.getLastRow('log') end
(lastLog['type']).should.equal('EDIT_SETTINGS')
end
it 'should change allowed and supported languages' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
result= request('/system/edit-settings', {
"csrf_userid" => $csrf_userid,
"csrf_token" => $csrf_token,
"supportedLanguages" => '["en", "pr", "jp", "ru"]',
"allowedLanguages" => '["en","pr", "jp", "ru", "de"]'
})
(result['status']).should.equal('success')
row = $database.getRow('language', 'en', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'pr', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'jp', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'ru', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'en', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'pr', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'jp', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'ru', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'de', 'code')
(row['allowed']).should.equal('1')
lastLog = $database.getLastRow('log')
(lastLog['type']).should.equal('EDIT_SETTINGS')
request('/user/logout')
end
end

View File

@ -32,7 +32,7 @@ describe '/ticket/comment/' do
it 'should fail if content is very long' do it 'should fail if content is very long' do
long_text = '' long_text = ''
600.times {long_text << 'a'} 6000.times {long_text << 'a'}
result = request('/ticket/comment', { result = request('/ticket/comment', {
content: long_text, content: long_text,

View File

@ -62,7 +62,7 @@ describe '/ticket/create' do
it 'should fail if content is very long' do it 'should fail if content is very long' do
long_text = '' long_text = ''
600.times {long_text << 'a'} 6000.times {long_text << 'a'}
result = request('/ticket/create',{ result = request('/ticket/create',{
title: 'Winter is coming', title: 'Winter is coming',

View File

@ -4,6 +4,7 @@ describe '/user/get-users' do
Scripts.createUser('tests@hotmail.com','passdasdasdas','laasdasd') Scripts.createUser('tests@hotmail.com','passdasdasdas','laasdasd')
Scripts.createUser('tests2@hotmail.com','passfasfasfsa','laeaefae') Scripts.createUser('tests2@hotmail.com','passfasfasfsa','laeaefae')
Scripts.createUser('tests3@hotmail.com','passfasfasfws','laeczvwaf') Scripts.createUser('tests3@hotmail.com','passfasfasfws','laeczvwaf')
result = request('/user/login', { result = request('/user/login', {
email: 'staff@opensupports.com', email: 'staff@opensupports.com',
password: 'staff', password: 'staff',
@ -86,4 +87,4 @@ describe '/user/get-users' do
(result['data']['users'][3]['name']).should.equal('Cersei Lannister') (result['data']['users'][3]['name']).should.equal('Cersei Lannister')
(result['data']['users'][4]['name']).should.equal('Tyrion Lannister') (result['data']['users'][4]['name']).should.equal('Tyrion Lannister')
end end
end end

View File

@ -12,6 +12,7 @@ describe '/user/get' do
csrf_userid: $csrf_userid, csrf_userid: $csrf_userid,
csrf_token: $csrf_token csrf_token: $csrf_token
}) })
@ticketNumber = result['data']['ticketNumber'] @ticketNumber = result['data']['ticketNumber']
it 'should fail if not logged' do it 'should fail if not logged' do
@ -54,4 +55,4 @@ describe '/user/get' do
(ticketFromUser['owner']).should.equal(nil) (ticketFromUser['owner']).should.equal(nil)
(ticketFromUser['events']).should.equal([]) (ticketFromUser['events']).should.equal([])
end end
end end

View File

@ -8,6 +8,11 @@ describe '/user/signup' do
userRow = $database.getRow('user', response['data']['userId']) userRow = $database.getRow('user', response['data']['userId'])
request('/user/verify', {
:email => 'steve@jobs.com',
:token => userRow['verification_token']
})
(userRow['email']).should.equal('steve@jobs.com') (userRow['email']).should.equal('steve@jobs.com')
(userRow['name']).should.equal('Steve Jobs') (userRow['name']).should.equal('Steve Jobs')