From b57dbd126734b4398861292c611197c6991ed3eb Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
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: `
+		<div>
+			<input id="modalUser" class="modal" type="checkbox"/>
+			<div role="dialog">
+				<div class="card">
+					<label class="modal-close" for="modalUser">
+					<h3>{{ stage }}</h3>
+					<form id="userForm" @submit.prevent="submit">
+						<fieldset>
+							<label for="useremail">Email</label>
+							<input id="useremail" type="email" v-model="user.email"/>
+						<fieldset>
+							<label for="username">Name</label>
+							<input id="username" type="text" v-model="user.name"/>
+						</fieldset>
+						<fieldset>
+							<label for="notifyNew">Notify new moves &amp; games</label>
+							<input id="notifyNew" type="checkbox" v-model="user.notify"/>
+						<button id="submit" @click.prevent="submit">
+							<span>{{ submitMessage }}</span>
+							<i class="material-icons">send</i>
+				<p v-if="stage!='Update'">
+					<button @click.prevent="toggleStage()">
+						<span>{{ stage=="Login" ? "Register" : "Login" }}</span>
+					</button>
+					<button>Logout</button>
+				</p>
+				<div id="dialog" :style="{display: displayInfo}">{{ infoMsg }}</div>
+			</div>
+		</div>
+	`,
+	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 &nbsp;&nbsp;&nbsp;&nbsp;
-						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 &amp; 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