mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Show "Next Hourglass" Month (#13860)
* Show "Next Hourglass" Month * fix lint * lint, * lint * lint.. * linting bad * ui fixes * remove additional margin * show next hourglass date to debug further * WIP tests - maybe broken logic * flex:1 doesn't work - so stats columns now at 33% width * fix(cron): lint and short circuit * refactor logic * update test dates using timezone * also check for the timezone date * fix timezone for tests * fixing the test dates? * fixing the test dates? * change nextHourglass logic + update gem cap label / value * fix lint * dont add gemsBought to it * remove tooltip Co-authored-by: SabreCat <sabe@habitica.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { startOfDay, daysSince } from '../../../website/common/script/cron';
|
import { startOfDay, daysSince, getPlanContext } from '../../../website/common/script/cron';
|
||||||
|
|
||||||
function localMoment (timeString, utcOffset) {
|
function localMoment (timeString, utcOffset) {
|
||||||
return moment(timeString).utcOffset(utcOffset, true);
|
return moment(timeString).utcOffset(utcOffset, true);
|
||||||
@@ -181,4 +181,63 @@ describe('cron utility functions', () => {
|
|||||||
expect(result).to.equal(0);
|
expect(result).to.equal(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getPlanContext', () => {
|
||||||
|
const now = new Date(2022, 5, 1);
|
||||||
|
|
||||||
|
function baseUserData (count, offset, planId) {
|
||||||
|
return {
|
||||||
|
purchased: {
|
||||||
|
plan: {
|
||||||
|
consecutive: {
|
||||||
|
count,
|
||||||
|
offset,
|
||||||
|
gemCapExtra: 25,
|
||||||
|
trinkets: 19,
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
extraMonths: 0,
|
||||||
|
gemsBought: 0,
|
||||||
|
owner: '116b4133-8fb7-43f2-b0de-706621a8c9d8',
|
||||||
|
nextBillingDate: null,
|
||||||
|
nextPaymentProcessing: null,
|
||||||
|
planId,
|
||||||
|
customerId: 'group-plan',
|
||||||
|
dateUpdated: '2022-05-10T03:00:00.144+01:00',
|
||||||
|
paymentMethod: 'Group Plan',
|
||||||
|
dateTerminated: null,
|
||||||
|
lastBillingDate: null,
|
||||||
|
dateCreated: '2017-02-10T19:00:00.355+01:00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('offset 0, next date in 3 months', () => {
|
||||||
|
const user = baseUserData(60, 0, 'group_plan_auto');
|
||||||
|
|
||||||
|
const planContext = getPlanContext(user, now);
|
||||||
|
|
||||||
|
expect(planContext.nextHourglassDate)
|
||||||
|
.to.be.sameMoment('2022-08-10T02:00:00.144Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offset 1, next date in 1 months', () => {
|
||||||
|
const user = baseUserData(60, 1, 'group_plan_auto');
|
||||||
|
|
||||||
|
const planContext = getPlanContext(user, now);
|
||||||
|
|
||||||
|
expect(planContext.nextHourglassDate)
|
||||||
|
.to.be.sameMoment('2022-06-10T02:00:00.144Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offset 2, next date in 2 months - with any plan', () => {
|
||||||
|
const user = baseUserData(60, 2, 'basic_3mo');
|
||||||
|
|
||||||
|
const planContext = getPlanContext(user, now);
|
||||||
|
|
||||||
|
expect(planContext.nextHourglassDate)
|
||||||
|
.to.be.sameMoment('2022-07-10T02:00:00.144Z');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
import getters from '@/store/getters';
|
||||||
|
|
||||||
export const userStyles = {
|
export const userStyles = {
|
||||||
contributor: {
|
contributor: {
|
||||||
@@ -82,3 +83,25 @@ export const userStyles = {
|
|||||||
classSelected: true,
|
classSelected: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export function mockStore ({
|
||||||
|
userData,
|
||||||
|
...state
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
getters,
|
||||||
|
dispatch: () => {
|
||||||
|
},
|
||||||
|
watch: () => {
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
user: {
|
||||||
|
data: {
|
||||||
|
...userData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...state,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { setup as setupPayments } from '@/libs/payments';
|
|||||||
|
|
||||||
setupPayments();
|
setupPayments();
|
||||||
|
|
||||||
storiesOf('Payments Buttons', module)
|
storiesOf('Subscriptions/Payments Buttons', module)
|
||||||
.add('simple', () => ({
|
.add('simple', () => ({
|
||||||
components: { PaymentsButtonsList },
|
components: { PaymentsButtonsList },
|
||||||
template: `
|
template: `
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import { storiesOf } from '@storybook/vue';
|
||||||
|
|
||||||
|
import Subscription from './subscription.vue';
|
||||||
|
import { mockStore } from '../../../config/storybook/mock.data';
|
||||||
|
|
||||||
|
storiesOf('Subscriptions/Detail Page', module)
|
||||||
|
.add('subscribed', () => ({
|
||||||
|
components: { Subscription },
|
||||||
|
template: `
|
||||||
|
<div style="position: absolute; margin: 20px">
|
||||||
|
<subscription ></subscription>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
store: mockStore({
|
||||||
|
userData: {
|
||||||
|
purchased: {
|
||||||
|
plan: {
|
||||||
|
customerId: 'customer-id',
|
||||||
|
planId: 'plan-id',
|
||||||
|
subscriptionId: 'sub-id',
|
||||||
|
gemsBought: 22,
|
||||||
|
dateUpdated: new Date(2021, 0, 15),
|
||||||
|
consecutive: {
|
||||||
|
count: 2,
|
||||||
|
gemCapExtra: 4,
|
||||||
|
offset: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
<div class="subscribe-card mx-auto">
|
<div class="subscribe-card mx-auto">
|
||||||
<div
|
<div
|
||||||
v-if="hasSubscription && !hasCanceledSubscription"
|
v-if="hasSubscription && !hasCanceledSubscription"
|
||||||
class="d-flex flex-column align-items-center my-4"
|
class="d-flex flex-column align-items-center"
|
||||||
>
|
>
|
||||||
<div class="round-container bg-green-10 d-flex align-items-center justify-content-center">
|
<div class="round-container bg-green-10 d-flex align-items-center justify-content-center">
|
||||||
<div
|
<div
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
v-html="icons.checkmarkIcon"
|
v-html="icons.checkmarkIcon"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="green-10 mx-auto">
|
<h2 class="green-10 mx-auto mb-75">
|
||||||
{{ $t('youAreSubscribed') }}
|
{{ $t('youAreSubscribed') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div
|
<div
|
||||||
@@ -180,17 +180,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="hasSubscription"
|
v-if="hasSubscription"
|
||||||
class="bg-gray-700 p-2 text-center"
|
class="bg-gray-700 py-3 mt-4 mb-3 text-center"
|
||||||
>
|
>
|
||||||
<div class="header-mini mb-3">
|
<div class="header-mini mb-3">
|
||||||
{{ $t('subscriptionStats') }}
|
{{ $t('subscriptionStats') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-around">
|
<div class="d-flex">
|
||||||
<div class="ml-4 mr-3">
|
<div class="stat-column">
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="svg-icon svg-calendar mr-2"
|
class="svg-icon svg-calendar mr-1"
|
||||||
v-html="icons.calendarIcon"
|
v-html="icons.calendarIcon"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,49 +204,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-spacer"></div>
|
<div class="stats-spacer"></div>
|
||||||
<div>
|
<div class="stat-column">
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="svg-icon svg-gem mr-2"
|
class="svg-icon svg-gem mr-1"
|
||||||
v-html="icons.gemIcon"
|
v-html="icons.gemIcon"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="number-heavy">
|
<div class="number-heavy">
|
||||||
{{ user.purchased.plan.consecutive.gemCapExtra }}
|
{{ gemCap }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-label">
|
<div class="stats-label">
|
||||||
{{ $t('gemCapExtra') }}
|
{{ $t('gemCap') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-spacer"></div>
|
<div class="stats-spacer"></div>
|
||||||
<div>
|
<div class="stat-column">
|
||||||
<div class="d-flex justify-content-center align-items-center">
|
<div class="d-flex justify-content-center align-items-center">
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="svg-icon svg-hourglass mt-1 mr-2"
|
class="svg-icon svg-hourglass mt-1 mr-1"
|
||||||
v-html="icons.hourglassIcon"
|
v-html="icons.hourglassIcon"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="number-heavy">
|
<div class="number-heavy">
|
||||||
{{ user.purchased.plan.consecutive.trinkets }}
|
{{ nextHourGlass }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-label">
|
<div class="stats-label">
|
||||||
{{ $t('mysticHourglassesTooltip') }}
|
{{ $t('nextHourglass') }}*
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 nextHourglassDescription" v-once>
|
||||||
|
*{{ $t('nextHourglassDescription') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column justify-content-center align-items-center mt-4 mb-3">
|
<div class="d-flex flex-column justify-content-center align-items-center mb-3">
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="svg-icon svg-heart mb-1"
|
class="svg-icon svg-heart mb-2"
|
||||||
v-html="icons.heartIcon"
|
v-html="icons.heartIcon"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-label">
|
<div class="thanks-for-support">
|
||||||
{{ $t('giftSubscriptionText4') }}
|
{{ $t('giftSubscriptionText4') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,7 +354,7 @@
|
|||||||
.cancel-card {
|
.cancel-card {
|
||||||
width: 28rem;
|
width: 28rem;
|
||||||
border: 2px solid $gray-500;
|
border: 2px solid $gray-500;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
@@ -405,7 +409,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.number-heavy {
|
.number-heavy {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: $gray-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Pet-Jackalope-RoyalPurple {
|
.Pet-Jackalope-RoyalPurple {
|
||||||
@@ -423,7 +430,10 @@
|
|||||||
|
|
||||||
.stats-label {
|
.stats-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $gray-200;
|
color: $gray-100;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.33;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-spacer {
|
.stats-spacer {
|
||||||
@@ -433,8 +443,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.subscribe-card {
|
.subscribe-card {
|
||||||
|
padding-top: 2rem;
|
||||||
width: 28rem;
|
width: 28rem;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
}
|
}
|
||||||
@@ -452,7 +463,14 @@
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-calendar, .svg-heart {
|
.svg-calendar {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-heart {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
@@ -479,8 +497,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.svg-gem {
|
.svg-gem {
|
||||||
width: 32px;
|
width: 24px;
|
||||||
height: 28px;
|
height: 24px;
|
||||||
|
|
||||||
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-gems {
|
.svg-gems {
|
||||||
@@ -494,8 +514,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.svg-hourglass {
|
.svg-hourglass {
|
||||||
width: 28px;
|
width: 24px;
|
||||||
height: 28px;
|
height: 24px;
|
||||||
|
|
||||||
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-gift-box {
|
.svg-gift-box {
|
||||||
@@ -521,11 +543,34 @@
|
|||||||
.w-55 {
|
.w-55 {
|
||||||
width: 55%;
|
width: 55%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nextHourglassDescription {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.33;
|
||||||
|
color: $gray-100;
|
||||||
|
margin-left: 100px;
|
||||||
|
margin-right: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-content-evenly {
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thanks-for-support {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.33;
|
||||||
|
text-align: center;
|
||||||
|
color: $gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-column {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import min from 'lodash/min';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
|
|
||||||
@@ -551,6 +596,7 @@ import logo from '@/assets/svg/habitica-logo-purple.svg';
|
|||||||
import paypalLogo from '@/assets/svg/paypal-logo.svg';
|
import paypalLogo from '@/assets/svg/paypal-logo.svg';
|
||||||
import subscriberGems from '@/assets/svg/subscriber-gems.svg';
|
import subscriberGems from '@/assets/svg/subscriber-gems.svg';
|
||||||
import subscriberHourglasses from '@/assets/svg/subscriber-hourglasses.svg';
|
import subscriberHourglasses from '@/assets/svg/subscriber-hourglasses.svg';
|
||||||
|
import { getPlanContext } from '@/../../common/script/cron';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -649,23 +695,9 @@ export default {
|
|||||||
months: parseFloat(this.user.purchased.plan.extraMonths).toFixed(2),
|
months: parseFloat(this.user.purchased.plan.extraMonths).toFixed(2),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
buyGemsGoldCap () {
|
gemCap () {
|
||||||
return {
|
return planGemLimits.convCap
|
||||||
amount: min(this.gemGoldCap),
|
+ this.user.purchased.plan.consecutive.gemCapExtra;
|
||||||
};
|
|
||||||
},
|
|
||||||
gemGoldCap () {
|
|
||||||
const baseCap = 25;
|
|
||||||
const gemCapIncrement = 5;
|
|
||||||
const capIncrementThreshold = 3;
|
|
||||||
const { gemCapExtra } = this.user.purchased.plan.consecutive;
|
|
||||||
const blocks = subscriptionBlocks[this.subscription.key].months / capIncrementThreshold;
|
|
||||||
const flooredBlocks = Math.floor(blocks);
|
|
||||||
|
|
||||||
const userTotalDropCap = baseCap + gemCapExtra + flooredBlocks * gemCapIncrement;
|
|
||||||
const maxDropCap = 50;
|
|
||||||
|
|
||||||
return [userTotalDropCap, maxDropCap];
|
|
||||||
},
|
},
|
||||||
numberOfMysticHourglasses () {
|
numberOfMysticHourglasses () {
|
||||||
const numberOfHourglasses = subscriptionBlocks[this.subscription.key].months / 3;
|
const numberOfHourglasses = subscriptionBlocks[this.subscription.key].months / 3;
|
||||||
@@ -719,6 +751,16 @@ export default {
|
|||||||
subscriptionEndDate () {
|
subscriptionEndDate () {
|
||||||
return moment(this.user.purchased.plan.dateTerminated).format('MM/DD/YYYY');
|
return moment(this.user.purchased.plan.dateTerminated).format('MM/DD/YYYY');
|
||||||
},
|
},
|
||||||
|
nextHourGlassDate () {
|
||||||
|
const currentPlanContext = getPlanContext(this.user, new Date());
|
||||||
|
|
||||||
|
return currentPlanContext.nextHourglassDate;
|
||||||
|
},
|
||||||
|
nextHourGlass () {
|
||||||
|
const nextHourglassMonth = this.nextHourGlassDate.format('MMM');
|
||||||
|
|
||||||
|
return nextHourglassMonth;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$store.dispatch('common:setTitle', {
|
this.$store.dispatch('common:setTitle', {
|
||||||
|
|||||||
@@ -156,9 +156,12 @@
|
|||||||
"purchasedPlanExtraMonths": "You have <strong><%= months %> months</strong> of extra subscription credit.",
|
"purchasedPlanExtraMonths": "You have <strong><%= months %> months</strong> of extra subscription credit.",
|
||||||
"consecutiveSubscription": "Consecutive Subscription",
|
"consecutiveSubscription": "Consecutive Subscription",
|
||||||
"consecutiveMonths": "Consecutive Months:",
|
"consecutiveMonths": "Consecutive Months:",
|
||||||
|
"gemCap": "Gem Cap",
|
||||||
"gemCapExtra": "Gem Cap Bonus",
|
"gemCapExtra": "Gem Cap Bonus",
|
||||||
"mysticHourglasses": "Mystic Hourglasses:",
|
"mysticHourglasses": "Mystic Hourglasses:",
|
||||||
"mysticHourglassesTooltip": "Mystic Hourglasses",
|
"mysticHourglassesTooltip": "Mystic Hourglasses",
|
||||||
|
"nextHourglass": "Next Hourglass",
|
||||||
|
"nextHourglassDescription": "Subscribers receive Mystic Hourglasses within\nthe first three days of the month.",
|
||||||
"paypal": "PayPal",
|
"paypal": "PayPal",
|
||||||
"amazonPayments": "Amazon Payments",
|
"amazonPayments": "Amazon Payments",
|
||||||
"amazonPaymentsRecurring": "Ticking the checkbox below is necessary for your subscription to be created. It allows your Amazon account to be used for ongoing payments for <strong>this</strong> subscription. It will not cause your Amazon account to be automatically used for any future purchases.",
|
"amazonPaymentsRecurring": "Ticking the checkbox below is necessary for your subscription to be created. It allows your Amazon account to be used for ongoing payments for <strong>this</strong> subscription. It will not cause your Amazon account to be automatically used for any future purchases.",
|
||||||
|
|||||||
@@ -250,3 +250,53 @@ export function shouldDo (day, dailyTask, options = {}) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPlanMonths (plan) {
|
||||||
|
// NB gift subscriptions don't have a planID
|
||||||
|
// (which doesn't matter because we don't need to reapply perks
|
||||||
|
// for them and by this point they should have expired anyway)
|
||||||
|
if (!plan.planId) return 1;
|
||||||
|
const planIdRegExp = new RegExp('_([0-9]+)mo'); // e.g., matches 'google_6mo' / 'basic_12mo' and captures '6' / '12'
|
||||||
|
const match = plan.planId.match(planIdRegExp);
|
||||||
|
if (match !== null && match[0] !== null) {
|
||||||
|
// 3 for 3-month recurring subscription, etc
|
||||||
|
return match[1]; // eslint-disable-line prefer-destructuring
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a helper method to get all the needed informations of the plan
|
||||||
|
*
|
||||||
|
* currently used in cron and the "next hourglass in" feature
|
||||||
|
*/
|
||||||
|
export function getPlanContext (user, now) {
|
||||||
|
const { plan } = user.purchased;
|
||||||
|
|
||||||
|
defaults(plan.consecutive, {
|
||||||
|
count: 0, offset: 0, trinkets: 0, gemCapExtra: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const nowMoment = moment(now);
|
||||||
|
|
||||||
|
const subscriptionEndDate = moment(plan.dateTerminated).isBefore()
|
||||||
|
? moment(plan.dateTerminated).startOf('month')
|
||||||
|
: nowMoment.startOf('month');
|
||||||
|
const dateUpdatedMoment = moment(plan.dateUpdated).startOf('month');
|
||||||
|
const elapsedMonths = moment(subscriptionEndDate).diff(dateUpdatedMoment, 'months');
|
||||||
|
|
||||||
|
const monthsTillNextHourglass = plan.consecutive.offset || (3 - (plan.consecutive.count % 3));
|
||||||
|
|
||||||
|
const possibleNextHourglassDate = moment(plan.dateUpdated)
|
||||||
|
.add(monthsTillNextHourglass, 'months');
|
||||||
|
|
||||||
|
return {
|
||||||
|
plan,
|
||||||
|
subscriptionEndDate,
|
||||||
|
dateUpdatedMoment,
|
||||||
|
elapsedMonths,
|
||||||
|
offset: plan.consecutive.offset, // months until the new hourglass is added
|
||||||
|
nextHourglassDate: possibleNextHourglassDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import {
|
|||||||
import content from './content/index';
|
import content from './content/index';
|
||||||
import * as count from './count';
|
import * as count from './count';
|
||||||
// TODO under api.libs.cron?
|
// TODO under api.libs.cron?
|
||||||
import { daysSince, DAY_MAPPING, shouldDo } from './cron';
|
import {
|
||||||
|
daysSince, DAY_MAPPING, shouldDo, getPlanContext, getPlanMonths,
|
||||||
|
} from './cron';
|
||||||
import apiErrors from './errors/apiErrorMessages';
|
import apiErrors from './errors/apiErrorMessages';
|
||||||
import commonErrors from './errors/commonErrorMessages';
|
import commonErrors from './errors/commonErrorMessages';
|
||||||
import autoAllocate from './fns/autoAllocate';
|
import autoAllocate from './fns/autoAllocate';
|
||||||
@@ -93,13 +95,16 @@ import { unEquipByType } from './ops/unequip';
|
|||||||
import getOfficialPinnedItems from './libs/getOfficialPinnedItems';
|
import getOfficialPinnedItems from './libs/getOfficialPinnedItems';
|
||||||
import { sleepAsync } from './libs/sleepAsync';
|
import { sleepAsync } from './libs/sleepAsync';
|
||||||
|
|
||||||
const api = {};
|
const api = {
|
||||||
api.content = content;
|
content,
|
||||||
api.errors = errors;
|
errors,
|
||||||
api.i18n = i18n;
|
i18n,
|
||||||
api.shouldDo = shouldDo;
|
shouldDo,
|
||||||
api.daysSince = daysSince;
|
getPlanContext,
|
||||||
api.DAY_MAPPING = DAY_MAPPING;
|
getPlanMonths,
|
||||||
|
daysSince,
|
||||||
|
DAY_MAPPING,
|
||||||
|
};
|
||||||
|
|
||||||
api.constants = {
|
api.constants = {
|
||||||
MAX_INCENTIVES,
|
MAX_INCENTIVES,
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import { revealMysteryItems } from './payments/subscriptions';
|
|||||||
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
|
||||||
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
|
||||||
const { MAX_INCENTIVES } = common.constants;
|
const { MAX_INCENTIVES } = common.constants;
|
||||||
const { shouldDo } = common;
|
const {
|
||||||
|
shouldDo,
|
||||||
|
i18n,
|
||||||
|
getPlanContext,
|
||||||
|
getPlanMonths,
|
||||||
|
} = common;
|
||||||
const { scoreTask } = common.ops;
|
const { scoreTask } = common.ops;
|
||||||
const { i18n } = common;
|
|
||||||
const { loginIncentives } = common.content;
|
const { loginIncentives } = common.content;
|
||||||
// const maxPMs = 200;
|
// const maxPMs = 200;
|
||||||
|
|
||||||
@@ -61,10 +65,8 @@ const CLEAR_BUFFS = {
|
|||||||
async function grantEndOfTheMonthPerks (user, now) {
|
async function grantEndOfTheMonthPerks (user, now) {
|
||||||
// multi-month subscriptions are for multiples of 3 months
|
// multi-month subscriptions are for multiples of 3 months
|
||||||
const SUBSCRIPTION_BASIC_BLOCK_LENGTH = 3;
|
const SUBSCRIPTION_BASIC_BLOCK_LENGTH = 3;
|
||||||
const { plan } = user.purchased;
|
|
||||||
const subscriptionEndDate = moment(plan.dateTerminated).isBefore() ? moment(plan.dateTerminated).startOf('month') : moment(now).startOf('month');
|
const { plan, elapsedMonths } = getPlanContext(user, now);
|
||||||
const dateUpdatedMoment = moment(plan.dateUpdated).startOf('month');
|
|
||||||
const elapsedMonths = moment(subscriptionEndDate).diff(dateUpdatedMoment, 'months');
|
|
||||||
|
|
||||||
if (elapsedMonths > 0) {
|
if (elapsedMonths > 0) {
|
||||||
plan.dateUpdated = now;
|
plan.dateUpdated = now;
|
||||||
@@ -72,9 +74,6 @@ async function grantEndOfTheMonthPerks (user, now) {
|
|||||||
// Give perks based on consecutive blocks
|
// Give perks based on consecutive blocks
|
||||||
// If they already got perks for those blocks (eg, 6mo subscription,
|
// If they already got perks for those blocks (eg, 6mo subscription,
|
||||||
// subscription gifts, etc) - then dec the offset until it hits 0
|
// subscription gifts, etc) - then dec the offset until it hits 0
|
||||||
_.defaults(plan.consecutive, {
|
|
||||||
count: 0, offset: 0, trinkets: 0, gemCapExtra: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Award mystery items
|
// Award mystery items
|
||||||
revealMysteryItems(user, elapsedMonths);
|
revealMysteryItems(user, elapsedMonths);
|
||||||
@@ -104,15 +103,7 @@ async function grantEndOfTheMonthPerks (user, now) {
|
|||||||
|
|
||||||
if (plan.consecutive.offset < 0) {
|
if (plan.consecutive.offset < 0) {
|
||||||
if (plan.planId) {
|
if (plan.planId) {
|
||||||
// NB gift subscriptions don't have a planID
|
planMonthsLength = getPlanMonths(plan);
|
||||||
// (which doesn't matter because we don't need to reapply perks
|
|
||||||
// for them and by this point they should have expired anyway)
|
|
||||||
const planIdRegExp = new RegExp('_([0-9]+)mo'); // e.g., matches 'google_6mo' / 'basic_12mo' and captures '6' / '12'
|
|
||||||
const match = plan.planId.match(planIdRegExp);
|
|
||||||
if (match !== null && match[0] !== null) {
|
|
||||||
// 3 for 3-month recurring subscription, etc
|
|
||||||
planMonthsLength = match[1]; // eslint-disable-line prefer-destructuring
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// every 3 months you get one set of perks - this variable records how many sets you need
|
// every 3 months you get one set of perks - this variable records how many sets you need
|
||||||
|
|||||||
Reference in New Issue
Block a user