Add basic UI
This includes the login form, registration, and a simple nav bar when you're logged in. I'm just sort of figuring all this stuff out
This commit is contained in:
parent
ca2b5cdabb
commit
867df0fc57
|
@ -0,0 +1,13 @@
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import * as ActionsSession from 'vanth/actions/session';
|
||||||
|
import * as ActionsURL from 'vanth/actions/url';
|
||||||
|
import * as ActionsUser from 'vanth/actions/user';
|
||||||
|
|
||||||
|
import RootStore from 'vanth/store/root';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Session : bindActionCreators(ActionsSession, RootStore.dispatch),
|
||||||
|
URL : bindActionCreators(ActionsURL, RootStore.dispatch),
|
||||||
|
User : bindActionCreators(ActionsUser, RootStore.dispatch),
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import * as ActionTools from 'vanth/actions/tools';
|
||||||
|
|
||||||
|
export function createSession(username, password, nextPath=null) {
|
||||||
|
const payload = {
|
||||||
|
password: password,
|
||||||
|
username: username,
|
||||||
|
};
|
||||||
|
return ActionTools.fetchAndDispatch(
|
||||||
|
'/session/',
|
||||||
|
'SESSION_POST_BEGIN',
|
||||||
|
'SESSION_POST_COMPLETE',
|
||||||
|
'SESSION_POST_ERROR',
|
||||||
|
ActionTools.Methods.POST,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get() {
|
||||||
|
return ActionTools.fetchAndDispatch(
|
||||||
|
'/session/',
|
||||||
|
'SESSION_GET_BEGIN',
|
||||||
|
'SESSION_GET_COMPLETE',
|
||||||
|
'SESSION_GET_ERROR'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout(uri) {
|
||||||
|
return ActionTools.fetchAndDispatch(
|
||||||
|
uri,
|
||||||
|
'SESSION_DELETE_BEGIN',
|
||||||
|
'SESSION_DELETE_COMPLETE',
|
||||||
|
'SESSION_DELETE_ERROR',
|
||||||
|
ActionTools.Methods.DELETE,
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import Config from 'vanth/config';
|
||||||
|
import { ActionType } from 'vanth/constants';
|
||||||
|
import * as Fetch from 'vanth/fetch';
|
||||||
|
|
||||||
|
export const Methods = {
|
||||||
|
DELETE : 'delete',
|
||||||
|
GET : 'get',
|
||||||
|
PATCH : 'patch',
|
||||||
|
POST : 'post',
|
||||||
|
PUT : 'put',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function action(type) {
|
||||||
|
return function(data) {
|
||||||
|
let action = {
|
||||||
|
type: ActionType[type],
|
||||||
|
}
|
||||||
|
if(data != undefined) {
|
||||||
|
action.data = data;
|
||||||
|
}
|
||||||
|
if(!action.type) {
|
||||||
|
throw new Error(`An action type is required. Could not find an action type constant for ${type}`);
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensureConstantAction(constant) {
|
||||||
|
if(typeof constant === "string") {
|
||||||
|
if(!ActionType[constant]) {
|
||||||
|
let message = `${constant} is not a valid constant - you'll need to add it to constants.js`;
|
||||||
|
console.error(message)
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
return action(constant);
|
||||||
|
}
|
||||||
|
return constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchAndDispatch(url, start, end, failed, method=Methods.GET, payload) {
|
||||||
|
let actionStart = ensureConstantAction(start);
|
||||||
|
let actionEnd = ensureConstantAction(end);
|
||||||
|
let actionFailed = ensureConstantAction(failed);
|
||||||
|
|
||||||
|
if(!Fetch[method]) {
|
||||||
|
throw new Error(`Invalid method for fetcher: ${method}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!payload && (method === Methods.POST || method === Methods.PUT)) {
|
||||||
|
throw new Error(`A payload is required for a ${method} method`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionData = {};
|
||||||
|
if(method === Methods.PUT || method === Methods.DELETE) {
|
||||||
|
actionData.uri = url;
|
||||||
|
} else {
|
||||||
|
actionData.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispatch => {
|
||||||
|
dispatch(actionStart(actionData));
|
||||||
|
let fullURL = url.indexOf('://') >= 0 ? url : Config.API + url;
|
||||||
|
return Fetch[method].call(this, fullURL, payload)
|
||||||
|
.then(response => {
|
||||||
|
let result = response.json;
|
||||||
|
switch(method) {
|
||||||
|
case Methods.GET:
|
||||||
|
break;
|
||||||
|
case Methods.POST:
|
||||||
|
result = _.assign({}, payload, {
|
||||||
|
uri: response.headers.get('Location')
|
||||||
|
}, actionData);
|
||||||
|
break;
|
||||||
|
case Methods.PUT:
|
||||||
|
result = _.assign({}, payload, {
|
||||||
|
uri: url
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case Methods.DELETE:
|
||||||
|
result = actionData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let eventType = {
|
||||||
|
[Methods.DELETE] : 'RESOURCE_DELETE',
|
||||||
|
[Methods.GET] : 'RESOURCE_GET',
|
||||||
|
[Methods.PUT] : 'RESOURCE_PUT',
|
||||||
|
[Methods.POST] : 'RESOURCE_POST',
|
||||||
|
}[method];
|
||||||
|
dispatch(ensureConstantAction(eventType)(result));
|
||||||
|
dispatch(actionEnd(result));
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.catch(data => {
|
||||||
|
actionData.errors = data.errors ? data.errors : [data];
|
||||||
|
dispatch(actionFailed(actionData));
|
||||||
|
throw data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { ActionType } from 'vanth/constants';
|
||||||
|
|
||||||
|
export function change(oldURL, newURL) {
|
||||||
|
return {
|
||||||
|
type : ActionType.URL_CHANGE,
|
||||||
|
data : {
|
||||||
|
oldURL : oldURL,
|
||||||
|
newURL : newURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replace(newURL) {
|
||||||
|
let oldURL = window.location.href;
|
||||||
|
history.replaceState(null, '', '#' + newURL);
|
||||||
|
return change(oldURL, window.location.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function navigate(newURL, query) {
|
||||||
|
let oldURL = window.location.href;
|
||||||
|
let url = '#' + newURL;
|
||||||
|
if(query) {
|
||||||
|
url = "?nextPath=" + query + url;
|
||||||
|
} else {
|
||||||
|
url = "/" + url;
|
||||||
|
}
|
||||||
|
history.pushState(null, '', url);
|
||||||
|
return change(oldURL, window.location.href);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import * as ActionTools from 'vanth/actions/tools';
|
||||||
|
|
||||||
|
export function register(name, username, password) {
|
||||||
|
const payload = {
|
||||||
|
name : name,
|
||||||
|
password: password,
|
||||||
|
username: username,
|
||||||
|
}
|
||||||
|
return ActionTools.fetchAndDispatch(
|
||||||
|
'/user/',
|
||||||
|
'USER_REGISTER_BEGIN',
|
||||||
|
'USER_REGISTER_COMPLETE',
|
||||||
|
'USER_REGISTER_ERROR',
|
||||||
|
ActionTools.Methods.POST,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { connect, Provider } from 'react-redux';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import Actions from 'vanth/actions/root';
|
||||||
|
import RootStore from 'vanth/store/root';
|
||||||
|
import Routes from 'vanth/routes';
|
||||||
|
|
||||||
|
const App = connect(state => state)(React.createClass({
|
||||||
|
componentWillMount: function() {
|
||||||
|
window.onhashchange = function(event) {
|
||||||
|
if(!event) return;
|
||||||
|
Actions.URL.change(event.oldURL, event.newURL || window.location.hash);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
let allProps = _.assign({}, this.props, this.state);
|
||||||
|
return (
|
||||||
|
<Routes {...allProps}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
ReactDOM.render((
|
||||||
|
<Provider store={RootStore}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
), document.getElementById('container'));
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
let thing = Actions.Session.get()
|
||||||
|
thing.then(session => {
|
||||||
|
console.log(session);
|
||||||
|
}).catch(error => {
|
||||||
|
//Actions.URL.navigate('/login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
module.exports = App;
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
API: 'http://www.vanth.com',
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
var make_constants = function(constants) {
|
||||||
|
let result = {};
|
||||||
|
for(var i = 0; i < constants.length; i++) {
|
||||||
|
let constant = constants[i];
|
||||||
|
result[constant] = constant;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActionType = make_constants([
|
||||||
|
'RESOURCE_DELETE',
|
||||||
|
'RESOURCE_GET',
|
||||||
|
'RESOURCE_POST',
|
||||||
|
'RESOURCE_PUT',
|
||||||
|
|
||||||
|
'SESSION_DELETE_BEGIN',
|
||||||
|
'SESSION_DELETE_COMPLETE',
|
||||||
|
'SESSION_DELETE_ERROR',
|
||||||
|
'SESSION_GET_BEGIN',
|
||||||
|
'SESSION_GET_COMPLETE',
|
||||||
|
'SESSION_GET_ERROR',
|
||||||
|
'SESSION_POST_BEGIN',
|
||||||
|
'SESSION_POST_COMPLETE',
|
||||||
|
'SESSION_POST_ERROR',
|
||||||
|
|
||||||
|
'URL_CHANGE',
|
||||||
|
'URL_NAVIGATE',
|
||||||
|
'URL_REPLACE',
|
||||||
|
|
||||||
|
'USER_REGISTER_BEGIN',
|
||||||
|
'USER_REGISTER_COMPLETE',
|
||||||
|
'USER_REGISTER_ERROR',
|
||||||
|
]);
|
|
@ -0,0 +1,33 @@
|
||||||
|
import * as BS from 'react-bootstrap';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import * as Actions from 'vanth/actions/root';
|
||||||
|
|
||||||
|
var Dashboard = React.createClass({
|
||||||
|
logout: function() {
|
||||||
|
Actions.Session.logout(this.props.session.uri).then(() => {
|
||||||
|
Actions.URL.navigate('/login');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div className='container-fluid'>
|
||||||
|
<BS.Navbar>
|
||||||
|
<BS.Navbar.Header>
|
||||||
|
<BS.Navbar.Brand>
|
||||||
|
<a href="/">Vanth</a>
|
||||||
|
</BS.Navbar.Brand>
|
||||||
|
</BS.Navbar.Header>
|
||||||
|
<BS.Nav>
|
||||||
|
<BS.NavItem eventKey={1} href="/thing">Thing</BS.NavItem>
|
||||||
|
<BS.NavDropdown eventKey={2} title="Account" id="account">
|
||||||
|
<BS.MenuItem eventKey={2.1} onClick={this.logout}>Logout</BS.MenuItem>
|
||||||
|
</BS.NavDropdown>
|
||||||
|
</BS.Nav>
|
||||||
|
</BS.Navbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Dashboard
|
|
@ -0,0 +1,107 @@
|
||||||
|
function FetchError(url, status, errors) {
|
||||||
|
this.errors = errors;
|
||||||
|
this.message = `Status code ${status} was returned from ${url}`;
|
||||||
|
this.name = 'FetchError';
|
||||||
|
this.status = status;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _handleResults = function(resolve, reject, url, fetchRequest) {
|
||||||
|
fetchRequest
|
||||||
|
.then(response => {
|
||||||
|
if(response.status == 204) {
|
||||||
|
resolve({
|
||||||
|
headers : response.headers,
|
||||||
|
json : null,
|
||||||
|
text : '',
|
||||||
|
});
|
||||||
|
} else if(response.status >= 400) {
|
||||||
|
console.error(`The request to ${url} failed with status ${response.status}`, response);
|
||||||
|
response.json()
|
||||||
|
.then(json => {
|
||||||
|
reject(new FetchError(url, response.status, json.errors));
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
} else {
|
||||||
|
if(response.headers.get('Content-Type') == 'application/json') {
|
||||||
|
response.json()
|
||||||
|
.then(json => {
|
||||||
|
resolve({
|
||||||
|
headers : response.headers,
|
||||||
|
json : json,
|
||||||
|
text : null,
|
||||||
|
});
|
||||||
|
}).catch(reject);
|
||||||
|
} else {
|
||||||
|
response.text()
|
||||||
|
.then(text => {
|
||||||
|
resolve({
|
||||||
|
headers : response.headers,
|
||||||
|
json : null,
|
||||||
|
text : text,
|
||||||
|
});
|
||||||
|
}).catch(reject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if(error instanceof Error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
console.error("Unrecognized error raised when fetching", error);
|
||||||
|
reject(new Error("Unknown error occurred during fetch"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get(url) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
_handleResults(resolve, reject, url,
|
||||||
|
fetch(url, {
|
||||||
|
credentials : 'include',
|
||||||
|
method : 'GET',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function post(url, data) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
_handleResults(resolve, reject, url,
|
||||||
|
fetch(url, {
|
||||||
|
credentials : 'include',
|
||||||
|
headers : {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
method : 'POST',
|
||||||
|
body : JSON.stringify(data),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function put(url, data) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
_handleResults(resolve, reject, url,
|
||||||
|
fetch(url, {
|
||||||
|
credentials : 'include',
|
||||||
|
headers : {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
method : 'PUT',
|
||||||
|
body : JSON.stringify(data),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.delete = function(url) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
_handleResults(resolve, reject, url,
|
||||||
|
fetch(url, {
|
||||||
|
credentials : 'include',
|
||||||
|
method : 'DELETE',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React from 'react';
|
||||||
|
import * as BS from 'react-bootstrap';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import Actions from 'vanth/actions/root';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
password : null,
|
||||||
|
username : null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange: function(parameter) {
|
||||||
|
return (e) => {
|
||||||
|
this.setState({
|
||||||
|
[parameter] : e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Actions.Session.createSession(
|
||||||
|
this.state.username,
|
||||||
|
this.state.password,
|
||||||
|
this.props.url.search.nextPath || "/"
|
||||||
|
).then(result => {
|
||||||
|
Actions.Session.get();
|
||||||
|
Actions.URL.navigate('/');
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const forgotPassword = <a href="#/forgot" className="pull-right"><small>Forgot Password</small></a>;
|
||||||
|
const pending = false;
|
||||||
|
return (
|
||||||
|
<BS.Grid>
|
||||||
|
<BS.Row>
|
||||||
|
<BS.Col xs={8} xsOffset={2}>
|
||||||
|
<h3 className="primary">Login</h3>
|
||||||
|
<hr />
|
||||||
|
<form onSubmit={this.handleSubmit} className='form-horizontal'>
|
||||||
|
<BS.FormGroup controlId="login">
|
||||||
|
<BS.ControlLabel>Username</BS.ControlLabel>
|
||||||
|
<BS.FormControl
|
||||||
|
disabled={pending}
|
||||||
|
onChange={this.handleChange('username')}
|
||||||
|
placeholder='Username'
|
||||||
|
required
|
||||||
|
type='text'
|
||||||
|
wrapperClassName='col-xs-10'
|
||||||
|
/>
|
||||||
|
<BS.ControlLabel>Password</BS.ControlLabel>
|
||||||
|
<BS.FormControl
|
||||||
|
disabled={pending}
|
||||||
|
onChange={this.handleChange('password')}
|
||||||
|
placeholder='Password'
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
wrapperClassName='col-xs-10'
|
||||||
|
/>
|
||||||
|
<BS.Button bsStyle='primary' type='submit' className='col-xs-3 col-xs-offset-2' disabled={pending}>Login</BS.Button>
|
||||||
|
<BS.Button bsStyle='link' className='col-xs-1' href="#/register">Register</BS.Button>
|
||||||
|
</BS.FormGroup>
|
||||||
|
</form>
|
||||||
|
</BS.Col>
|
||||||
|
</BS.Row>
|
||||||
|
</BS.Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { applyMiddleware, createStore } from 'redux';
|
||||||
|
|
||||||
|
import thunkMiddleware from 'redux-thunk';
|
||||||
|
import createLogger from 'redux-logger';
|
||||||
|
|
||||||
|
const loggerMiddleware = createLogger();
|
||||||
|
var createStoreWithMiddleware = applyMiddleware(
|
||||||
|
thunkMiddleware, // lets us dispatch() functions
|
||||||
|
loggerMiddleware // neat middleware that logs actions
|
||||||
|
)(createStore);
|
||||||
|
|
||||||
|
module.exports = createStoreWithMiddleware;
|
|
@ -0,0 +1,87 @@
|
||||||
|
import React from 'react';
|
||||||
|
import * as BS from 'react-bootstrap';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
|
import Actions from 'vanth/actions/root';
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
getInitialState() {
|
||||||
|
return {
|
||||||
|
name : null,
|
||||||
|
password : null,
|
||||||
|
username : null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChange: function(parameter) {
|
||||||
|
return (e) => {
|
||||||
|
this.setState({
|
||||||
|
[parameter] : e.target.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Actions.User.register(
|
||||||
|
this.state.name,
|
||||||
|
this.state.username,
|
||||||
|
this.state.password,
|
||||||
|
this.props.url.search.nextPath || "/"
|
||||||
|
).then(result => {
|
||||||
|
Actions.Session.get();
|
||||||
|
Actions.URL.navigate('/');
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const pending = false;
|
||||||
|
return (
|
||||||
|
<BS.Grid>
|
||||||
|
<BS.Row>
|
||||||
|
<BS.Col xs={8} xsOffset={2}>
|
||||||
|
<h3 className="primary">Vanth - register a new user</h3>
|
||||||
|
<hr />
|
||||||
|
<form onSubmit={this.handleSubmit} className='form-horizontal'>
|
||||||
|
<BS.FormGroup controlId="register">
|
||||||
|
<BS.ControlLabel>Name</BS.ControlLabel>
|
||||||
|
<BS.FormControl
|
||||||
|
disabled={pending}
|
||||||
|
onChange={this.handleChange('name')}
|
||||||
|
placeholder='Name'
|
||||||
|
required
|
||||||
|
type='text'
|
||||||
|
wrapperClassName='col-xs-10'
|
||||||
|
/>
|
||||||
|
<BS.ControlLabel>Username</BS.ControlLabel>
|
||||||
|
<BS.FormControl
|
||||||
|
disabled={pending}
|
||||||
|
onChange={this.handleChange('username')}
|
||||||
|
placeholder='Username'
|
||||||
|
required
|
||||||
|
type='text'
|
||||||
|
wrapperClassName='col-xs-10'
|
||||||
|
/>
|
||||||
|
<BS.ControlLabel>Password</BS.ControlLabel>
|
||||||
|
<BS.FormControl
|
||||||
|
disabled={pending}
|
||||||
|
onChange={this.handleChange('password')}
|
||||||
|
placeholder='Password'
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
wrapperClassName='col-xs-10'
|
||||||
|
/>
|
||||||
|
<BS.Button bsStyle='primary' type='submit' className='col-xs-3 col-xs-offset-2' disabled={pending}>Register</BS.Button>
|
||||||
|
<BS.Button bsStyle='link' className='col-xs-1' href="#/login">Login</BS.Button>
|
||||||
|
</BS.FormGroup>
|
||||||
|
</form>
|
||||||
|
</BS.Col>
|
||||||
|
</BS.Row>
|
||||||
|
</BS.Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PathToRegexp from 'path-to-regexp';
|
||||||
|
|
||||||
|
import Dashboard from 'vanth/dashboard';
|
||||||
|
import Login from 'vanth/login';
|
||||||
|
import Register from 'vanth/register';
|
||||||
|
|
||||||
|
const Router = React.createClass({
|
||||||
|
routes: {
|
||||||
|
"/" : Dashboard,
|
||||||
|
"/login" : Login,
|
||||||
|
"/register" : Register,
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var toRender = null;
|
||||||
|
for(var path in this.routes) {
|
||||||
|
var element = this.routes[path];
|
||||||
|
var keys = [];
|
||||||
|
var pattern = PathToRegexp(path, keys);
|
||||||
|
var match = pattern.exec(this.props.hash);
|
||||||
|
if(match) {
|
||||||
|
if(!!toRender) {
|
||||||
|
console.warn("Matched more than one route. First route was", toRender.path, " this match is ", path);
|
||||||
|
}
|
||||||
|
var route = {};
|
||||||
|
for(var i = 0; i < keys.length; i++) {
|
||||||
|
let key = keys[i];
|
||||||
|
route[key.name] = match[i+1];
|
||||||
|
}
|
||||||
|
var props = _.assign({}, this.props, {route: route});
|
||||||
|
toRender = {
|
||||||
|
element : React.createElement(element, props),
|
||||||
|
path : path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!toRender) {
|
||||||
|
return (
|
||||||
|
<div className="router">
|
||||||
|
<p>You seem to have reached a link that doesn't go anywhere. Maybe you want <a href="#/">to go back to the beginning?</a></p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="router">
|
||||||
|
{toRender.element}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Routes = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var hash = this.props.url.location.hash.substr(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router hash={hash} {...this.props}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Routes
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import createStoreWithMiddleware from 'vanth/middleware';
|
||||||
|
|
||||||
|
import SessionReducer from 'vanth/store/session';
|
||||||
|
import URLReducer from 'vanth/store/url';
|
||||||
|
|
||||||
|
const root = combineReducers({
|
||||||
|
session : SessionReducer,
|
||||||
|
url : URLReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = createStoreWithMiddleware(root);
|
||||||
|
|
||||||
|
module.exports = store;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
import * as Constants from 'vanth/constants';
|
||||||
|
|
||||||
|
const emptyState = {
|
||||||
|
name : null,
|
||||||
|
username : null,
|
||||||
|
uri : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
var reducer = function(state = emptyState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case Constants.ActionType.SESSION_GET_COMPLETE:
|
||||||
|
return _.assign({}, state, action.data);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = reducer;
|
|
@ -0,0 +1,34 @@
|
||||||
|
import urllite from 'urllite';
|
||||||
|
|
||||||
|
import { ActionType } from 'vanth/constants';
|
||||||
|
|
||||||
|
let _parseSearch = function(location) {
|
||||||
|
let search = {};
|
||||||
|
let query = location.search.substring(1);
|
||||||
|
let vars = query.split('&');
|
||||||
|
for(var i = 0; i < vars.length; i++) {
|
||||||
|
var pair = vars[i].split('=');
|
||||||
|
search[pair[0]] = decodeURIComponent(pair[1]);
|
||||||
|
}
|
||||||
|
return search;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
location : urllite(window.location),
|
||||||
|
search : _parseSearch(urllite(window.location)),
|
||||||
|
};
|
||||||
|
|
||||||
|
var reducer = function(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionType.URL_CHANGE:
|
||||||
|
let location = urllite(action.data.newURL);
|
||||||
|
return _.assign({}, state, {
|
||||||
|
location: location,
|
||||||
|
search: _parseSearch(location)
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = reducer;
|
Loading…
Reference in New Issue