Update /jobs

This commit is contained in:
Berkeley Martinez
2016-01-04 14:26:07 -08:00
parent c028398b9b
commit 844f43d271
11 changed files with 258 additions and 241 deletions

View File

@ -5,7 +5,7 @@ import { Disposable, Observable } from 'rx';
import { post$, postJSON$ } from '../utils/ajax-stream.js'; import { post$, postJSON$ } from '../utils/ajax-stream.js';
import { AppActions, AppStore } from './flux'; import { AppActions, AppStore } from './flux';
import { HikesActions } from './routes/Hikes/flux'; import { HikesActions } from './routes/Hikes/flux';
import { JobActions, JobsStore} from './routes/Jobs/flux'; import { JobActions } from './routes/Jobs/flux';
const ajaxStamp = stamp({ const ajaxStamp = stamp({
methods: { methods: {
@ -22,8 +22,7 @@ export default Cat().init(({ instance: cat, args: [services] }) => {
return Observable.create(function(observer) { return Observable.create(function(observer) {
services.read(resource, params, config, (err, res) => { services.read(resource, params, config, (err, res) => {
if (err) { if (err) {
observer.onError(err); return observer.onError(err);
return observer.onCompleted();
} }
observer.onNext(res); observer.onNext(res);
@ -31,8 +30,24 @@ export default Cat().init(({ instance: cat, args: [services] }) => {
}); });
return Disposable.create(function() { return Disposable.create(function() {
observer.dispose();
});
});
},
createService$(resource, params, body, config) {
return Observable.create(function(observer) {
services.create(resource, params, body, config, (err, res) => {
if (err) {
return observer.onError(err);
}
observer.onNext(res);
observer.onCompleted(); observer.onCompleted();
}); });
return Disposable.create(function() {
observer.dispose();
});
}); });
} }
} }
@ -40,9 +55,11 @@ export default Cat().init(({ instance: cat, args: [services] }) => {
cat.register(HikesActions.compose(serviceStamp, ajaxStamp), null, services); cat.register(HikesActions.compose(serviceStamp, ajaxStamp), null, services);
cat.register(AppActions.compose(serviceStamp), null, services); cat.register(AppActions.compose(serviceStamp), null, services);
cat.register(
JobActions.compose(serviceStamp, ajaxStamp),
null,
cat,
services
);
cat.register(AppStore, null, cat); cat.register(AppStore, null, cat);
cat.register(JobActions, null, cat, services);
cat.register(JobsStore.compose(serviceStamp), null, cat);
}); });

View File

@ -11,6 +11,9 @@ const initValue = {
// lecture state // lecture state
currentHike: {}, currentHike: {},
showQuestion: false showQuestion: false
},
jobsApp: {
showModal: false
} }
}; };
@ -19,25 +22,15 @@ export default Store({
displayName: 'AppStore', displayName: 'AppStore',
value: initValue value: initValue
}, },
init({ instance: appStore, args: [cat] }) { init({ instance: store, args: [cat] }) {
const register = createRegistrar(store);
// app
const { const {
updateLocation, updateLocation,
getUser, getUser,
setTitle setTitle
} = cat.getActions('appActions'); } = cat.getActions('appActions');
const register = createRegistrar(appStore);
const {
toggleQuestions,
fetchHikes,
hideInfo,
grabQuestion,
releaseQuestion,
moveQuestion,
answer
} = cat.getActions('hikesActions');
// app
register( register(
fromMany( fromMany(
setter( setter(
@ -51,6 +44,16 @@ export default Store({
); );
// hikes // hikes
const {
toggleQuestions,
fetchHikes,
hideInfo,
grabQuestion,
releaseQuestion,
moveQuestion,
answer
} = cat.getActions('hikesActions');
register( register(
fromMany( fromMany(
toggleQuestions, toggleQuestions,
@ -63,6 +66,36 @@ export default Store({
) )
); );
return appStore;
// jobs
const {
findJob,
saveJobToDb,
getJob,
getJobs,
openModal,
closeModal,
handleForm,
getSavedForm,
setPromoCode,
applyCode,
clearPromo
} = cat.getActions('JobActions');
register(
fromMany(
findJob,
saveJobToDb,
getJob,
getJobs,
openModal,
closeModal,
handleForm,
getSavedForm,
setPromoCode,
applyCode,
clearPromo
)
);
} }
}); });

View File

@ -11,12 +11,12 @@ const paypalIds = {
export default contain( export default contain(
{ {
store: 'JobsStore', store: 'appStore',
actions: [ actions: [
'jobActions', 'jobActions',
'appActions' 'appActions'
], ],
map({ map({ jobApp: {
job: { id, isHighlighted } = {}, job: { id, isHighlighted } = {},
buttonId = isHighlighted ? buttonId = isHighlighted ?
paypalIds.highlighted : paypalIds.highlighted :
@ -26,7 +26,7 @@ export default contain(
promoCode = '', promoCode = '',
promoApplied = false, promoApplied = false,
promoName promoName
}) { }}) {
return { return {
id, id,
isHighlighted, isHighlighted,
@ -57,7 +57,7 @@ export default contain(
goToJobBoard() { goToJobBoard() {
const { appActions } = this.props; const { appActions } = this.props;
appActions.updateRoute('/jobs'); appActions.goTo('/jobs');
}, },
renderDiscount(discountAmount) { renderDiscount(discountAmount) {

View File

@ -6,7 +6,10 @@ import ListJobs from './List.jsx';
export default contain( export default contain(
{ {
store: 'jobsStore', store: 'appStore',
map({ jobsApp: { jobs, showModal }}) {
return { jobs, showModal };
},
fetchAction: 'jobActions.getJobs', fetchAction: 'jobActions.getJobs',
actions: [ actions: [
'appActions', 'appActions',
@ -18,25 +21,19 @@ export default contain(
propTypes: { propTypes: {
children: PropTypes.element, children: PropTypes.element,
numOfFollowers: PropTypes.number,
appActions: PropTypes.object, appActions: PropTypes.object,
jobActions: PropTypes.object, jobActions: PropTypes.object,
jobs: PropTypes.array, jobs: PropTypes.array,
showModal: PropTypes.bool showModal: PropTypes.bool
}, },
componentDidMount() {
const { jobActions } = this.props;
jobActions.getFollowers();
},
handleJobClick(id) { handleJobClick(id) {
const { appActions, jobActions } = this.props; const { appActions, jobActions } = this.props;
if (!id) { if (!id) {
return null; return null;
} }
jobActions.findJob(id); jobActions.findJob(id);
appActions.updateRoute(`/jobs/${id}`); appActions.goTo(`/jobs/${id}`);
}, },
renderList(handleJobClick, jobs) { renderList(handleJobClick, jobs) {
@ -84,7 +81,7 @@ export default contain(
<Button <Button
className='signup-btn btn-block btn-cta' className='signup-btn btn-block btn-cta'
onClick={ ()=> { onClick={ ()=> {
appActions.updateRoute('/jobs/new'); appActions.goTo('/jobs/new');
}}> }}>
Post a job: $1,000 Post a job: $1,000
</Button> </Button>

View File

@ -103,9 +103,9 @@ function makeRequired(validator) {
} }
export default contain({ export default contain({
store: 'appStore',
actions: 'jobActions', actions: 'jobActions',
store: 'jobsStore', map({ jobsApp: { form = {} } }) {
map({ form = {} }) {
const { const {
position, position,
locale, locale,

View File

@ -8,12 +8,12 @@ import JobNotFound from './JobNotFound.jsx';
export default contain( export default contain(
{ {
store: 'JobsStore', store: 'appStore',
actions: [ actions: [
'appActions', 'appActions',
'jobActions' 'jobActions'
], ],
map({ form: job = {} }) { map({ jobApp: { form: job = {} } }) {
return { job }; return { job };
} }
}, },
@ -32,7 +32,7 @@ export default contain(
const { appActions, job } = this.props; const { appActions, job } = this.props;
// redirect user in client // redirect user in client
if (!job || !job.position || !job.description) { if (!job || !job.position || !job.description) {
appActions.updateRoute('/jobs/new'); appActions.goTo('/jobs/new');
} }
}, },

View File

@ -53,13 +53,14 @@ function generateMessage(
export default contain( export default contain(
{ {
stores: ['appStore', 'jobsStore'], store: 'appStore',
fetchWaitFor: 'jobsStore',
fetchAction: 'jobActions.getJob', fetchAction: 'jobActions.getJob',
combineLatest( map({
{ username, isFrontEndCert, isFullStackCert }, username,
{ currentJob } isFrontEndCert,
) { isFullStackCert,
jobsApp: { currentJob }
}) {
return { return {
username, username,
job: currentJob, job: currentJob,
@ -67,11 +68,11 @@ export default contain(
isFullStackCert isFullStackCert
}; };
}, },
getPayload({ params: { id }, job = {} }) { getPayload({ params: { id } }) {
return { return id;
id, },
isPrimed: job.id === id isPrimed({ params: { id } = {}, job = {} }) {
}; return job.id === id;
}, },
// using es6 destructuring // using es6 destructuring
shouldContainerFetch({ job = {} }, { params: { id } } shouldContainerFetch({ job = {} }, { params: { id } }

View File

@ -1,51 +1,100 @@
import { Actions } from 'thundercats'; import { Actions } from 'thundercats';
import store from 'store'; import store from 'store';
import debugFactory from 'debug'; import { nameSpacedTransformer } from '../../../../utils';
import { jsonp$ } from '../../../../utils/jsonp$';
import { postJSON$ } from '../../../../utils/ajax-stream';
const debug = debugFactory('freecc:jobs:actions');
const assign = Object.assign; const assign = Object.assign;
const jobsTranformer = nameSpacedTransformer('jobsApp');
const noOper = { transform: () => {} };
export default Actions({ export default Actions({
setJobs: null, refs: { displayName: 'JobActions' },
shouldBindMethods: true,
// findJob assumes that the job is already in the list of jobs // findJob assumes that the job is already in the list of jobs
findJob(id) { findJob(id) {
return oldState => { return {
const { currentJob = {}, jobs = [] } = oldState; transform: jobsTranformer(oldState => {
// currentJob already set const { currentJob = {}, jobs = [] } = oldState;
// do nothing // currentJob already set
if (currentJob.id === id) { // do nothing
return null; if (currentJob.id === id) {
} return null;
const foundJob = jobs.reduce((newJob, job) => {
if (job.id === id) {
return job;
} }
return newJob; const foundJob = jobs.reduce((newJob, job) => {
}, null); if (job.id === id) {
return job;
}
return newJob;
}, null);
// if no job found this will be null which is a op noop // if no job found this will be null which is a op noop
return foundJob ? return foundJob ?
assign({}, oldState, { currentJob: foundJob }) : assign({}, oldState, { currentJob: foundJob }) :
null; null;
})
}; };
}, },
setError: null, saveJobToDb({ goTo, job }) {
getJob: null, return this.createService$('jobs', { job })
saveJobToDb: null, .map(job => ({
getJobs(params) { transform(state) {
return { params }; state.location = {
action: 'PUSH',
pathname: goTo
};
return {
...state,
jobs: {
...state.jobs,
currentJob: job
}
};
}
}))
.catch(err => ({
transform(state) {
return { ...state, err };
}
}));
},
getJob(id) {
return this.readService$('jobs', { id })
.map(job => ({
transform: jobsTranformer(state => {
return { ...state, currentJob: job };
})
}))
.catch(err => ({
transform(state) {
return { ...state, err };
}
}));
},
getJobs() {
return this.readService$('jobs')
.map(jobs => ({
transform: jobsTranformer(state => {
return { ...state, jobs };
})
}))
.catch(err => ({
transform(state) {
return { state, err };
}
}));
}, },
openModal() { openModal() {
return { showModal: true }; return {
transform: jobsTranformer(state => ({ ...state, showModal: true }))
};
}, },
closeModal() { closeModal() {
return { showModal: false }; return {
transform: jobsTranformer(state => ({ ...state, showModal: false }))
};
}, },
handleForm(value) { handleForm(value) {
return { return {
transform(oldState) { transform: jobsTranformer(oldState => {
const { form } = oldState; const { form } = oldState;
const newState = assign({}, oldState); const newState = assign({}, oldState);
newState.form = assign( newState.form = assign(
@ -54,142 +103,92 @@ export default Actions({
value value
); );
return newState; return newState;
} })
}; };
}, },
saveForm: null, saveForm: null,
getSavedForm: null,
clearSavedForm: null, clearSavedForm: null,
setForm(form) { getSavedForm() {
return { form }; const form = store.get('newJob');
}, if (form && !Array.isArray(form) && typeof form === 'object') {
getFollowers: null, return {
setFollowersCount(numOfFollowers) { transform: jobsTranformer(state => {
return { numOfFollowers }; return { ...state, form };
})
};
}
return noOper;
}, },
setPromoCode({ target: { value = '' }} = {}) { setPromoCode({ target: { value = '' }} = {}) {
return { promoCode: value.replace(/[^\d\w\s]/, '') }; return {
transform: jobsTranformer(state => ({
...state,
promoCode: value.replace(/[^\d\w\s]/, '')
}))
};
},
applyCode({ id, code = '', type = null}) {
const body = {
id,
code: code.replace(/[^\d\w\s]/, '')
};
if (type) {
body.type = type;
}
return this.postJSON$('/api/promos/getButton', body)
.pluck('response')
.map(({ promo }) => {
if (!promo || !promo.buttonId) {
return noOper;
}
const {
fullPrice: price,
buttonId,
discountAmount,
code: promoCode,
name: promoName
} = promo;
return {
transform: jobsTranformer(state => ({
...state,
price,
buttonId,
discountAmount,
promoCode,
promoApplied: true,
promoName
}))
};
})
.catch(err => ({
transform(state) {
return { ...state, err };
}
}));
}, },
applyCode: null,
clearPromo(foo, undef) { clearPromo(foo, undef) {
return { return {
price: undef, transform: jobsTranformer(state => ({
buttonId: undef, ...state,
discountAmount: undef, price: undef,
promoCode: undef, buttonId: undef,
promoApplied: false, discountAmount: undef,
promoName: undef promoCode: undef,
promoApplied: false,
promoName: undef
}))
}; };
}, },
applyPromo({ init({ instance: jobActions }) {
fullPrice: price,
buttonId,
discountAmount,
code: promoCode,
name: promoName
} = {}) {
return {
price,
buttonId,
discountAmount,
promoCode,
promoApplied: true,
promoName
};
}
})
.refs({ displayName: 'JobActions' })
.init(({ instance: jobActions, args: [cat, services] }) => {
jobActions.getJobs.subscribe(() => {
services.read('jobs', null, null, (err, jobs) => {
if (err) {
debug('job services experienced an issue', err);
return jobActions.setError({ err });
}
jobActions.setJobs({ jobs });
});
});
jobActions.getJob.subscribe(({ id, isPrimed }) => {
// job is already set, do nothing.
if (isPrimed) {
debug('job is primed');
return;
}
services.read('jobs', { id }, null, (err, job) => {
if (err) {
debug('job services experienced an issue', err);
return jobActions.setError({ err });
}
if (job) {
jobActions.setJobs({ currentJob: job });
}
jobActions.setJobs({});
});
});
jobActions.saveForm.subscribe((form) => { jobActions.saveForm.subscribe((form) => {
store.set('newJob', form); store.set('newJob', form);
}); });
jobActions.getSavedForm.subscribe(() => {
const job = store.get('newJob');
if (job && !Array.isArray(job) && typeof job === 'object') {
jobActions.setForm(job);
}
});
jobActions.clearSavedForm.subscribe(() => { jobActions.clearSavedForm.subscribe(() => {
store.remove('newJob'); store.remove('newJob');
}); });
jobActions.saveJobToDb.subscribe(({ goTo, job }) => {
const appActions = cat.getActions('appActions');
services.create('jobs', { job }, null, (err, job) => {
if (err) {
debug('job services experienced an issue', err);
return jobActions.setError(err);
}
jobActions.setJobs({ job });
appActions.updateRoute(goTo);
});
});
jobActions.getFollowers.subscribe(() => {
const url = 'https://cdn.syndication.twimg.com/widgets/followbutton/' +
'info.json?lang=en&screen_names=CamperJobs' +
'&callback=JSONPCallback';
jsonp$(url)
.map(({ response }) => {
return response[0]['followers_count'];
})
.subscribe(
count => jobActions.setFollowersCount(count),
err => jobActions.setError(err)
);
});
jobActions.applyCode.subscribe(({ id, code = '', type = null}) => {
const body = {
id,
code: code.replace(/[^\d\w\s]/, '')
};
if (type) {
body.type = type;
}
postJSON$('/api/promos/getButton', body)
.pluck('response')
.subscribe(
({ promo }) => {
if (promo && promo.buttonId) {
jobActions.applyPromo(promo);
}
jobActions.setError(new Error('no promo found'));
},
jobActions.setError
);
});
return jobActions; return jobActions;
}); }
});

View File

@ -1,42 +0,0 @@
import { Store } from 'thundercats';
const {
createRegistrar,
setter,
transformer
} = Store;
export default Store({
refs: {
displayName: 'JobsStore',
value: { showModal: false }
},
init({ instance: jobsStore, args: [cat] }) {
const {
setJobs,
findJob,
setError,
openModal,
closeModal,
handleForm,
setForm,
setFollowersCount,
setPromoCode,
applyPromo,
clearPromo
} = cat.getActions('JobActions');
const register = createRegistrar(jobsStore);
register(setter(setJobs));
register(setter(setError));
register(setter(openModal));
register(setter(closeModal));
register(setter(setForm));
register(setter(setPromoCode));
register(setter(applyPromo));
register(setter(clearPromo));
register(setter(setFollowersCount));
register(transformer(findJob));
register(handleForm);
}
});

View File

@ -1,2 +1 @@
export { default as JobActions } from './Actions'; export { default as JobActions } from './Actions';
export { default as JobsStore } from './Store';

13
common/utils/index.js Normal file
View File

@ -0,0 +1,13 @@
export function nameSpacedTransformer(ns, transformer) {
if (!transformer) {
return nameSpacedTransformer.bind(null, ns);
}
return (state) => {
const newState = transformer(state[ns]);
// nothing has changed
if (newState === state[ns]) {
return state;
}
return { ...state, [ns]: newState };
};
}