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 { AppActions, AppStore } from './flux';
import { HikesActions } from './routes/Hikes/flux';
import { JobActions, JobsStore} from './routes/Jobs/flux';
import { JobActions } from './routes/Jobs/flux';
const ajaxStamp = stamp({
methods: {
@ -22,8 +22,7 @@ export default Cat().init(({ instance: cat, args: [services] }) => {
return Observable.create(function(observer) {
services.read(resource, params, config, (err, res) => {
if (err) {
observer.onError(err);
return observer.onCompleted();
return observer.onError(err);
}
observer.onNext(res);
@ -31,8 +30,24 @@ export default Cat().init(({ instance: cat, args: [services] }) => {
});
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();
});
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(AppActions.compose(serviceStamp), null, services);
cat.register(
JobActions.compose(serviceStamp, ajaxStamp),
null,
cat,
services
);
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
currentHike: {},
showQuestion: false
},
jobsApp: {
showModal: false
}
};
@ -19,25 +22,15 @@ export default Store({
displayName: 'AppStore',
value: initValue
},
init({ instance: appStore, args: [cat] }) {
init({ instance: store, args: [cat] }) {
const register = createRegistrar(store);
// app
const {
updateLocation,
getUser,
setTitle
} = cat.getActions('appActions');
const register = createRegistrar(appStore);
const {
toggleQuestions,
fetchHikes,
hideInfo,
grabQuestion,
releaseQuestion,
moveQuestion,
answer
} = cat.getActions('hikesActions');
// app
register(
fromMany(
setter(
@ -51,6 +44,16 @@ export default Store({
);
// hikes
const {
toggleQuestions,
fetchHikes,
hideInfo,
grabQuestion,
releaseQuestion,
moveQuestion,
answer
} = cat.getActions('hikesActions');
register(
fromMany(
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(
{
store: 'JobsStore',
store: 'appStore',
actions: [
'jobActions',
'appActions'
],
map({
map({ jobApp: {
job: { id, isHighlighted } = {},
buttonId = isHighlighted ?
paypalIds.highlighted :
@ -26,7 +26,7 @@ export default contain(
promoCode = '',
promoApplied = false,
promoName
}) {
}}) {
return {
id,
isHighlighted,
@ -57,7 +57,7 @@ export default contain(
goToJobBoard() {
const { appActions } = this.props;
appActions.updateRoute('/jobs');
appActions.goTo('/jobs');
},
renderDiscount(discountAmount) {

View File

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

View File

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

View File

@ -8,12 +8,12 @@ import JobNotFound from './JobNotFound.jsx';
export default contain(
{
store: 'JobsStore',
store: 'appStore',
actions: [
'appActions',
'jobActions'
],
map({ form: job = {} }) {
map({ jobApp: { form: job = {} } }) {
return { job };
}
},
@ -32,7 +32,7 @@ export default contain(
const { appActions, job } = this.props;
// redirect user in client
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(
{
stores: ['appStore', 'jobsStore'],
fetchWaitFor: 'jobsStore',
store: 'appStore',
fetchAction: 'jobActions.getJob',
combineLatest(
{ username, isFrontEndCert, isFullStackCert },
{ currentJob }
) {
map({
username,
isFrontEndCert,
isFullStackCert,
jobsApp: { currentJob }
}) {
return {
username,
job: currentJob,
@ -67,11 +68,11 @@ export default contain(
isFullStackCert
};
},
getPayload({ params: { id }, job = {} }) {
return {
id,
isPrimed: job.id === id
};
getPayload({ params: { id } }) {
return id;
},
isPrimed({ params: { id } = {}, job = {} }) {
return job.id === id;
},
// using es6 destructuring
shouldContainerFetch({ job = {} }, { params: { id } }

View File

@ -1,17 +1,18 @@
import { Actions } from 'thundercats';
import store from 'store';
import debugFactory from 'debug';
import { jsonp$ } from '../../../../utils/jsonp$';
import { postJSON$ } from '../../../../utils/ajax-stream';
import { nameSpacedTransformer } from '../../../../utils';
const debug = debugFactory('freecc:jobs:actions');
const assign = Object.assign;
const jobsTranformer = nameSpacedTransformer('jobsApp');
const noOper = { transform: () => {} };
export default Actions({
setJobs: null,
refs: { displayName: 'JobActions' },
shouldBindMethods: true,
// findJob assumes that the job is already in the list of jobs
findJob(id) {
return oldState => {
return {
transform: jobsTranformer(oldState => {
const { currentJob = {}, jobs = [] } = oldState;
// currentJob already set
// do nothing
@ -29,23 +30,71 @@ export default Actions({
return foundJob ?
assign({}, oldState, { currentJob: foundJob }) :
null;
})
};
},
setError: null,
getJob: null,
saveJobToDb: null,
getJobs(params) {
return { params };
saveJobToDb({ goTo, job }) {
return this.createService$('jobs', { job })
.map(job => ({
transform(state) {
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() {
return { showModal: true };
return {
transform: jobsTranformer(state => ({ ...state, showModal: true }))
};
},
closeModal() {
return { showModal: false };
return {
transform: jobsTranformer(state => ({ ...state, showModal: false }))
};
},
handleForm(value) {
return {
transform(oldState) {
transform: jobsTranformer(oldState => {
const { form } = oldState;
const newState = assign({}, oldState);
newState.form = assign(
@ -54,123 +103,31 @@ export default Actions({
value
);
return newState;
}
})
};
},
saveForm: null,
getSavedForm: null,
clearSavedForm: null,
setForm(form) {
return { form };
},
getFollowers: null,
setFollowersCount(numOfFollowers) {
return { numOfFollowers };
getSavedForm() {
const form = store.get('newJob');
if (form && !Array.isArray(form) && typeof form === 'object') {
return {
transform: jobsTranformer(state => {
return { ...state, form };
})
};
}
return noOper;
},
setPromoCode({ target: { value = '' }} = {}) {
return { promoCode: value.replace(/[^\d\w\s]/, '') };
},
applyCode: null,
clearPromo(foo, undef) {
return {
price: undef,
buttonId: undef,
discountAmount: undef,
promoCode: undef,
promoApplied: false,
promoName: undef
transform: jobsTranformer(state => ({
...state,
promoCode: value.replace(/[^\d\w\s]/, '')
}))
};
},
applyPromo({
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) => {
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(() => {
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}) => {
applyCode({ id, code = '', type = null}) {
const body = {
id,
code: code.replace(/[^\d\w\s]/, '')
@ -178,18 +135,60 @@ export default Actions({
if (type) {
body.type = type;
}
postJSON$('/api/promos/getButton', body)
return this.postJSON$('/api/promos/getButton', body)
.pluck('response')
.subscribe(
({ promo }) => {
if (promo && promo.buttonId) {
jobActions.applyPromo(promo);
.map(({ promo }) => {
if (!promo || !promo.buttonId) {
return noOper;
}
jobActions.setError(new Error('no promo found'));
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 };
}
}));
},
jobActions.setError
);
clearPromo(foo, undef) {
return {
transform: jobsTranformer(state => ({
...state,
price: undef,
buttonId: undef,
discountAmount: undef,
promoCode: undef,
promoApplied: false,
promoName: undef
}))
};
},
init({ instance: jobActions }) {
jobActions.saveForm.subscribe((form) => {
store.set('newJob', form);
});
jobActions.clearSavedForm.subscribe(() => {
store.remove('newJob');
});
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 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 };
};
}