Files
freeCodeCamp/common/app/routes/Jobs/components/NewJob.jsx

487 lines
15 KiB
JavaScript
Raw Normal View History

import { helpers } from 'rx';
2015-09-14 17:31:48 -07:00
import React, { PropTypes } from 'react';
2015-09-26 22:23:56 -07:00
import { History } from 'react-router';
2015-09-14 17:31:48 -07:00
import { contain } from 'thundercats-react';
2015-09-24 20:28:04 -07:00
import debugFactory from 'debug';
import dedent from 'dedent';
import normalizeUrl from 'normalize-url';
2015-09-24 20:28:04 -07:00
import { getDefaults } from '../utils';
import {
inHTMLData,
uriInSingleQuotedAttr
} from 'xss-filters';
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,
Row,
2015-10-20 15:30:25 -07:00
Panel,
Well
2015-09-14 17:31:48 -07:00
} from 'react-bootstrap';
2015-09-24 20:28:04 -07:00
2015-09-22 16:10:12 -07:00
import {
isAscii,
isEmail,
2015-09-22 17:26:53 -07:00
isURL
2015-09-22 16:10:12 -07:00
} from 'validator';
2015-09-14 17:31:48 -07:00
2015-09-24 20:28:04 -07:00
const debug = debugFactory('freecc:jobs:newForm');
const checkValidity = [
'position',
'locale',
'description',
'email',
'url',
'logo',
2015-10-15 22:30:07 -07:00
'company',
2015-10-20 16:03:22 -07:00
'isHighlighted',
'howToApply'
2015-09-24 20:28:04 -07:00
];
2015-10-16 20:05:22 -07:00
const hightlightCopy = `
Highlight my post to make it stand out. (+$50)
`;
2015-11-04 13:45:22 -08:00
const isRemoteCopy = `
2015-10-20 15:01:05 -07:00
This job can be performed remotely.
`;
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
`;
const checkboxClass = dedent`
2015-10-20 15:30:25 -07:00
text-left
jobs-checkbox-spacer
col-sm-offset-2
col-sm-6 col-md-offset-3
`;
function formatValue(value, validator, type = 'string') {
const formated = getDefaults(type);
if (validator && type === 'string') {
formated.valid = validator(value);
}
if (value) {
formated.value = value;
formated.bsStyle = formated.valid ? 'success' : 'error';
}
return formated;
}
const normalizeOptions = {
stripWWW: false
};
2015-10-30 14:43:38 -07:00
function formatUrl(url, shouldKeepTrailingSlash = true) {
if (
typeof url === 'string' &&
url.length > 4 &&
url.indexOf('.') !== -1
) {
// prevent trailing / from being stripped during typing
let lastChar = '';
2015-10-30 14:43:38 -07:00
if (shouldKeepTrailingSlash && url.substring(url.length - 1) === '/') {
lastChar = '/';
}
return normalizeUrl(url, normalizeOptions) + lastChar;
}
return url;
}
function isValidURL(data) {
return isURL(data, { 'require_protocol': true });
}
2015-10-11 21:11:27 -07:00
function makeRequired(validator) {
return (val) => !!val && validator(val);
}
2015-09-14 17:31:48 -07:00
export default contain({
actions: 'jobActions',
store: 'jobsStore',
map({ form = {} }) {
const {
position,
locale,
2015-09-22 16:10:12 -07:00
description,
email,
url,
2015-09-22 18:25:09 -07:00
logo,
2015-10-15 22:30:07 -07:00
company,
2015-11-04 14:23:42 -08:00
isFrontEndCert = true,
2015-11-04 13:45:22 -08:00
isFullStackCert,
isHighlighted,
2015-10-20 16:03:22 -07:00
isRemoteOk,
howToApply
} = form;
return {
2015-10-11 21:11:27 -07:00
position: formatValue(position, makeRequired(isAscii)),
locale: formatValue(locale, makeRequired(isAscii)),
description: formatValue(description, makeRequired(helpers.identity)),
2015-10-11 21:11:27 -07:00
email: formatValue(email, makeRequired(isEmail)),
url: formatValue(formatUrl(url), isValidURL),
logo: formatValue(formatUrl(logo), isValidURL),
2015-10-15 22:30:07 -07:00
company: formatValue(company, makeRequired(isAscii)),
isHighlighted: formatValue(isHighlighted, null, 'bool'),
2015-10-20 16:03:22 -07:00
isRemoteOk: formatValue(isRemoteOk, null, 'bool'),
2015-11-04 13:45:22 -08:00
howToApply: formatValue(howToApply, makeRequired(isAscii)),
isFrontEndCert,
isFullStackCert
};
2015-09-24 20:28:04 -07:00
},
subscribeOnWillMount() {
return typeof window !== 'undefined';
2015-09-14 17:31:48 -07:00
}
},
React.createClass({
displayName: 'NewJob',
2015-09-14 17:31:48 -07:00
propTypes: {
jobActions: PropTypes.object,
position: PropTypes.object,
locale: PropTypes.object,
2015-09-22 16:10:12 -07:00
description: PropTypes.object,
email: PropTypes.object,
2015-09-22 18:25:09 -07:00
url: PropTypes.object,
logo: PropTypes.object,
2015-10-15 22:30:07 -07:00
company: PropTypes.object,
isHighlighted: PropTypes.object,
2015-10-20 16:03:22 -07:00
isRemoteOk: PropTypes.object,
2015-11-04 13:45:22 -08:00
isFrontEndCert: PropTypes.bool,
isFullStackCert: PropTypes.bool,
2015-10-20 16:03:22 -07:00
howToApply: PropTypes.object
},
2015-09-26 22:23:56 -07:00
mixins: [History],
2015-09-24 20:28:04 -07:00
handleSubmit(e) {
e.preventDefault();
2015-11-05 15:10:18 -08:00
const pros = this.props;
2015-09-24 20:28:04 -07:00
let valid = true;
checkValidity.forEach((prop) => {
// if value exist, check if it is valid
2015-11-05 15:10:18 -08:00
if (pros[prop].value && pros[prop].type !== 'boolean') {
valid = valid && !!pros[prop].valid;
2015-09-24 20:28:04 -07:00
}
});
2015-11-05 15:10:18 -08:00
if (!valid || !pros.isFrontEndCert && !pros.isFullStackCert ) {
2015-09-24 20:28:04 -07:00
debug('form not valid');
return;
}
const {
jobActions,
// form values
2015-09-24 20:28:04 -07:00
position,
locale,
description,
email,
url,
logo,
2015-10-15 22:30:07 -07:00
company,
2015-11-04 13:45:22 -08:00
isFrontEndCert,
isFullStackCert,
isHighlighted,
2015-10-20 16:03:22 -07:00
isRemoteOk,
howToApply
2015-09-24 20:28:04 -07:00
} = this.props;
// sanitize user output
const jobValues = {
position: inHTMLData(position.value),
locale: inHTMLData(locale.value),
2015-09-24 20:28:04 -07:00
description: inHTMLData(description.value),
email: inHTMLData(email.value),
2015-10-30 14:43:38 -07:00
url: formatUrl(uriInSingleQuotedAttr(url.value), false),
logo: formatUrl(uriInSingleQuotedAttr(logo.value), false),
2015-10-15 22:30:07 -07:00
company: inHTMLData(company.value),
isHighlighted: !!isHighlighted.value,
2015-10-20 16:03:22 -07:00
isRemoteOk: !!isRemoteOk.value,
2015-11-04 13:45:22 -08:00
howToApply: inHTMLData(howToApply.value),
isFrontEndCert,
isFullStackCert
2015-09-24 20:28:04 -07:00
};
const job = Object.keys(jobValues).reduce((accu, prop) => {
if (jobValues[prop]) {
accu[prop] = jobValues[prop];
}
return accu;
}, {});
2015-09-26 22:23:56 -07:00
job.postedOn = new Date();
2015-09-24 20:28:04 -07:00
debug('job sanitized', job);
jobActions.saveForm(job);
2015-09-26 22:23:56 -07:00
this.history.pushState(null, '/jobs/new/preview');
2015-09-24 20:28:04 -07:00
},
componentDidMount() {
const { jobActions } = this.props;
jobActions.getSavedForm();
},
handleChange(name, { target: { value } }) {
const { jobActions: { handleForm } } = this.props;
handleForm({ [name]: value });
2015-09-14 17:31:48 -07:00
},
2015-11-04 14:23:42 -08:00
handleCertClick(name) {
const { jobActions: { handleForm } } = this.props;
const otherButton = name === 'isFrontEndCert' ?
'isFullStackCert' :
'isFrontEndCert';
handleForm({
[name]: true,
[otherButton]: false
});
},
2015-09-14 17:31:48 -07:00
render() {
const {
position,
locale,
2015-09-22 16:10:12 -07:00
description,
email,
2015-09-22 18:25:09 -07:00
url,
logo,
2015-10-15 22:30:07 -07:00
company,
isHighlighted,
isRemoteOk,
2015-10-20 16:03:22 -07:00
howToApply,
2015-11-04 13:45:22 -08:00
isFrontEndCert,
isFullStackCert,
jobActions: { handleForm }
} = this.props;
2015-11-04 13:45:22 -08:00
const { handleChange } = this;
const labelClass = 'col-sm-offset-1 col-sm-2';
const inputClass = 'col-sm-6';
2015-09-14 17:31:48 -07:00
return (
<div>
<Row>
<Col
md={ 10 }
mdOffset={ 1 }>
<Panel className='text-center'>
2015-09-24 20:28:04 -07:00
<form
className='form-horizontal'
onSubmit={ this.handleSubmit }>
2015-09-22 18:25:09 -07:00
2015-09-23 13:31:27 -07:00
<div className='spacer'>
2015-11-04 13:45:22 -08:00
<h2>First, select your ideal applicant: </h2>
</div>
<Row>
<Col
xs={ 6 }
xsOffset={ 3 }>
<Row>
2015-11-04 14:23:42 -08:00
<Button
className={ isFrontEndCert ? 'active' : '' }
2015-11-04 21:37:15 -08:00
onClick={ () => {
if (!isFrontEndCert) {
this.handleCertClick('isFrontEndCert');
}
}}>
2015-11-04 13:45:22 -08:00
<h4>Front End Development Certified</h4>
You can expect each applicant
to have a code portfolio using the
following technologies:
2015-11-04 21:37:15 -08:00
HTML5, CSS, jQuery, API integrations
2015-11-04 13:45:22 -08:00
<br />
<br />
</Button>
</Row>
<div className='button-spacer' />
<Row>
2015-11-04 14:23:42 -08:00
<Button
className={ isFullStackCert ? 'active' : ''}
2015-11-04 21:37:15 -08:00
onClick={ () => {
if (!isFullStackCert) {
this.handleCertClick('isFullStackCert');
}
}}>
2015-11-04 13:45:22 -08:00
<h4>Full Stack 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>
2015-09-23 13:31:27 -07:00
</div>
<Input
bsStyle={ position.bsStyle }
2015-10-16 16:12:01 -07:00
label='Job Title'
2015-09-23 13:31:27 -07:00
labelClassName={ labelClass }
onChange={ (e) => handleChange('position', e) }
2015-10-16 20:05:22 -07:00
placeholder={
'e.g. Full Stack Developer, Front End Developer, etc.'
}
2015-10-11 21:11:27 -07:00
required={ true }
2015-09-23 13:31:27 -07:00
type='text'
value={ position.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ locale.bsStyle }
label='Location'
labelClassName={ labelClass }
onChange={ (e) => handleChange('locale', e) }
2015-10-16 16:12:01 -07:00
placeholder='e.g. San Francisco, Remote, etc.'
2015-10-11 21:11:27 -07:00
required={ true }
2015-09-23 13:31:27 -07:00
type='text'
value={ locale.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ description.bsStyle }
label='Description'
labelClassName={ labelClass }
onChange={ (e) => handleChange('description', e) }
2015-10-11 21:11:27 -07:00
required={ true }
2015-09-23 13:31:27 -07:00
rows='10'
type='textarea'
value={ description.value }
wrapperClassName={ inputClass } />
2015-10-20 15:01:05 -07:00
<Input
checked={ isRemoteOk.value }
label={ isRemoteCopy }
onChange={
({ target: { checked } }) => handleForm({
isRemoteOk: !!checked
})
}
type='checkbox'
wrapperClassName={ checkboxClass } />
<div className='spacer' />
2015-10-20 16:03:22 -07:00
<Row>
<div>
<h2>How should they apply?</h2>
</div>
<Input
bsStyle={ howToApply.bsStyle }
label=' '
labelClassName={ labelClass }
onChange={ (e) => handleChange('howToApply', e) }
placeholder={ howToApplyCopy }
required={ true }
rows='2'
type='textarea'
value={ howToApply.value }
wrapperClassName={ inputClass } />
</Row>
2015-09-23 13:31:27 -07:00
2015-10-20 16:03:22 -07:00
<div className='spacer' />
<div>
2015-10-16 16:12:01 -07:00
<h2>Tell us about your organization</h2>
2015-09-23 13:31:27 -07:00
</div>
<Input
2015-10-15 22:30:07 -07:00
bsStyle={ company.bsStyle }
2015-09-23 13:31:27 -07:00
label='Company Name'
labelClassName={ labelClass }
2015-10-15 22:30:07 -07:00
onChange={ (e) => handleChange('company', e) }
2015-09-23 13:31:27 -07:00
type='text'
2015-10-15 22:30:07 -07:00
value={ company.value }
2015-09-23 13:31:27 -07:00
wrapperClassName={ inputClass } />
<Input
bsStyle={ email.bsStyle }
label='Email'
labelClassName={ labelClass }
onChange={ (e) => handleChange('email', e) }
2015-10-20 15:01:05 -07:00
placeholder='This is how we will contact you'
2015-10-11 21:11:27 -07:00
required={ true }
2015-09-23 13:31:27 -07:00
type='email'
value={ email.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ url.bsStyle }
label='URL'
labelClassName={ labelClass }
onChange={ (e) => handleChange('url', e) }
2015-10-20 15:01:05 -07:00
placeholder='http://yourcompany.com'
2015-09-23 13:31:27 -07:00
type='url'
value={ url.value }
wrapperClassName={ inputClass } />
<Input
bsStyle={ logo.bsStyle }
label='Logo'
labelClassName={ labelClass }
onChange={ (e) => handleChange('logo', e) }
2015-10-20 15:01:05 -07:00
placeholder='http://yourcompany.com/logo.png'
2015-09-23 13:31:27 -07:00
type='url'
value={ logo.value }
wrapperClassName={ inputClass } />
2015-09-22 18:25:09 -07:00
2015-10-20 15:01:05 -07:00
<div className='spacer' />
2015-10-20 15:30:25 -07:00
<Well>
<div>
<h2>Make it stand out</h2>
</div>
<div className='spacer' />
<Row>
<Col
md={ 6 }
mdOffset={ 3 }>
Highlight this ad to give it extra attention.
<br />
Featured listings receive more clicks and more applications.
</Col>
</Row>
<div className='spacer' />
<Row>
<Input
bsSize='large'
bsStyle='success'
checked={ isHighlighted.value }
label={ hightlightCopy }
onChange={
({ target: { checked } }) => handleForm({
isHighlighted: !!checked
})
}
type='checkbox'
wrapperClassName={
checkboxClass.replace('text-left', '')
} />
</Row>
</Well>
2015-10-20 16:03:22 -07:00
2015-09-23 13:31:27 -07:00
<Row>
<Col
2015-10-20 15:30:25 -07:00
className='text-left'
2015-09-23 13:31:27 -07:00
lg={ 6 }
lgOffset={ 3 }>
<Button
block={ true }
2015-09-24 20:28:04 -07:00
bsSize='large'
bsStyle='primary'
type='submit'>
2015-09-23 13:31:27 -07:00
Preview My Ad
</Button>
</Col>
</Row>
2015-09-14 17:31:48 -07:00
</form>
</Panel>
2015-09-14 17:31:48 -07:00
</Col>
</Row>
</div>
);
}
})
);