mirror of
https://github.com/opensupports/opensupports.git
synced 2025-04-08 18:35:06 +02:00
commit
e87c38b743
@ -55,6 +55,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-module-path": "^1.0.3",
|
"app-module-path": "^1.0.3",
|
||||||
"classnames": "^2.1.3",
|
"classnames": "^2.1.3",
|
||||||
|
"draft-js": "^0.8.1",
|
||||||
|
"draft-js-export-html": "^0.4.0",
|
||||||
"jquery": "^2.1.4",
|
"jquery": "^2.1.4",
|
||||||
"keycode": "^2.1.4",
|
"keycode": "^2.1.4",
|
||||||
"localStorage": "^1.0.3",
|
"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 React from 'react';
|
||||||
|
|
||||||
|
import CreateTicketForm from 'app/main/dashboard/dashboard-create-ticket/create-ticket-form';
|
||||||
|
|
||||||
class DashboardCreateTicketPage extends React.Component {
|
class DashboardCreateTicketPage extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
DASHBOARD CREATE TICKET
|
<CreateTicketForm />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,11 @@ class DashboardLayout extends React.Component {
|
|||||||
return (this.props.session.logged) ? (
|
return (this.props.session.logged) ? (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
<div className="dashboard__menu col-md-3"><DashboardMenu location={this.props.location} /></div>
|
<div className="dashboard__menu col-md-3"><DashboardMenu location={this.props.location} /></div>
|
||||||
<Widget className="dashboard__content col-md-9">
|
<div className="dashboard__content col-md-9">
|
||||||
{this.props.children}
|
<Widget>
|
||||||
</Widget>
|
{this.props.children}
|
||||||
|
</Widget>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
.dashboard {
|
.dashboard {
|
||||||
&__menu {
|
&__menu {
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import i18n from 'lib-app/i18n';
|
||||||
|
|
||||||
|
import Header from 'core-components/header';
|
||||||
import Table from 'core-components/table';
|
import Table from 'core-components/table';
|
||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
|
|
||||||
@ -16,7 +19,7 @@ class DashboardListTicketsPage extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-ticket-list">
|
<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()} />
|
<Table headers={this.getTableHeaders()} rows={this.getTableRows()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,12 +2,6 @@
|
|||||||
|
|
||||||
.dashboard-ticket-list {
|
.dashboard-ticket-list {
|
||||||
|
|
||||||
&__header {
|
|
||||||
text-align: left;
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__number {
|
&__number {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,7 @@ import i18n from 'lib-app/i18n';
|
|||||||
import SubmitButton from 'core-components/submit-button';
|
import SubmitButton from 'core-components/submit-button';
|
||||||
import Button from 'core-components/button';
|
import Button from 'core-components/button';
|
||||||
import Form from 'core-components/form';
|
import Form from 'core-components/form';
|
||||||
import Input from 'core-components/input';
|
import FormField from 'core-components/form-field';
|
||||||
import Checkbox from 'core-components/checkbox';
|
|
||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
import WidgetTransition from 'core-components/widget-transition';
|
import WidgetTransition from 'core-components/widget-transition';
|
||||||
import Message from 'core-components/message';
|
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">
|
<Widget className="main-home-page__widget" title="Login" ref="loginWidget">
|
||||||
<Form {...this.getLoginFormProps()}>
|
<Form {...this.getLoginFormProps()}>
|
||||||
<div className="login-widget__inputs">
|
<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/>
|
||||||
<Input placeholder="password" name="password" className="login-widget__input" password required/>
|
<FormField placeholder="password" name="password" className="login-widget__input" required fieldProps={{password: true}}/>
|
||||||
<Checkbox name="remember" label="Remember Me" className="login-widget__input"/>
|
<FormField name="remember" label="Remember Me" className="login-widget__input" field="checkbox"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="login-widget__submit-button">
|
<div className="login-widget__submit-button">
|
||||||
<SubmitButton type="primary">LOG IN</SubmitButton>
|
<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">
|
<Widget className="main-home-page__widget login-widget_password" title={i18n('RECOVER_PASSWORD')} ref="recoverWidget">
|
||||||
<Form {...this.getRecoverFormProps()}>
|
<Form {...this.getRecoverFormProps()}>
|
||||||
<div className="login-widget__inputs">
|
<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>
|
||||||
<div className="login-widget__submit-button">
|
<div className="login-widget__submit-button">
|
||||||
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
|
<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';
|
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
|
||||||
|
|
||||||
class MainHomePage extends React.Component {
|
class MainHomePage extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-home-page">
|
<div className="main-home-page">
|
||||||
<MainHomePageLoginWidget className="col-md-4" />
|
<div className="col-md-4">
|
||||||
<MainHomePagePortal className="col-md-8" />
|
<MainHomePageLoginWidget />
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="col-md-8">
|
||||||
}
|
<MainHomePagePortal />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MainHomePage;
|
export default MainHomePage;
|
@ -55,7 +55,7 @@ class MainLayoutHeader extends React.Component {
|
|||||||
return {
|
return {
|
||||||
className: 'main-layout-header__languages',
|
className: 'main-layout-header__languages',
|
||||||
items: this.getLanguageList(),
|
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)
|
onChange: this.changeLanguage.bind(this)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ class MainLayout extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="main-layout">
|
<div className="main-layout">
|
||||||
<MainHeader />
|
<MainHeader />
|
||||||
<div className="main-layout--content">
|
<div className="main-layout--content row">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
<MainFooter />
|
<MainFooter />
|
||||||
|
@ -6,7 +6,7 @@ import API from 'lib-app/api-call';
|
|||||||
|
|
||||||
import Widget from 'core-components/widget';
|
import Widget from 'core-components/widget';
|
||||||
import Form from 'core-components/form';
|
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 SubmitButton from 'core-components/submit-button';
|
||||||
import Message from 'core-components/message';
|
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">
|
<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}>
|
<Form className="recover-password__form" onSubmit={this.onRecoverPasswordSubmit.bind(this)} loading={this.state.loading}>
|
||||||
<div className="recover-password__inputs">
|
<div className="recover-password__inputs">
|
||||||
<Input placeholder={i18n('NEW_PASSWORD')} name="password" className="recover-password__input" validation="PASSWORD" password required/>
|
<FormField 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('REPEAT_NEW_PASSWORD')} name="password-repeat" className="recover-password__input" validation="REPEAT_PASSWORD" password required/>
|
||||||
</div>
|
</div>
|
||||||
<div className="recover-password__submit-button">
|
<div className="recover-password__submit-button">
|
||||||
<SubmitButton type="primary">{i18n('SUBMIT')}</SubmitButton>
|
<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 SubmitButton from 'core-components/submit-button';
|
||||||
import Message from 'core-components/message';
|
import Message from 'core-components/message';
|
||||||
import Form from 'core-components/form';
|
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';
|
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">
|
<Widget className="signup-widget col-md-6 col-md-offset-3" title="Register">
|
||||||
<Form {...this.getFormProps()}>
|
<Form {...this.getFormProps()}>
|
||||||
<div className="signup-widget__inputs">
|
<div className="signup-widget__inputs">
|
||||||
<Input {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
|
<FormField {...this.getInputProps()} label="Full Name" name="name" validation="NAME" required/>
|
||||||
<Input {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
|
<FormField {...this.getInputProps()} label="Email Address" name="email" validation="EMAIL" required/>
|
||||||
<Input {...this.getInputProps()} label="Password" name="password" password validation="PASSWORD" required/>
|
<FormField {...this.getInputProps(true)} label="Password" name="password" validation="PASSWORD" required/>
|
||||||
<Input {...this.getInputProps()} label="Repeat Password" name="repeated-password" password validation="REPEAT_PASSWORD" required/>
|
<FormField {...this.getInputProps(true)} label="Repeat Password" name="repeated-password" validation="REPEAT_PASSWORD" required/>
|
||||||
</div>
|
</div>
|
||||||
<div className="signup-widget__captcha">
|
<div className="signup-widget__captcha">
|
||||||
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
|
<ReCAPTCHA sitekey="6LfM5CYTAAAAAGLz6ctpf-hchX2_l0Ge-Bn-n8wS" onChange={function () {}}/>
|
||||||
@ -64,10 +64,13 @@ class MainSignUpPageWidget extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputProps() {
|
getInputProps(password) {
|
||||||
return {
|
return {
|
||||||
inputType: 'secondary',
|
className: 'signup-widget__input',
|
||||||
className: 'signup-widget__input'
|
fieldProps: {
|
||||||
|
size: 'medium',
|
||||||
|
password: password
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
449
client/src/core-components/__tests__/form-field-test.js
Normal file
449
client/src/core-components/__tests__/form-field-test.js
Normal file
@ -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
|
// MOCKS
|
||||||
const ValidationFactoryMock = require('lib-app/__mocks__/validations/validation-factory-mock');
|
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
|
// COMPONENT
|
||||||
const Form = requireUnit('core-components/form', {
|
const Form = requireUnit('core-components/form', {
|
||||||
'lib-app/validations/validations-factory': ValidationFactoryMock,
|
'lib-app/validations/validations-factory': ValidationFactoryMock,
|
||||||
'core-components/input': Input
|
'draft-js-export-html': draftJsExportHTML,
|
||||||
|
'core-components/form-field': FormField
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Form component', function () {
|
describe('Form component', function () {
|
||||||
@ -15,13 +20,13 @@ describe('Form component', function () {
|
|||||||
form = TestUtils.renderIntoDocument(
|
form = TestUtils.renderIntoDocument(
|
||||||
<Form {...props} onSubmit={onSubmit}>
|
<Form {...props} onSubmit={onSubmit}>
|
||||||
<div>
|
<div>
|
||||||
<Input name="first" value="value1" required/>
|
<FormField name="first" value="value1" required/>
|
||||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
<FormField name="second" value="value2" required validation="CUSTOM"/>
|
||||||
</div>
|
</div>
|
||||||
<Input name="third" value="value3" />
|
<FormField name="third" value="value3" />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
fields = TestUtils.scryRenderedComponentsWithType(form, Input);
|
fields = TestUtils.scryRenderedComponentsWithType(form, FormField);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetStubs() {
|
function resetStubs() {
|
||||||
@ -117,7 +122,7 @@ describe('Form component', function () {
|
|||||||
});
|
});
|
||||||
afterEach(resetStubs);
|
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[0].props.error).to.equal('MOCK_ERROR_CONTROLLED');
|
||||||
expect(fields[1].props.error).to.equal(undefined);
|
expect(fields[1].props.error).to.equal(undefined);
|
||||||
});
|
});
|
||||||
@ -138,13 +143,13 @@ describe('Form component', function () {
|
|||||||
form = reRenderIntoDocument(
|
form = reRenderIntoDocument(
|
||||||
<Form errors={errors}>
|
<Form errors={errors}>
|
||||||
<div>
|
<div>
|
||||||
<Input name="first" value="value1" required/>
|
<FormField name="first" value="value1" required/>
|
||||||
<Input name="second" value="value2" required validation="CUSTOM"/>
|
<FormField name="second" value="value2" required validation="CUSTOM"/>
|
||||||
</div>
|
</div>
|
||||||
<Input name="third" value="value3" />
|
<FormField name="third" value="value3" />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
fields = TestUtils.scryRenderedComponentsWithType(form, Input);
|
fields = TestUtils.scryRenderedComponentsWithType(form, FormField);
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrorsOrRender();
|
setErrorsOrRender();
|
||||||
@ -171,6 +176,19 @@ describe('Form component', function () {
|
|||||||
expect(form.props.onSubmit).to.have.been.calledWith(form.state.form);
|
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 () {
|
it('should validate all fields and not call onSubmit if there are errors', function () {
|
||||||
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
|
ValidationFactoryMock.validators.defaultValidatorMock.performValidation = stub().returns('MOCK_ERROR');
|
||||||
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
|
ValidationFactoryMock.validators.customValidatorMock.performValidation = stub().returns('MOCK_ERROR_2');
|
||||||
|
@ -6,6 +6,9 @@ import classNames from 'classnames';
|
|||||||
// CORE LIBS
|
// CORE LIBS
|
||||||
import callback from 'lib-core/callback';
|
import callback from 'lib-core/callback';
|
||||||
|
|
||||||
|
// CORE COMPONENTS
|
||||||
|
import Icon from 'core-components/icon';
|
||||||
|
|
||||||
class Button extends React.Component {
|
class Button extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -16,6 +19,7 @@ class Button extends React.Component {
|
|||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
type: React.PropTypes.oneOf([
|
type: React.PropTypes.oneOf([
|
||||||
'primary',
|
'primary',
|
||||||
|
'primary-icon',
|
||||||
'clean',
|
'clean',
|
||||||
'link'
|
'link'
|
||||||
]),
|
]),
|
||||||
@ -23,7 +27,8 @@ class Button extends React.Component {
|
|||||||
to: React.PropTypes. string.isRequired,
|
to: React.PropTypes. string.isRequired,
|
||||||
params: React.PropTypes.object,
|
params: React.PropTypes.object,
|
||||||
query: React.PropTypes.query
|
query: React.PropTypes.query
|
||||||
})
|
}),
|
||||||
|
iconName: React.PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -33,7 +38,7 @@ class Button extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<button {...this.getProps()}>
|
<button {...this.getProps()}>
|
||||||
{this.props.children}
|
{(this.props.iconName) ? <Icon size="sm" name={this.props.iconName}/> : this.props.children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -45,6 +50,7 @@ class Button extends React.Component {
|
|||||||
props.className = this.getClass();
|
props.className = this.getClass();
|
||||||
|
|
||||||
delete props.route;
|
delete props.route;
|
||||||
|
delete props.iconName;
|
||||||
delete props.type;
|
delete props.type;
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
||||||
&-primary {
|
&-primary,
|
||||||
|
&-primary-icon {
|
||||||
background-color: $primary-red;
|
background-color: $primary-red;
|
||||||
border: solid transparent;
|
border: solid transparent;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -12,6 +13,11 @@
|
|||||||
width: 239px;
|
width: 239px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-primary-icon {
|
||||||
|
width: initial;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
&-clean {
|
&-clean {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -28,13 +28,12 @@ class CheckBox extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<label className={this.getClass()}>
|
<span className={this.getClass()}>
|
||||||
<span {...this.getIconProps()}>
|
<span {...this.getIconProps()}>
|
||||||
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
|
{getIcon((this.getValue()) ? 'check-square' : 'square', 'lg') }
|
||||||
</span>
|
</span>
|
||||||
<span className="checkbox--label">{this.props.label}</span>
|
|
||||||
<input {...this.getProps()}/>
|
<input {...this.getProps()}/>
|
||||||
</label>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ class CheckBox extends React.Component {
|
|||||||
props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
|
props.onChange = callback(this.handleChange.bind(this), this.props.onChange);
|
||||||
|
|
||||||
delete props.alignment;
|
delete props.alignment;
|
||||||
delete props.error;
|
delete props.errored;
|
||||||
delete props.label;
|
delete props.label;
|
||||||
delete props.value;
|
delete props.value;
|
||||||
|
|
||||||
|
@ -18,12 +18,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--label {
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_checked {
|
&_checked {
|
||||||
.checkbox--icon {
|
.checkbox--icon {
|
||||||
color: $primary-red;
|
color: $primary-red;
|
||||||
|
@ -10,7 +10,8 @@ class DropDown extends React.Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
defaultSelectedIndex: React.PropTypes.number,
|
defaultSelectedIndex: React.PropTypes.number,
|
||||||
selectedIndex: React.PropTypes.number,
|
selectedIndex: React.PropTypes.number,
|
||||||
items: Menu.propTypes.items
|
items: Menu.propTypes.items,
|
||||||
|
size: React.PropTypes.oneOf(['small', 'medium', 'large'])
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -66,7 +67,7 @@ class DropDown extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="drop-down--list-container" style={style}>
|
<div className="drop-down__list-container" style={style}>
|
||||||
<Menu {...menuProps} />
|
<Menu {...menuProps} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -76,11 +77,11 @@ class DropDown extends React.Component {
|
|||||||
var iconNode = null;
|
var iconNode = null;
|
||||||
|
|
||||||
if (item.icon) {
|
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 (
|
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}
|
{iconNode}{item.content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -91,6 +92,7 @@ class DropDown extends React.Component {
|
|||||||
'drop-down': true,
|
'drop-down': true,
|
||||||
'drop-down_closed': !this.state.opened,
|
'drop-down_closed': !this.state.opened,
|
||||||
|
|
||||||
|
['drop-down_' + this.props.size]: true,
|
||||||
[this.props.className]: (this.props.className)
|
[this.props.className]: (this.props.className)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,19 +7,24 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&--current-item {
|
&__current-item {
|
||||||
background-color: $light-grey;
|
background-color: $light-grey;
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
color: $primary-black;
|
color: $primary-black;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: $medium-grey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--current-item-icon {
|
&__current-item-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--list-container {
|
&__list-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
@ -27,8 +32,21 @@
|
|||||||
|
|
||||||
&_closed {
|
&_closed {
|
||||||
|
|
||||||
.drop-down--list-container {
|
.drop-down__list-container {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_medium {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
.drop-down__current-item {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-down__list-container {
|
||||||
|
width: 200px;
|
||||||
|
border: 1px solid $light-grey;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
151
client/src/core-components/form-field.js
Normal file
151
client/src/core-components/form-field.js
Normal file
@ -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;
|
42
client/src/core-components/form-field.scss
Normal file
42
client/src/core-components/form-field.scss
Normal file
@ -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 React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import classNames from 'classnames';
|
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 {reactDFS, renderChildrenWithProps} from 'lib-core/react-dfs';
|
||||||
import ValidationFactory from 'lib-app/validations/validations-factory';
|
import ValidationFactory from 'lib-app/validations/validations-factory';
|
||||||
|
|
||||||
import Input from 'core-components/input';
|
import FormField from 'core-components/form-field';
|
||||||
import Checkbox from 'core-components/checkbox';
|
|
||||||
|
|
||||||
class Form extends React.Component {
|
class Form extends React.Component {
|
||||||
|
|
||||||
@ -75,14 +76,14 @@ class Form extends React.Component {
|
|||||||
getFieldProps({props, type}) {
|
getFieldProps({props, type}) {
|
||||||
let additionalProps = {};
|
let additionalProps = {};
|
||||||
|
|
||||||
if (type === Input || type === Checkbox) {
|
if (this.isValidField({type})) {
|
||||||
let fieldName = props.name;
|
let fieldName = props.name;
|
||||||
|
|
||||||
additionalProps = {
|
additionalProps = {
|
||||||
ref: fieldName,
|
ref: fieldName,
|
||||||
value: this.state.form[fieldName] || props.value,
|
value: this.state.form[fieldName] || props.value,
|
||||||
error: this.getFieldError(fieldName),
|
error: this.getFieldError(fieldName),
|
||||||
onChange: this.handleFieldChange.bind(this, fieldName, type),
|
onChange: this.handleFieldChange.bind(this, fieldName),
|
||||||
onBlur: this.validateField.bind(this, fieldName)
|
onBlur: this.validateField.bind(this, fieldName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,13 +139,8 @@ class Form extends React.Component {
|
|||||||
|
|
||||||
reactDFS(this.props.children, (child) => {
|
reactDFS(this.props.children, (child) => {
|
||||||
|
|
||||||
if (this.isValidFieldType(child)) {
|
if (this.isValidField(child)) {
|
||||||
if (child.type === Input) {
|
form[child.props.name] = child.props.value || FormField.getDefaultValue(child.props.field);
|
||||||
form[child.props.name] = child.props.value || '';
|
|
||||||
}
|
|
||||||
else if (child.type === Checkbox) {
|
|
||||||
form[child.props.name] = child.props.checked || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child.props.required) {
|
if (child.props.required) {
|
||||||
validations[child.props.name] = ValidationFactory.getValidator(child.props.validation || 'DEFAULT');
|
validations[child.props.name] = ValidationFactory.getValidator(child.props.validation || 'DEFAULT');
|
||||||
@ -161,29 +157,33 @@ class Form extends React.Component {
|
|||||||
handleSubmit(event) {
|
handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
const form = _.mapValues(this.state.form, (field) => {
|
||||||
|
if (field instanceof EditorState) {
|
||||||
|
return stateToHTML(field.getCurrentContent());
|
||||||
|
} else {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this.hasFormErrors()) {
|
if (this.hasFormErrors()) {
|
||||||
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this));
|
this.updateErrors(this.getAllFieldErrors(), this.focusFirstErrorField.bind(this));
|
||||||
} else if (this.props.onSubmit) {
|
} 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);
|
let form = _.clone(this.state.form);
|
||||||
|
|
||||||
form[fieldName] = event.target.value;
|
form[fieldName] = event.target.value;
|
||||||
|
|
||||||
if (type === Checkbox) {
|
|
||||||
form[fieldName] = event.target.checked || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
form: form
|
form: form
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidFieldType(child) {
|
isValidField(node) {
|
||||||
return child.type === Input || child.type === Checkbox;
|
return node.type === FormField;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFormErrors() {
|
hasFormErrors() {
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
.form {
|
|
||||||
.input {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
27
client/src/core-components/header.js
Normal file
27
client/src/core-components/header.js
Normal file
@ -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;
|
17
client/src/core-components/header.scss
Normal file
17
client/src/core-components/header.scss
Normal file
@ -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,
|
value: React.PropTypes.string,
|
||||||
validation: React.PropTypes.string,
|
validation: React.PropTypes.string,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
inputType: React.PropTypes.string,
|
size: React.PropTypes.oneOf(['small', 'medium', 'large']),
|
||||||
password: React.PropTypes.bool,
|
password: React.PropTypes.bool,
|
||||||
required: React.PropTypes.bool,
|
required: React.PropTypes.bool,
|
||||||
icon: React.PropTypes.string,
|
icon: React.PropTypes.string,
|
||||||
@ -22,30 +22,18 @@ class Input extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
inputType: 'primary'
|
size: 'small'
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<label className={this.getClass()}>
|
<span className={this.getClass()}>
|
||||||
<span className="input__label">{this.props.label}</span>
|
|
||||||
{this.renderIcon()}
|
{this.renderIcon()}
|
||||||
<input {...this.getInputProps()} className="input__text" />
|
<input {...this.getInputProps()} className="input__text" />
|
||||||
{this.renderError()}
|
</span>
|
||||||
</label>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderError() {
|
|
||||||
let error = null;
|
|
||||||
|
|
||||||
if (this.props.error){
|
|
||||||
error = <span className="input__error"> {this.props.error} </span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIcon() {
|
renderIcon() {
|
||||||
let icon = null;
|
let icon = null;
|
||||||
|
|
||||||
@ -62,12 +50,12 @@ class Input extends React.Component {
|
|||||||
props['aria-required'] = this.props.required;
|
props['aria-required'] = this.props.required;
|
||||||
props.type = (this.props.password) ? 'password' : 'text';
|
props.type = (this.props.password) ? 'password' : 'text';
|
||||||
props.ref = 'nativeInput';
|
props.ref = 'nativeInput';
|
||||||
props.disabled = this.context.loading;
|
|
||||||
|
|
||||||
|
delete props.errored;
|
||||||
delete props.required;
|
delete props.required;
|
||||||
delete props.validation;
|
delete props.validation;
|
||||||
delete props.inputType;
|
delete props.inputType;
|
||||||
delete props.error;
|
delete props.errored;
|
||||||
delete props.password;
|
delete props.password;
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
@ -77,8 +65,8 @@ class Input extends React.Component {
|
|||||||
let classes = {
|
let classes = {
|
||||||
'input': true,
|
'input': true,
|
||||||
'input_with-icon': (this.props.icon),
|
'input_with-icon': (this.props.icon),
|
||||||
'input_with-error': (this.props.error),
|
'input_errored': (this.props.errored),
|
||||||
['input_' + this.props.inputType]: true,
|
['input_' + this.props.size]: true,
|
||||||
|
|
||||||
[this.props.className]: (this.props.className)
|
[this.props.className]: (this.props.className)
|
||||||
};
|
};
|
||||||
|
@ -19,22 +19,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&_small {
|
||||||
color: $primary-black;
|
|
||||||
font-size: 15px;
|
|
||||||
display: block;
|
|
||||||
padding: 3px 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_primary {
|
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_secondary {
|
&_medium {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_large {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
&_with-icon {
|
&_with-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@ -52,16 +48,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_with-error {
|
&_errored {
|
||||||
.input__error {
|
|
||||||
color: $primary-red;
|
|
||||||
font-size: $font-size--sm;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.input__text {
|
.input__text {
|
||||||
border: 1px solid $primary-red;
|
border: 1px solid $primary-red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
118
client/src/core-components/text-editor.js
Normal file
118
client/src/core-components/text-editor.js
Normal file
@ -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;
|
39
client/src/core-components/text-editor.scss
Normal file
39
client/src/core-components/text-editor.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
client/src/data/fixtures/ticket-fixtures.js
Normal file
25
client/src/data/fixtures/ticket-fixtures.js
Normal file
@ -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',
|
'NEW_PASSWORD': 'New password',
|
||||||
'REPEAT_NEW_PASSWORD': 'Repeat new password',
|
'REPEAT_NEW_PASSWORD': 'Repeat new password',
|
||||||
'BACK_LOGIN_FORM': 'Back to login form',
|
'BACK_LOGIN_FORM': 'Back to login form',
|
||||||
'TICKET_LIST': 'Ticket List',
|
|
||||||
'CREATE_TICKET': 'Create Ticket',
|
|
||||||
'VIEW_ARTICLES': 'View Articles',
|
'VIEW_ARTICLES': 'View Articles',
|
||||||
'EDIT_PROFILE': 'Edit Profile',
|
'EDIT_PROFILE': 'Edit Profile',
|
||||||
'CLOSE_SESSION': 'Close session',
|
'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
|
//ERRORS
|
||||||
'EMAIL_NOT_EXIST': 'Email does not exist',
|
'EMAIL_NOT_EXIST': 'Email does not exist',
|
||||||
'ERROR_EMPTY': 'Invalid value',
|
'ERROR_EMPTY': 'Invalid value',
|
||||||
'ERROR_PASSWORD': 'Invalid password',
|
'ERROR_PASSWORD': 'Invalid password',
|
||||||
'ERROR_NAME': 'Invalid name',
|
'ERROR_NAME': 'Invalid name',
|
||||||
|
'ERROR_TITLE': 'Invalid title',
|
||||||
'ERROR_EMAIL': 'Invalid email',
|
'ERROR_EMAIL': 'Invalid email',
|
||||||
|
'ERROR_CONTENT_SHORT': 'Content too short',
|
||||||
'PASSWORD_NOT_MATCH': 'Password does not match',
|
'PASSWORD_NOT_MATCH': 'Password does not match',
|
||||||
'INVALID_RECOVER': 'Invalid recover data',
|
'INVALID_RECOVER': 'Invalid recover data',
|
||||||
|
'TICKET_SENT_ERROR': 'An error occurred while trying to create the ticket.',
|
||||||
|
|
||||||
//MESSAGES
|
//MESSAGES
|
||||||
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',
|
||||||
|
'TICKET_SENT': 'Ticket has been created successfully.',
|
||||||
'VALID_RECOVER': 'Password recovered successfully',
|
'VALID_RECOVER': 'Password recovered successfully',
|
||||||
'EMAIL_EXISTS': 'Email already exists, please try to log in or recover password'
|
'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 routes from 'app/Routes';
|
||||||
import store from 'app/store';
|
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' ) {
|
if ( process.env.NODE_ENV !== 'production' ) {
|
||||||
// Enable React devtools
|
// Enable React devtools
|
||||||
window.React = React;
|
window.React = React;
|
||||||
|
@ -17,6 +17,7 @@ let fixtures = (function () {
|
|||||||
|
|
||||||
// FIXTURES
|
// FIXTURES
|
||||||
fixtures.add(require('data/fixtures/user-fixtures'));
|
fixtures.add(require('data/fixtures/user-fixtures'));
|
||||||
|
fixtures.add(require('data/fixtures/ticket-fixtures'));
|
||||||
fixtures.add(require('data/fixtures/system-fixtures'));
|
fixtures.add(require('data/fixtures/system-fixtures'));
|
||||||
|
|
||||||
_.each(fixtures.getAll(), function (fixture) {
|
_.each(fixtures.getAll(), function (fixture) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import {EditorState} from 'draft-js';
|
||||||
|
|
||||||
import Validator from 'lib-app/validations/validator';
|
import Validator from 'lib-app/validations/validator';
|
||||||
|
|
||||||
class LengthValidator extends Validator {
|
class LengthValidator extends Validator {
|
||||||
@ -9,6 +11,10 @@ class LengthValidator extends Validator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate(value, form) {
|
validate(value, form) {
|
||||||
|
if (value instanceof EditorState) {
|
||||||
|
value = value.getCurrentContent().getPlainText();
|
||||||
|
}
|
||||||
|
|
||||||
if (value.length < this.minlength) return this.getError(this.errorKey);
|
if (value.length < this.minlength) return this.getError(this.errorKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import LengthValidator from 'lib-app/validations/length-validator';
|
|||||||
let validators = {
|
let validators = {
|
||||||
'DEFAULT': new Validator(),
|
'DEFAULT': new Validator(),
|
||||||
'NAME': new AlphaNumericValidator('ERROR_NAME', new LengthValidator(2, 'ERROR_NAME')),
|
'NAME': new AlphaNumericValidator('ERROR_NAME', new LengthValidator(2, 'ERROR_NAME')),
|
||||||
|
'TITLE': new AlphaNumericValidator('ERROR_TITLE', new LengthValidator(2, 'ERROR_TITLE')),
|
||||||
'EMAIL': new EmailValidator(),
|
'EMAIL': new EmailValidator(),
|
||||||
|
'TEXT_AREA': new LengthValidator(10, 'ERROR_CONTENT_SHORT'),
|
||||||
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
|
'PASSWORD': new LengthValidator(6, 'ERROR_PASSWORD'),
|
||||||
'REPEAT_PASSWORD': new RepeatPasswordValidator()
|
'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 {
|
class Validator {
|
||||||
constructor(validator = null) {
|
constructor(validator = null) {
|
||||||
@ -18,7 +20,11 @@ class Validator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate(value, form) {
|
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) {
|
getError(errorKey) {
|
||||||
|
@ -34,4 +34,11 @@ global.reRenderIntoDocument = (function () {
|
|||||||
})();
|
})();
|
||||||
global.ReduxMock = {
|
global.ReduxMock = {
|
||||||
connect: stub().returns(stub().returnsArg(0))
|
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…
x
Reference in New Issue
Block a user