sipp11
9 years ago
commit
62ddf4ff64
25 changed files with 673 additions and 0 deletions
@ -0,0 +1,39 @@ |
|||||||
|
# Add authentication to a React Flux app |
||||||
|
|
||||||
|
This is a sample that shows how you can add authentication to a React Flux app. Read more about it in [this blog post](https://auth0.com/blog/2015/04/09/adding-authentication-to-your-react-flux-app/) |
||||||
|
|
||||||
|
## Using it |
||||||
|
|
||||||
|
Clone this repository as well as [the server](https://github.com/auth0/nodejs-jwt-authentication-sample) for this example. |
||||||
|
|
||||||
|
First, run the server app in the port `3001`. |
||||||
|
|
||||||
|
Then, run `npm install` on this project and run `npm run watch` to start the app. Then just navigate to [http://localhost:3000](http://localhost:3000) |
||||||
|
|
||||||
|
## How does it work? |
||||||
|
|
||||||
|
To learn more about how this project works and how it has been implemented, please read [this blog post](https://auth0.com/blog/2015/04/09/adding-authentication-to-your-react-flux-app/) |
||||||
|
|
||||||
|
## Issue Reporting |
||||||
|
|
||||||
|
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. |
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
MIT |
||||||
|
|
||||||
|
## What is Auth0? |
||||||
|
|
||||||
|
Auth0 helps you to: |
||||||
|
|
||||||
|
* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**. |
||||||
|
* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**. |
||||||
|
* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user. |
||||||
|
* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely. |
||||||
|
* Analytics of how, when and where users are logging in. |
||||||
|
* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules). |
||||||
|
|
||||||
|
## Create a free account in Auth0 |
||||||
|
|
||||||
|
1. Go to [Auth0](https://auth0.com) and click Sign Up. |
||||||
|
2. Use Google, GitHub or Microsoft Account to login. |
@ -0,0 +1,14 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>React Browserify SPA seed</title> |
||||||
|
<link rel="stylesheet" type="text/css" href="build/build.css"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<!-- content section --> |
||||||
|
<section id="content"></section> |
||||||
|
|
||||||
|
<!-- Initialize SPA --> |
||||||
|
<script type="text/javascript" src="build/build.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,9 @@ |
|||||||
|
{ |
||||||
|
"name": "A Single Page App without a Framework", |
||||||
|
"type": "sample", |
||||||
|
"tags": [ |
||||||
|
"web", |
||||||
|
"javascript", |
||||||
|
"single-page-app" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
{ |
||||||
|
"name": "react-browserify-spa-seed", |
||||||
|
"version": "0.0.1", |
||||||
|
"description": "Seed project for React, Browserify, Rework SPAs", |
||||||
|
"main": "index.js", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git@github.com:mgonto/react-browserify-spa-seed.git" |
||||||
|
}, |
||||||
|
"authors": [ |
||||||
|
"Martin Gontovnikas <martin@gon.to> (http://gon.to/)" |
||||||
|
], |
||||||
|
"browserify": { |
||||||
|
"transform": [ |
||||||
|
"babelify" |
||||||
|
] |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"start": "npm run build && serve .", |
||||||
|
"build": "npm run build-js && npm run build-css", |
||||||
|
"watch": "npm run watch-js & npm run watch-css & serve .", |
||||||
|
"test": "npm run lint -s && npm run build", |
||||||
|
"build-css": "rework-npm index.css | cleancss -o build/build.css", |
||||||
|
"build-js": "browserify --extension=.jsx --extension=.js src/app.jsx | uglifyjs > build/build.js", |
||||||
|
"watch-js": "watchify --extension=.jsx --extension=.js src/app.jsx -o build/build.js --debug --verbose", |
||||||
|
"watch-css": "nodemon -e css --ignore build/build.css --exec 'rework-npm index.css -o build/build.css'", |
||||||
|
"lint-eslint": "eslint .", |
||||||
|
"lint-jscs": "jscs .", |
||||||
|
"lint": "npm run lint-eslint && npm run lint-jscs" |
||||||
|
}, |
||||||
|
"license": "MIT", |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/mgonto/react-browserify-spa-seed/issues" |
||||||
|
}, |
||||||
|
"homepage": "https://github.com/mgonto/react-browserify-spa-seed", |
||||||
|
"dependencies": { |
||||||
|
"bootstrap": "^3.3.0", |
||||||
|
"flux": "^2.0.1", |
||||||
|
"jwt-decode": "^1.1.0", |
||||||
|
"react": "^0.13", |
||||||
|
"react-mixin": "^1.1.0", |
||||||
|
"react-router": "^0.13.2", |
||||||
|
"reqwest": "^1.1.5", |
||||||
|
"when": "^3.7.2" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"babelify": "^6.1.0", |
||||||
|
"browser-sync": "^2.1.6", |
||||||
|
"browserify": "^8.0.3", |
||||||
|
"clean-css": "^3.1.9", |
||||||
|
"eslint": "^0.14.1", |
||||||
|
"nodemon": "^1.5.0", |
||||||
|
"rework": "^1.0.1", |
||||||
|
"rework-npm": "^1.0.0", |
||||||
|
"rework-npm-cli": "^0.1.1", |
||||||
|
"serve": "^1.4.0", |
||||||
|
"uglify-js": "^2.4.15", |
||||||
|
"watchify": "^2.1.1" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
import AppDispatcher from '../dispatchers/AppDispatcher.js'; |
||||||
|
import {LOGIN_USER, LOGOUT_USER} from '../constants/LoginConstants.js'; |
||||||
|
import RouterContainer from '../services/RouterContainer' |
||||||
|
|
||||||
|
export default { |
||||||
|
loginUser: (jwt) => { |
||||||
|
var savedJwt = localStorage.getItem('jwt'); |
||||||
|
|
||||||
|
AppDispatcher.dispatch({ |
||||||
|
actionType: LOGIN_USER, |
||||||
|
jwt: jwt |
||||||
|
}); |
||||||
|
|
||||||
|
if (savedJwt !== jwt) { |
||||||
|
var nextPath = RouterContainer.get().getCurrentQuery().nextPath || '/'; |
||||||
|
|
||||||
|
RouterContainer.get().transitionTo(nextPath); |
||||||
|
localStorage.setItem('jwt', jwt); |
||||||
|
} |
||||||
|
}, |
||||||
|
logoutUser: () => { |
||||||
|
RouterContainer.get().transitionTo('/login'); |
||||||
|
localStorage.removeItem('jwt'); |
||||||
|
AppDispatcher.dispatch({ |
||||||
|
actionType: LOGOUT_USER |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import AppDispatcher from '../dispatchers/AppDispatcher.js'; |
||||||
|
import {QUOTE_GET} from '../constants/QuoteConstants.js'; |
||||||
|
|
||||||
|
export default { |
||||||
|
gotQuote: (quote) => { |
||||||
|
AppDispatcher.dispatch({ |
||||||
|
actionType: QUOTE_GET, |
||||||
|
quote: quote |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import Router, {Route} from 'react-router'; |
||||||
|
import AuthenticatedApp from './components/AuthenticatedApp' |
||||||
|
import Login from './components/Login'; |
||||||
|
import Signup from './components/Signup'; |
||||||
|
import Home from './components/Home'; |
||||||
|
import Quote from './components/Quote'; |
||||||
|
import RouterContainer from './services/RouterContainer'; |
||||||
|
import LoginActions from './actions/LoginActions'; |
||||||
|
|
||||||
|
var routes = ( |
||||||
|
<Route handler={AuthenticatedApp}> |
||||||
|
<Route name="login" handler={Login}/> |
||||||
|
<Route name="signup" handler={Signup}/> |
||||||
|
<Route name="home" path="/" handler={Home}/> |
||||||
|
<Route name="quote" handler={Quote}/> |
||||||
|
</Route> |
||||||
|
); |
||||||
|
|
||||||
|
var router = Router.create({routes}); |
||||||
|
RouterContainer.set(router); |
||||||
|
|
||||||
|
let jwt = localStorage.getItem('jwt'); |
||||||
|
if (jwt) { |
||||||
|
LoginActions.loginUser(jwt); |
||||||
|
} |
||||||
|
|
||||||
|
router.run(function (Handler) { |
||||||
|
React.render(<Handler />, document.getElementById('content')); |
||||||
|
}); |
||||||
|
|
@ -0,0 +1,78 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
import React from 'react'; |
||||||
|
import LoginStore from '../stores/LoginStore' |
||||||
|
import { Route, RouteHandler, Link } from 'react-router'; |
||||||
|
import AuthService from '../services/AuthService' |
||||||
|
|
||||||
|
export default class AuthenticatedApp extends React.Component { |
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.state = this._getLoginState(); |
||||||
|
} |
||||||
|
|
||||||
|
_getLoginState() { |
||||||
|
return { |
||||||
|
userLoggedIn: LoginStore.isLoggedIn() |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
this.changeListener = this._onChange.bind(this); |
||||||
|
LoginStore.addChangeListener(this.changeListener); |
||||||
|
} |
||||||
|
|
||||||
|
_onChange() { |
||||||
|
this.setState(this._getLoginState()); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
LoginStore.removeChangeListener(this.changeListener); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div className="container"> |
||||||
|
<nav className="navbar navbar-default"> |
||||||
|
<div className="navbar-header"> |
||||||
|
<a className="navbar-brand" href="/">React Flux app with JWT authentication</a> |
||||||
|
</div> |
||||||
|
{this.headerItems} |
||||||
|
</nav> |
||||||
|
<RouteHandler/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
logout(e) { |
||||||
|
e.preventDefault(); |
||||||
|
AuthService.logout(); |
||||||
|
} |
||||||
|
|
||||||
|
get headerItems() { |
||||||
|
if (!this.state.userLoggedIn) { |
||||||
|
return ( |
||||||
|
<ul className="nav navbar-nav navbar-right"> |
||||||
|
<li> |
||||||
|
<Link to="login">Login</Link> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<Link to="signup">Signup</Link> |
||||||
|
</li> |
||||||
|
</ul>) |
||||||
|
} else { |
||||||
|
return ( |
||||||
|
<ul className="nav navbar-nav navbar-right"> |
||||||
|
<li> |
||||||
|
<Link to="home">Home</Link> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<Link to="quote">Quote</Link> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="" onClick={this.logout}>Logout</a> |
||||||
|
</li> |
||||||
|
</ul>) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import LoginStore from '../stores/LoginStore'; |
||||||
|
|
||||||
|
export default (ComposedComponent) => { |
||||||
|
return class AuthenticatedComponent extends React.Component { |
||||||
|
|
||||||
|
static willTransitionTo(transition) { |
||||||
|
if (!LoginStore.isLoggedIn()) { |
||||||
|
transition.redirect('/login', {}, {'nextPath' : transition.path}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.state = this._getLoginState(); |
||||||
|
} |
||||||
|
|
||||||
|
_getLoginState() { |
||||||
|
return { |
||||||
|
userLoggedIn: LoginStore.isLoggedIn(), |
||||||
|
user: LoginStore.user, |
||||||
|
jwt: LoginStore.jwt |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
this.changeListener = this._onChange.bind(this); |
||||||
|
LoginStore.addChangeListener(this.changeListener); |
||||||
|
} |
||||||
|
|
||||||
|
_onChange() { |
||||||
|
this.setState(this._getLoginState()); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
LoginStore.removeChangeListener(this.changeListener); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<ComposedComponent |
||||||
|
{...this.props} |
||||||
|
user={this.state.user} |
||||||
|
jwt={this.state.jwt} |
||||||
|
userLoggedIn={this.state.userLoggedIn} /> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,8 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import AuthenticatedComponent from './AuthenticatedComponent' |
||||||
|
|
||||||
|
export default AuthenticatedComponent(class Home extends React.Component { |
||||||
|
render() { |
||||||
|
return (<h1>Hello {this.props.user ? this.props.user.username : ''}</h1>); |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,44 @@ |
|||||||
|
import React from 'react/addons'; |
||||||
|
import ReactMixin from 'react-mixin'; |
||||||
|
import Auth from '../services/AuthService' |
||||||
|
|
||||||
|
export default class Login extends React.Component { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.state = { |
||||||
|
user: '', |
||||||
|
password: '' |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
login(e) { |
||||||
|
e.preventDefault(); |
||||||
|
Auth.login(this.state.user, this.state.password) |
||||||
|
.catch(function(err) { |
||||||
|
alert("There's an error logging in"); |
||||||
|
console.log("Error logging in", err); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div className="login jumbotron center-block"> |
||||||
|
<h1>Login</h1> |
||||||
|
<form role="form"> |
||||||
|
<div className="form-group"> |
||||||
|
<label htmlFor="username">Username</label> |
||||||
|
<input type="text" valueLink={this.linkState('user')} className="form-control" id="username" placeholder="Username" /> |
||||||
|
</div> |
||||||
|
<div className="form-group"> |
||||||
|
<label htmlFor="password">Password</label> |
||||||
|
<input type="password" valueLink={this.linkState('password')} className="form-control" id="password" ref="password" placeholder="Password" /> |
||||||
|
</div> |
||||||
|
<button type="submit" className="btn btn-default" onClick={this.login.bind(this)}>Submit</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ReactMixin(Login.prototype, React.addons.LinkedStateMixin); |
@ -0,0 +1,47 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import AuthenticatedComponent from './AuthenticatedComponent'; |
||||||
|
import QuoteStore from '../stores/QuoteStore.js'; |
||||||
|
import QuoteService from '../services/QuoteService.js'; |
||||||
|
|
||||||
|
export default AuthenticatedComponent(class Quote extends React.Component { |
||||||
|
constructor(props) { |
||||||
|
super(props); |
||||||
|
this.state = this.getQuoteState(); |
||||||
|
this._onChange = this._onChange.bind(this); |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount() { |
||||||
|
if (!this.state.quote) { |
||||||
|
this.requestNextQuote(); |
||||||
|
} |
||||||
|
|
||||||
|
QuoteStore.addChangeListener(this._onChange); |
||||||
|
} |
||||||
|
|
||||||
|
componentWillUnmount() { |
||||||
|
QuoteStore.removeChangeListener(this._onChange); |
||||||
|
} |
||||||
|
|
||||||
|
_onChange() { |
||||||
|
this.setState(this.getQuoteState()); |
||||||
|
} |
||||||
|
|
||||||
|
requestNextQuote() { |
||||||
|
QuoteService.nextQuote(); |
||||||
|
} |
||||||
|
|
||||||
|
getQuoteState() { |
||||||
|
return { |
||||||
|
quote: QuoteStore.quote |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<h1>{this.state.quote}</h1> |
||||||
|
<button className="btn btn-primary" type="button" onClick={this.requestNextQuote}>Next Quote</button> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,49 @@ |
|||||||
|
import React from 'react/addons'; |
||||||
|
import ReactMixin from 'react-mixin'; |
||||||
|
import Auth from '../services/AuthService' |
||||||
|
|
||||||
|
export default class Signup extends React.Component { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super() |
||||||
|
this.state = { |
||||||
|
user: '', |
||||||
|
password: '', |
||||||
|
extra: '' |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
signup(e) { |
||||||
|
e.preventDefault(); |
||||||
|
Auth.signup(this.state.user, this.state.password, this.state.extra) |
||||||
|
.catch(function(err) { |
||||||
|
alert("There's an error logging in"); |
||||||
|
console.log("Error logging in", err); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
return ( |
||||||
|
<div className="login jumbotron center-block"> |
||||||
|
<h1>Signup</h1> |
||||||
|
<form role="form"> |
||||||
|
<div className="form-group"> |
||||||
|
<label htmlFor="username">Username</label> |
||||||
|
<input type="text" valueLink={this.linkState('user')} className="form-control" id="username" placeholder="Username" /> |
||||||
|
</div> |
||||||
|
<div className="form-group"> |
||||||
|
<label htmlFor="password">Password</label> |
||||||
|
<input type="password" valueLink={this.linkState('password')} className="form-control" id="password" ref="password" placeholder="Password" /> |
||||||
|
</div> |
||||||
|
<div className="form-group"> |
||||||
|
<label htmlFor="extra">Extra</label> |
||||||
|
<input type="text" valueLink={this.linkState('extra')} className="form-control" id="password" ref="password" placeholder="Some extra information" /> |
||||||
|
</div> |
||||||
|
<button type="submit" className="btn btn-default" onClick={this.signup.bind(this)}>Submit</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ReactMixin(Signup.prototype, React.addons.LinkedStateMixin); |
@ -0,0 +1,8 @@ |
|||||||
|
var BASE_URL = 'http://rocket.dev/'; |
||||||
|
export default { |
||||||
|
BASE_URL: BASE_URL, |
||||||
|
LOGIN_URL: BASE_URL + 'api-token-auth/', |
||||||
|
SIGNUP_URL: BASE_URL + 'users', |
||||||
|
LOGIN_USER: 'LOGIN_USER', |
||||||
|
LOGOUT_USER: 'LOGOUT_USER' |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
var BASE_URL = 'http://rocket.dev/'; |
||||||
|
export default { |
||||||
|
BASE_URL: BASE_URL, |
||||||
|
QUOTE_URL: BASE_URL + 'account/', |
||||||
|
QUOTE_GET: 'QUOTE_GET' |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
import { Dispatcher } from 'flux'; |
||||||
|
|
||||||
|
export default new Dispatcher(); |
@ -0,0 +1,46 @@ |
|||||||
|
import request from 'reqwest'; |
||||||
|
import when from 'when'; |
||||||
|
import {LOGIN_URL, SIGNUP_URL} from '../constants/LoginConstants'; |
||||||
|
import LoginActions from '../actions/LoginActions'; |
||||||
|
|
||||||
|
class AuthService { |
||||||
|
|
||||||
|
login(username, password) { |
||||||
|
return this.handleAuth(when(request({ |
||||||
|
url: LOGIN_URL, |
||||||
|
method: 'POST', |
||||||
|
crossOrigin: true, |
||||||
|
type: 'json', |
||||||
|
data: { |
||||||
|
username, password |
||||||
|
} |
||||||
|
}))); |
||||||
|
} |
||||||
|
|
||||||
|
logout() { |
||||||
|
LoginActions.logoutUser(); |
||||||
|
} |
||||||
|
|
||||||
|
signup(username, password, extra) { |
||||||
|
return this.handleAuth(when(request({ |
||||||
|
url: SIGNUP_URL, |
||||||
|
method: 'POST', |
||||||
|
crossOrigin: true, |
||||||
|
type: 'json', |
||||||
|
data: { |
||||||
|
username, password, extra |
||||||
|
} |
||||||
|
}))); |
||||||
|
} |
||||||
|
|
||||||
|
handleAuth(loginPromise) { |
||||||
|
return loginPromise |
||||||
|
.then(function(response) { |
||||||
|
var jwt = response.token; |
||||||
|
LoginActions.loginUser(jwt); |
||||||
|
return true; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default new AuthService() |
@ -0,0 +1,27 @@ |
|||||||
|
import request from 'reqwest'; |
||||||
|
import when from 'when'; |
||||||
|
import {QUOTE_URL} from '../constants/QuoteConstants'; |
||||||
|
import QuoteActions from '../actions/QuoteActions'; |
||||||
|
import LoginStore from '../stores/LoginStore.js'; |
||||||
|
|
||||||
|
class QuoteService { |
||||||
|
|
||||||
|
nextQuote() { |
||||||
|
|
||||||
|
request({ |
||||||
|
url: QUOTE_URL, |
||||||
|
method: 'GET', |
||||||
|
crossOrigin: true, |
||||||
|
headers: { |
||||||
|
'Authorization': 'Bearer ' + LoginStore.jwt |
||||||
|
} |
||||||
|
}) |
||||||
|
.then(function(response) { |
||||||
|
console.log(response); |
||||||
|
QuoteActions.gotQuote(response); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
export default new QuoteService() |
@ -0,0 +1,5 @@ |
|||||||
|
var _router = null; |
||||||
|
export default { |
||||||
|
set: (router) => _router = router, |
||||||
|
get: () => _router |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { EventEmitter } from 'events'; |
||||||
|
import AppDispatcher from '../dispatchers/AppDispatcher'; |
||||||
|
|
||||||
|
export default class BaseStore extends EventEmitter { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
subscribe(actionSubscribe) { |
||||||
|
this._dispatchToken = AppDispatcher.register(actionSubscribe()); |
||||||
|
} |
||||||
|
|
||||||
|
get dispatchToken() { |
||||||
|
return this._dispatchToken; |
||||||
|
} |
||||||
|
|
||||||
|
emitChange() { |
||||||
|
this.emit('CHANGE'); |
||||||
|
} |
||||||
|
|
||||||
|
addChangeListener(cb) { |
||||||
|
this.on('CHANGE', cb) |
||||||
|
} |
||||||
|
|
||||||
|
removeChangeListener(cb) { |
||||||
|
this.removeListener('CHANGE', cb); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import {LOGIN_USER, LOGOUT_USER} from '../constants/LoginConstants'; |
||||||
|
import BaseStore from './BaseStore'; |
||||||
|
import jwt_decode from 'jwt-decode'; |
||||||
|
|
||||||
|
|
||||||
|
class LoginStore extends BaseStore { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
this.subscribe(() => this._registerToActions.bind(this)) |
||||||
|
this._user = null; |
||||||
|
this._jwt = null; |
||||||
|
} |
||||||
|
|
||||||
|
_registerToActions(action) { |
||||||
|
switch(action.actionType) { |
||||||
|
case LOGIN_USER: |
||||||
|
this._jwt = action.jwt; |
||||||
|
this._user = jwt_decode(this._jwt); |
||||||
|
this.emitChange(); |
||||||
|
break; |
||||||
|
case LOGOUT_USER: |
||||||
|
this._user = null; |
||||||
|
this.emitChange(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
get user() { |
||||||
|
return this._user; |
||||||
|
} |
||||||
|
|
||||||
|
get jwt() { |
||||||
|
return this._jwt; |
||||||
|
} |
||||||
|
|
||||||
|
isLoggedIn() { |
||||||
|
return !!this._user; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default new LoginStore(); |
@ -0,0 +1,33 @@ |
|||||||
|
import {QUOTE_GET} from '../constants/QuoteConstants'; |
||||||
|
import {LOGOUT_USER} from '../constants/LoginConstants'; |
||||||
|
import BaseStore from './BaseStore'; |
||||||
|
|
||||||
|
class QuoteStore extends BaseStore { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
this.subscribe(() => this._registerToActions.bind(this)) |
||||||
|
this._quote = ''; |
||||||
|
} |
||||||
|
|
||||||
|
_registerToActions(action) { |
||||||
|
switch(action.actionType) { |
||||||
|
case QUOTE_GET: |
||||||
|
this._quote = action.quote; |
||||||
|
this.emitChange(); |
||||||
|
break; |
||||||
|
case LOGOUT_USER: |
||||||
|
this._quote = null; |
||||||
|
this.emitChange(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
get quote() { |
||||||
|
return this._quote; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default new QuoteStore(); |
Loading…
Reference in new issue