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}}'})