mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
Client fixes (#8844)
* client: fix router when not authenticated, small fixes for tasks * load the user only when necessary * fix tests
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
describe('tasks actions', () => {
|
||||
describe('user actions', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<!-- Entry point component for the entire app -->
|
||||
|
||||
<template lang="pug">
|
||||
#app
|
||||
app-menu(v-if="userLoggedIn")
|
||||
.container-fluid(v-if="userLoggedIn")
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
|
||||
p Loading...
|
||||
template(v-else)
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header
|
||||
router-view
|
||||
|
||||
router-view(v-if="!userLoggedIn")
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppMenu from './components/appMenu';
|
||||
import AppHeader from './components/appHeader';
|
||||
import axios from 'axios';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
@@ -23,44 +24,32 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
userLoggedIn: false,
|
||||
isUserLoaded: false,
|
||||
};
|
||||
},
|
||||
async beforeCreate () {
|
||||
computed: {
|
||||
...mapState(['isUserLoggedIn']),
|
||||
isStaticPage () {
|
||||
return this.$route.meta.requiresLogin === false ? true : false;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
// Setup listener for title
|
||||
this.$store.watch(state => state.title, (title) => {
|
||||
document.title = title;
|
||||
});
|
||||
|
||||
// Mount the app when user and tasks are loaded
|
||||
const userDataWatcher = this.$store.watch(state => [state.user.data, state.tasks.data], ([user, tasks]) => {
|
||||
if (user && user._id && Array.isArray(tasks)) {
|
||||
userDataWatcher(); // remove the watcher
|
||||
// this.$mount('#app');
|
||||
}
|
||||
});
|
||||
|
||||
// @TODO: Move this to store?
|
||||
let authSettings = localStorage.getItem('habit-mobile-settings');
|
||||
if (!authSettings) return;
|
||||
|
||||
authSettings = JSON.parse(authSettings);
|
||||
axios.defaults.headers.common['x-api-user'] = authSettings.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = authSettings.auth.apiToken;
|
||||
|
||||
if (this.isUserLoggedIn && !this.isStaticPage) {
|
||||
// Load the user and the user tasks
|
||||
await Promise.all([
|
||||
Promise.all([
|
||||
this.$store.dispatch('user:fetch'),
|
||||
this.$store.dispatch('tasks:fetchUserTasks'),
|
||||
]).catch((err) => {
|
||||
console.error('Impossible to fetch user. Copy into localStorage a valid habit-mobile-settings object.', err); // eslint-disable-line no-console
|
||||
]).then(() => {
|
||||
this.isUserLoaded = true;
|
||||
}).catch((err) => {
|
||||
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
|
||||
});
|
||||
|
||||
this.userLoggedIn = true;
|
||||
},
|
||||
mounted () { // Remove the loading screen when the app is mounted
|
||||
let loadingScreen = document.getElementById('loading-screen');
|
||||
if (loadingScreen) document.body.removeChild(loadingScreen);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -6,51 +6,79 @@
|
||||
|
||||
&-worst {
|
||||
background: $maroon-100;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($maroon-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $maroon-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-worse {
|
||||
background: $red-100;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($red-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $red-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-bad {
|
||||
background: $orange-100;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($orange-100, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $orange-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-neutral {
|
||||
background: $yellow-50;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($yellow-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $yellow-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-good {
|
||||
background: $green-10;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($green-10, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $green-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-better {
|
||||
background: $blue-50;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($blue-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-best {
|
||||
background: $teal-50;
|
||||
&-control {
|
||||
&-control-habit {
|
||||
background: darken($teal-50, 12%);
|
||||
}
|
||||
|
||||
&-control-daily-todo {
|
||||
background: $teal-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-reward {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="pug">
|
||||
#app-header.row
|
||||
member-details(:member="user", @click="$router.push({name: 'avatar'})")
|
||||
.view-party
|
||||
.view-party(v-if="user.party && user.party._id")
|
||||
// TODO button should open the party members modal
|
||||
router-link.btn.btn-primary(:active-class="''", :to="{name: 'party'}") {{ $t('viewParty') }}
|
||||
.party-members.d-flex(v-if="partyMembers && partyMembers.length > 1")
|
||||
@@ -62,28 +62,14 @@
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.no-party, .party-members {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.party-members {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.no-party {
|
||||
.small-text {
|
||||
color: $header-color;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: $white;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
.btn {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -119,12 +105,9 @@ export default {
|
||||
this.expandedMember = memberId;
|
||||
}
|
||||
},
|
||||
launchPartyModal () {
|
||||
this.$root.$emit('show::modal', 'create-party-modal');
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.getPartyMembers();
|
||||
if (this.user.party && this.user.party._id) this.getPartyMembers();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
|
||||
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
|
||||
router-link.nav-item(tag="li", :to="{name: 'shops'}", exact)
|
||||
a.nav-link(v-once) {{ $t('shops') }}
|
||||
router-link.nav-item.dropdown(:to="{name: 'party'}")
|
||||
router-link.nav-item(tag="li", :to="{name: 'party'}")
|
||||
a.nav-link(v-once) {{ $t('party') }}
|
||||
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
|
||||
a.nav-link(v-once) {{ $t('guilds') }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="pug">
|
||||
nav
|
||||
a(href='/login') Login
|
||||
a(href='/register') Register
|
||||
router-link(:to="{name: 'login'}") Login
|
||||
router-link(:to="{name: 'register'}") Register
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
.task.d-flex
|
||||
// Habits left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
|
||||
.task-control.habit-control(:class="controlClass.up + '-control'")
|
||||
.task-control.habit-control(:class="controlClass.up + '-control-habit'")
|
||||
.svg-icon.positive(v-html="icons.positive")
|
||||
// Dailies and todos left side control
|
||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
|
||||
.task-control.daily-todo-control(:class="controlClass + '-control'")
|
||||
.task-control.daily-todo-control(:class="controlClass + '-control-daily-todo'")
|
||||
.svg-icon.check(v-html="icons.check", v-if="task.completed")
|
||||
// Task title, description and icons
|
||||
.task-content(:class="contentClass")
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
// Habits right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
|
||||
.task-control.habit-control(:class="controlClass.down + '-control'")
|
||||
.task-control.habit-control(:class="controlClass.down + '-control-habit'")
|
||||
.svg-icon.negative(v-html="icons.negative")
|
||||
// Rewards right side control
|
||||
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass")
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Habitica</title>
|
||||
<!-- TODO load google fonts separately as @import is slow, find alternative -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,400i,700,700i|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- #loading-screen needs to be rendered before vue, will be deleted once app is loaded -->
|
||||
<div id="loading-screen" class="h-100 w-100 d-flex justify-content-center align-items-center">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
require('babel-polyfill');
|
||||
|
||||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import AppComponent from './app';
|
||||
import router from './router';
|
||||
import generateStore from './store';
|
||||
import getStore from './store';
|
||||
import StoreModule from './libs/store';
|
||||
import './filters/registerGlobals';
|
||||
import i18n from './libs/i18n';
|
||||
@@ -26,18 +25,9 @@ Vue.config.productionTip = IS_PRODUCTION;
|
||||
Vue.use(i18n);
|
||||
Vue.use(StoreModule);
|
||||
|
||||
// TODO just until we have proper authentication
|
||||
let authSettings = localStorage.getItem('habit-mobile-settings');
|
||||
|
||||
if (authSettings) {
|
||||
authSettings = JSON.parse(authSettings);
|
||||
axios.defaults.headers.common['x-api-user'] = authSettings.auth.apiId;
|
||||
axios.defaults.headers.common['x-api-key'] = authSettings.auth.apiToken;
|
||||
}
|
||||
|
||||
export default new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store: generateStore(),
|
||||
store: getStore(),
|
||||
render: h => h(AppComponent),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import getStore from 'client/store';
|
||||
|
||||
import EmptyView from './components/emptyView';
|
||||
|
||||
@@ -38,7 +39,7 @@ const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/gu
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default new VueRouter({
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.NODE_ENV === 'production' ? '/new-app' : __dirname, // eslint-disable-line no-process-env
|
||||
linkActiveClass: 'active',
|
||||
@@ -47,10 +48,11 @@ export default new VueRouter({
|
||||
scrollBehavior () {
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
// requiresLogin is true by default, isStatic false
|
||||
routes: [
|
||||
{ name: 'home', path: '/home', component: Home },
|
||||
{ name: 'register', path: '/register', component: RegisterLogin },
|
||||
{ name: 'login', path: '/login', component: RegisterLogin },
|
||||
{ name: 'home', path: '/home', component: Home, meta: {requiresLogin: false} },
|
||||
{ name: 'register', path: '/register', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'login', path: '/login', component: RegisterLogin, meta: {requiresLogin: false} },
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
path: '/inventory',
|
||||
@@ -115,3 +117,22 @@ export default new VueRouter({
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const store = getStore();
|
||||
|
||||
router.beforeEach(function routerGuard (to, from, next) {
|
||||
const isUserLoggedIn = store.state.isUserLoggedIn;
|
||||
const routeRequiresLogin = to.meta.requiresLogin !== false;
|
||||
|
||||
if (!isUserLoggedIn && routeRequiresLogin) {
|
||||
// Redirect to the login page unless the user is trying to reach the
|
||||
// root of the website, in which case show the home page.
|
||||
// TODO when redirecting to login if user login then redirect back to initial page
|
||||
// so if you tried to go to /party you'll be redirected to /party after login/signup
|
||||
return next({name: to.path === '/' ? 'home' : 'login'});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -3,18 +3,40 @@ import deepFreeze from 'client/libs/deepFreeze';
|
||||
import content from 'common/script/content/index';
|
||||
import * as constants from 'common/script/constants';
|
||||
import { asyncResourceFactory } from 'client/libs/asyncResource';
|
||||
import axios from 'axios';
|
||||
|
||||
import actions from './actions';
|
||||
import getters from './getters';
|
||||
|
||||
const IS_TEST = process.env.NODE_ENV === 'test'; // eslint-disable-line no-process-env
|
||||
|
||||
// Load user auth parameters and determine if it's logged in
|
||||
// before trying to load data
|
||||
let isUserLoggedIn = false;
|
||||
|
||||
let AUTH_SETTINGS = localStorage.getItem('habit-mobile-settings');
|
||||
|
||||
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;
|
||||
isUserLoggedIn = true;
|
||||
}
|
||||
|
||||
// Export a function that generates the store and not the store directly
|
||||
// so that we can regenerate it multiple times for testing
|
||||
// so that we can regenerate it multiple times for testing, when not testing
|
||||
// always export the same route
|
||||
|
||||
let existingStore;
|
||||
export default function () {
|
||||
return new Store({
|
||||
if (!IS_TEST && existingStore) return existingStore;
|
||||
|
||||
existingStore = new Store({
|
||||
actions,
|
||||
getters,
|
||||
state: {
|
||||
title: 'Habitica',
|
||||
isUserLoggedIn,
|
||||
user: asyncResourceFactory(),
|
||||
tasks: asyncResourceFactory(), // user tasks
|
||||
party: {
|
||||
@@ -30,4 +52,6 @@ export default function () {
|
||||
constants: deepFreeze(constants),
|
||||
},
|
||||
});
|
||||
|
||||
return existingStore;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user