From 0d921472e2bf52dda1f65828218493ad3967d6bd Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 16 Dec 2016 01:02:34 -0300 Subject: [PATCH 01/19] Ivan - Fix bug os-171 - fixed width for add article modal[skip ci] --- client/src/app-components/article-add-modal.js | 4 ++-- client/src/app-components/article-add-modal.scss | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/app-components/article-add-modal.js b/client/src/app-components/article-add-modal.js index 613abbc7..e0e34d86 100644 --- a/client/src/app-components/article-add-modal.js +++ b/client/src/app-components/article-add-modal.js @@ -19,13 +19,13 @@ class ArticleAddModal extends React.Component { render() { 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})} /> <Form onSubmit={this.onAddNewArticleFormSubmit.bind(this)}> <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/> <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(); ModalContainer.closeModal(); }}>{i18n('CANCEL')}</Button> diff --git a/client/src/app-components/article-add-modal.scss b/client/src/app-components/article-add-modal.scss index 50ba5b88..3634480e 100644 --- a/client/src/app-components/article-add-modal.scss +++ b/client/src/app-components/article-add-modal.scss @@ -1,4 +1,5 @@ -.article-add-article { +.article-add-modal { + width: 800px; &__cancel-button { float: right; From e0d1fd0e4004d6f64f4823680ffc40afd4e94d06 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 16 Dec 2016 01:22:26 -0300 Subject: [PATCH 02/19] Ivan - Add delete article button[skip ci] --- .../articles/admin-panel-view-article.js | 25 ++++++++++++++++--- .../articles/admin-panel-view-article.scss | 6 ++++- client/src/data/languages/en.js | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/client/src/app/admin/panel/articles/admin-panel-view-article.js b/client/src/app/admin/panel/articles/admin-panel-view-article.js index 13c0af00..13ec35c2 100644 --- a/client/src/app/admin/panel/articles/admin-panel-view-article.js +++ b/client/src/app/admin/panel/articles/admin-panel-view-article.js @@ -1,6 +1,7 @@ import React from 'react'; import _ from 'lodash'; import {connect} from 'react-redux'; +import {browserHistory} from 'react-router'; import RichTextEditor from 'react-rte-browserify'; import ArticlesActions from 'actions/articles-actions'; @@ -9,6 +10,7 @@ import i18n from 'lib-app/i18n'; import API from 'lib-app/api-call'; import DateTransformer from 'lib-core/date-transformer'; +import AreYouSure from 'app-components/are-you-sure'; import Header from 'core-components/header'; import Loading from 'core-components/loading'; import Button from 'core-components/button'; @@ -61,10 +63,14 @@ class AdminPanelViewArticle extends React.Component { renderArticlePreview(article) { return ( <div className="admin-panel-view-article__content"> - <div className="admin-panel-view-article__edit-button"> - <Button size="medium" onClick={this.onEditClick.bind(this, article)}>{i18n('EDIT')}</Button> + <div className="admin-panel-view-article__edit-buttons"> + <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 className="admin-panel-view-article__article"> <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) { API.call({ path: '/article/edit', @@ -139,6 +149,15 @@ class AdminPanelViewArticle extends React.Component { 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) => { diff --git a/client/src/app/admin/panel/articles/admin-panel-view-article.scss b/client/src/app/admin/panel/articles/admin-panel-view-article.scss index 300041c9..431fe9a4 100644 --- a/client/src/app/admin/panel/articles/admin-panel-view-article.scss +++ b/client/src/app/admin/panel/articles/admin-panel-view-article.scss @@ -1,10 +1,14 @@ .admin-panel-view-article { - &__edit-button { + &__edit-buttons { text-align: left; margin-bottom: 20px; } + &__edit-button { + margin-right: 20px; + } + &__last-edited { font-style: italic; text-align: right; diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 0805e711..07d8f8e5 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -136,6 +136,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}.', '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.', + 'DELETE_ARTICLE_DESCRIPTION': 'You\'re going to delete this article for ever.', 'STAFF_MEMBERS_DESCRIPTION': 'Here you can see who are your staff members.', 'ADD_STAFF_DESCRIPTION': 'Here you can add staff members to your teams.', 'EDIT_STAFF_DESCRIPTION': 'Here you can edit information about a staff member.', From 04f1d12e7d89fc60eefa0ce88d8bd158373ccec8 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 16 Dec 2016 01:58:27 -0300 Subject: [PATCH 03/19] Ivan - Fix drag and drop os-172 multiple topics [skip ci] --- client/src/app-components/topic-viewer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/app-components/topic-viewer.js b/client/src/app-components/topic-viewer.js index fc923ea5..60efe0ad 100644 --- a/client/src/app-components/topic-viewer.js +++ b/client/src/app-components/topic-viewer.js @@ -139,12 +139,12 @@ class TopicViewer extends React.Component { if(this.props.editable) { _.extend(props, { - onDragOver: this.onItemDragOver.bind(this, article, index), - onDrop: this.onItemDrop.bind(this, article, index), + 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}); + this.setState({articles: this.props.articles, currentDraggedId: 0}); } } }); From 7ef2eec14b14b642a6ccc32204428a87aaeb592b Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Tue, 20 Dec 2016 23:26:46 -0300 Subject: [PATCH 04/19] Ivan - Add icon to drag and drop [skip ci] --- client/src/app-components/topic-viewer.js | 46 ++++++++++++--------- client/src/app-components/topic-viewer.scss | 22 +++++++++- client/src/core-components/icon.js | 2 +- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/client/src/app-components/topic-viewer.js b/client/src/app-components/topic-viewer.js index 60efe0ad..2009da8c 100644 --- a/client/src/app-components/topic-viewer.js +++ b/client/src/app-components/topic-viewer.js @@ -1,4 +1,5 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import _ from 'lodash'; import classNames from 'classnames'; import {Link} from 'react-router'; @@ -78,11 +79,33 @@ class TopicViewer extends React.Component { } 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 ( - <li className="topic-viewer__list-item" key={index}> + <li {...props}> <Link {...this.getArticleLinkProps(article, index)}> {article.title} </Link> + <Icon className="topic-viewer__grab-icon" name="arrows" ref={'grab-' + index}/> </li> ); } @@ -126,31 +149,16 @@ class TopicViewer extends React.Component { ); } - getArticleLinkProps(article, index) { + getArticleLinkProps(article) { let classes = { 'topic-viewer__list-item-button': true, 'topic-viewer__list-item-hidden': article.hidden }; - - let props = { + + return { className: classNames(classes), to: this.props.articlePath + article.id }; - - 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 props; } onDeleteClick() { diff --git a/client/src/app-components/topic-viewer.scss b/client/src/app-components/topic-viewer.scss index 0f489be2..f674fd88 100644 --- a/client/src/app-components/topic-viewer.scss +++ b/client/src/app-components/topic-viewer.scss @@ -38,12 +38,22 @@ width: 50%; color: $secondary-blue; margin-bottom: 10px; + user-select: none; &-hidden { - width: 80%; - display: inline-block; opacity: 0; } + + &:hover { + .topic-viewer__grab-icon { + display: inline-block; + left: 0; + } + } + + &-button { + user-select: none; + } } &-item:before { @@ -52,10 +62,18 @@ } &-item-button { + display: inline-block; color: $secondary-blue; } } + &__grab-icon { + color: $grey; + cursor: move; + margin-left: 10px; + display: none; + } + &__add-item { color: $dark-grey; } diff --git a/client/src/core-components/icon.js b/client/src/core-components/icon.js index d26123d1..becdb99b 100644 --- a/client/src/core-components/icon.js +++ b/client/src/core-components/icon.js @@ -19,7 +19,7 @@ class Icon extends React.Component { renderFontIcon() { 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}}/> ); } From 91f8394ef6427a96c481534ce261f658720055a1 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 17:07:34 -0300 Subject: [PATCH 05/19] Ivan - Add 6000 as max characters for commit [skip ci] --- client/src/app-components/ticket-event.scss | 1 + server/controllers/ticket/comment.php | 2 +- server/controllers/ticket/create.php | 2 +- tests/ticket/comment.rb | 2 +- tests/ticket/create.rb | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/app-components/ticket-event.scss b/client/src/app-components/ticket-event.scss index 5e36aaa0..50105bbe 100644 --- a/client/src/app-components/ticket-event.scss +++ b/client/src/app-components/ticket-event.scss @@ -47,6 +47,7 @@ &__comment { position: relative; + word-break: break-all; &-pointer { right: 100%; diff --git a/server/controllers/ticket/comment.php b/server/controllers/ticket/comment.php index d9db4566..2f016ebf 100644 --- a/server/controllers/ticket/comment.php +++ b/server/controllers/ticket/comment.php @@ -13,7 +13,7 @@ class CommentController extends Controller { 'permission' => 'user', 'requestData' => [ 'content' => [ - 'validation' => DataValidator::length(20, 500), + 'validation' => DataValidator::length(20, 5000), 'error' => ERRORS::INVALID_CONTENT ], 'ticketNumber' => [ diff --git a/server/controllers/ticket/create.php b/server/controllers/ticket/create.php index 2d9294ba..d36c3757 100644 --- a/server/controllers/ticket/create.php +++ b/server/controllers/ticket/create.php @@ -20,7 +20,7 @@ class CreateController extends Controller { 'error' => ERRORS::INVALID_TITLE ], 'content' => [ - 'validation' => DataValidator::length(10, 500), + 'validation' => DataValidator::length(10, 5000), 'error' => ERRORS::INVALID_CONTENT ], 'departmentId' => [ diff --git a/tests/ticket/comment.rb b/tests/ticket/comment.rb index 27222b87..9c39af38 100644 --- a/tests/ticket/comment.rb +++ b/tests/ticket/comment.rb @@ -32,7 +32,7 @@ describe '/ticket/comment/' do it 'should fail if content is very long' do long_text = '' - 600.times {long_text << 'a'} + 6000.times {long_text << 'a'} result = request('/ticket/comment', { content: long_text, diff --git a/tests/ticket/create.rb b/tests/ticket/create.rb index abafdc07..6b548d51 100644 --- a/tests/ticket/create.rb +++ b/tests/ticket/create.rb @@ -58,7 +58,7 @@ describe '/ticket/create' do it 'should fail if content is very long' do long_text = '' - 600.times {long_text << 'a'} + 6000.times {long_text << 'a'} result = request('/ticket/create',{ title: 'Winter is coming', From 488633b0c22db64d982370f28d9051e865a3bec6 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 17:54:02 -0300 Subject: [PATCH 06/19] Ivan - Avoid staff deleting himself and avoid level change to oneself [skip ci] --- server/controllers/staff/delete.php | 6 +++++- server/controllers/staff/edit.php | 33 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/server/controllers/staff/delete.php b/server/controllers/staff/delete.php index cde02d0e..55ef0760 100644 --- a/server/controllers/staff/delete.php +++ b/server/controllers/staff/delete.php @@ -21,6 +21,11 @@ class DeleteStaffController extends Controller { $staffId = Controller::request('staffId'); $staff = Staff::getDataStore($staffId); + if($staffId === Controller::getLoggedUser()->id) { + Response::respondError(ERRORS::INVALID_STAFF); + return; + } + foreach($staff->sharedTicketList as $ticket) { $ticket->owner = null; $ticket->true = true; @@ -31,7 +36,6 @@ class DeleteStaffController extends Controller { $department->owners--; $department->store(); } - $staff->delete(); Response::respondSuccess(); diff --git a/server/controllers/staff/edit.php b/server/controllers/staff/edit.php index f18c3f31..fef5f0fc 100644 --- a/server/controllers/staff/edit.php +++ b/server/controllers/staff/edit.php @@ -4,8 +4,7 @@ use Respect\Validation\Validator as DataValidator; class EditStaffController extends Controller { const PATH = '/edit'; - private $staffRow; - private $staffId; + private $staffInstance; public function validations() { return [ @@ -15,14 +14,14 @@ class EditStaffController extends Controller { } public function handler() { - $this->staffId = Controller::request('staffId'); + $staffId = Controller::request('staffId'); - if(!$this->staffId) { - $this->staffRow = Controller::getLoggedUser(); + if(!$staffId) { + $this->staffInstance = Controller::getLoggedUser(); } 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); return; } @@ -39,29 +38,29 @@ class EditStaffController extends Controller { Response::respondSuccess(); } - public function editInformation() { + private function editInformation() { if(Controller::request('email')) { - $this->staffRow->email = Controller::request('email'); + $this->staffInstance->email = Controller::request('email'); } 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)) { - $this->staffRow->level = Controller::request('level'); + if(Controller::request('level') && Controller::isStaffLogged(3) && Controller::request('staffId') !== Controller::getLoggedUser()->id) { + $this->staffInstance->level = Controller::request('level'); } 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(); $departmentIds = json_decode(Controller::request('departments')); @@ -73,8 +72,8 @@ class EditStaffController extends Controller { return $listDepartments; } - public function updateDepartmentsOwners() { - $list1 = $this->staffRow->sharedDepartmentList; + private function updateDepartmentsOwners() { + $list1 = $this->staffInstance->sharedDepartmentList; $list2 = $this->getDepartmentList(); foreach ($list1 as $department1) { From 750251311c6c2b35983fd45793908b9a970446f3 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 18:33:10 -0300 Subject: [PATCH 07/19] Ivan - Improve tooltip styling and animation [skip ci] --- client/src/core-components/tooltip.js | 4 ++-- client/src/core-components/tooltip.scss | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/core-components/tooltip.js b/client/src/core-components/tooltip.js index 04ea97e2..738b1051 100644 --- a/client/src/core-components/tooltip.js +++ b/client/src/core-components/tooltip.js @@ -28,7 +28,7 @@ class Tooltip extends React.Component { renderAnimatedMessage() { 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)} </Motion> ) @@ -36,7 +36,7 @@ class Tooltip extends React.Component { renderMessage(animation) { return ( - <div style={animation}> + <div className="tooltip__animated-container" style={animation}> <span className="tooltip__pointer-shadow"/> <span className="tooltip__pointer"/> <div className="tooltip__message"> diff --git a/client/src/core-components/tooltip.scss b/client/src/core-components/tooltip.scss index 857a917d..b9b4390b 100644 --- a/client/src/core-components/tooltip.scss +++ b/client/src/core-components/tooltip.scss @@ -8,12 +8,15 @@ cursor: default; } + &__animated-container { + position: relative; + } + &__message { position: absolute; bottom: 100%; - left: -25%; margin-bottom: 15px; - margin-left: -10px; + margin-left: -15px; border: 0 solid rgba(0, 0, 0, 0.247059); box-shadow: rgba(0, 0, 0, 0.247059) 0 -1px 4px; border-radius: 4px; From 84810ba57dc71310187b2c1377fe1f7806e0895f Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 18:55:12 -0300 Subject: [PATCH 08/19] Ivan - Improve TicketInfo style [skip ci] --- client/src/app-components/ticket-info.js | 27 ++++++++++++++++------- client/src/data/fixtures/user-fixtures.js | 17 ++------------ client/src/data/languages/en.js | 2 ++ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/client/src/app-components/ticket-info.js b/client/src/app-components/ticket-info.js index 788bd423..0feaa64b 100644 --- a/client/src/app-components/ticket-info.js +++ b/client/src/app-components/ticket-info.js @@ -1,4 +1,6 @@ import React from 'react'; +import _ from 'lodash'; + import i18n from 'lib-app/i18n'; class TicketInfo extends React.Component { @@ -16,12 +18,12 @@ class TicketInfo extends React.Component { {this.props.ticket.content} </div> <div className="ticket-info__author"> - Author: {this.props.ticket.author.name} + {i18n('AUTHOR')}: {this.props.ticket.author.name} </div> <div className="ticket-info__properties"> <div className="ticket-info__properties__status"> <span className="ticket-info__properties__label"> - Status: + {i18n('STATUS')}: </span> <span className={this.getStatusClass()}> {(this.props.ticket.closed) ? 'closed' : 'open'} @@ -29,7 +31,7 @@ class TicketInfo extends React.Component { </div> <div className="ticket-info__properties__priority"> <span className="ticket-info__properties__label"> - Priority: + {i18n('PRIORITY')}: </span> <span className={this.getPriorityClass()}> {this.props.ticket.priority} @@ -37,24 +39,25 @@ class TicketInfo extends React.Component { </div> <div className="ticket-info__properties__owner"> <span className="ticket-info__properties__label"> - Owned: + {i18n('OWNED')}: </span> - <span className="ticket-info__properties__badge-red"> - none + <span className={this.getOwnedClass()}> + {(this.props.ticket.owner) ? i18n('YES') : i18n('NO')} </span> </div> <div className="ticket-info__properties__comments"> <span className="ticket-info__properties__label"> - Comments: + {i18n('COMMENTS')}: </span> <span className="ticket-info__properties__badge-blue"> - 21 + {_.filter(this.props.ticket.events, event => event.type === 'COMMENT').length} </span> </div> </div> </div> ); } + getStatusClass() { if(this.props.ticket.closed) { 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() { let priorityClasses = { 'low': 'ticket-info__properties__badge-green', diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index d611956b..c23622e3 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -368,7 +368,7 @@ module.exports = [ { ticketNumber: '445441', 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: { id: 2, name: 'Technical Issues' @@ -502,9 +502,7 @@ module.exports = [ name: 'Haskell Curry', email: 'haskell@lambda.com' }, - owner: { - name: 'Steve Jobs' - }, + owner: null, events: [ { type: 'ASSIGN', @@ -516,17 +514,6 @@ module.exports = [ 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', date: '201504100924', diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 07d8f8e5..79b287bd 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -30,6 +30,7 @@ export default { 'STAFF': 'Staff', 'CUSTOMER': 'Customer', 'YES': 'Yes', + 'NO': 'No', 'CANCEL': 'Cancel', 'MY_ACCOUNT': 'My Account', 'DASHBOARD': 'Dashboard', @@ -114,6 +115,7 @@ export default { 'ADD_DEPARTMENT': 'Add department', 'UPDATE_DEPARTMENT': 'Update department', 'TRANSFER_TICKETS_TO': 'Transfer tickets to', + 'COMMENTS': 'Comments', //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.', From b6af2f44ab3603066fbe8858700017ea7c70f631 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 18:56:54 -0300 Subject: [PATCH 09/19] Ivan - Improve ticketinfo styling [skip ci] --- client/src/app-components/ticket-info.scss | 38 ++++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/client/src/app-components/ticket-info.scss b/client/src/app-components/ticket-info.scss index fde38901..13ccaf62 100644 --- a/client/src/app-components/ticket-info.scss +++ b/client/src/app-components/ticket-info.scss @@ -3,19 +3,28 @@ .ticket-info { width: 300px; font-weight: normal; - &__title{ + + &__title { color: $primary-black; - font-variant: small-caps; + font-size: $font-size--md; } - &__description{ - color: $grey; + + &__description { + margin-top: 5px; font-size: small; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } - &__author{ + + &__author { color: $primary-blue; - margin-bottom: 12px; + font-weight: bold; + margin-top: 10px; + margin-bottom: 5px; } - &__properties{ + + &__properties { background-color: $grey; padding: 10px; font-variant: small-caps; @@ -35,7 +44,7 @@ &__badge-green, &__badge-blue, - &__badge-red{ + &__badge-red { color: white; border-radius: 7px; display: inline-block; @@ -44,19 +53,19 @@ margin-left: 5px; } - &__badge-green{ + &__badge-green { background-color: $primary-green; } - &__badge-blue{ + &__badge-blue { background-color: $secondary-blue; } - &__badge-red{ + &__badge-red { background-color: $primary-red; } - &__label{ + &__label { text-align: right; width: 71px; display: inline-block; @@ -64,10 +73,11 @@ } &__status, - &__owner{ + &__owner { margin-right: 10px; width: 125px; - .ticket-info__properties__label{ + + .ticket-info__properties__label { width: 48px; } } From d2c2327a6030ed50b031ebccb63c7b101000618a Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Wed, 21 Dec 2016 19:16:21 -0300 Subject: [PATCH 10/19] Ivan - Add message for ticket comment error [skip ci] --- client/src/app-components/ticket-viewer.js | 14 ++++++++++++-- .../dashboard-list-articles-page.js | 2 +- client/src/data/languages/en.js | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/src/app-components/ticket-viewer.js b/client/src/app-components/ticket-viewer.js index bce229eb..262a7b9a 100644 --- a/client/src/app-components/ticket-viewer.js +++ b/client/src/app-components/ticket-viewer.js @@ -14,6 +14,7 @@ import FormField from 'core-components/form-field'; import SubmitButton from 'core-components/submit-button'; import DropDown from 'core-components/drop-down'; import Button from 'core-components/button'; +import Message from 'core-components/message'; class TicketViewer extends React.Component { static propTypes = { @@ -188,6 +189,7 @@ class TicketViewer extends React.Component { <SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton> </Form> </div> + {(this.state.commentError) ? this.renderCommentError() : null} </div> ); } @@ -216,6 +218,12 @@ class TicketViewer extends React.Component { return customResponsesNode; } + renderCommentError() { + return ( + <Message className="ticket-viewer__message" type="error">{i18n('TICKET_COMMENT_ERROR')}</Message> + ); + } + getCommentFormProps() { return { onSubmit: this.onSubmit.bind(this), @@ -316,7 +324,8 @@ class TicketViewer extends React.Component { onCommentSuccess() { this.setState({ - loading: false + loading: false, + commentError: false }); this.onTicketModification(); @@ -324,7 +333,8 @@ class TicketViewer extends React.Component { onCommentFail() { this.setState({ - loading: false + loading: false, + commentError: true }); } diff --git a/client/src/app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page.js b/client/src/app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page.js index 36837ab9..da942c22 100644 --- a/client/src/app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page.js +++ b/client/src/app/main/dashboard/dashboard-list-articles/dashboard-list-articles-page.js @@ -23,7 +23,7 @@ class DashboardListArticlesPage extends React.Component { render() { return ( - <div classnames="dashboard-list-articles-page"> + <div className="dashboard-list-articles-page"> <Header title={i18n('LIST_ARTICLES')} description={i18n('LIST_ARTICLES_DESCRIPTION')}/> <SearchBox className="dashboard-list-articles-page__search-box" onSearch={this.onSearch.bind(this)} searchOnType /> {(!this.state.showSearchResults) ? this.renderArticleList() : this.renderSearchResults()} diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 79b287bd..4b30f8c7 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -157,6 +157,7 @@ export default { 'PASSWORD_NOT_MATCH': 'Password does not match', 'INVALID_RECOVER': 'Invalid recover data', '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.', 'INVALID_USER': 'User id is invalid', From 5c2f482c10f56da12cc0a3f49e859d001dd5bbab Mon Sep 17 00:00:00 2001 From: AntonyAntonio <guillermo@opensupports.com> Date: Thu, 22 Dec 2016 03:07:06 -0300 Subject: [PATCH 11/19] Guillermo - add path /user/verify [skip ci] --- server/controllers/user.php | 2 ++ server/controllers/user/login.php | 2 +- server/controllers/user/signup.php | 7 ++++-- server/controllers/user/verify.php | 38 ++++++++++++++++++++++++++++++ server/data/ERRORS.php | 1 + server/models/User.php | 3 ++- 6 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 server/controllers/user/verify.php diff --git a/server/controllers/user.php b/server/controllers/user.php index c7a27ef6..40af4c14 100644 --- a/server/controllers/user.php +++ b/server/controllers/user.php @@ -14,6 +14,7 @@ include 'user/delete.php'; include 'user/ban.php'; include 'user/un-ban.php'; include 'user/list-ban.php'; +include 'user/verify.php'; $userControllers = new ControllerGroup(); $userControllers->setGroupPath('/user'); @@ -33,4 +34,5 @@ $userControllers->addController(new DeleteUserController); $userControllers->addController(new BanUserController); $userControllers->addController(new UnBanUserController); $userControllers->addController(new ListBanUserController); +$userControllers->addController(new VerifyController); $userControllers->finalize(); diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php index 020eae13..6a693558 100644 --- a/server/controllers/user/login.php +++ b/server/controllers/user/login.php @@ -26,7 +26,7 @@ class LoginController extends Controller { $this->userInstance->lastLogin = Date::getCurrentDate(); $this->userInstance->store(); } - + Response::respondSuccess($this->getUserData()); } else { Response::respondError(ERRORS::INVALID_CREDENTIALS); diff --git a/server/controllers/user/signup.php b/server/controllers/user/signup.php index b073fab1..9c642d11 100644 --- a/server/controllers/user/signup.php +++ b/server/controllers/user/signup.php @@ -68,13 +68,16 @@ class SignUpController extends Controller { public function createNewUserAndRetrieveId() { $userInstance = new User(); - + + $token = Hashing::generateRandomToken(); + $userInstance->setProperties([ 'name' => $this->userName, 'signupDate' => Date::getCurrentDate(), 'tickets' => 0, 'email' => $this->userEmail, - 'password' => Hashing::hashPassword($this->userPassword) + 'password' => Hashing::hashPassword($this->userPassword), + 'verificationToken' => $token ]); return $userInstance->store(); diff --git a/server/controllers/user/verify.php b/server/controllers/user/verify.php new file mode 100644 index 00000000..616d3b92 --- /dev/null +++ b/server/controllers/user/verify.php @@ -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(); + } +} \ No newline at end of file diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index d28286d5..b353240b 100644 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -30,4 +30,5 @@ class ERRORS { const ALREADY_A_STAFF = 'ALREADY_A_STAFF'; const INVALID_STAFF = 'INVALID_STAFF'; const SAME_DEPARTMENT = 'SAME_DEPARTMENT'; + const INVALID_TOKEN = 'INVALID_TOKEN'; } diff --git a/server/models/User.php b/server/models/User.php index beee0708..eb6d7b21 100644 --- a/server/models/User.php +++ b/server/models/User.php @@ -17,7 +17,8 @@ class User extends DataStore { 'name', 'signupDate', 'tickets', - 'sharedTicketList' + 'sharedTicketList', + 'verificationToken' ]; } From 63516662589035d3a16ce89e8e8862d9d171fa14 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 23 Dec 2016 00:18:41 -0300 Subject: [PATCH 12/19] Ivan - Add error messages for admin tickets view [skip ci] --- .../panel/tickets/admin-panel-all-tickets.js | 16 ++++--- .../panel/tickets/admin-panel-my-tickets.js | 9 ++-- .../panel/tickets/admin-panel-new-tickets.js | 9 ++-- client/src/data/languages/en.js | 1 + client/src/reducers/admin-data-reducer.js | 47 ++++++++++++++++--- 5 files changed, 63 insertions(+), 19 deletions(-) diff --git a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js index b721f35f..1f7f6cc2 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-all-tickets.js @@ -4,9 +4,11 @@ import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; import AdminDataAction from 'actions/admin-data-actions'; -import Header from 'core-components/header'; import TicketList from 'app-components/ticket-list'; + +import Header from 'core-components/header'; import SearchBox from 'core-components/search-box'; +import Message from 'core-components/message'; class AdminPanelAllTickets extends React.Component { @@ -31,7 +33,7 @@ class AdminPanelAllTickets extends React.Component { <div className="admin-panel-my-tickets__search-box"> <SearchBox onSearch={this.onSearch.bind(this)} /> </div> - <TicketList {...this.getTicketListProps()}/> + {(this.props.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_TICKETS')}</Message> : <TicketList {...this.getTicketListProps()}/>} </div> ); } @@ -52,7 +54,8 @@ class AdminPanelAllTickets extends React.Component { onSearch(query) { this.setState({query, page: 1}); - if (query) { + + if(query) { this.props.dispatch(AdminDataAction.searchTickets(query)); } else { this.props.dispatch(AdminDataAction.retrieveAllTickets()); @@ -60,9 +63,7 @@ class AdminPanelAllTickets extends React.Component { } onPageChange(event) { - this.setState({ - page: event.target.value - }); + this.setState({page: event.target.value}); if(this.state.query) { this.props.dispatch(AdminDataAction.searchTickets(this.state.query, event.target.value)); @@ -77,6 +78,7 @@ export default connect((store) => { departments: store.session.userDepartments, tickets: store.adminData.allTickets, pages: store.adminData.allTicketsPages, - loading: !store.adminData.allTicketsLoaded + loading: !store.adminData.allTicketsLoaded, + error: store.adminData.allTicketsError }; })(AdminPanelAllTickets); diff --git a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js index 512454b1..e90847ec 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-my-tickets.js @@ -4,9 +4,11 @@ import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; import AdminDataAction from 'actions/admin-data-actions'; -import Header from 'core-components/header'; import TicketList from 'app-components/ticket-list'; +import Header from 'core-components/header'; +import Message from 'core-components/message'; + class AdminPanelMyTickets extends React.Component { static defaultProps = { @@ -22,7 +24,7 @@ class AdminPanelMyTickets extends React.Component { return ( <div className="admin-panel-my-tickets"> <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> ); } @@ -42,6 +44,7 @@ export default connect((store) => { return { departments: store.session.userDepartments, tickets: store.adminData.myTickets, - loading: !store.adminData.myTicketsLoaded + loading: !store.adminData.myTicketsLoaded, + error: store.adminData.myTicketsError }; })(AdminPanelMyTickets); diff --git a/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js index 819e9b56..bb3240cb 100644 --- a/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js +++ b/client/src/app/admin/panel/tickets/admin-panel-new-tickets.js @@ -4,9 +4,11 @@ import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; import AdminDataAction from 'actions/admin-data-actions'; -import Header from 'core-components/header'; import TicketList from 'app-components/ticket-list'; +import Header from 'core-components/header'; +import Message from 'core-components/message'; + class AdminPanelNewTickets extends React.Component { static defaultProps = { @@ -22,7 +24,7 @@ class AdminPanelNewTickets extends React.Component { return ( <div className="admin-panel-my-tickets"> <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> ); } @@ -42,6 +44,7 @@ export default connect((store) => { return { departments: store.session.userDepartments, tickets: store.adminData.newTickets, - loading: !store.adminData.newTicketsLoaded + loading: !store.adminData.newTicketsLoaded, + error: store.adminData.newTicketsError }; })(AdminPanelNewTickets); diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 4b30f8c7..dfd41f50 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -160,6 +160,7 @@ export default { 'TICKET_COMMENT_ERROR': 'An error occurred while trying to add the comment.', 'NO_PERMISSION': 'You\'ve no permission to access to this page.', 'INVALID_USER': 'User id is invalid', + 'ERROR_RETRIEVING_TICKETS': 'An error occurred while trying to retrieve tickets.', //MESSAGES 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', diff --git a/client/src/reducers/admin-data-reducer.js b/client/src/reducers/admin-data-reducer.js index d3d753dc..30fc80f2 100644 --- a/client/src/reducers/admin-data-reducer.js +++ b/client/src/reducers/admin-data-reducer.js @@ -11,10 +11,15 @@ class AdminDataReducer extends Reducer { customResponsesLoaded: false, myTickets: [], myTicketsLoaded: false, + myTicketsError: false, + newTickets: [], newTicketsLoaded: false, + newTicketsError: false, + allTickets: [], - allTicketsLoaded: false + allTicketsLoaded: false, + allTicketsError: false }; } @@ -22,11 +27,17 @@ class AdminDataReducer extends Reducer { return { 'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses, 'SESSION_CHECKED': this.onSessionChecked, + 'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved, + 'MY_TICKETS_REJECTED': this.onMyTicketsRejected, 'MY_TICKETS_PENDING': this.onMyTicketsPending, + 'NEW_TICKETS_FULFILLED': this.onNewTicketsRetrieved, + 'NEW_TICKETS_REJECTED': this.onNewTicketsRejected, 'NEW_TICKETS_PENDING': this.onNewTicketsPending, + 'ALL_TICKETS_FULFILLED': this.onAllTicketsRetrieved, + 'ALL_TICKETS_REJECTED': this.onAllTicketsRejected, 'ALL_TICKETS_PENDING': this.onAllTicketsPending }; } @@ -53,26 +64,42 @@ class AdminDataReducer extends Reducer { return _.extend({}, state, { myTickets: payload.data, myTicketsLoaded: true + }); + } + + onMyTicketsRejected(state) { + return _.extend({}, state, { + myTicketsError: true, + myTicketsLoaded: true }) } onMyTicketsPending(state) { return _.extend({}, state, { + myTicketsError: false, myTicketsLoaded: false - }) + }); } onNewTicketsRetrieved(state, payload) { return _.extend({}, state, { newTickets: payload.data, newTicketsLoaded: true - }) + }); + } + + onNewTicketsRejected(state) { + return _.extend({}, state, { + newTicketsError: true, + newTicketsLoaded: false + }); } onNewTicketsPending(state) { return _.extend({}, state, { + newTicketsError: false, newTicketsLoaded: false - }) + }); } onAllTicketsRetrieved(state, payload) { @@ -80,13 +107,21 @@ class AdminDataReducer extends Reducer { allTickets: payload.data.tickets, allTicketsPages: payload.data.pages, allTicketsLoaded: true - }) + }); + } + + onAllTicketsRejected(state) { + return _.extend({}, state, { + allTicketsError: false, + allTicketsLoaded: false + }); } onAllTicketsPending(state) { return _.extend({}, state, { + allTicketsError: false, allTicketsLoaded: false - }) + }); } } From be2e492aa4de9f86eaebccae69084fe45f384fdf Mon Sep 17 00:00:00 2001 From: AntonyAntonio <guillermo@opensupports.com> Date: Fri, 23 Dec 2016 01:27:21 -0300 Subject: [PATCH 13/19] Guillermo - add path /user/verify [skip ci] --- server/controllers/user/login.php | 10 +++++++++- server/data/ERRORS.php | 1 + tests/scripts.rb | 5 +++++ tests/system/edit-settings.rb | 3 +-- tests/user/get-users-test.rb | 3 ++- tests/user/get.rb | 3 ++- tests/user/signup.rb | 5 +++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/server/controllers/user/login.php b/server/controllers/user/login.php index 6a693558..7eada075 100644 --- a/server/controllers/user/login.php +++ b/server/controllers/user/login.php @@ -26,7 +26,15 @@ class LoginController extends Controller { $this->userInstance->lastLogin = Date::getCurrentDate(); $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()); } else { Response::respondError(ERRORS::INVALID_CREDENTIALS); diff --git a/server/data/ERRORS.php b/server/data/ERRORS.php index b353240b..2dbe9857 100644 --- a/server/data/ERRORS.php +++ b/server/data/ERRORS.php @@ -31,4 +31,5 @@ class ERRORS { const INVALID_STAFF = 'INVALID_STAFF'; const SAME_DEPARTMENT = 'SAME_DEPARTMENT'; const INVALID_TOKEN = 'INVALID_TOKEN'; + const UNVERIFIED_USER = 'UNVERIFIED_USER'; } diff --git a/tests/scripts.rb b/tests/scripts.rb index 6b91569e..7e6a5506 100644 --- a/tests/scripts.rb +++ b/tests/scripts.rb @@ -9,6 +9,11 @@ class Scripts if response['status'] === 'fail' raise 'Could not create user' end + userRow = $database.getRow('user', email, 'email') + response = request('/user/verify', { + :email => email, + :token => userRow['verification_token'] + }) end def self.login(email = 'steve@jobs.com', password = 'custompassword', staff = false) diff --git a/tests/system/edit-settings.rb b/tests/system/edit-settings.rb index beb96aa1..0d9c1d02 100644 --- a/tests/system/edit-settings.rb +++ b/tests/system/edit-settings.rb @@ -18,7 +18,6 @@ describe'system/edit-settings' do "smtp-user" => 'admin', "smtp-pass" => 'pass1234', }) - puts result['message'] (result['status']).should.equal('success') @@ -54,4 +53,4 @@ describe'system/edit-settings' do request('/user/logout') end -end \ No newline at end of file +end diff --git a/tests/user/get-users-test.rb b/tests/user/get-users-test.rb index 8ccec70e..522f18c5 100644 --- a/tests/user/get-users-test.rb +++ b/tests/user/get-users-test.rb @@ -4,6 +4,7 @@ describe '/user/get-users' do Scripts.createUser('tests@hotmail.com','passdasdasdas','laasdasd') Scripts.createUser('tests2@hotmail.com','passfasfasfsa','laeaefae') Scripts.createUser('tests3@hotmail.com','passfasfasfws','laeczvwaf') + result = request('/user/login', { email: 'staff@opensupports.com', password: 'staff', @@ -86,4 +87,4 @@ describe '/user/get-users' do (result['data']['users'][3]['name']).should.equal('Cersei Lannister') (result['data']['users'][4]['name']).should.equal('Tyrion Lannister') end -end \ No newline at end of file +end diff --git a/tests/user/get.rb b/tests/user/get.rb index ef89384e..c2e27109 100644 --- a/tests/user/get.rb +++ b/tests/user/get.rb @@ -10,6 +10,7 @@ describe '/user/get' do csrf_userid: $csrf_userid, csrf_token: $csrf_token }) + @ticketNumber = result['data']['ticketNumber'] it 'should fail if not logged' do @@ -52,4 +53,4 @@ describe '/user/get' do (ticketFromUser['owner']).should.equal(nil) (ticketFromUser['events']).should.equal([]) end -end \ No newline at end of file +end diff --git a/tests/user/signup.rb b/tests/user/signup.rb index b3e0978b..0e21b020 100644 --- a/tests/user/signup.rb +++ b/tests/user/signup.rb @@ -8,6 +8,11 @@ describe '/user/signup' do 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['name']).should.equal('Steve Jobs') end From e24f46c27846725b98dc705e9d18460f9e7c16ae Mon Sep 17 00:00:00 2001 From: AntonyAntonio <guillermo@opensupports.com> Date: Fri, 23 Dec 2016 01:56:01 -0300 Subject: [PATCH 14/19] Guillermo - add path /user/verify [skip ci] --- server/controllers/user/get-user.php | 3 ++- server/controllers/user/signup.php | 9 +++++---- server/data/mail-templates/user-signup-en.html | 3 ++- server/data/mail-templates/user-signup-es.html | 3 ++- tests/system/edit-settings.rb | 18 +----------------- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/server/controllers/user/get-user.php b/server/controllers/user/get-user.php index 5f9e3059..8ea55c09 100644 --- a/server/controllers/user/get-user.php +++ b/server/controllers/user/get-user.php @@ -34,7 +34,8 @@ class GetUserByIdController extends Controller { 'name' => $user->name, 'email' => $user->email, 'signupDate' => $user->signupDate, - 'tickets' => $tickets->toArray() + 'tickets' => $tickets->toArray(), + 'verified' => !$user->verificationToken ]); } } \ No newline at end of file diff --git a/server/controllers/user/signup.php b/server/controllers/user/signup.php index 9c642d11..baca69b4 100644 --- a/server/controllers/user/signup.php +++ b/server/controllers/user/signup.php @@ -9,6 +9,7 @@ class SignUpController extends Controller { private $userEmail; private $userName; private $userPassword; + private $verificationToken; public function validations() { return [ @@ -64,20 +65,19 @@ class SignUpController extends Controller { $this->userName = Controller::request('name'); $this->userEmail = Controller::request('email'); $this->userPassword = Controller::request('password'); + $this->verificationToken = Hashing::generateRandomToken(); } public function createNewUserAndRetrieveId() { $userInstance = new User(); - $token = Hashing::generateRandomToken(); - $userInstance->setProperties([ 'name' => $this->userName, 'signupDate' => Date::getCurrentDate(), 'tickets' => 0, 'email' => $this->userEmail, 'password' => Hashing::hashPassword($this->userPassword), - 'verificationToken' => $token + 'verificationToken' => $this->verificationToken ]); return $userInstance->store(); @@ -88,7 +88,8 @@ class SignUpController extends Controller { $mailSender->setTemplate(MailTemplate::USER_SIGNUP, [ 'to' => $this->userEmail, - 'name' => $this->userName + 'name' => $this->userName, + 'verificationToken' => $this->verificationToken ]); $mailSender->send(); diff --git a/server/data/mail-templates/user-signup-en.html b/server/data/mail-templates/user-signup-en.html index 371a2441..66a0219a 100644 --- a/server/data/mail-templates/user-signup-en.html +++ b/server/data/mail-templates/user-signup-en.html @@ -1,4 +1,5 @@ <div> Welcome, {{name}} to our support center, - your email is {{to}} + your email is {{to}}, + your token is {{verificationToken}} </div> diff --git a/server/data/mail-templates/user-signup-es.html b/server/data/mail-templates/user-signup-es.html index 8af75ae0..da8e2c84 100644 --- a/server/data/mail-templates/user-signup-es.html +++ b/server/data/mail-templates/user-signup-es.html @@ -1,4 +1,5 @@ <div> Bienvenido, {{name}} a nuestro centro de soporte, - tu email es {{to}} + tu email es {{to}}, + tu codigo de verificacion es {{verificationToken}} </div> \ No newline at end of file diff --git a/tests/system/edit-settings.rb b/tests/system/edit-settings.rb index 0d9c1d02..f8755345 100644 --- a/tests/system/edit-settings.rb +++ b/tests/system/edit-settings.rb @@ -12,11 +12,7 @@ describe'system/edit-settings' do "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', + "no-reply-email" => 'testemail@hotmail.com' }) (result['status']).should.equal('success') @@ -39,18 +35,6 @@ describe'system/edit-settings' do row = $database.getRow('setting', 'no-reply-email', 'name') (row['value']).should.equal('testemail@hotmail.com') - row = $database.getRow('setting', 'smtp-host', 'name') - (row['value']).should.equal('www.opensupports.com') - - row = $database.getRow('setting', 'smtp-port', 'name') - (row['value']).should.equal('18') - - row = $database.getRow('setting', 'smtp-user', 'name') - (row['value']).should.equal('admin') - - row = $database.getRow('setting', 'smtp-pass', 'name') - (row['value']).should.equal('pass1234') - request('/user/logout') end end From 5b68997437241e86d1d13b141617260740a44a51 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 23 Dec 2016 04:28:32 -0300 Subject: [PATCH 15/19] Ivan - Add error messages for admin users view [skip ci] --- .../panel/users/admin-panel-ban-users.js | 51 +++++++++++++++---- .../panel/users/admin-panel-ban-users.scss | 8 +-- .../panel/users/admin-panel-list-users.js | 14 ++++- .../panel/users/admin-panel-view-user.js | 8 +-- client/src/core-components/message.js | 2 +- client/src/data/fixtures/user-fixtures.js | 2 +- client/src/data/languages/en.js | 4 ++ 7 files changed, 68 insertions(+), 21 deletions(-) diff --git a/client/src/app/admin/panel/users/admin-panel-ban-users.js b/client/src/app/admin/panel/users/admin-panel-ban-users.js index aac27f29..b4b215bc 100644 --- a/client/src/app/admin/panel/users/admin-panel-ban-users.js +++ b/client/src/app/admin/panel/users/admin-panel-ban-users.js @@ -11,24 +11,35 @@ import Button from 'core-components/button'; import SubmitButton from 'core-components/submit-button'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; +import Message from 'core-components/message'; class AdminPanelBanUsers extends React.Component { state = { loadingList: true, loadingForm: false, + listError: false, + addBanStatus: 'none', emails: [], filteredEmails: [] }; componentDidMount() { - this.retrieveEmails() + this.retrieveEmails(); } render() { return ( <div className="admin-panel-ban-users row"> <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"> <SearchBox className="admin-panel-ban-users__search" onSearch={this.onSearch.bind(this)} searchOnType placeholder={i18n('SEARCH_EMAIL')}/> <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'}}/> <SubmitButton>{i18n('BAN_EMAIL')}</SubmitButton> </Form> + {this.renderMessage()} </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() { return { loading: this.state.loadingList, @@ -94,7 +117,12 @@ class AdminPanelBanUsers extends React.Component { data: { email: form.email } - }).then(this.retrieveEmails.bind(this)); + }).then(() => { + this.setState({ + addBanStatus: 'success' + }); + this.retrieveEmails(); + }).catch(() => this.setState({addBanStatus: 'fail', loadingForm: false})); } onUnBanClick(email) { @@ -114,14 +142,17 @@ class AdminPanelBanUsers extends React.Component { API.call({ path: '/user/list-ban', data: {} - }).then((result) => { - this.setState({ - loadingList: false, - loadingForm: false, - emails: result.data, - filteredEmails: result.data - }); - }); + }).then(result => this.setState({ + listError: false, + loadingList: false, + loadingForm: false, + emails: result.data, + filteredEmails: result.data + })).catch(() => this.setState({ + listError: true, + loadingList: false, + loadingForm: false + })); } } diff --git a/client/src/app/admin/panel/users/admin-panel-ban-users.scss b/client/src/app/admin/panel/users/admin-panel-ban-users.scss index 799e21fe..379b437c 100644 --- a/client/src/app/admin/panel/users/admin-panel-ban-users.scss +++ b/client/src/app/admin/panel/users/admin-panel-ban-users.scss @@ -3,10 +3,6 @@ .admin-panel-ban-users { padding: 0 20px; - &__email-list { - - } - &__search { margin-bottom: 20px; } @@ -33,4 +29,8 @@ &__input { display: inline-block; } + + &__form-message { + margin-top: 20px; + } } \ No newline at end of file diff --git a/client/src/app/admin/panel/users/admin-panel-list-users.js b/client/src/app/admin/panel/users/admin-panel-list-users.js index 2b23c374..0b2896ec 100644 --- a/client/src/app/admin/panel/users/admin-panel-list-users.js +++ b/client/src/app/admin/panel/users/admin-panel-list-users.js @@ -8,6 +8,7 @@ import Header from 'core-components/header'; import Table from 'core-components/table'; import SearchBox from 'core-components/search-box'; import Button from 'core-components/button'; +import Message from 'core-components/message'; class AdminPanelListUsers extends React.Component { @@ -16,6 +17,7 @@ class AdminPanelListUsers extends React.Component { users: [], orderBy: 'id', desc: true, + error: false, page: 1, pages: 1 }; @@ -34,7 +36,7 @@ class AdminPanelListUsers extends React.Component { <div className="admin-panel-list-users"> <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)} /> - <Table {...this.getTableProps()} /> + {(this.state.error) ? <Message type="error">{i18n('ERROR_RETRIEVING_USERS')}</Message> : <Table {...this.getTableProps()}/>} </div> ); } @@ -144,7 +146,7 @@ class AdminPanelListUsers extends React.Component { API.call({ path: '/user/get-users', data: data - }).then(this.onUsersRetrieved.bind(this)); + }).then(this.onUsersRetrieved.bind(this)).catch(this.onUsersRejected.bind(this)); } onUsersRetrieved(result) { @@ -154,6 +156,14 @@ class AdminPanelListUsers extends React.Component { users: result.data.users, orderBy: result.data.orderBy, desc: (result.data.desc === '1'), + error: false, + loading: false + }); + } + + onUsersRejected() { + this.setState({ + error: true, loading: false }); } diff --git a/client/src/app/admin/panel/users/admin-panel-view-user.js b/client/src/app/admin/panel/users/admin-panel-view-user.js index 3b56f132..b18060a2 100644 --- a/client/src/app/admin/panel/users/admin-panel-view-user.js +++ b/client/src/app/admin/panel/users/admin-panel-view-user.js @@ -5,11 +5,13 @@ import {browserHistory} from 'react-router'; import i18n from 'lib-app/i18n'; 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 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 { state = { @@ -43,7 +45,7 @@ class AdminPanelViewUser extends React.Component { renderInvalid() { return ( <div className="admin-panel-view-user__invalid"> - {i18n('INVALID_USER')} + <Message type="error">{i18n('INVALID_USER')}</Message> </div> ); } diff --git a/client/src/core-components/message.js b/client/src/core-components/message.js index db56adfa..64f75a7a 100644 --- a/client/src/core-components/message.js +++ b/client/src/core-components/message.js @@ -29,7 +29,7 @@ class Message extends React.Component { getAnimationProps() { return { defaultStyle: { - opacity: spring(0, [100, 30]) + opacity: 0 }, style: { opacity: spring(1, [100, 30]) diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index c23622e3..bf795b56 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -147,7 +147,7 @@ module.exports = [ email: 'kurt@currycurrylady.hs', tickets: _.times(13).map(() => { return { - ticketNumber: '1185510000', + ticketNumber: '118551', title: 'Lorem ipsum door', content: 'I had a problem with the installation of the php server', department: { diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index dfd41f50..0f3e9c88 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -116,6 +116,7 @@ export default { 'UPDATE_DEPARTMENT': 'Update department', 'TRANSFER_TICKETS_TO': 'Transfer tickets to', 'COMMENTS': 'Comments', + 'EMAIL_BANNED_SUCCESSFULLY': 'Email has been banned successfully', //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.', @@ -161,6 +162,9 @@ export default { 'NO_PERMISSION': 'You\'ve no permission to access to this page.', '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.', //MESSAGES 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', From 0fd70ec4419d5d79a1cf2d8c03fb9ce571f93c95 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Fri, 23 Dec 2016 17:07:21 -0300 Subject: [PATCH 16/19] Ivan - Add error messages for admin articles view [skip ci] --- client/src/app-components/articles-list.js | 7 +++++++ client/src/app-components/people-list.js | 2 +- client/src/data/languages/en.js | 1 + client/src/reducers/articles-reducer.js | 11 +++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/client/src/app-components/articles-list.js b/client/src/app-components/articles-list.js index ed77d30c..586babc3 100644 --- a/client/src/app-components/articles-list.js +++ b/client/src/app-components/articles-list.js @@ -11,6 +11,7 @@ import TopicEditModal from 'app-components/topic-edit-modal'; import Loading from 'core-components/loading'; import Button from 'core-components/button'; import Icon from 'core-components/icon'; +import Message from 'core-components/message'; class ArticlesList extends React.Component { @@ -18,6 +19,7 @@ class ArticlesList extends React.Component { editable: React.PropTypes.bool, articlePath: React.PropTypes.string, loading: React.PropTypes.bool, + errored: React.PropTypes.bool, topics: React.PropTypes.array, retrieveOnMount: React.PropTypes.bool }; @@ -34,6 +36,10 @@ class ArticlesList extends React.Component { } render() { + if(this.props.errored) { + return <Message type="error">{i18n('ERROR_RETRIEVING_ARTICLES')}</Message>; + } + return (this.props.loading) ? <Loading /> : this.renderContent(); } @@ -84,6 +90,7 @@ class ArticlesList extends React.Component { export default connect((store) => { return { topics: store.articles.topics, + errored: store.articles.errored, loading: store.articles.loading }; })(ArticlesList); diff --git a/client/src/app-components/people-list.js b/client/src/app-components/people-list.js index 70a37feb..6a636bd3 100644 --- a/client/src/app-components/people-list.js +++ b/client/src/app-components/people-list.js @@ -10,7 +10,7 @@ class PeopleList extends React.Component { static propTypes = { list: React.PropTypes.arrayOf(React.PropTypes.shape({ profilePic: React.PropTypes.string, - name: React.PropTypes.string, + name: React.PropTypes.node, assignedTickets: React.PropTypes.number, closedTickets: React.PropTypes.number, lastLogin: React.PropTypes.number diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 0f3e9c88..7ab3af00 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -165,6 +165,7 @@ export default { '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 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', diff --git a/client/src/reducers/articles-reducer.js b/client/src/reducers/articles-reducer.js index fcc5a537..5bdf9ace 100644 --- a/client/src/reducers/articles-reducer.js +++ b/client/src/reducers/articles-reducer.js @@ -9,6 +9,7 @@ class ArticlesReducer extends Reducer { return { retrieved: false, loading: true, + errored: false, topics: [] }; } @@ -16,6 +17,7 @@ class ArticlesReducer extends Reducer { getTypeHandlers() { return { 'GET_ARTICLES_FULFILLED': this.onArticlesRetrieved, + 'GET_ARTICLES_REJECTED': this.onArticlesRejected, 'INIT_ARTICLES': this.onInitArticles }; } @@ -26,10 +28,19 @@ class ArticlesReducer extends Reducer { return _.extend({}, state, { retrieved: true, loading: false, + errored: false, topics: payload.data }); } + onArticlesRejected(state) { + return _.extend({}, state, { + retrieved: true, + loading: false, + errored: true + }); + } + onInitArticles(state) { let topics = SessionStore.getItem('topics'); From 01748a2a259fb7460d19f47231a154f6f0ec7aba Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Sat, 24 Dec 2016 16:53:33 -0300 Subject: [PATCH 17/19] Ivan - Update redbean version to support php7 [skip ci] --- server/composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/composer.json b/server/composer.json index cd6c91e4..71e09ba2 100644 --- a/server/composer.json +++ b/server/composer.json @@ -1,10 +1,10 @@ { "require": { "slim/slim": "~2.0", - "gabordemooij/redbean": "~4.2", "respect/validation": "^1.1", "phpmailer/phpmailer": "^5.2", - "google/recaptcha": "~1.1" + "google/recaptcha": "~1.1", + "gabordemooij/redbean": "^4.3" }, "require-dev": { "phpunit/phpunit": "5.0.*" From cb7885f5d9abe8b8906ee535b34bee778a9ee3ff Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Mon, 26 Dec 2016 01:07:40 -0300 Subject: [PATCH 18/19] Ivan - Add messages to staff editor [skip ci] --- .../src/app/admin/panel/staff/staff-editor.js | 50 ++++++++++++++++--- .../app/admin/panel/staff/staff-editor.scss | 4 ++ client/src/data/languages/en.js | 5 +- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/client/src/app/admin/panel/staff/staff-editor.js b/client/src/app/admin/panel/staff/staff-editor.js index 4a8905a7..c8b663cf 100644 --- a/client/src/app/admin/panel/staff/staff-editor.js +++ b/client/src/app/admin/panel/staff/staff-editor.js @@ -9,6 +9,7 @@ import TicketList from 'app-components/ticket-list'; 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'; class StaffEditor extends React.Component { static propTypes = { @@ -26,12 +27,14 @@ class StaffEditor extends React.Component { state = { email: this.props.email, level: this.props.level - 1, + message: null, departments: this.getUserDepartments() }; render() { return ( <div className="staff-editor"> + {(this.state.message) ? this.renderMessage() : null} <div className="row"> <div className="col-md-4"> <div className="staff-editor__card"> @@ -67,12 +70,12 @@ class StaffEditor extends React.Component { </div> <div className="col-md-8"> <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'}}/> <SubmitButton size="medium" className="staff-editor__submit-button">{i18n('UPDATE_EMAIL')}</SubmitButton> </Form> <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="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> @@ -100,11 +103,36 @@ class StaffEditor extends React.Component { ); } + 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() { return ( <div> <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={{ items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}], size: 'large' @@ -117,7 +145,7 @@ class StaffEditor extends React.Component { renderDepartmentsForm() { 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()}} /> <SubmitButton size="medium">{i18n('UPDATE_DEPARTMENTS')}</SubmitButton> </Form> @@ -171,7 +199,7 @@ class StaffEditor extends React.Component { return SessionStore.getDepartments().map(department => department.name); } - onSubmit(form) { + onSubmit(eventType, form) { let departments; if(form.departments) { @@ -189,7 +217,17 @@ class StaffEditor extends React.Component { level: (form.level !== undefined) ? form.level + 1 : null, departments: departments && JSON.stringify(departments) } - }).then(this.props.onChange); + }).then((result) => { + window.scrollTo(0,0); + this.setState({message: eventType}); + + if(this.props.onChange) { + this.props.onChange(); + } + }).catch((result) => { + window.scrollTo(0,0); + this.setState({message: 'FAIL'}); + }); } } diff --git a/client/src/app/admin/panel/staff/staff-editor.scss b/client/src/app/admin/panel/staff/staff-editor.scss index 43883a9d..7c070a7c 100644 --- a/client/src/app/admin/panel/staff/staff-editor.scss +++ b/client/src/app/admin/panel/staff/staff-editor.scss @@ -122,4 +122,8 @@ &__separator { margin: 3px 0; } + + &__message { + margin-bottom: 20px; + } } \ No newline at end of file diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 7ab3af00..3b6b6d54 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -179,5 +179,8 @@ export default { 'WILL_LOSE_CHANGES': 'You haven\'t save. Your changes will be lost.', '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.', - '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.' }; From 42cd02543393b057c6b3737e9e853ebe3bf89508 Mon Sep 17 00:00:00 2001 From: ivan <ivan@opensupports.com> Date: Mon, 26 Dec 2016 01:30:04 -0300 Subject: [PATCH 19/19] Ivan - Add delete button for staff [skip ci] --- .../panel/staff/admin-panel-view-staff.js | 9 ++++- .../src/app/admin/panel/staff/staff-editor.js | 38 +++++++++++++++++-- .../app/admin/panel/staff/staff-editor.scss | 13 +++++++ client/src/data/fixtures/staff-fixtures.js | 10 +++++ client/src/data/languages/en.js | 6 ++- 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/client/src/app/admin/panel/staff/admin-panel-view-staff.js b/client/src/app/admin/panel/staff/admin-panel-view-staff.js index a1d5d933..3e5a0970 100644 --- a/client/src/app/admin/panel/staff/admin-panel-view-staff.js +++ b/client/src/app/admin/panel/staff/admin-panel-view-staff.js @@ -1,5 +1,5 @@ import React from 'react'; -import {connect} from 'react-redux'; +import {browserHistory} from 'react-router'; import _ from 'lodash'; import i18n from 'lib-app/i18n'; @@ -36,7 +36,8 @@ class AdminPanelViewStaff extends React.Component { getProps() { 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 }); } + + onDelete() { + browserHistory.push('/admin/panel/staff/staff-members'); + } } export default AdminPanelViewStaff; \ No newline at end of file diff --git a/client/src/app/admin/panel/staff/staff-editor.js b/client/src/app/admin/panel/staff/staff-editor.js index c8b663cf..e5513e41 100644 --- a/client/src/app/admin/panel/staff/staff-editor.js +++ b/client/src/app/admin/panel/staff/staff-editor.js @@ -5,11 +5,13 @@ import i18n from 'lib-app/i18n'; import API from 'lib-app/api-call'; import SessionStore from 'lib-app/session-store'; import TicketList from 'app-components/ticket-list'; +import AreYouSure from 'app-components/are-you-sure'; 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 Button from 'core-components/button'; class StaffEditor extends React.Component { static propTypes = { @@ -21,7 +23,8 @@ class StaffEditor extends React.Component { level: React.PropTypes.number.isRequired, tickets: React.PropTypes.array.isRequired, departments: React.PropTypes.array.isRequired, - onChange: React.PropTypes.func + onChange: React.PropTypes.func, + onDelete: React.PropTypes.func }; state = { @@ -99,6 +102,7 @@ class StaffEditor extends React.Component { </div> </div> {(this.props.tickets) ? this.renderTickets() : null} + {(!this.props.myAccount) ? this.renderDelete() : null} </div> ); } @@ -172,6 +176,22 @@ class StaffEditor extends React.Component { </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() { return { @@ -217,14 +237,26 @@ class StaffEditor extends React.Component { level: (form.level !== undefined) ? form.level + 1 : null, departments: departments && JSON.stringify(departments) } - }).then((result) => { + }).then(() => { window.scrollTo(0,0); this.setState({message: eventType}); if(this.props.onChange) { this.props.onChange(); } - }).catch((result) => { + }).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'}); }); diff --git a/client/src/app/admin/panel/staff/staff-editor.scss b/client/src/app/admin/panel/staff/staff-editor.scss index 7c070a7c..aaf998d5 100644 --- a/client/src/app/admin/panel/staff/staff-editor.scss +++ b/client/src/app/admin/panel/staff/staff-editor.scss @@ -126,4 +126,17 @@ &__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; + } } \ No newline at end of file diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js index dd6cae10..0de75e1f 100644 --- a/client/src/data/fixtures/staff-fixtures.js +++ b/client/src/data/fixtures/staff-fixtures.js @@ -1047,5 +1047,15 @@ module.exports = [ data: {} }; } + }, + { + path: '/staff/delete', + time: 100, + response: function () { + return { + status: 'success', + data: {} + }; + } } ]; diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 3b6b6d54..ae1a3ebf 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -116,7 +116,7 @@ export default { 'UPDATE_DEPARTMENT': 'Update department', 'TRANSFER_TICKETS_TO': 'Transfer tickets to', 'COMMENTS': 'Comments', - 'EMAIL_BANNED_SUCCESSFULLY': 'Email has been banned successfully', + 'DELETE_STAFF_MEMBER': 'Delete staff member', //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.', @@ -182,5 +182,7 @@ export default { '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.' + '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.' };