front/auth+users: implement talking to backend
This commit is contained in:
parent
2e4e77791d
commit
5a0ce0b49d
11 changed files with 177 additions and 31 deletions
|
@ -15,6 +15,7 @@
|
|||
"ini": "^1.3.4",
|
||||
"merge": "^1.2.0",
|
||||
"page": "^1.7.1",
|
||||
"superagent": "^1.8.3",
|
||||
"uglify-js": "git://github.com/mishoo/UglifyJS2.git#harmony"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
--text-color: #111;
|
||||
--inactive-link-color: #888;
|
||||
--line-color: #DDD;
|
||||
--message-error-border-color: #FCC;
|
||||
--message-error-background-color: #FFF5F5;
|
||||
--input-bad-border-color: #FCC;
|
||||
--input-bad-background-color: #FFF5F5;
|
||||
--input-good-border-color: #D3E3D3;
|
||||
|
@ -91,3 +93,11 @@ nav.text-nav ul li.active a {
|
|||
#top-nav ul li[data-name=help] {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.messages .message {
|
||||
padding: 0.5em;
|
||||
}
|
||||
.message.error {
|
||||
border: 1px solid var(--message-error-border-color);
|
||||
background: var(--message-error-background-color);
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<ul>
|
||||
<li>
|
||||
<label for='user-name'>User name</label>
|
||||
<input id='user-name' name='user-name' type='text' required/>
|
||||
<input id='user-name' name='name' type='text' required/>
|
||||
</li>
|
||||
<li>
|
||||
<label for='user-password'>Password</label>
|
||||
<input id='user-password' name='user-password' type='password' required/>
|
||||
<input id='user-password' name='password' type='password' required/>
|
||||
</li>
|
||||
<li>
|
||||
<input id='remember-user' name='remember-user' type='checkbox'/>
|
||||
|
@ -17,6 +17,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset class='messages'></fieldset>
|
||||
<fieldset class='buttons'>
|
||||
<input type='submit' value='Log in'/>
|
||||
<a>Forgot the password?</a>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset class='messages'></fieldset>
|
||||
<fieldset class='buttons'>
|
||||
<input type='submit' value='Create an account'/>
|
||||
</fieldset>
|
||||
|
|
39
static/js/api.js
Normal file
39
static/js/api.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
const request = require('superagent');
|
||||
const config = require('./config.js');
|
||||
|
||||
class Api {
|
||||
get(url) {
|
||||
const fullUrl = this.getFullUrl(url);
|
||||
return this.process(fullUrl, () => request.get(fullUrl));
|
||||
}
|
||||
|
||||
post(url, data) {
|
||||
const fullUrl = this.getFullUrl(url);
|
||||
return this.process(fullUrl, () => request.post(fullUrl).send(data));
|
||||
}
|
||||
|
||||
process(url, requestFactory) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = requestFactory();
|
||||
if (this.userName && this.userPassword) {
|
||||
req.auth(this.userName, this.userPassword);
|
||||
}
|
||||
req.set('Accept', 'application/json')
|
||||
.end((error, response) => {
|
||||
if (error) {
|
||||
reject(response.body);
|
||||
} else {
|
||||
resolve(response.body);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getFullUrl(url) {
|
||||
return (config.basic.apiUrl + '/' + url).replace(/([^:])\/+/g, '$1/');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Api;
|
|
@ -1,10 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const page = require('page');
|
||||
const config = require('../config.js');
|
||||
|
||||
class AuthController {
|
||||
constructor(topNavigationController, loginView) {
|
||||
constructor(api, topNavigationController, loginView) {
|
||||
this.api = api;
|
||||
this.topNavigationController = topNavigationController;
|
||||
this.loginView = loginView;
|
||||
this.currentUser = null;
|
||||
/* TODO: load from cookies */
|
||||
}
|
||||
|
||||
isLoggedIn() {
|
||||
|
@ -15,20 +20,40 @@ class AuthController {
|
|||
return true;
|
||||
}
|
||||
|
||||
login(user) {
|
||||
this.currentUser = user;
|
||||
login(userName, userPassword) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.api.userName = userName;
|
||||
this.api.userPassword = userPassword;
|
||||
this.api.get('/user/' + userName)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
logout(user) {
|
||||
this.currentUser = null;
|
||||
this.api.userName = null;
|
||||
this.api.userPassword = null;
|
||||
/* TODO: clear cookie */
|
||||
}
|
||||
|
||||
loginRoute() {
|
||||
this.topNavigationController.activate('login');
|
||||
this.loginView.render({
|
||||
login: (user, password) => {
|
||||
alert(user, password);
|
||||
//self.authController.login(user);
|
||||
login: (userName, userPassword, doRemember) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this
|
||||
.login(userName, userPassword)
|
||||
.then(response => {
|
||||
if (doRemember) {
|
||||
/* TODO: set cookie */
|
||||
}
|
||||
resolve();
|
||||
page('/');
|
||||
/* TODO: update top navigation */
|
||||
})
|
||||
.catch(response => { reject(response.description); });
|
||||
});
|
||||
}});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
const page = require('page');
|
||||
|
||||
class UsersController {
|
||||
constructor(topNavigationController, authController, registrationView) {
|
||||
constructor(
|
||||
api, topNavigationController, authController, registrationView) {
|
||||
this.api = api;
|
||||
this.topNavigationController = topNavigationController;
|
||||
this.authController = authController;
|
||||
this.registrationView = registrationView;
|
||||
|
@ -12,12 +16,31 @@ class UsersController {
|
|||
}
|
||||
|
||||
createUserRoute() {
|
||||
const self = this;
|
||||
this.topNavigationController.activate('register');
|
||||
this.registrationView.render({
|
||||
register: (user) => {
|
||||
alert(user);
|
||||
self.authController.login(user);
|
||||
register: (userName, userPassword, userEmail) => {
|
||||
const data = {
|
||||
'name': userName,
|
||||
'password': userPassword,
|
||||
'email': userEmail
|
||||
};
|
||||
// TODO: reduce callback hell
|
||||
return new Promise((resolve, reject) => {
|
||||
this.api.post('/users/', data)
|
||||
.then(() => {
|
||||
this.authController.login(userName, userPassword)
|
||||
.then(() => {
|
||||
resolve();
|
||||
page('/');
|
||||
})
|
||||
.catch(response => {
|
||||
reject(response.description);
|
||||
});
|
||||
})
|
||||
.catch(response => {
|
||||
reject(response.description);
|
||||
});
|
||||
});
|
||||
}});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
const page = require('page');
|
||||
const handlebars = require('handlebars');
|
||||
|
||||
const Api = require('./api.js');
|
||||
const LoginView = require('./views/login_view.js');
|
||||
const RegistrationView = require('./views/registration_view.js');
|
||||
const TopNavigationView = require('./views/top_navigation_view.js');
|
||||
|
@ -24,11 +25,13 @@ const TagsController = require('./controllers/tags_controller.js');
|
|||
// -------------------
|
||||
// - resolve objects -
|
||||
// -------------------
|
||||
const api = new Api();
|
||||
|
||||
const topNavigationView = new TopNavigationView(handlebars);
|
||||
const loginView = new LoginView(handlebars);
|
||||
const registrationView = new RegistrationView(handlebars);
|
||||
|
||||
const authController = new AuthController(null, loginView);
|
||||
const authController = new AuthController(api, null, loginView);
|
||||
const topNavigationController
|
||||
= new TopNavigationController(topNavigationView, authController);
|
||||
// break cyclic dependency topNavigationView<->authController
|
||||
|
@ -37,6 +40,7 @@ authController.topNavigationController = topNavigationController;
|
|||
const homeController = new HomeController(topNavigationController);
|
||||
const postsController = new PostsController(topNavigationController);
|
||||
const usersController = new UsersController(
|
||||
api,
|
||||
topNavigationController,
|
||||
authController,
|
||||
registrationView);
|
||||
|
@ -52,13 +56,13 @@ page('/', () => { homeController.indexRoute(); });
|
|||
|
||||
page('/upload', () => { postsController.uploadPostsRoute(); });
|
||||
page('/posts', () => { postsController.listPostsRoute(); });
|
||||
page('/post/:id', (id) => { postsController.showPostRoute(id); });
|
||||
page('/post/:id/edit', (id) => { postsController.editPostRoute(id); });
|
||||
page('/post/:id', id => { postsController.showPostRoute(id); });
|
||||
page('/post/:id/edit', id => { postsController.editPostRoute(id); });
|
||||
|
||||
page('/register', () => { usersController.createUserRoute(); });
|
||||
page('/users', () => { usersController.listUsersRoute(); });
|
||||
page('/user/:user', (user) => { usersController.showUserRoute(user); });
|
||||
page('/user/:user/edit', (user) => { usersController.editUserRoute(user); });
|
||||
page('/user/:user', user => { usersController.showUserRoute(user); });
|
||||
page('/user/:user/edit', user => { usersController.editUserRoute(user); });
|
||||
|
||||
page('/history', () => { historyController.showHistoryRoute(); });
|
||||
page('/tags', () => { tagsController.listTagsRoute(); });
|
||||
|
|
|
@ -19,14 +19,32 @@ class BaseView {
|
|||
return this.handlebars.compile(templateText);
|
||||
}
|
||||
|
||||
showError(messagesHolder, errorMessage) {
|
||||
/* TODO: animate this */
|
||||
const node = document.createElement('div');
|
||||
node.innerHTML = errorMessage;
|
||||
node.classList.add('message');
|
||||
node.classList.add('error');
|
||||
messagesHolder.appendChild(node);
|
||||
}
|
||||
|
||||
clearMessages(messagesHolder) {
|
||||
/* TODO: animate that */
|
||||
while (messagesHolder.lastChild) {
|
||||
messagesHolder.removeChild(messagesHolder.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
decorateValidator(form) {
|
||||
// postpone showing form fields validity until user actually tries
|
||||
// to submit it (seeing red/green form w/o doing anything breaks POLA)
|
||||
const submitButton
|
||||
= document.querySelector('#content-holder .buttons input');
|
||||
submitButton.addEventListener('click', (e) => {
|
||||
const submitButton = form.querySelector('.buttons input');
|
||||
submitButton.addEventListener('click', e => {
|
||||
form.classList.add('show-validation');
|
||||
});
|
||||
form.addEventListener('submit', e => {
|
||||
form.classList.remove('show-validation');
|
||||
});
|
||||
}
|
||||
|
||||
showView(html) {
|
||||
|
|
|
@ -11,17 +11,32 @@ class LoginView extends BaseView {
|
|||
|
||||
render(options) {
|
||||
this.showView(this.template());
|
||||
const form = document.querySelector('#content-holder form');
|
||||
const messagesHolder = this.contentHolder.querySelector('.messages');
|
||||
const form = this.contentHolder.querySelector('form');
|
||||
this.decorateValidator(form);
|
||||
|
||||
const userNameField = document.getElementById('user-name');
|
||||
const passwordField = document.getElementById('user-password');
|
||||
const rememberUserField = document.getElementById('remember-user');
|
||||
userNameField.setAttribute('pattern', config.service.userNameRegex);
|
||||
passwordField.setAttribute('pattern', config.service.passwordRegex);
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
options.login(userNameField.value, passwordField.value);
|
||||
this.clearMessages(messagesHolder);
|
||||
form.setAttribute('disabled', true);
|
||||
options
|
||||
.login(
|
||||
userNameField.value,
|
||||
passwordField.value,
|
||||
rememberUserField.checked)
|
||||
.then(() => {
|
||||
form.setAttribute('disabled', false);
|
||||
})
|
||||
.catch(errorMessage => {
|
||||
form.setAttribute('disabled', false);
|
||||
this.showError(messagesHolder, errorMessage);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ class RegistrationView extends BaseView {
|
|||
|
||||
render(options) {
|
||||
this.showView(this.template());
|
||||
const messagesHolder = this.contentHolder.querySelector('.messages');
|
||||
const form = document.querySelector('#content-holder form');
|
||||
this.decorateValidator(form);
|
||||
|
||||
|
@ -20,14 +21,22 @@ class RegistrationView extends BaseView {
|
|||
userNameField.setAttribute('pattern', config.service.userNameRegex);
|
||||
passwordField.setAttribute('pattern', config.service.passwordRegex);
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
form.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
const user = {
|
||||
name: userNameField.value,
|
||||
password: passwordField.value,
|
||||
email: emailField.value,
|
||||
};
|
||||
options.register(user);
|
||||
this.clearMessages(messagesHolder);
|
||||
form.setAttribute('disabled', true);
|
||||
options
|
||||
.register(
|
||||
userNameField.value,
|
||||
passwordField.value,
|
||||
emailField.value)
|
||||
.then(() => {
|
||||
form.setAttribute('disabled', false);
|
||||
})
|
||||
.catch(errorMessage => {
|
||||
form.setAttribute('disabled', false);
|
||||
this.showError(messagesHolder, errorMessage);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue