Merged in fixes-deploy-bloque-3 (pull request #134)

Fixes deploy bloque 3
This commit is contained in:
Ivan Diaz 2017-03-04 22:08:53 +00:00
commit 751876bd11
100 changed files with 1743 additions and 468 deletions

View File

@ -55,24 +55,24 @@
"dependencies": {
"app-module-path": "^1.0.3",
"chart.js": "^2.4.0",
"classnames": "^2.1.3",
"draft-js": "^0.8.1",
"draft-js-export-html": "^0.4.0",
"classnames": "^2.2.5",
"draft-js": "^0.10.0",
"draft-js-export-html": "^0.5.2",
"jquery": "^2.1.4",
"keycode": "^2.1.4",
"localStorage": "^1.0.3",
"lodash": "^3.10.0",
"messageformat": "^0.2.2",
"react": "^15.0.1",
"react": "^15.4.2",
"react-chartjs-2": "^2.0.0",
"react-document-title": "^1.0.2",
"react-dom": "^15.0.1",
"react-dom": "^15.4.2",
"react-draft-wysiwyg": "^1.7.6",
"react-google-recaptcha": "^0.5.2",
"react-motion": "^0.4.7",
"react-redux": "^4.4.5",
"react-router": "^2.4.0",
"react-router-redux": "^4.0.5",
"react-rte-browserify": "^0.5.0",
"redux": "^3.5.2",
"redux-promise-middleware": "^3.3.2"
}

View File

@ -102,6 +102,7 @@ describe('Session Actions,', function () {
});
APICallMock.call.reset();
storeMock.dispatch.reset();
sessionStoreMock.isLoggedIn.returns(true)
});
after(function () {
@ -114,7 +115,7 @@ describe('Session Actions,', function () {
}));
});
it('should return CHECK_SESSION and dispatch SESSION_ACTIVE if session is active', function () {
it('should return CHECK_SESSION and dispatch SESSION_CHECKED if session is active', function () {
APICallMock.call.returns({
then: function (resolve) {
resolve({

View File

@ -1,9 +1,9 @@
export default {
openModal(content) {
openModal(config) {
return {
type: 'OPEN_MODAL',
payload: content
payload: config
}
},

View File

@ -4,20 +4,36 @@ import sessionStore from 'lib-app/session-store';
import store from 'app/store';
export default {
login(loginData) {
return {
type: 'LOGIN',
payload: API.call({
path: '/user/login',
data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
if(result.data.staff) {
store.dispatch(AdminDataActions.retrieveCustomResponses());
}
payload: new Promise((resolve, reject) => {
let loginCall = () => {
API.call({
path: '/user/login',
data: loginData
}).then((result) => {
store.dispatch(this.getUserData(result.data.userId, result.data.token, result.data.staff));
return result;
if(result.data.staff) {
store.dispatch(AdminDataActions.retrieveCustomResponses());
}
resolve(result);
}).catch((result) => {
if(result.message === 'SESSION_EXISTS') {
API.call({
path: '/user/logout',
data: {}
}).then(loginCall);
} else {
reject(result);
}
})
};
loginCall();
})
};
},
@ -81,23 +97,31 @@ export default {
initSession() {
return {
type: 'CHECK_SESSION',
payload: API.call({
path: '/user/check-session',
data: {}
}).then((result) => {
if (!result.data.sessionActive) {
if (sessionStore.isRememberDataExpired()) {
payload: new Promise((resolve, reject) => {
API.call({
path: '/user/check-session',
data: {}
}).then((result) => {
if (!result.data.sessionActive) {
if (sessionStore.isRememberDataExpired()) {
store.dispatch({
type: 'LOGOUT_FULFILLED'
});
} else {
store.dispatch(this.autoLogin());
}
resolve(result);
} else if(sessionStore.isLoggedIn()) {
store.dispatch({
type: 'LOGOUT_FULFILLED'
type: 'SESSION_CHECKED'
});
resolve(result);
} else {
store.dispatch(this.autoLogin());
reject(result);
}
} else {
store.dispatch({
type: 'SESSION_CHECKED'
});
}
});
})
}
}

View File

@ -68,7 +68,7 @@ class ActivityRow extends React.Component {
</Link>
</span>
<span className="activity-row__message"> {i18n('ACTIVITY_' + this.props.type)} </span>
{_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : null}
{_.includes(ticketRelatedTypes, this.props.type) ? this.renderTicketNumber() : this.props.to}
<span className="separator" />
</div>
);

View File

@ -19,8 +19,8 @@
&__ticket-link {
}
}
.separator {
margin: 15px;
}
.separator {
margin: 15px;
}
}

View File

@ -1,9 +1,12 @@
import React from 'react';
import i18n from 'lib-app/i18n';
import ModalContainer from 'app-components/modal-container';
import Button from 'core-components/button';
import Input from 'core-components/input';
import ModalContainer from 'app-components/modal-container';
import Icon from 'core-components/icon';
class AreYouSure extends React.Component {
static propTypes = {
@ -26,7 +29,8 @@ class AreYouSure extends React.Component {
static openModal(description, onYes, type) {
ModalContainer.openModal(
<AreYouSure description={description} onYes={onYes} type={type}/>
<AreYouSure description={description} onYes={onYes} type={type}/>,
true
);
}
@ -40,21 +44,25 @@ class AreYouSure extends React.Component {
<div className="are-you-sure__header" id="are-you-sure__header">
{i18n('ARE_YOU_SURE')}
</div>
<span className="are-you-sure__close-icon" onClick={this.onNo.bind(this)}>
<Icon name="times" size="2x"/>
</span>
<div className="are-you-sure__description" id="are-you-sure__description">
{this.props.description || (this.props.type === 'secure' && i18n('PLEASE_CONFIRM_PASSWORD'))}
</div>
{(this.props.type === 'secure') ? this.renderPassword() : null}
<span className="separator" />
<div className="are-you-sure__buttons">
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
</Button>
</div>
<div className="are-you-sure__no-button">
<Button type="link" size="auto" onClick={this.onNo.bind(this)} tabIndex="2">
{i18n('CANCEL')}
</Button>
</div>
<div className="are-you-sure__yes-button">
<Button type="secondary" size="small" onClick={this.onYes.bind(this)} ref="yesButton" tabIndex="2">
{i18n('YES')}
</Button>
</div>
</div>
</div>
);
@ -62,7 +70,7 @@ class AreYouSure extends React.Component {
renderPassword() {
return (
<Input className="are-you-sure__password" password placeholder="password" name="password" value={this.state.password} onChange={this.onPasswordChange.bind(this)} onKeyDown={this.onInputKeyDown.bind(this)}/>
<Input className="are-you-sure__password" password placeholder="password" name="password" ref="password" size="medium" value={this.state.password} onChange={this.onPasswordChange.bind(this)} onKeyDown={this.onInputKeyDown.bind(this)}/>
);
}
@ -79,7 +87,11 @@ class AreYouSure extends React.Component {
}
onYes() {
if (this.props.type === 'default' || this.state.password){
if (this.props.type === 'secure' && !this.state.password) {
this.refs.password.focus()
}
if (this.props.type === 'default' || this.state.password) {
this.closeModal();
if (this.props.onYes) {

View File

@ -1,25 +1,30 @@
@import "../scss/vars";
.are-you-sure {
width: 400px;
text-align: center;
width: 800px;
text-align: left;
&__header {
color: $primary-red;
font-size: $font-size--xl;
background-color: $secondary-blue;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
color: white;
font-size: $font-size--lg;
font-weight: bold;
margin-bottom: 20px;
margin-bottom: 10px;
padding: 10px;
}
&__description {
color: $dark-grey;
font-size: $font-size--md;
margin-bottom: 50px;
padding: 14px 5% 0;
}
&__buttons {
margin: 0 auto;
margin-top: 10px;
padding-bottom: 10px;
text-align: right;
}
&__yes-button,
@ -30,7 +35,25 @@
&__password {
margin: 0 auto;
margin-top: -30px;
margin-bottom: 20px;
margin-top: 20px;
}
&__close-icon {
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
color: white;
}
.separator {
width: 90%;
margin: 30px auto;
}
}
@media screen and (max-width: 800px) {
.are-you-sure {
width: auto;
}
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import keyCode from 'keycode';
import classNames from 'classnames';
import store from 'app/store';
import ModalActions from 'actions/modal-actions';
@ -8,11 +9,12 @@ import Modal from 'core-components/modal';
class ModalContainer extends React.Component {
static openModal(content) {
static openModal(content, noPadding) {
store.dispatch(
ModalActions.openModal(
content
)
ModalActions.openModal({
content,
noPadding
})
);
}
@ -48,7 +50,7 @@ class ModalContainer extends React.Component {
renderModal() {
return (
<Modal content={this.props.modal.content} />
<Modal content={this.props.modal.content} noPadding={this.props.modal.noPadding}/>
);
}

View File

@ -13,7 +13,7 @@ class PeopleList extends React.Component {
name: React.PropTypes.node,
assignedTickets: React.PropTypes.number,
closedTickets: React.PropTypes.number,
lastLogin: React.PropTypes.number
lastLogin: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
})),
pageSize: React.PropTypes.number,
page: React.PropTypes.number,

View File

@ -134,10 +134,10 @@ class Stats extends React.Component {
API.call({
path: '/system/get-stats',
data: this.getApiCallData(periodName)
}).then(this.onRetrieveSuccess.bind(this, period));
}).then(this.onRetrieveSuccess.bind(this));
}
onRetrieveSuccess(period, result) {
onRetrieveSuccess(result) {
let newStats = this.getDefaultStats();
let newStrokes = this.getStrokes().map((name) => {
@ -149,7 +149,7 @@ class Stats extends React.Component {
let realPeriod = result.data.length / this.getStrokes().length;
result.data.map((item) => {
result.data.reverse().map((item) => {
newStats[item.type] += item.value * 1;
newStrokes[ ID[item.type] ].values.push({

View File

@ -46,9 +46,11 @@ class TicketEvent extends React.Component {
}
renderStaffPic() {
let profilePicName = this.props.author.profilePic;
return (
<div className="ticket-event__staff-pic">
<img src={this.props.author.profilePic} className="ticket-event__staff-pic-img" />
<img src={(profilePicName) ? API.getFileLink(profilePicName) : (API.getURL() + '/images/profile.png')} className="ticket-event__staff-pic-img" />
</div>
);
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import _ from 'lodash';
import RichTextEditor from 'react-rte-browserify';
import {connect} from 'react-redux';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -16,6 +16,7 @@ import DropDown from 'core-components/drop-down';
import Button from 'core-components/button';
import Message from 'core-components/message';
import Icon from 'core-components/icon';
import TextEditor from 'core-components/text-editor';
class TicketViewer extends React.Component {
static propTypes = {
@ -37,7 +38,7 @@ class TicketViewer extends React.Component {
state = {
loading: false,
commentValue: RichTextEditor.createEmptyValue(),
commentValue: TextEditor.createEmpty(),
commentEdited: false
};
@ -178,7 +179,7 @@ class TicketViewer extends React.Component {
renderTicketEvent(options, index) {
return (
<TicketEvent {...options} key={index} />
<TicketEvent {...options} author={(!_.isEmpty(options.author)) ? options.author : this.props.ticket.author} key={index} />
);
}
@ -190,7 +191,7 @@ class TicketViewer extends React.Component {
<div className="ticket-viewer__response-field row">
<Form {...this.getCommentFormProps()}>
<FormField name="content" validation="TEXT_AREA" required field="textarea" />
<FormField name="file" field="file"/>
{(this.props.allowAttachments) ? <FormField name="file" field="file"/> : null}
<SubmitButton>{i18n('RESPOND_TICKET')}</SubmitButton>
</Form>
</div>
@ -304,7 +305,7 @@ class TicketViewer extends React.Component {
onCustomResponsesChanged({index}) {
let replaceContentWithCustomResponse = () => {
this.setState({
commentValue: RichTextEditor.createValueFromString(this.props.customResponses[index-1].content || '', 'html'),
commentValue: TextEditor.getEditorStateFromHTML(this.props.customResponses[index-1].content || ''),
commentEdited: false
});
};
@ -333,7 +334,9 @@ class TicketViewer extends React.Component {
onCommentSuccess() {
this.setState({
loading: false,
commentError: false
commentValue: TextEditor.createEmpty(),
commentError: false,
commentEdited: false
});
this.onTicketModification();
@ -353,4 +356,9 @@ class TicketViewer extends React.Component {
}
}
export default TicketViewer;
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments'],
userSystemEnabled: store.config['user-system-enabled']
};
})(TicketViewer);

View File

@ -25,7 +25,7 @@ class AdminPanelStaffWidget extends React.Component {
</div>
</div>
<div className="admin-panel-staff-widget__profile-pic-wrapper">
<img className="admin-panel-staff-widget__profile-pic" src={(this.props.session.userProfilePic) ? API.getFileLink(this.props.session.userProfilePic) : (API.getURL() + '/images/logo.png')} />
<img className="admin-panel-staff-widget__profile-pic" src={(this.props.session.userProfilePic) ? API.getFileLink(this.props.session.userProfilePic) : (API.getURL() + '/images/profile.png')} />
</div>
</div>
);

View File

@ -2,7 +2,6 @@ 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';
import SessionStore from 'lib-app/session-store';
@ -17,6 +16,7 @@ import Button from 'core-components/button';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import TextEditor from 'core-components/text-editor';
class AdminPanelViewArticle extends React.Component {
@ -117,7 +117,7 @@ class AdminPanelViewArticle extends React.Component {
editable: true,
form: {
title: article.title,
content: RichTextEditor.createValueFromString(article.content, 'html')
content: TextEditor.getEditorStateFromHTML(article.content)
}
});
}

View File

@ -23,11 +23,11 @@ class AdminPanelMyAccount extends React.Component {
getEditorProps() {
return {
myAccount: true,
staffId: this.props.userId,
staffId: this.props.userId * 1,
name: this.props.userName,
email: this.props.userEmail,
profilePic: this.props.userProfilePic,
level: this.props.userLevel,
level: this.props.userLevel * 1,
departments: this.props.userDepartments,
onChange: () => this.props.dispatch(SessionActions.getUserData(null, null, true))
};

View File

@ -8,7 +8,7 @@ class AdminPanelStats extends React.Component {
render() {
return (
<div class="admin-panel-stats">
<div className="admin-panel-stats">
<Header title={i18n('STATISTICS')} description={i18n('STATISTICS_DESCRIPTION')}/>
<Stats type="general"/>
</div>

View File

@ -9,6 +9,7 @@ import AreYouSure from 'app-components/are-you-sure';
import ModalContainer from 'app-components/modal-container';
import Message from 'core-components/message';
import InfoTooltip from 'core-components/info-tooltip';
import Button from 'core-components/button';
import FileUploader from 'core-components/file-uploader';
import Header from 'core-components/header';
@ -21,6 +22,7 @@ class AdminPanelAdvancedSettings extends React.Component {
state = {
loading: true,
messageTitle: null,
messageType: '',
messageContent: '',
keyName: '',
@ -42,7 +44,9 @@ class AdminPanelAdvancedSettings extends React.Component {
<div className="col-md-12">
<div className="col-md-6">
<div className="admin-panel-advanced-settings__user-system-enabled">
<span className="admin-panel-advanced-settings__text">{i18n('ENABLE_USER_SYSTEM')}</span>
<span className="admin-panel-advanced-settings__text">
{i18n('ENABLE_USER_SYSTEM')} <InfoTooltip text={i18n('ENABLE_USER_SYSTEM_DESCRIPTION')} />
</span>
<ToggleButton className="admin-panel-advanced-settings__toggle-button" value={this.props.config['user-system-enabled']} onChange={this.onToggleButtonUserSystemChange.bind(this)}/>
</div>
</div>
@ -57,19 +61,17 @@ class AdminPanelAdvancedSettings extends React.Component {
<span className="separator" />
</div>
<div className="col-md-12">
<div className="col-md-3">
<div className="admin-panel-advanced-settings__text">{i18n('INCLUDE_USERS_VIA_CSV')}</div>
<div className="col-md-4">
<div className="admin-panel-advanced-settings__text">
{i18n('INCLUDE_USERS_VIA_CSV')} <InfoTooltip text={i18n('CSV_DESCRIPTION')} />
</div>
<FileUploader className="admin-panel-advanced-settings__button" text="Upload" onChange={this.onImportCSV.bind(this)}/>
</div>
<div className="col-md-3">
<div className="admin-panel-advanced-settings__text">{i18n('INCLUDE_DATABASE_VIA_SQL')}</div>
<FileUploader className="admin-panel-advanced-settings__button" text="Upload" onChange={this.onImportSQL.bind(this)}/>
</div>
<div className="col-md-3">
<div className="col-md-4">
<div className="admin-panel-advanced-settings__text">{i18n('BACKUP_DATABASE')}</div>
<Button className="admin-panel-advanced-settings__button" type="secondary" size="medium" onClick={this.onBackupDatabase.bind(this)}>Download</Button>
</div>
<div className="col-md-3">
<div className="col-md-4">
<div className="admin-panel-advanced-settings__text">{i18n('DELETE_ALL_USERS')}</div>
<Button className="admin-panel-advanced-settings__button" size="medium" onClick={this.onDeleteAllUsers.bind(this)}>Delete</Button>
</div>
@ -93,7 +95,7 @@ class AdminPanelAdvancedSettings extends React.Component {
renderMessage() {
return (
<Message type={this.state.messageType}>{this.state.messageContent}</Message>
<Message type={this.state.messageType} title={this.state.messageTitle}>{this.state.messageContent}</Message>
);
}
@ -197,10 +199,11 @@ class AdminPanelAdvancedSettings extends React.Component {
}).then(() => {
this.setState({
messageType: 'success',
messageTitle: null,
messageContent: this.props.config['user-system-enabled'] ? i18n('USER_SYSTEM_DISABLED') : i18n('USER_SYSTEM_ENABLED')
});
this.props.dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}
onAreYouSureRegistrationOk(password) {
@ -212,42 +215,39 @@ class AdminPanelAdvancedSettings extends React.Component {
}).then(() => {
this.setState({
messageType: 'success',
messageTitle: null,
messageContent: this.props.config['registration'] ? i18n('REGISTRATION_DISABLED') : i18n('REGISTRATION_ENABLED')
});
this.props.dispatch(ConfigActions.updateData());
}).catch(() => this.setState({messageType: 'error', messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_UPDATING_SETTINGS')}));
}
onImportCSV(event) {
AreYouSure.openModal(null, this.onAreYouSureCSVOk.bind(this, event.target.value), 'secure');
}
onImportSQL(event) {
AreYouSure.openModal(null, this.onAreYouSureSQLOk.bind(this, event.target.value), 'secure');
}
onAreYouSureCSVOk(file, password) {
API.call({
path: '/system/import-csv',
data: {
file: file,
password: password
}
})
.then(() => this.setState({messageType: 'success', messageContent: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION')}))
.catch(() => this.setState({messageType: 'error', messageContent: i18n('ERROR_IMPORTING_CSV_DESCRIPTION')}));
}
onAreYouSureSQLOk(file, password) {
API.call({
path: '/system/import-sql',
path: '/system/csv-import',
dataAsForm: true,
data: {
file: file,
password: password
}
})
.then(() => this.setState({messageType: 'success', messageContent: i18n('SUCCESS_IMPORTING_SQL_DESCRIPTION')}))
.catch(() => this.setState({messageType: 'error', messageContent: i18n('ERROR_IMPORTING_SQL_DESCRIPTION')}));
.then((result) => this.setState({
messageType: 'success',
messageTitle: i18n('SUCCESS_IMPORTING_CSV_DESCRIPTION'),
messageContent: (result.data.length) ? (
<div>
{i18n('ERRORS_FOUND')}
<ul>
{result.data.map((error) => <li>{error}</li>)}
</ul>
</div>
) : null
}))
.catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('INVALID_FILE')}));
}
onBackupDatabase() {
@ -275,8 +275,8 @@ class AdminPanelAdvancedSettings extends React.Component {
data: {
password: password
}
}).then(() => this.setState({messageType: 'success', messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
)).catch(() => this.setState({messageType: 'error', messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
}).then(() => this.setState({messageType: 'success', messageTitle: null, messageContent: i18n('SUCCESS_DELETING_ALL_USERS')}
)).catch(() => this.setState({messageType: 'error', messageTitle: null, messageContent: i18n('ERROR_DELETING_ALL_USERS')}));
}
}

View File

@ -1,6 +1,5 @@
import React from 'react';
import _ from 'lodash';
import RichTextEditor from 'react-rte-browserify';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -15,6 +14,7 @@ import Loading from 'core-components/loading';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import TextEditor from 'core-components/text-editor';
class AdminPanelEmailTemplates extends React.Component {
@ -28,7 +28,7 @@ class AdminPanelEmailTemplates extends React.Component {
language: 'en',
form: {
title: '',
content: RichTextEditor.createEmptyValue()
content: TextEditor.createEmpty()
}
};
@ -182,7 +182,7 @@ class AdminPanelEmailTemplates extends React.Component {
language = language || this.state.language;
form.title = (items[index] && items[index][language].subject) || '';
form.content = RichTextEditor.createValueFromString((items[index] && items[index][language].body) || '', 'html');
form.content = TextEditor.getEditorStateFromHTML((items[index] && items[index][language].body) || '');
this.setState({
selectedIndex: index,

View File

@ -167,7 +167,7 @@ class AdminPanelSystemPreferences extends React.Component {
'recaptcha-private': form.reCaptchaPrivate,
'url': form['url'],
'title': form['title'],
'layout': form['layout'] == 1 ? 'Full width' : 'Boxed',
'layout': form['layout'] ? 'full-width' : 'boxed',
'time-zone': form['time-zone'],
'no-reply-email': form['no-reply-email'],
'smtp-host': form['smtp-host'],
@ -213,15 +213,15 @@ class AdminPanelSystemPreferences extends React.Component {
'reCaptchaPrivate': result.data.reCaptchaPrivate,
'url': result.data['url'],
'title': result.data['title'],
'layout': result.data['layout'] == 'full-width' ? 1 : 0,
'layout': (result.data['layout'] == 'full-width') ? 1 : 0,
'time-zone': result.data['time-zone'],
'no-reply-email': result.data['no-reply-email'],
'smtp-host': result.data['smtp-host'],
'smtp-port': result.data['smtp-port'],
'smtp-user': result.data['smtp-user'],
'smtp-pass': '',
'maintenance-mode': result.data['maintenance-mode'],
'allow-attachments': result.data['allow-attachments'],
'maintenance-mode': !!(result.data['maintenance-mode'] * 1),
'allow-attachments': !!(result.data['allow-attachments'] * 1),
'max-size': result.data['max-size'],
'allowedLanguages': result.data.allowedLanguages.map(lang => (_.indexOf(languageKeys, lang))),
'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(languageKeys, lang)))

View File

@ -17,6 +17,10 @@ class AddStaffModal extends React.Component {
closeModal: React.PropTypes.func
};
static propTypes = {
onSuccess: React.PropTypes.func
};
state = {
loading: false,
errors: {},
@ -78,7 +82,13 @@ class AddStaffModal extends React.Component {
level: form.level + 1,
departments: JSON.stringify(departments)
}
}).then(this.context.closeModal).catch((result) => {
}).then(() => {
this.context.closeModal();
if(this.props.onSuccess) {
this.props.onSuccess();
}
}).catch((result) => {
this.setState({
loading: false,
error: result.message

View File

@ -1,7 +1,6 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import RichTextEditor from 'react-rte-browserify';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -31,7 +30,6 @@ class AdminPanelDepartments extends React.Component {
errors: {},
form: {
title: '',
content: RichTextEditor.createEmptyValue(),
language: 'en'
}
};

View File

@ -9,6 +9,7 @@
&__update-name-button {
float: left;
min-width: 156px;
}
&__optional-buttons {

View File

@ -26,10 +26,7 @@ class AdminPanelStaffMembers extends React.Component {
};
componentDidMount() {
API.call({
path: '/staff/get-all',
data: {}
}).then(this.onStaffRetrieved.bind(this));
this.retrieveStaffMembers();
}
render() {
@ -48,7 +45,7 @@ class AdminPanelStaffMembers extends React.Component {
}
onAddNewStaff() {
ModalContainer.openModal(<AddStaffModal />);
ModalContainer.openModal(<AddStaffModal onSuccess={this.retrieveStaffMembers.bind(this)} />);
}
getDepartmentDropdownProps() {
@ -71,13 +68,14 @@ class AdminPanelStaffMembers extends React.Component {
if(!this.state.selectedDepartment) {
staffList = this.state.staffList;
} else {
staffList = _.filter(this.state.staffList, (o) => {
return _.findIndex(o.departments, {id: this.state.selectedDepartment}) !== -1;
})
staffList = _.filter(this.state.staffList, (staff) => {
return _.findIndex(staff.departments, {id: this.state.selectedDepartment}) !== -1;
});
}
return staffList.map(staff => {
return _.extend({}, staff, {
profilePic: (staff.profilePic) ? API.getFileLink(staff.profilePic) : (API.getURL() + '/images/profile.png'),
name: (
<Link className="admin-panel-staff-members__link" to={'/admin/panel/staff/view-staff/' + staff.id}>
{staff.name}
@ -99,6 +97,13 @@ class AdminPanelStaffMembers extends React.Component {
return departments;
}
retrieveStaffMembers() {
API.call({
path: '/staff/get-all',
data: {}
}).then(this.onStaffRetrieved.bind(this));
}
onStaffRetrieved(result) {
if(result.status == 'success'){
this.setState({

View File

@ -1,9 +1,11 @@
import React from 'react';
import {browserHistory} from 'react-router';
import {connect} from 'react-redux';
import _ from 'lodash';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import SessionActions from 'actions/session-actions';
import StaffEditor from 'app/admin/panel/staff/staff-editor';
import Header from 'core-components/header';
@ -17,12 +19,7 @@ class AdminPanelViewStaff extends React.Component {
};
componentDidMount() {
API.call({
path: '/staff/get',
data: {
staffId: this.props.params.staffId
}
}).then(this.onStaffRetrieved.bind(this));
this.retrieveStaff();
}
render() {
@ -37,15 +34,29 @@ class AdminPanelViewStaff extends React.Component {
getProps() {
return _.extend({}, this.state.userData, {
staffId: this.props.params.staffId * 1,
onDelete: this.onDelete.bind(this)
onDelete: this.onDelete.bind(this),
onChange: this.retrieveStaff.bind(this)
});
}
retrieveStaff() {
API.call({
path: '/staff/get',
data: {
staffId: this.props.params.staffId
}
}).then(this.onStaffRetrieved.bind(this));
}
onStaffRetrieved(result) {
this.setState({
loading: false,
userData: result.data
});
if(this.props.userId == this.props.params.staffId) {
this.props.dispatch(SessionActions.getUserData(null, null, true))
}
}
onDelete() {
@ -53,4 +64,4 @@ class AdminPanelViewStaff extends React.Component {
}
}
export default AdminPanelViewStaff;
export default connect((store) => store.session)(AdminPanelViewStaff);

View File

@ -31,6 +31,10 @@ class StaffEditor extends React.Component {
onDelete: React.PropTypes.func
};
static defaultProps = {
tickets: []
};
state = {
email: this.props.email,
level: this.props.level - 1,
@ -73,7 +77,7 @@ class StaffEditor extends React.Component {
</div>
<label className={this.getPictureWrapperClass()}>
<div className="staff-editor__card-pic-background"></div>
<img className="staff-editor__card-pic" src={(this.props.profilePic) ? API.getFileLink(this.props.profilePic) : (API.getURL() + '/images/logo.png')} />
<img className="staff-editor__card-pic" src={(this.props.profilePic) ? API.getFileLink(this.props.profilePic) : (API.getURL() + '/images/profile.png')} />
{(this.state.loadingPicture) ? <Loading className="staff-editor__card-pic-loading" size="large"/> : <Icon className="staff-editor__card-pic-icon" name="upload" size="4x"/>}
<input className="staff-editor__image-uploader" type="file" multiple={false} accept="image/x-png,image/gif,image/jpeg" onChange={this.onProfilePicChange.bind(this)}/>
</label>

View File

@ -1,7 +1,6 @@
import React from 'react';
import _ from 'lodash';
import {connect} from 'react-redux';
import RichTextEditor from 'react-rte-browserify';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -18,6 +17,7 @@ import Loading from 'core-components/loading';
import Form from 'core-components/form';
import FormField from 'core-components/form-field';
import SubmitButton from 'core-components/submit-button';
import TextEditor from 'core-components/text-editor';
class AdminPanelCustomResponses extends React.Component {
static defaultProps = {
@ -31,7 +31,7 @@ class AdminPanelCustomResponses extends React.Component {
errors: {},
form: {
title: '',
content: RichTextEditor.createEmptyValue(),
content: TextEditor.createEmpty(),
language: 'en'
}
};
@ -203,7 +203,7 @@ class AdminPanelCustomResponses extends React.Component {
let form = _.clone(this.state.form);
form.title = (this.props.items[index] && this.props.items[index].name) || '';
form.content = RichTextEditor.createValueFromString((this.props.items[index] && this.props.items[index].content) || '', 'html');
form.content = TextEditor.getEditorStateFromHTML((this.props.items[index] && this.props.items[index].content) || '');
form.language = (this.props.items[index] && this.props.items[index].language) || 'en';
this.setState({

View File

@ -14,6 +14,7 @@ class AdminPanelViewTicket extends React.Component {
static propTypes = {
avoidSeen: React.PropTypes.bool,
onRetrieveFail: React.PropTypes.func,
assignmentAllowed: React.PropTypes.bool
};
@ -108,6 +109,10 @@ class AdminPanelViewTicket extends React.Component {
loading: false,
ticket: {}
});
if(this.props.onRetrieveFail) {
this.props.onRetrieveFail();
}
}
}

View File

@ -151,8 +151,8 @@ class AdminPanelListUsers extends React.Component {
onUsersRetrieved(result) {
this.setState({
page: result.data.page,
pages: result.data.pages,
page: result.data.page * 1,
pages: result.data.pages * 1,
users: result.data.users,
orderBy: result.data.orderBy,
desc: (result.data.desc === '1'),

View File

@ -46,6 +46,10 @@
}
}
.ticket-list__number {
text-align: center;
}
.signup-widget {
background-color: $very-light-grey;
}

View File

@ -153,9 +153,7 @@ let DemoPage = React.createClass({
title: 'ModalTrigger',
render: (
<Button onClick={function () {
ModalContainer.openModal(
<AreYouSure description="I confirm I want to perform this action." onYes={()=> {alert('yes');}} />
);
AreYouSure.openModal('I confirm I want to perform this action.', ()=> {alert('yes');}, 'secure')
}}>
Open Modal
</Button>

View File

@ -1,8 +1,8 @@
import React from 'react';
import _ from 'lodash';
import ReCAPTCHA from 'react-google-recaptcha';
import { browserHistory } from 'react-router';
import RichTextEditor from 'react-rte-browserify';
import {connect} from 'react-redux';
import {EditorState, convertToRaw} from 'draft-js';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
@ -33,7 +33,7 @@ class CreateTicketForm extends React.Component {
message: null,
form: {
title: '',
content: RichTextEditor.createEmptyValue(),
content: EditorState.createEmpty(),
departmentIndex: 0,
email: '',
name: '',
@ -59,11 +59,9 @@ class CreateTicketForm extends React.Component {
}}/>
</div>
<FormField label={i18n('CONTENT')} name="content" validation="TEXT_AREA" required field="textarea" />
<div className="create-ticket-form__file">
<FormField name="file" field="file" />
</div>
{(this.props.allowAttachments) ? this.renderFileUpload() : null}
{(!this.props.userLogged) ? this.renderCaptcha() : null}
<SubmitButton>Create Ticket</SubmitButton>
<SubmitButton>{i18n('CREATE_TICKET')}</SubmitButton>
</Form>
{this.renderMessage()}
</div>
@ -79,6 +77,14 @@ class CreateTicketForm extends React.Component {
);
}
renderFileUpload() {
return (
<div className="create-ticket-form__file">
<FormField name="file" field="file" />
</div>
);
}
renderCaptcha() {
return (
<div className="create-ticket-form__captcha">
@ -138,7 +144,7 @@ class CreateTicketForm extends React.Component {
store.dispatch(SessionActions.getUserData());
setTimeout(() => {browserHistory.push('/dashboard')}, 2000);
} else {
setTimeout(() => {browserHistory.push('/check-ticket/' + email + '/' + result.data.ticketNumber)}, 2000);
setTimeout(() => {browserHistory.push('/check-ticket/' + result.data.ticketNumber + '/' + email)}, 1000);
}
}
@ -150,4 +156,9 @@ class CreateTicketForm extends React.Component {
}
}
export default CreateTicketForm;
export default connect((store) => {
return {
allowAttachments: store.config['allow-attachments']
};
})(CreateTicketForm);

View File

@ -3,7 +3,6 @@ import React from 'react';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import ModalContainer from 'app-components/modal-container';
import AreYouSure from 'app-components/are-you-sure';
import Header from 'core-components/header';
@ -64,11 +63,11 @@ class DashboardEditProfilePage extends React.Component {
}
}
onSubmitEditEmail(formState) {
ModalContainer.openModal(<AreYouSure onYes={this.callEditEmailAPI.bind(this, formState)}/>);
AreYouSure.openModal(i18n('EMAIL_WILL_CHANGE'), this.callEditEmailAPI.bind(this, formState));
}
onSubmitEditPassword(formState) {
ModalContainer.openModal(<AreYouSure onYes={this.callEditPassAPI.bind(this, formState)}/>);
AreYouSure.openModal(i18n('PASSWORD_WILL_CHANGE'), this.callEditPassAPI.bind(this, formState));
}
callEditEmailAPI(formState){

View File

@ -81,7 +81,7 @@ class MainCheckTicketPage extends React.Component {
});
API.call({
path: '/ticket/get',
path: '/ticket/check',
data: {
captcha: captcha && captcha.getValue(),
ticketNumber: form.ticketNumber,

View File

@ -32,7 +32,7 @@ class MainLayoutHeader extends React.Component {
result = (
<div className="main-layout-header__login-links">
<Button type="clean" route={{to:'/'}}>{i18n('LOG_IN')}</Button>
{(this.props.config['registration']) ? <Button type="clean" route={{to:'/signup'}}>{i18n('SIGN_UP')}</Button> : null}
{(!!(this.props.config['registration'] * 1)) ? <Button type="clean" route={{to:'/signup'}}>{i18n('SIGN_UP')}</Button> : null}
</div>
);
}

View File

@ -1,4 +1,6 @@
import React from 'react';
import {browserHistory} from 'react-router';
import {connect} from 'react-redux';
import AdminPanelViewTicket from 'app/admin/panel/tickets/admin-panel-view-ticket'
@ -10,11 +12,22 @@ class MainViewTicketPage extends React.Component {
return (
<div className="main-view-ticket-page">
<Widget>
<AdminPanelViewTicket {...this.props} avoidSeen assignmentAllowed={false} />
<AdminPanelViewTicket {...this.props} avoidSeen assignmentAllowed={false} onRetrieveFail={this.onRetrieveFail.bind(this)} />
</Widget>
</div>
);
}
onRetrieveFail() {
if (!this.props.config['user-system-enabled']) {
setTimeout(() => {browserHistory.push('/check-ticket')}, 2000);
}
}
}
export default MainViewTicketPage;
export default connect((store) => {
return {
config: store.config
};
})(MainViewTicketPage);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,17 @@
import _ from 'lodash';
let Mock = ReactMock();
_.extend(Mock, {
createEmpty: stub().returns({editorState: true}),
getEditorStateFromHTML: stub().returns({editorState: true}),
getHTMLFromEditorState: stub().returns('HTML_CODE'),
isEditorState: (item) => {
return item.editorState;
}
});
export default Mock;

View File

@ -1,15 +1,13 @@
const Input = ReactMock();
const Checkbox = ReactMock();
const DropDown = ReactMock();
const TextEditor = ReactMock();
const RichTextEditor = require('react-rte-browserify');
const TextEditorMock = require('core-components/__mocks__/text-editor-mock');
const FormField = requireUnit('core-components/form-field', {
'core-components/input': Input,
'core-components/checkbox': Checkbox,
'core-components/drop-down': DropDown,
'core-components/text-editor': TextEditor
'core-components/text-editor': TextEditorMock
});
@ -21,7 +19,7 @@ describe('FormField component', function () {
'input': Input,
'checkbox': Checkbox,
'select': DropDown,
'textarea': TextEditor
'textarea': TextEditorMock
};
component = reRenderIntoDocument(
@ -32,10 +30,9 @@ describe('FormField component', function () {
describe('when calling static getDefaultValue', function () {
it('should return correct values', function () {
expect(FormField.getDefaultValue('input')).to.equal('');
expect(FormField.getDefaultValue('checkbox')).to.equal(false);
expect(FormField.getDefaultValue('select')).to.equal(0);
expect(FormField.getDefaultValue('textarea') instanceof RichTextEditor.EditorValue).to.equal(true);
expect(FormField.getDefaultValue('textarea')).to.equal(TextEditorMock.createEmpty());
});
});
@ -415,7 +412,7 @@ describe('FormField component', function () {
errored: true,
name: 'MOCK_NAME',
onBlur: component.props.onBlur,
required: true,
required: true
});
expect(innerField.props.value).to.deep.equal({value: 'VALUE_MOCk'});
});

View File

@ -1,12 +1,13 @@
// MOCKS
const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock');
const TextEditorMock = require('core-components/__mocks__/text-editor-mock');
const FormField = ReactMock();
const RichTextEditor = require('react-rte-browserify');
// COMPONENT
const Form = requireUnit('core-components/form', {
'lib-app/validations/validations-factory': ValidationFactoryMock,
'core-components/form-field': FormField
'core-components/form-field': FormField,
'core-components/text-editor': TextEditorMock
});
describe('Form component', function () {
@ -186,12 +187,11 @@ describe('Form component', function () {
expect(form.props.onSubmit).to.not.have.been.called;
});
it('should tranform RichTextEditor value to HTML', function () {
form.state.form.first = RichTextEditor.createEmptyValue();
form.state.form.first.toString = stub().returns('HTML_CODE');
it('should transform TextEdit value to HTML', function () {
form.state.form.first = TextEditorMock.createEmpty();
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
expect(form.state.form.first.toString).to.have.been.called;
expect(TextEditorMock.getHTMLFromEditorState).to.have.been.calledWith(form.state.form.first);
expect(form.props.onSubmit).to.have.been.calledWith({
first: 'HTML_CODE',
second: 'value2',

View File

@ -63,6 +63,7 @@ class Button extends React.Component {
delete props.route;
delete props.iconName;
delete props.type;
delete props.inverted;
return props;
}

View File

@ -1,5 +1,4 @@
import React from 'react';
import RichTextEditor from 'react-rte-browserify';
import classNames from 'classnames';
import _ from 'lodash';
@ -44,7 +43,7 @@ class FormField extends React.Component {
return [];
}
else if (field === 'textarea') {
return RichTextEditor.createEmptyValue();
return TextEditor.createEmpty();
}
else if (field === 'select') {
return 0;

View File

@ -1,12 +1,12 @@
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import RichTextEditor from 'react-rte-browserify';
import {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs';
import ValidationFactory from 'lib-app/validations/validations-factory';
import FormField from 'core-components/form-field';
import TextEditor from 'core-components/text-editor';
class Form extends React.Component {
@ -161,8 +161,8 @@ class Form extends React.Component {
event.preventDefault();
const form = _.mapValues(this.getFormValue(), (field) => {
if (field instanceof RichTextEditor.EditorValue) {
return field.toString('html');
if (TextEditor.isEditorState(field)) {
return TextEditor.getHTMLFromEditorState(field);
} else {
return field;
}

View File

@ -1,10 +1,12 @@
import React from 'react';
import classNames from 'classnames';
import {Motion, spring} from 'react-motion';
class Modal extends React.Component {
static propTypes = {
content: React.PropTypes.node
content: React.PropTypes.node,
noPadding: React.PropTypes.bool
};
render() {
@ -30,13 +32,22 @@ class Modal extends React.Component {
renderModal(animation) {
return (
<div className="modal" style={{opacity: animation.fade}}>
<div className={this.getClass()} style={{opacity: animation.fade}}>
<div className="modal__content" style={{transform: 'scale(' + animation.scale + ')'}}>
{this.props.content}
</div>
</div>
)
}
getClass() {
let classes = {
'modal': true,
'modal_no-padding': this.props.noPadding
};
return classNames(classes);
}
}
export default Modal;

View File

@ -18,4 +18,11 @@
padding: 50px;
box-shadow: 0 0 10px white;
}
&_no-padding {
.modal__content {
padding: 0;
}
}
}

View File

@ -1,6 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import RichTextEditor from 'react-rte-browserify';
import {Editor} from 'react-draft-wysiwyg';
import {EditorState, ContentState, convertFromHTML} from 'draft-js';
import {stateToHTML} from 'draft-js-export-html';
class TextEditor extends React.Component {
static propTypes = {
@ -8,16 +10,38 @@ class TextEditor extends React.Component {
onChange: React.PropTypes.func,
value: React.PropTypes.object
};
static createEmpty() {
return EditorState.createEmpty()
}
static getEditorStateFromHTML(htmlString) {
const blocksFromHTML = convertFromHTML(htmlString);
const state = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
return EditorState.createWithContent(state);
}
static getHTMLFromEditorState(editorState) {
return stateToHTML(editorState.getCurrentContent());
}
static isEditorState(editorState) {
return editorState && editorState.getCurrentContent;
}
state = {
value: RichTextEditor.createEmptyValue(),
value: EditorState.createEmpty(),
focused: false
};
render() {
return (
<div className={this.getClass()}>
<RichTextEditor {...this.getEditorProps()} />
<Editor {...this.getEditorProps()} />
</div>
);
}
@ -36,15 +60,39 @@ class TextEditor extends React.Component {
getEditorProps() {
return {
className: 'text-editor__editor',
value: this.props.value || this.state.value,
wrapperClassName: 'text-editor__editor',
editorState: this.props.value || this.state.value,
ref: 'editor',
onChange: this.onEditorChange.bind(this),
toolbar: this.getToolbarOptions(),
onEditorStateChange: this.onEditorChange.bind(this),
onFocus: this.onEditorFocus.bind(this),
onBlur: this.onBlur.bind(this)
};
}
getToolbarOptions() {
return {
options: ['inline', 'blockType', 'list', 'link', 'image'],
inline: {
inDropdown: false,
options: ['bold', 'italic', 'underline', 'strikethrough', 'monospace']
},
blockType: {
inDropdown: true,
options: [ 'Normal', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Blockquote']
},
list: {
inDropdown: false,
options: ['unordered', 'ordered']
},
image: {
urlEnabled: true,
uploadEnabled: false,
alignmentEnabled: false
}
};
}
onEditorChange(value) {
this.setState({value});
@ -71,7 +119,7 @@ class TextEditor extends React.Component {
focus() {
if (this.refs.editor) {
this.refs.editor._focus();
this.refs.editor.focusEditor();
}
}
}

View File

@ -3,11 +3,17 @@
.text-editor {
.text-editor__editor {
background-color: white;
border: 1px solid $grey;
border-radius: 3px;
.DraftEditor-root {
height: 200px;
padding-left: 10px;
}
.public-DraftEditor-content {
height: 185px;
}
}

View File

@ -10,7 +10,7 @@ module.exports = [
data: {
name: 'Emilia Clarke',
email: 'staff@opensupports.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
level: 3,
staff: true,
departments: [
@ -50,7 +50,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -61,7 +61,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -71,7 +71,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -82,7 +82,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -103,7 +103,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -123,7 +123,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -167,7 +167,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -178,7 +178,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -188,7 +188,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -199,7 +199,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -220,7 +220,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -240,7 +240,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -284,7 +284,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -295,7 +295,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -305,7 +305,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -316,7 +316,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -337,7 +337,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -357,7 +357,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -443,7 +443,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -454,7 +454,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -464,7 +464,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -475,7 +475,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -496,7 +496,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -516,7 +516,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -560,7 +560,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -571,7 +571,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -581,7 +581,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -592,7 +592,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -613,7 +613,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -633,7 +633,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -677,7 +677,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -688,7 +688,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -698,7 +698,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -709,7 +709,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -730,7 +730,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -750,7 +750,7 @@ module.exports = [
author: {
name: 'Emilia Clarke',
email: 'jobs@steve.com',
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
staff: true
}
},
@ -979,7 +979,7 @@ module.exports = [
data: [
{
id: 22,
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
name: 'Emilia Clarke',
departments: [{id: 2, name: 'Technical issues'}],
assignedTickets: 4,
@ -988,7 +988,7 @@ module.exports = [
},
{
id: 22,
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
name: 'Yulian A GUI Yermo',
departments: [{id: 2, name: 'Technical issues'}, {id: 1, name: 'Sales Support'}],
assignedTickets: 9,
@ -997,7 +997,7 @@ module.exports = [
},
{
id: 22,
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
name: 'Miltona Costa',
departments: [{id: 1, name: 'Sales Support'}],
assignedTickets: -1,
@ -1006,7 +1006,7 @@ module.exports = [
},
{
id: 22,
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
name: 'Emiliasnikova Rusachestkvuy',
departments: [{id: 1, name: 'Sales Support'}, {id: 3, name: 'System and Administration'}],
assignedTickets: 100,
@ -1015,7 +1015,7 @@ module.exports = [
},
{
id: 22,
profilePic: 'http://www.opensupports.com/profilepic.jpg',
profilePic: '',
name: 'Laurita Morrechaga Rusachestkvuy',
departments: [{id: 3, name: 'System and Administration'}],
assignedTickets: 1,

View File

@ -40,7 +40,7 @@ module.exports = [
status: 'success',
data: [
{id: 1, name: 'Common issue #1', language: 'en', content: 'some content 1'},
{id: 2, name: 'Common issue #2', language: 'en', content: 'some content 2'},
{id: 2, name: 'Common issue #2', language: 'en', content: 'some <strong>content</strong> 2'},
{id: 3, name: 'Common issue #3', language: 'en', content: 'some content 3'},
{id: 4, name: 'H<>ufiges Problem #1', language: 'de', content: 'einige Inhalte 1'},
{id: 5, name: 'H<>ufiges Problem #2', language: 'de', content: 'einige Inhalte 2'}

View File

@ -161,7 +161,6 @@ export default {
'ENABLE_USER_SYSTEM': 'Use user system for customers',
'ENABLE_USER_REGISTRATION': 'Enable user registration',
'INCLUDE_USERS_VIA_CSV': 'Include users via CSV file',
'INCLUDE_DATABASE_VIA_SQL': 'Include database via SQL file',
'BACKUP_DATABASE': 'Backup database',
'DELETE_ALL_USERS': 'Delete all users',
'PLEASE_CONFIRM_PASSWORD': 'Please confirm your password to make these changes',
@ -250,6 +249,8 @@ export default {
'ADD_API_KEY_DESCRIPTION': 'Insert the name and a registration api key be generated.',
'SIGN_UP_VIEW_DESCRIPTION': 'Here you can create an account for our support center. It is required for send tickets and see documentation.',
'EDIT_PROFILE_VIEW_DESCRIPTION': 'Here you can edit your user by changing your email or your password.',
'ENABLE_USER_SYSTEM_DESCRIPTION': 'Enable/disable the use of an user system. If you disable it, all users will be deleted but the tickets will be kept. If you enable it, the users of existent tickets will be created.',
'CSV_DESCRIPTION': 'The CSV file must have 3 columns: email, password, name. There is no limit in row count. It will be created one user per row in the file.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
@ -275,13 +276,18 @@ export default {
'ERROR_URL': 'Invalid URL',
'UNVERIFIED_EMAIL': 'Email is not verified yet',
'ERROR_UPDATING_SETTINGS': 'An error occurred while trying to update settings',
'INVALID_EMAIL_OR_TICKET_NUMBER': 'Invalid email or ticket number',
'INVALID_FILE': 'Invalid file',
'ERRORS_FOUND': 'Errors found',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
'TICKET_SENT': 'Ticket has been created successfully.',
'VALID_RECOVER': 'Password recovered successfully',
'EMAIL_EXISTS': 'Email already exists',
'ARE_YOU_SURE': 'Are you sure?',
'ARE_YOU_SURE': 'Confirm action',
'EMAIL_WILL_CHANGE': 'The current email will be changed',
'PASSWORD_WILL_CHANGE': 'The current password will be changed',
'EMAIL_CHANGED': 'Email has been changed successfully',
'PASSWORD_CHANGED': 'Password has been changed successfully',
'OLD_PASSWORD_INCORRECT': 'Old password is incorrect',
@ -294,5 +300,7 @@ export default {
'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.',
'WILL_RECOVER_EMAIL_TEMPLATE': 'This email template will be recover to it\'s default value on this language.'
'WILL_RECOVER_EMAIL_TEMPLATE': 'This email template will be recover to it\'s default value on this language.',
'SUCCESS_IMPORTING_CSV_DESCRIPTION': 'CSV File has been imported successfully',
'SUCCESS_DELETING_ALL_USERS': 'Users have beend deleted successfully'
};

View File

@ -27,8 +27,6 @@ let renderApplication = function () {
render(<Provider store={store}>{routes}</Provider>, document.getElementById('app'));
};
window.store = store;
store.dispatch(ConfigActions.init());
store.dispatch(SessionActions.initSession());
let unsubscribe = store.subscribe(() => {
console.log(store.getState());
@ -38,3 +36,6 @@ let unsubscribe = store.subscribe(() => {
renderApplication();
}
});
store.dispatch(ConfigActions.init());
store.dispatch(SessionActions.initSession());

View File

@ -29,6 +29,7 @@ function processData (data, dataAsForm = false) {
module.exports = {
call: function ({path, data, plain, dataAsForm}) {
console.log('request ' + path, data);
return new Promise(function (resolve, reject) {
APIUtils.post(apiUrl + path, processData(data, dataAsForm), dataAsForm)
.then(function (result) {

View File

@ -23,7 +23,7 @@ class SessionStore {
}
isLoggedIn() {
return !!this.getItem('token');
return !!this.getItem('userId');
}
closeSession() {
@ -62,6 +62,7 @@ class SessionStore {
this.setItem('title', configs.title);
this.setItem('registration', configs.registration);
this.setItem('user-system-enabled', configs['user-system-enabled']);
this.setItem('allow-attachments', configs['allow-attachments']);
}
getConfigs() {
@ -72,9 +73,11 @@ class SessionStore {
allowedLanguages: JSON.parse(this.getItem('allowedLanguages')),
supportedLanguages: JSON.parse(this.getItem('supportedLanguages')),
layout: this.getItem('layout'),
registration: this.getItem('registration'),
title: this.getItem('title'),
['user-system-enabled']: this.getItem('user-system-enabled')
registration: (this.getItem('registration') * 1),
'user-system-enabled': (this.getItem('user-system-enabled') * 1),
'allow-attachments': (this.getItem('allow-attachments') * 1),
'maintenance-mode': (this.getItem('maintenance-mode') * 1)
};
}

View File

@ -1,4 +1,4 @@
import RichTextEditor from 'react-rte-browserify';
import TextEditor from 'core-components/text-editor';
import Validator from 'lib-app/validations/validator';
@ -11,8 +11,8 @@ class LengthValidator extends Validator {
}
validate(value = '', form = {}) {
if (value instanceof RichTextEditor.EditorValue) {
value = value.getEditorState().getCurrentContent().getPlainText();
if (TextEditor.isEditorState(value)) {
value = value.getCurrentContent().getPlainText();
}
if (value.length < this.minlength) return this.getError(this.errorKey);

View File

@ -1,5 +1,4 @@
import Validator from 'lib-app/validations/validator';
import AlphaNumericValidator from 'lib-app/validations/alphanumeric-validator';
import EmailValidator from 'lib-app/validations/email-validator';
import RepeatPasswordValidator from 'lib-app/validations/repeat-password-validator';
import LengthValidator from 'lib-app/validations/length-validator';
@ -7,8 +6,8 @@ import ListValidator from 'lib-app/validations/list-validator';
let validators = {
'DEFAULT': new Validator(),
'NAME': new AlphaNumericValidator('ERROR_NAME', new LengthValidator(2, 'ERROR_NAME')),
'TITLE': new AlphaNumericValidator('ERROR_TITLE', new LengthValidator(2, 'ERROR_TITLE')),
'NAME': new LengthValidator(2, 'ERROR_NAME'),
'TITLE': new LengthValidator(2, 'ERROR_TITLE'),
'EMAIL': new EmailValidator(),
'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'),
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),

View File

@ -1,4 +1,4 @@
import RichTextEditor from 'react-rte-browserify';
import TextEditor from 'core-components/text-editor';
import i18n from 'lib-app/i18n';
@ -20,8 +20,8 @@ class Validator {
}
validate(value, form) {
if (value instanceof RichTextEditor.EditorValue) {
value = value.getEditorState().getPlainText()
if (TextEditor.isEditorState(value)) {
value = value.getPlainText();
}
if (value.length === 0) return this.getError('ERROR_EMPTY');

View File

@ -3,6 +3,7 @@
@import 'scss/typography';
@import 'scss/base';
@import 'scss/font_awesome/font-awesome';
@import 'scss/react-draft-wysiwyg';
@import 'core-components/*';
@import 'app-components/*';

View File

@ -26,7 +26,6 @@ class AdminDataReducer extends Reducer {
getTypeHandlers() {
return {
'CUSTOM_RESPONSES_FULFILLED': this.onCustomResponses,
'SESSION_CHECKED': this.onSessionChecked,
'MY_TICKETS_FULFILLED': this.onMyTicketsRetrieved,
'MY_TICKETS_REJECTED': this.onMyTicketsRejected,
@ -51,15 +50,6 @@ class AdminDataReducer extends Reducer {
});
}
onSessionChecked(state) {
const customResponses = sessionStore.getItem('customResponses');
return _.extend({}, state, {
customResponses: JSON.parse(customResponses),
customResponsesLoaded: true
});
}
onMyTicketsRetrieved(state, payload) {
return _.extend({}, state, {
myTickets: payload.data,

View File

@ -37,6 +37,10 @@ class ConfigReducer extends Reducer {
return _.extend({}, state, payload.data, {
language: currentLanguage || payload.language,
registration: !!(payload.data.registration * 1),
'user-system-enabled': !!(payload.data['user-system-enabled']* 1),
'allow-attachments': !!(payload.data['allow-attachments']* 1),
'maintenance-mode': !!(payload.data['maintenance-mode']* 1),
initDone: true
});
}

View File

@ -7,6 +7,7 @@ class ModalReducer extends Reducer {
getInitialState() {
return {
opened: false,
noPadding: false,
content: null
};
}
@ -23,7 +24,8 @@ class ModalReducer extends Reducer {
return _.extend({}, state, {
opened: true,
content: payload
content: payload.content,
noPadding: payload.noPadding || false
});
}
@ -32,7 +34,8 @@ class ModalReducer extends Reducer {
return _.extend({}, state, {
opened: false,
content: null
content: null,
noPadding: false
});
}
}

View File

@ -0,0 +1,985 @@
.rdw-option-wrapper {
border: 1px solid #F1F1F1;
padding: 5px;
min-width: 25px;
height: 20px;
border-radius: 2px;
margin: 0 4px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
background: white;
text-transform: capitalize;
}
.rdw-option-wrapper:hover {
box-shadow: 1px 1px 0px #BFBDBD;
}
.rdw-option-wrapper:active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-option-active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-option-disabled {
opacity: 0.3;
cursor: default;
}
.rdw-dropdown-wrapper {
height: 30px;
background: white;
cursor: pointer;
border: 1px solid #F1F1F1;
border-radius: 2px;
margin: 0 3px;
text-transform: capitalize;
background: white;
}
.rdw-dropdown-wrapper:focus {
outline: none;
}
.rdw-dropdown-wrapper:hover {
box-shadow: 1px 1px 0px #BFBDBD;
background-color: #FFFFFF;
}
.rdw-dropdown-wrapper:active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-dropdown-carettoopen {
height: 0px;
width: 0px;
position: absolute;
top: 35%;
right: 10%;
border-top: 6px solid black;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.rdw-dropdown-carettoclose {
height: 0px;
width: 0px;
position: absolute;
top: 35%;
right: 10%;
border-bottom: 6px solid black;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
.rdw-dropdown-selectedtext {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: relative;
height: 100%;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 5px;
}
.rdw-dropdown-optionwrapper {
z-index: 100;
position: relative;
border: 1px solid #F1F1F1;
width: 98%;
background: white;
border-radius: 2px;
margin: 0;
padding: 0;
}
.rdw-dropdown-optionwrapper:hover {
box-shadow: 1px 1px 0px #BFBDBD;
background-color: #FFFFFF;
}
.rdw-dropdownoption-default {
min-height: 25px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 0 5px;
}
.rdw-dropdownoption-highlighted {
background: #F1F1F1;
}
.rdw-dropdownoption-active {
background: #f5f5f5;
}
.rdw-dropdownoption-disabled {
opacity: 0.3;
cursor: default;
}
.rdw-inline-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin-bottom: 6px;
}
.rdw-inline-dropdown {
width: 50px;
}
.rdw-inline-dropdownoption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-block-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-block-dropdown {
width: 110px;
}
.rdw-fontsize-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-fontsize-dropdown {
min-width: 40px;
}
.rdw-fontsize-option {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-fontfamily-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-fontfamily-dropdown {
width: 115px;
}
.rdw-fontfamily-placeholder {
white-space: nowrap;
max-width: 90px;
overflow: hidden;
text-overflow: ellipsis;
}
.rdw-fontfamily-optionwrapper {
width: 140px;
}
.rdw-list-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-list-dropdown {
width: 50px;
z-index: 90;
}
.rdw-list-dropdownOption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-text-align-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-text-align-dropdown {
width: 50px;
z-index: 90;
}
.rdw-text-align-dropdownOption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-right-aligned-block {
text-align: right;
}
.rdw-left-aligned-block {
text-align: left;
}
.rdw-center-aligned-block {
text-align: center;
}
.rdw-justify-aligned-block {
text-align: justify;
}
.rdw-right-aligned-block > div {
display: inline;
}
.rdw-left-aligned-block > div {
display: inline;
}
.rdw-center-aligned-block > div {
display: inline;
}
.rdw-justify-aligned-block > div {
display: inline;
}
.rdw-colorpicker-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-colorpicker-modal {
position: absolute;
top: 35px;
left: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
width: 175px;
height: 175px;
border: 1px solid #F1F1F1;
padding: 15px;
border-radius: 2px;
z-index: 100;
background: white;
box-shadow: 3px 3px 5px #BFBDBD;
}
.rdw-colorpicker-modal-header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding-bottom: 5px;
}
.rdw-colorpicker-modal-style-label {
font-size: 15px;
width: 50%;
text-align: center;
cursor: pointer;
padding: 0 10px 5px;
}
.rdw-colorpicker-modal-style-label-active {
border-bottom: 2px solid #0a66b7;
}
.rdw-colorpicker-modal-options {
margin: 5px auto;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: 170px;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.rdw-colorpicker-cube {
width: 22px;
height: 22px;
border: 1px solid #F1F1F1;
}
.rdw-colorpicker-option {
margin: 3px;
padding: 0;
min-height: 20px;
border: none;
width: 22px;
height: 22px;
min-width: 22px;
box-shadow: 1px 2px 1px #BFBDBD inset;
}
.rdw-colorpicker-option:hover {
box-shadow: 1px 2px 1px #BFBDBD;
}
.rdw-colorpicker-option:active {
box-shadow: -1px -2px 1px #BFBDBD;
}
.rdw-colorpicker-option-active {
box-shadow: 0px 0px 2px 2px #BFBDBD;
}
.rdw-link-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-link-dropdown {
width: 50px;
}
.rdw-link-dropdownOption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-link-dropdownPlaceholder {
margin-left: 8px;
}
.rdw-link-modal {
position: absolute;
top: 35px;
left: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
width: 235px;
height: 180px;
border: 1px solid #F1F1F1;
padding: 15px;
border-radius: 2px;
z-index: 100;
background: white;
box-shadow: 3px 3px 5px #BFBDBD;
}
.rdw-link-modal-label {
font-size: 15px;
}
.rdw-link-modal-input {
margin-top: 5px;
border-radius: 2px;
border: 1px solid #F1F1F1;
height: 25px;
margin-bottom: 15px;
padding: 0 5px;
}
.rdw-link-modal-input:focus {
outline: none;
}
.rdw-link-modal-buttonsection {
margin: 0 auto;
}
.rdw-link-modal-btn {
margin-left: 10px;
width: 75px;
height: 30px;
border: 1px solid #F1F1F1;
border-radius: 2px;
cursor: pointer;
background: white;
text-transform: capitalize;
}
.rdw-link-modal-btn:hover {
box-shadow: 1px 1px 0px #BFBDBD;
}
.rdw-link-modal-btn:active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-link-modal-btn:focus {
outline: none !important;
}
.rdw-link-modal-btn:disabled {
background: #ece9e9;
}
.rdw-link-dropdownoption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-history-dropdown {
width: 50px;
}
.rdw-embedded-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-embedded-modal {
position: absolute;
top: 35px;
left: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
width: 235px;
height: 180px;
border: 1px solid #F1F1F1;
padding: 15px;
border-radius: 2px;
z-index: 100;
background: white;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
box-shadow: 3px 3px 5px #BFBDBD;
}
.rdw-embedded-modal-header {
font-size: 15px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.rdw-embedded-modal-header-option {
width: 50%;
cursor: pointer;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.rdw-embedded-modal-header-label {
width: 95px;
border: 1px solid #f1f1f1;
margin-top: 5px;
background: #6EB8D4;
border-bottom: 2px solid #0a66b7;
}
.rdw-embedded-modal-link-section {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.rdw-embedded-modal-link-input {
width: 95%;
height: 35px;
margin: 10px 0;
border: 1px solid #F1F1F1;
border-radius: 2px;
font-size: 15px;
padding: 0 5px;
}
.rdw-embedded-modal-link-input:focus {
outline: none;
}
.rdw-embedded-modal-btn-section {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-embedded-modal-btn {
margin: 0 3px;
width: 75px;
height: 30px;
border: 1px solid #F1F1F1;
border-radius: 2px;
cursor: pointer;
background: white;
text-transform: capitalize;
}
.rdw-embedded-modal-btn:hover {
box-shadow: 1px 1px 0px #BFBDBD;
}
.rdw-embedded-modal-btn:active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-embedded-modal-btn:focus {
outline: none !important;
}
.rdw-embedded-modal-btn:disabled {
background: #ece9e9;
}
.rdw-embedded-modal-size {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin: 5px 0 10px;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
.rdw-embedded-modal-size-input {
width: 45%;
height: 20px;
border: 1px solid #F1F1F1;
border-radius: 2px;
font-size: 12px;
}
.rdw-embedded-modal-size-input:focus {
outline: none;
}
.rdw-emoji-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-emoji-modal {
overflow: auto;
position: absolute;
top: 35px;
left: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
width: 235px;
height: 180px;
border: 1px solid #F1F1F1;
padding: 15px;
border-radius: 2px;
z-index: 100;
background: white;
box-shadow: 3px 3px 5px #BFBDBD;
}
.rdw-emoji-icon {
margin: 2.5px;
height: 24px;
width: 24px;
cursor: pointer;
font-size: 22px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.rdw-spinner {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
height: 100%;
width: 100%;
}
.rdw-spinner > div {
width: 12px;
height: 12px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.rdw-spinner .rdw-bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.rdw-spinner .rdw-bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
.rdw-image-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-image-modal {
position: absolute;
top: 35px;
left: 5px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
width: 235px;
height: 200px;
border: 1px solid #F1F1F1;
padding: 15px;
border-radius: 2px;
z-index: 100;
background: white;
box-shadow: 3px 3px 5px #BFBDBD;
}
.rdw-image-modal-header {
font-size: 15px;
margin: 10px 0;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.rdw-image-modal-header-option {
width: 50%;
cursor: pointer;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
}
.rdw-image-modal-header-label {
width: 80px;
background: #f1f1f1;
border: 1px solid #f1f1f1;
margin-top: 5px;
}
.rdw-image-modal-header-label-highlighted {
background: #6EB8D4;
border-bottom: 2px solid #0a66b7;
}
.rdw-image-modal-upload-option {
height: 65px;
width: 100%;
color: gray;
cursor: pointer;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
border: none;
font-size: 15px;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
background-color: #f1f1f1;
outline: 2px dashed gray;
outline-offset: -10px;
margin: 10px 0;
}
.rdw-image-modal-upload-option-highlighted {
outline: 2px dashed #0a66b7;
}
.rdw-image-modal-upload-option-label {
cursor: pointer;
height: 100%;
width: 100%;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.rdw-image-modal-upload-option-input {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.rdw-image-modal-url-section {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.rdw-image-modal-url-input {
width: 95%;
height: 35px;
margin: 25px 0 5px;
border: 1px solid #F1F1F1;
border-radius: 2px;
font-size: 15px;
padding: 0 5px;
}
.rdw-image-modal-btn-section {
margin: 10px auto 0;
}
.rdw-image-modal-url-input:focus {
outline: none;
}
.rdw-image-modal-btn {
margin: 0 5px;
width: 75px;
height: 30px;
border: 1px solid #F1F1F1;
border-radius: 2px;
cursor: pointer;
background: white;
text-transform: capitalize;
}
.rdw-image-modal-btn:hover {
box-shadow: 1px 1px 0px #BFBDBD;
}
.rdw-image-modal-btn:active {
box-shadow: 1px 1px 0px #BFBDBD inset;
}
.rdw-image-modal-btn:focus {
outline: none !important;
}
.rdw-image-modal-btn:disabled {
background: #ece9e9;
}
.rdw-image-modal-spinner {
position: absolute;
top: -3px;
left: 0;
width: 100%;
height: 100%;
opacity: 0.5;
}
.rdw-remove-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
position: relative;
}
.rdw-history-wrapper {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
margin-bottom: 6px;
}
.rdw-history-dropdownoption {
height: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-history-dropdown {
width: 50px;
}
.rdw-link-decorator-wrapper {
position: relative;
}
.rdw-link-decorator-icon {
position: absolute;
left: 40%;
top: 0;
cursor: pointer;
background-color: white;
}
.rdw-mention-link {
text-decoration: none;
color: #1236ff;
background-color: #f0fbff;
padding: 1px 2px;
border-radius: 2px;
}
.rdw-suggestion-wrapper {
position: relative;
}
.rdw-suggestion-dropdown {
position: absolute;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
border: 1px solid #F1F1F1;
min-width: 100px;
max-height: 150px;
overflow: auto;
background: white;
z-index: 100;
}
.rdw-suggestion-option {
padding: 7px 5px;
border-bottom: 1px solid #f1f1f1;
}
.rdw-suggestion-option-active {
background-color: #F1F1F1;
}
.rdw-hashtag-link {
text-decoration: none;
color: #1236ff;
background-color: #f0fbff;
padding: 1px 2px;
border-radius: 2px;
}
.rdw-image-alignment-options-popup {
position: absolute;;
background: white;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 5px 2px;
border-radius: 2px;
border: 1px solid #F1F1F1;
width: 105px;
cursor: pointer;
z-index: 100;
}
.rdw-alignment-option-left {
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.rdw-image-alignment-option {
height: 15px;
width: 15px;
min-width: 15px;
}
.rdw-image-alignment {
position: relative;
}
.rdw-image-imagewrapper {
position: relative;
}
.rdw-image-center {
float: none;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.rdw-image-left {
float: left;
}
.rdw-image-right {
float: right;
}
.rdw-editor-main {
height: 100%;
width: 100%;
overflow: auto;
box-sizing: content-box;
}
.rdw-editor-toolbar {
padding: 6px 5px 0;
border-radius: 2px;
border: 1px solid #F1F1F1;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: start;
-ms-flex-pack: start;
justify-content: flex-start;
width: 100%;
background: white;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
font-size: 15px;
margin-bottom: 5px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.public-DraftStyleDefault-block {
margin: 1em 0;
}
.rdw-editor-wrapper:focus {
outline: none;
}
/**
* Draft v0.9.1
*
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
.DraftEditor-editorContainer, .DraftEditor-root, .public-DraftEditor-content{height:inherit;text-align:initial}.public-DraftEditor-content[contenteditable=true]{-webkit-user-modify:read-write-plaintext-only}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:0}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol, .public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4}
/*# sourceMappingURL=react-draft-wysiwyg.css.map*/

View File

@ -5,7 +5,8 @@
"phpmailer/phpmailer": "^5.2",
"google/recaptcha": "~1.1",
"gabordemooij/redbean": "^4.3",
"ifsnop/mysqldump-php": "2.*"
"ifsnop/mysqldump-php": "2.*",
"ezyang/htmlpurifier": "^4.8"
},
"require-dev": {
"phpunit/phpunit": "5.0.*"

View File

@ -26,8 +26,6 @@ class AddTopicController extends Controller {
'iconColor' => Controller::request('iconColor')
]);
$staff = Controller::getLoggedUser();
Log::createLog('ADD_TOPIC', $topic->name);
Response::respondSuccess([

View File

@ -30,7 +30,7 @@ class AddArticleController extends Controller {
$article = new Article();
$article->setProperties([
'title' => Controller::request('title'),
'content' => Controller::request('content'),
'content' => Controller::request('content', true),
'lastEdited' => Date::getCurrentDate(),
'position' => Controller::request('position') || 1
]);
@ -39,8 +39,6 @@ class AddArticleController extends Controller {
$topic->ownArticleList->add($article);
$topic->store();
$staff = Controller::getLoggedUser();
Log::createLog('ADD_ARTICLE', $article->title);
Response::respondSuccess([

View File

@ -33,7 +33,7 @@ class EditArticleController extends Controller {
}
if(Controller::request('content')) {
$article->content = Controller::request('content');
$article->content = Controller::request('content', true);
}
if(Controller::request('title')) {

View File

@ -19,7 +19,7 @@ class AddStaffController extends Controller {
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 55)->alpha(),
'validation' => DataValidator::length(2, 55),
'error' => ERRORS::INVALID_NAME
],
'email' => [

View File

@ -1,4 +1,5 @@
<?php
use RedBeanPHP\Facade as RedBean;
use Respect\Validation\Validator as DataValidator;
class GetNewTicketsStaffController extends Controller {
@ -18,10 +19,16 @@ class GetNewTicketsStaffController extends Controller {
$query .= 'department_id=' . $department->id . ' OR ';
}
$query = substr($query,0,-3);
$query .= ') AND owner_id IS NULL';
$ownerExists = RedBean::exec('SHOW COLUMNS FROM ticket LIKE \'owner_id\'');
if($ownerExists != 0) {
$query .= ') AND owner_id IS NULL';
} else {
$query .= ')';
}
$ticketList = Ticket::find($query);
Response::respondSuccess($ticketList->toArray());
}
}
}

View File

@ -28,8 +28,11 @@ class LastEventsStaffController extends Controller {
$query = substr($query,0,-3);
$query .= ') ORDER BY id desc LIMIT ? OFFSET ?' ;
$eventList = Ticketevent::find($query, [10, 10*($page-1)]);
Response::respondSuccess($eventList->toArray());
if(Ticketevent::count() && !$user->sharedTicketList->isEmpty()) {
$eventList = Ticketevent::find($query, [10, 10*($page-1)]);
Response::respondSuccess($eventList->toArray());
} else {
Response::respondSuccess([]);
}
}
}

View File

@ -10,7 +10,7 @@ class SearchTicketStaffController extends Controller {
'permission' => 'staff_1',
'requestData' => [
'query' => [
'validation' => DataValidator::alpha(),
'validation' => DataValidator::length(1),
'error' => ERRORS::INVALID_QUERY
],
'page' => [

View File

@ -12,10 +12,10 @@ require_once 'system/recover-mail-template.php';
require_once 'system/disable-registration.php';
require_once 'system/enable-registration.php';
require_once 'system/disable-user-system.php';
require_once 'system/enabled-user-system.php';
require_once 'system/enable-user-system.php';
require_once 'system/add-api-key.php';
require_once 'system/delete-api-key.php';
require_once 'system/get-all-keys.php';
require_once 'system/get-api-keys.php';
require_once 'system/get-stats.php';
require_once 'system/delete-all-users.php';
require_once 'system/csv-import.php';
@ -40,12 +40,12 @@ $systemControllerGroup->addController(new EnableRegistrationController);
$systemControllerGroup->addController(new GetStatsController);
$systemControllerGroup->addController(new AddAPIKeyController);
$systemControllerGroup->addController(new DeleteAPIKeyController);
$systemControllerGroup->addController(new GetAllKeyController);
$systemControllerGroup->addController(new GetAPIKeysController);
$systemControllerGroup->addController(new DeleteAllUsersController);
$systemControllerGroup->addController(new BackupDatabaseController);
$systemControllerGroup->addController(new DownloadController);
$systemControllerGroup->addController(new CSVImportController);
$systemControllerGroup->addController(new DisableUserSystemController);
$systemControllerGroup->addController(new EnabledUserSystemController);
$systemControllerGroup->addController(new EnableUserSystemController);
$systemControllerGroup->finalize();
$systemControllerGroup->finalize();

View File

@ -12,7 +12,7 @@ class CSVImportController extends Controller {
}
public function handler() {
$fileUploader = $this->uploadFile();
$fileUploader = $this->uploadFile(true);
if(!$fileUploader instanceof FileUploader) {
throw new Exception(ERRORS::INVALID_FILE);

View File

@ -10,7 +10,7 @@ class DeleteAPIKeyController extends Controller {
'permission' => 'staff_3',
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 55)->alpha(),
'validation' => DataValidator::length(2, 55),
'error' => ERRORS::INVALID_NAME
]
]

View File

@ -32,8 +32,8 @@ class EditMailTemplateController extends Controller {
public function handler() {
$language = Controller::request('language');
$templateType = Controller::request('templateType');
$subject = Controller::request('subject');
$body = Controller::request('body');
$subject = Controller::request('subject', true);
$body = Controller::request('body', true);
$mailTemplate = MailTemplate::findOne(' language = ? AND type = ?', [$language, $templateType]);
if($mailTemplate->isNull()) {

View File

@ -1,7 +1,7 @@
<?php
class EnabledUserSystemController extends Controller {
const PATH = '/enabled-user-system';
class EnableUserSystemController extends Controller {
const PATH = '/enable-user-system';
const METHOD = 'POST';
public function validations() {
@ -31,19 +31,17 @@ class EnabledUserSystemController extends Controller {
foreach($ticketList as $ticket) {
$userRow = User::getDataStore($ticket->authorEmail, 'email');
$userInstance = User::getDataStore($ticket->authorEmail, 'email');
if($userRow->isNull()) {
$this->createUser($ticket->authorEmail,$ticket->authorName);
} else {
$userRow->tickets = $userRow->tickets + 1;
$userRow->sharedTicketList->add($ticket);
$userRow->store();
if($userInstance->isNull()) {
$userInstance = $this->createUser($ticket->authorEmail, $ticket->authorName);
}
$actualUserRow = User::getDataStore($ticket->authorEmail,'email');
$ticket->author = $actualUserRow;
$userInstance->tickets = $userInstance->tickets + 1;
$userInstance->sharedTicketList->add($ticket);
$userInstance->store();
$ticket->author = $userInstance;
$ticket->authorName = null;
$ticket->authorEmail = null;
$ticket->store();
@ -59,7 +57,7 @@ class EnabledUserSystemController extends Controller {
$userInstance->setProperties([
'name' => $name,
'signupDate' => Date::getCurrentDate(),
'tickets' => 1,
'tickets' => 0,
'email' => $email,
'password' => Hashing::hashPassword($password),
'verificationToken' => null
@ -75,5 +73,6 @@ class EnabledUserSystemController extends Controller {
]);
$mailSender->send();
return $userInstance;
}
}
}

View File

@ -1,8 +1,8 @@
<?php
use Respect\Validation\Validator as DataValidator;
class GetAllKeyController extends Controller {
const PATH = '/get-all-keys';
class GetAPIKeysController extends Controller {
const PATH = '/get-api-keys';
const METHOD = 'POST';
public function validations() {

View File

@ -10,7 +10,7 @@ class GetStatsController extends Controller {
'permission' => 'staff_1',
'requestData' => [
'period' => [
'validation' => DataValidator::in(['week', 'month', 'quarter', 'year']),
'validation' => DataValidator::in(['WEEK', 'MONTH', 'QUARTER', 'YEAR']),
'error' => ERRORS::INVALID_PERIOD
]
]
@ -123,16 +123,16 @@ class GetStatsController extends Controller {
$daysToRetrieve = 0;
switch ($period) {
case 'week':
case 'WEEK':
$daysToRetrieve = 7;
break;
case 'month':
case 'MONTH':
$daysToRetrieve = 30;
break;
case 'quarter':
case 'QUARTER':
$daysToRetrieve = 90;
break;
case 'year':
case 'YEAR':
$daysToRetrieve = 365;
break;
}

View File

@ -44,7 +44,7 @@ class InitSettingsController extends Controller {
'url' => 'http://www.opensupports.com/support',
'registration' => true,
'user-system-enabled' => true,
'last-stat-day' => '20170101', //TODO: get current date
'last-stat-day' => date('YmdHi', strtotime(' -12 day ')), //TODO: get current date
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-first-number' => Hashing::generateRandomNumber(100000, 999999),

View File

@ -2,6 +2,7 @@
include 'ticket/create.php';
include 'ticket/comment.php';
include 'ticket/get.php';
include 'ticket/check.php';
include 'ticket/add-custom-response.php';
include 'ticket/delete-custom-response.php';
include 'ticket/edit-custom-response.php';
@ -18,6 +19,7 @@ $ticketControllers->setGroupPath('/ticket');
$ticketControllers->addController(new CreateController);
$ticketControllers->addController(new CommentController);
$ticketControllers->addController(new TicketGetController);
$ticketControllers->addController(new CheckTicketController);
$ticketControllers->addController(new AddCustomResponseController);
$ticketControllers->addController(new DeleteCustomResponseController);
$ticketControllers->addController(new EditCustomResponseController);

View File

@ -0,0 +1,49 @@
<?php
use Respect\Validation\Validator as DataValidator;
DataValidator::with('CustomValidations', true);
class CheckTicketController extends Controller {
const PATH = '/check';
const METHOD = 'POST';
public function validations() {
return [
'permission' => 'any',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
],
'email' => [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
],
'captcha' => [
'validation' => DataValidator::captcha(),
'error' => ERRORS::INVALID_CAPTCHA
]
]
];
}
public function handler() {
if (Controller::isUserSystemEnabled() || Controller::isStaffLogged()) {
throw new Exception(ERRORS::NO_PERMISSION);
}
$email = Controller::request('email');
$ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if($ticket->authorEmail === $email) {
$session = Session::getInstance();
$session->createTicketSession($ticket->ticketNumber);
Response::respondSuccess([
'token' => $session->getToken(),
'ticketNumber' => $ticket->ticketNumber
]);
} else {
throw new Exception(ERRORS::NO_PERMISSION);
}
}
}

View File

@ -10,42 +10,49 @@ class CommentController extends Controller {
private $content;
public function validations() {
$validations = [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::length(20, 5000),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
if(!Controller::isUserSystemEnabled()) {
$validations['permission'] = 'any';
$session = Session::getInstance();
$session = Session::getInstance();
$validations['requestData']['csrf_token'] = [
'validation' => DataValidator::equals($session->getToken()),
'error' => ERRORS::NO_PERMISSION
if (Controller::isUserSystemEnabled() || Controller::isStaffLogged()) {
return [
'permission' => 'user',
'requestData' => [
'content' => [
'validation' => DataValidator::length(20, 5000),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
];
$validations['requestData']['ticketNumber'] = [
'validation' => DataValidator::equals($session->getTicketNumber()),
'error' => ERRORS::INVALID_TICKET
} else {
return [
'permission' => 'any',
'requestData' => [
'content' => [
'validation' => DataValidator::length(20, 5000),
'error' => ERRORS::INVALID_CONTENT
],
'ticketNumber' => [
'validation' => DataValidator::equals($session->getTicketNumber()),
'error' => ERRORS::INVALID_TICKET
],
'csrf_token' => [
'validation' => DataValidator::equals($session->getToken()),
'error' => Controller::request('csrf_token') . ' ' . $session->getToken()
]
]
];
}
return $validations;
}
public function handler() {
$session = Session::getInstance();
$this->requestData();
if (!Controller::isUserSystemEnabled() || $session->isLoggedWithId($this->ticket->author->id) || Controller::isStaffLogged()) {
if ((!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) || $session->isLoggedWithId(($this->ticket->author) ? $this->ticket->author->id : 0) || (Controller::isStaffLogged() && $session->isLoggedWithId(($this->ticket->owner) ? $this->ticket->owner->id : 0))) {
$this->storeComment();
Log::createLog('COMMENT', $this->ticket->ticketNumber);
@ -58,13 +65,8 @@ class CommentController extends Controller {
private function requestData() {
$ticketNumber = Controller::request('ticketNumber');
$email = Controller::request('email');
$this->ticket = Ticket::getByTicketNumber($ticketNumber);
$this->content = Controller::request('content');
if(!Controller::isUserSystemEnabled() && $this->ticket->authorEmail !== $email && !Controller::isStaffLogged()) {
throw new Exception(ERRORS::NO_PERMISSION);
}
$this->content = Controller::request('content', true);
}
private function storeComment() {
@ -84,7 +86,7 @@ class CommentController extends Controller {
$this->ticket->unreadStaff = true;
$comment->authorUser = Controller::getLoggedUser();
}
$this->ticket->addEvent($comment);
$this->ticket->store();
}

View File

@ -50,7 +50,7 @@ class CreateController extends Controller {
public function handler() {
$this->title = Controller::request('title');
$this->content = Controller::request('content');
$this->content = Controller::request('content', true);
$this->departmentId = Controller::request('departmentId');
$this->language = Controller::request('language');
$this->email = Controller::request('email');

View File

@ -22,7 +22,7 @@ class EditCustomResponseController extends Controller {
$customResponse = CustomResponse::getDataStore(Controller::request('id'));
if (Controller::request('content')) {
$customResponse->content = Controller::request('content');
$customResponse->content = Controller::request('content', true);
}
if (Controller::request('language')) {

View File

@ -9,83 +9,53 @@ class TicketGetController extends Controller {
private $ticket;
public function validations() {
$validations = [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
$session = Session::getInstance();
if (Controller::isUserSystemEnabled() || Controller::isStaffLogged()) {
return [
'permission' => 'user',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::validTicketNumber(),
'error' => ERRORS::INVALID_TICKET
]
]
]
];
if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) {
$validations['permission'] = 'any';
if(Controller::request('token')) {
$session = Session::getInstance();
$validations['requestData']['csrf_token'] = [
'validation' => DataValidator::equals($session->getToken()),
'error' => ERRORS::NO_PERMISSION
];
$validations['requestData']['ticketNumber'] = [
'validation' => DataValidator::equals($session->getTicketNumber()),
'error' => ERRORS::INVALID_TICKET
];
} else {
$validations['requestData']['email'] = [
'validation' => DataValidator::email(),
'error' => ERRORS::INVALID_EMAIL
];
$validations['requestData']['captcha'] = [
'validation' => DataValidator::captcha(),
'error' => ERRORS::INVALID_CAPTCHA
];
}
];
} else {
return [
'permission' => 'any',
'requestData' => [
'ticketNumber' => [
'validation' => DataValidator::equals($session->getTicketNumber()),
'error' => ERRORS::INVALID_TICKET
],
'csrf_token' => [
'validation' => DataValidator::equals($session->getToken()),
'error' => $session->getToken() . ' != ' . Controller::request('csrf_token')
]
]
];
}
return $validations;
}
public function handler() {
$email = Controller::request('email');
$this->ticket = Ticket::getByTicketNumber(Controller::request('ticketNumber'));
if(!Controller::isUserSystemEnabled() && !Controller::isStaffLogged()) {
if($this->ticket->authorEmail === $email) {
if(!Controller::request('token')) {
$this->generateSessionToken();
} else {
Response::respondSuccess($this->ticket->toArray());
}
return;
} else {
if(Controller::isUserSystemEnabled() || Controller::isStaffLogged()) {
if ($this->shouldDenyPermission()) {
throw new Exception(ERRORS::NO_PERMISSION);
} else {
Response::respondSuccess($this->ticket->toArray());
}
}
if ($this->shouldDenyPermission()) {
throw new Exception(ERRORS::NO_PERMISSION);
} else {
Response::respondSuccess($this->ticket->toArray());
}
}
private function generateSessionToken() {
$session = Session::getInstance();
$token = Hashing::generateRandomToken();
$session->createTicketSession($this->ticket->ticketNUmber);
Response::respondSuccess(['token' => $token, 'ticketNumber' => $this->ticket->ticketNUmber]);
}
private function shouldDenyPermission() {
$user = Controller::getLoggedUser();
return (!Controller::isStaffLogged() && (Controller::isUserSystemEnabled() && $this->ticket->author->id !== $user->id)) ||
(Controller::isStaffLogged() && $this->ticket->owner && $this->ticket->owner->id !== $user->id);
(Controller::isStaffLogged() && (($this->ticket->owner && $this->ticket->owner->id !== $user->id) || !$user->sharedDepartmentList->includesId($this->ticket->department->id)));
}
}

View File

@ -30,6 +30,11 @@ class DeleteUserController extends Controller {
Log::createLog('DELETE_USER', $user->name);
RedBean::exec('DELETE FROM log WHERE author_user_id = ?', [$userId]);
foreach($user->sharedTicketList as $ticket) {
$ticket->delete();
}
$user->delete();
Response::respondSuccess();

View File

@ -24,6 +24,11 @@ class LoginController extends Controller {
}
if ($this->checkInputCredentials() || $this->checkRememberToken()) {
if($this->userInstance->verificationToken !== null) {
Response::respondError(ERRORS::UNVERIFIED_USER);
return;
}
$this->createUserSession();
$this->createSessionCookie();
if(Controller::request('staff')) {
@ -31,14 +36,6 @@ class LoginController extends Controller {
$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);

View File

@ -22,7 +22,7 @@ class SignUpController extends Controller {
'permission' => 'any',
'requestData' => [
'name' => [
'validation' => DataValidator::length(2, 55)->alpha(),
'validation' => DataValidator::length(2, 55),
'error' => ERRORS::INVALID_NAME
],
'email' => [

View File

@ -55,4 +55,6 @@ foreach (glob('controllers/*.php') as $controller) {
include_once $controller;
}
Date::setTimeZone();
$app->run();

View File

@ -47,8 +47,16 @@ abstract class Controller {
self::$dataRequester = $dataRequester;
}
public static function request($key) {
return call_user_func(self::$dataRequester, $key);
public static function request($key, $secure = false) {
$result = call_user_func(self::$dataRequester, $key);
if($secure) {
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
return $purifier->purify($result);
} else {
return $result;
}
}
public static function getLoggedUser() {
@ -78,8 +86,10 @@ abstract class Controller {
return \Slim\Slim::getInstance();
}
public function uploadFile() {
if(!isset($_FILES['file'])) return '';
public function uploadFile($forceUpload = false) {
$allowAttachments = Setting::getSetting('allow-attachments')->getValue();
if(!isset($_FILES['file']) || (!$allowAttachments && !$forceUpload)) return '';
$maxSize = Setting::getSetting('max-size')->getValue();
$fileGap = Setting::getSetting('file-gap')->getValue();

View File

@ -45,6 +45,10 @@ class DataStoreList implements IteratorAggregate {
return $includes;
}
public function isEmpty() {
return empty($this->list);
}
public function toBeanList() {
$beanList = [];

View File

@ -1,5 +1,21 @@
<?php
class Date {
public static function setTimeZone() {
$timezone = Setting::getSetting('time-zone');
if(!$timezone->isNull()) {
$timezone = $timezone->getValue();
if($timezone > 0) {
date_default_timezone_set('GMT+' . $timezone);
} else if($timezone == 0) {
date_default_timezone_set('GMT');
} else {
date_default_timezone_set('GMT-' . abs($timezone * 1));
}
}
}
public static function getCurrentDate() {
return date('YmdHi');
}

View File

@ -29,7 +29,7 @@ class Ticket extends DataStore {
public static function getTicket($value, $property = 'id') {
return parent::getDataStore($value, $property);
}
public static function getByTicketNumber($value) {
return Ticket::getTicket($value, 'ticketNumber');
}
@ -50,19 +50,19 @@ class Ticket extends DataStore {
public function generateUniqueTicketNumber() {
$linearCongruentialGenerator = new LinearCongruentialGenerator();
$ticketQuantity = Ticket::count();
if ($ticketQuantity === 0) {
$ticketNumber = $linearCongruentialGenerator->generateFirst();
} else {
$linearCongruentialGenerator->setGap(Setting::getSetting('ticket-gap')->value);
$linearCongruentialGenerator->setFirst(Ticket::getTicket(1)->ticketNumber);
$ticketNumber = $linearCongruentialGenerator->generate($ticketQuantity);
}
return $ticketNumber;
}
public function toArray() {
return [
'ticketNumber' => $this->ticketNumber,
@ -81,9 +81,7 @@ class Ticket extends DataStore {
'priority' => $this->priority,
'author' => $this->authorToArray(),
'owner' => $this->ownerToArray(),
'events' => $this->eventsToArray(),
'authorEmail' => $this->authorEmail,
'authorName' => $this->authorName
'events' => $this->eventsToArray()
];
}
@ -97,7 +95,10 @@ class Ticket extends DataStore {
'email' => $author->email
];
} else {
return [];
return [
'name' => $this->authorName,
'email' => $this->authorEmail
];
}
}
@ -143,7 +144,7 @@ class Ticket extends DataStore {
return $events;
}
public function addEvent(Ticketevent $event) {
$this->ownTicketeventList->add($event);
}

View File

@ -55,7 +55,7 @@ require './system/disable-registration.rb'
require './system/enable-registration.rb'
require './system/add-api-key.rb'
require './system/delete-api-key.rb'
require './system/get-all-keys.rb'
require './system/get-api-keys.rb'
require './system/file-upload-download.rb'
require './system/csv-import.rb'
require './system/disable-user-system.rb'

View File

@ -10,7 +10,7 @@ class Scripts
raise response['message']
end
userRow = $database.getRow('user', email, 'email')
response = request('/user/verify', {
request('/user/verify', {
:email => email,
:token => userRow['verification_token']
})
@ -32,9 +32,9 @@ class Scripts
response['data']
end
def self.createTicket()
def self.createTicket(title = 'Winter is coming')
result = request('/ticket/create', {
title: 'Winter is coming',
title: title,
content: 'The north remembers',
departmentId: 1,
language: 'en',
@ -46,7 +46,7 @@ class Scripts
end
def self.createAPIKey(name)
result = request('/system/add-api-key', {
request('/system/add-api-key', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
name: name

View File

@ -9,7 +9,6 @@ describe'system/disable-user-system' do
password:$staff[:password]
})
puts result['message']
(result['status']).should.equal('success')
row = $database.getRow('setting', 'user-system-enabled', 'name')
@ -20,7 +19,7 @@ describe'system/disable-user-system' do
numberOftickets= $database.query("SELECT * FROM ticket WHERE author_id IS NULL AND author_email IS NOT NULL AND author_name IS NOT NULL")
(numberOftickets.num_rows).should.equal(35)
(numberOftickets.num_rows).should.equal(36)
request('/user/logout')
@ -56,14 +55,13 @@ describe'system/disable-user-system' do
(result['message']).should.equal('SYSTEM_USER_IS_ALREADY_DISABLED')
end
it 'should enabled the user system' do
result = request('/system/enabled-user-system', {
it 'should enable the user system' do
result = request('/system/enable-user-system', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
password:$staff[:password]
})
puts result['message']
(result['status']).should.equal('success')
row = $database.getRow('setting', 'user-system-enabled', 'name')
@ -71,12 +69,12 @@ describe'system/disable-user-system' do
numberOftickets= $database.query("SELECT * FROM ticket WHERE author_email IS NULL AND author_name IS NULL AND author_id IS NOT NULL" )
(numberOftickets.num_rows).should.equal(35)
(numberOftickets.num_rows).should.equal(36)
end
it 'should not enabled the user system' do
result = request('/system/enabled-user-system', {
it 'should not enable the user system' do
result = request('/system/enable-user-system', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
password:$staff[:password]

View File

@ -1,15 +1,15 @@
describe'system/get-all-keys' do
describe'system/get-api-keys' do
request('/user/logout')
Scripts.login($staff[:email], $staff[:password], true)
it 'should get all API keys' do
it 'should get all API keys' do
Scripts.createAPIKey('namekey1')
Scripts.createAPIKey('namekey2')
Scripts.createAPIKey('namekey3')
Scripts.createAPIKey('namekey4')
Scripts.createAPIKey('namekey5')
result= request('/system/get-all-keys', {
result = request('/system/get-api-keys', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
})

View File

@ -74,7 +74,7 @@ describe'/system/get-stats' do
@result = request('/system/get-stats', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
period: 'week'
period: 'WEEK'
})
def assertData(position, date, type, value)
@ -126,7 +126,7 @@ describe'/system/get-stats' do
@result = request('/system/get-stats', {
csrf_userid: $csrf_userid,
csrf_token: $csrf_token,
period: 'week',
period: 'WEEK',
staffId: '1'
})
assertData(0, yesterday, 'CLOSE', '4')

View File

@ -1,28 +1,30 @@
describe '/user/delete' do
request('/user/logout')
result = request('/user/login', {
email: 'staff@opensupports.com',
password: 'staff',
staff: true
})
$csrf_userid = result['data']['userId']
$csrf_token = result['data']['token']
it 'should delete user' do
Scripts.createUser('deletable@opensupports.com', 'deletable')
Scripts.login('deletable@opensupports.com', 'deletable')
Scripts.createTicket('Ticket that will be deleted')
request('/user/logout')
Scripts.login('staff@opensupports.com', 'staff', true)
ticket = $database.getLastRow('ticket')
deletable_user = $database.getLastRow('user')
result = request('/user/delete', {
userId: 4,
userId: deletable_user['id'],
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
user = $database.getRow('user', 4 , 'id')
(user).should.equal(nil)
last_ticket = $database.getLastRow('ticket')
last_log = $database.getLastRow('log')
user = $database.getRow('user', deletable_user['id'] , 'id')
lastLog = $database.getLastRow('log')
(lastLog['type']).should.equal('DELETE_USER')
(user).should.equal(nil)
(ticket['id']).should.not.equal(last_ticket['id'])
(last_log['type']).should.equal('DELETE_USER')
end
end

View File

@ -52,5 +52,13 @@ describe '/user/edit-password' do
csrf_token: $csrf_token
})
(result['status']).should.equal('success')
request('/user/logout')
result = request('/user/login',{
email: 'steve@jobs.com',
password: 'newpassword'
})
(result['status']).should.equal('success')
end
end

View File

@ -41,15 +41,6 @@ describe '/user/signup' do
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_NAME')
result = request('/user/signup', {
name: 'tyri0n',
email: 'tyrion@outlook.com',
password: 'Lannister'
})
(result['status']).should.equal('fail')
(result['message']).should.equal('INVALID_NAME')
end
it 'should fail if email is invalid' do