Improved handling of tiers, made guest tier funcitonal, improved invites
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,6 +18,10 @@ Meteor.publish('user', function(){
|
||||
{inviter: this.userId},
|
||||
{invitee: this.userId}
|
||||
],
|
||||
}, {
|
||||
fields: {
|
||||
inviteToken: 0,
|
||||
}
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
@@ -49,26 +49,12 @@
|
||||
<v-subheader>
|
||||
Patreon
|
||||
</v-subheader>
|
||||
<template v-if="user.services.patreon">
|
||||
<v-list-tile>
|
||||
<v-list-tile-title>
|
||||
Pledged amount: ${{ entitledCents/100 }}
|
||||
</v-list-tile-title>
|
||||
<!--
|
||||
<v-list-tile-action>
|
||||
<v-btn icon>
|
||||
<v-icon>refresh</v-icon>
|
||||
</v-btn>
|
||||
</v-list-tile-action>
|
||||
-->
|
||||
</v-list-tile>
|
||||
<v-list-tile>
|
||||
<v-list-tile-title>
|
||||
Tier: {{ tier.name }}
|
||||
</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
<v-list-tile v-else>
|
||||
<v-list-tile>
|
||||
<v-list-tile-title>
|
||||
Tier: {{ tier.name }}
|
||||
</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
<v-list-tile v-if="!user.services.patreon">
|
||||
<v-btn @click="linkWithPatreon">
|
||||
Link Patreon
|
||||
</v-btn>
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
large
|
||||
to="/sign-in"
|
||||
>
|
||||
Sign up
|
||||
Sign In
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</section>
|
||||
|
||||
@@ -6,25 +6,26 @@
|
||||
justify-center
|
||||
>
|
||||
<h2 style="margin: 48px 28px 16px">
|
||||
Your current patreon support is ${{ entitledDollars }}.
|
||||
</h2>
|
||||
<h2 style="margin: 16px 28px">
|
||||
You need to pledge at least $5 to use this beta.
|
||||
Your current Patreon tier is {{ tier.name }}
|
||||
</h2>
|
||||
<h3>
|
||||
You need to be at least Adventurer tier (or be invited by a Patron of
|
||||
a higher tier) to access this beta
|
||||
</h3>
|
||||
</v-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TIERS, { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
export default {
|
||||
meteor: {
|
||||
entitledDollars(){
|
||||
tier(){
|
||||
let user = Meteor.user();
|
||||
if (!user) return 0;
|
||||
let entitledCents = user.services.patreon.entitledCents || 0;
|
||||
let overrideCents = user.services.patreon.entitledCentsOverride || 0;
|
||||
return Math.max(entitledCents, overrideCents)/100;
|
||||
if (!user) return TIERS[0];
|
||||
return getUserTier(user);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
width="120px"
|
||||
class="ma-3"
|
||||
/>
|
||||
<!--
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
type="text"
|
||||
@@ -51,7 +50,6 @@
|
||||
Register
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
-->
|
||||
</v-layout>
|
||||
</v-form>
|
||||
<v-divider class="ma-4" />
|
||||
@@ -59,7 +57,6 @@
|
||||
column
|
||||
align-center
|
||||
>
|
||||
<!--
|
||||
<div class="error--text">
|
||||
{{ googleError }}
|
||||
</div>
|
||||
@@ -69,7 +66,6 @@
|
||||
>
|
||||
Sign in with Google
|
||||
</v-btn>
|
||||
-->
|
||||
<div class="error--text">
|
||||
{{ patreonError }}
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,12 +5,29 @@
|
||||
Invite
|
||||
</v-toolbar-title>
|
||||
</template>
|
||||
<div v-if="invite.invitee" />
|
||||
<div v-else>
|
||||
<div
|
||||
v-if="invite.invitee"
|
||||
class="layout column align-center"
|
||||
>
|
||||
{{ username || invite.invitee }}
|
||||
<div>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="revokeInvite"
|
||||
>
|
||||
Revoke Invite
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="layout column align-center"
|
||||
>
|
||||
<p>This invite is available</p>
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-btn
|
||||
v-if="!inviteLink"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="getInviteLink"
|
||||
@@ -27,7 +44,7 @@
|
||||
|
||||
<script>
|
||||
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});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user