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",
|
||||
"version": "4.249.0",
|
||||
"version": "4.249.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"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",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
|
||||
@@ -417,6 +417,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors when a user is using the same subscription', async () => {
|
||||
user = new User();
|
||||
user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
await user.save();
|
||||
payments.createSubscription.restore();
|
||||
|
||||
@@ -430,6 +431,8 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
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))
|
||||
.to.eventually.be.rejected.and.to.eql({
|
||||
|
||||
@@ -370,6 +370,10 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
context('Purchasing a subscription for self', () => {
|
||||
beforeEach(() => {
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
});
|
||||
|
||||
it('creates a subscription', async () => {
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
@@ -396,6 +400,7 @@ describe('payments/index', () => {
|
||||
user.purchased.plan = plan;
|
||||
user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months');
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -406,6 +411,7 @@ describe('payments/index', () => {
|
||||
user.purchased.plan = plan;
|
||||
user.purchased.plan.dateTerminated = moment(new Date()).subtract(2, 'months');
|
||||
expect(user.purchased.plan.extraMonths).to.eql(0);
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -415,6 +421,7 @@ describe('payments/index', () => {
|
||||
it('does not reset Gold-to-Gems cap on additional subscription', async () => {
|
||||
user.purchased.plan = plan;
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -551,6 +558,10 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
context('Block subscription perks', () => {
|
||||
beforeEach(() => {
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
});
|
||||
|
||||
it('adds block months to plan.consecutive.offset', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
@@ -587,6 +598,7 @@ describe('payments/index', () => {
|
||||
data.sub.key = 'basic_12mo';
|
||||
|
||||
await api.createSubscription(data);
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25);
|
||||
@@ -753,6 +765,7 @@ describe('payments/index', () => {
|
||||
now: mayMysteryItemTimeframe,
|
||||
toFake: ['Date'],
|
||||
});
|
||||
data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate();
|
||||
});
|
||||
|
||||
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
|
||||
:hero="hero"
|
||||
:reset-counter="resetCounter"
|
||||
/>
|
||||
|
||||
<contributor-details
|
||||
|
||||
@@ -30,6 +30,10 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
resetCounter: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -38,6 +42,14 @@ export default {
|
||||
hourglassTransactions: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
resetCounter () {
|
||||
if (this.expand) {
|
||||
this.expand = !this.expand;
|
||||
this.toggleTransactionsOpen();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async toggleTransactionsOpen () {
|
||||
this.expand = !this.expand;
|
||||
|
||||
@@ -1,105 +1,308 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h1>{{ $t('gemTransactions') }}</h1>
|
||||
<span v-if="gemTransactions.length === 0">{{ $t('noGemTransactions') }}</span>
|
||||
<table class="table">
|
||||
<tr
|
||||
v-for="entry in gemTransactions"
|
||||
:key="entry.createdAt"
|
||||
<div>
|
||||
<div class="clearfix">
|
||||
<div class="mb-4 float-left">
|
||||
<button
|
||||
class="page-header btn-flat tab-button textCondensed"
|
||||
:class="{'active': selectedTab === 'gems'}"
|
||||
@click="selectTab('gems')"
|
||||
>
|
||||
<td>
|
||||
<span
|
||||
v-b-tooltip.hover="entry.createdAt"
|
||||
>{{ entry.createdAt | timeAgo }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<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"
|
||||
{{ $t('gems') }}
|
||||
</button>
|
||||
<button
|
||||
class="page-header btn-flat tab-button textCondensed"
|
||||
:class="{'active': selectedTab === 'hourglass'}"
|
||||
@click="selectTab('hourglass')"
|
||||
>
|
||||
<td>
|
||||
<span
|
||||
v-b-tooltip.hover="entry.createdAt"
|
||||
>{{ 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>
|
||||
{{ $t('mysticHourglass', { amount: ''}) }}
|
||||
</button>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@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 {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
.added::before {
|
||||
content: "+";
|
||||
}
|
||||
|
||||
.gems {
|
||||
color: $gems-color;
|
||||
color: $green-10;
|
||||
|
||||
&.deducted {
|
||||
color: $red-10;
|
||||
color: $maroon-50;
|
||||
}
|
||||
}
|
||||
|
||||
.hourglasses {
|
||||
font-weight: bold;
|
||||
color: $hourglass-color;
|
||||
color: $green-10;
|
||||
&.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>
|
||||
@@ -107,9 +310,15 @@
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import svgGem from '@/assets/svg/gem.svg';
|
||||
import svgGemRed from '@/assets/svg/gem-red.svg';
|
||||
import svgHourglass from '@/assets/svg/hourglass.svg';
|
||||
import svgHourglassRed from '@/assets/svg/hourglass-red.svg';
|
||||
import markdownDirective from '@/directives/markdown';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
filters: {
|
||||
timeAgo (value) {
|
||||
return moment(value).fromNow();
|
||||
@@ -118,6 +327,13 @@ export default {
|
||||
// @TODO: Vue doesn't support this so we cant user preference
|
||||
return moment(value).toDate().toString();
|
||||
},
|
||||
addedDeducted (amount) {
|
||||
if (amount === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return amount < 0 ? 'deducted' : 'added';
|
||||
},
|
||||
},
|
||||
props: {
|
||||
gemTransactions: {
|
||||
@@ -133,11 +349,21 @@ export default {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gem: svgGem,
|
||||
gemRed: svgGemRed,
|
||||
hourglass: svgHourglass,
|
||||
hourglassRed: svgHourglassRed,
|
||||
}),
|
||||
selectedTab: 'gems',
|
||||
transactionTypes: Object.freeze({
|
||||
gifted: ['gift_send', 'gift_receive'],
|
||||
challenges: ['create_challenge', 'create_bank_challenge'],
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
selectTab (type) {
|
||||
this.selectedTab = type;
|
||||
},
|
||||
entryReferenceText (entry) {
|
||||
if (entry.reference === undefined && entry.referenceText === undefined) {
|
||||
return '';
|
||||
|
||||
@@ -830,8 +830,8 @@
|
||||
"backgrounds112022": "SET 102: Released November 2022",
|
||||
"backgroundAmongGiantMushroomsText": "Among Giant Mushrooms",
|
||||
"backgroundAmongGiantMushroomsNotes": "Marvel at Giant Mushrooms.",
|
||||
"backgroundMistAutumnForestText": "Misty Autumn Forest",
|
||||
"backgroundMistAutumnForestNotes": "Wander through a Misty Autumn Forest.",
|
||||
"backgroundMistyAutumnForestText": "Misty Autumn Forest",
|
||||
"backgroundMistyAutumnForestNotes": "Wander through a Misty Autumn Forest.",
|
||||
"backgroundAutumnBridgeText": "Bridge in Autumn",
|
||||
"backgroundAutumnBridgeNotes": "Admire the beauty of a Bridge in Autumn.",
|
||||
|
||||
|
||||
@@ -777,7 +777,7 @@
|
||||
"questRobotUnlockText": "Unlocks purchasable Robot Eggs in the Market",
|
||||
|
||||
"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",
|
||||
"delightfulDinosNotes": "Contains 'The Pterror-dactyl,' 'The Trampling Triceratops,' and 'The Dinosaur Unearthed.' Available until May 31.",
|
||||
|
||||
@@ -192,27 +192,32 @@
|
||||
"everywhere": "Everywhere",
|
||||
"onlyPrivateSpaces": "Only in private spaces",
|
||||
"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",
|
||||
"gemTransactions": "Gem Transactions",
|
||||
"hourglassTransactions": "Hourglass Transactions",
|
||||
"noGemTransactions": "You don't have any gem transactions yet.",
|
||||
"noHourglassTransactions": "You don't have any hourglass transactions yet.",
|
||||
"transaction_debug": "Debug Action",
|
||||
"transaction_buy_money": "Bought with money",
|
||||
"transaction_buy_gold": "Bought with gold",
|
||||
"transaction_contribution": "Through contribution",
|
||||
"transaction_spend": "Spent on",
|
||||
"transaction_gift_send": "Gifted to",
|
||||
"transaction_gift_receive": "Received from",
|
||||
"transaction_create_challenge": "Created challenge",
|
||||
"transaction_buy_money": "<b>Bought</b> with money",
|
||||
"transaction_buy_gold": "<b>Bought</b> with gold",
|
||||
"transaction_contribution": "<b>Tier</b> change",
|
||||
"transaction_spend": "<b>Spent</b> on",
|
||||
"transaction_gift_send": "<b>Gifted</b> to",
|
||||
"transaction_gift_receive": "<b>Received</b> from",
|
||||
"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_guild": "Created guild",
|
||||
"transaction_change_class": "Changed class",
|
||||
"transaction_create_guild": "<b>Created</b> guild",
|
||||
"transaction_change_class": "<b>Class</b> change",
|
||||
"transaction_rebirth": "Used Orb of Rebirth",
|
||||
"transaction_release_pets": "Released pets",
|
||||
"transaction_release_mounts": "Released mounts",
|
||||
"transaction_reroll": "Used Fortify Potion",
|
||||
"transaction_subscription_perks": "From subscription perk",
|
||||
"transaction_admin_update_balance": "Admin given",
|
||||
"transaction_admin_update_hourglasses": "Admin updated"
|
||||
"transaction_subscription_perks": "<b>Subscription</b> perk",
|
||||
"transaction_admin_update_balance": "<b>Admin</b> given",
|
||||
"transaction_admin_update_hourglasses": "<b>Admin</b> updated"
|
||||
}
|
||||
|
||||
@@ -208,8 +208,9 @@ const bundles = {
|
||||
'snake',
|
||||
'velociraptor',
|
||||
],
|
||||
event: EVENTS.bundle202211,
|
||||
canBuy () {
|
||||
return moment().isBetween('2019-09-10', '2019-10-02');
|
||||
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||
},
|
||||
type: 'quests',
|
||||
value: 7,
|
||||
|
||||
@@ -9,9 +9,21 @@ const gemsPromo = {
|
||||
};
|
||||
|
||||
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: {
|
||||
start: '2022-10-31T20:00-04:00',
|
||||
end: '2022-12-21T08:00-04:00',
|
||||
end: '2022-11-15T08:00-05:00',
|
||||
season: 'normal',
|
||||
npcImageSuffix: '',
|
||||
},
|
||||
|
||||
@@ -122,26 +122,26 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionEmber'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202111,
|
||||
event: EVENTS.bundle202211,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndNovember'),
|
||||
previousDate: t('novemberYYYY', { year: 2019 }),
|
||||
previousDate: t('novemberYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore(EVENTS.potions202111.end);
|
||||
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||
},
|
||||
},
|
||||
Thunderstorm: {
|
||||
value: 2,
|
||||
text: t('hatchingPotionThunderstorm'),
|
||||
limited: true,
|
||||
event: EVENTS.potions202108,
|
||||
event: EVENTS.bundle202211,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndAugust'),
|
||||
previousDate: t('novemberYYYY', { year: 2019 }),
|
||||
availableDate: t('dateEndNovember'),
|
||||
previousDate: t('novemberYYYY', { year: 2021 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBetween(EVENTS.potions202108.start, EVENTS.potions202108.end);
|
||||
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||
},
|
||||
},
|
||||
Spooky: {
|
||||
@@ -251,12 +251,13 @@ const premium = {
|
||||
value: 2,
|
||||
text: t('hatchingPotionFrost'),
|
||||
limited: true,
|
||||
event: EVENTS.bundle202211,
|
||||
_addlNotes: t('eventAvailabilityReturning', {
|
||||
availableDate: t('dateEndNovember'),
|
||||
previousDate: t('novemberYYYY', { year: 2018 }),
|
||||
previousDate: t('novemberYYYY', { year: 2020 }),
|
||||
}),
|
||||
canBuy () {
|
||||
return moment().isBefore('2020-12-02');
|
||||
return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end);
|
||||
},
|
||||
},
|
||||
IcySnow: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EVENTS } from './constants';
|
||||
// path: 'premiumHatchingPotions.Rainbow',
|
||||
const featuredItems = {
|
||||
market () {
|
||||
if (moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end)) {
|
||||
if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) {
|
||||
return [
|
||||
{
|
||||
type: 'armoire',
|
||||
@@ -13,15 +13,15 @@ const featuredItems = {
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.Vampire',
|
||||
path: 'premiumHatchingPotions.Frost',
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.Ghost',
|
||||
path: 'premiumHatchingPotions.Ember',
|
||||
},
|
||||
{
|
||||
type: 'premiumHatchingPotion',
|
||||
path: 'premiumHatchingPotions.Shadow',
|
||||
path: 'premiumHatchingPotions.Thunderstorm',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -32,47 +32,47 @@ const featuredItems = {
|
||||
},
|
||||
{
|
||||
type: 'food',
|
||||
path: 'food.Potatoe',
|
||||
path: 'food.Milk',
|
||||
},
|
||||
{
|
||||
type: 'hatchingPotions',
|
||||
path: 'hatchingPotions.Desert',
|
||||
path: 'hatchingPotions.White',
|
||||
},
|
||||
{
|
||||
type: 'eggs',
|
||||
path: 'eggs.Dragon',
|
||||
path: 'eggs.Fox',
|
||||
},
|
||||
];
|
||||
},
|
||||
quests () {
|
||||
if (moment().isBetween(EVENTS.bundle202210.start, EVENTS.bundle202210.end)) {
|
||||
if (moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end)) {
|
||||
return [
|
||||
{
|
||||
type: 'bundles',
|
||||
path: 'bundles.witchyFamiliars',
|
||||
path: 'bundles.rockingReptiles',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.snake',
|
||||
path: 'quests.peacock',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.owl',
|
||||
path: 'quests.harpy',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.guineapig',
|
||||
path: 'quests.axolotl',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.onyx',
|
||||
path: 'quests.stone',
|
||||
},
|
||||
{
|
||||
type: 'quests',
|
||||
path: 'quests.rooster',
|
||||
path: 'quests.whale',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -267,7 +267,7 @@ api.updateHero = {
|
||||
const hero = await User.findById(heroId).exec();
|
||||
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');
|
||||
|
||||
hero.balance = updateData.balance;
|
||||
|
||||
@@ -13,10 +13,10 @@ import { // eslint-disable-line import/no-cycle
|
||||
model as Group,
|
||||
basicFields as basicGroupFields,
|
||||
} from '../../models/group';
|
||||
import { model as User } from '../../models/user'; // eslint-disable-line import/no-cycle
|
||||
import {
|
||||
NotAuthorized,
|
||||
NotFound,
|
||||
TooManyRequests,
|
||||
} from '../errors';
|
||||
import shared from '../../../common';
|
||||
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 recipientIsSubscribed = recipient.isSubscribed();
|
||||
|
||||
if (data.user && !data.gift && !data.groupId) {
|
||||
const unlockedUser = await User.findOneAndUpdate(
|
||||
{
|
||||
_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.');
|
||||
if (data.user && !data.gift && !data.groupId && data.customerId !== 'group-plan') {
|
||||
if (moment().diff(data.user.purchased.plan.dateUpdated, 'minutes') < 3) {
|
||||
throw new TooManyRequests('Subscription already processed, likely duplicate request');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
buyer: {
|
||||
id: data.user._id,
|
||||
@@ -372,24 +366,6 @@ async function createSubscription (data) {
|
||||
groupId,
|
||||
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
|
||||
|
||||
@@ -5,7 +5,7 @@ import baseModel from '../libs/baseModel';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
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({
|
||||
currency: { $type: String, enum: currencies, required: true },
|
||||
|
||||
Reference in New Issue
Block a user