2016-03-02 16:33:44 -08:00
|
|
|
import { helpers } from 'rx';
|
2015-09-14 17:31:48 -07:00
|
|
|
import React, { PropTypes } from 'react';
|
2016-03-05 21:06:04 -08:00
|
|
|
import PureComponent from 'react-pure-render/component';
|
2016-03-02 16:33:44 -08:00
|
|
|
import { push } from 'react-router-redux';
|
2016-02-25 18:30:10 -08:00
|
|
|
import { reduxForm } from 'redux-form';
|
2016-02-28 15:45:38 -08:00
|
|
|
// import debug from 'debug';
|
2015-10-20 13:08:14 -07:00
|
|
|
import dedent from 'dedent';
|
2015-09-24 20:28:04 -07:00
|
|
|
|
|
|
|
import {
|
2016-02-28 15:45:38 -08:00
|
|
|
isAscii,
|
|
|
|
isEmail,
|
|
|
|
isURL
|
|
|
|
} from 'validator';
|
2015-09-24 20:28:04 -07:00
|
|
|
|
2015-09-14 17:31:48 -07:00
|
|
|
import {
|
2015-09-23 13:31:27 -07:00
|
|
|
Button,
|
2015-09-14 17:31:48 -07:00
|
|
|
Col,
|
|
|
|
Input,
|
2016-01-15 02:46:25 -08:00
|
|
|
Row
|
2015-09-14 17:31:48 -07:00
|
|
|
} from 'react-bootstrap';
|
2015-09-24 20:28:04 -07:00
|
|
|
|
2016-03-02 16:33:44 -08:00
|
|
|
import { saveForm, loadSavedForm } from '../redux/actions';
|
2015-09-14 17:31:48 -07:00
|
|
|
|
2016-02-28 15:45:38 -08:00
|
|
|
// const log = debug('fcc:jobs:newForm');
|
2015-09-24 20:28:04 -07:00
|
|
|
|
2015-10-16 20:05:22 -07:00
|
|
|
const hightlightCopy = `
|
2015-11-30 14:33:39 -08:00
|
|
|
Highlight my post to make it stand out. (+$250)
|
2015-10-16 20:05:22 -07:00
|
|
|
`;
|
2015-09-21 22:41:12 -07:00
|
|
|
|
2015-10-20 13:08:14 -07:00
|
|
|
const isRemoteCopy = `
|
2015-10-20 15:01:05 -07:00
|
|
|
This job can be performed remotely.
|
2015-10-20 13:08:14 -07:00
|
|
|
`;
|
|
|
|
|
2015-10-20 16:03:22 -07:00
|
|
|
const howToApplyCopy = dedent`
|
|
|
|
Examples: click here to apply yourcompany.com/jobs/33
|
|
|
|
Or email jobs@yourcompany.com
|
|
|
|
`;
|
|
|
|
|
2015-10-20 13:08:14 -07:00
|
|
|
const checkboxClass = dedent`
|
2015-10-20 15:30:25 -07:00
|
|
|
text-left
|
2015-10-20 13:08:14 -07:00
|
|
|
jobs-checkbox-spacer
|
|
|
|
col-sm-offset-2
|
|
|
|
col-sm-6 col-md-offset-3
|
|
|
|
`;
|
|
|
|
|
2016-02-28 15:45:38 -08:00
|
|
|
const certTypes = {
|
|
|
|
isFrontEndCert: 'isFrontEndCert',
|
|
|
|
isBackEndCert: 'isBackEndCert'
|
2015-10-30 14:00:07 -07:00
|
|
|
};
|
|
|
|
|
2015-09-25 12:53:29 -07:00
|
|
|
function isValidURL(data) {
|
2016-04-15 23:48:02 +08:00
|
|
|
return isURL(data, { require_protocol: true });
|
2015-09-25 12:53:29 -07:00
|
|
|
}
|
|
|
|
|
2016-02-28 15:45:38 -08:00
|
|
|
const fields = [
|
|
|
|
'position',
|
|
|
|
'locale',
|
|
|
|
'description',
|
|
|
|
'email',
|
|
|
|
'url',
|
|
|
|
'logo',
|
|
|
|
'company',
|
|
|
|
'isHighlighted',
|
|
|
|
'isRemoteOk',
|
|
|
|
'isFrontEndCert',
|
|
|
|
'isBackEndCert',
|
|
|
|
'howToApply'
|
|
|
|
];
|
|
|
|
|
|
|
|
const fieldValidators = {
|
|
|
|
position: makeRequired(isAscii),
|
|
|
|
locale: makeRequired(isAscii),
|
|
|
|
description: makeRequired(helpers.identity),
|
|
|
|
email: makeRequired(isEmail),
|
2016-03-01 20:31:43 -08:00
|
|
|
url: makeRequired(isValidURL),
|
|
|
|
logo: makeOptional(isValidURL),
|
2016-02-28 15:45:38 -08:00
|
|
|
company: makeRequired(isAscii),
|
|
|
|
howToApply: makeRequired(isAscii)
|
|
|
|
};
|
|
|
|
|
2016-03-01 20:31:43 -08:00
|
|
|
function makeOptional(validator) {
|
|
|
|
return val => val ? validator(val) : true;
|
|
|
|
}
|
2015-10-11 21:11:27 -07:00
|
|
|
function makeRequired(validator) {
|
2016-03-01 20:31:43 -08:00
|
|
|
return (val) => val ? validator(val) : false;
|
2015-10-11 21:11:27 -07:00
|
|
|
}
|
|
|
|
|
2016-02-28 15:45:38 -08:00
|
|
|
function validateForm(values) {
|
|
|
|
return Object.keys(fieldValidators)
|
|
|
|
.map(field => {
|
|
|
|
if (fieldValidators[field](values[field])) {
|
|
|
|
return null;
|
|
|
|
}
|
2016-03-01 20:31:43 -08:00
|
|
|
return { [field]: !fieldValidators[field](values[field]) };
|
2016-02-28 15:45:38 -08:00
|
|
|
})
|
|
|
|
.filter(Boolean)
|
|
|
|
.reduce((errors, error) => ({ ...errors, ...error }), {});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBsStyle(field) {
|
|
|
|
if (field.pristine) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return field.error ?
|
|
|
|
'error' :
|
|
|
|
'success';
|
2016-02-25 18:30:10 -08:00
|
|
|
}
|
|
|
|
|
2016-03-05 21:06:04 -08:00
|
|
|
export class NewJob extends PureComponent {
|
2016-02-25 18:30:10 -08:00
|
|
|
static displayName = 'NewJob';
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
fields: PropTypes.object,
|
2016-03-01 20:31:43 -08:00
|
|
|
handleSubmit: PropTypes.func,
|
2016-03-02 16:33:44 -08:00
|
|
|
loadSavedForm: PropTypes.func,
|
|
|
|
push: PropTypes.func,
|
|
|
|
saveForm: PropTypes.func
|
2016-02-25 18:30:10 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
componentDidMount() {
|
2016-03-02 16:33:44 -08:00
|
|
|
this.props.loadSavedForm();
|
2016-03-01 20:31:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleSubmit(job) {
|
2016-03-02 16:33:44 -08:00
|
|
|
this.props.saveForm(job);
|
|
|
|
this.props.push('/jobs/new/preview');
|
2016-03-01 20:31:43 -08:00
|
|
|
}
|
|
|
|
|
2016-02-25 18:30:10 -08:00
|
|
|
handleCertClick(name) {
|
2016-02-28 15:45:38 -08:00
|
|
|
const { fields } = this.props;
|
|
|
|
Object.keys(certTypes).forEach(certType => {
|
|
|
|
if (certType === name) {
|
|
|
|
return fields[certType].onChange(true);
|
|
|
|
}
|
2016-03-02 20:54:14 -08:00
|
|
|
return fields[certType].onChange(false);
|
2016-02-25 18:30:10 -08:00
|
|
|
});
|
2016-02-28 15:45:38 -08:00
|
|
|
}
|
2016-02-25 18:30:10 -08:00
|
|
|
|
|
|
|
render() {
|
|
|
|
const {
|
2016-02-28 15:45:38 -08:00
|
|
|
fields: {
|
|
|
|
position,
|
|
|
|
locale,
|
|
|
|
description,
|
|
|
|
email,
|
|
|
|
url,
|
|
|
|
logo,
|
|
|
|
company,
|
|
|
|
isHighlighted,
|
|
|
|
isRemoteOk,
|
|
|
|
howToApply,
|
|
|
|
isFrontEndCert,
|
|
|
|
isBackEndCert
|
|
|
|
},
|
|
|
|
handleSubmit
|
2016-02-25 18:30:10 -08:00
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
const { handleChange } = this;
|
|
|
|
const labelClass = 'col-sm-offset-1 col-sm-2';
|
|
|
|
const inputClass = 'col-sm-6';
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<Row>
|
|
|
|
<Col
|
|
|
|
md={ 10 }
|
|
|
|
mdOffset={ 1 }>
|
|
|
|
<div className='text-center'>
|
|
|
|
<form
|
|
|
|
className='form-horizontal'
|
2016-02-28 15:45:38 -08:00
|
|
|
onSubmit={ handleSubmit(data => this.handleSubmit(data)) }>
|
2016-02-25 18:30:10 -08:00
|
|
|
|
|
|
|
<div className='spacer'>
|
|
|
|
<h2>First, select your ideal applicant: </h2>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Row>
|
|
|
|
<Col
|
|
|
|
xs={ 6 }
|
|
|
|
xsOffset={ 3 }>
|
|
|
|
<Row>
|
|
|
|
<Button
|
|
|
|
bsStyle='primary'
|
2016-02-28 15:45:38 -08:00
|
|
|
className={ isFrontEndCert.value ? 'active' : '' }
|
2016-02-25 18:30:10 -08:00
|
|
|
onClick={ () => {
|
2016-02-28 15:45:38 -08:00
|
|
|
if (!isFrontEndCert.value) {
|
|
|
|
this.handleCertClick(certTypes.isFrontEndCert);
|
2016-02-25 18:30:10 -08:00
|
|
|
}
|
|
|
|
}}>
|
|
|
|
<h4>Front End Development Certified</h4>
|
|
|
|
You can expect each applicant
|
|
|
|
to have a code portfolio using the
|
|
|
|
following technologies:
|
|
|
|
HTML5, CSS, jQuery, API integrations
|
|
|
|
<br />
|
|
|
|
<br />
|
|
|
|
</Button>
|
|
|
|
</Row>
|
|
|
|
<div className='button-spacer' />
|
|
|
|
<Row>
|
|
|
|
<Button
|
|
|
|
bsStyle='primary'
|
2016-02-28 15:45:38 -08:00
|
|
|
className={ isBackEndCert.value ? 'active' : ''}
|
2016-02-25 18:30:10 -08:00
|
|
|
onClick={ () => {
|
2016-02-28 15:45:38 -08:00
|
|
|
if (!isBackEndCert.value) {
|
|
|
|
this.handleCertClick(certTypes.isBackEndCert);
|
2016-02-25 18:30:10 -08:00
|
|
|
}
|
|
|
|
}}>
|
|
|
|
<h4>Back End Development Certified</h4>
|
|
|
|
You can expect each applicant to have a code
|
|
|
|
portfolio using the following technologies:
|
|
|
|
HTML5, CSS, jQuery, API integrations, MVC Framework,
|
|
|
|
JavaScript, Node.js, MongoDB, Express.js
|
|
|
|
<br />
|
|
|
|
<br />
|
|
|
|
</Button>
|
|
|
|
</Row>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<div className='spacer'>
|
|
|
|
<h2>Tell us about the position</h2>
|
|
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(position) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Job Title'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
placeholder={
|
|
|
|
'e.g. Full Stack Developer, Front End Developer, etc.'
|
|
|
|
}
|
|
|
|
required={ true }
|
|
|
|
type='text'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...position }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(locale) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Location'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
placeholder='e.g. San Francisco, Remote, etc.'
|
|
|
|
required={ true }
|
|
|
|
type='text'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...locale }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(description) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Description'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
required={ true }
|
|
|
|
rows='10'
|
|
|
|
type='textarea'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...description }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
|
|
|
label={ isRemoteCopy }
|
|
|
|
type='checkbox'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ checkboxClass }
|
|
|
|
{ ...isRemoteOk }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<div className='spacer' />
|
|
|
|
|
|
|
|
<hr />
|
|
|
|
<Row>
|
2015-10-20 16:03:22 -07:00
|
|
|
<div>
|
2016-02-25 18:30:10 -08:00
|
|
|
<h2>How should they apply?</h2>
|
2015-09-23 13:31:27 -07:00
|
|
|
</div>
|
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(howToApply) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label=' '
|
2015-09-23 13:31:27 -07:00
|
|
|
labelClassName={ labelClass }
|
2016-02-25 18:30:10 -08:00
|
|
|
placeholder={ howToApplyCopy }
|
2015-10-11 21:11:27 -07:00
|
|
|
required={ true }
|
2016-02-25 18:30:10 -08:00
|
|
|
rows='2'
|
|
|
|
type='textarea'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...howToApply }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
</Row>
|
|
|
|
|
|
|
|
<div className='spacer' />
|
|
|
|
<hr />
|
|
|
|
<div>
|
|
|
|
<h2>Tell us about your organization</h2>
|
|
|
|
</div>
|
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(company) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Company Name'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
onChange={ (e) => handleChange('company', e) }
|
|
|
|
type='text'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...company }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(email) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Email'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
placeholder='This is how we will contact you'
|
|
|
|
required={ true }
|
|
|
|
type='email'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...email }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(url) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='URL'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
placeholder='http://yourcompany.com'
|
|
|
|
type='url'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...url }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
<Input
|
2016-02-28 15:45:38 -08:00
|
|
|
bsStyle={ getBsStyle(logo) }
|
2016-02-25 18:30:10 -08:00
|
|
|
label='Logo'
|
|
|
|
labelClassName={ labelClass }
|
|
|
|
placeholder='http://yourcompany.com/logo.png'
|
|
|
|
type='url'
|
2016-02-28 15:45:38 -08:00
|
|
|
wrapperClassName={ inputClass }
|
|
|
|
{ ...logo }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
|
|
|
|
<div className='spacer' />
|
|
|
|
<hr />
|
|
|
|
<div>
|
2016-01-15 02:46:25 -08:00
|
|
|
<div>
|
2016-02-25 18:30:10 -08:00
|
|
|
<h2>Make it stand out</h2>
|
2016-01-15 02:46:25 -08:00
|
|
|
</div>
|
2016-02-25 18:30:10 -08:00
|
|
|
<div className='spacer' />
|
2015-09-23 13:31:27 -07:00
|
|
|
<Row>
|
|
|
|
<Col
|
2016-02-25 18:30:10 -08:00
|
|
|
md={ 6 }
|
|
|
|
mdOffset={ 3 }>
|
|
|
|
Highlight this ad to give it extra attention.
|
|
|
|
<br />
|
2016-02-28 15:45:38 -08:00
|
|
|
Featured listings receive more clicks and more applications.
|
2015-09-23 13:31:27 -07:00
|
|
|
</Col>
|
2015-09-22 17:19:14 -07:00
|
|
|
</Row>
|
2016-02-25 18:30:10 -08:00
|
|
|
<div className='spacer' />
|
|
|
|
<Row>
|
|
|
|
<Input
|
|
|
|
bsSize='large'
|
|
|
|
bsStyle='success'
|
|
|
|
label={ hightlightCopy }
|
|
|
|
type='checkbox'
|
|
|
|
wrapperClassName={
|
|
|
|
checkboxClass.replace('text-left', '')
|
2016-02-28 15:45:38 -08:00
|
|
|
}
|
|
|
|
{ ...isHighlighted }
|
|
|
|
/>
|
2016-02-25 18:30:10 -08:00
|
|
|
</Row>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Row>
|
|
|
|
<Col
|
|
|
|
className='text-left'
|
|
|
|
lg={ 6 }
|
|
|
|
lgOffset={ 3 }>
|
|
|
|
<Button
|
|
|
|
block={ true }
|
|
|
|
bsSize='large'
|
|
|
|
bsStyle='primary'
|
|
|
|
type='submit'>
|
|
|
|
Preview My Ad
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default reduxForm(
|
2016-02-28 15:45:38 -08:00
|
|
|
{
|
|
|
|
form: 'NewJob',
|
|
|
|
fields,
|
|
|
|
validate: validateForm
|
|
|
|
},
|
2016-03-02 16:33:44 -08:00
|
|
|
state => ({ initialValues: state.jobsApp.initialValues }),
|
2016-02-28 15:45:38 -08:00
|
|
|
{
|
2016-03-02 16:33:44 -08:00
|
|
|
loadSavedForm,
|
|
|
|
push,
|
|
|
|
saveForm
|
2016-02-28 15:45:38 -08:00
|
|
|
}
|
2016-02-25 18:30:10 -08:00
|
|
|
)(NewJob);
|