diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 4322c538075..8c75e4f542d 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,21 +1,19 @@
-[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issues If you find your issue there, read at least the first post to see if there is a workaround for you)
+[//]: # (Before logging this issue, please post to the Report a Bug guild from the Habitica website's Help menu. Most bugs can be handled quickly there. If a GitHub issue is needed, you will be advised of that by a moderator or staff member -- a player with a dark blue or purple name. It is recommended that you don't create a new issue unless advised to.)
-[//]: # (Github is primarily used for reporting bugs. If you have a feature request, use "Help > Request a Feature" so that the feature request can be vetted by the larger Habitica community)
+[//]: # (Bugs in the mobile apps can also be reported there.)
-[//]: # (To report a bug in one of the mobile apps, please report it in the correct repository. Android: https://github.com/HabitRPG/habitrpg-android, iOS: https://github.com/HabitRPG/habitrpg-ios)
+[//]: # (If you have a feature request, use "Help > Request a Feature", not GitHub or the Report a Bug guild.)
[//]: # (For more guidelines see https://github.com/HabitRPG/habitrpg/issues/2760)
[//]: # (Fill out relevant information - UUID is found in Settings -> API)
-General Info
+### General Info
* UUID:
* Browser:
* OS:
### Description
-[//]: # (Describe bug in detail here. Include pictures if helpful.)
-
-
+[//]: # (Describe bug in detail here. Include screenshots if helpful.)
#### Console Errors
[//]: # (Include any JavaScript console errors here.)
diff --git a/test/client-old/spec/controllers/userCtrlSpec.js b/test/client-old/spec/controllers/userCtrlSpec.js
new file mode 100644
index 00000000000..2ac2dbe8e15
--- /dev/null
+++ b/test/client-old/spec/controllers/userCtrlSpec.js
@@ -0,0 +1,69 @@
+'use strict';
+
+describe('User Controller', function() {
+ var $rootScope, $window, User, shared, scope, ctrl, content;
+
+ beforeEach(function() {
+ module(function ($provide) {
+ var user = specHelper.newUser();
+ User = {user: user}
+ $provide.value('Guide', sandbox.stub());
+ $provide.value('User', User);
+ $provide.value('Achievement', sandbox.stub());
+ $provide.value('Social', sandbox.stub());
+ $provide.value('Shared', {
+ achievements: {
+ getAchievementsForProfile: sandbox.stub()
+ },
+ shops: {
+ getBackgroundShopSets: sandbox.stub()
+ }
+ });
+ $provide.value('Content', {
+ loginIncentives: sandbox.stub()
+ })
+ });
+
+ inject(function($rootScope, $controller, User, Content) {
+ scope = $rootScope.$new();
+ content = Content;
+ $controller('RootCtrl', { $scope: scope, User: User});
+ ctrl = $controller('UserCtrl', { $scope: scope, User: User, $window: $window});
+ });
+ });
+
+ describe('getProgressDisplay', function() {
+
+ beforeEach(() => {
+ sandbox.stub(window.env, 't');
+ window.env.t.onFirstCall().returns('Progress until next');
+ });
+
+ it('should return initial progress', function() {
+ scope.profile.loginIncentives = 0;
+ content.loginIncentives = [{
+ nextRewardAt: 1,
+ reward: true
+ }];
+ var actual = scope.getProgressDisplay();
+ expect(actual.trim()).to.eql('Progress until next 0/1');
+ });
+
+ it('should return progress between next reward and current reward', function() {
+ scope.profile.loginIncentives = 1;
+ content.loginIncentives = [{
+ nextRewardAt: 1,
+ reward: true
+ }, {
+ prevRewardAt: 0,
+ nextRewardAt: 2,
+ reward: true
+ }, {
+ prevRewardAt: 1,
+ nextRewardAt: 3
+ }];
+ var actual = scope.getProgressDisplay();
+ expect(actual.trim()).to.eql('Progress until next 0/1');
+ });
+ });
+});
diff --git a/website/client-old/js/controllers/userCtrl.js b/website/client-old/js/controllers/userCtrl.js
index f233f366cd6..a109db178b1 100644
--- a/website/client-old/js/controllers/userCtrl.js
+++ b/website/client-old/js/controllers/userCtrl.js
@@ -1,6 +1,6 @@
"use strict";
-habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', 'Content', 'Stats', 'Social', 'Costume',
+habitrpg.controller('UserCtrl', ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', 'Content', 'Stats', 'Social', 'Costume',
function($rootScope, $scope, $location, User, $http, $state, Guide, Shared, Content, Stats, Social, Costume) {
$scope.profile = User.user;
@@ -102,6 +102,8 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$
if (!currentLoginDay) return env.t('moreIncentivesComingSoon');
var nextRewardAt = currentLoginDay.nextRewardAt;
if (!nextRewardAt) return env.t('moreIncentivesComingSoon');
+ // if we are on a reward day, let's show progress relative to this
+ if (currentLoginDay.reward) currentLoginDay.prevRewardKey = $scope.profile.loginIncentives;
if (!currentLoginDay.prevRewardKey) currentLoginDay.prevRewardKey = 0;
return env.t('checkinProgressTitle') + ' ' + ($scope.profile.loginIncentives - currentLoginDay.prevRewardKey) + '/' + (nextRewardAt - currentLoginDay.prevRewardKey);
};
diff --git a/website/client-old/js/services/notificationServices.js b/website/client-old/js/services/notificationServices.js
index edb08cd4cc1..7e380e36fac 100644
--- a/website/client-old/js/services/notificationServices.js
+++ b/website/client-old/js/services/notificationServices.js
@@ -147,6 +147,7 @@ angular.module("habitrpg").factory("Notification",
modalScope.data = rewardData;
var nextRewardKey = Shared.content.loginIncentives[user.loginIncentives].nextRewardAt;
modalScope.nextReward = Shared.content.loginIncentives[nextRewardKey];
+ modalScope.MAX_INCENTIVES = Shared.constants.MAX_INCENTIVES;
modalScope.user = user;
// modalScope.loadWidgets = Social.loadWidgets;
modalScope.loadWidgets = loadWidgets;
diff --git a/website/common/locales/en/loginIncentives.json b/website/common/locales/en/loginIncentives.json
index a434f4cc771..06b1774f6fe 100644
--- a/website/common/locales/en/loginIncentives.json
+++ b/website/common/locales/en/loginIncentives.json
@@ -12,5 +12,6 @@
"totalCheckinsTitle": "Total Check-Ins",
"checkinProgressTitle": "Progress until next",
"incentiveBackgroundsUnlockedWithCheckins": "Locked Plain Backgrounds will unlock with Daily Check-Ins.",
- "moreIncentivesComingSoon": "New check-in incentives will be added soon!"
+ "moreIncentivesComingSoon": "New check-in incentives will be added soon!",
+ "checkinReceivedAllRewardsMessage" : "You have received all the Check-In prizes currently available! More will be added in the future."
}
diff --git a/website/common/locales/zh/gear.json b/website/common/locales/zh/gear.json
index f6dce54dc9a..75e309adc14 100644
--- a/website/common/locales/zh/gear.json
+++ b/website/common/locales/zh/gear.json
@@ -1189,4 +1189,4 @@
"eyewearMystery301703Notes": "完美适配美妙的化妆舞会,或者静悄悄地穿过那些特别讲究穿着的人群。没有属性加成。3017年三月捐赠者礼品。",
"eyewearArmoirePlagueDoctorMaskText": "瘟疫医生面具",
"eyewearArmoirePlagueDoctorMaskNotes": "防治延迟瘟疫的医生袋的地道面具。没有属性加成。魔法衣橱: 瘟疫医生系列 (3件的第2件)。"
-}
\ No newline at end of file
+}
diff --git a/website/common/script/content/loginIncentives.js b/website/common/script/content/loginIncentives.js
index 10bd714174a..39783702eb1 100644
--- a/website/common/script/content/loginIncentives.js
+++ b/website/common/script/content/loginIncentives.js
@@ -15,6 +15,7 @@ module.exports = function getLoginIncentives (api) {
2: {
rewardKey: ['background_purple'],
reward: [api.backgrounds.incentiveBackgrounds],
+ rewardName: 'incentiveBackgrounds', // i18n string
assignReward: function assignReward (user) {
user.purchased.background.blue = true;
user.purchased.background.green = true;
@@ -225,8 +226,10 @@ module.exports = function getLoginIncentives (api) {
},
},
};
+ // When the final check-in prize is added here, change checkinReceivedAllRewardsMessage in website/common/locales/en/loginIncentives.json
+ // to say "You have received the final Check-In prize!". Confirm the message with Lemoness first.
- // Add refence link to next reward and add filler days so we have a map to refernce the next reward from any day
+ // Add reference link to next reward and add filler days so we have a map to reference the next reward from any day
// We could also, use a list, but then we would be cloning each of the rewards.
// Create a new array if we want the loginIncentives to be immutable in the future
let nextRewardKey;
diff --git a/website/common/script/fns/updateStats.js b/website/common/script/fns/updateStats.js
index 3a8bc5b1312..9fa1ff1231d 100644
--- a/website/common/script/fns/updateStats.js
+++ b/website/common/script/fns/updateStats.js
@@ -94,7 +94,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
};
}
});
- if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
+ if (!user.flags.rebirthEnabled && user.stats.lvl >= 50) {
user.addNotification('REBIRTH_ENABLED');
user.flags.rebirthEnabled = true;
}
diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js
index 36f3d3f2e16..ec7f5915d71 100644
--- a/website/server/libs/cron.js
+++ b/website/server/libs/cron.js
@@ -157,7 +157,7 @@ function awardLoginIncentives (user) {
notificationData.rewardText = i18n.t('potion', {potionType: notificationData.rewardText}, user.preferences.language);
}
} else if (loginIncentive.rewardKey[0] === 'background_blue') {
- notificationData.rewardText = i18n.t('incentiveBackgrounds');
+ notificationData.rewardText = i18n.t('incentiveBackgrounds', user.preferences.language);
}
if (loginIncentive.reward.length > 0 && count < loginIncentive.reward.length - 1) notificationData.rewardText += ', ';
@@ -165,6 +165,11 @@ function awardLoginIncentives (user) {
count += 1;
}
+ // Overwrite notificationData.rewardText if rewardName was explicitly declared
+ if (loginIncentive.rewardName) {
+ notificationData.rewardText = i18n.t(loginIncentive.rewardName, user.preferences.language);
+ }
+
notificationData.rewardKey = loginIncentive.rewardKey;
notificationData.message = i18n.t('unlockedCheckInReward', user.preferences.language);
}
diff --git a/website/views/options/profile/stats.jade b/website/views/options/profile/stats.jade
index 144f617caa3..b1e06a9a2d0 100644
--- a/website/views/options/profile/stats.jade
+++ b/website/views/options/profile/stats.jade
@@ -1,51 +1,51 @@
-script(id='partials/options.profile.stats.html', type='text/ng-template')
- .container-fluid
- div(class='row')
- .border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
- include ../../shared/profiles/stats_col1
- div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
- button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
- hr(ng-if='user.preferences.disableClasses')
- include ../../shared/profiles/stats_col2
- .col-md-4.border-left.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses')
- h3=env.t('characterBuild')
- h4
- =env.t('class') + ': '
- span {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[user.stats.class] }}
- a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass')
- small.cost 3
- table.table.table-striped
- tr
- td
- p(ng-if='::user.stats.lvl >= 100')!=env.t('noMoreAllocate')
- p(ng-if='user.stats.points || user.stats.lvl < 100')
- strong.inline
- |{{user.stats.points}}
- strong.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))=env.t('unallocated')
- td
- tr
- td(colspan=2)
- fieldset.auto-allocate
- .checkbox
- label
- input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})', ng-click='set({"preferences.allocationMode":"taskbased"})')
- span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))=env.t('autoAllocation')
- form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em')
- .radio
- label
- input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})')
- span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))=env.t('evenAllocation')
- .radio
- label
- input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})')
- span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))=env.t('classAllocation')
- .radio
- label
- input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})')
- span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation')
- div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
- a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop'))
- span.glyphicon.glyphicon-download
- |
- =env.t('distributePoints')
- +statAllocation()
+script(id='partials/options.profile.stats.html', type='text/ng-template')
+ .container-fluid
+ div(class='row')
+ .border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
+ include ../../shared/profiles/stats_col1
+ div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"')
+ button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
+ hr(ng-if='user.preferences.disableClasses')
+ include ../../shared/profiles/stats_col2
+ .col-md-4.border-left.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses')
+ h3=env.t('characterBuild')
+ h4
+ =env.t('class') + ': '
+ span {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[user.stats.class] }}
+ a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass')
+ small.cost 3
+ table.table.table-striped
+ tr
+ td
+ p(ng-if='::user.stats.lvl >= 100')!=env.t('noMoreAllocate')
+ p(ng-if='user.stats.points || user.stats.lvl < 100')
+ strong.inline
+ |{{user.stats.points}}
+ strong.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))=env.t('unallocated')
+ td
+ tr
+ td(colspan=2)
+ fieldset.auto-allocate
+ .checkbox
+ label
+ input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation, "preferences.allocationMode":"taskbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))=env.t('autoAllocation')
+ form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em')
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))=env.t('evenAllocation')
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))=env.t('classAllocation')
+ .radio
+ label
+ input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})')
+ span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation')
+ div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)')
+ a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop'))
+ span.glyphicon.glyphicon-download
+ |
+ =env.t('distributePoints')
+ +statAllocation()
diff --git a/website/views/shared/modals/login-incentives-reward-unlocked.jade b/website/views/shared/modals/login-incentives-reward-unlocked.jade
index 91986aa96d5..a1a7ac14627 100644
--- a/website/views/shared/modals/login-incentives-reward-unlocked.jade
+++ b/website/views/shared/modals/login-incentives-reward-unlocked.jade
@@ -18,6 +18,8 @@ script(id='modals/login-incentives-reward-unlocked.html', type='text/ng-template
p {{env.t('earnedRewardForDevotion', {reward: data.rewardText})}}
.col-md-12.text-center(ng-show="data.nextRewardAt")
h4 {{env.t('nextRewardUnlocksIn', {numberOfCheckinsLeft: data.nextRewardAt - user.loginIncentives})}}
+ .col-md-10.col-md-offset-1.text-center
+ h4(ng-if="user.loginIncentives === MAX_INCENTIVES")=env.t('checkinReceivedAllRewardsMessage')
.modal-footer
.row
a.btn.btn-primary.col-md-6.col-md-offset-3(ng-click='$close()')=env.t('awesome')
diff --git a/website/views/shared/modals/members.jade b/website/views/shared/modals/members.jade
index 1e761661af6..2ce09156266 100644
--- a/website/views/shared/modals/members.jade
+++ b/website/views/shared/modals/members.jade
@@ -87,7 +87,7 @@ script(type='text/ng-template', id='modals/send-gift.html')
.panel-heading=env.t('subscription')
.panel-body
.form-group
- .radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"', ng-if="block.type !== 'group'")
+ .radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"', ng-if="block.target !== 'group' && block.canSubscribe === true")
label
input(type="radio", name="subRadio", ng-value="block.key", ng-model='gift.subscription.key')
=env.t('sendGiftSubscription', {price: '{{::block.price}}', months: '{{::block.months}}'})