(select a file on the left)
src/components/nav-helper.js
import app from 'ampersand-app'
import React from 'react'
import localLinks from 'local-links'
export default React.createClass({
displayName: 'NavHelper',
onClick (event) {
const pathname = localLinks.getLocalPathname(event)
if (pathname) {
event.preventDefault()
app.router.history.navigate(pathname)
}
},
render () {
return (
<div {...this.props} onClick={this.onClick}>
{this.props.children}
</div>
)
}
})
src/models/me.js
import Model from 'ampersand-model'
export default Model.extend({
url: 'https://api.github.com/user',
initialize () {
this.token = window.localStorage.token
this.on('change:token', this.onChangeToken)
this.fetchInitialData()
},
props: {
id: 'number',
login: 'string',
avatar_url: 'string'
},
session: {
token: 'string'
},
onChangeToken () {
window.localStorage.token = this.token
this.fetchInitialData()
},
fetchInitialData () {
if (this.token) {
this.fetch()
}
},
ajaxConfig () {
return {
headers: {
Authorization: 'token ' + this.token
}
}
}
})
src/pages/public.js
import React from 'react'
import NavHelper from '../components/nav-helper'
export default React.createClass({
displayName: 'PublicPage',
render () {
return (
<NavHelper className='container'>
<header role='banner'>
<h1>Labelr</h1>
</header>
<div>
<p>We label stuff for you, because, we can™</p>
<a href='/login' className='button button-large'>
<span className='mega-octicon octicon-mark-github'></span> Login with GitHub
</a>
</div>
</NavHelper>
)
}
})
src/pages/repos.js
import React from 'react'
export default React.createClass({
displayName: 'ReposPage',
render () {
return <h1>Repos page</h1>
}
})
src/styles/main.styl
@import 'yeticss'
header
padding-top: 50px
.label
height: 40px
.label-color
width: 24px
height: 24px
border: 1px solid grey
border-radius: 20px
display: inline-block
> span
margin-left: 10px
margin-right: 10px
src/app.js
import app from 'ampersand-app'
import styles from './styles/main.styl'
import Router from './router'
import Me from './models/me'
window.app = app
app.extend({
init () {
this.me = new Me()
this.router = new Router()
this.router.history.start()
}
})
app.init()
src/layout.js
import React from 'react'
import NavHelper from './components/nav-helper'
import ampersandMixin from 'ampersand-react-mixin'
export default React.createClass({
mixins: [ampersandMixin],
displayName: 'Layout',
render () {
const {me} = this.props
return (
<NavHelper>
<nav className='top-nav top-nav-light cf' role='navigation'>
<input id='menu-toggle' className='menu-toggle' type='checkbox'/>
<label htmlFor='menu-toggle'>Menu</label>
<ul className='list-unstyled list-inline cf'>
<li>Labelr</li>
<li><a href='/repos'>Repos</a></li>
<li className='pull-right'>{me.login} <a href='/logout'>Logout</a></li>
</ul>
</nav>
<div className='container'>
{this.props.children}
</div>
</NavHelper>
)
}
})
src/router.js
import app from 'ampersand-app'
import React from 'react'
import qs from 'qs'
import xhr from 'xhr'
import uuid from 'node-uuid'
import Router from 'ampersand-router'
import PublicPage from './pages/public'
import ReposPage from './pages/repos'
import Layout from './layout'
export default Router.extend({
renderPage (page, opts = {layout: true}) {
if (opts.layout) {
page = (
<Layout me={app.me}>
{page}
</Layout>
)
}
React.render(page, document.body)
},
routes: {
'': 'public',
'repos': 'repos',
'login': 'login',
'logout': 'logout',
'auth/callback?:query': 'authCallback'
},
public () {
this.renderPage(<PublicPage/>, {layout: false})
},
repos () {
this.renderPage(<ReposPage/>)
},
login () {
const state = uuid()
window.localStorage.state = state
window.location = 'https://github.com/login/oauth/authorize?' + qs.stringify({
client_id: 'f8dd69187841cdd22a26',
redirect_uri: window.location.origin + '/auth/callback',
scope: 'user,repo',
state: state
})
},
logout () {
window.localStorage.clear()
window.location = '/'
},
authCallback (query) {
query = qs.parse(query)
if (query.state === window.localStorage.state) {
delete window.localStorage.state
xhr({
url: 'https://labelr-localhost.herokuapp.com/authenticate/' + query.code,
json: true
}, (err, resp, body) => {
if (err) {
console.error('something went wrong')
} else {
app.me.token = body.token
this.redirectTo('/repos')
}
})
}
}
})
package.json
{
"name": "labelr",
"version": "1.0.0",
"description": "A really awesome way to manage labels for github issues.",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server",
"prebuild": "rm -rf public && mkdir public",
"build": "NODE_ENV=production webpack",
"deploy": "surge -p public -d labelr.surge.sh",
"yolo": "npm run build && npm run deploy"
},
"author": "Henrik Joreteg <henrik@andyet.net>",
"license": "MIT",
"dependencies": {
"ampersand-app": "^1.0.4",
"ampersand-model": "^5.0.3",
"ampersand-react-mixin": "^0.1.3",
"ampersand-router": "^3.0.2",
"autoprefixer-stylus": "^0.5.0",
"babel": "^5.1.13",
"babel-core": "^5.1.13",
"babel-loader": "^5.0.0",
"css-loader": "^0.12.0",
"file-loader": "^0.8.1",
"hjs-webpack": "^2.0.1",
"local-links": "^1.4.0",
"node-uuid": "^1.4.3",
"qs": "^2.4.1",
"react": "^0.13.2",
"react-hot-loader": "^1.2.5",
"style-loader": "^0.12.1",
"stylus-loader": "^1.1.0",
"surge": "^0.11.1",
"url-loader": "^0.5.5",
"webpack": "^1.8.11",
"webpack-dev-server": "^1.8.2",
"xhr": "^2.0.1",
"yeticss": "^6.0.5"
}
}
webpack.config.js
var getConfig = require('hjs-webpack')
module.exports = getConfig({
in: 'src/app.js',
out: 'public',
isDev: process.env.NODE_ENV !== 'production',
})