Merge pull request #3525 from FreeCodeCamp/feature/jobs
intermediate pull
This commit is contained in:
@ -4,7 +4,7 @@ import React from 'react';
|
||||
import Fetchr from 'fetchr';
|
||||
import debugFactory from 'debug';
|
||||
import { Router } from 'react-router';
|
||||
import { history } from 'react-router/lib/BrowserHistory';
|
||||
import { createLocation, createHistory } from 'history';
|
||||
import { hydrate } from 'thundercats';
|
||||
import { Render } from 'thundercats-react';
|
||||
|
||||
@ -18,21 +18,29 @@ const services = new Fetchr({
|
||||
});
|
||||
|
||||
Rx.config.longStackSupport = !!debug.enabled;
|
||||
|
||||
const history = createHistory();
|
||||
const appLocation = createLocation(
|
||||
location.pathname + location.search
|
||||
);
|
||||
// returns an observable
|
||||
app$(history)
|
||||
app$({ history, location: appLocation })
|
||||
.flatMap(
|
||||
({ AppCat }) => {
|
||||
// instantiate the cat with service
|
||||
const appCat = AppCat(null, services);
|
||||
// hydrate the stores
|
||||
return hydrate(appCat, catState)
|
||||
.map(() => appCat);
|
||||
},
|
||||
({ initialState }, appCat) => ({ initialState, appCat })
|
||||
// not using nextLocation at the moment but will be used for
|
||||
// redirects in the future
|
||||
({ nextLocation, props }, appCat) => ({ nextLocation, props, appCat })
|
||||
)
|
||||
.flatMap(({ initialState, appCat }) => {
|
||||
.flatMap(({ props, appCat }) => {
|
||||
props.history = history;
|
||||
return Render(
|
||||
appCat,
|
||||
React.createElement(Router, initialState),
|
||||
React.createElement(Router, props),
|
||||
DOMContianer
|
||||
);
|
||||
})
|
||||
|
@ -1,17 +1,17 @@
|
||||
import Rx from 'rx';
|
||||
import { Router } from 'react-router';
|
||||
import { match } from 'react-router';
|
||||
import App from './App.jsx';
|
||||
import AppCat from './Cat';
|
||||
|
||||
import childRoutes from './routes';
|
||||
|
||||
const router$ = Rx.Observable.fromNodeCallback(Router.run, Router);
|
||||
const route$ = Rx.Observable.fromNodeCallback(match);
|
||||
|
||||
const routes = Object.assign({ components: App }, childRoutes);
|
||||
|
||||
export default function app$(location) {
|
||||
return router$(routes, location)
|
||||
.map(([initialState, transistion]) => {
|
||||
return { initialState, transistion, AppCat };
|
||||
export default function app$({ location, history }) {
|
||||
return route$({ routes, location, history })
|
||||
.map(([nextLocation, props]) => {
|
||||
return { nextLocation, props, AppCat };
|
||||
});
|
||||
}
|
||||
|
43
common/app/routes/Jobs/components/CreateJobModal.jsx
Normal file
43
common/app/routes/Jobs/components/CreateJobModal.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { History } from 'react-router';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'CreateJobsModal',
|
||||
|
||||
propTypes: {
|
||||
onHide: PropTypes.func,
|
||||
showModal: PropTypes.bool
|
||||
},
|
||||
|
||||
mixins: [History],
|
||||
|
||||
goToNewJob(onHide) {
|
||||
onHide();
|
||||
this.history.pushState(null, '/jobs/new');
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
showModal,
|
||||
onHide
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onHide={ onHide }
|
||||
show={ showModal }>
|
||||
<Modal.Body>
|
||||
<h4>Welcome to Free Code Camp's board</h4>
|
||||
<p>We post jobs specifically target to our junior developers.</p>
|
||||
<Button
|
||||
block={ true }
|
||||
className='signup-btn'
|
||||
onClick={ () => this.goToNewJob(onHide) }>
|
||||
Post a Job
|
||||
</Button>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,7 +1,9 @@
|
||||
import React, { cloneElement, PropTypes } from 'react';
|
||||
import { contain } from 'thundercats-react';
|
||||
import { Navigation } from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
import { Button, Jumbotron, Row } from 'react-bootstrap';
|
||||
|
||||
import CreateJobModal from './CreateJobModal.jsx';
|
||||
import ListJobs from './List.jsx';
|
||||
|
||||
export default contain(
|
||||
@ -13,12 +15,14 @@ export default contain(
|
||||
React.createClass({
|
||||
displayName: 'Jobs',
|
||||
|
||||
mixins: [History],
|
||||
|
||||
propTypes: {
|
||||
children: PropTypes.element,
|
||||
jobActions: PropTypes.object,
|
||||
jobs: PropTypes.array
|
||||
jobs: PropTypes.array,
|
||||
showModal: PropTypes.bool
|
||||
},
|
||||
mixins: [Navigation],
|
||||
|
||||
handleJobClick(id) {
|
||||
const { jobActions } = this.props;
|
||||
@ -26,7 +30,7 @@ export default contain(
|
||||
return null;
|
||||
}
|
||||
jobActions.findJob(id);
|
||||
this.transitionTo(`/jobs/${id}`);
|
||||
this.history.pushState(null, `/jobs/${id}`);
|
||||
},
|
||||
|
||||
renderList(handleJobClick, jobs) {
|
||||
@ -48,7 +52,12 @@ export default contain(
|
||||
},
|
||||
|
||||
render() {
|
||||
const { children, jobs } = this.props;
|
||||
const {
|
||||
children,
|
||||
jobs,
|
||||
showModal,
|
||||
jobActions
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -62,7 +71,8 @@ export default contain(
|
||||
</p>
|
||||
<Button
|
||||
bsSize='large'
|
||||
className='signup-btn'>
|
||||
className='signup-btn'
|
||||
onClick={ jobActions.openModal }>
|
||||
Try the first month 20% off!
|
||||
</Button>
|
||||
</Jumbotron>
|
||||
@ -70,7 +80,10 @@ export default contain(
|
||||
<Row>
|
||||
{ this.renderChild(children, jobs) ||
|
||||
this.renderList(this.handleJobClick, jobs) }
|
||||
</Row>
|
||||
</Row>
|
||||
<CreateJobModal
|
||||
onHide={ jobActions.closeModal }
|
||||
showModal={ showModal } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ export default React.createClass({
|
||||
id,
|
||||
company,
|
||||
position,
|
||||
isHighlighted,
|
||||
description,
|
||||
logo,
|
||||
city,
|
||||
@ -44,6 +45,7 @@ export default React.createClass({
|
||||
);
|
||||
return (
|
||||
<Panel
|
||||
bsStyle={ isHighlighted ? 'warning' : 'default' }
|
||||
collapsible={ true }
|
||||
eventKey={ index }
|
||||
header={ header }
|
||||
|
319
common/app/routes/Jobs/components/NewJob.jsx
Normal file
319
common/app/routes/Jobs/components/NewJob.jsx
Normal file
@ -0,0 +1,319 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { History } from 'react-router';
|
||||
import { contain } from 'thundercats-react';
|
||||
import debugFactory from 'debug';
|
||||
import { getDefaults } from '../utils';
|
||||
|
||||
import {
|
||||
inHTMLData,
|
||||
uriInSingleQuotedAttr
|
||||
} from 'xss-filters';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Input,
|
||||
Row,
|
||||
Well
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import {
|
||||
isAscii,
|
||||
isEmail,
|
||||
isMobilePhone,
|
||||
isURL
|
||||
} from 'validator';
|
||||
|
||||
const debug = debugFactory('freecc:jobs:newForm');
|
||||
|
||||
const checkValidity = [
|
||||
'position',
|
||||
'locale',
|
||||
'description',
|
||||
'email',
|
||||
'phone',
|
||||
'url',
|
||||
'logo',
|
||||
'name',
|
||||
'highlight'
|
||||
];
|
||||
|
||||
function formatValue(value, validator, type = 'string') {
|
||||
const formated = getDefaults(type);
|
||||
if (validator && type === 'string') {
|
||||
formated.valid = validator(value);
|
||||
}
|
||||
if (value) {
|
||||
formated.value = value;
|
||||
formated.bsStyle = formated.valid ? 'success' : 'error';
|
||||
}
|
||||
return formated;
|
||||
}
|
||||
|
||||
function isValidURL(data) {
|
||||
return isURL(data, { 'require_protocol': true });
|
||||
}
|
||||
|
||||
function isValidPhone(data) {
|
||||
return isMobilePhone(data, 'en-US');
|
||||
}
|
||||
|
||||
export default contain({
|
||||
actions: 'jobActions',
|
||||
store: 'jobsStore',
|
||||
map({ form = {} }) {
|
||||
const {
|
||||
position,
|
||||
locale,
|
||||
description,
|
||||
email,
|
||||
phone,
|
||||
url,
|
||||
logo,
|
||||
name,
|
||||
highlight
|
||||
} = form;
|
||||
return {
|
||||
position: formatValue(position, isAscii),
|
||||
locale: formatValue(locale, isAscii),
|
||||
description: formatValue(description, isAscii),
|
||||
email: formatValue(email, isEmail),
|
||||
phone: formatValue(phone, isValidPhone),
|
||||
url: formatValue(url, isValidURL),
|
||||
logo: formatValue(logo, isValidURL),
|
||||
name: formatValue(name, isAscii),
|
||||
highlight: formatValue(highlight, null, 'bool')
|
||||
};
|
||||
},
|
||||
subscribeOnWillMount() {
|
||||
return typeof window !== 'undefined';
|
||||
}
|
||||
},
|
||||
React.createClass({
|
||||
displayName: 'NewJob',
|
||||
|
||||
propTypes: {
|
||||
jobActions: PropTypes.object,
|
||||
position: PropTypes.object,
|
||||
locale: PropTypes.object,
|
||||
description: PropTypes.object,
|
||||
email: PropTypes.object,
|
||||
phone: PropTypes.object,
|
||||
url: PropTypes.object,
|
||||
logo: PropTypes.object,
|
||||
name: PropTypes.object,
|
||||
highlight: PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [History],
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const props = this.props;
|
||||
let valid = true;
|
||||
checkValidity.forEach((prop) => {
|
||||
// if value exist, check if it is valid
|
||||
if (props[prop].value && props[prop].type !== 'boolean') {
|
||||
valid = valid && !!props[prop].valid;
|
||||
}
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
debug('form not valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
position,
|
||||
locale,
|
||||
description,
|
||||
email,
|
||||
phone,
|
||||
url,
|
||||
logo,
|
||||
name,
|
||||
highlight,
|
||||
jobActions
|
||||
} = this.props;
|
||||
|
||||
// sanitize user output
|
||||
const jobValues = {
|
||||
position: inHTMLData(position.value),
|
||||
location: inHTMLData(locale.value),
|
||||
description: inHTMLData(description.value),
|
||||
email: inHTMLData(email.value),
|
||||
phone: inHTMLData(phone.value),
|
||||
url: uriInSingleQuotedAttr(url.value),
|
||||
logo: uriInSingleQuotedAttr(logo.value),
|
||||
name: inHTMLData(name.value),
|
||||
highlight: !!highlight.value
|
||||
};
|
||||
|
||||
const job = Object.keys(jobValues).reduce((accu, prop) => {
|
||||
if (jobValues[prop]) {
|
||||
accu[prop] = jobValues[prop];
|
||||
}
|
||||
return accu;
|
||||
}, {});
|
||||
|
||||
job.postedOn = new Date();
|
||||
debug('job sanitized', job);
|
||||
jobActions.saveForm(job);
|
||||
|
||||
this.history.pushState(null, '/jobs/new/preview');
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
const { jobActions } = this.props;
|
||||
jobActions.getSavedForm();
|
||||
},
|
||||
|
||||
handleChange(name, { target: { value } }) {
|
||||
const { jobActions: { handleForm } } = this.props;
|
||||
handleForm({ [name]: value });
|
||||
},
|
||||
|
||||
render() {
|
||||
const {
|
||||
position,
|
||||
locale,
|
||||
description,
|
||||
email,
|
||||
phone,
|
||||
url,
|
||||
logo,
|
||||
name,
|
||||
highlight,
|
||||
jobActions: { handleForm }
|
||||
} = this.props;
|
||||
const { handleChange } = this;
|
||||
const labelClass = 'col-sm-offset-1 col-sm-2';
|
||||
const inputClass = 'col-sm-6';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Col>
|
||||
<Well className='text-center'>
|
||||
<h1>Create Your Job Post</h1>
|
||||
<form
|
||||
className='form-horizontal'
|
||||
onSubmit={ this.handleSubmit }>
|
||||
|
||||
<div className='spacer'>
|
||||
<h2>Job Information</h2>
|
||||
</div>
|
||||
<Input
|
||||
bsStyle={ position.bsStyle }
|
||||
label='Position'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('position', e) }
|
||||
placeholder='Position'
|
||||
type='text'
|
||||
value={ position.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ locale.bsStyle }
|
||||
label='Location'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('locale', e) }
|
||||
placeholder='Location'
|
||||
type='text'
|
||||
value={ locale.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ description.bsStyle }
|
||||
label='Description'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('description', e) }
|
||||
placeholder='Description'
|
||||
rows='10'
|
||||
type='textarea'
|
||||
value={ description.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
|
||||
<div className='divider'>
|
||||
<h2>Company Information</h2>
|
||||
</div>
|
||||
<Input
|
||||
bsStyle={ name.bsStyle }
|
||||
label='Company Name'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('name', e) }
|
||||
placeholder='Foo, INC'
|
||||
type='text'
|
||||
value={ name.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ email.bsStyle }
|
||||
label='Email'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('email', e) }
|
||||
placeholder='Email'
|
||||
type='email'
|
||||
value={ email.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ phone.bsStyle }
|
||||
label='Phone'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('phone', e) }
|
||||
placeholder='555-123-1234'
|
||||
type='tel'
|
||||
value={ phone.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ url.bsStyle }
|
||||
label='URL'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('url', e) }
|
||||
placeholder='http://freecatphotoapp.com'
|
||||
type='url'
|
||||
value={ url.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
<Input
|
||||
bsStyle={ logo.bsStyle }
|
||||
label='Logo'
|
||||
labelClassName={ labelClass }
|
||||
onChange={ (e) => handleChange('logo', e) }
|
||||
placeholder='http://freecatphotoapp.com/logo.png'
|
||||
type='url'
|
||||
value={ logo.value }
|
||||
wrapperClassName={ inputClass } />
|
||||
|
||||
<div className='divider'>
|
||||
<h2>Make it stand out</h2>
|
||||
</div>
|
||||
<Input
|
||||
checked={ highlight.value }
|
||||
label='Highlight your ad'
|
||||
labelClassName={ 'col-sm-offset-1 col-sm-6'}
|
||||
onChange={
|
||||
({ target: { checked } }) => handleForm({
|
||||
highlight: !!checked
|
||||
})
|
||||
}
|
||||
type='checkbox' />
|
||||
<div className='spacer' />
|
||||
<Row>
|
||||
<Col
|
||||
lg={ 6 }
|
||||
lgOffset={ 3 }>
|
||||
<Button
|
||||
block={ true }
|
||||
bsSize='large'
|
||||
bsStyle='primary'
|
||||
type='submit'>
|
||||
Preview My Ad
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
</Well>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
14
common/app/routes/Jobs/components/Preview.jsx
Normal file
14
common/app/routes/Jobs/components/Preview.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
// import React, { PropTypes } from 'react';
|
||||
import { contain } from 'thundercats-react';
|
||||
import ShowJob from './ShowJob.jsx';
|
||||
|
||||
export default contain(
|
||||
{
|
||||
store: 'JobsStore',
|
||||
actions: 'JobActions',
|
||||
map({ form: job = {} }) {
|
||||
return { job };
|
||||
}
|
||||
},
|
||||
ShowJob
|
||||
);
|
@ -1,13 +1,5 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { contain } from 'thundercats-react';
|
||||
import { Row, Thumbnail, Panel, Well } from 'react-bootstrap';
|
||||
import moment from 'moment';
|
||||
|
||||
const thumbnailStyle = {
|
||||
backgroundColor: 'white',
|
||||
maxHeight: '100px',
|
||||
maxWidth: '100px'
|
||||
};
|
||||
import ShowJob from './ShowJob.jsx';
|
||||
|
||||
export default contain(
|
||||
{
|
||||
@ -28,61 +20,5 @@ export default contain(
|
||||
return job.id !== id;
|
||||
}
|
||||
},
|
||||
React.createClass({
|
||||
displayName: 'ShowJob',
|
||||
propTypes: {
|
||||
job: PropTypes.object,
|
||||
params: PropTypes.object
|
||||
},
|
||||
|
||||
renderHeader({ company, position }) {
|
||||
return (
|
||||
<div>
|
||||
<h4 style={{ display: 'inline-block' }}>{ company }</h4>
|
||||
<h5
|
||||
className='pull-right hidden-xs hidden-md'
|
||||
style={{ display: 'inline-block' }}>
|
||||
{ position }
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
const { job = {} } = this.props;
|
||||
const {
|
||||
logo,
|
||||
position,
|
||||
city,
|
||||
company,
|
||||
state,
|
||||
email,
|
||||
phone,
|
||||
postedOn,
|
||||
description
|
||||
} = job;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Well>
|
||||
<Thumbnail
|
||||
alt={ company + 'company logo' }
|
||||
src={ logo }
|
||||
style={ thumbnailStyle } />
|
||||
<Panel>
|
||||
Position: { position }
|
||||
Location: { city }, { state }
|
||||
<br />
|
||||
Contact: { email || phone || 'N/A' }
|
||||
<br />
|
||||
Posted On: { moment(postedOn).format('MMMM Do, YYYY') }
|
||||
</Panel>
|
||||
<p>{ description }</p>
|
||||
</Well>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
ShowJob
|
||||
);
|
||||
|
67
common/app/routes/Jobs/components/ShowJob.jsx
Normal file
67
common/app/routes/Jobs/components/ShowJob.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Row, Thumbnail, Panel, Well } from 'react-bootstrap';
|
||||
import moment from 'moment';
|
||||
|
||||
const thumbnailStyle = {
|
||||
backgroundColor: 'white',
|
||||
maxHeight: '100px',
|
||||
maxWidth: '100px'
|
||||
};
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'ShowJob',
|
||||
propTypes: {
|
||||
job: PropTypes.object,
|
||||
params: PropTypes.object
|
||||
},
|
||||
|
||||
renderHeader({ company, position }) {
|
||||
return (
|
||||
<div>
|
||||
<h4 style={{ display: 'inline-block' }}>{ company }</h4>
|
||||
<h5
|
||||
className='pull-right hidden-xs hidden-md'
|
||||
style={{ display: 'inline-block' }}>
|
||||
{ position }
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
const { job = {} } = this.props;
|
||||
const {
|
||||
logo,
|
||||
position,
|
||||
city,
|
||||
company,
|
||||
state,
|
||||
email,
|
||||
phone,
|
||||
postedOn,
|
||||
description
|
||||
} = job;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Row>
|
||||
<Well>
|
||||
<Thumbnail
|
||||
alt={ company + 'company logo' }
|
||||
src={ logo }
|
||||
style={ thumbnailStyle } />
|
||||
<Panel>
|
||||
Position: { position }
|
||||
Location: { city }, { state }
|
||||
<br />
|
||||
Contact: { email || phone || 'N/A' }
|
||||
<br />
|
||||
Posted On: { moment(postedOn).format('MMMM Do, YYYY') }
|
||||
</Panel>
|
||||
<p>{ description }</p>
|
||||
</Well>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,7 +1,9 @@
|
||||
import { Actions } from 'thundercats';
|
||||
import store from 'store';
|
||||
import debugFactory from 'debug';
|
||||
|
||||
const debug = debugFactory('freecc:jobs:actions');
|
||||
const assign = Object.assign;
|
||||
|
||||
export default Actions({
|
||||
setJobs: null,
|
||||
@ -23,7 +25,7 @@ export default Actions({
|
||||
|
||||
// if no job found this will be null which is a op noop
|
||||
return foundJob ?
|
||||
Object.assign({}, oldState, { currentJob: foundJob }) :
|
||||
assign({}, oldState, { currentJob: foundJob }) :
|
||||
null;
|
||||
};
|
||||
},
|
||||
@ -31,6 +33,31 @@ export default Actions({
|
||||
getJob: null,
|
||||
getJobs(params) {
|
||||
return { params };
|
||||
},
|
||||
openModal() {
|
||||
return { showModal: true };
|
||||
},
|
||||
closeModal() {
|
||||
return { showModal: false };
|
||||
},
|
||||
handleForm(value) {
|
||||
return {
|
||||
transform(oldState) {
|
||||
const { form } = oldState;
|
||||
const newState = assign({}, oldState);
|
||||
newState.form = assign(
|
||||
{},
|
||||
form,
|
||||
value
|
||||
);
|
||||
return newState;
|
||||
}
|
||||
};
|
||||
},
|
||||
saveForm: null,
|
||||
getSavedForm: null,
|
||||
setForm(form) {
|
||||
return { form };
|
||||
}
|
||||
})
|
||||
.refs({ displayName: 'JobActions' })
|
||||
@ -56,8 +83,22 @@ export default Actions({
|
||||
debug('job services experienced an issue', err);
|
||||
return jobActions.setError({ err });
|
||||
}
|
||||
jobActions.setJobs({ currentJob: job });
|
||||
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);
|
||||
}
|
||||
});
|
||||
return jobActions;
|
||||
});
|
||||
|
@ -6,12 +6,25 @@ const {
|
||||
transformer
|
||||
} = Store;
|
||||
|
||||
export default Store()
|
||||
export default Store({ showModal: false })
|
||||
.refs({ displayName: 'JobsStore' })
|
||||
.init(({ instance: jobsStore, args: [cat] }) => {
|
||||
const { setJobs, findJob, setError } = cat.getActions('JobActions');
|
||||
const {
|
||||
setJobs,
|
||||
findJob,
|
||||
setError,
|
||||
openModal,
|
||||
closeModal,
|
||||
handleForm,
|
||||
setForm
|
||||
} = cat.getActions('JobActions');
|
||||
const register = createRegistrar(jobsStore);
|
||||
register(setter(setJobs));
|
||||
register(transformer(findJob));
|
||||
register(setter(setError));
|
||||
register(setter(openModal));
|
||||
register(setter(closeModal));
|
||||
register(setter(setForm));
|
||||
|
||||
register(transformer(findJob));
|
||||
register(handleForm);
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import Jobs from './components/Jobs.jsx';
|
||||
import NewJob from './components/NewJob.jsx';
|
||||
import Show from './components/Show.jsx';
|
||||
import Preview from './components/Preview.jsx';
|
||||
|
||||
/*
|
||||
* index: /jobs list jobs
|
||||
@ -11,6 +13,12 @@ export default {
|
||||
childRoutes: [{
|
||||
path: '/jobs',
|
||||
component: Jobs
|
||||
}, {
|
||||
path: 'jobs/new',
|
||||
component: NewJob
|
||||
}, {
|
||||
path: 'jobs/new/preview',
|
||||
component: Preview
|
||||
}, {
|
||||
path: 'jobs/:id',
|
||||
component: Show
|
||||
|
22
common/app/routes/Jobs/utils.js
Normal file
22
common/app/routes/Jobs/utils.js
Normal file
@ -0,0 +1,22 @@
|
||||
const defaults = {
|
||||
'string': {
|
||||
value: '',
|
||||
valid: false,
|
||||
pristine: true,
|
||||
type: 'string'
|
||||
},
|
||||
bool: {
|
||||
value: false,
|
||||
type: 'boolean'
|
||||
}
|
||||
};
|
||||
|
||||
export function getDefaults(type, value) {
|
||||
if (!type) {
|
||||
return defaults['string'];
|
||||
}
|
||||
if (value) {
|
||||
return Object.assign({}, defaults[type], { value });
|
||||
}
|
||||
return Object.assign({}, defaults[type]);
|
||||
}
|
@ -3,12 +3,8 @@ import Hikes from './Hikes';
|
||||
|
||||
export default {
|
||||
path: '/',
|
||||
getChildRoutes(locationState, cb) {
|
||||
setTimeout(() => {
|
||||
cb(null, [
|
||||
Jobs,
|
||||
Hikes
|
||||
]);
|
||||
}, 0);
|
||||
}
|
||||
childRoutes: [
|
||||
Jobs,
|
||||
Hikes
|
||||
]
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "job",
|
||||
"base": "PersistedModel",
|
||||
"strict": true,
|
||||
"idInjection": true,
|
||||
"trackChanges": false,
|
||||
"properties": {
|
||||
@ -29,6 +30,9 @@
|
||||
"state": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -38,7 +42,7 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"isApproverd": {
|
||||
"isApproved": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isHighlighted": {
|
||||
|
@ -49,6 +49,7 @@ var paths = {
|
||||
'!public/js/bundle*',
|
||||
'node_modules/',
|
||||
'client/',
|
||||
'seed',
|
||||
'server/manifests/*.json',
|
||||
'server/rev-manifest.json'
|
||||
],
|
||||
|
10
package.json
10
package.json
@ -58,10 +58,10 @@
|
||||
"gulp-webpack": "^1.5.0",
|
||||
"helmet": "~0.9.0",
|
||||
"helmet-csp": "^0.2.3",
|
||||
"history": "^1.9.0",
|
||||
"jade": "~1.8.0",
|
||||
"json-loader": "^0.5.2",
|
||||
"less": "~1.7.5",
|
||||
"less-middleware": "~2.0.1",
|
||||
"less": "~2.5.1",
|
||||
"lodash": "^3.9.3",
|
||||
"loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password",
|
||||
"loopback-boot": "2.8.2",
|
||||
@ -89,7 +89,7 @@
|
||||
"react": "^0.13.3",
|
||||
"react-bootstrap": "~0.23.7",
|
||||
"react-motion": "~0.1.0",
|
||||
"react-router": "https://github.com/BerkeleyTrue/react-router#freecodecamp",
|
||||
"react-router": "^1.0.0-rc1",
|
||||
"react-vimeo": "^0.0.3",
|
||||
"request": "~2.53.0",
|
||||
"rev-del": "^1.0.5",
|
||||
@ -97,12 +97,14 @@
|
||||
"sanitize-html": "~1.6.1",
|
||||
"sort-keys": "^1.1.1",
|
||||
"source-map-support": "^0.3.2",
|
||||
"store": "https://github.com/berkeleytrue/store.js.git#feature/noop-server",
|
||||
"thundercats": "^2.1.0",
|
||||
"thundercats-react": "^0.1.0",
|
||||
"twit": "~1.1.20",
|
||||
"uglify-js": "~2.4.15",
|
||||
"validator": "~3.22.1",
|
||||
"validator": "^3.22.1",
|
||||
"webpack": "^1.9.12",
|
||||
"xss-filters": "^1.2.6",
|
||||
"yui": "~3.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { RoutingContext } from 'react-router';
|
||||
import Fetchr from 'fetchr';
|
||||
import Location from 'react-router/lib/Location';
|
||||
import { createLocation } from 'history';
|
||||
import debugFactory from 'debug';
|
||||
import { app$ } from '../../common/app';
|
||||
import { RenderToString } from 'thundercats-react';
|
||||
@ -30,25 +30,25 @@ export default function reactSubRouter(app) {
|
||||
|
||||
function serveReactApp(req, res, next) {
|
||||
const services = new Fetchr({ req });
|
||||
const location = new Location(req.path, req.query);
|
||||
const location = createLocation(req.path);
|
||||
|
||||
// returns a router wrapped app
|
||||
app$(location)
|
||||
app$({ location })
|
||||
// if react-router does not find a route send down the chain
|
||||
.filter(function({ initialState }) {
|
||||
if (!initialState) {
|
||||
.filter(function({ props}) {
|
||||
if (!props) {
|
||||
debug('react tried to find %s but got 404', location.pathname);
|
||||
return next();
|
||||
}
|
||||
return !!initialState;
|
||||
return !!props;
|
||||
})
|
||||
.flatMap(function({ initialState, AppCat }) {
|
||||
.flatMap(function({ props, AppCat }) {
|
||||
// call thundercats renderToString
|
||||
// prefetches data and sets up it up for current state
|
||||
debug('rendering to string');
|
||||
return RenderToString(
|
||||
AppCat(null, services),
|
||||
React.createElement(Router, initialState)
|
||||
React.createElement(RoutingContext, props)
|
||||
);
|
||||
})
|
||||
// makes sure we only get one onNext and closes subscription
|
||||
|
Reference in New Issue
Block a user