Fixed settings form styles

master
Keith Irwin 2017-04-09 23:40:08 -04:00
parent 861fb48bb6
commit 53bbbdf626
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
8 changed files with 232 additions and 115 deletions

View File

@ -50,13 +50,13 @@ You can get API keys at the [google developer's console](https://console.develop
## Running
```sh
$ npm start
$ node server.js
```
Or with [nodemon](https://nodemon.io/):
or
```sh
$ npm dev
$ npm start
```
## Contributing

View File

@ -1,26 +1,28 @@
'use strict';
const mw = require('./middleware.js'),
const
mw = require('./middleware.js'),
mail = require('./mail.js'),
User = require('./models.js').user,
env = require('./env.js');
module.exports = function(app, passport) {
// Methods for success and failure
var loginOutcome = {
const
loginOutcome = {
failureRedirect: '/login',
failureFlash: true
};
var connectOutcome = {
failureRedirect: '/account',
},
connectOutcome = {
failureRedirect: '/settings',
failureFlash: true
};
var loginCallback = function(req,res){
res.redirect( req.session.returnTo || '/settings' );
delete req.session.returnTo;
};
},
loginCallback = function(req,res){
res.redirect( req.session.returnTo || '/settings' );
delete req.session.returnTo;
};
// Login/-out
app.route('/login')
.get( function(req,res){
@ -115,7 +117,7 @@ module.exports = function(app, passport) {
});
});
// Social
app.get('/login/:service', function(req,res,next){
var service = req.params.service;

View File

@ -1,17 +1,25 @@
'use strict';
const mongoose = require('mongoose');
const mongoose = require('mongoose'),
unique = require('mongoose-unique-validator'),
bcrypt = require('bcrypt-nodejs');
const userSchema = new mongoose.Schema({
name: {type:String, required:true},
email: String,
email: {type:String, required:true},
slug: {type:String, required:true, unique:true},
requestId: String,
auth: {
password: String,
passToken: String,
tokenExpires: Date,
google: {type:String, unique:true},
facebook: {type:String, unique:true},
twitter: {type:String, unique:true},
},
isAdmin: {type:Boolean, required:true, default:false},
isPro: {type:Boolean, required:true, default:false},
created: Date,
lastLogin: Date,
googleID: {type:Number, unique:true},
settings: {
units: {type:String, default:'standard'},
defaultMap: {type:String, default:'road'},
@ -30,7 +38,38 @@ const userSchema = new mongoose.Schema({
spd: {type:Number, default:0}
},
sk32: {type:String, required:true, unique:true}
});
}).plugin(unique);
/* User methods */ {
// Generate hash for new password
userSchema.methods.generateHash = function(password, next) {
bcrypt.genSalt(8, function(err,salt){
if (err){ return next(err); }
bcrypt.hash(password, salt, null, next);
});
};
// Create password reset token
userSchema.methods.createToken = function(next){
var user = this;
require('crypto').randomBytes(16, function(err,buf){
if (err){ next(err,null); }
else {
user.auth.passToken = buf.toString('hex');
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null,user.auth.passToken);
}
});
};
// Check for valid password
userSchema.methods.validPassword = function(password, next) {
bcrypt.compare(password, this.auth.password, next);
};
}
module.exports = {
'user': mongoose.model('User', userSchema)

View File

@ -1,11 +1,13 @@
'use strict';
var vars = require('./env.js'),
User = require('./models.js').user,
const
LocalStrategy = require('passport-local').Strategy,
GoogleStrategy = require('passport-google-oauth20').Strategy,
FacebookStrategy = require('passport-facebook').Strategy,
TwitterStrategy = require('passport-twitter').Strategy;
TwitterStrategy = require('passport-twitter').Strategy,
env = require('./env.js'),
mw = require('./middleware.js'),
User = require('./models.js').user;
module.exports = function(passport) {
@ -87,28 +89,66 @@ module.exports = function(passport) {
// Social login
function socialLogin(req, service, profileId, done) {
if (!req.user) { // Log in
// Log in
if (!req.user) {
// console.log(`Logging in with ${service}.`);
var query = {};
query['auth.'+service] = profileId;
User.findOne(query, function (err, user) {
if (err){ return done(err); }
else if (!user){ return done(); }
else { return done(null, user); }
else if (!user){
// console.log('User not found.');
// Lazy update from old googleId field
if (service==='google') {
User.findOne({'googleID':parseInt(profileId)}, function(err,user){
// console.log(`searched for user with googleID ${profileId}`);
if (err){ mw.throwErr(err); }
if (user) {
// console.log(`Lazily updating schema for ${user.name}.`);
user.auth.google = profileId;
user.googleId = null;
user.save(function(err){
if (err){ mw.throwErr(err); }
return done(null, user);
});
} else {
req.flash('danger',`There's no user for that ${service} account. `);
return done();
}
});
} else {
req.flash('danger',`There's no user for that ${service} account. `);
return done();
}
}
else {
// console.log(`Found user: ${user}`);
return done(null, user);
}
});
} else {
}
// Connect account
else {
console.log(`Connecting ${service} account.`);
req.user.auth[service] = profileId;
req.user.save(function(err){
if (err){ return done(err); }
else { return done(null, req.user); }
});
}
}
// Google
passport.use('google', new GoogleStrategy({
clientID: vars.googleClientId,
clientSecret: vars.googleClientSecret,
callbackURL: vars.url+'/login/google/cb',
clientID: env.googleClientId,
clientSecret: env.googleClientSecret,
callbackURL: env.url+'/login/google/cb',
passReqToCallback: true
}, function(req, accessToken, refreshToken, profile, done) {
socialLogin(req, 'google', profile.id, done);
@ -117,9 +157,9 @@ module.exports = function(passport) {
// Facebook
passport.use('facebook', new FacebookStrategy({
clientID: vars.facebookAppId,
clientSecret: vars.facebookAppSecret,
callbackURL: vars.url+'/login/facebook/cb',
clientID: env.facebookAppId,
clientSecret: env.facebookAppSecret,
callbackURL: env.url+'/login/facebook/cb',
passReqToCallback: true
}, function(req, accessToken, refreshToken, profile, done) {
socialLogin(req, 'facebook', profile.id, done);
@ -128,9 +168,9 @@ module.exports = function(passport) {
// Twitter
passport.use(new TwitterStrategy({
consumerKey: vars.twitterConsumerKey,
consumerSecret: vars.twitterConsumerSecret,
callbackURL: vars.url+'/login/twitter/cb',
consumerKey: env.twitterConsumerKey,
consumerSecret: env.twitterConsumerSecret,
callbackURL: env.url+'/login/twitter/cb',
passReqToCallback: true
}, function(req, token, tokenSecret, profile, done) {
socialLogin(req, 'twitter', profile.id, done);

View File

@ -4,6 +4,7 @@
"description": "Tracks user's GPS location",
"main": "server.js",
"dependencies": {
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.17.1",
"connect-flash": "^0.1.1",
"cookie-parser": "^1.4.1",
@ -14,6 +15,7 @@
"moment": "^2.12.0",
"mongodb": "^2.1.4",
"mongoose": "^4.9.0",
"mongoose-unique-validator": "^1.0.5",
"node-jose": "^0.8.0",
"nodemailer": "^3.1.8",
"nunjucks": "^2.3.0",

View File

@ -86,8 +86,9 @@ pre {
word-wrap: break-word;
}
.hide { display:none }
.red { color: #fb6e3d; }
.hide { display: none !important; }
.red, .red:hover { color: #fb6e3d !important; }
.yellow, .yellow:hover { color: #fbc93d !important; }
.shadow {
-moz-box-shadow: .18vw .18vw .36vw #000;
-webkit-box-shadow: .18vw .18vw .36vw #000;

View File

@ -1,26 +1,62 @@
form {
margin: auto;
max-width: 600px;
}
.form-group {
display: flex;
justify-content: space-between;
margin: 8% 0;
}
/* Sizing */
form label {
font-size: 1.2em;
margin-right: 3%;
}
/* Input formatting */
form input, form textarea, form select {
-moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
-webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
color: #eee;
background-color: #202020;
background-color: rgba(255,255,255,0.1);
padding: 1% 1.5%;
border: 1px solid #666;
border-radius: .3vw;
}
form input:active, form textarea:active, form select:active,
form input:focus, form textarea:focus, form select:focus {
form input:active:not(.input-addon), form textarea:active, form select:active,
form input:focus:not(.input-addon), form textarea:focus, form select:focus {
outline: none;
border: 1px solid #fbc93d;
}
form .input-with-addon-group {
display: flex;
}
form .input-addon {
padding: 1% 0 1% 1.5%;
border-right-color: #202020;
border-right-color: rgba(102,102,102,0);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
-moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
-webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5);
}
form .input-with-addon {
padding: 1% 1.5% 1% 0;
border-left-color: #202020;
border-left-color: rgba(102,102,102,0);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
-moz-box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5);
-webkit-box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5);
box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5);
}
::-webkit-input-placeholder {
color: #666;
}:-moz-placeholder {
@ -42,12 +78,19 @@ form select {
inset 0 .11vw .52vw rgba(0,0,0,.4);
}
form select > option {
color: #000;
background: #222;
color: inherit;
}
form .radio {
min-width: 150px;
display: flex;
justify-content: space-between;
}
form input[type="checkbox"], form input[type="radio"] {
width: auto;
margin: 8px 8px 8px 0;
margin: 8px;
}
form input[type="checkbox"]:active, form input[type="radio"]:active,
form input[type="checkbox"]:focus, form input[type="radio"]:focus {
@ -56,4 +99,4 @@ form input[type="checkbox"]:focus, form input[type="radio"]:focus {
form .btn {
font-size: 1.5em;
}
}

View File

@ -1,75 +1,72 @@
{% extends 'templates/base.html' %}
{% block title %}{{super()}} | Settings{% endblock %}
{% block head %}
{{super()}}
<link rel="stylesheet" type="text/css" href="/static/css/form.css">
<style>
/*#slug-input {*/
/* padding-left: calc(146px + 1.6%);*/
/*}*/
</style>
{% endblock %}
{% block main %}
<section class='container'>
<h2>Settings</h2>
<script src="/static/js/validator.min.js"></script>
<form id='settings-form' class='col-lg-10 col-lg-offset-1 form-horizontal' data-toggle="validator" role="form" method="post">
<h1>Settings</h1>
<form id='settings-form' role="form" method="post">
<div id='name' class='form-group' title="This appears in your page's title. ">
<label class='control-label col-sm-2 col-lg-3' for="name">Name</label>
<div class='input-group col-xs-12 col-sm-10 col-lg-9'>
<input class='form-control' name="name" type="text" value="{{user.name}}"
maxlength="160" data-error="Invalid input"><br>
</div>
<div class='help-block with-errors col-xs-12 col-sm-10 col-sm-offset-2 col-lg-9 col-lg-offset-3'></div>
<div id='name' class='form-group' title="This appears in your page's title. ">
<label for="name">Name</label>
<input class='form-control' name="name" type="text" value="{{user.name}}" maxlength="160">
</div>
<div id='email' class='form-group' title="For account stuff, no dumb newsletters. ">
<label class='control-label col-sm-2 col-lg-3' for="email">Email</label>
<div class='input-group col-xs-12 col-sm-10 col-lg-9'>
<input class='form-control' name="email" type="email" value="{{user.email}}"
maxlength="160" data-error="Invalid input"><br>
</div>
<div class='help-block with-errors col-xs-12 col-sm-10 col-sm-offset-2 col-lg-9 col-lg-offset-3'></div>
<label for="email">Email</label>
<input class='form-control' name="email" type="email" value="{{user.email}}" maxlength="160">
</div>
<div id='slug' class='form-group' title="This is the URL which shows your location. Be careful whom you share it with! ">
<label class='control-label col-sm-2 col-lg-3' for="slug">URL</label>
<div class='input-group col-xs-12 col-sm-10 col-lg-9'>
<span class='input-group-addon'>tracman.org/map/</span>
<input class='form-control' type="text" name="slug" value="{{user.slug}}" required data-remote="/validate"
maxlength="160" data-remote-error="That URL is already taken. " data-error="Invalid input"><br>
</div>
<div class='help-block with-errors col-xs-12 col-sm-10 col-sm-offset-2 col-lg-9 col-lg-offset-3'></div>
</div>
<div id='units' class='form-group col-xs-12' title="Select standard units for feet and miles/hour. Select metric units if you are a commie. ">
<label class='control-label col-sm-4 col-lg-3' for="units">Units</label>
<div class='input-group col-sm-8 col-lg-9'>
<div class='radio-inline'><label>
<input type="radio" name="units" value="standard" {% if user.settings.units == 'standard' %}checked{% endif %}>
Standard
</label></div>
<div class='radio-inline'><label>
<input type="radio" name="units" value="metric" {% if user.settings.units == 'metric' %}checked{% endif %}>
Metric
</label></div>
<div id='slug' class='form-group' title="This is the URL which shows your location. Be careful whom you share it with! ">
<label for="slug">URL</label>
<div class='input-with-addon-group'>
<input type="text" class='input-addon' size="13" value="tracman.org/map/" disabled readonly>
<input type="text" class='input-with-addon' name="slug" value="{{user.slug}}" maxlength="160" required>
</div>
</div>
<div id='defaultMap' class='form-group col-xs-12' title="Shows whether to show a satellite image or standard google road map as the default on your page. Visitors will have the option to change this. Note that satellite images load slower. ">
<label class='control-label col-sm-4 col-lg-3' for="map">Default map</label>
<div class='input-group col-sm-8 col-lg-9'>
<div class='radio-inline'><label>
<div id='units' class='form-group' title="Select standard units for feet and miles/hour. Select metric units if you are a commie. ">
<label for="units">Units</label>
<div class='radio-group'>
<div class='radio'>
<label>Standard</label>
<input type="radio" name="units" value="standard" {% if user.settings.units == 'standard' %}checked{% endif %}>
</div>
<div class='radio'>
<label>Metric</label>
<input type="radio" name="units" value="metric" {% if user.settings.units == 'metric' %}checked{% endif %}>
</div>
</div>
</div>
<div id='defaultMap' class='form-group' title="Shows whether to show a satellite image or standard google road map as the default on your page. Visitors will have the option to change this. Note that satellite images load slower. ">
<label for="map">Default map</label>
<div class='radio-group'>
<div class='radio'>
<label>Road</label>
<input type="radio" name="map" value="road" {% if user.settings.defaultMap == 'road' %}checked{% endif %}>
Road
</label></div>
<div class='radio-inline'><label>
</div>
<div class='radio'>
<label>Satellite</label>
<input type="radio" name="map" value="sat" {% if user.settings.defaultMap == 'sat' %}checked{% endif %}>
Satellite
</label></div>
</div>
</div>
</div>
<div id='defaultZoom' class='form-group col-xs-12' title="Shows the initial map zoom level on your page. A higher number means more zoom. Note that the size of the viewing window will also have an effect on how much of the map a visitor can see. ">
<label class='control-label col-xs-6 col-sm-4 col-lg-3' for="map">Default zoom</label>
<div class='input-group col-xs-6 col-sm-8 col-lg-9'>
<select class='c-select' name="zoom">
<div id='defaultZoom' class='form-group' title="Shows the initial map zoom level on your page. A higher number means more zoom. Note that the size of the viewing window will also have an effect on how much of the map a visitor can see. ">
<label for="map">Default zoom</label>
<select name="zoom">
<option {% if user.settings.defaultZoom==1 %}selected {% endif %}value="1">1 World</option>
<option {% if user.settings.defaultZoom==2 %}selected {% endif %}value="2">2</option>
<option {% if user.settings.defaultZoom==3 %}selected {% endif %}value="3">3</option>
@ -91,35 +88,28 @@
<option {% if user.settings.defaultZoom==19 %}selected {% endif %}value="19">19</option>
<option {% if user.settings.defaultZoom==20 %}selected {% endif %}value="20">20 Buildings</option>
</select>
</div>
</div>
<div id='showSpeed' class='form-group col-xs-12' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows a spedometer on the map.">
<label class='control-label col-xs-6 col-sm-4 col-lg-3' for="showSpeed">Show speed{% if not user.isPro %} <span class='red'>(PRO)</span>{% endif %}</label>
<div class='input-group col-xs-6 col-sm-8 col-lg-9'>
<input class='form-control' name="showSpeed" type="checkbox" {% if not user.isPro %}disabled {% elif user.settings.showSpeed %}checked{% else %}{% endif %}><br>
</div>
<div id='showSpeed' class='form-group' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows a spedometer on the map.">
<label for="showSpeed">Show speed{% if not user.isPro %} <span class='red'>(PRO)</span>{% endif %}</label>
<input name="showSpeed" type="checkbox" {% if not user.isPro %}disabled {% elif user.settings.showSpeed %}checked{% else %}{% endif %}>
</div>
<div id='showAltitude' class='form-group col-xs-12' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows the current elevation on the map. ">
<label class='control-label col-xs-6 col-sm-4 col-lg-3' for="showAlt">Show altitude{% if not user.isPro %} <span class='red'>(PRO)</span>{% endif %}</label>
<div class='input-group col-xs-6 col-sm-8 col-lg-9'>
<input class='form-control' name="showAlt" type="checkbox" {% if not user.isPro %}disabled {% elif user.settings.showAlt %}checked{% else %}{% endif %}><br>
</div>
<div id='showAltitude' class='form-group' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows the current elevation on the map. ">
<label for="showAlt">Show altitude{% if not user.isPro %} <span class='red'>(PRO)</span>{% endif %}</label>
<input name="showAlt" type="checkbox" {% if not user.isPro %}disabled {% elif user.settings.showAlt %}checked{% else %}{% endif %}>
</div>
<div id='showStreet' class='form-group col-xs-12' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows a Google street view image at or near your current location, oriented in the direction of travel. ">
<label class='control-label col-xs-6 col-sm-4 col-lg-3' for="showStreet">Show street view{% if not user.isPro %} <span class='red'>(PRO)</span><br>{% endif %}</label>
<div class='input-group col-xs-6 col-sm-8 col-lg-9'>
<input class='form-control' name="showStreet" type="checkbox" {% if not user.isPro %}disabled{% elif user.settings.showStreetview %}checked{% else %}{% endif %}><br>
</div>
<div id='showStreet' class='form-group' title="{% if not user.isPro %}PRO ONLY! {% endif %}Shows a Google street view image at or near your current location, oriented in the direction of travel. ">
<label for="showStreet">Show street view{% if not user.isPro %} <span class='red'>(PRO)</span>{% endif %}</label>
<input name="showStreet" type="checkbox" {% if not user.isPro %}disabled{% elif user.settings.showStreetview %}checked{% else %}{% endif %}>
</div>
<div id='delete' class='form-group col-xs-12'>
<a class='btn red col-xs-5 col-md-3' style='margin-bottom:5px;float:right;' onclick="deleteAccount()">Delete account</a>
<div id='delete' class='form-group' title="Permently delete your Tracman account. ">
<a class='red' style='width:100%; text-align:right' href="" onclick="deleteAccount()">Delete account</a>
</div>
<div id='submit' class='form-group col-xs-12 flexbox' style="padding:0 0 60px">
<div id='submit-group' class='form-group flexbox' style="padding:0 0 60px">
<input class='btn yellow' style="width:50%; background:#333" type="submit" value="Save">
<a href="#" class='btn' style="width:50%; background:#333">cancel</a>
</div>