Merged in 184-add-info-tooltip-form-fields (pull request #113)

184 add info tooltip form fields
This commit is contained in:
Ivan Diaz 2017-01-06 16:36:37 -03:00
commit e9854341f0
15 changed files with 159 additions and 33 deletions

View File

@ -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;
}
}
}

View File

@ -6,4 +6,5 @@
padding: 2px;
border-radius: 4px;
text-align: center;
user-select: none;
}

View File

@ -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 (
<div className="admin-panel-system-preferences">
<Header title={i18n('SYSTEM_PREFERENCES')} description="Here you can adjust your system preferences :)"/>
<Form values={this.state.values} onChange={values => this.setState({values, message: null})} onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}>
<Header title={i18n('SYSTEM_PREFERENCES')} description={i18n('SYSTEM_PREFERENCES_DESCRIPTION')}/>
<Form values={this.state.values} onChange={this.onFormChange.bind(this)} onSubmit={this.onSubmit.bind(this)} loading={this.state.loading}>
<div className="row">
<div className="col-md-12">
<div className="admin-panel-system-preferences__maintenance">
<span>Maintenance Mode</span>
<span>{i18n('MAINTENANCE_MODE')} <InfoTooltip text={i18n('MAINTENANCE_MODE_INFO')} /></span>
<FormField className="admin-panel-system-preferences__maintenance-field" name="maintenance-mode" decorator={ToggleButton}/>
</div>
</div>
@ -48,11 +51,11 @@ class AdminPanelSystemPreferences extends React.Component {
</div>
<div className="row">
<div className="col-md-6">
<FormField label={i18n('SUPPORT_CENTER_URL')} fieldProps={{size: 'large'}} name="url"/>
<FormField label={i18n('SUPPORT_CENTER_LAYOUT')} fieldProps={{size: 'large', items: [{content: i18n('BOXED')}, {content: i18n('FULL_WIDTH')}]}} field="select" name="layout"/>
<FormField label={i18n('SUPPORT_CENTER_URL')} fieldProps={{size: 'large'}} name="url" validation="URL" required/>
<FormField label={i18n('SUPPORT_CENTER_LAYOUT')} fieldProps={{size: 'large', items: [{content: i18n('BOXED')}, {content: i18n('FULL_WIDTH')}]}} field="select" name="layout" />
</div>
<div className="col-md-6">
<FormField label={i18n('SUPPORT_CENTER_TITLE')} fieldProps={{size: 'large'}} name="title"/>
<FormField label={i18n('SUPPORT_CENTER_TITLE')} fieldProps={{size: 'large'}} name="title" validation="TITLE" required/>
<FormField label={i18n('DEFAULT_TIMEZONE')} fieldProps={{size: 'large'}} name="time-zone"/>
</div>
</div>
@ -85,16 +88,20 @@ class AdminPanelSystemPreferences extends React.Component {
<div className="col-md-6">
<div className="row admin-panel-system-preferences__languages">
<div className="col-md-6 admin-panel-system-preferences__languages-allowed">
<div>{i18n('ALLOWED_LANGUAGES')}</div>
<FormField name="allowedLanguages" field="checkbox-group" fieldProps={{items: this.getLanguageList()}} />
<div>{i18n('ALLOWED_LANGUAGES')} <InfoTooltip text={i18n('ALLOWED_LANGUAGES_INFO')} /></div>
<FormField name="allowedLanguages" field="checkbox-group" fieldProps={{items: this.getLanguageList()}} validation="LIST" required/>
</div>
<div className="col-md-6 admin-panel-system-preferences__languages-supported">
<div>{i18n('SUPPORTED_LANGUAGES')}</div>
<FormField name="supportedLanguages" field="checkbox-group" fieldProps={{items: this.getLanguageList()}} />
<div>{i18n('SUPPORTED_LANGUAGES')} <InfoTooltip text={i18n('SUPPORTED_LANGUAGES_INFO')} /></div>
<FormField name="supportedLanguages" field="checkbox-group" fieldProps={{items: this.getLanguageList()}} validation="LIST" required/>
</div>
</div>
</div>
<div className="col-md-6">
<FormField className="admin-panel-system-preferences__default-language-field" name="language" label={i18n('DEFAULT_LANGUAGE')} decorator={LanguageSelector} fieldProps={{
type: 'custom',
customList: (this.state.values.supportedLanguages && this.state.values.supportedLanguages.length) ? this.state.values.supportedLanguages.map(index => languageKeys[index]) : undefined
}} />
<FormField label={i18n('RECAPTCHA_PUBLIC_KEY')} fieldProps={{size: 'large'}} name="reCaptchaKey"/>
<FormField label={i18n('RECAPTCHA_PRIVATE_KEY')} fieldProps={{size: 'large'}} name="reCaptchaPrivate"/>
<div className="admin-panel-system-preferences__file-attachments">
@ -105,7 +112,6 @@ class AdminPanelSystemPreferences extends React.Component {
<span>{i18n('MAX_SIZE_KB')}</span>
<FormField className="admin-panel-system-preferences__max-size-field" fieldProps={{size: 'small'}} name="max-size"/>
</div>
<FormField className="admin-panel-system-preferences__default-language-field" name="language" label={i18n('DEFAULT_LANGUAGE')} decorator={LanguageSelector} />
</div>
</div>
<div className="row">
@ -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'
});

View File

@ -137,7 +137,7 @@ class StaffEditor extends React.Component {
<div>
<span className="separator staff-editor__separator"/>
<Form className="staff-editor__update-level" values={{level: this.state.level}} onChange={form => this.setState({level: form.level})} onSubmit={this.onSubmit.bind(this, 'LEVEL')}>
<FormField name="level" label={i18n('LEVEL')} field="select" fieldProps={{
<FormField name="level" label={i18n('LEVEL')} field="select" infoMessage={this.getStaffLevelInfo()} fieldProps={{
items: [{content: i18n('LEVEL_1')}, {content: i18n('LEVEL_2')}, {content: i18n('LEVEL_3')}],
size: 'large'
}} />
@ -219,6 +219,25 @@ class StaffEditor extends React.Component {
return SessionStore.getDepartments().map(department => department.name);
}
getStaffLevelInfo() {
return (
<div className="staff-editor__level-info">
<div className="staff-editor__level-info-box">
<span className="staff-editor__level-info-title">{i18n('LEVEL')} 1 </span>
<span className="staff-editor__level-info-description">{i18n('LEVEL_1_DESCRIPTION')}</span>
</div>
<div className="staff-editor__level-info-box">
<span className="staff-editor__level-info-title">{i18n('LEVEL')} 2 </span>
<span className="staff-editor__level-info-description">{i18n('LEVEL_2_DESCRIPTION')}</span>
</div>
<div className="staff-editor__level-info-box">
<span className="staff-editor__level-info-title">{i18n('LEVEL')} 3 </span>
<span className="staff-editor__level-info-description">{i18n('LEVEL_3_DESCRIPTION')}</span>
</div>
</div>
);
}
onSubmit(eventType, form) {
let departments;

View File

@ -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;
}
}
}

View File

@ -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 (
<ul className="checkbox-group">
<ul className={this.getClass()}>
{this.props.items.map(this.renderItem.bind(this))}
</ul>
);
@ -31,6 +33,17 @@ class CheckboxGroup extends React.Component {
</li>
);
}
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());

View File

@ -8,4 +8,9 @@
&__item {
margin: 10px 0;
}
&_error {
border: 1px solid $primary-red;
padding-left: 7px;
}
}

View File

@ -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 = [
<span className="form-field__label" key="label">{this.props.label}</span>,
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 (
<Wrapper className={this.getClass()}>
{fieldContent}
@ -66,6 +69,15 @@ class FormField extends React.Component {
);
}
renderLabel() {
return (
<span className="form-field__label" key="label">
{this.props.label}
{(this.props.infoMessage) ? <InfoTooltip text={this.props.infoMessage} className="form-field__info" /> : null}
</span>
)
}
renderField() {
let Field = {
'input': Input,

View File

@ -12,6 +12,12 @@
text-align: left;
}
&__info {
position: relative;
top: -1px;
left: 5px;
}
&_errored {
.form-field__error {
color: $primary-red;

View File

@ -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 (
<div className="info-tooltip__text">
<div className="info-tooltip__text-title">
@ -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);
}
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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.',

View File

@ -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;

View File

@ -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 {