commit
e87c38b743
|
@ -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",
|
||||
|
|
|
@ -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 (
|
||||
<div className="create-ticket-form">
|
||||
<Header title={i18n('CREATE_TICKET')} description={i18n('CREATE_TICKET_DESCRIPTION')} />
|
||||
<Form loading={this.state.loading} onSubmit={this.onSubmit.bind(this)}>
|
||||
{(!this.props.userLogged) ? this.renderEmailAndName() : null}
|
||||
<div className="row">
|
||||
<FormField className="col-md-7" label="Title" name="title" validation="TITLE" required field="input" fieldProps={{size: 'large'}}/>
|
||||
<FormField className="col-md-5" label="Department" name="departmentId" field="select" fieldProps={{
|
||||
items: [
|
||||
{content: 'Sales Support'},
|
||||
{content: 'Technical Issues'},
|
||||
{content: 'System and Administration'}
|
||||
],
|
||||
size: 'medium'
|
||||
}} />
|
||||
</div>
|
||||
<FormField label="Content" name="content" validation="TEXT_AREA" required field="textarea" />
|
||||
{(!this.props.userLogged) ? this.renderCaptcha() : null}
|
||||
<SubmitButton>Create Ticket</SubmitButton>
|
||||
</Form>
|
||||
{this.renderMessage()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderEmailAndName() {
|
||||
return (
|
||||
<div className="row">
|
||||
<FormField className="col-md-6" label="Email" name="email" validation="EMAIL" required field="input" fieldProps={{size: 'large'}}/>
|
||||
<FormField className="col-md-6" label="Full Name" name="name" validation="NAME" required field="input" fieldProps={{size: 'large'}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderCaptcha() {
|
||||
return (
|
||||
<div className="create-ticket-form__captcha">
|
||||
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMessage() {
|
||||
switch (this.state.message) {
|
||||
case 'success':
|
||||
return <Message className="create-ticket-form__message" type="success">{i18n('TICKET_SENT')}</Message>;
|
||||
case 'fail':
|
||||
return <Message className="create-ticket-form__message" type="error">{i18n('TICKET_SENT_ERROR')}</Message>;
|
||||
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;
|
|
@ -0,0 +1,12 @@
|
|||
.create-ticket-form {
|
||||
|
||||
&__message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__captcha {
|
||||
margin: 0 auto 20px;
|
||||
height: 78px;
|
||||
width: 304px;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div>
|
||||
DASHBOARD CREATE TICKET
|
||||
<CreateTicketForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ class DashboardLayout extends React.Component {
|
|||
return (this.props.session.logged) ? (
|
||||
<div className="dashboard">
|
||||
<div className="dashboard__menu col-md-3"><DashboardMenu location={this.props.location} /></div>
|
||||
<Widget className="dashboard__content col-md-9">
|
||||
{this.props.children}
|
||||
</Widget>
|
||||
<div className="dashboard__content col-md-9">
|
||||
<Widget>
|
||||
{this.props.children}
|
||||
</Widget>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
.dashboard {
|
||||
&__menu {
|
||||
|
||||
}
|
||||
|
||||
&__content {
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div className="dashboard-ticket-list">
|
||||
<div className="dashboard-ticket-list__header">Tickets</div>
|
||||
<Header title={i18n('TICKET_LIST')} description={i18n('TICKET_LIST_DESCRIPTION')} />
|
||||
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
.dashboard-ticket-list {
|
||||
|
||||
&__header {
|
||||
text-align: left;
|
||||
font-variant: small-caps;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&__number {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
<Widget className="main-home-page__widget" title="Login" ref="loginWidget">
|
||||
<Form {...this.getLoginFormProps()}>
|
||||
<div className="login-widget__inputs">
|
||||
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
|
||||
<Input placeholder="password" name="password" className="login-widget__input" password required/>
|
||||
<Checkbox name="remember" label="Remember Me" className="login-widget__input"/>
|
||||
<FormField placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
|
||||
<FormField placeholder="password" name="password" className="login-widget__input" required fieldProps={{password: true}}/>
|
||||
<FormField name="remember" label="Remember Me" className="login-widget__input" field="checkbox"/>
|
||||
</div>
|
||||
<div className="login-widget__submit-button">
|
||||
<SubmitButton type="primary">LOG IN</SubmitButton>
|
||||
|
@ -73,7 +72,7 @@ class MainHomePageLoginWidget extends React.Component {
|
|||
<Widget className="main-home-page__widget login-widget_password" title={i18n('RECOVER_PASSWORD')} ref="recoverWidget">
|
||||
<Form {...this.getRecoverFormProps()}>
|
||||
<div className="login-widget__inputs">
|
||||
<Input placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
|
||||
<FormField placeholder="email" name="email" className="login-widget__input" validation="EMAIL" required/>
|
||||
</div>
|
||||
<div className="login-widget__submit-button">
|
||||
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
|
||||
|
|
|
@ -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 (
|
||||
<div className="main-home-page">
|
||||
<MainHomePageLoginWidget className="col-md-4" />
|
||||
<MainHomePagePortal className="col-md-8" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="main-home-page">
|
||||
<div className="col-md-4">
|
||||
<MainHomePageLoginWidget />
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<MainHomePagePortal />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MainHomePage;
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class MainLayout extends React.Component {
|
|||
return (
|
||||
<div className="main-layout">
|
||||
<MainHeader />
|
||||
<div className="main-layout--content">
|
||||
<div className="main-layout--content row">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<MainFooter />
|
||||
|
|
|
@ -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 {
|
|||
<Widget title={i18n('RECOVER_PASSWORD')} className="col-md-4 col-md-offset-4">
|
||||
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
|
||||
<div className="recover-password__inputs">
|
||||
<Input placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
|
||||
<Input placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
|
||||
<FormField placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
|
||||
<FormField placeholder={i18n('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
|
||||
</div>
|
||||
<div className="recover-password__submit-button">
|
||||
<SubmitButton type="primary">{i18n('SUBMIT')}</SubmitButton>
|
||||
|
|
|
@ -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 {
|
|||
<Widget className="signup-widget col-md-6 col-md-offset-3" title="Register">
|
||||
<Form {...this.getFormProps()}>
|
||||
<div className="signup-widget__inputs">
|
||||
<Input {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
|
||||
<Input {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
|
||||
<Input {...this.getInputProps()} label="Password" name="password" password validation="PASSWORD" required/>
|
||||
<Input {...this.getInputProps()} label="Repeat Password" name="repeated-password" password validation="REPEAT_PASSWORD" required/>
|
||||
<FormField {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
|
||||
<FormField {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
|
||||
<FormField {...this.getInputProps(true)} label="Password" name="password" validation="PASSWORD" required/>
|
||||
<FormField {...this.getInputProps(true)} label="Repeat Password" name="repeated-password" validation="REPEAT_PASSWORD" required/>
|
||||
</div>
|
||||
<div className="signup-widget__captcha">
|
||||
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
<FormField {...props}/>
|
||||
);
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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(
|
||||
<Form {...props} onSubmit={onSubmit}>
|
||||
<div>
|
||||
<Input name="first" value="value1" required/>
|
||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
||||
<FormField name="first" value="value1" required/>
|
||||
<FormField name="second" value="value2" required validation="CUSTOM"/>
|
||||
</div>
|
||||
<Input name="third" value="value3" />
|
||||
<FormField name="third" value="value3" />
|
||||
</Form>
|
||||
);
|
||||
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(
|
||||
<Form errors={errors}>
|
||||
<div>
|
||||
<Input name="first" value="value1" required/>
|
||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
||||
<FormField name="first" value="value1" required/>
|
||||
<FormField name="second" value="value2" required validation="CUSTOM"/>
|
||||
</div>
|
||||
<Input name="third" value="value3" />
|
||||
<FormField name="third" value="value3" />
|
||||
</Form>
|
||||
);
|
||||
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');
|
||||
|
|
|
@ -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 (
|
||||
<button {...this.getProps()}>
|
||||
{this.props.children}
|
||||
{(this.props.iconName) ? <Icon size="sm" name={this.props.iconName}/> : this.props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -45,6 +50,7 @@ class Button extends React.Component {
|
|||
props.className = this.getClass();
|
||||
|
||||
delete props.route;
|
||||
delete props.iconName;
|
||||
delete props.type;
|
||||
|
||||
return props;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -28,13 +28,12 @@ class CheckBox extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<label className={this.getClass()}>
|
||||
<span className={this.getClass()}>
|
||||
<span {...this.getIconProps()}>
|
||||
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
|
||||
</span>
|
||||
<span className="checkbox--label">{this.props.label}</span>
|
||||
<input {...this.getProps()}/>
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -18,12 +18,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
&--label {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&_checked {
|
||||
.checkbox--icon {
|
||||
color: $primary-red;
|
||||
|
|
|
@ -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 (
|
||||
<div className="drop-down--list-container" style={style}>
|
||||
<div className="drop-down__list-container" style={style}>
|
||||
<Menu {...menuProps} />
|
||||
</div>
|
||||
);
|
||||
|
@ -76,11 +77,11 @@ class DropDown extends React.Component {
|
|||
var iconNode = null;
|
||||
|
||||
if (item.icon) {
|
||||
iconNode = <Icon className="drop-down--current-item-icon" name={item.icon} />;
|
||||
iconNode = <Icon className="drop-down__current-item-icon" name={item.icon} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="drop-down--current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
|
||||
<div className="drop-down__current-item" onBlur={this.handleBlur.bind(this)} onClick={this.handleClick.bind(this)} tabIndex="0">
|
||||
{iconNode}{item.content}
|
||||
</div>
|
||||
);
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = [
|
||||
<span className="form-field__label" key="label">{this.props.label}</span>,
|
||||
this.renderField(),
|
||||
this.renderError()
|
||||
];
|
||||
|
||||
if (this.props.field === 'checkbox') {
|
||||
fieldContent.swap(0, 1);
|
||||
}
|
||||
return (
|
||||
<Wrapper className={this.getClass()}>
|
||||
{fieldContent}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
renderField() {
|
||||
const Field = {
|
||||
'input': Input,
|
||||
'textarea': TextEditor,
|
||||
'select': DropDown,
|
||||
'checkbox': Checkbox
|
||||
}[this.props.field];
|
||||
|
||||
return <Field {...this.getFieldProps()} />;
|
||||
}
|
||||
|
||||
renderError() {
|
||||
let error = null;
|
||||
|
||||
if (this.props.error) {
|
||||
error = <span className="form-field__error" key="error">{this.props.error}</span>;
|
||||
}
|
||||
|
||||
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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
.form {
|
||||
.input {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div className="header">
|
||||
<h2 className="header__title">{this.props.title}</h2>
|
||||
{(this.props.description) ? this.renderDescription() : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderDescription() {
|
||||
return (
|
||||
<div className="header__description">
|
||||
{this.props.description}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Header;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<label className={this.getClass()}>
|
||||
<span className="input__label">{this.props.label}</span>
|
||||
<span className={this.getClass()}>
|
||||
{this.renderIcon()}
|
||||
<input {...this.getInputProps()} className="input__text" />
|
||||
{this.renderError()}
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
renderError() {
|
||||
let error = null;
|
||||
|
||||
if (this.props.error){
|
||||
error = <span className="input__error"> {this.props.error} </span>;
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 (
|
||||
<div className={this.getClass()}>
|
||||
{this.renderEditOptions()}
|
||||
<div className="text-editor__editor" onClick={this.focus.bind(this)} onMouseDown={(event) => event.preventDefault()}>
|
||||
<span onMouseDown={(event) => event.stopPropagation()}>
|
||||
<Editor {...this.getEditorProps()} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="text-editor__options">
|
||||
<Button type="primary-icon" iconName="bold" onClick={onBoldClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||
<Button type="primary-icon" iconName="italic" onClick={onItalicsClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||
<Button type="primary-icon" iconName="underline" onClick={onUnderlineClick.bind(this)} onMouseDown={(e) => {e.preventDefault()}} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
];
|
|
@ -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'
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -34,4 +34,11 @@ global.reRenderIntoDocument = (function () {
|
|||
})();
|
||||
global.ReduxMock = {
|
||||
connect: stub().returns(stub().returnsArg(0))
|
||||
};
|
||||
};
|
||||
|
||||
Array.prototype.swap = function (x,y) {
|
||||
var b = this[x];
|
||||
this[x] = this[y];
|
||||
this[y] = b;
|
||||
return this;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue