diff --git a/app/imports/api/users/Invites.js b/app/imports/api/users/Invites.js index c3b0fd33..a7293ee1 100644 --- a/app/imports/api/users/Invites.js +++ b/app/imports/api/users/Invites.js @@ -145,7 +145,41 @@ const acceptInviteToken = new ValidatedMethod({ }, }); +const revokeInvite = new ValidatedMethod({ + name: 'Invites.methods.revokeInvite', + validate: new SimpleSchema({ + inviteId: { + type: String, + regEx: SimpleSchema.RegEx.Id, + }, + }).validator(), + run({inviteId}) { + if (!this.userId) { + throw new Meteor.Error('Invites.methods.revokeInvite.denied', + 'You need to be the logged in to revoke a token'); + } + if (Meteor.isClient) return; + let invite = Invites.findOne(inviteId); + if (!invite){ + throw new Meteor.Error('Invites.methods.revokeInvite.notFound', + 'No invite could be found for this id'); + } + if (this.userId !== invite.inviter) { + throw new Meteor.Error('Invites.methods.revokeInvite.denied', + 'You are not the owner of this invite'); + } + + // If the invitee is empty, the token has already been revoked + if (!invite.invitee){ + return; + } + Invites.update(invite._id, { + $unset: {invitee: 1, dateConfirmed: 1}, + }); + }, +}); + Invites.attachSchema(InviteSchema); export default Invites; -export { alignInvitesWithPatreonTier, getInviteToken, acceptInviteToken }; +export { alignInvitesWithPatreonTier, getInviteToken, acceptInviteToken, revokeInvite }; diff --git a/app/imports/api/users/patreon/tiers.js b/app/imports/api/users/patreon/tiers.js index 36744414..3b58984a 100644 --- a/app/imports/api/users/patreon/tiers.js +++ b/app/imports/api/users/patreon/tiers.js @@ -1,5 +1,6 @@ import { findLast } from 'lodash'; import getEntitledCents from '/imports/api/users/patreon/getEntitledCents.js'; +import Invites from '/imports/api/users/Invites.js'; const TIERS = [ { @@ -62,7 +63,14 @@ export function getUserTier(user){ if (!user) throw 'User not found'; } const entitledCents = getEntitledCents(user); - return getTierByEntitledCents(entitledCents); + const tier = getTierByEntitledCents(entitledCents); + if (tier.paidBenefits) return tier; + let invite = Invites.findOne({invitee: user._id, isFunded: true}); + if (invite){ + return GUEST_TIER; + } else { + return tier; + } } export default TIERS; diff --git a/app/imports/server/publications/users.js b/app/imports/server/publications/users.js index dbf3b242..e888a355 100644 --- a/app/imports/server/publications/users.js +++ b/app/imports/server/publications/users.js @@ -18,6 +18,10 @@ Meteor.publish('user', function(){ {inviter: this.userId}, {invitee: this.userId} ], + }, { + fields: { + inviteToken: 0, + } }), ]; }); diff --git a/app/imports/ui/pages/Account.vue b/app/imports/ui/pages/Account.vue index 4b58edfd..59581282 100644 --- a/app/imports/ui/pages/Account.vue +++ b/app/imports/ui/pages/Account.vue @@ -49,26 +49,12 @@ Patreon - - - - Pledged amount: ${{ entitledCents/100 }} - - - - - - Tier: {{ tier.name }} - - - - + + + Tier: {{ tier.name }} + + + Link Patreon @@ -130,7 +116,10 @@ export default { meteor: { $subscribe: { - 'user': [], + 'userPublicProfiles'(){ + if (!this.invites) return false; + return [this.invites.map(i => i.invitee).filter(i => !!i)]; + }, }, user(){ return Meteor.user(); @@ -143,9 +132,9 @@ const user = Meteor.user(); return user && user.emails; }, - darkMode(){ - return this.user && this.user.darkMode; - }, + darkMode(){ + return this.user && this.user.darkMode; + }, invites(){ let usernames = {}; Meteor.users.find({}).forEach(user => { @@ -154,12 +143,12 @@ return Invites.find({ inviter: Meteor.userId(), }, { - sort: {dateConfirmed: 1}, + sort: {dateConfirmed: 1, invitee: -1}, }).map(invite => { invite.inviteeName = usernames[invite.invitee]; return invite; }); - } + }, }, data(){ return { showApiKey: false, @@ -186,9 +175,9 @@ Meteor.logout(); router.push('/'); }, - setDarkMode(value){ - Meteor.users.setDarkMode.call({darkMode: !!value}); - }, + setDarkMode(value){ + Meteor.users.setDarkMode.call({darkMode: !!value}); + }, generateKey(){ Meteor.users.gnerateApiKey.call(error => { if(error) this.apiKeyGenerationError = error.reason; @@ -202,10 +191,10 @@ }, clickInvite(invite){ this.$store.commit('pushDialogStack', { - component: 'invite-dialog', - elementId: invite._id, - data: {inviteId: invite._id}, - }); + component: 'invite-dialog', + elementId: invite._id, + data: {inviteId: invite._id}, + }); }, linkWithPatreon, }, diff --git a/app/imports/ui/pages/Home.vue b/app/imports/ui/pages/Home.vue index a53d110f..9cf1ca97 100644 --- a/app/imports/ui/pages/Home.vue +++ b/app/imports/ui/pages/Home.vue @@ -100,7 +100,7 @@ large to="/sign-in" > - Sign up + Sign In diff --git a/app/imports/ui/pages/PatreonLevelTooLow.vue b/app/imports/ui/pages/PatreonLevelTooLow.vue index 63c75ab0..ef0de5c2 100644 --- a/app/imports/ui/pages/PatreonLevelTooLow.vue +++ b/app/imports/ui/pages/PatreonLevelTooLow.vue @@ -6,25 +6,26 @@ justify-center > - Your current patreon support is ${{ entitledDollars }}. - - - You need to pledge at least $5 to use this beta. + Your current Patreon tier is {{ tier.name }} + + You need to be at least Adventurer tier (or be invited by a Patron of + a higher tier) to access this beta + diff --git a/app/imports/ui/pages/SignIn.vue b/app/imports/ui/pages/SignIn.vue index 143c720a..0c837ec1 100644 --- a/app/imports/ui/pages/SignIn.vue +++ b/app/imports/ui/pages/SignIn.vue @@ -13,7 +13,6 @@ width="120px" class="ma-3" /> - @@ -59,7 +57,6 @@ column align-center > - {{ patreonError }} diff --git a/app/imports/ui/router.js b/app/imports/ui/router.js index e13b6e65..44bd3a9a 100644 --- a/app/imports/ui/router.js +++ b/app/imports/ui/router.js @@ -1,5 +1,5 @@ import { RouterFactory, nativeScrollBehavior } from 'meteor/akryum:vue-router2'; -import getEntitledCents from '/imports/api/users/patreon/getEntitledCents.js'; +import { getUserTier } from '/imports/api/users/patreon/tiers.js'; import LAUNCH_DATE from '/imports/constants/LAUNCH_DATE.js'; import { acceptInviteToken } from '/imports/api/users/Invites.js'; @@ -48,7 +48,7 @@ function ensureLoggedIn(to, from, next){ }); } -function ensurePatronTier5(to, from, next){ +function ensurePaidFeatures(to, from, next){ Tracker.autorun((computation) => { if (userSubscription.ready()){ computation.stop(); @@ -57,11 +57,11 @@ function ensurePatronTier5(to, from, next){ next({ name: 'signIn', query: { redirect: to.path} }); return; } - let entitledCents = getEntitledCents(user); - if (entitledCents < 500){ - next('/patreon-level-too-low'); - } else { + let tier = getUserTier(user); + if (tier && tier.paidBenefits){ next(); + } else { + next('/patreon-level-too-low'); } } }); @@ -118,7 +118,7 @@ RouterFactory.configure(factory => { meta: { title: 'Character List', }, - beforeEnter: ensurePatronTier5, + beforeEnter: ensurePaidFeatures, },{ path: '/library', components: { @@ -127,7 +127,7 @@ RouterFactory.configure(factory => { meta: { title: 'Library', }, - beforeEnter: ensurePatronTier5, + beforeEnter: ensurePaidFeatures, },{ name: 'singleLibrary', path: '/library/:id', @@ -138,7 +138,7 @@ RouterFactory.configure(factory => { meta: { title: 'Library', }, - beforeEnter: ensurePatronTier5, + beforeEnter: ensurePaidFeatures, },{ path: '/character/:id/:urlName', components: { @@ -149,7 +149,7 @@ RouterFactory.configure(factory => { meta: { title: 'Character Sheet', }, - beforeEnter: ensurePatronTier5, + beforeEnter: ensurePaidFeatures, },{ path: '/character/:id', components: { @@ -160,7 +160,7 @@ RouterFactory.configure(factory => { meta: { title: 'Character Sheet', }, - beforeEnter: ensurePatronTier5, + beforeEnter: ensurePaidFeatures, },{ path: '/friends', components: { @@ -179,15 +179,15 @@ RouterFactory.configure(factory => { meta: { title: 'Sign In', }, - },/*{ + },{ path: '/register', components: { default: Register, }, meta: { - title: 'Home', + title: 'Register', }, - },*/{ + },{ path: '/account', components: { default: Account, diff --git a/app/imports/ui/user/InviteDialog.vue b/app/imports/ui/user/InviteDialog.vue index 828a00a8..28063609 100644 --- a/app/imports/ui/user/InviteDialog.vue +++ b/app/imports/ui/user/InviteDialog.vue @@ -5,12 +5,29 @@ Invite - - + + {{ username || invite.invitee }} + + + Revoke Invite + + + + This invite is available import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; -import Invites, { getInviteToken } from '/imports/api/users/Invites.js'; +import Invites, { getInviteToken, revokeInvite } from '/imports/api/users/Invites.js'; export default { components: { @@ -47,6 +64,11 @@ export default { meteor: { invite(){ return Invites.findOne(this.inviteId); + }, + username(){ + if (!this.invite) return; + let user = Meteor.users.findOne(this.invite.invitee); + return user && user.username; } }, computed: { @@ -67,8 +89,11 @@ export default { this.inviteToken = result; } }); - } - } + }, + revokeInvite(){ + revokeInvite.call({inviteId: this.inviteId}); + }, + }, }
This invite is available