From e72ad8c513dc07e8c8df787d988ebf75d4130661 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 12 May 2016 18:52:03 -0700 Subject: [PATCH] fix(ui): Fix flash shown to user on page refresh Store user theme preference on user object. Must be logged in to use themes --- client/less/main.less | 1 - client/main.js | 90 +++++++++++-------- common/models/user.js | 128 ++++++++++++++++++---------- common/models/user.json | 11 +++ server/middlewares/global-locals.js | 4 + server/views/account/settings.jade | 4 +- server/views/layout-wide.jade | 12 +-- server/views/partials/scripts.jade | 6 ++ 8 files changed, 168 insertions(+), 88 deletions(-) diff --git a/client/less/main.less b/client/less/main.less index 60320c5d6a..0bd7729021 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -64,7 +64,6 @@ body.top-and-bottom-margins { } body.no-top-and-bottom-margins { - display: none; margin: 75px 20px 0px 20px; } diff --git a/client/main.js b/client/main.js index 05fd4dc69f..4ac008e5da 100644 --- a/client/main.js +++ b/client/main.js @@ -165,6 +165,7 @@ main.setMapShare = function setMapShare(id) { $(document).ready(function() { + const { Observable } = window.Rx; var CSRF_HEADER = 'X-CSRF-Token'; var setCSRFToken = function(securityToken) { @@ -548,51 +549,68 @@ $(document).ready(function() { // keyboard shortcuts: open map window.Mousetrap.bind('g m', toggleMap); - // Night Mode - function changeMode() { - var newValue = false; - try { - newValue = !JSON.parse(localStorage.getItem('nightMode')); - } catch (e) { - console.error('Error parsing value form local storage:', 'nightMode', e); - } - localStorage.setItem('nightMode', String(newValue)); - toggleNightMode(newValue); + function addAlert(message = '', type = 'alert-info') { + return $('.flashMessage').append($(` +
+ +
${message}
+
+ `)); } - function toggleNightMode(nightModeEnabled) { - var iframe = document.getElementById('map-aside-frame'); - if (iframe) { - iframe.src = iframe.src; + function toggleNightMode() { + if (!main.userId) { + return addAlert('Must be logged in to use themes'); } - var body = $('body'); - body.hide(); - if (nightModeEnabled) { - body.addClass('night'); + const iframe$ = document.getElementById('map-aside-frame'); + const body$ = $('body'); + if (iframe$) { + iframe$.src = iframe$.src; + } + body$.hide(); + let updateThemeTo; + if (body$.hasClass('night')) { + body$.removeClass('night'); + updateThemeTo = 'default'; } else { - body.removeClass('night'); + body$.addClass('night'); + updateThemeTo = 'night'; } - body.fadeIn('100'); + body$.fadeIn('100'); + const options = { + url: `/api/users/${main.userId}/update-theme`, + type: 'POST', + data: { theme: updateThemeTo }, + dataType: 'json' + }; + return $.ajax(options) + .success(() => console.log('theme updated successfully')) + .fail(err => { + let message; + try { + message = JSON.parse(err.responseText).error.message; + } catch (error) { + return null; + } + if (!message) { + return null; + } + return addAlert(message); + }); } - if (typeof localStorage.getItem('nightMode') !== 'undefined') { - var oldVal = false; - try { - oldVal = JSON.parse(localStorage.getItem('nightMode')); - } catch (e) { - console.error('Error parsing value form local storage:', 'nightMode', e); - } - toggleNightMode(oldVal); - $('.nightMode-btn').on('click', function() { - changeMode(); - }); - } else { - localStorage.setItem('nightMode', 'false'); - toggleNightMode('false'); - } + Observable.merge( + Observable.fromEvent($('#night-mode'), 'click'), + Observable.create(observer => { + window.Mousetrap.bind('g t n', () => observer.onNext()); + }) + ) + .debounce(500) + .subscribe(toggleNightMode, err => console.error(err)); // Hot Keys - window.Mousetrap.bind('g t n', changeMode); window.Mousetrap.bind('g n n', () => { // Next Challenge window.location = '/challenges/next-challenge'; diff --git a/common/models/user.js b/common/models/user.js index 0ced33a968..b26b5cb13d 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -307,49 +307,6 @@ module.exports = function(User) { } } ); - - User.prototype.updateEmail = function updateEmail(email) { - if (this.email && this.email === email) { - return Promise.reject(new Error( - `${email} is already associated with this account.` - )); - } - return User.doesExist(null, email) - .then(exists => { - if (exists) { - return Promise.reject( - new Error(`${email} is already associated with another account.`) - ); - } - return this.update$({ email }).toPromise(); - }); - }; - - User.remoteMethod( - 'updateEmail', - { - isStatic: false, - description: 'updates the email of the user object', - accepts: [ - { - arg: 'email', - type: 'string', - required: true - } - ], - returns: [ - { - arg: 'status', - type: 'object' - } - ], - http: { - path: '/update-email', - verb: 'POST' - } - } - ); - User.giveBrowniePoints = function giveBrowniePoints(receiver, giver, data = {}, dev = false, cb) { const findUser = observeMethod(User, 'findOne'); @@ -467,6 +424,91 @@ module.exports = function(User) { } ); + User.prototype.updateEmail = function updateEmail(email) { + if (this.email && this.email === email) { + return Promise.reject(new Error( + `${email} is already associated with this account.` + )); + } + return User.doesExist(null, email) + .then(exists => { + if (exists) { + return Promise.reject( + new Error(`${email} is already associated with another account.`) + ); + } + return this.update$({ email }).toPromise(); + }); + }; + + User.remoteMethod( + 'updateEmail', + { + isStatic: false, + description: 'updates the email of the user object', + accepts: [ + { + arg: 'email', + type: 'string', + required: true + } + ], + returns: [ + { + arg: 'status', + type: 'object' + } + ], + http: { + path: '/update-email', + verb: 'POST' + } + } + ); + + User.themes = { + night: true, + default: true + }; + User.prototype.updateTheme = function updateTheme(theme) { + if (!this.constructor.themes[theme]) { + const err = new Error( + 'Theme is not valid.' + ); + err.messageType = 'info'; + err.userMessage = err.message; + return Promise.reject(err); + } + return this.update$({ theme }) + .map({ updatedTo: theme }) + .toPromise(); + }; + + User.remoteMethod( + 'updateTheme', + { + isStatic: false, + description: 'updates the users chosen theme', + accepts: [ + { + arg: 'theme', + type: 'string', + required: true + } + ], + returns: [ + { + arg: 'status', + type: 'object' + } + ], + http: { + path: '/update-theme', + verb: 'POST' + } + } + ); + // user.updateTo$(updateData: Object) => Observable[Number] User.prototype.update$ = function update$(updateData) { const id = this.getId(); diff --git a/common/models/user.json b/common/models/user.json index ad7294688d..cdbff24c58 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -188,6 +188,10 @@ }, "timezone": { "type": "string" + }, + "theme": { + "type": "string", + "default": "default" } }, "validations": [], @@ -242,6 +246,13 @@ "principalId": "$owner", "permission": "ALLOW", "property": "updateEmail" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW", + "property": "updateTheme" } ], "methods": [] diff --git a/server/middlewares/global-locals.js b/server/middlewares/global-locals.js index 1871eb95ff..aa2d7e32d0 100644 --- a/server/middlewares/global-locals.js +++ b/server/middlewares/global-locals.js @@ -6,6 +6,10 @@ export default function globalLocals() { if (req.csrfToken) { res.expose({ token: res.locals._csrf }, 'csrf'); } + res.locals.theme = req.user && req.user.theme || + req.cookies.theme || + 'default'; + next(); }; } diff --git a/server/views/account/settings.jade b/server/views/account/settings.jade index c992ece48e..e904dbc413 100644 --- a/server/views/account/settings.jade +++ b/server/views/account/settings.jade @@ -5,7 +5,7 @@ block content h2.text-center Actions .row .col-xs-12 - a.btn.btn-lg.btn-block.btn-primary.btn-link-social(class = "nightMode-btn") Night Mode + a#night-mode.btn.btn-lg.btn-block.btn-primary.btn-link-social Night Mode .row .col-xs-12 if (!user.isGithubCool) @@ -133,4 +133,4 @@ block content form(action='/account/delete', method='POST') input(type='hidden', name='_csrf', value=_csrf) button.btn.btn-danger.btn-block(type='submit') - | I am 100% sure I want to delete my account and all of my progress \ No newline at end of file + | I am 100% sure I want to delete my account and all of my progress diff --git a/server/views/layout-wide.jade b/server/views/layout-wide.jade index 4ffc168436..cf548c3edc 100644 --- a/server/views/layout-wide.jade +++ b/server/views/layout-wide.jade @@ -8,9 +8,9 @@ html(lang='en') include partials/scripts block content else - body.no-top-and-bottom-margins - include partials/scripts - include partials/navbar - include partials/flash - block content - include partials/footer + body.no-top-and-bottom-margins(class=theme !== 'default' ? theme : '') + include partials/scripts + include partials/navbar + include partials/flash + block content + include partials/footer diff --git a/server/views/partials/scripts.jade b/server/views/partials/scripts.jade index a002f935eb..988b26bd9f 100644 --- a/server/views/partials/scripts.jade +++ b/server/views/partials/scripts.jade @@ -7,5 +7,11 @@ script. ga('require', 'displayfeatures'); ga('send', 'pageview'); // Leave the below lines alone! +script. + (function(global) { + global.main = global.main || {}; + global.main.isLoggedIn = !{JSON.stringify(!!user)}; + global.main.userId = !{JSON.stringify(user && user.id || false)}; + }(window)) script(src=rev('/js', 'vendor-main.js')) script(src=rev('/js', 'main.js'))