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 { reducer as formReducer } from 'redux-form';
import { reducer as app } from './redux';
import { reducer as hikesApp } from './routes/Hikes/redux';
@ -7,6 +8,7 @@ export default function createReducer(sideReducers = {}) {
return combineReducers({
...sideReducers,
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 { Button, Row, Col } from 'react-bootstrap';
export default React.createClass({
displayName: 'NoJobFound',
export default class extends React.Component {
static displayName = 'NoJobFound';
shouldComponentUpdate() {
return false;
}
render() {
return (
@ -28,4 +32,4 @@ export default React.createClass({
</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 { 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 contain from '../../../utils/professor-x';
import ListJobs from './List.jsx';
export default contain(
{
store: 'appStore',
map({ jobsApp: { jobs, showModal }}) {
return { jobs, showModal };
},
fetchAction: 'jobActions.getJobs',
isPrimed({ jobs = [] }) {
return !!jobs.length;
},
actions: [
'appActions',
'jobActions'
]
},
React.createClass({
displayName: 'Jobs',
import {
findJob,
fetchJobs
} from '../redux/actions';
propTypes: {
children: PropTypes.element,
appActions: PropTypes.object,
jobActions: PropTypes.object,
jobs: PropTypes.array,
showModal: PropTypes.bool
},
const mapSateToProps = createSelector(
state => state.jobsApp.jobs.entities,
state => state.jobsApp.jobs.results,
state => state.jobsApp,
(jobsMap, jobsById) => {
return jobsById.map(id => jobsMap[id]);
}
);
handleJobClick(id) {
const { appActions, jobActions } = this.props;
if (!id) {
return null;
}
jobActions.findJob(id);
appActions.goTo(`/jobs/${id}`);
},
const bindableActions = {
findJob,
fetchJobs,
push
};
renderList(handleJobClick, jobs) {
return (
<ListJobs
handleClick={ handleJobClick }
jobs={ jobs }/>
);
},
const fetchOptions = {
fetchAction: 'fetchJobs',
isPrimed({ jobs }) {
return !!jobs.results.length;
}
};
renderChild(child, jobs) {
if (!child) {
return null;
}
return cloneElement(
child,
{ jobs }
);
},
export class Jobs extends PureComponent {
static displayName = 'Jobs';
render() {
const {
children,
jobs,
appActions
} = this.props;
static propTypes = {
push: PropTypes.func,
findJob: PropTypes.func,
fetchJobs: PropTypes.func,
children: PropTypes.element,
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>
<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={ ()=> {
appActions.goTo('/jobs/new');
}}>
Post a job: $1,000
</Button>
<div className='spacer' />
</Col>
</Row>
<div className='spacer' />
<Row>
<Col
md={ 2 }
xs={ 4 }>
<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>
md={ 2 }
xs={ 4 }>
<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.renderList(this.handleJobClick, jobs) }
this.renderList(this.createJobClickHandler(), jobs) }
</Row>
</Col>
</Row>
);
}
})
);
);
}
}
export default compose(
connect(mapSateToProps, bindableActions),
contain(fetchOptions)
)(Jobs);

View File

@ -1,14 +1,15 @@
import React, { PropTypes } from 'react';
import classnames from 'classnames';
import { ListGroup, ListGroupItem } from 'react-bootstrap';
import PureComponent from 'react-pure-render/component';
export default React.createClass({
displayName: 'ListJobs',
export default class ListJobs extends PureComponent {
static displayName = 'ListJobs';
propTypes: {
static propTypes = {
handleClick: PropTypes.func,
jobs: PropTypes.array
},
};
addLocation(locale) {
if (!locale) {
@ -19,50 +20,50 @@ export default React.createClass({
{ locale }
</span>
);
},
}
renderJobs(handleClick, jobs = []) {
return jobs
.filter(({ isPaid, isApproved, isFilled }) => {
return isPaid && isApproved && !isFilled;
})
.map(({
id,
company,
position,
isHighlighted,
locale
}) => {
.filter(({ isPaid, isApproved, isFilled }) => {
return isPaid && isApproved && !isFilled;
})
.map(({
id,
company,
position,
isHighlighted,
locale
}) => {
const className = classnames({
'jobs-list': true,
'col-xs-12': true,
'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>
);
const className = classnames({
'jobs-list': true,
'col-xs-12': true,
'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>
);
});
}
render() {
const {
@ -76,4 +77,4 @@ export default React.createClass({
</ListGroup>
);
}
});
}

View File

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

View File

@ -1,79 +1,84 @@
import React, { PropTypes } from 'react';
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 JobNotFound from './JobNotFound.jsx';
export default contain(
{
store: 'appStore',
actions: [
'appActions',
'jobActions'
],
map({ jobsApp: { form: job = {} } }) {
return { job };
import { clearSavedForm, saveJobToDb } from '../redux/actions';
const mapStateToProps = createSelector(
state => state.jobsApp.form,
(job = {}) => ({ job })
);
const bindableActions = {
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: {
appActions: PropTypes.object,
job: PropTypes.object,
jobActions: PropTypes.object
},
render() {
const { job, goBack, clearSavedForm, saveJobToDb } = this.props;
componentDidMount() {
const { appActions, job } = this.props;
// redirect user in client
if (!job || !job.position || !job.description) {
appActions.goTo('/jobs/new');
}
},
if (!job || !job.position || !job.description) {
return <JobNotFound />;
}
render() {
const { appActions, job, jobActions } = this.props;
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={ () => {
clearSavedForm();
saveJobToDb({
goTo: '/jobs/new/check-out',
job
});
}}>
if (!job || !job.position || !job.description) {
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
Looks great! Let's Check Out
</Button>
<Button
block={ true }
onClick={ () => appActions.goBack() } >
onClick={ goBack } >
Head back and make edits
</Button>
</div>
</Col>
</Row>
</div>
);
}
})
);
);
}
}
export default connect(mapStateToProps, bindableActions)(JobPreview);

View File

@ -1,6 +1,11 @@
import React, { PropTypes } from 'react';
import { History } from 'react-router';
import { contain } from 'thundercats-react';
import { connect, compose } from 'redux';
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 JobNotFound from './JobNotFound.jsx';
@ -51,86 +56,89 @@ function generateMessage(
"You've earned it, so feel free to apply.";
}
export default contain(
{
store: 'appStore',
fetchAction: 'jobActions.getJob',
map({
username,
isFrontEndCert,
isBackEndCert,
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 mapStateToProps = createSelector(
state => state.app,
state => state.jobsApp.currentJob,
({ username, isFrontEndCert, isBackEndCert }, job = {}) => ({
username,
isFrontEndCert,
isBackEndCert,
job
})
);
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}` }), {});