From bbda0ea1b6090b91c3448cb79ad7d31124961996 Mon Sep 17 00:00:00 2001 From: Thaum Rystra Date: Tue, 12 May 2020 14:11:43 +0200 Subject: [PATCH] Invites can now be managed to some extent --- app/imports/api/users/Invites.js | 12 ++- .../ui/dialogStack/DialogComponentIndex.js | 2 + app/imports/ui/pages/Account.vue | 61 ++++++++++++++- app/imports/ui/pages/InviteError.vue | 22 ++++++ app/imports/ui/pages/InviteSuccess.vue | 13 ++++ app/imports/ui/pages/SignIn.vue | 7 +- app/imports/ui/router.js | 54 ++++++++++++- app/imports/ui/user/InviteDialog.vue | 76 +++++++++++++++++++ 8 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 app/imports/ui/pages/InviteError.vue create mode 100644 app/imports/ui/pages/InviteSuccess.vue create mode 100644 app/imports/ui/user/InviteDialog.vue diff --git a/app/imports/api/users/Invites.js b/app/imports/api/users/Invites.js index ba621b2d..c3b0fd33 100644 --- a/app/imports/api/users/Invites.js +++ b/app/imports/api/users/Invites.js @@ -15,6 +15,7 @@ let InviteSchema = new SimpleSchema({ regEx: SimpleSchema.RegEx.Id, optional: true, index: 1, + unique: 1, }, inviteToken: { type: String, @@ -25,10 +26,6 @@ let InviteSchema = new SimpleSchema({ isFunded: { type: Boolean, }, - isRedundant: { - type: Boolean, - optional: true, - }, // The timestamp of when the invitee was confirmed // Older invites have priority over newer ones dateConfirmed: { @@ -119,14 +116,15 @@ const acceptInviteToken = new ValidatedMethod({ }, }).validator(), run({inviteToken}) { - if (this.userId) { + if (!this.userId) { throw new Meteor.Error('Invites.methods.acceptToken.denied', 'You need to be the logged in to accept a token'); } + if (Meteor.isClient) return; let invite = Invites.findOne({inviteToken}); if (!invite){ throw new Meteor.Error('Invites.methods.acceptToken.notFound', - 'No invite could be found for this token'); + 'No invite could be found for this link, maybe it has already been claimed'); } // If the invitee is already filled, fix unexpected case by deleting the token if (invite.invitee){ @@ -134,7 +132,7 @@ const acceptInviteToken = new ValidatedMethod({ $unset: {inviteToken: 1} }); throw new Meteor.Error('Invites.methods.acceptToken.alreadyAccepted', - 'This invite already has an invitee, and shouldn\'t have a token'); + 'This invite has already been claimed'); } if (this.userId === invite.inviter){ throw new Meteor.Error('Invites.methods.acceptToken.ownToken', diff --git a/app/imports/ui/dialogStack/DialogComponentIndex.js b/app/imports/ui/dialogStack/DialogComponentIndex.js index 66b13b9d..8725a6d6 100644 --- a/app/imports/ui/dialogStack/DialogComponentIndex.js +++ b/app/imports/ui/dialogStack/DialogComponentIndex.js @@ -3,6 +3,7 @@ import CreaturePropertyCreationDialog from '/imports/ui/creature/creaturePropert import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue' import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue' import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue'; +import InviteDialog from '/imports/ui/user/InviteDialog.vue'; import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue'; import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue'; import LibraryNodeCreationDialog from '/imports/ui/library/LibraryNodeCreationDialog.vue'; @@ -16,6 +17,7 @@ export default { CreaturePropertyDialog, CreaturePropertyFromLibraryDialog, DeleteConfirmationDialog, + InviteDialog, LibraryCreationDialog, LibraryEditDialog, LibraryNodeCreationDialog, diff --git a/app/imports/ui/pages/Account.vue b/app/imports/ui/pages/Account.vue index 581ee6d4..4b58edfd 100644 --- a/app/imports/ui/pages/Account.vue +++ b/app/imports/ui/pages/Account.vue @@ -74,16 +74,48 @@ - - + Sign Out - + + @@ -114,6 +146,20 @@ darkMode(){ return this.user && this.user.darkMode; }, + invites(){ + let usernames = {}; + Meteor.users.find({}).forEach(user => { + usernames[user._id] = user.username; + }); + return Invites.find({ + inviter: Meteor.userId(), + }, { + sort: {dateConfirmed: 1}, + }).map(invite => { + invite.inviteeName = usernames[invite.invitee]; + return invite; + }); + } }, data(){ return { showApiKey: false, @@ -154,6 +200,13 @@ if(error) this.emailVerificationError = error.reason; }); }, + clickInvite(invite){ + this.$store.commit('pushDialogStack', { + component: 'invite-dialog', + elementId: invite._id, + data: {inviteId: invite._id}, + }); + }, linkWithPatreon, }, } diff --git a/app/imports/ui/pages/InviteError.vue b/app/imports/ui/pages/InviteError.vue new file mode 100644 index 00000000..0d64a569 --- /dev/null +++ b/app/imports/ui/pages/InviteError.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/app/imports/ui/pages/InviteSuccess.vue b/app/imports/ui/pages/InviteSuccess.vue new file mode 100644 index 00000000..0ee8e680 --- /dev/null +++ b/app/imports/ui/pages/InviteSuccess.vue @@ -0,0 +1,13 @@ + + + + + diff --git a/app/imports/ui/pages/SignIn.vue b/app/imports/ui/pages/SignIn.vue index 55c5eac3..143c720a 100644 --- a/app/imports/ui/pages/SignIn.vue +++ b/app/imports/ui/pages/SignIn.vue @@ -85,7 +85,6 @@ + +