diff --git a/app/Model/Users/Users.js b/app/Model/Users/Users.js index a54d3f7d..a818962e 100644 --- a/app/Model/Users/Users.js +++ b/app/Model/Users/Users.js @@ -70,42 +70,41 @@ Schemas.User = new SimpleSchema({ Meteor.users.attachSchema(Schemas.User); -Meteor.users.allow({ - update: function(userId, doc, fields, modifier) { - if ( - doc._id === userId && - _.contains(fields, "username") && - _.contains(fields, "profile") && - fields.length === 2 && - _.keys(modifier).length === 1 && - modifier.$set && - modifier.$set["profile.username"] && - modifier.$set.username && - _.keys(modifier.$set).length === 2 - ){ - var expectedUsername = modifier.$set["profile.username"]; - expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, ""); - if (modifier.$set.username !== expectedUsername){ - return false; - } - var foundUser = Meteor.call("getUserId", expectedUsername); - return !foundUser || foundUser === userId; - } - } -}); - -if (Meteor.isServer) Meteor.methods({ - generateMyApiKey() { +Meteor.users.gnerateApiKey = new ValidatedMethod({ + name: "Users.methods.generateApiKey", + validate: null, + run(){ + if(Meteor.isClient) return; var user = Meteor.users.findOne(this.userId); if (!user) return; if (user && user.apiKey) return; var apiKey = Random.id(30); Meteor.users.update(this.userId, {$set: {apiKey}}); }, - sendVerificationEmail(address) { - var user = Meteor.users.findOne(this.userId); - if (!user) return; - if (!_.some(user.emails, email => email.address === address)) return; - Accounts.sendVerificationEmail(this.userId, address); - }, +}); + +Meteor.users.sendVerificationEmail = new ValidatedMethod({ + name: "Users.methods.sendVerificationEmail", + validate: new SimpleSchema({ + userId:{ + type: String, + optional: true, + }, + address: { + type: String, + }, + }).validator(), + run(userId, address){ + userId = this.userId || userId; + let user = Meteor.users.findOne(); + if (!user) { + throw new Meteor.Error("User not found", + "Can't send a validation email to a user that does not exist"); + } + if (!_.some(user.emails, email => email.address === address)) { + throw new Meteor.Error("Email address not found", + "The specified email address wasn't found on this user account"); + } + Accounts.sendVerificationEmail(this.userId, address); + } }); diff --git a/app/imports/ui/components/DialogBase.vue b/app/imports/ui/components/DialogBase.vue deleted file mode 100644 index de48808a..00000000 --- a/app/imports/ui/components/DialogBase.vue +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/app/imports/ui/components/DialogStack.vue b/app/imports/ui/components/DialogStack.vue deleted file mode 100644 index e416aa6d..00000000 --- a/app/imports/ui/components/DialogStack.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/app/imports/ui/dialogStack/DialogBase.vue b/app/imports/ui/dialogStack/DialogBase.vue new file mode 100644 index 00000000..e7b3b142 --- /dev/null +++ b/app/imports/ui/dialogStack/DialogBase.vue @@ -0,0 +1,13 @@ + diff --git a/app/imports/ui/dialogStack/DialogStack.vue b/app/imports/ui/dialogStack/DialogStack.vue new file mode 100644 index 00000000..effc4049 --- /dev/null +++ b/app/imports/ui/dialogStack/DialogStack.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/app/imports/ui/dialogStack/TestDialog.vue b/app/imports/ui/dialogStack/TestDialog.vue new file mode 100644 index 00000000..798deb55 --- /dev/null +++ b/app/imports/ui/dialogStack/TestDialog.vue @@ -0,0 +1,28 @@ + + + diff --git a/app/imports/ui/dialogStack/dialogStackStore.js b/app/imports/ui/dialogStack/dialogStackStore.js new file mode 100644 index 00000000..a2cad8ce --- /dev/null +++ b/app/imports/ui/dialogStack/dialogStackStore.js @@ -0,0 +1,87 @@ +import store from "/imports/ui/vuexStore.js"; + +const offset = 16; +const duration = 400; +let dialogStack = {}; +dialogStack.dialogs = []; + +const dialogStackStore = { + state: { + dialogs: [], + }, + mutations: { + pushDialogStack(state, {component, data, element, returnElement, callback}){ + // Generate a new _id so that Vue knows how to shuffle the array + const _id = Random.id(); + state.dialogs.push({ + _id, + component, + data, + element, + returnElement, + callback, + }); + updateHistory(); + }, + popDialogStackMutation (state, result){ + const dialog = state.dialogs.pop(); + updateHistory(); + if (!dialog) return; + dialog.callback && dialog.callback(result); + }, + }, + actions: { + popDialogStack(context, result){ + if (history && history.state && history.state.openDialogs){ + history.back(); + } else { + context.commit("popDialogStackMutation", result) + } + } + } +}; + +export default dialogStackStore; + +const updateHistory = function(){ + // history should looks like: [{openDialogs: 0}, {openDialogs: n}] where + // n is the number of open dialogs + + // If we can't access the history object, give up + if (!history) return; + // Make sure that there is a state tracking open dialogs + // replace the state without bashing it in the process + if (!history.state || !_.isFinite(history.state.openDialogs)){ + let newState = _.clone(history.state) || {}; + newState.openDialogs = 0; + history.replaceState(newState, ""); + } + + const numDialogs = dialogStackStore.state.dialogs.length; + const stateDialogs = history.state.openDialogs; + + // If the number of dialogs and state dialogs are equal, we don't need to do + // anything + if (numDialogs === stateDialogs) return; + + if (stateDialogs > 0){ + // On a dialog count + if (numDialogs === 0){ + // but shouldn't be + history.back(); + } else { + // but should replace with correct count + let newState = _.clone(history.state) || {}; + newState.openDialogs = dialogStackStore.state.dialogs.length; + history.replaceState(newState, ""); + } + } else if (numDialogs > 0 && stateDialogs === 0){ + // On the zero state, push a dialog count + history.pushState({openDialogs: numDialogs}, ""); + } else { + console.warn( + "History could not be updated correctly, unexpected case", + {stateDialogs, numDialogs}, + ) + } +}; diff --git a/app/imports/ui/dialogStack/dialogStackWindowEvents.js b/app/imports/ui/dialogStack/dialogStackWindowEvents.js new file mode 100644 index 00000000..ec728c26 --- /dev/null +++ b/app/imports/ui/dialogStack/dialogStackWindowEvents.js @@ -0,0 +1,11 @@ +import store from "/imports/ui/vuexStore.js"; + +if (window){ + window.onpopstate = function(event){ + let state = event.state; + let numDialogs = store.state.dialogStack.dialogs.length; + if (_.isFinite(state.openDialogs) && numDialogs > state.openDialogs){ + store.commit("popDialogStackMutation"); + } + }; +} diff --git a/app/imports/ui/layouts/AppLayout.vue b/app/imports/ui/layouts/AppLayout.vue index 00709f08..6cf917fb 100644 --- a/app/imports/ui/layouts/AppLayout.vue +++ b/app/imports/ui/layouts/AppLayout.vue @@ -4,11 +4,13 @@ + diff --git a/app/imports/ui/pages/Account.vue b/app/imports/ui/pages/Account.vue index 614302a4..b054f876 100644 --- a/app/imports/ui/pages/Account.vue +++ b/app/imports/ui/pages/Account.vue @@ -140,6 +140,8 @@ data(){ return { showApiKey: false, signOutBusy: false, + apiKeyGenerationError: null, + emailVerificationError: null, }}, methods: { signOut(){ @@ -148,11 +150,15 @@ }); }, generateKey(){ - Meteor.call("generateMyApiKey"); + Meteor.users.gnerateApiKey.call(error => { + if(error) this.apiKeyGenerationError = error.reason; + }); this.showApiKey = true; }, verifyEmail(address){ - Meteor.call("sendVerificationEmail", address); + Meteor.users.sendVerificationEmail.call({address}, error => { + if(error) this.emailVerificationError = error.reason; + }); }, }, components: { diff --git a/app/imports/ui/pages/NotFound.vue b/app/imports/ui/pages/NotFound.vue index e69de29b..5added14 100644 --- a/app/imports/ui/pages/NotFound.vue +++ b/app/imports/ui/pages/NotFound.vue @@ -0,0 +1,12 @@ + diff --git a/app/imports/ui/pages/Register.vue b/app/imports/ui/pages/Register.vue index 77a1a00e..53f23582 100644 --- a/app/imports/ui/pages/Register.vue +++ b/app/imports/ui/pages/Register.vue @@ -67,6 +67,7 @@