diff --git a/client/package.json b/client/package.json index 09757c3a..2cf86903 100644 --- a/client/package.json +++ b/client/package.json @@ -55,6 +55,8 @@ "dependencies": { "app-module-path": "^1.0.3", "classnames": "^2.1.3", + "draft-js": "^0.8.1", + "draft-js-export-html": "^0.4.0", "jquery": "^2.1.4", "keycode": "^2.1.4", "localStorage": "^1.0.3", diff --git a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js new file mode 100644 index 00000000..f23e7fbe --- /dev/null +++ b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js @@ -0,0 +1,119 @@ +import React from 'react'; +import ReCAPTCHA from 'react-google-recaptcha'; +import { browserHistory } from 'react-router'; + +import i18n from 'lib-app/i18n'; +import API from 'lib-app/api-call'; +import store from 'app/store'; +import SessionActions from 'actions/session-actions'; + +import Header from 'core-components/header'; +import Form from 'core-components/form'; +import FormField from 'core-components/form-field'; +import SubmitButton from 'core-components/submit-button'; +import Message from 'core-components/message'; + +class CreateTicketForm extends React.Component { + + static propTypes = { + userLogged: React.PropTypes.bool + }; + + static defaultProps = { + userLogged: true + }; + + constructor(props) { + super(props); + + this.state = { + loading: false, + message: null + }; + } + + render() { + return ( +
+
+
+ {(!this.props.userLogged) ? this.renderEmailAndName() : null} +
+ + +
+ + {(!this.props.userLogged) ? this.renderCaptcha() : null} + Create Ticket + + {this.renderMessage()} +
+ ); + } + + renderEmailAndName() { + return ( +
+ + +
+ ); + } + + renderCaptcha() { + return ( +
+ +
+ ); + } + + renderMessage() { + switch (this.state.message) { + case 'success': + return {i18n('TICKET_SENT')}; + case 'fail': + return {i18n('TICKET_SENT_ERROR')}; + default: + return null; + } + } + + onSubmit(formState) { + this.setState({ + loading: true + }); + + API.call({ + path: '/ticket/create', + data: formState + }).then(this.onTicketSuccess.bind(this)).catch(this.onTicketFail.bind(this)); + } + + onTicketSuccess() { + this.setState({ + loading: false, + message: 'success' + }); + + store.dispatch(SessionActions.getUserData()); + + setTimeout(() => {browserHistory.push('/dashboard')}, 2000); + } + + onTicketFail() { + this.setState({ + loading: false, + message: 'fail' + }); + } +} + +export default CreateTicketForm; \ No newline at end of file diff --git a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.scss b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.scss new file mode 100644 index 00000000..045c290c --- /dev/null +++ b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.scss @@ -0,0 +1,12 @@ +.create-ticket-form { + + &__message { + margin-top: 20px; + } + + &__captcha { + margin: 0 auto 20px; + height: 78px; + width: 304px; + } +} \ No newline at end of file diff --git a/client/src/app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page.js b/client/src/app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page.js index 9914bc12..596cc4a9 100644 --- a/client/src/app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page.js +++ b/client/src/app/main/dashboard/dashboard-create-ticket/dashboard-create-ticket-page.js @@ -1,11 +1,13 @@ import React from 'react'; +import CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form'; + class DashboardCreateTicketPage extends React.Component { render() { return (
- DASHBOARD CREATE TICKET +
); } diff --git a/client/src/app/main/dashboard/dashboard-layout.js b/client/src/app/main/dashboard/dashboard-layout.js index 93e66076..b1c5c25e 100644 --- a/client/src/app/main/dashboard/dashboard-layout.js +++ b/client/src/app/main/dashboard/dashboard-layout.js @@ -10,9 +10,11 @@ class DashboardLayout extends React.Component { return (this.props.session.logged) ? (
- - {this.props.children} - +
+ + {this.props.children} + +
) : null; } diff --git a/client/src/app/main/dashboard/dashboard-layout.scss b/client/src/app/main/dashboard/dashboard-layout.scss index a61782a9..49c597af 100644 --- a/client/src/app/main/dashboard/dashboard-layout.scss +++ b/client/src/app/main/dashboard/dashboard-layout.scss @@ -1,9 +1,5 @@ .dashboard { &__menu { - - } - - &__content { - + margin-bottom: 10px; } } \ No newline at end of file diff --git a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js index 184bf375..f033fb93 100644 --- a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js +++ b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.js @@ -1,6 +1,9 @@ import React from 'react'; import {connect} from 'react-redux'; +import i18n from 'lib-app/i18n'; + +import Header from 'core-components/header'; import Table from 'core-components/table'; import Button from 'core-components/button'; @@ -16,7 +19,7 @@ class DashboardListTicketsPage extends React.Component { render() { return (
-
Tickets
+
); diff --git a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss index 19223237..3c9439e1 100644 --- a/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss +++ b/client/src/app/main/dashboard/dashboard-list-tickets/dashboard-list-tickets-page.scss @@ -2,12 +2,6 @@ .dashboard-ticket-list { - &__header { - text-align: left; - font-variant: small-caps; - font-size: 16px; - } - &__number { text-align: left; } diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index c9b985df..0e99b8b9 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -12,8 +12,7 @@ import i18n from 'lib-app/i18n'; import SubmitButton from 'core-components/submit-button'; import Button from 'core-components/button'; import Form from 'core-components/form'; -import Input from 'core-components/input'; -import Checkbox from 'core-components/checkbox'; +import FormField from 'core-components/form-field'; import Widget from 'core-components/widget'; import WidgetTransition from 'core-components/widget-transition'; import Message from 'core-components/message'; @@ -53,9 +52,9 @@ class MainHomePageLoginWidget extends React.Component {
- - - + + +
LOG IN @@ -73,7 +72,7 @@ class MainHomePageLoginWidget extends React.Component {
- +
{i18n('RECOVER_PASSWORD')} diff --git a/client/src/app/main/main-home/main-home-page.js b/client/src/app/main/main-home/main-home-page.js index f8a512a2..c691a717 100644 --- a/client/src/app/main/main-home/main-home-page.js +++ b/client/src/app/main/main-home/main-home-page.js @@ -4,15 +4,19 @@ import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-wid import MainHomePagePortal from 'app/main/main-home/main-home-page-portal'; class MainHomePage extends React.Component { - - render() { - return ( -
- - -
- ); - } + + render() { + return ( +
+
+ +
+
+ +
+
+ ); + } } export default MainHomePage; \ No newline at end of file diff --git a/client/src/app/main/main-layout-header.js b/client/src/app/main/main-layout-header.js index 43bc7e45..e636d73c 100644 --- a/client/src/app/main/main-layout-header.js +++ b/client/src/app/main/main-layout-header.js @@ -55,7 +55,7 @@ class MainLayoutHeader extends React.Component { return { className: 'main-layout-header__languages', items: this.getLanguageList(), - selectedIndex: Object.values(codeLanguages).indexOf(this.props.config.language), + selectedIndex: Object.keys(codeLanguages).map((key) => codeLanguages[key]).indexOf(this.props.config.language), onChange: this.changeLanguage.bind(this) }; } diff --git a/client/src/app/main/main-layout.js b/client/src/app/main/main-layout.js index 74be940d..5a259324 100644 --- a/client/src/app/main/main-layout.js +++ b/client/src/app/main/main-layout.js @@ -9,7 +9,7 @@ class MainLayout extends React.Component { return (
-
+
{this.props.children}
diff --git a/client/src/app/main/main-recover-password/main-recover-password-page.js b/client/src/app/main/main-recover-password/main-recover-password-page.js index 451bcc05..ce146a13 100644 --- a/client/src/app/main/main-recover-password/main-recover-password-page.js +++ b/client/src/app/main/main-recover-password/main-recover-password-page.js @@ -6,7 +6,7 @@ import API from 'lib-app/api-call'; import Widget from 'core-components/widget'; import Form from 'core-components/form'; -import Input from 'core-components/input'; +import FormField from 'core-components/form-field'; import SubmitButton from 'core-components/submit-button'; import Message from 'core-components/message'; @@ -32,8 +32,8 @@ class MainRecoverPasswordPage extends React.Component {
- - + +
{i18n('SUBMIT')} diff --git a/client/src/app/main/main-signup/main-signup-page.js b/client/src/app/main/main-signup/main-signup-page.js index 44c0e987..86d0fc00 100644 --- a/client/src/app/main/main-signup/main-signup-page.js +++ b/client/src/app/main/main-signup/main-signup-page.js @@ -7,7 +7,7 @@ import API from 'lib-app/api-call'; import SubmitButton from 'core-components/submit-button'; import Message from 'core-components/message'; import Form from 'core-components/form'; -import Input from 'core-components/input'; +import FormField from 'core-components/form-field'; import Widget from 'core-components/widget'; @@ -28,10 +28,10 @@ class MainSignUpPageWidget extends React.Component {
- - - - + + + +
@@ -64,10 +64,13 @@ class MainSignUpPageWidget extends React.Component { }; } - getInputProps() { + getInputProps(password) { return { - inputType: 'secondary', - className: 'signup-widget__input' + className: 'signup-widget__input', + fieldProps: { + size: 'medium', + password: password + } }; } diff --git a/client/src/core-components/__tests__/form-field-test.js b/client/src/core-components/__tests__/form-field-test.js new file mode 100644 index 00000000..43fece59 --- /dev/null +++ b/client/src/core-components/__tests__/form-field-test.js @@ -0,0 +1,449 @@ +const Input = ReactMock(); +const Checkbox = ReactMock(); +const DropDown = ReactMock(); +const TextEditor = ReactMock(); + +const {EditorState} = require('draft-js'); + +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 +}); + + +describe('FormField component', function () { + let component, innerField; + + function renderFormField(props = { field: 'input'}) { + let fields = { + 'input': Input, + 'checkbox': Checkbox, + 'select': DropDown, + 'textarea': TextEditor + }; + + component = reRenderIntoDocument( + + ); + innerField = TestUtils.scryRenderedComponentsWithType(component, fields[props.field])[0]; + } + + 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 EditorState).to.equal(true); + }); + }); + + describe('when rendering an input field', function () { + + beforeEach(function () { + renderFormField({ + field: 'input', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: 'MOCK_ERROR', + value: 'VALUE_MOCK', + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + }); + + it('should be wrapped in a label', function () { + expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + + renderFormField({ + field: 'input', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: '', + value: 'VALUE_MOCK', + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select'); + }); + + it('should render error and label in right order and pass correct classes', function () { + expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label'); + expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL'); + + expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField)); + + expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error'); + expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR'); + }); + + it('should pass props correctly to Input', function () { + expect(innerField.props).to.include({ + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + errored: true, + name: 'MOCK_NAME', + onBlur: component.props.onBlur, + required: true, + value: 'VALUE_MOCK' + }); + }); + + it('should pass disable field when context is loading', function () { + component.context.loading = true; + component.forceUpdate(); + + expect(innerField.props.disabled).to.equal(true); + component.context.loading = false; + }); + + it('should pass callbacks correctly', function () { + component.props.onChange.reset(); + component.props.onBlur.reset(); + + innerField.props.onChange({ target: { value: 'SOME_VALUE_2'}}); + innerField.props.onBlur(); + + expect(component.props.onBlur).to.have.been.called; + expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: 'SOME_VALUE_2'}}); + }); + + it('should pass focus to the field', function () { + innerField.focus = stub(); + component.focus(); + + expect(innerField.focus).to.have.been.called; + }); + }); + + describe('when rendering a checkbox field', function () { + + beforeEach(function () { + renderFormField({ + field: 'checkbox', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: 'MOCK_ERROR', + value: false, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + }); + + it('should be wrapped in a label', function () { + expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_checkbox'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored'); + + renderFormField({ + field: 'checkbox', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: '', + value: false, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_checkbox'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored'); + }); + + it('should render error and label in right order and pass correct classes', function () { + expect(ReactDOM.findDOMNode(component).children[0]).to.equal(ReactDOM.findDOMNode(innerField)); + + expect(ReactDOM.findDOMNode(component).children[1].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[1].className).to.equal('form-field__label'); + expect(ReactDOM.findDOMNode(component).children[1].textContent).to.equal('MOCK_LABEL'); + + expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error'); + expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR'); + }); + + it('should pass props correctly to Checkbox', function () { + expect(innerField.props).to.include({ + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + errored: true, + name: 'MOCK_NAME', + onBlur: component.props.onBlur, + required: true, + value: false + }); + }); + + it('should pass disable field when context is loading', function () { + component.context.loading = true; + component.forceUpdate(); + + expect(innerField.props.disabled).to.equal(true); + component.context.loading = false; + }); + + it('should pass callbacks correctly', function () { + component.props.onChange.reset(); + component.props.onBlur.reset(); + + innerField.props.onChange({ target: { checked: true }}); + innerField.props.onBlur(); + + expect(component.props.onBlur).to.have.been.called; + expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: true}}); + }); + + it('should pass focus to the field', function () { + innerField.focus = stub(); + component.focus(); + + expect(innerField.focus).to.have.been.called; + }); + }); + + describe('when rendering an select field', function () { + + beforeEach(function () { + renderFormField({ + field: 'select', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: 'MOCK_ERROR', + value: 5, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + items: [] + }, + onChange: stub(), + onBlur: stub() + }); + }); + + it('should be wrapped in a label', function () { + expect(ReactDOM.findDOMNode(component).tagName).to.equal('LABEL'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_select'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + + renderFormField({ + field: 'select', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: '', + value: 5, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + items: [] + }, + onChange: stub(), + onBlur: stub() + }); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_select'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + }); + + it('should render error and label in right order and pass correct classes', function () { + expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label'); + expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL'); + + expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField)); + + expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error'); + expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR'); + }); + + it('should pass props correctly to DropDown', function () { + expect(innerField.props).to.include({ + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + errored: true, + name: 'MOCK_NAME', + onBlur: component.props.onBlur, + required: true, + selectedIndex: 5 + }); + }); + + it('should pass disable field when context is loading', function () { + component.context.loading = true; + component.forceUpdate(); + + expect(innerField.props.disabled).to.equal(true); + component.context.loading = false; + }); + + it('should pass callbacks correctly', function () { + component.props.onChange.reset(); + component.props.onBlur.reset(); + + innerField.props.onChange({index: 2}); + innerField.props.onBlur(); + + expect(component.props.onBlur).to.have.been.called; + expect(component.props.onChange).to.have.been.calledWithMatch({target: {value: 2}}); + }); + + it('should pass focus to the field', function () { + innerField.focus = stub(); + component.focus(); + + expect(innerField.focus).to.have.been.called; + }); + }); + + describe('when rendering an textarea field', function () { + + beforeEach(function () { + renderFormField({ + field: 'textarea', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: 'MOCK_ERROR', + value: {value: 'VALUE_MOCk'}, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + }); + + it('should be wrapped in a div', function () { + expect(ReactDOM.findDOMNode(component).tagName).to.equal('DIV'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + + renderFormField({ + field: 'textarea', + name: 'MOCK_NAME', + label: 'MOCK_LABEL', + error: '', + value: {value: 'VALUE_MOCk'}, + required: true, + validation: 'MOCK_VALIDATION', + fieldProps: { + prop1: 'value1', + prop2: 'value2', + prop3: 'value3' + }, + onChange: stub(), + onBlur: stub() + }); + expect(ReactDOM.findDOMNode(component).className).to.include('form-field'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_errored'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_checkbox'); + expect(ReactDOM.findDOMNode(component).className).to.not.include('form-field_select'); + }); + + it('should render error and label in right order and pass correct classes', function () { + expect(ReactDOM.findDOMNode(component).children[0].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[0].className).to.equal('form-field__label'); + expect(ReactDOM.findDOMNode(component).children[0].textContent).to.equal('MOCK_LABEL'); + + expect(ReactDOM.findDOMNode(component).children[1]).to.equal(ReactDOM.findDOMNode(innerField)); + + expect(ReactDOM.findDOMNode(component).children[2].tagName).to.equal('SPAN'); + expect(ReactDOM.findDOMNode(component).children[2].className).to.equal('form-field__error'); + expect(ReactDOM.findDOMNode(component).children[2].textContent).to.equal('MOCK_ERROR'); + }); + + it('should pass props correctly to TextEditor', function () { + expect(innerField.props).to.include({ + prop1: 'value1', + prop2: 'value2', + prop3: 'value3', + errored: true, + name: 'MOCK_NAME', + onBlur: component.props.onBlur, + required: true, + }); + expect(innerField.props.value).to.deep.equal({value: 'VALUE_MOCk'}); + }); + + it('should pass disable field when context is loading', function () { + component.context.loading = true; + component.forceUpdate(); + + expect(innerField.props.disabled).to.equal(true); + component.context.loading = false; + }); + + it('should pass callbacks correctly', function () { + component.props.onChange.reset(); + component.props.onBlur.reset(); + + innerField.props.onChange({ target: { value: 'SOME_VALUE_2'}}); + innerField.props.onBlur(); + + expect(component.props.onBlur).to.have.been.called; + expect(component.props.onChange).to.have.been.calledWithMatch({target: { value: 'SOME_VALUE_2'}}); + }); + + it('should pass focus to the field', function () { + innerField.focus = stub(); + component.focus(); + + expect(innerField.focus).to.have.been.called; + }); + }); +}); \ No newline at end of file diff --git a/client/src/core-components/__tests__/form-test.js b/client/src/core-components/__tests__/form-test.js index 46136923..a778d6b4 100644 --- a/client/src/core-components/__tests__/form-test.js +++ b/client/src/core-components/__tests__/form-test.js @@ -1,11 +1,16 @@ // MOCKS const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock'); -const Input = ReactMock(); +const FormField = ReactMock(); +const {EditorState} = require('draft-js'); +const draftJsExportHTML = { + stateToHTML: stub().returns('HTML_CODE') +}; // COMPONENT const Form = requireUnit('core-components/form', { 'lib-app/validations/validations-factory': ValidationFactoryMock, - 'core-components/input': Input + 'draft-js-export-html': draftJsExportHTML, + 'core-components/form-field': FormField }); describe('Form component', function () { @@ -15,13 +20,13 @@ describe('Form component', function () { form = TestUtils.renderIntoDocument(
- - + +
- + ); - fields = TestUtils.scryRenderedComponentsWithType(form, Input); + fields = TestUtils.scryRenderedComponentsWithType(form, FormField); } function resetStubs() { @@ -117,7 +122,7 @@ describe('Form component', function () { }); afterEach(resetStubs); - it('should pass the errors to inputs', function () { + it('should pass the errors to fields', function () { expect(fields[0].props.error).to.equal('MOCK_ERROR_CONTROLLED'); expect(fields[1].props.error).to.equal(undefined); }); @@ -138,13 +143,13 @@ describe('Form component', function () { form = reRenderIntoDocument(
- - + +
- + ); - fields = TestUtils.scryRenderedComponentsWithType(form, Input); + fields = TestUtils.scryRenderedComponentsWithType(form, FormField); } setErrorsOrRender(); @@ -171,6 +176,19 @@ describe('Form component', function () { expect(form.props.onSubmit).to.have.been.calledWith(form.state.form); }); + it('should tranform EditorState to HTML usign draft-js-export-html library', function () { + draftJsExportHTML.stateToHTML.reset(); + form.state.form.first = EditorState.createEmpty(); + + TestUtils.Simulate.submit(ReactDOM.findDOMNode(form)); + expect(draftJsExportHTML.stateToHTML).to.have.been.calledWith(form.state.form.first.getCurrentContent()); + expect(form.props.onSubmit).to.have.been.calledWith({ + first: 'HTML_CODE', + second: 'value2', + third: 'value3' + }); + }); + it('should validate all fields and not call onSubmit if there are errors', function () { ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR'); ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2'); diff --git a/client/src/core-components/button.js b/client/src/core-components/button.js index 755004f9..8ad69522 100644 --- a/client/src/core-components/button.js +++ b/client/src/core-components/button.js @@ -6,6 +6,9 @@ import classNames from 'classnames'; // CORE LIBS import callback from 'lib-core/callback'; +// CORE COMPONENTS +import Icon from 'core-components/icon'; + class Button extends React.Component { static contextTypes = { @@ -16,6 +19,7 @@ class Button extends React.Component { children: React.PropTypes.node, type: React.PropTypes.oneOf([ 'primary', + 'primary-icon', 'clean', 'link' ]), @@ -23,7 +27,8 @@ class Button extends React.Component { to: React.PropTypes. string.isRequired, params: React.PropTypes.object, query: React.PropTypes.query - }) + }), + iconName: React.PropTypes.string }; static defaultProps = { @@ -33,7 +38,7 @@ class Button extends React.Component { render() { return ( ); } @@ -45,6 +50,7 @@ class Button extends React.Component { props.className = this.getClass(); delete props.route; + delete props.iconName; delete props.type; return props; diff --git a/client/src/core-components/button.scss b/client/src/core-components/button.scss index 974cf04e..34c58bea 100644 --- a/client/src/core-components/button.scss +++ b/client/src/core-components/button.scss @@ -2,7 +2,8 @@ .button { - &-primary { + &-primary, + &-primary-icon { background-color: $primary-red; border: solid transparent; border-radius: 4px; @@ -12,6 +13,11 @@ width: 239px; } + &-primary-icon { + width: initial; + height: initial; + } + &-clean { background: none; border: none; diff --git a/client/src/core-components/checkbox.js b/client/src/core-components/checkbox.js index 8394772d..28a48ffa 100644 --- a/client/src/core-components/checkbox.js +++ b/client/src/core-components/checkbox.js @@ -28,13 +28,12 @@ class CheckBox extends React.Component { render() { return ( - + ); } @@ -49,7 +48,7 @@ class CheckBox extends React.Component { props.onChange = callback(this.handleChange.bind(this), this.props.onChange); delete props.alignment; - delete props.error; + delete props.errored; delete props.label; delete props.value; diff --git a/client/src/core-components/checkbox.scss b/client/src/core-components/checkbox.scss index 3213846f..1e1301d6 100644 --- a/client/src/core-components/checkbox.scss +++ b/client/src/core-components/checkbox.scss @@ -18,12 +18,6 @@ } } - &--label { - margin-left: 10px; - font-size: 14px; - user-select: none; - } - &_checked { .checkbox--icon { color: $primary-red; diff --git a/client/src/core-components/drop-down.js b/client/src/core-components/drop-down.js index 969bc589..70c3500d 100644 --- a/client/src/core-components/drop-down.js +++ b/client/src/core-components/drop-down.js @@ -10,7 +10,8 @@ class DropDown extends React.Component { static propTypes = { defaultSelectedIndex: React.PropTypes.number, selectedIndex: React.PropTypes.number, - items: Menu.propTypes.items + items: Menu.propTypes.items, + size: React.PropTypes.oneOf(['small', 'medium', 'large']) }; static defaultProps = { @@ -66,7 +67,7 @@ class DropDown extends React.Component { }; return ( -
+
); @@ -76,11 +77,11 @@ class DropDown extends React.Component { var iconNode = null; if (item.icon) { - iconNode = ; + iconNode = ; } return ( -
+
{iconNode}{item.content}
); @@ -91,6 +92,7 @@ class DropDown extends React.Component { 'drop-down': true, 'drop-down_closed': !this.state.opened, + ['drop-down_' + this.props.size]: true, [this.props.className]: (this.props.className) }; diff --git a/client/src/core-components/drop-down.scss b/client/src/core-components/drop-down.scss index 74eb1270..12e74a5f 100644 --- a/client/src/core-components/drop-down.scss +++ b/client/src/core-components/drop-down.scss @@ -7,19 +7,24 @@ user-select: none; cursor: pointer; - &--current-item { + &__current-item { background-color: $light-grey; border-radius: 4px 4px 0 0; color: $primary-black; padding: 6px; + + &:focus { + outline: none; + background-color: $medium-grey; + } } - &--current-item-icon { + &__current-item-icon { margin-right: 8px; margin-bottom: 2px; } - &--list-container { + &__list-container { position: absolute; width: 150px; z-index: 5; @@ -27,8 +32,21 @@ &_closed { - .drop-down--list-container { + .drop-down__list-container { pointer-events: none; } } + + &_medium { + width: 200px; + + .drop-down__current-item { + border-radius: 4px; + } + + .drop-down__list-container { + width: 200px; + border: 1px solid $light-grey; + } + } } \ No newline at end of file diff --git a/client/src/core-components/form-field.js b/client/src/core-components/form-field.js new file mode 100644 index 00000000..98b9dd35 --- /dev/null +++ b/client/src/core-components/form-field.js @@ -0,0 +1,151 @@ +import React from 'react'; +import {EditorState} from 'draft-js'; +import classNames from 'classnames'; +import _ from 'lodash'; + +import Input from 'core-components/input'; +import DropDown from 'core-components/drop-down'; +import Checkbox from 'core-components/checkbox'; +import TextEditor from 'core-components/text-editor'; + +class FormField extends React.Component { + static contextTypes = { + loading: React.PropTypes.bool + }; + + static propTypes = { + validation: React.PropTypes.string, + onChange: React.PropTypes.func, + onBlur: React.PropTypes.func, + required: React.PropTypes.bool, + error: React.PropTypes.string, + value: React.PropTypes.any, + field: React.PropTypes.oneOf(['input', 'textarea', 'select', 'checkbox']), + fieldProps: React.PropTypes.object + }; + + static defaultProps = { + field: 'input' + }; + + static getDefaultValue(field) { + if (field === 'input') { + return ''; + } + else if (field === 'checkbox') { + return false; + } + else if (field === 'textarea') { + return EditorState.createEmpty(); + } + else if (field === 'select') { + return 0; + } + } + + render() { + const Wrapper = (this.props.field === 'textarea') ? 'div' : 'label'; + const fieldContent = [ + {this.props.label}, + this.renderField(), + this.renderError() + ]; + + if (this.props.field === 'checkbox') { + fieldContent.swap(0, 1); + } + return ( + + {fieldContent} + + ); + } + + renderField() { + const Field = { + 'input': Input, + 'textarea': TextEditor, + 'select': DropDown, + 'checkbox': Checkbox + }[this.props.field]; + + return ; + } + + renderError() { + let error = null; + + if (this.props.error) { + error = {this.props.error}; + } + + return error; + } + + getClass() { + let classes = { + 'form-field': true, + 'form-field_errored': (this.props.error), + 'form-field_checkbox': (this.props.field === 'checkbox'), + 'form-field_select': (this.props.field === 'select'), + + [this.props.className]: (this.props.className) + }; + + return classNames(classes); + } + + getFieldProps() { + let props = _.extend({}, this.props.fieldProps, { + disabled: this.context.loading, + errored: !!this.props.error, + name: this.props.name, + placeholder: this.props.placeholder, + key: 'nativeField', + onChange: this.onChange.bind(this), + onBlur: this.props.onBlur, + ref: 'nativeField', + required: this.props.required + }); + + if (this.props.field === 'select') { + props.selectedIndex = this.props.value; + } else { + props.value = this.props.value; + } + + return props; + } + + onChange(nativeEvent) { + let event = nativeEvent; + + if (this.props.field === 'checkbox') { + event = { + target: { + value: event.target.checked + } + }; + } + + if (this.props.field === 'select') { + event = { + target: { + value: event.index + } + }; + } + + if (this.props.onChange) { + this.props.onChange(event) + } + } + + focus() { + if (this.refs.nativeField) { + this.refs.nativeField.focus(); + } + } +} + +export default FormField; \ No newline at end of file diff --git a/client/src/core-components/form-field.scss b/client/src/core-components/form-field.scss new file mode 100644 index 00000000..0b2ee428 --- /dev/null +++ b/client/src/core-components/form-field.scss @@ -0,0 +1,42 @@ +@import "../scss/vars"; + +.form-field { + display: block; + margin-bottom: 20px; + + &__label { + color: $primary-black; + font-size: 15px; + display: block; + padding: 3px 0; + text-align: left; + } + + &_errored { + .form-field__error { + color: $primary-red; + font-size: $font-size--sm; + display: block; + position: absolute; + } + } + + &_checkbox { + display: inline-block; + margin-bottom: 0; + + .form-field__label { + display: inline-block; + + margin-left: 10px; + font-size: 14px; + user-select: none; + } + } + + &_select { + .form-field__label { + padding-bottom: 10px; + } + } +} \ No newline at end of file diff --git a/client/src/core-components/form.js b/client/src/core-components/form.js index 54ec26e3..8de7cbd8 100644 --- a/client/src/core-components/form.js +++ b/client/src/core-components/form.js @@ -1,12 +1,13 @@ import React from 'react'; import _ from 'lodash'; import classNames from 'classnames'; +import {EditorState} from 'draft-js'; +import {stateToHTML} from 'draft-js-export-html'; import {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs'; import ValidationFactory from 'lib-app/validations/validations-factory'; -import Input from 'core-components/input'; -import Checkbox from 'core-components/checkbox'; +import FormField from 'core-components/form-field'; class Form extends React.Component { @@ -75,14 +76,14 @@ class Form extends React.Component { getFieldProps({props, type}) { let additionalProps = {}; - if (type === Input || type === Checkbox) { + if (this.isValidField({type})) { let fieldName = props.name; additionalProps = { ref: fieldName, value: this.state.form[fieldName] || props.value, error: this.getFieldError(fieldName), - onChange: this.handleFieldChange.bind(this, fieldName, type), + onChange: this.handleFieldChange.bind(this, fieldName), onBlur: this.validateField.bind(this, fieldName) } } @@ -138,13 +139,8 @@ class Form extends React.Component { reactDFS(this.props.children, (child) => { - if (this.isValidFieldType(child)) { - if (child.type === Input) { - form[child.props.name] = child.props.value || ''; - } - else if (child.type === Checkbox) { - form[child.props.name] = child.props.checked || false; - } + if (this.isValidField(child)) { + form[child.props.name] = child.props.value || FormField.getDefaultValue(child.props.field); if (child.props.required) { validations[child.props.name] = ValidationFactory.getValidator(child.props.validation || 'DEFAULT'); @@ -161,29 +157,33 @@ class Form extends React.Component { handleSubmit(event) { event.preventDefault(); + const form = _.mapValues(this.state.form, (field) => { + if (field instanceof EditorState) { + return stateToHTML(field.getCurrentContent()); + } else { + return field; + } + }); + if (this.hasFormErrors()) { this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this)); } else if (this.props.onSubmit) { - this.props.onSubmit(this.state.form); + this.props.onSubmit(form); } } - handleFieldChange(fieldName, type, event) { + handleFieldChange(fieldName, event) { let form = _.clone(this.state.form); form[fieldName] = event.target.value; - if (type === Checkbox) { - form[fieldName] = event.target.checked || false; - } - this.setState({ form: form }); } - isValidFieldType(child) { - return child.type === Input || child.type === Checkbox; + isValidField(node) { + return node.type === FormField; } hasFormErrors() { diff --git a/client/src/core-components/form.scss b/client/src/core-components/form.scss deleted file mode 100644 index cc2c9aac..00000000 --- a/client/src/core-components/form.scss +++ /dev/null @@ -1,5 +0,0 @@ -.form { - .input { - margin-bottom: 20px; - } -} \ No newline at end of file diff --git a/client/src/core-components/header.js b/client/src/core-components/header.js new file mode 100644 index 00000000..31741183 --- /dev/null +++ b/client/src/core-components/header.js @@ -0,0 +1,27 @@ +import React from 'react'; + +class Header extends React.Component { + static propTypes = { + title: React.PropTypes.string.isRequired, + description: React.PropTypes.string + }; + + render() { + return ( +
+

{this.props.title}

+ {(this.props.description) ? this.renderDescription() : null} +
+ ) + } + + renderDescription() { + return ( +
+ {this.props.description} +
+ ) + } +} + +export default Header; \ No newline at end of file diff --git a/client/src/core-components/header.scss b/client/src/core-components/header.scss new file mode 100644 index 00000000..ebb0cc3b --- /dev/null +++ b/client/src/core-components/header.scss @@ -0,0 +1,17 @@ +@import '../scss/vars'; + +.header { + margin-bottom: 30px; + text-align: left; + + &__title { + margin: 5px 0 14px; + font-size: 24px; + } + + &__description { + font-size: 13px; + color: $dark-grey; + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; + } +} \ No newline at end of file diff --git a/client/src/core-components/input.js b/client/src/core-components/input.js index 44dec9fe..d1a01476 100644 --- a/client/src/core-components/input.js +++ b/client/src/core-components/input.js @@ -14,7 +14,7 @@ class Input extends React.Component { value: React.PropTypes.string, validation: React.PropTypes.string, onChange: React.PropTypes.func, - inputType: React.PropTypes.string, + size: React.PropTypes.oneOf(['small', 'medium', 'large']), password: React.PropTypes.bool, required: React.PropTypes.bool, icon: React.PropTypes.string, @@ -22,30 +22,18 @@ class Input extends React.Component { }; static defaultProps = { - inputType: 'primary' + size: 'small' }; render() { return ( - + ); } - renderError() { - let error = null; - - if (this.props.error){ - error = {this.props.error} ; - } - - return error; - } - renderIcon() { let icon = null; @@ -62,12 +50,12 @@ class Input extends React.Component { props['aria-required'] = this.props.required; props.type = (this.props.password) ? 'password' : 'text'; props.ref = 'nativeInput'; - props.disabled = this.context.loading; + delete props.errored; delete props.required; delete props.validation; delete props.inputType; - delete props.error; + delete props.errored; delete props.password; return props; @@ -77,8 +65,8 @@ class Input extends React.Component { let classes = { 'input': true, 'input_with-icon': (this.props.icon), - 'input_with-error': (this.props.error), - ['input_' + this.props.inputType]: true, + 'input_errored': (this.props.errored), + ['input_' + this.props.size]: true, [this.props.className]: (this.props.className) }; diff --git a/client/src/core-components/input.scss b/client/src/core-components/input.scss index 4e5066f9..d3b88eee 100644 --- a/client/src/core-components/input.scss +++ b/client/src/core-components/input.scss @@ -19,22 +19,18 @@ } } - &__label { - color: $primary-black; - font-size: 15px; - display: block; - padding: 3px 0; - text-align: left; - } - - &_primary { + &_small { width: 200px; } - &_secondary { + &_medium { width: 250px; } + &_large { + width: 350px; + } + &_with-icon { position: relative; @@ -52,16 +48,9 @@ } } - &_with-error { - .input__error { - color: $primary-red; - font-size: $font-size--sm; - display: block; - position: absolute; - } + &_errored { .input__text { border: 1px solid $primary-red; } } - } \ No newline at end of file diff --git a/client/src/core-components/text-editor.js b/client/src/core-components/text-editor.js new file mode 100644 index 00000000..d546d8a0 --- /dev/null +++ b/client/src/core-components/text-editor.js @@ -0,0 +1,118 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import classNames from 'classnames'; +import _ from 'lodash'; +import {Editor, EditorState, RichUtils} from 'draft-js'; +import Button from 'core-components/button'; + +class TextEditor extends React.Component { + static propTypes = { + errored: React.PropTypes.bool, + onChange: React.PropTypes.func, + value: React.PropTypes.object + }; + + constructor(props) { + super(props); + + this.state = { + editorState: EditorState.createEmpty(), + focused: false + }; + } + + render() { + return ( +
+ {this.renderEditOptions()} +
event.preventDefault()}> + event.stopPropagation()}> + + +
+
+ ); + } + + renderEditOptions() { + const onBoldClick = (event) => { + event.preventDefault(); + this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD')); + }; + const onItalicsClick = (event) => { + event.preventDefault(); + this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'ITALIC')); + }; + const onUnderlineClick = (event) => { + event.preventDefault(); + this.onEditorChange(RichUtils.toggleInlineStyle(this.state.editorState, 'UNDERLINE')); + }; + + + return ( +
+
+ ) + } + + getClass() { + let classes = { + 'text-editor': true, + 'text-editor_errored': (this.props.errored), + 'text-editor_focused': (this.state.focused), + + [this.props.className]: (this.props.className) + }; + + return classNames(classes); + } + + getEditorProps() { + return { + editorState: this.props.value || this.state.editorState, + ref: 'editor', + onChange: this.onEditorChange.bind(this), + onFocus: this.onEditorFocus.bind(this), + onBlur: this.onBlur.bind(this) + }; + } + + onEditorChange(editorState) { + this.setState({editorState}); + + if (this.props.onChange) { + this.props.onChange({ + target: { + value: editorState + } + }); + } + } + + onEditorFocus(event) { + this.setState({focused: true}); + + if(this.props.onFocus) { + this.props.onFocus(event) + } + } + + onBlur(event) { + this.setState({focused: false}); + + if(this.props.onBlur) { + this.props.onBlur(event) + } + } + + focus() { + if (this.refs.editor) { + this.refs.editor.focus(); + } + } +} + +export default TextEditor; \ No newline at end of file diff --git a/client/src/core-components/text-editor.scss b/client/src/core-components/text-editor.scss new file mode 100644 index 00000000..c1536d01 --- /dev/null +++ b/client/src/core-components/text-editor.scss @@ -0,0 +1,39 @@ +@import "../scss/vars"; + +.text-editor { + + &__editor { + border: 1px solid $grey; + border-radius: 3px; + padding: 8px; + width: 100%; + height: 200px; + text-align: left; + overflow: auto; + + &:hover { + border-color: $medium-grey; + } + } + + &__options { + text-align: left; + margin-bottom: 5px; + + .button { + margin-right: 3px; + } + } + + &_focused { + .text-editor__editor { + border-color: $primary-blue; + } + } + + &_errored { + .text-editor__editor { + border: 1px solid $primary-red; + } + } +} \ No newline at end of file diff --git a/client/src/data/fixtures/ticket-fixtures.js b/client/src/data/fixtures/ticket-fixtures.js new file mode 100644 index 00000000..16d169cd --- /dev/null +++ b/client/src/data/fixtures/ticket-fixtures.js @@ -0,0 +1,25 @@ +module.exports = [ + { + path: '/ticket/create', + time: 2000, + response: function (data) { + let response; + + if (data.title !== 'error') { + response = { + status: 'success', + data: { + 'ticketNumber': 121444 + } + }; + } else { + response = { + status: 'fail', + message: 'Ticket could not be created' + }; + } + + return response; + } + } +]; \ No newline at end of file diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index acd007f8..940b4baf 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -9,23 +9,29 @@ export default { 'NEW_PASSWORD': 'New password', 'REPEAT_NEW_PASSWORD': 'Repeat new password', 'BACK_LOGIN_FORM': 'Back to login form', - 'TICKET_LIST': 'Ticket List', - 'CREATE_TICKET': 'Create Ticket', 'VIEW_ARTICLES': 'View Articles', 'EDIT_PROFILE': 'Edit Profile', 'CLOSE_SESSION': 'Close session', + 'CREATE_TICKET': 'Create Ticket', + 'CREATE_TICKET_DESCRIPTION': 'This is a form for creating tickets. Fill the form and send us your issues/doubts/suggestions. Our support system will answer it as soon as possible.', + 'TICKET_LIST': 'Ticket List', + 'TICKET_LIST_DESCRIPTION': 'Here you can find a list of all tickets you have sent to our support team.', //ERRORS 'EMAIL_NOT_EXIST': 'Email does not exist', 'ERROR_EMPTY': 'Invalid value', 'ERROR_PASSWORD': 'Invalid password', 'ERROR_NAME': 'Invalid name', + 'ERROR_TITLE': 'Invalid title', 'ERROR_EMAIL': 'Invalid email', + 'ERROR_CONTENT_SHORT': 'Content too short', 'PASSWORD_NOT_MATCH': 'Password does not match', 'INVALID_RECOVER': 'Invalid recover data', + 'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.', //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, please try to log in or recover password' }; \ No newline at end of file diff --git a/client/src/index.js b/client/src/index.js index 7d53e718..00c95300 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -7,6 +7,13 @@ import ConfigActions from 'actions/config-actions'; import routes from 'app/Routes'; import store from 'app/store'; +Array.prototype.swap = function (x,y) { + var b = this[x]; + this[x] = this[y]; + this[y] = b; + return this; +}; + if ( process.env.NODE_ENV !== 'production' ) { // Enable React devtools window.React = React; diff --git a/client/src/lib-app/fixtures-loader.js b/client/src/lib-app/fixtures-loader.js index 8cbb9dbd..fcf645a2 100644 --- a/client/src/lib-app/fixtures-loader.js +++ b/client/src/lib-app/fixtures-loader.js @@ -17,6 +17,7 @@ let fixtures = (function () { // FIXTURES fixtures.add(require('data/fixtures/user-fixtures')); +fixtures.add(require('data/fixtures/ticket-fixtures')); fixtures.add(require('data/fixtures/system-fixtures')); _.each(fixtures.getAll(), function (fixture) { diff --git a/client/src/lib-app/validations/length-validator.js b/client/src/lib-app/validations/length-validator.js index b91e3e16..3951c31e 100644 --- a/client/src/lib-app/validations/length-validator.js +++ b/client/src/lib-app/validations/length-validator.js @@ -1,3 +1,5 @@ +import {EditorState} from 'draft-js'; + import Validator from 'lib-app/validations/validator'; class LengthValidator extends Validator { @@ -9,6 +11,10 @@ class LengthValidator extends Validator { } validate(value, form) { + if (value instanceof EditorState) { + value = value.getCurrentContent().getPlainText(); + } + if (value.length < this.minlength) return this.getError(this.errorKey); } } diff --git a/client/src/lib-app/validations/validations-factory.js b/client/src/lib-app/validations/validations-factory.js index 00e443f9..6749d3b5 100644 --- a/client/src/lib-app/validations/validations-factory.js +++ b/client/src/lib-app/validations/validations-factory.js @@ -7,7 +7,9 @@ import LengthValidator from 'lib-app/validations/length-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')), 'EMAIL': new EmailValidator(), + 'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'), 'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'), 'REPEAT_PASSWORD': new RepeatPasswordValidator() }; diff --git a/client/src/lib-app/validations/validator.js b/client/src/lib-app/validations/validator.js index 5eeb7e35..b02ab078 100644 --- a/client/src/lib-app/validations/validator.js +++ b/client/src/lib-app/validations/validator.js @@ -1,4 +1,6 @@ -const i18n = require('lib-app/i18n'); +import {EditorState} from 'draft-js'; + +import i18n from 'lib-app/i18n'; class Validator { constructor(validator = null) { @@ -18,7 +20,11 @@ class Validator { } validate(value, form) { - if (!value.length) return this.getError('ERROR_EMPTY'); + if (value instanceof EditorState) { + value = value.getCurrentContent().getPlainText() + } + + if (value.length === 0) return this.getError('ERROR_EMPTY'); } getError(errorKey) { diff --git a/client/src/lib-test/preprocessor.js b/client/src/lib-test/preprocessor.js index 59744b44..e3a1e44d 100644 --- a/client/src/lib-test/preprocessor.js +++ b/client/src/lib-test/preprocessor.js @@ -34,4 +34,11 @@ global.reRenderIntoDocument = (function () { })(); global.ReduxMock = { connect: stub().returns(stub().returnsArg(0)) -}; \ No newline at end of file +}; + +Array.prototype.swap = function (x,y) { + var b = this[x]; + this[x] = this[y]; + this[y] = b; + return this; +};