Keep session and added Topbar component
This commit is contained in:
parent
5d6cdcb6df
commit
44b823e376
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="40mm" height="40mm" viewBox="0 0 40 40" version="1.1" id="svg823" inkscape:version="0.92.4 (5da689c313, 2019-01-14)" sodipodi:docname="logo.svg">
|
||||
<defs id="defs817"/>
|
||||
<sodipodi:namedview id="base" pagecolor="#2f2f2f" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:zoom="5.635625" inkscape:cx="70.551181" inkscape:cy="75.590551" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" inkscape:window-width="1920" inkscape:window-height="1043" inkscape:window-x="1920" inkscape:window-y="0" inkscape:window-maximized="1"/>
|
||||
<metadata id="metadata820">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-257)">
|
||||
<path style="fill:#88bd32;stroke-width:0.83333331" inkscape:connector-curvature="0" id="path4" d="m 27.583333,261.08333 c 0.25,-0.0833 0.5,-0.16666 0.75,-0.16666 L 39,259.5 l -0.166667,6.08333 c -0.166666,5 -3.083333,9.5 -6.666666,10.5 -0.25,0.0833 -0.5,0.16667 -0.75,0.16667 L 20.75,277.66667 l 0.166667,-6.08334 c 0.166666,-5.08333 3.083333,-9.5 6.666666,-10.5 z" class="st0"/>
|
||||
<path style="fill:#88bd32;stroke-width:0.83333331" inkscape:connector-curvature="0" id="path6" d="m 5.9166667,281.91667 c 0.1666667,-0.0833 0.4166667,-0.0833 0.5833334,-0.0833 L 14.25,280.75 14.16667,285.08333 C 14.08334,288.75 11.91667,292 9.3333364,292.75 c -0.25,0.0833 -0.4166667,0.0833 -0.5833334,0.0833 L 1.0000001,293.91667 1.0833334,289.5 c 0.1666667,-3.58333 2.25,-6.91667 4.8333333,-7.58333 z" class="st0"/>
|
||||
<g id="g10" style="fill:#f7931e;fill-opacity:1;stroke:none;stroke-opacity:1" transform="matrix(0.83333333,0,0,0.83333333,8.0000002e-8,257)">
|
||||
<path style="fill:#f7931e;fill-opacity:1;stroke:none;stroke-opacity:1" inkscape:connector-curvature="0" id="path8" d="m 12,48 c -0.4,0 -0.7,-0.3 -0.7,-0.6 0,-0.4 0.2,-0.7 0.6,-0.8 0,0 3,-0.3 5.5,-3.4 3,-3.8 4.1,-10.1 3,-18.4 C 19.3,16.3 20.6,9.7 24.1,5.3 27.8,0.6 32.5,0 32.7,0 c 0.4,0 0.7,0.2 0.8,0.6 0,0.4 -0.2,0.7 -0.6,0.8 0,0 -4.3,0.6 -7.6,4.7 -3.3,4.2 -4.4,10.4 -3.4,18.5 1.1,8.8 0,15.4 -3.4,19.5 -2.8,3.5 -6.2,3.9 -6.5,3.9 0.1,0 0.1,0 0,0 z" class="st1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<q-header class="bg-darker" color="white" elevated>
|
||||
<q-toolbar class="q-py-sm q-px-md">
|
||||
<q-btn flat @click="drawer = !drawer" round dense icon="menu" />
|
||||
<router-link to="/">
|
||||
<q-btn flat round class="q-ml-xs" v-if="$q.screen.gt.xs">
|
||||
<q-avatar square size="md"><img src="@/assets/logo_icon.svg" alt="Logo" /></q-avatar>
|
||||
</q-btn>
|
||||
</router-link>
|
||||
<q-toolbar-title shrink class="text-weight-bold"> Salix </q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
<q-btn dense flat no-wrap>
|
||||
<q-avatar size="lg">
|
||||
<q-img
|
||||
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</q-avatar>
|
||||
<q-tooltip>Account</q-tooltip>
|
||||
<q-icon name="arrow_drop_down" size="s" />
|
||||
|
||||
<q-menu>
|
||||
<div class="row no-wrap q-pa-md">
|
||||
<div class="column">
|
||||
<div class="text-h6 q-mb-md">Settings</div>
|
||||
<q-toggle
|
||||
:label="$t(`globals.lang['${language}']`)"
|
||||
icon="public"
|
||||
color="orange"
|
||||
false-value="es"
|
||||
true-value="en"
|
||||
v-model="language"
|
||||
/>
|
||||
<q-toggle
|
||||
v-model="mode"
|
||||
checked-icon="dark_mode"
|
||||
color="orange"
|
||||
unchecked-icon="light_mode"
|
||||
/>
|
||||
|
||||
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
|
||||
</div>
|
||||
|
||||
<q-separator vertical inset class="q-mx-lg" />
|
||||
|
||||
<div class="column items-center" style="min-width: 150px">
|
||||
<q-avatar size="80px">
|
||||
<q-img
|
||||
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</q-avatar>
|
||||
|
||||
<div class="text-subtitle1 q-mt-md">
|
||||
<strong>{{ user.nickname }}</strong>
|
||||
</div>
|
||||
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
|
||||
|
||||
<q-btn
|
||||
color="orange"
|
||||
flat
|
||||
label="Log Out"
|
||||
size="sm"
|
||||
icon="logout"
|
||||
@click="logout()"
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from 'vue-class-component';
|
||||
import axios from 'axios';
|
||||
import { Dark } from 'quasar';
|
||||
|
||||
interface UserProfile {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
@Options({
|
||||
props: {
|
||||
drawer: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Topbar extends Vue {
|
||||
drawer?: boolean;
|
||||
private lang = 'es';
|
||||
|
||||
mounted(): void {
|
||||
axios
|
||||
.get('/api/accounts/acl')
|
||||
.then((response) => {
|
||||
this.$store.dispatch('updateUserData', response.data);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$q.notify({
|
||||
message: this.$t('globals.accessDenied'),
|
||||
type: 'negative',
|
||||
});
|
||||
this.$store.dispatch('logOut');
|
||||
this.$router.push('/login');
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.$store.dispatch('logOut');
|
||||
this.$router.push('/login');
|
||||
}
|
||||
|
||||
get user(): UserProfile {
|
||||
return this.$store.state.user;
|
||||
}
|
||||
|
||||
get token(): string {
|
||||
return this.$store.getters.token;
|
||||
}
|
||||
|
||||
get mode(): boolean {
|
||||
return Dark.isActive;
|
||||
}
|
||||
|
||||
set mode(value: boolean) {
|
||||
Dark.set(value);
|
||||
}
|
||||
|
||||
get language(): string {
|
||||
return this.lang;
|
||||
}
|
||||
|
||||
set language(value: string) {
|
||||
this.lang = value;
|
||||
this.$i18n.locale = value;
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -3,7 +3,9 @@
|
|||
"lang": {
|
||||
"es": "Español",
|
||||
"en": "Inglés"
|
||||
}
|
||||
},
|
||||
"accessDenied": "Acceso denegado",
|
||||
"internalServerError": "Ha ocurrido un error interno del servidor"
|
||||
},
|
||||
"login": {
|
||||
"title": "Iniciar sesión",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||
import store from '../store';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
|
@ -12,15 +13,15 @@ const routes: Array<RouteRecordRaw> = [
|
|||
component: () => import('../views/Main/Main.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('../views/Main/Dashboard.vue'),
|
||||
},
|
||||
/* {
|
||||
path: '/Client',
|
||||
name: 'Client',
|
||||
component: () => import('../views/Client/client.vue'),
|
||||
}, */
|
||||
{
|
||||
path: '/customer',
|
||||
name: 'Customer',
|
||||
component: () => import('../views/Customer/Main.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
|
@ -36,7 +37,7 @@ const router = createRouter({
|
|||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const loggedIn = localStorage.getItem('token');
|
||||
const loggedIn = store.getters.isLoggedIn;
|
||||
|
||||
if (to.name !== 'Login' && !loggedIn) {
|
||||
next({ path: '/login', query: { redirect: to.fullPath } });
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
import { createStore } from 'vuex';
|
||||
|
||||
interface UserProfile {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
user: null,
|
||||
user: {},
|
||||
roles: [],
|
||||
},
|
||||
mutations: {
|
||||
setUser(state, user) {
|
||||
setUser(state, data) {
|
||||
const user: UserProfile = {
|
||||
id: data.id,
|
||||
username: data.name,
|
||||
nickname: data.nickname,
|
||||
token: data.token,
|
||||
};
|
||||
|
||||
state.user = user;
|
||||
},
|
||||
setRoles(state, roles) {
|
||||
|
@ -14,15 +28,20 @@ export default createStore({
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
logIn({ commit }, auth) {
|
||||
localStorage.setItem('token', auth.token);
|
||||
logIn({ commit }, data) {
|
||||
if (data.keepLogin) {
|
||||
localStorage.setItem('token', data.token);
|
||||
} else {
|
||||
sessionStorage.setItem('token', data.token);
|
||||
}
|
||||
|
||||
commit('setUser', auth);
|
||||
commit('setUser', data);
|
||||
},
|
||||
logOut({ commit }) {
|
||||
localStorage.removeItem('token');
|
||||
sessionStorage.getItem('token');
|
||||
|
||||
commit('setUser', null);
|
||||
commit('setUser', {});
|
||||
},
|
||||
updateUserData({ commit }, data) {
|
||||
commit('setUser', data.user);
|
||||
|
@ -30,8 +49,17 @@ export default createStore({
|
|||
},
|
||||
},
|
||||
getters: {
|
||||
hasData(state) {
|
||||
return !!state.user;
|
||||
isLoggedIn() {
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
return !!(localToken || sessionToken);
|
||||
},
|
||||
token() {
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
return localToken || sessionToken;
|
||||
},
|
||||
},
|
||||
modules: {},
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<q-layout>
|
||||
<q-page class="q-pa-md">
|
||||
<q-card class="q-pa-md"> Dashboard page.. </q-card>
|
||||
</q-page>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from 'vue-class-component';
|
||||
|
||||
@Options({})
|
||||
export default class Customer extends Vue {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -55,7 +55,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue } from 'vue-class-component';
|
||||
import { Options, Vue } from 'vue-class-component';
|
||||
import { Dark, useQuasar } from 'quasar';
|
||||
import axios from 'axios';
|
||||
|
||||
|
@ -63,20 +63,21 @@ interface LoginForm {
|
|||
username: string;
|
||||
password: string;
|
||||
keepLogin: boolean;
|
||||
Dark: Dark;
|
||||
}
|
||||
|
||||
@Options({
|
||||
setup() {
|
||||
return {
|
||||
$q: useQuasar(),
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Login extends Vue {
|
||||
username?: string;
|
||||
password?: string;
|
||||
keepLogin?: boolean;
|
||||
private lang = 'es';
|
||||
|
||||
setup() {
|
||||
return {
|
||||
$q: useQuasar(),
|
||||
};
|
||||
}
|
||||
|
||||
get mode(): boolean {
|
||||
return Dark.isActive;
|
||||
}
|
||||
|
@ -103,6 +104,7 @@ export default class Login extends Vue {
|
|||
.then((response) => {
|
||||
this.$store.dispatch('logIn', {
|
||||
token: response.data.token,
|
||||
keepLogin: this.keepLogin,
|
||||
});
|
||||
|
||||
this.$q.notify({
|
||||
|
@ -111,12 +113,20 @@ export default class Login extends Vue {
|
|||
});
|
||||
this.$router.push('/');
|
||||
})
|
||||
.catch(() =>
|
||||
this.$q.notify({
|
||||
message: this.$t('login.loginError'),
|
||||
type: 'negative',
|
||||
})
|
||||
);
|
||||
.catch((error) => {
|
||||
const errorCode = error.response.status;
|
||||
if (errorCode === 401) {
|
||||
this.$q.notify({
|
||||
message: this.$t('login.loginFailed'),
|
||||
type: 'negative',
|
||||
});
|
||||
} else {
|
||||
this.$q.notify({
|
||||
message: this.$t('globals.internalServerError'),
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data(): LoginForm {
|
||||
|
@ -124,7 +134,6 @@ export default class Login extends Vue {
|
|||
username: '',
|
||||
password: '',
|
||||
keepLogin: true,
|
||||
Dark: Dark,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<q-layout> page.. </q-layout>
|
||||
<q-layout>
|
||||
<q-page class="q-pa-md">
|
||||
<q-card class="q-pa-md"> Dashboard page.. </q-card>
|
||||
</q-page>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,73 +1,6 @@
|
|||
<template>
|
||||
<q-layout>
|
||||
<q-header class="bg-darker" color="white" elevated>
|
||||
<q-toolbar>
|
||||
<q-btn flat @click="drawer = !drawer" round dense icon="menu" />
|
||||
<q-toolbar-title>Salix</q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
|
||||
<q-btn dense flat no-wrap>
|
||||
<q-avatar size="lg">
|
||||
<q-img
|
||||
:src="`/api/Images/user/160x160/${id}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</q-avatar>
|
||||
<q-tooltip>Account</q-tooltip>
|
||||
<q-icon name="arrow_drop_down" size="s" />
|
||||
|
||||
<q-menu>
|
||||
<div class="row no-wrap q-pa-md">
|
||||
<div class="column">
|
||||
<div class="text-h6 q-mb-md">Settings</div>
|
||||
<q-toggle
|
||||
:label="$t(`globals.lang['${language}']`)"
|
||||
icon="public"
|
||||
color="orange"
|
||||
false-value="es"
|
||||
true-value="en"
|
||||
v-model="language"
|
||||
/>
|
||||
<q-toggle
|
||||
v-model="mode"
|
||||
checked-icon="dark_mode"
|
||||
color="orange"
|
||||
unchecked-icon="light_mode"
|
||||
/>
|
||||
|
||||
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
|
||||
</div>
|
||||
|
||||
<q-separator vertical inset class="q-mx-lg" />
|
||||
|
||||
<div class="column items-center" style="min-width: 150px">
|
||||
<q-avatar size="80px">
|
||||
<q-img
|
||||
:src="`/api/Images/user/160x160/${id}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</q-avatar>
|
||||
|
||||
<div class="text-subtitle1 q-mt-md">
|
||||
<strong>{{ nickname }}</strong>
|
||||
</div>
|
||||
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ username }}</div>
|
||||
|
||||
<q-btn
|
||||
color="orange"
|
||||
flat
|
||||
label="Log Out"
|
||||
size="sm"
|
||||
icon="logout"
|
||||
@click="logout()"
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<Topbar :drawer="drawer" />
|
||||
<q-drawer
|
||||
v-model="drawer"
|
||||
show-if-above
|
||||
|
@ -80,21 +13,18 @@
|
|||
>
|
||||
<q-scroll-area class="fit">
|
||||
<q-list padding>
|
||||
<q-item active clickable v-ripple>
|
||||
<q-item clickable v-ripple :to="{ path: '/dashboard' }" active-class="text-orange">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="dashboard" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section> Dashboard </q-item-section>
|
||||
<q-item-section> Dashboard</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple>
|
||||
<q-item clickable v-ripple :to="{ path: '/customer' }" active-class="text-orange">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="people" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section> Customers </q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="receipt" />
|
||||
|
@ -131,81 +61,16 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from 'vue-class-component';
|
||||
import { Dark } from 'quasar';
|
||||
import axios from 'axios';
|
||||
import { ref } from 'vue';
|
||||
import Topbar from '@/components/Topbar.vue';
|
||||
|
||||
@Options({})
|
||||
@Options({
|
||||
components: {
|
||||
Topbar,
|
||||
},
|
||||
})
|
||||
export default class Main extends Vue {
|
||||
private lang = 'es';
|
||||
drawer = false;
|
||||
miniState = true;
|
||||
|
||||
mounted(): void {
|
||||
axios.get('/api/accounts/acl').then((response) => {
|
||||
this.$store.dispatch('updateUserData', response.data);
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.$store.dispatch('logOut');
|
||||
this.$router.push('/login');
|
||||
}
|
||||
|
||||
get nickLetter(): string {
|
||||
if (this.nickname) {
|
||||
return this.nickname.charAt(0);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
get nickname(): string {
|
||||
if (this.$store.getters.hasData) {
|
||||
return this.$store.state.user.nickname;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
get username(): string {
|
||||
if (this.$store.getters.hasData) {
|
||||
return this.$store.state.user.name;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
if (this.$store.getters.hasData) {
|
||||
return this.$store.state.user.id;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
get token(): string {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
return token ? token : '';
|
||||
}
|
||||
|
||||
get mode(): boolean {
|
||||
return Dark.isActive;
|
||||
}
|
||||
|
||||
set mode(value: boolean) {
|
||||
Dark.set(value);
|
||||
}
|
||||
|
||||
get language(): string {
|
||||
return this.lang;
|
||||
}
|
||||
|
||||
set language(value: string) {
|
||||
this.lang = value;
|
||||
this.$i18n.locale = value;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in New Issue