Initial Job move to redux

This commit is contained in:
Berkeley Martinez
2016-02-05 20:48:59 -08:00
parent 5f97394520
commit 371cde1e34
15 changed files with 661 additions and 585 deletions

View File

@ -1,4 +1,5 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
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';
@ -7,6 +8,7 @@ export default function createReducer(sideReducers = {}) {
return combineReducers({ return combineReducers({
...sideReducers, ...sideReducers,
app, app,
hikesApp hikesApp,
form: formReducer
}); });
} }

View File

@ -1,277 +0,0 @@
import React, { PropTypes } from 'react';
import { Button, Input, Col, Row, Well } from 'react-bootstrap';
import { contain } from 'thundercats-react';
// real paypal buttons
// will take your money
const paypalIds = {
regular: 'Q8Z82ZLAX3Q8N',
highlighted: 'VC8QPSKCYMZLN'
};
export default contain(
{
store: 'appStore',
actions: [
'jobActions',
'appActions'
],
map({ jobsApp: {
currentJob: { id, isHighlighted } = {},
buttonId = isHighlighted ?
paypalIds.highlighted :
paypalIds.regular,
price = 1000,
discountAmount = 0,
promoCode = '',
promoApplied = false,
promoName = ''
}}) {
return {
id,
isHighlighted,
buttonId,
price,
discountAmount,
promoName,
promoCode,
promoApplied
};
}
},
React.createClass({
displayName: 'GoToPayPal',
propTypes: {
appActions: PropTypes.object,
id: PropTypes.string,
isHighlighted: PropTypes.bool,
buttonId: PropTypes.string,
price: PropTypes.number,
discountAmount: PropTypes.number,
promoName: PropTypes.string,
promoCode: PropTypes.string,
promoApplied: PropTypes.bool,
jobActions: PropTypes.object
},
componentDidMount() {
const { jobActions } = this.props;
jobActions.clearPromo();
},
goToJobBoard() {
const { appActions } = this.props;
setTimeout(() => appActions.goTo('/jobs'), 0);
},
renderDiscount(discountAmount) {
if (!discountAmount) {
return null;
}
return (
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Promo Discount</h4>
</Col>
<Col
md={ 3 }>
<h4>-{ discountAmount }</h4>
</Col>
</Row>
);
},
renderHighlightPrice(isHighlighted) {
if (!isHighlighted) {
return null;
}
return (
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Highlighting</h4>
</Col>
<Col
md={ 3 }>
<h4>+ 250</h4>
</Col>
</Row>
);
},
renderPromo() {
const {
id,
promoApplied,
promoCode,
promoName,
isHighlighted,
jobActions
} = this.props;
if (promoApplied) {
return (
<div>
<div className='spacer' />
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
{ promoName } applied
</Col>
</Row>
</div>
);
}
return (
<div>
<div className='spacer' />
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
Have a promo code?
</Col>
</Row>
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<Input
onChange={ jobActions.setPromoCode }
type='text'
value={ promoCode } />
</Col>
<Col
md={ 3 }>
<Button
block={ true }
onClick={ () => {
jobActions.applyCode({
id,
code: promoCode,
type: isHighlighted ? 'isHighlighted' : null
});
}}>
Apply Promo Code
</Button>
</Col>
</Row>
</div>
);
},
render() {
const {
id,
isHighlighted,
buttonId,
price,
discountAmount
} = this.props;
return (
<div>
<Row>
<Col
md={ 10 }
mdOffset={ 1 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }>
<div>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }>
<h2 className='text-center'>
One more step
</h2>
<div className='spacer' />
You're Awesome! just one more step to go.
Clicking on the link below will redirect to paypal.
</Col>
</Row>
<div className='spacer' />
<Well>
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Job Posting</h4>
</Col>
<Col
md={ 6 }>
<h4>+ { price }</h4>
</Col>
</Row>
{ this.renderHighlightPrice(isHighlighted) }
{ this.renderDiscount(discountAmount) }
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Total</h4>
</Col>
<Col
md={ 6 }>
<h4>${
price - discountAmount + (isHighlighted ? 250 : 0)
}</h4>
</Col>
</Row>
</Well>
{ this.renderPromo() }
<div className='spacer' />
<Row>
<Col
md={ 6 }
mdOffset={ 3 }>
<form
action='https://www.paypal.com/cgi-bin/webscr'
method='post'
onClick={ this.goToJobBoard }
target='_blank'>
<input
name='cmd'
type='hidden'
value='_s-xclick' />
<input
name='hosted_button_id'
type='hidden'
value={ buttonId } />
<input
name='custom'
type='hidden'
value={ '' + id } />
<Button
block={ true }
bsSize='large'
className='signup-btn'
type='submit'>
<i className='fa fa-paypal' />
Continue to PayPal
</Button>
<div className='spacer' />
<img
alt='An array of credit cards'
border='0'
src='http://i.imgur.com/Q2SdSZG.png'
style={{
width: '100%'
}} />
</form>
</Col>
</Row>
<div className='spacer' />
</div>
</Col>
</Row>
</div>
);
}
})
);

