diff --git a/client/src/app-components/language-selector.js b/client/src/app-components/language-selector.js index 14ab8012..cacb0d8e 100644 --- a/client/src/app-components/language-selector.js +++ b/client/src/app-components/language-selector.js @@ -3,6 +3,7 @@ import {connect} from 'react-redux'; import classNames from 'classnames'; import languageList from 'data/language-list'; +import i18n from 'lib-app/i18n'; import DropDown from 'core-components/drop-down'; const languageCodes = Object.keys(languageList); @@ -10,13 +11,15 @@ const languageCodes = Object.keys(languageList); class LanguageSelector extends React.Component { static propTypes = { value: React.PropTypes.oneOf(languageCodes), - type: React.PropTypes.oneOf(['allowed', 'supported']), + type: React.PropTypes.oneOf(['allowed', 'supported', 'custom']), + customList: React.PropTypes.array, allowedLanguages: React.PropTypes.array, supportedLanguages: React.PropTypes.array }; static defaultProps = { type: 'allowed', + customList: ['en'], allowedLanguages: languageCodes, supportedLanguages: languageCodes }; @@ -75,7 +78,14 @@ class LanguageSelector extends React.Component { } getLanguageList() { - return (this.props.type === 'supported') ? this.props.supportedLanguages : this.props.allowedLanguages; + switch(this.props.type) { + case 'supported': + return this.props.supportedLanguages; + case 'allowed': + return this.props.supportedLanguages; + case 'custom': + return this.props.customList; + } } } diff --git a/client/src/app-components/toggle-button.scss b/client/src/app-components/toggle-button.scss index 4711e8fe..6006ae56 100644 --- a/client/src/app-components/toggle-button.scss +++ b/client/src/app-components/toggle-button.scss @@ -6,4 +6,5 @@ padding: 2px; border-radius: 4px; text-align: center; + user-select: none; } \ No newline at end of file diff --git a/client/src/app/admin/panel/settings/admin-panel-system-preferences.js b/client/src/app/admin/panel/settings/admin-panel-system-preferences.js index 03ed99a7..dac728dc 100644 --- a/client/src/app/admin/panel/settings/admin-panel-system-preferences.js +++ b/client/src/app/admin/panel/settings/admin-panel-system-preferences.js @@ -1,5 +1,10 @@ import React from 'react'; import _ from 'lodash'; + +import API from 'lib-app/api-call'; +import i18n from 'lib-app/i18n'; +import LanguageSelector from 'app-components/language-selector'; +import ToggleButton from 'app-components/toggle-button'; import languageList from 'data/language-list'; import Form from 'core-components/form'; @@ -8,11 +13,9 @@ import Header from 'core-components/header'; import SubmitButton from 'core-components/submit-button'; import Button from 'core-components/button'; import Message from 'core-components/message'; +import InfoTooltip from 'core-components/info-tooltip'; -import API from 'lib-app/api-call'; -import i18n from 'lib-app/i18n'; -import LanguageSelector from 'app-components/language-selector'; -import ToggleButton from 'app-components/toggle-button'; +const languageKeys = Object.keys(languageList); class AdminPanelSystemPreferences extends React.Component { @@ -20,7 +23,7 @@ class AdminPanelSystemPreferences extends React.Component { loading: true, message: null, values: { - 'maintenance': false + maintenance: false } }; @@ -31,12 +34,12 @@ class AdminPanelSystemPreferences extends React.Component { render() { return (
-
-
this.setState({values, message: null})} onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}> +
+
- Maintenance Mode + {i18n('MAINTENANCE_MODE')}
@@ -48,11 +51,11 @@ class AdminPanelSystemPreferences extends React.Component {
- - + +
- +
@@ -85,16 +88,20 @@ class AdminPanelSystemPreferences extends React.Component {
-
{i18n('ALLOWED_LANGUAGES')}
- +
{i18n('ALLOWED_LANGUAGES')}
+
-
{i18n('SUPPORTED_LANGUAGES')}
- +
{i18n('SUPPORTED_LANGUAGES')}
+
+ languageKeys[index]) : undefined + }} />
@@ -105,7 +112,6 @@ class AdminPanelSystemPreferences extends React.Component { {i18n('MAX_SIZE_KB')}
-
@@ -138,8 +144,17 @@ class AdminPanelSystemPreferences extends React.Component { } } + onFormChange(form) { + let values = _.clone(form); + + _.extend(values, { + supportedLanguages: _.filter(values.supportedLanguages, (language) => _.includes(values.allowedLanguages, language)) + }); + + this.setState({values, message: null}); + } + onSubmit(form) { - let fullList = Object.keys(languageList); this.setState({loading: true}); API.call({ @@ -160,8 +175,8 @@ class AdminPanelSystemPreferences extends React.Component { 'maintenance-mode': form['maintenance-mode'], 'allow-attachments': form['allow-attachments'], 'max-size': form['max-size'], - 'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => fullList[index])), - 'supportedLanguages': JSON.stringify(form.supportedLanguages.map(index => fullList[index])) + 'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => languageKeys[index])), + 'supportedLanguages': JSON.stringify(form.supportedLanguages.map(index => languageKeys[index])) } }).then(this.onSubmitSuccess.bind(this)).catch(() => this.setState({loading: false, message: 'fail'})); } @@ -188,8 +203,6 @@ class AdminPanelSystemPreferences extends React.Component { } onRecoverSettingsSuccess(result) { - let fullList = Object.keys(languageList); - this.setState({ loading: false, values: { @@ -208,13 +221,13 @@ class AdminPanelSystemPreferences extends React.Component { 'maintenance-mode': result.data['maintenance-mode'], 'allow-attachments': result.data['allow-attachments'], 'max-size': result.data['max-size'], - 'allowedLanguages': result.data.allowedLanguages.map(lang => (_.indexOf(fullList, lang))), - 'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(fullList, lang))) + 'allowedLanguages': result.data.allowedLanguages.map(lang => (_.indexOf(languageKeys, lang))), + 'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(languageKeys, lang))) } }); } - onRecoverSettingsFail(result) { + onRecoverSettingsFail() { this.setState({ message: 'error' }); diff --git a/client/src/app/admin/panel/staff/staff-editor.js b/client/src/app/admin/panel/staff/staff-editor.js index e5513e41..fdca49f0 100644 --- a/client/src/app/admin/panel/staff/staff-editor.js +++ b/client/src/app/admin/panel/staff/staff-editor.js @@ -137,7 +137,7 @@ class StaffEditor extends React.Component {
this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this, 'LEVEL')}> - @@ -219,6 +219,25 @@ class StaffEditor extends React.Component { return SessionStore.getDepartments().map(department => department.name); } + getStaffLevelInfo() { + return ( +
+
+ {i18n('LEVEL')} 1 + {i18n('LEVEL_1_DESCRIPTION')} +
+
+ {i18n('LEVEL')} 2 + {i18n('LEVEL_2_DESCRIPTION')} +
+
+ {i18n('LEVEL')} 3 + {i18n('LEVEL_3_DESCRIPTION')} +
+
+ ); + } + onSubmit(eventType, form) { let departments; diff --git a/client/src/app/admin/panel/staff/staff-editor.scss b/client/src/app/admin/panel/staff/staff-editor.scss index aaf998d5..8e29ab3c 100644 --- a/client/src/app/admin/panel/staff/staff-editor.scss +++ b/client/src/app/admin/panel/staff/staff-editor.scss @@ -139,4 +139,21 @@ float: left; margin-top: 11px; } + + &__level-info { + width: 360px; + font-size: $font-size--sm; + + &-box { + margin-top: 5px; + } + + &-title { + color: $secondary-blue; + } + + &-description { + color: $dark-grey; + } + } } \ No newline at end of file diff --git a/client/src/core-components/checkbox-group.js b/client/src/core-components/checkbox-group.js index 76f6eb5c..bbe229a7 100644 --- a/client/src/core-components/checkbox-group.js +++ b/client/src/core-components/checkbox-group.js @@ -1,4 +1,5 @@ import React from 'react'; +import classNames from 'classnames'; import _ from 'lodash'; import Checkbox from 'core-components/checkbox'; @@ -6,6 +7,7 @@ import Checkbox from 'core-components/checkbox'; class CheckboxGroup extends React.Component { static propTypes = { items: React.PropTypes.array.isRequired, + errored: React.PropTypes.bool, value: React.PropTypes.arrayOf(React.PropTypes.number), onChange: React.PropTypes.func }; @@ -16,7 +18,7 @@ class CheckboxGroup extends React.Component { render() { return ( -
    +
      {this.props.items.map(this.renderItem.bind(this))}
    ); @@ -31,6 +33,17 @@ class CheckboxGroup extends React.Component { ); } + + getClass() { + let classes = { + 'checkbox-group': true, + 'checkbox-group_error': this.props.errored + }; + + classes[this.props.className] = (this.props.className); + + return classNames(classes); + } onCheckboxChange(index) { let value = _.clone(this.getValue()); diff --git a/client/src/core-components/checkbox-group.scss b/client/src/core-components/checkbox-group.scss index 92612c5c..198476d0 100644 --- a/client/src/core-components/checkbox-group.scss +++ b/client/src/core-components/checkbox-group.scss @@ -8,4 +8,9 @@ &__item { margin: 10px 0; } + + &_error { + border: 1px solid $primary-red; + padding-left: 7px; + } } \ No newline at end of file diff --git a/client/src/core-components/form-field.js b/client/src/core-components/form-field.js index 78cb9dad..cbb8f091 100644 --- a/client/src/core-components/form-field.js +++ b/client/src/core-components/form-field.js @@ -8,6 +8,7 @@ import DropDown from 'core-components/drop-down'; import Checkbox from 'core-components/checkbox'; import CheckboxGroup from 'core-components/checkbox-group'; import TextEditor from 'core-components/text-editor'; +import InfoTooltip from 'core-components/info-tooltip'; class FormField extends React.Component { static contextTypes = { @@ -21,6 +22,7 @@ class FormField extends React.Component { onBlur: React.PropTypes.func, required: React.PropTypes.bool, error: React.PropTypes.string, + infoMessage: React.PropTypes.node, value: React.PropTypes.any, field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox', 'checkbox-group']), fieldProps: React.PropTypes.object @@ -51,7 +53,7 @@ class FormField extends React.Component { render() { const Wrapper = (_.includes(this.getDivTypes(), this.props.field)) ? 'div' : 'label'; const fieldContent = [ - {this.props.label}, + this.renderLabel(), this.renderField(), this.renderError() ]; @@ -59,6 +61,7 @@ class FormField extends React.Component { if (this.props.field === 'checkbox') { fieldContent.swap(0, 1); } + return ( {fieldContent} @@ -66,6 +69,15 @@ class FormField extends React.Component { ); } + renderLabel() { + return ( + + {this.props.label} + {(this.props.infoMessage) ? : null} + + ) + } + renderField() { let Field = { 'input': Input, diff --git a/client/src/core-components/form-field.scss b/client/src/core-components/form-field.scss index e818fcaf..28a3d6df 100644 --- a/client/src/core-components/form-field.scss +++ b/client/src/core-components/form-field.scss @@ -12,6 +12,12 @@ text-align: left; } + &__info { + position: relative; + top: -1px; + left: 5px; + } + &_errored { .form-field__error { color: $primary-red; diff --git a/client/src/core-components/info-tooltip.js b/client/src/core-components/info-tooltip.js index 92c6ca24..47f2c1da 100644 --- a/client/src/core-components/info-tooltip.js +++ b/client/src/core-components/info-tooltip.js @@ -8,7 +8,7 @@ import Tooltip from 'core-components/tooltip'; class InfoTooltip extends React.Component { static propTypes = { type: React.PropTypes.oneOf(['default', 'warning']), - text: React.PropTypes.string.isRequired + text: React.PropTypes.node.isRequired }; static defaultProps = { @@ -31,6 +31,7 @@ class InfoTooltip extends React.Component { renderText() { let message = (this.props.type === 'default') ? i18n('INFO') : i18n('WARNING'); + return (
    @@ -47,6 +48,8 @@ class InfoTooltip extends React.Component { 'info-tooltip_warning': (this.props.type === 'warning') }; + classes[this.props.className] = (this.props.className); + return classNames(classes); } } diff --git a/client/src/core-components/info-tooltip.scss b/client/src/core-components/info-tooltip.scss index 63691bad..b73f08b1 100644 --- a/client/src/core-components/info-tooltip.scss +++ b/client/src/core-components/info-tooltip.scss @@ -1,7 +1,10 @@ @import "../scss/vars"; .info-tooltip { + display: inline-block; + &__text { + &-title { color: $secondary-blue; font-size: $font-size--md; @@ -10,9 +13,11 @@ &__icon { color: $secondary-blue; + font-size: $font-size--sm; } &_warning { + .info-tooltip__icon { color: $primary-red; } diff --git a/client/src/data/fixtures/system-fixtures.js b/client/src/data/fixtures/system-fixtures.js index a9f5687c..b67d450f 100644 --- a/client/src/data/fixtures/system-fixtures.js +++ b/client/src/data/fixtures/system-fixtures.js @@ -10,7 +10,7 @@ module.exports = [ 'language': 'en', 'reCaptchaKey': '6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS', 'reCaptchaPrivate': 'LALA', - 'url': 'hola@lala.com', + 'url': 'http://www.opensupports.com/support', 'title': 'Very Cool', 'layout': 'Boxed', 'time-zone': 3, diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 3f193dd2..67a36e64 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -107,6 +107,9 @@ export default { 'LEVEL_1': 'Level 1 (Tickets)', 'LEVEL_2': 'Level 2 (Tickets + Articles)', 'LEVEL_3': 'Level 3 (Tickets + Articles + Staff)', + 'LEVEL_1_DESCRIPTION': 'can only respond tickets and manage users.', + 'LEVEL_2_DESCRIPTION': 'can do every Level 1 does, can create or edit articles and it can create custom responses.', + 'LEVEL_3_DESCRIPTION': 'can do every Level 2 does, can create or edit staff members and can manage the whole system.', 'UPDATE_EMAIL': 'Update email', 'UPDATE_PASSWORD': 'Update password', 'UPDATE_LEVEL': 'Update level', @@ -118,11 +121,12 @@ export default { 'COMMENTS': 'Comments', 'DELETE_STAFF_MEMBER': 'Delete staff member', 'MAINTENANCE_MODE': 'Maintenance mode', + 'MAINTENANCE_MODE_INFO': 'It will temporary disable the system for regular users.', 'RECOVER_DEFAULT': 'Recover default', 'SUPPORT_CENTER_URL': 'Support Center URL', 'SUPPORT_CENTER_TITLE': 'Support Center Title', 'SUPPORT_CENTER_LAYOUT': 'Support Center Layout', - 'DEFAULT_TIMEZONE': 'Default Timezone', + 'DEFAULT_TIMEZONE': 'Default Timezone (GMT)', 'NOREPLY_EMAIL': 'Noreply Email', 'SMTP_USER': 'SMTP User', 'SMTP_SERVER': 'SMTP Server', @@ -135,7 +139,9 @@ export default { 'UPDATE_SETTINGS': 'Update settings', 'DEFAULT_LANGUAGE': 'Default Language', 'SUPPORTED_LANGUAGES': 'Supported Languages', + 'SUPPORTED_LANGUAGES_INFO': 'Supported languages are the languages that tickets can be written in.', 'ALLOWED_LANGUAGES': 'Allowed Languages', + 'ALLOWED_LANGUAGES_INFO': 'Allowed languages are the languages that can be used by an user.', 'SETTINGS_UPDATED': 'Settings have been updated', 'ON': 'On', 'OFF': 'Off', @@ -202,6 +208,7 @@ export default { 'DEPARTMENTS_DESCRIPTION': 'A department is a group where the tickets can go. They are used to categorize the tickets. You can assign them to other staff members.', 'MAINTENANCE_MODE_DESCRIPTION': 'The support system is in maintenance mode, thus unavailable at the moment. We will come back as soon as possible.', 'EMAIL_TEMPLATES_DESCRIPTION': 'Here you can edit the templates of the emails that will be sent to users. Remember that the double brackets curly braces indicate a variable value. For example, \'name\' represents the user\'s name.', + 'SYSTEM_PREFERENCES_DESCRIPTION': 'Here you can edit the preferences of the system.', //ERRORS 'EMAIL_OR_PASSWORD': 'Email or password invalid', @@ -223,6 +230,8 @@ export default { 'ERROR_RETRIEVING_BAN_LIST': 'An error occurred while trying to retrieve the list of banned emails.', 'ERROR_BANNING_EMAIL': 'An error occurred while trying to ban the email.', 'ERROR_RETRIEVING_ARTICLES': 'An error occurred while trying to retrieve articles.', + 'ERROR_LIST': 'Select at least one', + 'ERROR_URL': 'Invalid URL', //MESSAGES 'SIGNUP_SUCCESS': 'You have registered successfully in our support system.', diff --git a/client/src/lib-app/validations/list-validator.js b/client/src/lib-app/validations/list-validator.js new file mode 100644 index 00000000..eedc1663 --- /dev/null +++ b/client/src/lib-app/validations/list-validator.js @@ -0,0 +1,10 @@ +import Validator from 'lib-app/validations/validator'; + +class ListValidator extends Validator { + + validate(value, form) { + if (!value.length) return this.getError('ERROR_LIST'); + } +} + +export default ListValidator; \ No newline at end of file diff --git a/client/src/lib-app/validations/validations-factory.js b/client/src/lib-app/validations/validations-factory.js index 6749d3b5..28aab598 100644 --- a/client/src/lib-app/validations/validations-factory.js +++ b/client/src/lib-app/validations/validations-factory.js @@ -3,6 +3,7 @@ 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'; +import ListValidator from 'lib-app/validations/list-validator'; let validators = { 'DEFAULT': new Validator(), @@ -11,7 +12,9 @@ let validators = { 'EMAIL': new EmailValidator(), 'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'), 'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'), - 'REPEAT_PASSWORD': new RepeatPasswordValidator() + 'REPEAT_PASSWORD': new RepeatPasswordValidator(), + 'URL': new LengthValidator(5, 'ERROR_URL'), + 'LIST': new ListValidator() }; class ValidatorFactory {