From 1b0b31cd806c2f26ed135568294a4b5b51c7f3bc Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 30 Mar 2021 15:46:29 -0500 Subject: [PATCH 01/13] fix(subscriptions): handle future/prebuilt mystery items --- website/server/libs/payments/subscriptions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index 06deefd40f..a2f9ec768e 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -29,6 +29,7 @@ function _findMysteryItems (user, dateMoment) { _.each(shared.content.gear.flat, item => { if ( item.klass === 'mystery' + && shared.content.mystery[item.mystery] && dateMoment.isSameOrAfter(shared.content.mystery[item.mystery].start) && dateMoment.isSameOrBefore(moment(shared.content.mystery[item.mystery].end).endOf('day')) && !user.items.gear.owned[item.key] From cdaa504db4c8c75d9d7fb29d1b9d8f8e0ee443de Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 7 Apr 2021 12:24:24 -0500 Subject: [PATCH 02/13] feat(event): countdown banners for limited availability items --- .../client/src/components/shops/buyModal.vue | 18 ++-- .../src/components/shops/countdownBanner.vue | 84 +++++++++++++++++++ .../components/shops/market/categoryRow.vue | 35 +++++++- .../components/shops/quests/buyQuestModal.vue | 19 ++--- .../src/components/shops/quests/questInfo.vue | 31 ++++++- .../client/src/components/shops/shopItem.vue | 40 +++++++-- website/common/locales/en/npc.json | 4 +- 7 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 website/client/src/components/shops/countdownBanner.vue diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 2a4a793c20..13e4b7ac05 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -161,16 +161,10 @@ -
- - {{ limitedString }} -
+ :endDate = "endDate" + />
+
+ + {{ limitedString }} +
+ + + + + diff --git a/website/client/src/components/shops/market/categoryRow.vue b/website/client/src/components/shops/market/categoryRow.vue index 92012294b6..d63e92ded2 100644 --- a/website/client/src/components/shops/market/categoryRow.vue +++ b/website/client/src/components/shops/market/categoryRow.vue @@ -48,6 +48,12 @@ export default { }, mixins: [pinUtils], props: ['hideLocked', 'hidePinned', 'searchBy', 'sortBy', 'category'], + data () { + return { + timer: '', + limitedString: '', + }; + }, computed: { ...mapState({ content: 'content', @@ -60,9 +66,6 @@ export default { return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought; }, - limitedString () { - return this.$t('limitedOffer', { date: moment(seasonalShopConfig.dateRange.end).format('LL') }); - }, sortedMarketItems () { let result = _map(this.category.items, e => ({ ...e, @@ -103,10 +106,36 @@ export default { return result; }, }, + mounted () { + this.countdownString(); + this.timer = setInterval(this.countdownString, 30000); + }, methods: { itemSelected (item) { this.$root.$emit('buyModal::showItem', item); }, + countdownString () { + const diffDuration = moment.duration(moment(seasonalShopConfig.dateRange.end).diff(moment())); + + if (diffDuration.days() > 0) { + this.limitedString = this.$t('limitedAvailabilityDays', { + days: diffDuration.days(), + hours: diffDuration.hours(), + minutes: diffDuration.minutes(), + }); + } else { + this.limitedString = this.$t('limitedAvailabilityHours', { + hours: diffDuration.hours(), + minutes: diffDuration.minutes(), + }); + } + }, + cancelAutoUpdate () { + clearInterval(this.timer); + }, + }, + beforeDestroy () { + this.cancelAutoUpdate(); }, }; diff --git a/website/client/src/components/shops/quests/buyQuestModal.vue b/website/client/src/components/shops/quests/buyQuestModal.vue index a11e3bc05c..da8bf6b1b7 100644 --- a/website/client/src/components/shops/quests/buyQuestModal.vue +++ b/website/client/src/components/shops/quests/buyQuestModal.vue @@ -84,16 +84,10 @@ >
-
- - {{ limitedString }} -
+ :endDate="endDate" + />
diff --git a/website/client/src/components/shops/shopItem.vue b/website/client/src/components/shops/shopItem.vue index f3bc30e77c..d2bc6c228c 100644 --- a/website/client/src/components/shops/shopItem.vue +++ b/website/client/src/components/shops/shopItem.vue @@ -291,16 +291,18 @@ export default { }, }, data () { - return Object.freeze({ + return { itemId: uuid(), - icons: { + icons: Object.freeze({ gems: svgGem, gold: svgGold, lock: svgLock, hourglasses: svgHourglasses, clock: svgClock, - }, - }); + }), + timer: '', + limitedString: '', + }; }, computed: { showNotes () { @@ -314,10 +316,10 @@ export default { } return 'gold'; }, - limitedString () { - return this.item.owned === false ? '' - : this.$t('limitedOffer', { date: moment(seasonalShopConfig.dateRange.end).format('LL') }); - }, + }, + mounted () { + this.countdownString(); + this.timer = setInterval(this.countdownString, 30000); }, methods: { click () { @@ -338,6 +340,28 @@ export default { locked: this.item.locked, }; }, + countdownString () { + const diffDuration = moment.duration(moment(seasonalShopConfig.dateRange.end).diff(moment())); + + if (diffDuration.days() > 0) { + this.limitedString = this.$t('limitedAvailabilityDays', { + days: diffDuration.days(), + hours: diffDuration.hours(), + minutes: diffDuration.minutes(), + }); + } else { + this.limitedString = this.$t('limitedAvailabilityHours', { + hours: diffDuration.hours(), + minutes: diffDuration.minutes(), + }); + } + }, + cancelAutoUpdate () { + clearInterval(this.timer); + }, + }, + beforeDestroy () { + this.cancelAutoUpdate(); }, }; diff --git a/website/common/locales/en/npc.json b/website/common/locales/en/npc.json index 684e6ac81b..d4461ec9fa 100644 --- a/website/common/locales/en/npc.json +++ b/website/common/locales/en/npc.json @@ -125,5 +125,7 @@ "welcome3": "Progress in life and the game!", "welcome3notes": "As you improve your life, your avatar will level up and unlock pets, quests, equipment, and more!", "imReady": "Enter Habitica", - "limitedOffer": "Available until <%= date %>" + "limitedOffer": "Available until <%= date %>", + "limitedAvailabilityDays": "Available for <%= days %>d <%= hours %>h <%= minutes %>m", + "limitedAvailabilityHours": "Available for <%= hours %>h <%= minutes %>m" } From 5ffdf09be4d1bb0438d5af096168f3ff7c87bc6f Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 7 Apr 2021 13:07:04 -0500 Subject: [PATCH 03/13] fix(event): don't flatten start/end moments --- website/common/script/libs/shops-seasonal.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/common/script/libs/shops-seasonal.config.js b/website/common/script/libs/shops-seasonal.config.js index 06657c4ed7..fb50933dda 100644 --- a/website/common/script/libs/shops-seasonal.config.js +++ b/website/common/script/libs/shops-seasonal.config.js @@ -18,8 +18,8 @@ export default { currentSeason: SHOP_OPEN ? upperFirst(CURRENT_EVENT.season) : 'Closed', dateRange: { - start: moment(CURRENT_EVENT.start).format('YYYY-MM-DD'), - end: moment(CURRENT_EVENT.end).format('YYYY-MM-DD'), + start: SHOP_OPEN ? moment(CURRENT_EVENT.start) : moment().subtract(1, 'days').toDate(), + end: SHOP_OPEN ? moment(CURRENT_EVENT.end) : moment().subtract(1, 'seconds').toDate(), }, availableSets: SHOP_OPEN From 23f6e9b91114cc0eec53e2a82ed6115d31117169 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 8 Apr 2021 15:43:47 -0500 Subject: [PATCH 04/13] feat(event): more datetime countdown improvements --- .../client/src/components/shops/buyModal.vue | 21 ----------- .../src/components/shops/countdownBanner.vue | 35 +++++++++++++++---- .../components/shops/market/categoryRow.vue | 13 +++++-- .../components/shops/quests/buyQuestModal.vue | 21 ----------- .../src/components/shops/quests/questInfo.vue | 13 +++++-- .../client/src/components/shops/shopItem.vue | 13 +++++-- website/common/locales/en/limited.json | 3 +- website/common/locales/en/npc.json | 3 +- website/common/script/content/quests.js | 5 +-- 9 files changed, 65 insertions(+), 62 deletions(-) diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 13e4b7ac05..e591f58fd4 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -318,27 +318,6 @@ opacity: 0.55; } - .limitedTime { - height: 32px; - background-color: $purple-300; - width: calc(100% + 30px); - margin: 0 -15px; // the modal content has its own padding - - font-size: 12px; - line-height: 1.33; - text-align: center; - color: $white; - - display: flex; - align-items: center; - justify-content: center; - - .limitedString { - height: 16px; - margin-left: 8px; - } - } - .attributesGrid { margin-top: 8px; border-radius: 2px; diff --git a/website/client/src/components/shops/countdownBanner.vue b/website/client/src/components/shops/countdownBanner.vue index 7bb29e7a92..dee2380f72 100644 --- a/website/client/src/components/shops/countdownBanner.vue +++ b/website/client/src/components/shops/countdownBanner.vue @@ -1,8 +1,11 @@