From b57dbd126734b4398861292c611197c6991ed3eb Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 9 Jan 2019 01:04:07 +0100 Subject: [PATCH] Advance on users management. TODO: routes/users + debug + test --- app.js | 1 + public/javascripts/components/upsertUser.js | 120 ++++++++++++++++++++ public/javascripts/index.js | 2 +- public/javascripts/utils/ajax.js | 2 +- public/javascripts/variant.js | 2 +- public/stylesheets/_users.sass | 9 ++ public/stylesheets/index.sass | 16 ++- public/stylesheets/layout.sass | 2 + sockets.js | 3 + views/index.pug | 18 ++- views/layout.pug | 10 +- views/login_register.pug | 31 ----- views/logout_update.pug | 36 ------ views/modalLang.pug | 11 ++ views/settings.pug | 12 -- views/variant.pug | 11 +- 16 files changed, 191 insertions(+), 95 deletions(-) create mode 100644 public/javascripts/components/upsertUser.js create mode 100644 public/stylesheets/_users.sass delete mode 100644 views/login_register.pug delete mode 100644 views/logout_update.pug create mode 100644 views/modalLang.pug diff --git a/app.js b/app.js index 3199af3e..22c220b9 100644 --- a/app.js +++ b/app.js @@ -46,6 +46,7 @@ app.use(express.static(path.join(__dirname, 'public'))); // Before showing any page, check + save credentials app.use(function(req, res, next) { req.loggedIn = false; + res.locals.user = { name: "" }; if (!req.cookies.token) return next(); UserModel.getOne("sessionToken", req.cookies.token, function(err, user) { diff --git a/public/javascripts/components/upsertUser.js b/public/javascripts/components/upsertUser.js new file mode 100644 index 00000000..29444b46 --- /dev/null +++ b/public/javascripts/components/upsertUser.js @@ -0,0 +1,120 @@ +// Logic to login, or create / update a user (and also logout) +Vue.component('my-upsert-user', { + props: ["initUser"], //to find the game in storage (assumption: it exists) + data: function() { + return { + user: initUser, //initialized with prop value + stage: (!initUser.email ? "Login" : "Update"), + infoMsg: "", + }; + }, + template: ` +
+ +
+
+
+
+ `, + computed: { + submitMessage: function() { + switch (this.stage) + { + case "Login": + return "Go"; + case "Register": + return "Send"; + case "Update": + return "Apply"; + } + }, + displayInfo: function() { + return (this.infoMsg.length > 0 ? "block" : "none"); + }, + }, + methods: { + toggleStage: function() { + this.stage = (this.stage == "Login" ? "Register" : "Login"); + }, + ajaxUrl: function() { + switch (this.stage) + { + case "Login": + return "/sendtoken"; + case "Register": + return "/register"; + case "Update": + return "/update"; + } + }, + ajaxMethod: function() { + switch (this.stage) + { + case "Login": + return "GET"; + case "Register": + return "POST"; + case "Update": + return "PUT"; + } + }, + infoMessage: function() { + switch (this.stage) + { + case "Login": + return "Connection token sent. Check your emails!"; + case "Register": + return "Registration complete! Please check your emails."; + case "Update": + return "Modifications applied!"; + } + }, + submit: function() { + // TODO: re-activate simple measures like this: (using time of click on modal) +// const exitTime = new Date(); +// if (this.stage=="Register" && exitTime.getTime() - enterTime.getTime() < 5000) +// return; + if (!this.user.name.match(/[a-z0-9_]+/i)) + return alert("User name: only alphanumerics and underscore"); + this.infoMsg = "Processing... Please wait"; + ajax(this.ajaxUrl(), this.ajaxMethod(), + this.stage == "Login" ? "PUT" : "POST", this.user, + res => { + this.infoMsg = this.infoMessage(); + if (this.stage != "Update") + { + this.user["email"] = ""; + this.user["name"] = ""; + } + setTimeout(() => { + this.infoMsg = ""; + document.getElementById("modalUser").checked = false; + }, 2000); + } + ); + }, + } +}); diff --git a/public/javascripts/index.js b/public/javascripts/index.js index 6859e8bf..f59bc045 100644 --- a/public/javascripts/index.js +++ b/public/javascripts/index.js @@ -1,6 +1,6 @@ // Javascript for index page: mostly counters updating new Vue({ - el: "#indexPage", + el: "#VueElement", data: { counts: {}, curPrefix: "", diff --git a/public/javascripts/utils/ajax.js b/public/javascripts/utils/ajax.js index ab288bc7..a3546900 100644 --- a/public/javascripts/utils/ajax.js +++ b/public/javascripts/utils/ajax.js @@ -3,7 +3,7 @@ function toQueryString(data) { let data_str = ""; Object.keys(data).forEach(k => { - data_str += k + "=" + data[k] + "&"; + data_str += k + "=" + encodeURIComponent(data[k]) + "&"; }); return data_str.slice(0, -1); //remove last "&" } diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js index eac1ec09..1ec4d89b 100644 --- a/public/javascripts/variant.js +++ b/public/javascripts/variant.js @@ -1,5 +1,5 @@ new Vue({ - el: "#variantPage", + el: "#VueElement", data: { display: "room", //default: main hall gameid: "undefined", //...yet diff --git a/public/stylesheets/_users.sass b/public/stylesheets/_users.sass new file mode 100644 index 00000000..69bc438d --- /dev/null +++ b/public/stylesheets/_users.sass @@ -0,0 +1,9 @@ +button#submit + display: inline-flex + //i + line-height: 36px + span + margin-right: 7px + +#dialog + clear: both diff --git a/public/stylesheets/index.sass b/public/stylesheets/index.sass index e38f87dd..d25ecb49 100644 --- a/public/stylesheets/index.sass +++ b/public/stylesheets/index.sass @@ -11,7 +11,7 @@ padding: 0 box-sizing: border-box p - display: inline-block + display: flex padding: 3px border: 1px solid black; margin: 25px 15px 5px 7px @@ -40,13 +40,25 @@ margin-top: 10px font-size: 1em -#settingsMenu, #introductionMenu +#introductionMenu, #userMenu float: right @media screen and (max-width: 767px) .info-container p margin-right: 5px +#flagMenu + float: right + margin-right: 10px + @media screen and (max-width: 767px) + margin-right: 5px + img + display: inline-block + height: 30px + padding-top: 27px + @media screen and (max-width: 767px) + padding-top: 8px + // TODO: box-shadow or box-sizing ? https://stackoverflow.com/a/13517809 .variant box-sizing: border-box diff --git a/public/stylesheets/layout.sass b/public/stylesheets/layout.sass index dedab08e..eae4542a 100644 --- a/public/stylesheets/layout.sass +++ b/public/stylesheets/layout.sass @@ -1,3 +1,5 @@ +@import users + html, * font-family: "Open Sans", Arial, sans-serif --back-color: #f2f2f2 diff --git a/sockets.js b/sockets.js index e411050d..60434b5e 100644 --- a/sockets.js +++ b/sockets.js @@ -13,6 +13,9 @@ function getJsonFromUrl(url) { return result; } +// TODO: empêcher multi-log du même user (envoyer le user ID + secret en même temps que name et...) +// --> si secret ne matche pas celui trouvé en DB, stop + module.exports = function(wss) { db.serialize(function() { db.all("SELECT * FROM Variants", (err,variants) => { diff --git a/views/index.pug b/views/index.pug index dd095931..5dfa903a 100644 --- a/views/index.pug +++ b/views/index.pug @@ -4,7 +4,7 @@ block css link(rel="stylesheet", href="/stylesheets/index.css") block content - .container#indexPage + .container case lang when "en" include welcome/en @@ -19,10 +19,20 @@ block content .info-container p vchess.club img(src="/images/index/wildebeest.svg") - #settingsMenu.clickable( - onClick="document.getElementById('modalSettings').checked=true") + #flagMenu.clickable( + onClick="document.getElementById('modalLang').checked=true") + img(src="/images/flags/" + lang + ".svg") + #userMenu.clickable( + onClick="document.getElementById('modalUser').checked=true") .info-container - p Settings + if !user.email + p + span Login + i.material-icons person + else + p + span Update + i.material-icons person #introductionMenu.clickable( onClick="document.getElementById('modalWelcome').checked=true") .info-container diff --git a/views/layout.pug b/views/layout.pug index 60b7adcf..03249bed 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -33,13 +33,10 @@ html include translations/es when "fr" include translations/fr - if !user - include login_register - else - include logout_update include contactForm - include settings - main + include modalLang + main#VueElement + my-upsert-user(:user="user" :stage="stage") block content footer.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2.text-center div @@ -56,6 +53,7 @@ html script(src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js") else script(src="https://cdn.jsdelivr.net/npm/vue") + script(src="/javascripts/components/upsertUser.js") script. const translations = !{JSON.stringify(translations)}; const user = !{JSON.stringify(user)}; diff --git a/views/login_register.pug b/views/login_register.pug deleted file mode 100644 index c831c8a1..00000000 --- a/views/login_register.pug +++ /dev/null @@ -1,31 +0,0 @@ -extends layout - -block css - link(rel="stylesheet", href="/stylesheets/login.css") - -block content - .mui-container - .row - .mui-col.xs-12.mui-col-sm-8.mui-col-sm-offset-2.mui-col-md-6.mui-col-md-offset-3.mui-col-lg-4.mui-col-lg-offset-4.mui--z1.white.pad-updown.pad-sides - form#loginForm(@submit.prevent="submit") - .mui-textfield.mui-textfield--float-label - input#email(type="email" ref="userEmail" v-model="user.email") - label#labEmail(for="email") Email - .mui-textfield.mui-textfield--float-label(v-show="stage == 'Register'") - input#name(type="text" v-model="user.name") - label#labName(for="name") Name - .mui--pull-left.space-bottom.space-top - button#submit.mui-btn.mui-btn--primary(@click.prevent="submit") - span {{ stage=="Login" ? "Go" : "Send" }} - i.material-icons.right send - .mui--pull-right.space-bottom.space-top - p - button.mui-btn.mui-btn--accent(@click.prevent="toggleStage()") - span {{ stage=="Login" ? "Register" : "Login" }} - #dialog.mui--hide.space-top - -block javascripts - script(src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js") - script(src="/javascripts/utils/dialog.js") - script(src="/javascripts/utils/validation.js") - script(src="/javascripts/login.js") diff --git a/views/logout_update.pug b/views/logout_update.pug deleted file mode 100644 index 1b84483b..00000000 --- a/views/logout_update.pug +++ /dev/null @@ -1,36 +0,0 @@ -extends layout - -block css - link(rel="stylesheet", href="/stylesheets/settings.css") - -block content - .mui-container-fluid - .mui-row - .mui-col-xs-12.mui-col-sm-10.mui-col-sm-offset-1.mui-col-md-8.mui-col-md-offset-2.mui-col-lg-6.mui-col-lg-offset-3.mui--z1.white.pad-updown.pad-sides - form#settingsForm(@submit.prevent="submit") - .mui-textfield.mui-textfield--float-label - input#email(type="email" ref="userEmail" v-model="user.email") - label#labEmail.active(for="email") Email - .mui-textfield.mui-textfield--float-label - input#name(type="text" v-model="user.name") - label#labName.active(for="name") Name - p - span Theme      - button(v-for="theme in themes" class="theme-btn mui-btn grey" - :class="themeClass(theme)" @click.prevent="toggleTheme(theme)") - | {{ theme }} - .mui-radio - input#notify(type="checkbox" v-model="user.notify") - label(for="notify") Notify new moves & games - button#submit.mui-btn.mui-btn--primary(@click.prevent="submit") - span Apply - i.material-icons.right send - #dialog.mui--hide.space-top - -block javascripts - script(src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js") - script(src="/javascripts/utils/dialog.js") - script(src="/javascripts/utils/validation.js") - script. - var user = !{JSON.stringify(user)}; - script(src="/javascripts/settings.js") diff --git a/views/modalLang.pug b/views/modalLang.pug new file mode 100644 index 00000000..b6311829 --- /dev/null +++ b/views/modalLang.pug @@ -0,0 +1,11 @@ +input#modalLang.modal(type="checkbox") +div(role="dialog") + #language.card + label.modal-close(for="modalLang") + form + fieldset + label(for="langSelect")= translations["Language"] + select#langSelect + each language,langCode in langName + option(value=langCode selected=(lang==langCode)) + =language diff --git a/views/settings.pug b/views/settings.pug index 063e61a1..8bf7de59 100644 --- a/views/settings.pug +++ b/views/settings.pug @@ -3,18 +3,6 @@ div(role="dialog" aria-labelledby="settingsTitle") .card.smallpad(onChange="blabla(event)") label.modal-close(for="modalSettings") h3#settingsTitle.section= translations["Preferences"] - fieldset - label(for="langSelect")= translations["Language"] - // image avec drapeau + select language ici - select#langSelect - each language,langCode in langName - option(value=langCode selected=(lang==langCode)) - =language - fieldset - label(for="nameSetter") - =translations["My name is..."] - input#nameSetter(type="text" value=this.myname) - // theme sombre / clair // taille echiquier : TODO fieldset label(for="setHints")= translations["Show hints?"] diff --git a/views/variant.pug b/views/variant.pug index 9419998a..5bfcba10 100644 --- a/views/variant.pug +++ b/views/variant.pug @@ -4,7 +4,8 @@ block css link(rel="stylesheet" href="/stylesheets/variant.css") block content - .container#variantPage + include settings + .container .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 label.drawer-toggle(for="drawer-control") @@ -24,6 +25,14 @@ block content #settings.clickable( onClick="document.getElementById('modalSettings').checked=true") i.material-icons settings + #userMenu.clickable( + onClick="document.getElementById('modalUser').checked=true") + .info-container + i.material-icons person + if !user.email + p Login + else + p Update .row my-room(v-show="display=='room'") my-game-list(v-show="display=='gameList'") -- 2.44.0