Jobs page initially renders
This commit is contained in:
@ -3,12 +3,17 @@ import { reducer as formReducer } from 'redux-form';
|
|||||||
|
|
||||||
import { reducer as app } from './redux';
|
import { reducer as app } from './redux';
|
||||||
import { reducer as hikesApp } from './routes/Hikes/redux';
|
import { reducer as hikesApp } from './routes/Hikes/redux';
|
||||||
|
import {
|
||||||
|
reducer as jobsApp,
|
||||||
|
formNormalizer as jobsNormalizer
|
||||||
|
} from './routes/Jobs/redux';
|
||||||
|
|
||||||
export default function createReducer(sideReducers = {}) {
|
export default function createReducer(sideReducers = {}) {
|
||||||
return combineReducers({
|
return combineReducers({
|
||||||
...sideReducers,
|
...sideReducers,
|
||||||
app,
|
app,
|
||||||
hikesApp,
|
hikesApp,
|
||||||
form: formReducer
|
jobsApp,
|
||||||
|
form: formReducer.normalize(jobsNormalizer)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,12 @@ import {
|
|||||||
fetchJobs
|
fetchJobs
|
||||||
} from '../redux/actions';
|
} from '../redux/actions';
|
||||||
|
|
||||||
const mapSateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
state => state.jobsApp.jobs.entities,
|
state => state.jobsApp.jobs.entities,
|
||||||
state => state.jobsApp.jobs.results,
|
state => state.jobsApp.jobs.results,
|
||||||
state => state.jobsApp,
|
state => state.jobsApp,
|
||||||
(jobsMap, jobsById) => {
|
(jobsMap, jobsById) => {
|
||||||
return jobsById.map(id => jobsMap[id]);
|
return { jobs: jobsById.map(id => jobsMap[id]) };
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -152,6 +152,6 @@ export class Jobs extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
connect(mapSateToProps, bindableActions),
|
connect(mapStateToProps, bindableActions),
|
||||||
contain(fetchOptions)
|
contain(fetchOptions)
|
||||||
)(Jobs);
|
)(Jobs);
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { helpers } from 'rx';
|
import { helpers } from 'rx';
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { reduxForm } from 'redux-form';
|
import { reduxForm } from 'redux-form';
|
||||||
import { connector } from 'react-redux';
|
// import debug from 'debug';
|
||||||
import debug from 'debug';
|
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import normalizeUrl from 'normalize-url';
|
|
||||||
|
|
||||||
import { getDefaults } from '../utils';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
inHTMLData,
|
isAscii,
|
||||||
uriInSingleQuotedAttr
|
isEmail,
|
||||||
} from 'xss-filters';
|
isURL
|
||||||
|
} from 'validator';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -20,30 +17,14 @@ import {
|
|||||||
Row
|
Row
|
||||||
} from 'react-bootstrap';
|
} from 'react-bootstrap';
|
||||||
|
|
||||||
import {
|
import { saveJob } from '../redux/actions';
|
||||||
isAscii,
|
|
||||||
isEmail,
|
|
||||||
isURL
|
|
||||||
} from 'validator';
|
|
||||||
|
|
||||||
const log = debug('fcc:jobs:newForm');
|
// const log = debug('fcc:jobs:newForm');
|
||||||
|
|
||||||
const checkValidity = [
|
|
||||||
'position',
|
|
||||||
'locale',
|
|
||||||
'description',
|
|
||||||
'email',
|
|
||||||
'url',
|
|
||||||
'logo',
|
|
||||||
'company',
|
|
||||||
'isHighlighted',
|
|
||||||
'howToApply'
|
|
||||||
];
|
|
||||||
const hightlightCopy = `
|
const hightlightCopy = `
|
||||||
Highlight my post to make it stand out. (+$250)
|
Highlight my post to make it stand out. (+$250)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
const isRemoteCopy = `
|
const isRemoteCopy = `
|
||||||
This job can be performed remotely.
|
This job can be performed remotely.
|
||||||
`;
|
`;
|
||||||
@ -60,177 +41,106 @@ const checkboxClass = dedent`
|
|||||||
col-sm-6 col-md-offset-3
|
col-sm-6 col-md-offset-3
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function formatValue(value, validator, type = 'string') {
|
const certTypes = {
|
||||||
const formatted = getDefaults(type);
|
isFrontEndCert: 'isFrontEndCert',
|
||||||
if (validator && type === 'string' && typeof value === 'string') {
|
isBackEndCert: 'isBackEndCert'
|
||||||
formatted.valid = validator(value);
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
formatted.value = value;
|
|
||||||
formatted.bsStyle = formatted.valid ? 'success' : 'error';
|
|
||||||
}
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeOptions = {
|
|
||||||
stripWWW: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatUrl(url, shouldKeepTrailingSlash = true) {
|
|
||||||
if (
|
|
||||||
typeof url === 'string' &&
|
|
||||||
url.length > 4 &&
|
|
||||||
url.indexOf('.') !== -1
|
|
||||||
) {
|
|
||||||
// prevent trailing / from being stripped during typing
|
|
||||||
let lastChar = '';
|
|
||||||
if (shouldKeepTrailingSlash && url.substring(url.length - 1) === '/') {
|
|
||||||
lastChar = '/';
|
|
||||||
}
|
|
||||||
return normalizeUrl(url, normalizeOptions) + lastChar;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidURL(data) {
|
function isValidURL(data) {
|
||||||
return isURL(data, { 'require_protocol': true });
|
return isURL(data, { 'require_protocol': true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
url: isValidURL,
|
||||||
|
logo: isValidURL,
|
||||||
|
company: makeRequired(isAscii),
|
||||||
|
howToApply: makeRequired(isAscii)
|
||||||
|
};
|
||||||
|
|
||||||
function makeRequired(validator) {
|
function makeRequired(validator) {
|
||||||
return (val) => !!val && validator(val);
|
return (val) => !!val && validator(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formOptions = {
|
function validateForm(values) {
|
||||||
fields: [
|
return Object.keys(fieldValidators)
|
||||||
'position',
|
.map(field => {
|
||||||
'locale',
|
if (fieldValidators[field](values[field])) {
|
||||||
'description',
|
return null;
|
||||||
'email',
|
}
|
||||||
'url',
|
return { [field]: fieldValidators[field](values[field]) };
|
||||||
'logo',
|
})
|
||||||
'company',
|
.filter(Boolean)
|
||||||
'isHighlighted',
|
.reduce((errors, error) => ({ ...errors, ...error }), {});
|
||||||
'isRemoteOk',
|
}
|
||||||
'isFrontEndCert',
|
|
||||||
'isBackEndCert',
|
function getBsStyle(field) {
|
||||||
'howToApply'
|
if (field.pristine) {
|
||||||
]
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.error ?
|
||||||
|
'error' :
|
||||||
|
'success';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NewJob extends React.Component {
|
export class NewJob extends React.Component {
|
||||||
static displayName = 'NewJob';
|
static displayName = 'NewJob';
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
jobActions: PropTypes.object,
|
|
||||||
fields: PropTypes.object,
|
fields: PropTypes.object,
|
||||||
onSubmit: PropTypes.func
|
handleSubmit: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const pros = this.props;
|
|
||||||
let valid = true;
|
|
||||||
checkValidity.forEach((prop) => {
|
|
||||||
// if value exist, check if it is valid
|
|
||||||
if (pros[prop].value && pros[prop].type !== 'boolean') {
|
|
||||||
valid = valid && !!pros[prop].valid;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
!valid ||
|
|
||||||
!pros.isFrontEndCert &&
|
|
||||||
!pros.isBackEndCert
|
|
||||||
) {
|
|
||||||
debug('form not valid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
jobActions,
|
|
||||||
|
|
||||||
// form values
|
|
||||||
position,
|
|
||||||
locale,
|
|
||||||
description,
|
|
||||||
email,
|
|
||||||
url,
|
|
||||||
logo,
|
|
||||||
company,
|
|
||||||
isFrontEndCert,
|
|
||||||
isBackEndCert,
|
|
||||||
isHighlighted,
|
|
||||||
isRemoteOk,
|
|
||||||
howToApply
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
// sanitize user output
|
|
||||||
const jobValues = {
|
|
||||||
position: inHTMLData(position.value),
|
|
||||||
locale: inHTMLData(locale.value),
|
|
||||||
description: inHTMLData(description.value),
|
|
||||||
email: inHTMLData(email.value),
|
|
||||||
url: formatUrl(uriInSingleQuotedAttr(url.value), false),
|
|
||||||
logo: formatUrl(uriInSingleQuotedAttr(logo.value), false),
|
|
||||||
company: inHTMLData(company.value),
|
|
||||||
isHighlighted: !!isHighlighted.value,
|
|
||||||
isRemoteOk: !!isRemoteOk.value,
|
|
||||||
howToApply: inHTMLData(howToApply.value),
|
|
||||||
isFrontEndCert,
|
|
||||||
isBackEndCert
|
|
||||||
};
|
|
||||||
|
|
||||||
const job = Object.keys(jobValues).reduce((accu, prop) => {
|
|
||||||
if (jobValues[prop]) {
|
|
||||||
accu[prop] = jobValues[prop];
|
|
||||||
}
|
|
||||||
return accu;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
job.postedOn = new Date();
|
|
||||||
debug('job sanitized', job);
|
|
||||||
jobActions.saveForm(job);
|
|
||||||
|
|
||||||
this.history.pushState(null, '/jobs/new/preview');
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { jobActions } = this.props;
|
// this.prop.getSavedForm();
|
||||||
jobActions.getSavedForm();
|
}
|
||||||
},
|
|
||||||
|
|
||||||
handleChange(name, { target: { value } }) {
|
|
||||||
const { jobActions: { handleForm } } = this.props;
|
|
||||||
handleForm({ [name]: value });
|
|
||||||
},
|
|
||||||
|
|
||||||
handleCertClick(name) {
|
handleCertClick(name) {
|
||||||
const { jobActions: { handleForm } } = this.props;
|
const { fields } = this.props;
|
||||||
const otherButton = name === 'isFrontEndCert' ?
|
Object.keys(certTypes).forEach(certType => {
|
||||||
'isBackEndCert' :
|
if (certType === name) {
|
||||||
'isFrontEndCert';
|
return fields[certType].onChange(true);
|
||||||
|
}
|
||||||
handleForm({
|
fields[certType].onChange(false);
|
||||||
[name]: true,
|
|
||||||
[otherButton]: false
|
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
position,
|
fields: {
|
||||||
locale,
|
position,
|
||||||
description,
|
locale,
|
||||||
email,
|
description,
|
||||||
url,
|
email,
|
||||||
logo,
|
url,
|
||||||
company,
|
logo,
|
||||||
isHighlighted,
|
company,
|
||||||
isRemoteOk,
|
isHighlighted,
|
||||||
howToApply,
|
isRemoteOk,
|
||||||
isFrontEndCert,
|
howToApply,
|
||||||
isBackEndCert,
|
isFrontEndCert,
|
||||||
jobActions: { handleForm }
|
isBackEndCert
|
||||||
|
},
|
||||||
|
handleSubmit
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { handleChange } = this;
|
const { handleChange } = this;
|
||||||
@ -246,7 +156,7 @@ export class NewJob extends React.Component {
|
|||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<form
|
<form
|
||||||
className='form-horizontal'
|
className='form-horizontal'
|
||||||
onSubmit={ this.handleSubmit }>
|
onSubmit={ handleSubmit(data => this.handleSubmit(data)) }>
|
||||||
|
|
||||||
<div className='spacer'>
|
<div className='spacer'>
|
||||||
<h2>First, select your ideal applicant: </h2>
|
<h2>First, select your ideal applicant: </h2>
|
||||||
@ -259,10 +169,10 @@ export class NewJob extends React.Component {
|
|||||||
<Row>
|
<Row>
|
||||||
<Button
|
<Button
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
className={ isFrontEndCert ? 'active' : '' }
|
className={ isFrontEndCert.value ? 'active' : '' }
|
||||||
onClick={ () => {
|
onClick={ () => {
|
||||||
if (!isFrontEndCert) {
|
if (!isFrontEndCert.value) {
|
||||||
this.handleCertClick('isFrontEndCert');
|
this.handleCertClick(certTypes.isFrontEndCert);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<h4>Front End Development Certified</h4>
|
<h4>Front End Development Certified</h4>
|
||||||
@ -278,10 +188,10 @@ export class NewJob extends React.Component {
|
|||||||
<Row>
|
<Row>
|
||||||
<Button
|
<Button
|
||||||
bsStyle='primary'
|
bsStyle='primary'
|
||||||
className={ isBackEndCert ? 'active' : ''}
|
className={ isBackEndCert.value ? 'active' : ''}
|
||||||
onClick={ () => {
|
onClick={ () => {
|
||||||
if (!isBackEndCert) {
|
if (!isBackEndCert.value) {
|
||||||
this.handleCertClick('isBackEndCert');
|
this.handleCertClick(certTypes.isBackEndCert);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<h4>Back End Development Certified</h4>
|
<h4>Back End Development Certified</h4>
|
||||||
@ -300,47 +210,43 @@ export class NewJob extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ position.bsStyle }
|
bsStyle={ getBsStyle(position) }
|
||||||
label='Job Title'
|
label='Job Title'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('position', e) }
|
|
||||||
placeholder={
|
placeholder={
|
||||||
'e.g. Full Stack Developer, Front End Developer, etc.'
|
'e.g. Full Stack Developer, Front End Developer, etc.'
|
||||||
}
|
}
|
||||||
required={ true }
|
required={ true }
|
||||||
type='text'
|
type='text'
|
||||||
value={ position.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...position }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ locale.bsStyle }
|
bsStyle={ getBsStyle(locale) }
|
||||||
label='Location'
|
label='Location'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('locale', e) }
|
|
||||||
placeholder='e.g. San Francisco, Remote, etc.'
|
placeholder='e.g. San Francisco, Remote, etc.'
|
||||||
required={ true }
|
required={ true }
|
||||||
type='text'
|
type='text'
|
||||||
value={ locale.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...locale }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ description.bsStyle }
|
bsStyle={ getBsStyle(description) }
|
||||||
label='Description'
|
label='Description'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('description', e) }
|
|
||||||
required={ true }
|
required={ true }
|
||||||
rows='10'
|
rows='10'
|
||||||
type='textarea'
|
type='textarea'
|
||||||
value={ description.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...description }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
checked={ isRemoteOk.value }
|
|
||||||
label={ isRemoteCopy }
|
label={ isRemoteCopy }
|
||||||
onChange={
|
|
||||||
({ target: { checked } }) => handleForm({
|
|
||||||
isRemoteOk: !!checked
|
|
||||||
})
|
|
||||||
}
|
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
wrapperClassName={ checkboxClass } />
|
wrapperClassName={ checkboxClass }
|
||||||
|
{ ...isRemoteOk }
|
||||||
|
/>
|
||||||
<div className='spacer' />
|
<div className='spacer' />
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@ -349,16 +255,16 @@ export class NewJob extends React.Component {
|
|||||||
<h2>How should they apply?</h2>
|
<h2>How should they apply?</h2>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ howToApply.bsStyle }
|
bsStyle={ getBsStyle(howToApply) }
|
||||||
label=' '
|
label=' '
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('howToApply', e) }
|
|
||||||
placeholder={ howToApplyCopy }
|
placeholder={ howToApplyCopy }
|
||||||
required={ true }
|
required={ true }
|
||||||
rows='2'
|
rows='2'
|
||||||
type='textarea'
|
type='textarea'
|
||||||
value={ howToApply.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...howToApply }
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<div className='spacer' />
|
<div className='spacer' />
|
||||||
@ -367,41 +273,42 @@ export class NewJob extends React.Component {
|
|||||||
<h2>Tell us about your organization</h2>
|
<h2>Tell us about your organization</h2>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ company.bsStyle }
|
bsStyle={ getBsStyle(company) }
|
||||||
label='Company Name'
|
label='Company Name'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('company', e) }
|
onChange={ (e) => handleChange('company', e) }
|
||||||
type='text'
|
type='text'
|
||||||
value={ company.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...company }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ email.bsStyle }
|
bsStyle={ getBsStyle(email) }
|
||||||
label='Email'
|
label='Email'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('email', e) }
|
|
||||||
placeholder='This is how we will contact you'
|
placeholder='This is how we will contact you'
|
||||||
required={ true }
|
required={ true }
|
||||||
type='email'
|
type='email'
|
||||||
value={ email.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...email }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ url.bsStyle }
|
bsStyle={ getBsStyle(url) }
|
||||||
label='URL'
|
label='URL'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('url', e) }
|
|
||||||
placeholder='http://yourcompany.com'
|
placeholder='http://yourcompany.com'
|
||||||
type='url'
|
type='url'
|
||||||
value={ url.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...url }
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
bsStyle={ logo.bsStyle }
|
bsStyle={ getBsStyle(logo) }
|
||||||
label='Logo'
|
label='Logo'
|
||||||
labelClassName={ labelClass }
|
labelClassName={ labelClass }
|
||||||
onChange={ (e) => handleChange('logo', e) }
|
|
||||||
placeholder='http://yourcompany.com/logo.png'
|
placeholder='http://yourcompany.com/logo.png'
|
||||||
type='url'
|
type='url'
|
||||||
value={ logo.value }
|
wrapperClassName={ inputClass }
|
||||||
wrapperClassName={ inputClass } />
|
{ ...logo }
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='spacer' />
|
<div className='spacer' />
|
||||||
<hr />
|
<hr />
|
||||||
@ -416,7 +323,7 @@ export class NewJob extends React.Component {
|
|||||||
mdOffset={ 3 }>
|
mdOffset={ 3 }>
|
||||||
Highlight this ad to give it extra attention.
|
Highlight this ad to give it extra attention.
|
||||||
<br />
|
<br />
|
||||||
Featured listings receive more clicks and more applications.
|
Featured listings receive more clicks and more applications.
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<div className='spacer' />
|
<div className='spacer' />
|
||||||
@ -424,17 +331,13 @@ export class NewJob extends React.Component {
|
|||||||
<Input
|
<Input
|
||||||
bsSize='large'
|
bsSize='large'
|
||||||
bsStyle='success'
|
bsStyle='success'
|
||||||
checked={ isHighlighted.value }
|
|
||||||
label={ hightlightCopy }
|
label={ hightlightCopy }
|
||||||
onChange={
|
|
||||||
({ target: { checked } }) => handleForm({
|
|
||||||
isHighlighted: !!checked
|
|
||||||
})
|
|
||||||
}
|
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
wrapperClassName={
|
wrapperClassName={
|
||||||
checkboxClass.replace('text-left', '')
|
checkboxClass.replace('text-left', '')
|
||||||
} />
|
}
|
||||||
|
{ ...isHighlighted }
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -462,7 +365,13 @@ export class NewJob extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default reduxForm(
|
export default reduxForm(
|
||||||
formOptions,
|
{
|
||||||
mapStateToProps,
|
form: 'NewJob',
|
||||||
bindableActions
|
fields,
|
||||||
|
validate: validateForm
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
onSubmit: saveJob
|
||||||
|
}
|
||||||
)(NewJob);
|
)(NewJob);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import { Button, Row, Col } from 'react-bootstrap';
|
import { Button, Row, Col } from 'react-bootstrap';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
import { goBack, push } from 'react-router-redux';
|
import { goBack, push } from 'react-router-redux';
|
||||||
|
|
||||||
@ -10,11 +9,7 @@ import JobNotFound from './JobNotFound.jsx';
|
|||||||
|
|
||||||
import { clearSavedForm, saveJobToDb } from '../redux/actions';
|
import { clearSavedForm, saveJobToDb } from '../redux/actions';
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = state => ({ job: state.jobsApp.newJob });
|
||||||
state => state.jobsApp.previewJob,
|
|
||||||
state => state.jobsApp.jobs.entities
|
|
||||||
(job, jobsMap) => ({ job: jobsMap[job] || {} })
|
|
||||||
);
|
|
||||||
|
|
||||||
const bindableActions = {
|
const bindableActions = {
|
||||||
goBack,
|
goBack,
|
||||||
|
38
common/app/routes/Jobs/redux/jobs-form-normalizer.js
Normal file
38
common/app/routes/Jobs/redux/jobs-form-normalizer.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import normalizeUrl from 'normalize-url';
|
||||||
|
import {
|
||||||
|
inHTMLData,
|
||||||
|
uriInSingleQuotedAttr
|
||||||
|
} from 'xss-filters';
|
||||||
|
|
||||||
|
const normalizeOptions = {
|
||||||
|
stripWWW: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatUrl(url, shouldKeepTrailingSlash = true) {
|
||||||
|
if (
|
||||||
|
typeof url === 'string' &&
|
||||||
|
url.length > 4 &&
|
||||||
|
url.indexOf('.') !== -1
|
||||||
|
) {
|
||||||
|
// prevent trailing / from being stripped during typing
|
||||||
|
let lastChar = '';
|
||||||
|
if (shouldKeepTrailingSlash && url.substring(url.length - 1) === '/') {
|
||||||
|
lastChar = '/';
|
||||||
|
}
|
||||||
|
return normalizeUrl(url, normalizeOptions) + lastChar;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
NewJob: {
|
||||||
|
position: inHTMLData,
|
||||||
|
locale: inHTMLData,
|
||||||
|
description: inHTMLData,
|
||||||
|
email: inHTMLData,
|
||||||
|
url: value => formatUrl(uriInSingleQuotedAttr(value)),
|
||||||
|
logo: value => formatUrl(uriInSingleQuotedAttr(value)),
|
||||||
|
company: inHTMLData,
|
||||||
|
howToApply: inHTMLData
|
||||||
|
}
|
||||||
|
};
|
@ -66,8 +66,6 @@ export default function contain(options = {}, Component) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static displayName = `Container(${Component.displayName})`;
|
static displayName = `Container(${Component.displayName})`;
|
||||||
static propTypes = Component.propTypes;
|
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
...Component.contextTypes,
|
...Component.contextTypes,
|
||||||
professor: PropTypes.object
|
professor: PropTypes.object
|
||||||
|
Reference in New Issue
Block a user