diff --git a/test/client/unit/specs/store/actions/user.js b/test/client/unit/specs/store/actions/user.js
index 5069e04087..38739d8c45 100644
--- a/test/client/unit/specs/store/actions/user.js
+++ b/test/client/unit/specs/store/actions/user.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import generateStore from 'client/store';
-describe('tasks actions', () => {
+describe('user actions', () => {
let store;
beforeEach(() => {
diff --git a/website/client/app.vue b/website/client/app.vue
index 6f05b18552..2d0feaaf89 100644
--- a/website/client/app.vue
+++ b/website/client/app.vue
@@ -1,19 +1,20 @@
-
-
#app
- app-menu(v-if="userLoggedIn")
- .container-fluid(v-if="userLoggedIn")
- app-header
- router-view
-
- router-view(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
diff --git a/website/client/assets/scss/task.scss b/website/client/assets/scss/task.scss
index ee64dc34e8..805cdec48d 100644
--- a/website/client/assets/scss/task.scss
+++ b/website/client/assets/scss/task.scss
@@ -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 {
diff --git a/website/client/components/appHeader.vue b/website/client/components/appHeader.vue
index 21e7f631bf..352ad1086d 100644
--- a/website/client/components/appHeader.vue
+++ b/website/client/components/appHeader.vue
@@ -1,7 +1,7 @@
#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,27 +62,13 @@
flex-wrap: nowrap;
}
- .no-party, .party-members {
- flex-grow: 1;
+ h3 {
+ color: $white;
+ margin-bottom: 4px;
}
- .party-members {
- overflow-x: auto;
- }
-
- .no-party {
- .small-text {
- color: $header-color;
- }
-
- h3 {
- color: $white;
- margin-bottom: 4px;
- }
-
- button {
- margin-top: 16px;
- }
+ .btn {
+ margin-top: 16px;
}
}
@@ -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();
},
};
diff --git a/website/client/components/appMenu.vue b/website/client/components/appMenu.vue
index 8c2f92b2d5..18e8542d90 100644
--- a/website/client/components/appMenu.vue
+++ b/website/client/components/appMenu.vue
@@ -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') }}
diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue
index eef1253435..c6e85f8eb9 100644
--- a/website/client/components/static/home.vue
+++ b/website/client/components/static/home.vue
@@ -1,7 +1,7 @@
nav
- a(href='/login') Login
- a(href='/register') Register
+ router-link(:to="{name: 'login'}") Login
+ router-link(:to="{name: 'register'}") Register
diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue
index 84e5c58efb..f0348269c7 100644
--- a/website/client/components/tasks/task.vue
+++ b/website/client/components/tasks/task.vue
@@ -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")
diff --git a/website/client/index.html b/website/client/index.html
index 73f895a0f0..204f884234 100644
--- a/website/client/index.html
+++ b/website/client/index.html
@@ -4,14 +4,9 @@
Habitica
-
-
-
diff --git a/website/client/main.js b/website/client/main.js
index 3e69523450..4ec81e1669 100644
--- a/website/client/main.js
+++ b/website/client/main.js
@@ -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),
});
diff --git a/website/client/router.js b/website/client/router.js
index 52e1d70a25..aa97d8b968 100644
--- a/website/client/router.js
+++ b/website/client/router.js
@@ -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;
diff --git a/website/client/store/index.js b/website/client/store/index.js
index fee62bf38c..4643f22600 100644
--- a/website/client/store/index.js
+++ b/website/client/store/index.js
@@ -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;
}