mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
Client Fixes (#9021)
* pass the timezoneoffset to the server * implement yesterdaily modal timer * fix typos * todo * task editing: checklist should not overlay the tags box * make tags selector use two columns * show all tags * do not allow users to go to /login and /register if logged in
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
div
|
div
|
||||||
yesterdaily-modal(:yesterDailies='yesterDailies')
|
yesterdaily-modal(
|
||||||
|
:yesterDailies='yesterDailies',
|
||||||
|
@hide="runYesterDailiesAction()",
|
||||||
|
)
|
||||||
armoire-empty
|
armoire-empty
|
||||||
new-stuff
|
new-stuff
|
||||||
death
|
death
|
||||||
@@ -227,44 +230,67 @@ export default {
|
|||||||
this.$root.$emit('show::modal', 'quest-invitation');
|
this.$root.$emit('show::modal', 'quest-invitation');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted () {
|
mounted () {
|
||||||
Promise.all(['user.fetch', 'tasks.fetchUserTasks'])
|
Promise.all([
|
||||||
.then(() => {
|
this.$store.dispatch('user:fetch'),
|
||||||
if (this.user.flags.newStuff) {
|
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||||
this.$root.$emit('show::modal', 'new-stuff');
|
]).then(() => {
|
||||||
}
|
if (this.user.flags.newStuff) {
|
||||||
|
this.$root.$emit('show::modal', 'new-stuff');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.user.flags.welcomed) {
|
if (!this.user.flags.welcomed) {
|
||||||
this.$store.state.avatarEditorOptions.editingUser = false;
|
this.$store.state.avatarEditorOptions.editingUser = false;
|
||||||
this.$root.$emit('show::modal', 'avatar-modal');
|
this.$root.$emit('show::modal', 'avatar-modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: This is a timeout to ensure dom is loaded
|
// @TODO: This is a timeout to ensure dom is loaded
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.initTour();
|
this.initTour();
|
||||||
if (this.user.flags.tour.intro === this.TOUR_END || !this.user.flags.welcomed) return;
|
if (this.user.flags.tour.intro === this.TOUR_END || !this.user.flags.welcomed) return;
|
||||||
this.goto('intro', 0);
|
this.goto('intro', 0);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
this.runYesterDailies();
|
this.runYesterDailies();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
playSound (sound) {
|
playSound (sound) {
|
||||||
this.$root.$emit('playSound', sound);
|
this.$root.$emit('playSound', sound);
|
||||||
},
|
},
|
||||||
async runYesterDailies () {
|
scheduleNextCron () {
|
||||||
|
// Open yesterdailies modal the next time cron runs
|
||||||
|
const dayStart = this.user.preferences.dayStart;
|
||||||
|
let nextCron = moment().hours(dayStart).minutes(0).seconds(0).milliseconds(0);
|
||||||
|
|
||||||
|
const currentHour = moment().format('H');
|
||||||
|
if (currentHour >= dayStart) {
|
||||||
|
nextCron = nextCron.add(1, 'day');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup a listener that executes 10 seconds after the next cron time
|
||||||
|
const nextCronIn = Number(nextCron.format('x')) - Date.now() + 10 * 1000;
|
||||||
|
setInterval(() => {
|
||||||
|
this.user.needsCron = true; // @TODO move to action? not sent to the server
|
||||||
|
this.runYesterDailies();
|
||||||
|
}, nextCronIn);
|
||||||
|
},
|
||||||
|
async runYesterDailies (forceRun = false) {
|
||||||
// @TODO: Hopefully we don't need this even we load correctly
|
// @TODO: Hopefully we don't need this even we load correctly
|
||||||
if (this.isRunningYesterdailies) return;
|
if (this.isRunningYesterdailies) return;
|
||||||
if (!this.user.needsCron) {
|
if (!this.user.needsCron && !forceRun) {
|
||||||
this.handleUserNotifications(this.user.notifications);
|
this.handleUserNotifications(this.user.notifications);
|
||||||
|
this.scheduleNextCron();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dailys = this.$store.state.tasks.data.dailys;
|
let dailys = this.$store.state.tasks.data.dailys;
|
||||||
this.isRunningYesterdailies = true;
|
this.isRunningYesterdailies = true;
|
||||||
|
|
||||||
let yesterDay = moment().subtract('1', 'day').startOf('day').add({ hours: this.user.preferences.dayStart });
|
let yesterDay = moment().subtract('1', 'day').startOf('day').add({
|
||||||
|
hours: this.user.preferences.dayStart,
|
||||||
|
});
|
||||||
|
|
||||||
dailys.forEach((task) => {
|
dailys.forEach((task) => {
|
||||||
if (task && task.group.approval && task.group.approval.requested) return;
|
if (task && task.group.approval && task.group.approval.requested) return;
|
||||||
if (task.completed) return;
|
if (task.completed) return;
|
||||||
@@ -273,14 +299,29 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.yesterDailies.length === 0) {
|
if (this.yesterDailies.length === 0) {
|
||||||
this.isRunningYesterdailies = false;
|
this.runYesterDailiesAction();
|
||||||
await axios.post('/api/v3/cron');
|
|
||||||
this.handleUserNotifications(this.user.notifications);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$root.$emit('show::modal', 'yesterdaily');
|
this.$root.$emit('show::modal', 'yesterdaily');
|
||||||
},
|
},
|
||||||
|
async runYesterDailiesAction () {
|
||||||
|
// Run Cron
|
||||||
|
await axios.post('/api/v3/cron');
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
|
||||||
|
// Sync
|
||||||
|
// @TODO add a loading spinner somewhere
|
||||||
|
await Promise.all([
|
||||||
|
this.$store.dispatch('user:fetch', {forceLoad: true}),
|
||||||
|
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.handleUserNotifications(this.user.notifications);
|
||||||
|
this.isRunningYesterdailies = false;
|
||||||
|
this.scheduleNextCron();
|
||||||
|
},
|
||||||
transferGroupNotification (notification) {
|
transferGroupNotification (notification) {
|
||||||
if (!this.user.groupNotifications) this.user.groupNotifications = [];
|
if (!this.user.groupNotifications) this.user.groupNotifications = [];
|
||||||
this.user.groupNotifications.push(notification);
|
this.user.groupNotifications.push(notification);
|
||||||
@@ -442,16 +483,6 @@ export default {
|
|||||||
|
|
||||||
this.user.notifications = []; // reset the notifications
|
this.user.notifications = []; // reset the notifications
|
||||||
},
|
},
|
||||||
// @TODO: I think I have these handled in the http interceptor
|
|
||||||
// this.$on('responseError500', function(ev, error){
|
|
||||||
// Notification.error(error);
|
|
||||||
// });
|
|
||||||
// this.$on('responseError', function(ev, error){
|
|
||||||
// Notification.error(error, true);
|
|
||||||
// });
|
|
||||||
// this.$on('responseText', function(ev, error){
|
|
||||||
// Notification.text(error);
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -123,15 +123,18 @@
|
|||||||
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
|
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
|
||||||
span.category-select(v-else) {{getTagsFor(task)[0]}}
|
span.category-select(v-else) {{getTagsFor(task)[0]}}
|
||||||
.category-box(v-if="showTagsSelect")
|
.category-box(v-if="showTagsSelect")
|
||||||
.form-check(
|
.container
|
||||||
v-for="tag in user.tags",
|
.row
|
||||||
:key="tag.id",
|
.form-check.col-6(
|
||||||
)
|
v-for="tag in user.tags",
|
||||||
label.custom-control.custom-checkbox
|
:key="tag.id",
|
||||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
)
|
||||||
span.custom-control-indicator
|
label.custom-control.custom-checkbox
|
||||||
span.custom-control-description(v-once) {{ tag.name }}
|
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
||||||
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description(v-once) {{ tag.name }}
|
||||||
|
.row
|
||||||
|
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
|
||||||
.option(v-if="task.type === 'habit'")
|
.option(v-if="task.type === 'habit'")
|
||||||
label(v-once) {{ $t('resetStreak') }}
|
label(v-once) {{ $t('resetStreak') }}
|
||||||
b-dropdown(:text="$t(task.frequency)")
|
b-dropdown(:text="$t(task.frequency)")
|
||||||
@@ -145,15 +148,19 @@
|
|||||||
span.category-select(v-else)
|
span.category-select(v-else)
|
||||||
span(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
span(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
||||||
.category-box(v-if="showAssignedSelect")
|
.category-box(v-if="showAssignedSelect")
|
||||||
.form-check(
|
.container
|
||||||
v-for="member in members",
|
.row
|
||||||
:key="member._id",
|
.form-check.col-6(
|
||||||
)
|
v-for="member in members",
|
||||||
label.custom-control.custom-checkbox
|
:key="member._id",
|
||||||
input.custom-control-input(type="checkbox", :value="member._id", v-model="assignedMembers", @change='toggleAssignment(member._id)')
|
)
|
||||||
span.custom-control-indicator
|
label.custom-control.custom-checkbox
|
||||||
span.custom-control-description(v-once) {{ member.profile.name }}
|
input.custom-control-input(type="checkbox", :value="member._id", v-model="assignedMembers", @change='toggleAssignment(member._id)')
|
||||||
button.btn.btn-primary(@click="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description(v-once) {{ member.profile.name }}
|
||||||
|
|
||||||
|
.row
|
||||||
|
button.btn.btn-primary(@click="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||||
|
|
||||||
.option.group-options(v-if='groupId')
|
.option.group-options(v-if='groupId')
|
||||||
label(v-once) Needs Approval
|
label(v-once) Needs Approval
|
||||||
@@ -313,6 +320,12 @@
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 40px;
|
left: 40px;
|
||||||
top: auto;
|
top: auto;
|
||||||
|
z-index: 11;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.checklist-group {
|
.checklist-group {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
b-modal#yesterdaily(size='m', :hide-header="true", :hide-footer="true")
|
b-modal#yesterdaily(
|
||||||
|
size="m",
|
||||||
|
:hide-header="true",
|
||||||
|
:hide-footer="true",
|
||||||
|
:no-close-on-backdrop="true",
|
||||||
|
:no-close-on-esc="true",
|
||||||
|
@hide="$emit('hide')",
|
||||||
|
)
|
||||||
.modal-body
|
.modal-body
|
||||||
h1.header-welcome.text-center {{ $t('welcomeBack') }}
|
h1.header-welcome.text-center {{ $t('welcomeBack') }}
|
||||||
p.call-to-action.text-center {{ $t('checkOffYesterDailies') }}
|
p.call-to-action.text-center {{ $t('checkOffYesterDailies') }}
|
||||||
@@ -44,7 +51,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||||
@@ -71,9 +77,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async close () {
|
async close () {
|
||||||
await axios.post('/api/v3/cron');
|
|
||||||
// @TODO: Better way to sync user?
|
|
||||||
window.location.href = '/';
|
|
||||||
this.$root.$emit('hide::modal', 'yesterdaily');
|
this.$root.$emit('hide::modal', 'yesterdaily');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -283,6 +283,10 @@ router.beforeEach(function routerGuard (to, from, next) {
|
|||||||
return next({name: to.path === '/' ? 'home' : 'login'});
|
return next({name: to.path === '/' ? 'home' : 'login'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUserLoggedIn && (to.name === 'login' || to.name === 'register')) {
|
||||||
|
return next({name: 'tasks'});
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import changeClassOp from 'common/script/ops/changeClass';
|
|||||||
import disableClassesOp from 'common/script/ops/disableClasses';
|
import disableClassesOp from 'common/script/ops/disableClasses';
|
||||||
|
|
||||||
|
|
||||||
export function fetch (store, forceLoad = false) { // eslint-disable-line no-shadow
|
export function fetch (store, options = {}) { // eslint-disable-line no-shadow
|
||||||
return loadAsyncResource({
|
return loadAsyncResource({
|
||||||
store,
|
store,
|
||||||
path: 'user',
|
path: 'user',
|
||||||
@@ -15,7 +15,7 @@ export function fetch (store, forceLoad = false) { // eslint-disable-line no-sha
|
|||||||
deserialize (response) {
|
deserialize (response) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
forceLoad,
|
forceLoad: options.forceLoad,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as commonConstants from 'common/script/constants';
|
|||||||
import { DAY_MAPPING } from 'common/script/cron';
|
import { DAY_MAPPING } from 'common/script/cron';
|
||||||
import { asyncResourceFactory } from 'client/libs/asyncResource';
|
import { asyncResourceFactory } from 'client/libs/asyncResource';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
import getters from './getters';
|
import getters from './getters';
|
||||||
@@ -22,6 +23,10 @@ if (AUTH_SETTINGS) {
|
|||||||
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
|
AUTH_SETTINGS = JSON.parse(AUTH_SETTINGS);
|
||||||
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
|
axios.defaults.headers.common['x-api-user'] = AUTH_SETTINGS.auth.apiId;
|
||||||
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
|
axios.defaults.headers.common['x-api-key'] = AUTH_SETTINGS.auth.apiToken;
|
||||||
|
|
||||||
|
const timezoneOffset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
|
||||||
|
axios.defaults.headers.common['x-user-timezoneOffset'] = timezoneOffset;
|
||||||
|
|
||||||
isUserLoggedIn = true;
|
isUserLoggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user