mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +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">
|
||||
div
|
||||
yesterdaily-modal(:yesterDailies='yesterDailies')
|
||||
yesterdaily-modal(
|
||||
:yesterDailies='yesterDailies',
|
||||
@hide="runYesterDailiesAction()",
|
||||
)
|
||||
armoire-empty
|
||||
new-stuff
|
||||
death
|
||||
@@ -227,44 +230,67 @@ export default {
|
||||
this.$root.$emit('show::modal', 'quest-invitation');
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
Promise.all(['user.fetch', 'tasks.fetchUserTasks'])
|
||||
.then(() => {
|
||||
if (this.user.flags.newStuff) {
|
||||
this.$root.$emit('show::modal', 'new-stuff');
|
||||
}
|
||||
mounted () {
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch'),
|
||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||
]).then(() => {
|
||||
if (this.user.flags.newStuff) {
|
||||
this.$root.$emit('show::modal', 'new-stuff');
|
||||
}
|
||||
|
||||
if (!this.user.flags.welcomed) {
|
||||
this.$store.state.avatarEditorOptions.editingUser = false;
|
||||
this.$root.$emit('show::modal', 'avatar-modal');
|
||||
}
|
||||
if (!this.user.flags.welcomed) {
|
||||
this.$store.state.avatarEditorOptions.editingUser = false;
|
||||
this.$root.$emit('show::modal', 'avatar-modal');
|
||||
}
|
||||
|
||||
// @TODO: This is a timeout to ensure dom is loaded
|
||||
window.setTimeout(() => {
|
||||
this.initTour();
|
||||
if (this.user.flags.tour.intro === this.TOUR_END || !this.user.flags.welcomed) return;
|
||||
this.goto('intro', 0);
|
||||
}, 2000);
|
||||
// @TODO: This is a timeout to ensure dom is loaded
|
||||
window.setTimeout(() => {
|
||||
this.initTour();
|
||||
if (this.user.flags.tour.intro === this.TOUR_END || !this.user.flags.welcomed) return;
|
||||
this.goto('intro', 0);
|
||||
}, 2000);
|
||||
|
||||
this.runYesterDailies();
|
||||
});
|
||||
this.runYesterDailies();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
if (this.isRunningYesterdailies) return;
|
||||
if (!this.user.needsCron) {
|
||||
if (!this.user.needsCron && !forceRun) {
|
||||
this.handleUserNotifications(this.user.notifications);
|
||||
this.scheduleNextCron();
|
||||
return;
|
||||
}
|
||||
|
||||
let dailys = this.$store.state.tasks.data.dailys;
|
||||
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) => {
|
||||
if (task && task.group.approval && task.group.approval.requested) return;
|
||||
if (task.completed) return;
|
||||
@@ -273,14 +299,29 @@ export default {
|
||||
});
|
||||
|
||||
if (this.yesterDailies.length === 0) {
|
||||
this.isRunningYesterdailies = false;
|
||||
await axios.post('/api/v3/cron');
|
||||
this.handleUserNotifications(this.user.notifications);
|
||||
this.runYesterDailiesAction();
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!this.user.groupNotifications) this.user.groupNotifications = [];
|
||||
this.user.groupNotifications.push(notification);
|
||||
@@ -442,16 +483,6 @@ export default {
|
||||
|
||||
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>
|
||||
|
||||
@@ -123,15 +123,18 @@
|
||||
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
|
||||
span.category-select(v-else) {{getTagsFor(task)[0]}}
|
||||
.category-box(v-if="showTagsSelect")
|
||||
.form-check(
|
||||
v-for="tag in user.tags",
|
||||
:key="tag.id",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ tag.name }}
|
||||
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
|
||||
.container
|
||||
.row
|
||||
.form-check.col-6(
|
||||
v-for="tag in user.tags",
|
||||
:key="tag.id",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
||||
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'")
|
||||
label(v-once) {{ $t('resetStreak') }}
|
||||
b-dropdown(:text="$t(task.frequency)")
|
||||
@@ -145,15 +148,19 @@
|
||||
span.category-select(v-else)
|
||||
span(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
||||
.category-box(v-if="showAssignedSelect")
|
||||
.form-check(
|
||||
v-for="member in members",
|
||||
:key="member._id",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value="member._id", v-model="assignedMembers", @change='toggleAssignment(member._id)')
|
||||
span.custom-control-indicator
|
||||
span.custom-control-description(v-once) {{ member.profile.name }}
|
||||
button.btn.btn-primary(@click="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||
.container
|
||||
.row
|
||||
.form-check.col-6(
|
||||
v-for="member in members",
|
||||
:key="member._id",
|
||||
)
|
||||
label.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value="member._id", v-model="assignedMembers", @change='toggleAssignment(member._id)')
|
||||
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')
|
||||
label(v-once) Needs Approval
|
||||
@@ -313,6 +320,12 @@
|
||||
bottom: 0px;
|
||||
left: 40px;
|
||||
top: auto;
|
||||
z-index: 11;
|
||||
|
||||
.container {
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-group {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<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
|
||||
h1.header-welcome.text-center {{ $t('welcomeBack') }}
|
||||
p.call-to-action.text-center {{ $t('checkOffYesterDailies') }}
|
||||
@@ -44,7 +51,6 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
@@ -71,9 +77,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async close () {
|
||||
await axios.post('/api/v3/cron');
|
||||
// @TODO: Better way to sync user?
|
||||
window.location.href = '/';
|
||||
this.$root.$emit('hide::modal', 'yesterdaily');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -283,6 +283,10 @@ router.beforeEach(function routerGuard (to, from, next) {
|
||||
return next({name: to.path === '/' ? 'home' : 'login'});
|
||||
}
|
||||
|
||||
if (isUserLoggedIn && (to.name === 'login' || to.name === 'register')) {
|
||||
return next({name: 'tasks'});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import changeClassOp from 'common/script/ops/changeClass';
|
||||
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({
|
||||
store,
|
||||
path: 'user',
|
||||
@@ -15,7 +15,7 @@ export function fetch (store, forceLoad = false) { // eslint-disable-line no-sha
|
||||
deserialize (response) {
|
||||
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 { asyncResourceFactory } from 'client/libs/asyncResource';
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
|
||||
import actions from './actions';
|
||||
import getters from './getters';
|
||||
@@ -22,6 +23,10 @@ if (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-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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user