diff --git a/client/less/main.less b/client/less/main.less
index b24a4a35b7..35b5491ae2 100644
--- a/client/less/main.less
+++ b/client/less/main.less
@@ -959,7 +959,6 @@ code {
z-index: 20000 !important;
}
-@import "jobs.less";
//uncomment this to see the dimensions of all elements outlined in red
//* {
@@ -1070,3 +1069,4 @@ code {
}
@import "chat.less";
+@import "jobs.less";
diff --git a/common/app/routes/Jobs/components/Jobs.jsx b/common/app/routes/Jobs/components/Jobs.jsx
index d9c7eccdf4..bc995ae9c1 100644
--- a/common/app/routes/Jobs/components/Jobs.jsx
+++ b/common/app/routes/Jobs/components/Jobs.jsx
@@ -3,6 +3,7 @@ import { contain } from 'thundercats-react';
import { Button, Panel, Row, Col } from 'react-bootstrap';
import ListJobs from './List.jsx';
+import TwitterBtn from './TwitterBtn.jsx';
export default contain(
{
@@ -18,12 +19,18 @@ 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) {
@@ -54,6 +61,7 @@ export default contain(
render() {
const {
children,
+ numOfFollowers,
jobs,
appActions
} = this.props;
@@ -88,13 +96,7 @@ export default contain(
Post a job: $200 for 30 days + weekly tweets
-
- Follow @CamperJobs
-
+
diff --git a/common/app/routes/Jobs/components/TwitterBtn.jsx b/common/app/routes/Jobs/components/TwitterBtn.jsx
new file mode 100644
index 0000000000..fb5f8edf12
--- /dev/null
+++ b/common/app/routes/Jobs/components/TwitterBtn.jsx
@@ -0,0 +1,33 @@
+import React, { createClass, PropTypes } from 'react';
+import { Button } from 'react-bootstrap';
+
+const followLink = 'https://twitter.com/intent/follow?' +
+ 'ref_src=twsrc%5Etfw®ion=follow_link&screen_name=CamperJobs&' +
+ 'amp;tw_p=followbutton';
+
+function commify(count) {
+ return Number(count).toLocaleString('en');
+}
+
+export default createClass({
+
+ displayName: 'FollowButton',
+
+ propTypes: {
+ count: PropTypes.number
+ },
+
+ render() {
+ const { count } = this.props;
+ return (
+
+ );
+ }
+});
diff --git a/common/app/routes/Jobs/flux/Actions.js b/common/app/routes/Jobs/flux/Actions.js
index d37f16a7f5..d14d83716d 100644
--- a/common/app/routes/Jobs/flux/Actions.js
+++ b/common/app/routes/Jobs/flux/Actions.js
@@ -1,6 +1,7 @@
import { Actions } from 'thundercats';
import store from 'store';
import debugFactory from 'debug';
+import { jsonp$ } from '../../../../utils/jsonp$';
const debug = debugFactory('freecc:jobs:actions');
const assign = Object.assign;
@@ -59,6 +60,10 @@ export default Actions({
getSavedForm: null,
setForm(form) {
return { form };
+ },
+ getFollowers: null,
+ setFollowersCount(numOfFollowers) {
+ return { numOfFollowers };
}
})
.refs({ displayName: 'JobActions' })
@@ -113,5 +118,20 @@ export default Actions({
appActions.goTo(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)
+ );
+ });
return jobActions;
});
diff --git a/common/app/routes/Jobs/flux/Store.js b/common/app/routes/Jobs/flux/Store.js
index 8f3acd2949..15aa55b770 100644
--- a/common/app/routes/Jobs/flux/Store.js
+++ b/common/app/routes/Jobs/flux/Store.js
@@ -19,7 +19,8 @@ export default Store({
openModal,
closeModal,
handleForm,
- setForm
+ setForm,
+ setFollowersCount
} = cat.getActions('JobActions');
const register = createRegistrar(jobsStore);
register(setter(setJobs));
@@ -27,6 +28,7 @@ export default Store({
register(setter(openModal));
register(setter(closeModal));
register(setter(setForm));
+ register(setter(setFollowersCount));
register(transformer(findJob));
register(handleForm);
diff --git a/common/utils/jsonp$.js b/common/utils/jsonp$.js
new file mode 100644
index 0000000000..99dbbe2eb4
--- /dev/null
+++ b/common/utils/jsonp$.js
@@ -0,0 +1,77 @@
+import { AnonymousObservable, Disposable } from 'rx';
+
+const root = typeof window !== 'undefined' ? window : {};
+const trash = 'document' in root && root.document.createElement('div');
+
+function destroy(element) {
+ trash.appendChild(element);
+ trash.innerHTML = '';
+}
+
+export function jsonp$(options) {
+ let id = 0;
+ if (typeof options === 'string') {
+ options = { url: options };
+ }
+
+ return new AnonymousObservable(function(o) {
+ const settings = Object.assign(
+ {},
+ {
+ jsonp: 'JSONPCallback',
+ async: true,
+ jsonpCallback: 'rxjsjsonpCallbackscallback_' + (id++).toString(36)
+ },
+ options
+ );
+
+ let script = root.document.createElement('script');
+ script.type = 'text/javascript';
+ script.async = settings.async;
+ script.src = settings.url.replace(settings.jsonp, settings.jsonpCallback);
+
+ root[settings.jsonpCallback] = function(data) {
+ root[settings.jsonpCallback].called = true;
+ root[settings.jsonpCallback].data = data;
+ };
+
+ const handler = function(e) {
+ if (e.type === 'load' && !root[settings.jsonpCallback].called) {
+ e = { type: 'error' };
+ }
+ const status = e.type === 'error' ? 400 : 200;
+ const data = root[settings.jsonpCallback].data;
+
+ if (status === 200) {
+ o.onNext({
+ status: status,
+ responseType: 'jsonp',
+ response: data,
+ originalEvent: e
+ });
+
+ o.onCompleted();
+ } else {
+ o.onError({
+ type: 'error',
+ status: status,
+ originalEvent: e
+ });
+ }
+ };
+
+ script.onload = script.onreadystatechanged = script.onerror = handler;
+
+ const head = root.document.getElementsByTagName('head')[0] ||
+ root.document.documentElement;
+
+ head.insertBefore(script, head.firstChild);
+
+ return Disposable.create(() => {
+ script.onload = script.onreadystatechanged = script.onerror = null;
+
+ destroy(script);
+ script = null;
+ });
+ });
+}