mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
Merge branch 'release' into develop
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "4.249.0",
|
"version": "4.249.3",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||||
"version": "4.249.0",
|
"version": "4.249.3",
|
||||||
"main": "./website/server/index.js",
|
"main": "./website/server/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.19.6",
|
"@babel/core": "^7.19.6",
|
||||||
|
|||||||
@@ -417,6 +417,7 @@ describe('Apple Payments', () => {
|
|||||||
|
|
||||||
it('errors when a user is using the same subscription', async () => {
|
it('errors when a user is using the same subscription', async () => {
|
||||||
user = new User();
|
user = new User();
|
||||||
|
user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
await user.save();
|
await user.save();
|
||||||
payments.createSubscription.restore();
|
payments.createSubscription.restore();
|
||||||
|
|
||||||
@@ -430,6 +431,8 @@ describe('Apple Payments', () => {
|
|||||||
}]);
|
}]);
|
||||||
|
|
||||||
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing);
|
||||||
|
user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
|||||||
@@ -370,6 +370,10 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Purchasing a subscription for self', () => {
|
context('Purchasing a subscription for self', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a subscription', async () => {
|
it('creates a subscription', async () => {
|
||||||
expect(user.purchased.plan.planId).to.not.exist;
|
expect(user.purchased.plan.planId).to.not.exist;
|
||||||
|
|
||||||
@@ -396,6 +400,7 @@ describe('payments/index', () => {
|
|||||||
user.purchased.plan = plan;
|
user.purchased.plan = plan;
|
||||||
user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months');
|
user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months');
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
@@ -406,6 +411,7 @@ describe('payments/index', () => {
|
|||||||
user.purchased.plan = plan;
|
user.purchased.plan = plan;
|
||||||
user.purchased.plan.dateTerminated = moment(new Date()).subtract(2, 'months');
|
user.purchased.plan.dateTerminated = moment(new Date()).subtract(2, 'months');
|
||||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
@@ -415,6 +421,7 @@ describe('payments/index', () => {
|
|||||||
it('does not reset Gold-to-Gems cap on additional subscription', async () => {
|
it('does not reset Gold-to-Gems cap on additional subscription', async () => {
|
||||||
user.purchased.plan = plan;
|
user.purchased.plan = plan;
|
||||||
user.purchased.plan.gemsBought = 10;
|
user.purchased.plan.gemsBought = 10;
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
@@ -551,6 +558,10 @@ describe('payments/index', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('Block subscription perks', () => {
|
context('Block subscription perks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
|
});
|
||||||
|
|
||||||
it('adds block months to plan.consecutive.offset', async () => {
|
it('adds block months to plan.consecutive.offset', async () => {
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
@@ -587,6 +598,7 @@ describe('payments/index', () => {
|
|||||||
data.sub.key = 'basic_12mo';
|
data.sub.key = 'basic_12mo';
|
||||||
|
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
await api.createSubscription(data);
|
await api.createSubscription(data);
|
||||||
|
|
||||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||||
@@ -753,6 +765,7 @@ describe('payments/index', () => {
|
|||||||
now: mayMysteryItemTimeframe,
|
now: mayMysteryItemTimeframe,
|
||||||
toFake: ['Date'],
|
toFake: ['Date'],
|
||||||
});
|
});
|
||||||
|
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
10
website/client/src/assets/svg/gem-red.svg
Normal file
10
website/client/src/assets/svg/gem-red.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path fill="#DE3F3F" d="M0 5.667 3.333 1h9.334L16 5.667l-8 8.666z"/>
|
||||||
|
<path fill="#FFF" opacity=".25" d="M4.667 5.533 4 2.333h4zM11.333 5.533l.667-3.2H8z"/>
|
||||||
|
<path fill="#FFF" opacity=".5" d="M4.667 5.533 8 2.333l3.333 3.2zM1.733 5.533 4 2.333l.667 3.2z"/>
|
||||||
|
<path fill="#34313A" opacity=".11" d="M14.267 5.533 12 2.333l-.667 3.2zM1.733 5.533h2.934L8 12.4z"/>
|
||||||
|
<path fill="#FFF" opacity=".5" d="M14.267 5.533h-2.934L8 12.4z"/>
|
||||||
|
<path fill="#FFF" opacity=".25" d="M4.667 5.533h6.666L8 12.4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 675 B |
10
website/client/src/assets/svg/hourglass-red.svg
Normal file
10
website/client/src/assets/svg/hourglass-red.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M3 12.606v1.778c0 .208.093.408.262.53 1.842 1.347 6.923 1.347 8.766 0a.655.655 0 0 0 .26-.53v-1.778c0-1.621-.831-3.177-2.091-4.104a.666.666 0 0 1 0-1.08c1.26-.927 2.092-2.483 2.092-4.105V1.54a.652.652 0 0 0-.261-.53c-1.843-1.346-6.924-1.346-8.766 0A.65.65 0 0 0 3 1.54v1.777c0 1.622.832 3.178 2.092 4.105.368.27.368.81 0 1.08C3.832 9.429 3 10.985 3 12.606" fill="#F19595"/>
|
||||||
|
<path d="M7.644 1.327c1.51 0 2.684.274 3.318.587v1.403c0 1.169-.594 2.332-1.551 3.036a2.006 2.006 0 0 0-.818 1.609c0 .63.305 1.232.817 1.608.958.705 1.552 1.868 1.552 3.036v1.404c-.634.313-1.809.587-3.318.587-1.508 0-2.683-.274-3.317-.587v-1.404c0-1.168.594-2.331 1.551-3.035.513-.377.817-.978.817-1.609 0-.63-.304-1.232-.816-1.609-.958-.704-1.552-1.867-1.552-3.036V1.914c.634-.313 1.809-.587 3.317-.587" fill-opacity=".9" fill="#FFF"/>
|
||||||
|
<path d="M7.797 2.324c-1.132 0-2.331.105-2.343.385-.01.226-.005.664.914 1.13.893.453 1.06 1.282 1.546 1.282.564 0 .596-.477 1.284-.95.71-.488.823-1.148.815-1.408-.011-.363-1.084-.439-2.216-.439" fill="#DE3F3F"/>
|
||||||
|
<path d="M9.198 4.17c.71-.487.823-1.146.815-1.407-.009-.288-.684-.395-1.526-.427.236.12.543.377.467.88-.078.525-.904 1.105-.77 1.568.025.09.069.162.124.221.247-.17.408-.502.89-.835" fill="#B01515"/>
|
||||||
|
<path d="M7.644 9.17c-.344 0-.433.628-.933 1.018-.613.478-1.196 1.067-1.356 1.914-.131.698-.012.785.148.834.16.049 1.386.257 2.588 0 1.203-.258 1.87-.737 1.755-1.227-.111-.466-.448-.865-1.068-1.325-.593-.44-.79-1.214-1.134-1.214" fill="#DE3F3F"/>
|
||||||
|
<path d="M5.503 12.936c.16.05 1.386.257 2.588 0 .956-.205 1.574-.55 1.729-.929a.096.096 0 0 0-.005-.023c-.067-.256-1.073-.41-2.325-.207-1.192.192-2.158.586-2.153 1.03.037.08.097.108.166.129" fill="#B01515"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
<transactions
|
<transactions
|
||||||
:hero="hero"
|
:hero="hero"
|
||||||
|
:reset-counter="resetCounter"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<contributor-details
|
<contributor-details
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
resetCounter: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -38,6 +42,14 @@ export default {
|
|||||||
hourglassTransactions: [],
|
hourglassTransactions: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
resetCounter () {
|
||||||
|
if (this.expand) {
|
||||||
|
this.expand = !this.expand;
|
||||||
|
this.toggleTransactionsOpen();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async toggleTransactionsOpen () {
|
async toggleTransactionsOpen () {
|
||||||
this.expand = !this.expand;
|
this.expand = !this.expand;
|
||||||
|
|||||||
@@ -1,105 +1,308 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div>
|
||||||
<div class="col-6">
|
<div class="clearfix">
|
||||||
<h1>{{ $t('gemTransactions') }}</h1>
|
<div class="mb-4 float-left">
|
||||||
<span v-if="gemTransactions.length === 0">{{ $t('noGemTransactions') }}</span>
|
<button
|
||||||
<table class="table">
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
<tr
|
:class="{'active': selectedTab === 'gems'}"
|
||||||
v-for="entry in gemTransactions"
|
@click="selectTab('gems')"
|
||||||
:key="entry.createdAt"
|
|
||||||
>
|
>
|
||||||
<td>
|
{{ $t('gems') }}
|
||||||
<span
|
</button>
|
||||||
v-b-tooltip.hover="entry.createdAt"
|
<button
|
||||||
>{{ entry.createdAt | timeAgo }}</span>
|
class="page-header btn-flat tab-button textCondensed"
|
||||||
</td>
|
:class="{'active': selectedTab === 'hourglass'}"
|
||||||
<td>
|
@click="selectTab('hourglass')"
|
||||||
<span
|
|
||||||
class="svg-icon inline icon-24"
|
|
||||||
aria-hidden="true"
|
|
||||||
v-html="icons.gem"
|
|
||||||
></span>
|
|
||||||
<span
|
|
||||||
class="amount gems"
|
|
||||||
:class="entry.amount < 0 ? 'deducted' : 'added'"
|
|
||||||
>{{ entry.amount * 4 }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span>{{ transactionTypeText(entry.transactionType) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span v-html="entryReferenceText(entry)"></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<h1>{{ $t('hourglassTransactions') }}</h1>
|
|
||||||
<span v-if="hourglassTransactions.length === 0">{{ $t('noHourglassTransactions') }}</span>
|
|
||||||
<table class="table">
|
|
||||||
<tr
|
|
||||||
v-for="entry in hourglassTransactions"
|
|
||||||
:key="entry.createdAt"
|
|
||||||
>
|
>
|
||||||
<td>
|
{{ $t('mysticHourglass', { amount: ''}) }}
|
||||||
<span
|
</button>
|
||||||
v-b-tooltip.hover="entry.createdAt"
|
</div>
|
||||||
>{{ entry.createdAt | timeAgo }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span
|
|
||||||
class="svg-icon inline icon-24"
|
|
||||||
aria-hidden="true"
|
|
||||||
v-html="icons.hourglass"
|
|
||||||
></span>
|
|
||||||
<span
|
|
||||||
class="amount hourglasses"
|
|
||||||
:class="entry.amount < 0 ? 'deducted' : 'added'"
|
|
||||||
>{{ entry.amount }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span>{{ transactionTypeText(entry.transactionType) }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span v-html="entryReferenceText(entry)"></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12" v-if="selectedTab === 'gems'">
|
||||||
|
<span v-if="gemTransactions.length === 0">
|
||||||
|
{{ $t('noGemTransactions') }}
|
||||||
|
</span>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th v-once class="timestamp-column">
|
||||||
|
{{ $t('timestamp')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="amount-column">
|
||||||
|
{{ $t('amount')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="action-column">
|
||||||
|
{{ $t('action')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="note-column">
|
||||||
|
{{ $t('note')}}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in gemTransactions"
|
||||||
|
:key="entry.createdAt"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.createdAt"
|
||||||
|
>{{ entry.createdAt | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="amount-with-icon" :id="entry.id">
|
||||||
|
<span
|
||||||
|
class="svg-icon inline icon-16 my-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
v-html="entry.amount < 0 ? icons.gemRed : icons.gem"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="amount gems"
|
||||||
|
:class="entry.amount | addedDeducted"
|
||||||
|
>{{ entry.amount * 4 }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-popover
|
||||||
|
v-if="typeof entry.currentAmount !== 'undefined'"
|
||||||
|
ref="popover"
|
||||||
|
:target="entry.id"
|
||||||
|
triggers="hover focus click"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<div class="remaining-amount-popover-content">
|
||||||
|
{{ $t('remainingBalance') }}:
|
||||||
|
<span
|
||||||
|
class="svg-icon inline icon-16 ml-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
v-html="icons.gem"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="amount gems"
|
||||||
|
>{{ entry.currentAmount * 4 }}</span>
|
||||||
|
</div>
|
||||||
|
</b-popover>
|
||||||
|
</td>
|
||||||
|
<td class="entry-action">
|
||||||
|
<span v-html="transactionTypeText(entry.transactionType)"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="transactionTypes.gifted.includes(entry.transactionType)">
|
||||||
|
<router-link
|
||||||
|
class="user-link"
|
||||||
|
:to="{'name': 'userProfile', 'params': {'userId': entry.reference}}"
|
||||||
|
>
|
||||||
|
@{{ entry.referenceText }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<span v-else-if="transactionTypes.challenges.includes(entry.transactionType)">
|
||||||
|
<router-link
|
||||||
|
class="challenge-link"
|
||||||
|
:to="{ name: 'challenge', params: { challengeId: entry.reference } }">
|
||||||
|
<span
|
||||||
|
v-markdown="entry.referenceText"
|
||||||
|
></span>
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
<span v-else v-html="entryReferenceText(entry)"></span>
|
||||||
|
|
||||||
|
<span v-if="entry.reference">
|
||||||
|
({{entry.reference}})
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-12" v-if="selectedTab === 'hourglass'">
|
||||||
|
<span v-if="hourglassTransactions.length === 0">
|
||||||
|
{{ $t('noHourglassTransactions') }}
|
||||||
|
</span>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th v-once class="timestamp-column">
|
||||||
|
{{ $t('timestamp')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="amount-column">
|
||||||
|
{{ $t('amount')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="action-column">
|
||||||
|
{{ $t('action')}}
|
||||||
|
</th>
|
||||||
|
<th v-once class="note-column">
|
||||||
|
{{ $t('note')}}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-for="entry in hourglassTransactions"
|
||||||
|
:key="entry.createdAt"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
v-b-tooltip.hover="entry.createdAt"
|
||||||
|
>{{ entry.createdAt | timeAgo }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="amount-with-icon" :id="entry.id">
|
||||||
|
<span
|
||||||
|
class="svg-icon inline icon-16 my-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
v-html="entry.amount < 0 ? icons.hourglassRed : icons.hourglass"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="amount hourglasses"
|
||||||
|
:class="entry.amount | addedDeducted"
|
||||||
|
>{{ entry.amount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b-popover
|
||||||
|
v-if="typeof entry.currentAmount !== 'undefined'"
|
||||||
|
ref="popover"
|
||||||
|
:target="entry.id"
|
||||||
|
triggers="hover focus click"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<div class="remaining-amount-popover-content">
|
||||||
|
{{ $t('remainingBalance') }}:
|
||||||
|
<span
|
||||||
|
class="svg-icon inline icon-16 ml-1"
|
||||||
|
aria-hidden="true"
|
||||||
|
v-html="icons.hourglass"
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="amount gems"
|
||||||
|
>{{ entry.currentAmount }}</span>
|
||||||
|
</div>
|
||||||
|
</b-popover>
|
||||||
|
</td>
|
||||||
|
<td class="entry-action">
|
||||||
|
<span v-html="transactionTypeText(entry.transactionType)"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-html="entryReferenceText(entry)"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.page-header.btn-flat {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
height: 2rem;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-stretch: condensed;
|
||||||
|
line-height: 1.33;
|
||||||
|
letter-spacing: normal;
|
||||||
|
color: $gray-10;
|
||||||
|
|
||||||
|
margin-right: 1.125rem;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 2.5rem;
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
color: $purple-300;
|
||||||
|
box-shadow: 0px -0.25rem 0px $purple-300 inset;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-column {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.1rem;
|
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.added::before {
|
.added::before {
|
||||||
content: "+";
|
content: "+";
|
||||||
}
|
}
|
||||||
|
|
||||||
.gems {
|
.gems {
|
||||||
color: $gems-color;
|
color: $green-10;
|
||||||
|
|
||||||
&.deducted {
|
&.deducted {
|
||||||
color: $red-10;
|
color: $maroon-50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourglasses {
|
.hourglasses {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $hourglass-color;
|
color: $green-10;
|
||||||
&.deducted {
|
&.deducted {
|
||||||
color: $red-10;
|
color: $maroon-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-with-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remaining-amount-popover-content {
|
||||||
|
display: flex;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.33;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
line-height: 1.71;
|
||||||
|
color: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
border-top: 0 !important;
|
||||||
|
padding: 0.25rem 0.5rem !important;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.71;
|
||||||
|
color: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-left: 0.5rem !important;
|
||||||
|
padding-right: 0.5rem !important;
|
||||||
|
|
||||||
|
line-height: 1.71;
|
||||||
|
color: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding-top: 0.35rem !important;
|
||||||
|
padding-bottom: 0.35rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp-column, .action-column {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-column {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-column {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.challenge-link, .user-link {
|
||||||
|
color: $blue-10 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-action {
|
||||||
|
b {
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -107,9 +310,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import svgGem from '@/assets/svg/gem.svg';
|
import svgGem from '@/assets/svg/gem.svg';
|
||||||
|
import svgGemRed from '@/assets/svg/gem-red.svg';
|
||||||
import svgHourglass from '@/assets/svg/hourglass.svg';
|
import svgHourglass from '@/assets/svg/hourglass.svg';
|
||||||
|
import svgHourglassRed from '@/assets/svg/hourglass-red.svg';
|
||||||
|
import markdownDirective from '@/directives/markdown';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
directives: {
|
||||||
|
markdown: markdownDirective,
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
timeAgo (value) {
|
timeAgo (value) {
|
||||||
return moment(value).fromNow();
|
return moment(value).fromNow();
|
||||||
@@ -118,6 +327,13 @@ export default {
|
|||||||
// @TODO: Vue doesn't support this so we cant user preference
|
// @TODO: Vue doesn't support this so we cant user preference
|
||||||
return moment(value).toDate().toString();
|
return moment(value).toDate().toString();
|
||||||
},
|
},
|
||||||
|
addedDeducted (amount) {
|
||||||
|
if (amount === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return amount < 0 ? 'deducted' : 'added';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
gemTransactions: {
|
gemTransactions: {
|
||||||
@@ -133,11 +349,21 @@ export default {
|
|||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
gem: svgGem,
|
gem: svgGem,
|
||||||
|
gemRed: svgGemRed,
|
||||||
hourglass: svgHourglass,
|
hourglass: svgHourglass,
|
||||||
|
hourglassRed: svgHourglassRed,
|
||||||
|
}),
|
||||||
|
selectedTab: 'gems',
|
||||||
|
transactionTypes: Object.freeze({
|
||||||
|
gifted: ['gift_send', 'gift_receive'],
|
||||||
|
challenges: ['create_challenge', 'create_bank_challenge'],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
selectTab (type) {
|
||||||
|
this.selectedTab = type;
|
||||||
|
},
|
||||||
entryReferenceText (entry) {
|
entryReferenceText (entry) {
|
||||||
if (entry.reference === undefined && entry.referenceText === undefined) {
|
if (entry.reference === undefined && entry.referenceText === undefined) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -830,8 +830,8 @@
|
|||||||
"backgrounds112022": "SET 102: Released November 2022",
|
"backgrounds112022": "SET 102: Released November 2022",
|
||||||
"backgroundAmongGiantMushroomsText": "Among Giant Mushrooms",
|
"backgroundAmongGiantMushroomsText": "Among Giant Mushrooms",
|
||||||
"backgroundAmongGiantMushroomsNotes": "Marvel at Giant Mushrooms.",
|
"backgroundAmongGiantMushroomsNotes": "Marvel at Giant Mushrooms.",
|
||||||
"backgroundMistAutumnForestText": "Misty Autumn Forest",
|
"backgroundMistyAutumnForestText": "Misty Autumn Forest",
|
||||||
"backgroundMistAutumnForestNotes": "Wander through a Misty Autumn Forest.",
|
"backgroundMistyAutumnForestNotes": "Wander through a Misty Autumn Forest.",
|
||||||
"backgroundAutumnBridgeText": "Bridge in Autumn",
|
"backgroundAutumnBridgeText": "Bridge in Autumn",
|
||||||
"backgroundAutumnBridgeNotes": "Admire the beauty of a Bridge in Autumn.",
|
"backgroundAutumnBridgeNotes": "Admire the beauty of a Bridge in Autumn.",
|
||||||
|
|
||||||
|
|||||||
@@ -777,7 +777,7 @@
|
|||||||
"questRobotUnlockText": "Unlocks purchasable Robot Eggs in the Market",
|
"questRobotUnlockText": "Unlocks purchasable Robot Eggs in the Market",
|
||||||
|
|
||||||
"rockingReptilesText": "Rocking Reptiles Quest Bundle",
|
"rockingReptilesText": "Rocking Reptiles Quest Bundle",
|
||||||
"rockingReptilesNotes": "Contains 'The Insta-Gator,' 'The Serpent of Distraction,' and 'The Veloci-Rapper.' Available until September 30.",
|
"rockingReptilesNotes": "Contains 'The Insta-Gator,' 'The Serpent of Distraction,' and 'The Veloci-Rapper.' Available until November 30.",
|
||||||
|
|
||||||
"delightfulDinosText": "Delightful Dinos Quest Bundle",
|
"delightfulDinosText": "Delightful Dinos Quest Bundle",
|
||||||
"delightfulDinosNotes": "Contains 'The Pterror-dactyl,' 'The Trampling Triceratops,' and 'The Dinosaur Unearthed.' Available until May 31.",
|
"delightfulDinosNotes": "Contains 'The Pterror-dactyl,' 'The Trampling Triceratops,' and 'The Dinosaur Unearthed.' Available until May 31.",
|
||||||
|
|||||||
@@ -192,27 +192,32 @@
|
|||||||
"everywhere": "Everywhere",
|
"everywhere": "Everywhere",
|
||||||
"onlyPrivateSpaces": "Only in private spaces",
|
"onlyPrivateSpaces": "Only in private spaces",
|
||||||
"bannedSlurUsedInProfile": "Your Display Name or About text contained a slur, and your chat privileges have been revoked.",
|
"bannedSlurUsedInProfile": "Your Display Name or About text contained a slur, and your chat privileges have been revoked.",
|
||||||
|
"timestamp": "Timestamp",
|
||||||
|
"amount": "Amount",
|
||||||
|
"action": "Action",
|
||||||
|
"note": "Note",
|
||||||
|
"remainingBalance": "Remaining Balance",
|
||||||
"transactions": "Transactions",
|
"transactions": "Transactions",
|
||||||
"gemTransactions": "Gem Transactions",
|
|
||||||
"hourglassTransactions": "Hourglass Transactions",
|
"hourglassTransactions": "Hourglass Transactions",
|
||||||
"noGemTransactions": "You don't have any gem transactions yet.",
|
"noGemTransactions": "You don't have any gem transactions yet.",
|
||||||
"noHourglassTransactions": "You don't have any hourglass transactions yet.",
|
"noHourglassTransactions": "You don't have any hourglass transactions yet.",
|
||||||
"transaction_debug": "Debug Action",
|
"transaction_debug": "Debug Action",
|
||||||
"transaction_buy_money": "Bought with money",
|
"transaction_buy_money": "<b>Bought</b> with money",
|
||||||
"transaction_buy_gold": "Bought with gold",
|
"transaction_buy_gold": "<b>Bought</b> with gold",
|
||||||
"transaction_contribution": "Through contribution",
|
"transaction_contribution": "<b>Tier</b> change",
|
||||||
"transaction_spend": "Spent on",
|
"transaction_spend": "<b>Spent</b> on",
|
||||||
"transaction_gift_send": "Gifted to",
|
"transaction_gift_send": "<b>Gifted</b> to",
|
||||||
"transaction_gift_receive": "Received from",
|
"transaction_gift_receive": "<b>Received</b> from",
|
||||||
"transaction_create_challenge": "Created challenge",
|
"transaction_create_challenge": "<b>Created</b> challenge",
|
||||||
|
"transaction_create_bank_challenge": "<b>Created</b> bank challenge",
|
||||||
"transaction_create_bank_challenge": "Created bank challenge",
|
"transaction_create_bank_challenge": "Created bank challenge",
|
||||||
"transaction_create_guild": "Created guild",
|
"transaction_create_guild": "<b>Created</b> guild",
|
||||||
"transaction_change_class": "Changed class",
|
"transaction_change_class": "<b>Class</b> change",
|
||||||
"transaction_rebirth": "Used Orb of Rebirth",
|
"transaction_rebirth": "Used Orb of Rebirth",
|
||||||
"transaction_release_pets": "Released pets",
|
"transaction_release_pets": "Released pets",
|
||||||
"transaction_release_mounts": "Released mounts",
|
"transaction_release_mounts": "Released mounts",
|
||||||
"transaction_reroll": "Used Fortify Potion",
|
"transaction_reroll": "Used Fortify Potion",
|
||||||
"transaction_subscription_perks": "From subscription perk",
|
"transaction_subscription_perks": "<b>Subscription</b> perk",
|
||||||
"transaction_admin_update_balance": "Admin given",
|
"transaction_admin_update_balance": "<b>Admin</b> given",
|
||||||
"transaction_admin_update_hourglasses": "Admin updated"
|
"transaction_admin_update_hourglasses": "<b>Admin</b> updated"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,8 +208,9 @@ const bundles = {
|
|||||||
'snake',
|
'snake',
|
||||||
'velociraptor',
|
'velociraptor',
|
||||||
],
|
],
|
||||||
|
event: EVENTS.bundle202211,
|
||||||
canBuy () {
|
canBuy () {
|
||||||
return moment().isBetween('2019-09-10', '2019-10-02');
|
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||||
},
|
},
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
value: 7,
|
value: 7,
|
||||||
|
|||||||
@@ -9,9 +9,21 @@ const gemsPromo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const EVENTS = {
|
export const EVENTS = {
|
||||||
|
noEvent: {
|
||||||
|
start: '2022-11-30T20:00-05:00',
|
||||||
|
end: '2022-12-20T08:00-05:00',
|
||||||
|
season: 'normal',
|
||||||
|
npcImageSuffix: '',
|
||||||
|
},
|
||||||
|
bundle202211: {
|
||||||
|
start: '2022-11-15T08:00-05:00',
|
||||||
|
end: '2022-11-30T20:00-05:00',
|
||||||
|
season: 'normal',
|
||||||
|
npcImageSuffix: '',
|
||||||
|
},
|
||||||
afterGala: {
|
afterGala: {
|
||||||
start: '2022-10-31T20:00-04:00',
|
start: '2022-10-31T20:00-04:00',
|
||||||
end: '2022-12-21T08:00-04:00',
|
end: '2022-11-15T08:00-05:00',
|
||||||
season: 'normal',
|
season: 'normal',
|
||||||
npcImageSuffix: '',
|
npcImageSuffix: '',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -122,26 +122,26 @@ const premium = {
|
|||||||
value: 2,
|
value: 2,
|
||||||
text: t('hatchingPotionEmber'),
|
text: t('hatchingPotionEmber'),
|
||||||
limited: true,
|
limited: true,
|
||||||
event: EVENTS.potions202111,
|
event: EVENTS.bundle202211,
|
||||||
_addlNotes: t('eventAvailabilityReturning', {
|
_addlNotes: t('eventAvailabilityReturning', {
|
||||||
availableDate: t('dateEndNovember'),
|
availableDate: t('dateEndNovember'),
|
||||||
previousDate: t('novemberYYYY', { year: 2019 }),
|
previousDate: t('novemberYYYY', { year: 2021 }),
|
||||||
}),
|
}),
|
||||||
canBuy () {
|
canBuy () {
|
||||||
return moment().isBefore(EVENTS.potions202111.end);
|
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Thunderstorm: {
|
Thunderstorm: {
|
||||||
value: 2,
|
value: 2,
|
||||||
text: t('hatchingPotionThunderstorm'),
|
text: t('hatchingPotionThunderstorm'),
|
||||||
limited: true,
|
limited: true,
|
||||||
event: EVENTS.potions202108,
|
event: EVENTS.bundle202211,
|
||||||
_addlNotes: t('eventAvailabilityReturning', {
|
_addlNotes: t('eventAvailabilityReturning', {
|
||||||
availableDate: t('dateEndAugust'),
|
availableDate: t('dateEndNovember'),
|
||||||
previousDate: t('novemberYYYY', { year: 2019 }),
|
previousDate: t('novemberYYYY', { year: 2021 }),
|
||||||
}),
|
}),
|
||||||
canBuy () {
|
canBuy () {
|
||||||
return moment().isBetween(EVENTS.potions202108.start, EVENTS.potions202108.end);
|
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spooky: {
|
Spooky: {
|
||||||
@@ -251,12 +251,13 @@ const premium = {
|
|||||||
value: 2,
|
value: 2,
|
||||||
text: t('hatchingPotionFrost'),
|
text: t('hatchingPotionFrost'),
|
||||||
limited: true,
|
limited: true,
|
||||||
|
event: EVENTS.bundle202211,
|
||||||
_addlNotes: t('eventAvailabilityReturning', {
|
_addlNotes: t('eventAvailabilityReturning', {
|
||||||
availableDate: t('dateEndNovember'),
|
availableDate: t('dateEndNovember'),
|
||||||
previousDate: t('novemberYYYY', { year: 2018 }),
|
previousDate: t('novemberYYYY', { year: 2020 }),
|
||||||
}),
|
}),
|
||||||
canBuy () {
|
canBuy () {
|
||||||
return moment().isBefore('2020-12-02');
|
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IcySnow: {
|
IcySnow: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { EVENTS } from './constants';
|
|||||||
// path: 'premiumHatchingPotions.Rainbow',
|
// path: 'premiumHatchingPotions.Rainbow',
|
||||||
const featuredItems = {
|
const featuredItems = {
|
||||||
market () {
|
market () {
|
||||||
if (moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end)) {
|
if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'armoire',
|
type: 'armoire',
|
||||||
@@ -13,15 +13,15 @@ const featuredItems = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'premiumHatchingPotion',
|
type: 'premiumHatchingPotion',
|
||||||
path: 'premiumHatchingPotions.Vampire',
|
path: 'premiumHatchingPotions.Frost',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'premiumHatchingPotion',
|
type: 'premiumHatchingPotion',
|
||||||
path: 'premiumHatchingPotions.Ghost',
|
path: 'premiumHatchingPotions.Ember',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'premiumHatchingPotion',
|
type: 'premiumHatchingPotion',
|
||||||
path: 'premiumHatchingPotions.Shadow',
|
path: 'premiumHatchingPotions.Thunderstorm',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -32,47 +32,47 @@ const featuredItems = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'food',
|
type: 'food',
|
||||||
path: 'food.Potatoe',
|
path: 'food.Milk',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'hatchingPotions',
|
type: 'hatchingPotions',
|
||||||
path: 'hatchingPotions.Desert',
|
path: 'hatchingPotions.White',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'eggs',
|
type: 'eggs',
|
||||||
path: 'eggs.Dragon',
|
path: 'eggs.Fox',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
quests () {
|
quests () {
|
||||||
if (moment().isBetween(EVENTS.bundle202210.start, EVENTS.bundle202210.end)) {
|
if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'bundles',
|
type: 'bundles',
|
||||||
path: 'bundles.witchyFamiliars',
|
path: 'bundles.rockingReptiles',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
path: 'quests.snake',
|
path: 'quests.peacock',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
path: 'quests.owl',
|
path: 'quests.harpy',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
path: 'quests.guineapig',
|
path: 'quests.axolotl',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
path: 'quests.onyx',
|
path: 'quests.stone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'quests',
|
type: 'quests',
|
||||||
path: 'quests.rooster',
|
path: 'quests.whale',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ api.updateHero = {
|
|||||||
const hero = await User.findById(heroId).exec();
|
const hero = await User.findById(heroId).exec();
|
||||||
if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId }));
|
if (!hero) throw new NotFound(res.t('userWithIDNotFound', { userId: heroId }));
|
||||||
|
|
||||||
if (updateData.balance) {
|
if (updateData.balance && updateData.balance !== hero.balance) {
|
||||||
await hero.updateBalance(updateData.balance - hero.balance, 'admin_update_balance', '', 'Given by Habitica staff');
|
await hero.updateBalance(updateData.balance - hero.balance, 'admin_update_balance', '', 'Given by Habitica staff');
|
||||||
|
|
||||||
hero.balance = updateData.balance;
|
hero.balance = updateData.balance;
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { // eslint-disable-line import/no-cycle
|
|||||||
model as Group,
|
model as Group,
|
||||||
basicFields as basicGroupFields,
|
basicFields as basicGroupFields,
|
||||||
} from '../../models/group';
|
} from '../../models/group';
|
||||||
import { model as User } from '../../models/user'; // eslint-disable-line import/no-cycle
|
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
NotFound,
|
NotFound,
|
||||||
|
TooManyRequests,
|
||||||
} from '../errors';
|
} from '../errors';
|
||||||
import shared from '../../../common';
|
import shared from '../../../common';
|
||||||
import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle
|
import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle
|
||||||
@@ -92,19 +92,9 @@ async function prepareSubscriptionValues (data) {
|
|||||||
let emailType = 'subscription-begins';
|
let emailType = 'subscription-begins';
|
||||||
let recipientIsSubscribed = recipient.isSubscribed();
|
let recipientIsSubscribed = recipient.isSubscribed();
|
||||||
|
|
||||||
if (data.user && !data.gift && !data.groupId) {
|
if (data.user && !data.gift && !data.groupId && data.customerId !== 'group-plan') {
|
||||||
const unlockedUser = await User.findOneAndUpdate(
|
if (moment().diff(data.user.purchased.plan.dateUpdated, 'minutes') < 3) {
|
||||||
{
|
throw new TooManyRequests('Subscription already processed, likely duplicate request');
|
||||||
_id: data.user._id,
|
|
||||||
$or: [
|
|
||||||
{ _subSignature: 'NOT_RUNNING' },
|
|
||||||
{ _subSignature: { $exists: false } },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ $set: { _subSignature: 'SUB_IN_PROGRESS' } },
|
|
||||||
);
|
|
||||||
if (!unlockedUser) {
|
|
||||||
throw new NotFound('User not found or subscription already processing.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +346,10 @@ async function createSubscription (data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (group) await group.save();
|
||||||
|
if (data.user && data.user.isModified()) await data.user.save();
|
||||||
|
if (data.gift) await data.gift.member.save();
|
||||||
|
|
||||||
slack.sendSubscriptionNotification({
|
slack.sendSubscriptionNotification({
|
||||||
buyer: {
|
buyer: {
|
||||||
id: data.user._id,
|
id: data.user._id,
|
||||||
@@ -372,24 +366,6 @@ async function createSubscription (data) {
|
|||||||
groupId,
|
groupId,
|
||||||
autoRenews,
|
autoRenews,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (group) {
|
|
||||||
await group.save();
|
|
||||||
}
|
|
||||||
if (data.user) {
|
|
||||||
if (data.user.isModified()) {
|
|
||||||
await data.user.save();
|
|
||||||
}
|
|
||||||
if (!data.gift && !data.groupId) {
|
|
||||||
await User.findOneAndUpdate(
|
|
||||||
{ _id: data.user._id },
|
|
||||||
{ $set: { _subSignature: 'NOT_RUNNING' } },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.gift) {
|
|
||||||
await data.gift.member.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancels a subscription or group plan, setting termination to happen later
|
// Cancels a subscription or group plan, setting termination to happen later
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import baseModel from '../libs/baseModel';
|
|||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
export const currencies = ['gems', 'hourglasses'];
|
export const currencies = ['gems', 'hourglasses'];
|
||||||
export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'admin_update_balance', 'admin_update_hourglasses'];
|
export const transactionTypes = ['buy_money', 'buy_gold', 'spend', 'gift_send', 'gifted_with_money', 'gift_receive', 'debug', 'create_challenge', 'create_bank_challenge', 'create_guild', 'change_class', 'rebirth', 'release_pets', 'release_mounts', 'reroll', 'contribution', 'subscription_perks', 'admin_update_balance', 'admin_update_hourglasses'];
|
||||||
|
|
||||||
export const schema = new Schema({
|
export const schema = new Schema({
|
||||||
currency: { $type: String, enum: currencies, required: true },
|
currency: { $type: String, enum: currencies, required: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user