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 errSaga from './err-saga';
import titleSaga from './title-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, saveForm,
clearForm, clearForm,
loadSavedForm loadSavedForm
} from '../common/app/routes/Jobs/redux/types'; } from '../../common/app/routes/Jobs/redux/types';
import { import {
saveCompleted,
loadSavedFormCompleted loadSavedFormCompleted
} from '../common/app/routes/Jobs/redux/actions'; } from '../../common/app/routes/Jobs/redux/actions';
const formKey = 'newJob'; const formKey = 'newJob';
let enabled = false; let enabled = false;
@ -17,7 +18,7 @@ let store = typeof window !== 'undefined' ?
try { try {
const testKey = '__testKey__'; const testKey = '__testKey__';
store.setItem(testKey, testKey); store.setItem(testKey, testKey);
enabled = store.getItem(testKey) !== testKey; enabled = store.getItem(testKey) === testKey;
store.removeItem(testKey); store.removeItem(testKey);
} catch (e) { } catch (e) {
enabled = !e; enabled = !e;
@ -35,11 +36,12 @@ export default () => ({ dispatch }) => next => {
const form = action.payload; const form = action.payload;
try { try {
store.setItem(formKey, JSON.stringify(form)); store.setItem(formKey, JSON.stringify(form));
return null; next(action);
} catch (e) { return dispatch(saveCompleted(form));
} catch (error) {
return dispatch({ return dispatch({
type: 'app.handleError', type: 'app.handleError',
error: new Error('could not parse form data') error
}); });
} }
} }
@ -54,10 +56,10 @@ export default () => ({ dispatch }) => next => {
try { try {
const form = JSON.parse(formString); const form = JSON.parse(formString);
return dispatch(loadSavedFormCompleted(form)); return dispatch(loadSavedFormCompleted(form));
} catch (err) { } catch (error) {
return dispatch({ return dispatch({
type: 'app.handleError', 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 React, { PropTypes } from 'react';
import { Button, Input, Col, Row, Well } from 'react-bootstrap'; import { Button, Input, Col, Row, Well } from 'react-bootstrap';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import PureComponent from 'react-pure-render/component'; import PureComponent from 'react-pure-render/component';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import {
applyPromo,
clearPromo,
updatePromo
} from '../redux/actions';
// real paypal buttons // real paypal buttons
// will take your money // will take your money
const paypalIds = { const paypalIds = {
@ -12,10 +20,14 @@ const paypalIds = {
}; };
const bindableActions = { const bindableActions = {
applyPromo,
clearPromo,
push,
updatePromo
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
state => state.jobsApp.currentJob, state => state.jobsApp.newJob,
state => state.jobsApp, state => state.jobsApp,
( (
{ id, isHighlighted } = {}, { id, isHighlighted } = {},
@ -46,6 +58,11 @@ const mapStateToProps = createSelector(
); );
export class JobTotal extends PureComponent { export class JobTotal extends PureComponent {
constructor(...args) {
super(...args);
this._subscriptions = new CompositeDisposable();
}
static displayName = 'JobTotal'; static displayName = 'JobTotal';
static propTypes = { static propTypes = {
@ -60,13 +77,15 @@ export class JobTotal extends PureComponent {
}; };
componentDidMount() { componentDidMount() {
const { jobActions } = this.props; if (!this.props.id) {
jobActions.clearPromo(); this.props.push('/jobs');
} }
goToJobBoard() { this.props.clearPromo();
const { appActions } = this.props; }
setTimeout(() => appActions.goTo('/jobs'), 0);
componentWillUnmount() {
this._subscriptions.dispose();
} }
renderDiscount(discountAmount) { renderDiscount(discountAmount) {
@ -114,7 +133,8 @@ export class JobTotal extends PureComponent {
promoCode, promoCode,
promoName, promoName,
isHighlighted, isHighlighted,
jobActions applyPromo,
updatePromo
} = this.props; } = this.props;
if (promoApplied) { if (promoApplied) {
@ -147,7 +167,7 @@ export class JobTotal extends PureComponent {
md={ 3 } md={ 3 }
mdOffset={ 3 }> mdOffset={ 3 }>
<Input <Input
onChange={ jobActions.setPromoCode } onChange={ updatePromo }
type='text' type='text'
value={ promoCode } /> value={ promoCode } />
</Col> </Col>
@ -156,11 +176,12 @@ export class JobTotal extends PureComponent {
<Button <Button
block={ true } block={ true }
onClick={ () => { onClick={ () => {
jobActions.applyCode({ const subscription = applyPromo({
id, id,
code: promoCode, code: promoCode,
type: isHighlighted ? 'isHighlighted' : null type: isHighlighted ? 'isHighlighted' : null
}); }).subscribe();
this._subscriptions.add(subscription);
}}> }}>
Apply Promo Code Apply Promo Code
</Button> </Button>
@ -176,7 +197,8 @@ export class JobTotal extends PureComponent {
isHighlighted, isHighlighted,
buttonId, buttonId,
price, price,
discountAmount discountAmount,
push
} = this.props; } = this.props;
return ( return (
@ -239,7 +261,7 @@ export class JobTotal extends PureComponent {
<form <form
action='https://www.paypal.com/cgi-bin/webscr' action='https://www.paypal.com/cgi-bin/webscr'
method='post' method='post'
onClick={ this.goToJobBoard } onClick={ () => setTimeout(push, 0, '/jobs') }
target='_blank'> target='_blank'>
<input <input
name='cmd' name='cmd'

View File

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

View File

@ -1,3 +1,4 @@
import { CompositeDisposable } from 'rx';
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';
@ -7,22 +8,30 @@ 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';
import { clearSavedForm, saveJobToDb } from '../redux/actions'; import { clearForm, saveJob } from '../redux/actions';
const mapStateToProps = state => ({ job: state.jobsApp.newJob }); const mapStateToProps = state => ({ job: state.jobsApp.newJob });
const bindableActions = { const bindableActions = {
goBack, goBack,
push, push,
clearSavedForm, clearForm,
saveJobToDb saveJob
}; };
export class JobPreview extends PureComponent { export class JobPreview extends PureComponent {
constructor(...args) {
super(...args);
this._subscriptions = new CompositeDisposable();
}
static displayName = 'Preview'; static displayName = 'Preview';
static propTypes = { static propTypes = {
job: PropTypes.object job: PropTypes.object,
saveJob: PropTypes.func,
clearForm: PropTypes.func,
push: PropTypes.func
}; };
componentDidMount() { 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() { render() {
const { job, goBack, clearSavedForm, saveJobToDb } = this.props; const { job, goBack } = this.props;
if (!job || !job.position || !job.description) { if (!job || !job.position || !job.description) {
return <JobNotFound />; return <JobNotFound />;
@ -54,13 +74,7 @@ export class JobPreview extends PureComponent {
<Button <Button
block={ true } block={ true }
className='signup-btn' className='signup-btn'
onClick={ () => { onClick={ () => this.handleJobSubmit() }>
clearSavedForm();
saveJobToDb({
goTo: '/jobs/new/check-out',
job
});
}}>
Looks great! Let's Check Out Looks great! Let's Check Out
</Button> </Button>

View File

@ -10,11 +10,25 @@ export const fetchJobsCompleted = createAction(
export const findJob = createAction(types.findJob); export const findJob = createAction(types.findJob);
// saves to database
export const saveJob = createAction(types.saveJob); export const saveJob = createAction(types.saveJob);
export const saveJobCompleted = createAction(types.saveJobCompleted); // saves to localStorage
export const saveForm = createAction(types.saveForm); 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( export const loadSavedFormCompleted = createAction(
types.loadSavedFormCompleted 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 { Observable } from 'rx';
import { testPromo } from './types'; import { applyPromo } from './types';
import { applyPromo } from './actions'; import { applyPromoCompleted } from './actions';
import { postJSON$ } from '../../../../utils/ajax-stream'; import { postJSON$ } from '../../../../utils/ajax-stream';
export default () => ({ dispatch }) => next => { export default () => ({ dispatch }) => next => {
return function applyPromoSaga(action) { return function applyPromoSaga(action) {
if (action.type !== testPromo) { if (action.type !== applyPromo) {
return next(action); return next(action);
} }
@ -28,7 +28,7 @@ export default () => ({ dispatch }) => next => {
throw new Error('No promo returned by server'); throw new Error('No promo returned by server');
} }
return applyPromo(promo); return applyPromoCompleted(promo);
}) })
.catch(error => Observable.just({ .catch(error => Observable.just({
type: 'app.handleError', type: 'app.handleError',

View File

@ -4,6 +4,8 @@ export types from './types';
import fetchJobsSaga from './fetch-jobs-saga'; import fetchJobsSaga from './fetch-jobs-saga';
import saveJobSaga from './save-job-saga'; import saveJobSaga from './save-job-saga';
import applyPromoSaga from './apply-promo-saga';
export formNormalizer from './jobs-form-normalizer'; 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 = { const initialState = {
// used by NewJob form
initialValues: {},
currentJob: '', currentJob: '',
newJob: {}, newJob: {},
jobs: { jobs: {
@ -28,22 +30,26 @@ export default handleActions(
state.currentJob state.currentJob
}; };
}, },
[types.saveJobCompleted]: (state, { payload: newJob }) => {
return {
...state,
newJob
};
},
[types.fetchJobsCompleted]: (state, { payload: { jobs, currentJob } }) => ({ [types.fetchJobsCompleted]: (state, { payload: { jobs, currentJob } }) => ({
...state, ...state,
currentJob, currentJob,
jobs jobs
}), }),
[types.updatePromoCode]: (state, { payload }) => ({ [types.updatePromo]: (state, { payload }) => ({
...state, ...state,
promoCode: replace(payload) 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 { const {
fullPrice: price, fullPrice: price,

View File

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

View File

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