View File

@ -2,8 +2,12 @@ import React from 'react';
import { LinkContainer } from 'react-router-bootstrap'; import { LinkContainer } from 'react-router-bootstrap';
import { Button, Row, Col } from 'react-bootstrap'; import { Button, Row, Col } from 'react-bootstrap';
export default React.createClass({ export default class extends React.Component {
displayName: 'NoJobFound', static displayName = 'NoJobFound';
shouldComponentUpdate() {
return false;
}
render() { render() {
return ( return (
@ -28,4 +32,4 @@ export default React.createClass({
</div> </div>
); );
} }
}); }

View File

@ -0,0 +1,284 @@
import React, { PropTypes } from 'react';
import { Button, Input, Col, Row, Well } from 'react-bootstrap';
import { connect } from 'react-redux';
import PureComponent from 'react-pure-render/component';
import { createSelector } from 'reselect';
// real paypal buttons
// will take your money
const paypalIds = {
regular: 'Q8Z82ZLAX3Q8N',
highlighted: 'VC8QPSKCYMZLN'
};
const bindableActions = {
};
const mapStateToProps = createSelector(
state => state.jobsApp.currentJob,
state => state.jobsApp,
(
{ id, isHighlighted } = {},
{
buttonId,
price = 1000,
discountAmount = 0,
promoCode = '',
promoApplied = false,
promoName = ''
}
) => {
if (!buttonId) {
buttonId = isHighlighted ?
paypalIds.highlighted :
paypalIds.regular;
}
return {
id,
isHighlighted,
price,
discountAmount,
promoName,
promoCode,
promoApplied
};
}
);
export class JobTotal extends PureComponent {
static displayName = 'JobTotal';
static propTypes = {
id: PropTypes.string,
isHighlighted: PropTypes.bool,
buttonId: PropTypes.string,
price: PropTypes.number,
discountAmount: PropTypes.number,
promoName: PropTypes.string,
promoCode: PropTypes.string,
promoApplied: PropTypes.bool
};
componentDidMount() {
const { jobActions } = this.props;
jobActions.clearPromo();
}
goToJobBoard() {
const { appActions } = this.props;
setTimeout(() => appActions.goTo('/jobs'), 0);
}
renderDiscount(discountAmount) {
if (!discountAmount) {
return null;
}
return (
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Promo Discount</h4>
</Col>
<Col
md={ 3 }>
<h4>-{ discountAmount }</h4>
</Col>
</Row>
);
}
renderHighlightPrice(isHighlighted) {
if (!isHighlighted) {
return null;
}
return (
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Highlighting</h4>
</Col>
<Col
md={ 3 }>
<h4>+ 250</h4>
</Col>
</Row>
);
}
renderPromo() {
const {
id,
promoApplied,
promoCode,
promoName,
isHighlighted,
jobActions
} = this.props;
if (promoApplied) {
return (
<div>
<div className='spacer' />
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
{ promoName } applied
</Col>
</Row>
</div>
);
}
return (
<div>
<div className='spacer' />
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
Have a promo code?
</Col>
</Row>
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<Input
onChange={ jobActions.setPromoCode }
type='text'
value={ promoCode } />
</Col>
<Col
md={ 3 }>
<Button
block={ true }
onClick={ () => {
jobActions.applyCode({
id,
code: promoCode,
type: isHighlighted ? 'isHighlighted' : null
});
}}>
Apply Promo Code
</Button>
</Col>
</Row>
</div>
);
}
render() {
const {
id,
isHighlighted,
buttonId,
price,
discountAmount
} = this.props;
return (
<div>
<Row>
<Col
md={ 10 }
mdOffset={ 1 }
sm={ 8 }
smOffset={ 2 }
xs={ 12 }>
<div>
<Row>
<Col
md={ 6 }
mdOffset={ 3 }>
<h2 className='text-center'>
One more step
</h2>
<div className='spacer' />
You're Awesome! just one more step to go.
Clicking on the link below will redirect to paypal.
</Col>
</Row>
<div className='spacer' />
<Well>
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Job Posting</h4>
</Col>
<Col
md={ 6 }>
<h4>+ { price }</h4>
</Col>
</Row>
{ this.renderHighlightPrice(isHighlighted) }
{ this.renderDiscount(discountAmount) }
<Row>
<Col
md={ 3 }
mdOffset={ 3 }>
<h4>Total</h4>
</Col>
<Col
md={ 6 }>
<h4>${
price - discountAmount + (isHighlighted ? 250 : 0)
}</h4>
</Col>
</Row>
</Well>
{ this.renderPromo() }
<div className='spacer' />
<Row>
<Col
md={ 6 }
mdOffset={ 3 }>
<form
action='https://www.paypal.com/cgi-bin/webscr'
method='post'
onClick={ this.goToJobBoard }
target='_blank'>
<input
name='cmd'
type='hidden'
value='_s-xclick' />
<input
name='hosted_button_id'
type='hidden'
value={ buttonId } />
<input
name='custom'
type='hidden'
value={ '' + id } />
<Button
block={ true }
bsSize='large'
className='signup-btn'
type='submit'>
<i className='fa fa-paypal' />
Continue to PayPal
</Button>
<div className='spacer' />
<img
alt='An array of credit cards'
border='0'
src='http://i.imgur.com/Q2SdSZG.png'
style={{
width: '100%'
}} />
</form>
</Col>
</Row>
<div className='spacer' />
</div>
</Col>
</Row>
</div>
);
}
}
export default connect(mapStateToProps, bindableActions)(JobTotal);

View File

@ -1,132 +1,156 @@
import React, { cloneElement, PropTypes } from 'react'; import React, { cloneElement, PropTypes } from 'react';
import { contain } from 'thundercats-react'; import { connect, compose } from 'redux';
import { createSelector } from 'reselect';
import { push } from 'react-router-redux';
import PureComponent from 'react-pure-render/component';
import { Button, Row, Col } from 'react-bootstrap'; import { Button, Row, Col } from 'react-bootstrap';
import contain from '../../../utils/professor-x';
import ListJobs from './List.jsx'; import ListJobs from './List.jsx';
export default contain( import {
{ findJob,
store: 'appStore', fetchJobs
map({ jobsApp: { jobs, showModal }}) { } from '../redux/actions';
return { jobs, showModal };
},
fetchAction: 'jobActions.getJobs',
isPrimed({ jobs = [] }) {
return !!jobs.length;
},
actions: [
'appActions',
'jobActions'
]
},
React.createClass({
displayName: 'Jobs',
propTypes: { const mapSateToProps = createSelector(
children: PropTypes.element, state => state.jobsApp.jobs.entities,
appActions: PropTypes.object, state => state.jobsApp.jobs.results,
jobActions: PropTypes.object, state => state.jobsApp,
jobs: PropTypes.array, (jobsMap, jobsById) => {
showModal: PropTypes.bool return jobsById.map(id => jobsMap[id]);
}, }
);
handleJobClick(id) { const bindableActions = {
const { appActions, jobActions } = this.props; findJob,
if (!id) { fetchJobs,
return null; push
} };
jobActions.findJob(id);
appActions.goTo(`/jobs/${id}`);
},
renderList(handleJobClick, jobs) { const fetchOptions = {
return ( fetchAction: 'fetchJobs',
<ListJobs isPrimed({ jobs }) {
handleClick={ handleJobClick } return !!jobs.results.length;
jobs={ jobs }/> }
); };
},
renderChild(child, jobs) { export class Jobs extends PureComponent {
if (!child) { static displayName = 'Jobs';
return null;
}
return cloneElement(
child,
{ jobs }
);
},
render() { static propTypes = {
const { push: PropTypes.func,
children, findJob: PropTypes.func,
jobs, fetchJobs: PropTypes.func,
appActions children: PropTypes.element,
} = this.props; jobs: PropTypes.array,
showModal: PropTypes.bool
};
return ( createJobClickHandler(id) {
const { findJob, push } = this.props;
if (!id) {
return null;
}
return id => {
findJob(id);
push(`/jobs/${id}`);
};
}
renderList(handleJobClick, jobs) {
return (
<ListJobs
handleClick={ handleJobClick }
jobs={ jobs }/>
);
}
renderChild(child, jobs) {
if (!child) {
return null;
}
return cloneElement(
child,
{ jobs }
);
}
render() {
const {
children,
jobs
} = this.props;
return (
<Row>
<Col
md={ 10 }
mdOffset= { 1 }
xs={ 12 }>
<h1 className='text-center'>
Hire a JavaScript engineer who's experienced in HTML5,
Node.js, MongoDB, and Agile Development.
</h1>
<div className='spacer' />
<Row className='text-center'>
<Col
sm={ 8 }
smOffset={ 2 }
xs={ 12 }>
<Button
className='signup-btn btn-block btn-cta'
onClick={ ()=> {
push('/jobs/new');
}}>
Post a job: $1,000
</Button>
<div className='spacer' />
</Col>
</Row>
<div className='spacer' />
<Row> <Row>
<Col <Col
md={ 10 } md={ 2 }
mdOffset= { 1 } xs={ 4 }>
xs={ 12 }> <img
<h1 className='text-center'> alt={`
Hire a JavaScript engineer who's experienced in HTML5, a photo of Michael Gai, who recently hired a software
Node.js, MongoDB, and Agile Development. engineer through Free Code Camp
</h1> `}
<div className='spacer' /> className='img-responsive testimonial-image-jobs img-center'
<Row className='text-center'> src='http://i.imgur.com/tGcAA8H.jpg' />
<Col </Col>
sm={ 8 } <Col
smOffset={ 2 } md={ 10 }
xs={ 12 }> xs={ 8 }>
<Button <blockquote>
className='signup-btn btn-block btn-cta' <p>
onClick={ ()=> { We hired our last developer out of Free Code Camp
appActions.goTo('/jobs/new'); and couldn't be happier. Free Code Camp is now
}}> our go-to way to bring on pre-screened candidates
Post a job: $1,000 who are enthusiastic about learning quickly and
</Button> becoming immediately productive in their new career.
<div className='spacer' /> </p>
</Col> <footer>
</Row> Michael Gai, <cite>CEO at CoNarrative</cite>
<div className='spacer' /> </footer>
<Row> </blockquote>
<Col </Col>
md={ 2 } </Row>
xs={ 4 }> <Row>
<img
alt={`
a photo of Michael Gai, who recently hired a software
engineer through Free Code Camp
`}
className='img-responsive testimonial-image-jobs img-center'
src='http://i.imgur.com/tGcAA8H.jpg' />
</Col>
<Col
md={ 10 }
xs={ 8 }>
<blockquote>
<p>
We hired our last developer out of Free Code Camp
and couldn't be happier. Free Code Camp is now
our go-to way to bring on pre-screened candidates
who are enthusiastic about learning quickly and
becoming immediately productive in their new career.
</p>
<footer>
Michael Gai, <cite>CEO at CoNarrative</cite>
</footer>
</blockquote>
</Col>
</Row>
<Row>
{ this.renderChild(children, jobs) || { this.renderChild(children, jobs) ||
this.renderList(this.handleJobClick, jobs) } this.renderList(this.createJobClickHandler(), jobs) }
</Row> </Row>
</Col> </Col>
</Row> </Row>
); );
} }
}) }
);
export default compose(
connect(mapSateToProps, bindableActions),
contain(fetchOptions)
)(Jobs);

View File

@ -1,14 +1,15 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { ListGroup, ListGroupItem } from 'react-bootstrap'; import { ListGroup, ListGroupItem } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
export default React.createClass({ export default class ListJobs extends PureComponent {
displayName: 'ListJobs', static displayName = 'ListJobs';
propTypes: { static propTypes = {
handleClick: PropTypes.func, handleClick: PropTypes.func,
jobs: PropTypes.array jobs: PropTypes.array
}, };
addLocation(locale) { addLocation(locale) {
if (!locale) { if (!locale) {
@ -19,50 +20,50 @@ export default React.createClass({
{ locale } { locale }
</span> </span>
); );
}, }
renderJobs(handleClick, jobs = []) { renderJobs(handleClick, jobs = []) {
return jobs return jobs
.filter(({ isPaid, isApproved, isFilled }) => { .filter(({ isPaid, isApproved, isFilled }) => {
return isPaid && isApproved && !isFilled; return isPaid && isApproved && !isFilled;
}) })
.map(({ .map(({
id, id,
company, company,
position, position,
isHighlighted, isHighlighted,
locale locale
}) => { }) => {
const className = classnames({ const className = classnames({
'jobs-list': true, 'jobs-list': true,
'col-xs-12': true, 'col-xs-12': true,
'jobs-list-highlight': isHighlighted 'jobs-list-highlight': isHighlighted
});
return (
<ListGroupItem
className={ className }
key={ id }
onClick={ () => handleClick(id) }>
<div>
<h4 className='pull-left' style={{ display: 'inline-block' }}>
<bold>{ company }</bold>
{' '}
<span className='hidden-xs hidden-sm'>
- { position }
</span>
</h4>
<h4
className='pull-right'
style={{ display: 'inline-block' }}>
{ this.addLocation(locale) }
</h4>
</div>
</ListGroupItem>
);
}); });
},
return (
<ListGroupItem
className={ className }
key={ id }
onClick={ () => handleClick(id) }>
<div>
<h4 className='pull-left' style={{ display: 'inline-block' }}>
<bold>{ company }</bold>
{' '}
<span className='hidden-xs hidden-sm'>
- { position }
</span>
</h4>
<h4
className='pull-right'
style={{ display: 'inline-block' }}>
{ this.addLocation(locale) }
</h4>
</div>
</ListGroupItem>
);
});
}
render() { render() {
const { const {
@ -76,4 +77,4 @@ export default React.createClass({
</ListGroup> </ListGroup>
); );
} }
}); }

View File

@ -2,8 +2,8 @@ import React from 'react';
import { LinkContainer } from 'react-router-bootstrap'; import { LinkContainer } from 'react-router-bootstrap';
import { Button, Col, Row } from 'react-bootstrap'; import { Button, Col, Row } from 'react-bootstrap';
export default React.createClass({ export default class extends React.createClass {
displayName: 'NewJobCompleted', static displayName = 'NewJobCompleted';
render() { render() {
return ( return (
@ -36,4 +36,4 @@ export default React.createClass({
</div> </div>
); );
} }
}); }

View File

@ -1,79 +1,84 @@
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 { contain } from 'thundercats-react'; import { connect } from 'redux';
import { createSelector } from 'reselect';
import PureComponent from 'react-pure-render/component';
import { goBack, push } from 'react-router-redux';
import ShowJob from './ShowJob.jsx'; import ShowJob from './ShowJob.jsx';
import JobNotFound from './JobNotFound.jsx'; import JobNotFound from './JobNotFound.jsx';
export default contain( import { clearSavedForm, saveJobToDb } from '../redux/actions';
{
store: 'appStore', const mapStateToProps = createSelector(
actions: [ state => state.jobsApp.form,
'appActions', (job = {}) => ({ job })
'jobActions' );
],
map({ jobsApp: { form: job = {} } }) { const bindableActions = {
return { job }; goBack,
push,
clearSavedForm,
saveJobToDb
};
export class JobPreview extends PureComponent {
static displayName = 'Preview';
static propTypes = {
job: PropTypes.object
};
componentDidMount() {
const { push, job } = this.props;
// redirect user in client
if (!job || !job.position || !job.description) {
push('/jobs/new');
} }
}, }
React.createClass({
displayName: 'Preview',
propTypes: { render() {
appActions: PropTypes.object, const { job, goBack, clearSavedForm, saveJobToDb } = this.props;
job: PropTypes.object,
jobActions: PropTypes.object
},
componentDidMount() { if (!job || !job.position || !job.description) {
const { appActions, job } = this.props; return <JobNotFound />;
// redirect user in client }
if (!job || !job.position || !job.description) {
appActions.goTo('/jobs/new');
}
},
render() { return (
const { appActions, job, jobActions } = this.props; <div>
<ShowJob job={ job } />
<div className='spacer'></div>
<hr />
<Row>
<Col
md={ 10 }
mdOffset={ 1 }
xs={ 12 }>
<div>
<Button
block={ true }
className='signup-btn'
onClick={ () => {
clearSavedForm();
saveJobToDb({
goTo: '/jobs/new/check-out',
job
});
}}>
if (!job || !job.position || !job.description) { Looks great! Let's Check Out
return <JobNotFound />;
}
return (
<div>
<ShowJob job={ job } />
<div className='spacer'></div>
<hr />
<Row>
<Col
md={ 10 }
mdOffset={ 1 }
xs={ 12 }>
<div>
<Button
block={ true }
className='signup-btn'
onClick={ () => {
jobActions.clearSavedForm();
jobActions.saveJobToDb({
goTo: '/jobs/new/check-out',
job
});
}}>
Looks great! Let's Check Out
</Button> </Button>
<Button <Button
block={ true } block={ true }
onClick={ () => appActions.goBack() } > onClick={ goBack } >
Head back and make edits Head back and make edits
</Button> </Button>
</div> </div>
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} }
}) }
);
export default connect(mapStateToProps, bindableActions)(JobPreview);

View File

@ -1,6 +1,11 @@
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { History } from 'react-router'; import { connect, compose } from 'redux';
import { contain } from 'thundercats-react'; import { push } from 'react-router-redux';
import PureComponent from 'react-pure-render/component';
import { createSelector } from 'reselect';
import contain from '../../../utils/professor-x';
import { fetchJob } from '../redux/actions';
import ShowJob from './ShowJob.jsx'; import ShowJob from './ShowJob.jsx';
import JobNotFound from './JobNotFound.jsx'; import JobNotFound from './JobNotFound.jsx';
@ -51,86 +56,89 @@ function generateMessage(
"You've earned it, so feel free to apply."; "You've earned it, so feel free to apply.";
} }
export default contain( const mapStateToProps = createSelector(
{ state => state.app,
store: 'appStore', state => state.jobsApp.currentJob,
fetchAction: 'jobActions.getJob', ({ username, isFrontEndCert, isBackEndCert }, job = {}) => ({
map({ username,
username, isFrontEndCert,
isFrontEndCert, isBackEndCert,
isBackEndCert, job
jobsApp: { currentJob }
}) {
return {
username,
job: currentJob,
isFrontEndCert,
isBackEndCert
};
},
getPayload({ params: { id } }) {
return id;
},
isPrimed({ params: { id } = {}, job = {} }) {
return job.id === id;
},
// using es6 destructuring
shouldContainerFetch({ job = {} }, { params: { id } }
) {
return job.id !== id;
}
},
React.createClass({
displayName: 'Show',
propTypes: {
job: PropTypes.object,
isBackEndCert: PropTypes.bool,
isFrontEndCert: PropTypes.bool,
username: PropTypes.string
},
mixins: [History],
componentDidMount() {
const { job } = this.props;
// redirect user in client
if (!isJobValid(job)) {
this.history.pushState(null, '/jobs');
}
},
render() {
const {
isBackEndCert,
isFrontEndCert,
job,
username
} = this.props;
if (!isJobValid(job)) {
return <JobNotFound />;
}
const isSignedIn = !!username;
const showApply = shouldShowApply(
job,
{ isFrontEndCert, isBackEndCert }
);
const message = generateMessage(
job,
{ isFrontEndCert, isBackEndCert, isSignedIn }
);
return (
<ShowJob
message={ message }
preview={ false }
showApply={ showApply }
{ ...this.props }/>
);
}
}) })
); );
const bindableActions = {
push,
fetchJob
};
const fetchOptions = {
fetchAction: 'fetchJob',
getActionArgs({ params: { id } }) {
return [ id ];
},
isPrimed({ params: { id } = {}, job = {} }) {
return job.id === id;
},
// using es6 destructuring
shouldRefetch({ job }, { params: { id } }) {
return job.id !== id;
}
};
export class Show extends PureComponent {
static displayName = 'Show';
static propTypes = {
job: PropTypes.object,
isBackEndCert: PropTypes.bool,
isFrontEndCert: PropTypes.bool,
username: PropTypes.string
};
componentDidMount() {
const { job, push } = this.props;
// redirect user in client
if (!isJobValid(job)) {
push('/jobs');
}
}
render() {
const {
isBackEndCert,
isFrontEndCert,
job,
username
} = this.props;
if (!isJobValid(job)) {
return <JobNotFound />;
}
const isSignedIn = !!username;
const showApply = shouldShowApply(
job,
{ isFrontEndCert, isBackEndCert }
);
const message = generateMessage(
job,
{ isFrontEndCert, isBackEndCert, isSignedIn }
);
return (
<ShowJob
message={ message }
preview={ false }
showApply={ showApply }
{ ...this.props }/>
);
}
}
export default compose(
connect(mapStateToProps, bindableActions),
contain(fetchOptions)
)(Show);

View File

View File

@ -0,0 +1,7 @@
import { fetchJobs, fetchJobsCompleted } from './types';
export default ({ services }) => ({ dispatch, getState }) => next => {
return function fetchJobsSaga(action) {
return next(action);
};
};

View File

View File

@ -0,0 +1,18 @@
const types = [
'fetchJobs',
'fetchJobsCompleted',
'findJob',
'saveJob',
'getJob',
'getJobs',
'openModal',
'closeModal',
'handleFormUpdate',
'saveForm',
'clear'
];
export default types
.reduce((types, type) => ({ ...types, [type]: `jobs.${type}` }), {});