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:
Matteo Pagliazzi
2017-09-07 14:16:39 +02:00
committed by GitHub
parent adeee244e3
commit 5ec7815cfe
6 changed files with 116 additions and 60 deletions

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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');
}, },
}, },

View File

@@ -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();
}); });

View File

@@ -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,
}); });
} }

View File

@@ -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;
} }