Fix job creation

This commit is contained in:
Berkeley Martinez
2016-03-02 16:33:44 -08:00
parent d6f21b01e6
commit a2a988764c
11 changed files with 149 additions and 84 deletions

View File

@ -1,4 +1,5 @@
import errSaga from './err-saga';
import titleSaga from './title-saga';
import localStorageSaga from './local-storage-saga';
export default [errSaga, titleSaga];
export default [errSaga, titleSaga, localStorageSaga];

View File

@ -2,11 +2,12 @@ import {
saveForm,
clearForm,
loadSavedForm
} from '../common/app/routes/Jobs/redux/types';
} from '../../common/app/routes/Jobs/redux/types';
import {
saveCompleted,
loadSavedFormCompleted
} from '../common/app/routes/Jobs/redux/actions';
} from '../../common/app/routes/Jobs/redux/actions';
const formKey = 'newJob';
let enabled = false;
@ -17,7 +18,7 @@ let store = typeof window !== 'undefined' ?
try {
const testKey = '__testKey__';
store.setItem(testKey, testKey);
enabled = store.getItem(testKey) !== testKey;
enabled = store.getItem(testKey) === testKey;
store.removeItem(testKey);
} catch (e) {
enabled = !e;
@ -35,11 +36,12 @@ export default () => ({ dispatch }) => next => {
const form = action.payload;
try {
store.setItem(formKey, JSON.stringify(form));
return null;
} catch (e) {
next(action);
return dispatch(saveCompleted(form));
} catch (error) {
return dispatch({
type: 'app.handleError',
error: new Error('could not parse form data')
error
});
}
}
@ -54,10 +56,10 @@ export default () => ({ dispatch }) => next => {
try {
const form = JSON.parse(formString);
return dispatch(loadSavedFormCompleted(form));
} catch (err) {
} catch (error) {
return dispatch({
type: 'app.handleError',
error: new Error('could not parse form data')
error
});
}
}

View File

@ -1,9 +1,17 @@
import { CompositeDisposable } from 'rx';
import React, { PropTypes } from 'react';
import { Button, Input, Col, Row, Well } from 'react-bootstrap';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import PureComponent from 'react-pure-render/component';
import { createSelector } from 'reselect';
import {
applyPromo,
clearPromo,
updatePromo
} from '../redux/actions';
// real paypal buttons
// will take your money
const paypalIds = {
@ -12,10 +20,14 @@ const paypalIds = {
};
const bindableActions = {
applyPromo,
clearPromo,
push,
updatePromo
};
const mapStateToProps = createSelector(
state => state.jobsApp.currentJob,
state => state.jobsApp.newJob,
state => state.jobsApp,
(
{ id, isHighlighted } = {},
@ -46,6 +58,11 @@ const mapStateToProps = createSelector(
);
export class JobTotal extends PureComponent {
constructor(...args) {
super(...args);
this._subscriptions = new CompositeDisposable();
}
static displayName = 'JobTotal';
static propTypes = {
@ -60,13 +77,15 @@ export class JobTotal extends PureComponent {
};
componentDidMount() {
const { jobActions } = this.props;
jobActions.clearPromo();
if (!this.props.id) {
this.props.push('/jobs');
}
this.props.clearPromo();
}
goToJobBoard() {
const { appActions } = this.props;
setTimeout(() => appActions.goTo('/jobs'), 0);
componentWillUnmount() {
this._subscriptions.dispose();
}
renderDiscount(discountAmount) {
@ -114,7 +133,8 @@ export class JobTotal extends PureComponent {
promoCode,
promoName,
isHighlighted,
jobActions
applyPromo,
updatePromo
} = this.props;
if (promoApplied) {
@ -147,7 +167,7 @@ export class JobTotal extends PureComponent {
md={ 3 }
mdOffset={ 3 }>
<Input
onChange={ jobActions.setPromoCode }
onChange={ updatePromo }
type='text'
value={ promoCode } />
</Col>
@ -156,11 +176,12 @@ export class JobTotal extends PureComponent {
<Button
block={ true }
onClick={ () => {
jobActions.applyCode({
const subscription = applyPromo({
id,
code: promoCode,
type: isHighlighted ? 'isHighlighted' : null
});
}).subscribe();
this._subscriptions.add(subscription);
}}>
Apply Promo Code
</Button>
@ -176,7 +197,8 @@ export class JobTotal extends PureComponent {
isHighlighted,
buttonId,
price,
discountAmount
discountAmount,
push
} = this.props;
return (
@ -239,7 +261,7 @@ export class JobTotal extends PureComponent {
<form
action='https://www.paypal.com/cgi-bin/webscr'
method='post'
onClick={ this.goToJobBoard }
onClick={ () => setTimeout(push, 0, '/jobs') }
target='_blank'>
<input
name='cmd'

View File

@ -1,5 +1,6 @@
import { CompositeDisposable, helpers } from 'rx';
import { helpers } from 'rx';
import React, { PropTypes } from 'react';
import { push } from 'react-router-redux';
import { reduxForm } from 'redux-form';
// import debug from 'debug';
import dedent from 'dedent';
@ -17,7 +18,7 @@ import {
Row
} from 'react-bootstrap';
import { saveJob } from '../redux/actions';
import { saveForm, loadSavedForm } from '../redux/actions';
// const log = debug('fcc:jobs:newForm');
@ -106,30 +107,23 @@ function getBsStyle(field) {
}
export class NewJob extends React.Component {
constructor(...args) {
super(...args);
this._subscriptions = new CompositeDisposable();
}
static displayName = 'NewJob';
static propTypes = {
fields: PropTypes.object,
handleSubmit: PropTypes.func,
saveJob: PropTypes.func
loadSavedForm: PropTypes.func,
push: PropTypes.func,
saveForm: PropTypes.func
};
componentDidMount() {
// this.prop.getSavedForm();
}
componentWillUnmount() {
this._subscriptions.dispose();
this.props.loadSavedForm();
}
handleSubmit(job) {
const subscription = this.props.saveJob(job).subscribe();
this._subscriptions.add(subscription);
this.props.saveForm(job);
this.props.push('/jobs/new/preview');
}
handleCertClick(name) {
@ -388,8 +382,10 @@ export default reduxForm(
fields,
validate: validateForm
},
null,
state => ({ initialValues: state.jobsApp.initialValues }),
{
saveJob
loadSavedForm,
push,
saveForm
}
)(NewJob);

View File

@ -1,3 +1,4 @@
import { CompositeDisposable } from 'rx';
import React, { PropTypes } from 'react';
import { Button, Row, Col } from 'react-bootstrap';
import { connect } from 'react-redux';
@ -7,22 +8,30 @@ import { goBack, push } from 'react-router-redux';
import ShowJob from './ShowJob.jsx';
import JobNotFound from './JobNotFound.jsx';
import { clearSavedForm, saveJobToDb } from '../redux/actions';
import { clearForm, saveJob } from '../redux/actions';
const mapStateToProps = state => ({ job: state.jobsApp.newJob });
const bindableActions = {
goBack,
push,
clearSavedForm,
saveJobToDb
clearForm,
saveJob
};
export class JobPreview extends PureComponent {
constructor(...args) {
super(...args);
this._subscriptions = new CompositeDisposable();
}
static displayName = 'Preview';
static propTypes = {
job: PropTypes.object
job: PropTypes.object,
saveJob: PropTypes.func,
clearForm: PropTypes.func,
push: PropTypes.func
};
componentDidMount() {
@ -33,8 +42,19 @@ export class JobPreview extends PureComponent {
}
}
componentWillUnmount() {
this._subscriptions.dispose();
}
handleJobSubmit() {
const { clearForm, saveJob, job } = this.props;
clearForm();
const subscription = saveJob(job).subscribe();
this._subscriptions.add(subscription);
}
render() {
const { job, goBack, clearSavedForm, saveJobToDb } = this.props;
const { job, goBack } = this.props;
if (!job || !job.position || !job.description) {
return <JobNotFound />;
@ -54,25 +74,19 @@ export class JobPreview extends PureComponent {
<Button
block={ true }
className='signup-btn'
onClick={ () => {
clearSavedForm();
saveJobToDb({
goTo: '/jobs/new/check-out',
job
});
}}>
onClick={ () => this.handleJobSubmit() }>
Looks great! Let's Check Out
</Button>
<Button
block={ true }
onClick={ goBack } >
Head back and make edits
</Button>
</div>
</Col>
</Row>
</div>
</Button>
<Button
block={ true }
onClick={ goBack } >
Head back and make edits
</Button>
</div>
</Col>
</Row>
</div>
);
}
}

View File

@ -10,11 +10,25 @@ export const fetchJobsCompleted = createAction(
export const findJob = createAction(types.findJob);
// saves to database
export const saveJob = createAction(types.saveJob);
export const saveJobCompleted = createAction(types.saveJobCompleted);
// saves to localStorage
export const saveForm = createAction(types.saveForm);
export const clearForm = createAction(types.clearSavedForm);
export const saveCompleted = createAction(types.saveCompleted);
export const clearForm = createAction(types.clearForm);
export const loadSavedForm = createAction(types.loadSavedForm);
export const loadSavedFormCompleted = createAction(
types.loadSavedFormCompleted
);
export const clearPromo = createAction(types.clearPromo);
export const updatePromo = createAction(
types.updatePromo,
({ target: { value = '' } = {} } = {}) => value
);
export const applyPromo = createAction(types.applyPromo);
export const applyPromoCompleted = createAction(types.applyPromoCompleted);

View File

@ -1,12 +1,12 @@
import { Observable } from 'rx';
import { testPromo } from './types';
import { applyPromo } from './actions';
import { applyPromo } from './types';
import { applyPromoCompleted } from './actions';
import { postJSON$ } from '../../../../utils/ajax-stream';
export default () => ({ dispatch }) => next => {
return function applyPromoSaga(action) {
if (action.type !== testPromo) {
if (action.type !== applyPromo) {
return next(action);
}
@ -28,7 +28,7 @@ export default () => ({ dispatch }) => next => {
throw new Error('No promo returned by server');
}
return applyPromo(promo);
return applyPromoCompleted(promo);
})
.catch(error => Observable.just({
type: 'app.handleError',

View File

@ -4,6 +4,8 @@ export types from './types';
import fetchJobsSaga from './fetch-jobs-saga';
import saveJobSaga from './save-job-saga';
import applyPromoSaga from './apply-promo-saga';
export formNormalizer from './jobs-form-normalizer';
export const sagas = [ fetchJobsSaga, saveJobSaga ];
export const sagas = [ fetchJobsSaga, saveJobSaga, applyPromoSaga ];

View File

@ -9,6 +9,8 @@ function replace(str) {
}
const initialState = {
// used by NewJob form
initialValues: {},
currentJob: '',
newJob: {},
jobs: {
@ -28,22 +30,26 @@ export default handleActions(
state.currentJob
};
},
[types.saveJobCompleted]: (state, { payload: newJob }) => {
return {
...state,
newJob
};
},
[types.fetchJobsCompleted]: (state, { payload: { jobs, currentJob } }) => ({
...state,
currentJob,
jobs
}),
[types.updatePromoCode]: (state, { payload }) => ({
[types.updatePromo]: (state, { payload }) => ({
...state,
promoCode: replace(payload)
}),
[types.applyPromo]: (state, { payload: promo }) => {
[types.saveCompleted]: (state, { payload: newJob }) => {
return {
...state,
newJob
};
},
[types.loadSavedFormCompleted]: (state, { payload: initialValues }) => ({
...state,
initialValues
}),
[types.applyPromoCompleted]: (state, { payload: promo }) => {
const {
fullPrice: price,

View File

@ -1,15 +1,16 @@
import { Observable } from 'rx';
import { push } from 'react-router-redux';
import { Observable } from 'rx';
import { saveJobCompleted } from './actions';
import { saveCompleted } from './actions';
import { saveJob } from './types';
import { handleError } from '../../../redux/types';
export default ({ services }) => ({ dispatch }) => next => {
return function saveJobSaga(action) {
const result = next(action);
if (action.type !== saveJob) {
return next(action);
return result;
}
const { payload: job } = action;
@ -19,8 +20,8 @@ export default ({ services }) => ({ dispatch }) => next => {
})
.retry(3)
.flatMap(job => Observable.of(
saveJobCompleted(job),
push('/jobs/new/preview')
saveCompleted(job),
push('/jobs/new/check-out')
))
.catch(error => Observable.just({
type: handleError,

View File

@ -4,12 +4,19 @@ const types = [
'findJob',
'saveJob',
'saveJobCompleted',
'saveForm',
'saveCompleted',
'clearForm',
'loadSavedForm',
'loadSavedFormCompleted'
'loadSavedFormCompleted',
'clearPromo',
'updatePromo',
'applyPromo',
'applyPromoCompleted'
];
export default types.reduce((types, type) => {