diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json
index 587055ae78..10026384f6 100644
--- a/common/locales/en/settings.json
+++ b/common/locales/en/settings.json
@@ -39,9 +39,10 @@
"xml": "(XML)",
"json": "(JSON)",
"customDayStart": "Custom Day Start",
- "24HrClock": "24Hr Clock",
- "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. It is recommended that you read the following information before changing it: ",
- "customDayStartInfo4": "Complete all your Dailies before changing the Custom Day Start or Rest in the Inn that day. Changing your Custom Day Start may cause Cron to run immediately, but after the first day it works as expected.
Allow a window of two hours for the change to take effect. For example, if it is currently set to 0 (midnight), change it before 10pm; if you want to set it to 9pm, change it before 7pm.
Enter an hour from 0 to 23 (it uses a 24 hour clock). Typing is more effective than arrow keys. Once set, reload the page to confirm that the new value is being displayed.",
+ "changeCustomDayStart": "Change Custom Day Start?",
+ "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
+ "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Misc",
"showHeader": "Show Header",
"changePass": "Change Password",
diff --git a/test/spec/controllers/settingsCtrlSpec.js b/test/spec/controllers/settingsCtrlSpec.js
new file mode 100644
index 0000000000..2950b235d6
--- /dev/null
+++ b/test/spec/controllers/settingsCtrlSpec.js
@@ -0,0 +1,86 @@
+'use strict';
+
+describe('Settings Controller', function() {
+ var rootScope, scope, user, User, ctrl;
+
+ beforeEach(function() {
+ module(function($provide) {
+ user = specHelper.newUser();
+ User = {
+ set: sandbox.stub(),
+ user: user
+ };
+ $provide.value('User', User);
+ $provide.value('Guide', sandbox.stub());
+ });
+
+ inject(function(_$rootScope_, _$controller_) {
+ scope = _$rootScope_.$new();
+ rootScope = _$rootScope_;
+
+ // Load RootCtrl to ensure shared behaviors are loaded
+ _$controller_('RootCtrl', {$scope: scope, User: User});
+
+ ctrl = _$controller_('SettingsCtrl', {$scope: scope, User: User});
+ });
+ });
+
+ describe('#openDayStartModal', function() {
+ beforeEach(function() {
+ sandbox.stub(rootScope, 'openModal');
+ sandbox.stub(window, 'alert');
+ });
+
+ it('opens the day start modal', function() {
+ scope.openDayStartModal(5);
+
+ expect(rootScope.openModal).to.be.calledOnce;
+ expect(rootScope.openModal).to.be.calledWith('change-day-start', {scope: scope});
+ });
+
+ it('sets nextCron variable', function() {
+ expect(scope.nextCron).to.not.exist;
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.exist;
+ });
+
+ it('calculates the next time cron will run', function() {
+ var fakeCurrentTime = new Date(2013, 3, 1, 3, 12).getTime();
+ var expectedTime = new Date(2013, 3, 1, 5, 0, 0).getTime();
+ sandbox.useFakeTimers(fakeCurrentTime);
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.eq(expectedTime);
+ });
+
+ it('calculates the next time cron will run and adds a day if cron would have already passed', function() {
+ var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime();
+ var expectedTime = new Date(2013, 3, 2, 5, 0, 0).getTime();
+ sandbox.useFakeTimers(fakeCurrentTime);
+
+ scope.openDayStartModal(5);
+
+ expect(scope.nextCron).to.eq(expectedTime);
+ });
+ });
+
+ describe('#saveDayStart', function() {
+
+ it('updates user\'s custom day start and last cron', function() {
+ var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime();
+ var expectedTime = fakeCurrentTime;
+ sandbox.useFakeTimers(fakeCurrentTime);
+ scope.dayStart = 5;
+ scope.saveDayStart();
+
+ expect(User.set).to.be.calledOnce;
+ expect(User.set).to.be.calledWith({
+ 'preferences.dayStart': 5,
+ 'lastCron': expectedTime
+ });
+ });
+ });
+});
diff --git a/website/public/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js
index 256a6d80ae..a61eb3bd31 100644
--- a/website/public/js/controllers/settingsCtrl.js
+++ b/website/public/js/controllers/settingsCtrl.js
@@ -62,14 +62,21 @@ habitrpg.controller('SettingsCtrl',
User.set({'flags.newStuff':true});
}
- $scope.saveDayStart = function(){
- var dayStart = +User.user.preferences.dayStart;
- if (_.isNaN(dayStart) || dayStart < 0 || dayStart > 24) {
- dayStart = 0;
- return alert(window.env.t('enterNumber'));
- }
- User.set({'preferences.dayStart': dayStart});
- }
+ $scope.dayStart = User.user.preferences.dayStart;
+
+ $scope.openDayStartModal = function(dayStart) {
+ $scope.dayStart = +dayStart;
+ $scope.nextCron = _calculateNextCron();
+
+ $rootScope.openModal('change-day-start', { scope: $scope });
+ };
+
+ $scope.saveDayStart = function() {
+ User.set({
+ 'preferences.dayStart': Math.floor($scope.dayStart),
+ 'lastCron': +new Date
+ });
+ };
$scope.language = window.env.language;
$scope.avalaibleLanguages = window.env.avalaibleLanguages;
@@ -192,5 +199,18 @@ habitrpg.controller('SettingsCtrl',
subs["google_6mo"].discount = false;
});
}
+
+ function _calculateNextCron() {
+ $scope.dayStart;
+
+ var nextCron = moment().hours($scope.dayStart).minutes(0).seconds(0).milliseconds(0);
+
+ var currentHour = moment().format('H');
+ if (currentHour >= $scope.dayStart) {
+ nextCron = nextCron.add(1, 'day');;
+ }
+
+ return +nextCron.format('x');
+ }
}
]);
diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade
index 60f34431a1..c2445a04a8 100644
--- a/website/views/options/settings.jade
+++ b/website/views/options/settings.jade
@@ -39,9 +39,14 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
strong
!=env.t('helpWithTranslation')
+ hr
+
.form-horizontal
h5=env.t('dateFormat')
select.form-control(ng-model='user.preferences.dateFormat', ng-options='DF for DF in availableFormats', ng-change='set({"preferences.dateFormat": user.preferences.dateFormat})')
+
+ hr
+
.checkbox
label
input(type='checkbox', ng-click='hideHeader() ', ng-checked='user.preferences.hideHeader!==true')
@@ -71,19 +76,34 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
input(type='checkbox', ng-model='user.preferences.displayInviteToPartyWhenPartyIs1', ng-change='set({"preferences.displayInviteToPartyWhenPartyIs1": user.preferences.displayInviteToPartyWhenPartyIs1 ? true : false})')
span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('displayInviteToPartyWhenPartyIs1'))=env.t('displayInviteToPartyWhenPartyIs1')
// button.btn.btn-default(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
+
+ hr
+
button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
- div.alert.alert-warning(style='padding:2px;margin-top:7px')
- h5=env.t('customDayStart')
- h5=env.t('customDayStartInfo1')
- a(ng-click='showCustomDayStartInfo = !showCustomDayStartInfo') {{!showCustomDayStartInfo ? env.t('showMoreMore') : env.t('showMoreLess')}}
- h5(ng-if='showCustomDayStartInfo')!=env.t('customDayStartInfo4')
+ hr
+
+ h5=env.t('customDayStart')
+ alert.alert-warning=env.t('customDayStartInfo1')
+
+ .form-horizontal
.form-group
- .input-group
- input.form-control(type='number', min='0', max='23', ng-model='user.preferences.dayStart', ng-blur='saveDayStart()')
- span.input-group-addon= ':00 (' + env.t('24HrClock') + ')'
+ .col-sm-7
+ select.form-control(ng-model='dayStart')
+ - var number = 0
+ while number < 24
+ - var value = number
+ - var meridian = number < 12 ? 'AM' : 'PM'
+ - var hour = number++ % 12
+ option(value=value) #{hour ? hour : 12}:00 #{meridian}
+
+ .col-sm-5
+ br.visible-xs
+ button.btn.btn-block.btn-primary(ng-click='openDayStartModal(dayStart)',
+ ng-disabled='dayStart == user.preferences.dayStart')
+ | Save Custom Day Start
.personal-options.col-md-6
.panel.panel-default
diff --git a/website/views/shared/modals/index.jade b/website/views/shared/modals/index.jade
index 8146882046..a70803cf49 100644
--- a/website/views/shared/modals/index.jade
+++ b/website/views/shared/modals/index.jade
@@ -14,3 +14,7 @@ include ./limited
include ./invite-friends
include ./welcome.jade
include ./low-health.jade
+
+//- Settings
+script(type='text/ng-template', id='modals/change-day-start.html')
+ include ./settings/change-day-start.jade
diff --git a/website/views/shared/modals/settings/change-day-start.jade b/website/views/shared/modals/settings/change-day-start.jade
new file mode 100644
index 0000000000..8dfa3abb63
--- /dev/null
+++ b/website/views/shared/modals/settings/change-day-start.jade
@@ -0,0 +1,11 @@
+.text-center
+ .modal-header
+ h3=env.t('changeCustomDayStart')
+
+ .modal-body
+ p!=env.t('nextCron', {time: '{{::nextCron | date: user.preferences.dateFormat + " @ h:mm a"}}'})
+
+ .modal-footer
+ p.pull-left-sm=env.t('sureChangeCustomDayStart')
+ a.btn.btn-default(ng-click='$close()')=env.t('close')
+ a.btn.btn-primary(ng-click='saveDayStart(); $close()')=env.t('confirm')