forked from verdnatura/salix-front
Login fixed
This commit is contained in:
parent
517c6c2d5f
commit
659a396fbb
|
@ -80,6 +80,12 @@ module.exports = {
|
|||
// TypeScript
|
||||
quotes: ['warn', 'single', { avoidEscape: true }],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
|
||||
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'off',
|
||||
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
|
|
|
@ -58,6 +58,7 @@ module.exports = {
|
|||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
'^app/(.*)$': '<rootDir>/$1',
|
||||
'^components/(.*)$': '<rootDir>/src/components/$1',
|
||||
'^composables/(.*)$': '<rootDir>/src/composables/$1',
|
||||
'^layouts/(.*)$': '<rootDir>/src/layouts/$1',
|
||||
'^pages/(.*)$': '<rootDir>/src/pages/$1',
|
||||
'^assets/(.*)$': '<rootDir>/src/assets/$1',
|
||||
|
|
|
@ -107,7 +107,7 @@ module.exports = configure(function (ctx) {
|
|||
// directives: [],
|
||||
|
||||
// Quasar plugins
|
||||
plugins: [],
|
||||
plugins: ['Notify'],
|
||||
},
|
||||
|
||||
// animations: 'all', // --- includes all animations
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
const { getToken } = useSession();
|
||||
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$axios: AxiosInstance;
|
||||
|
@ -15,6 +19,22 @@ declare module '@vue/runtime-core' {
|
|||
// for each client)
|
||||
const api = axios.create({ baseURL: 'https://api.example.com' });
|
||||
|
||||
axios.interceptors.request.use(
|
||||
function (context) {
|
||||
const token = getToken();
|
||||
|
||||
if (token.length && context.headers) {
|
||||
context.headers.Authorization = token;
|
||||
}
|
||||
|
||||
return context;
|
||||
},
|
||||
function (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
export default boot(({ app }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<q-header class="bg-dark" color="white" elevated bordered>
|
||||
<q-toolbar class="q-py-sm q-px-md">
|
||||
<q-btn flat @click="$emit('on-toggle-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">
|
||||
<q-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>
|
||||
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
|
||||
<q-btn v-if="$q.screen.gt.xs" dense flat size="md" icon="add">
|
||||
<q-icon name="arrow_drop_down" size="s" />
|
||||
<q-menu>
|
||||
<q-list style="min-width: 150px">
|
||||
<q-item clickable>
|
||||
<q-item-section>New customer</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable>
|
||||
<q-item-section>New ticket</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
<q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications" />
|
||||
<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" />
|
||||
<UserPanel />
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import UserPanel from 'src/components/UserPanel.vue';
|
||||
|
||||
const session = useSession();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
const token = session.getToken();
|
||||
</script>
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<q-menu>
|
||||
<div class="row no-wrap q-pa-md">
|
||||
<div class="column">
|
||||
<div class="text-h6 q-mb-md">{{ t('components.userPanel.settings') }}</div>
|
||||
<q-toggle
|
||||
:label="t(`globals.lang['${locale}']`)"
|
||||
icon="public"
|
||||
color="orange"
|
||||
false-value="es"
|
||||
true-value="en"
|
||||
v-model="locale"
|
||||
/>
|
||||
<q-toggle
|
||||
v-model="darkMode"
|
||||
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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, computed } from 'vue';
|
||||
import { Dark, useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const state = useState();
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const darkMode = computed({
|
||||
get(): boolean {
|
||||
return Dark.isActive;
|
||||
},
|
||||
set(value: boolean): void {
|
||||
Dark.set(value);
|
||||
},
|
||||
});
|
||||
|
||||
const user = state.getUser();
|
||||
const token = session.getToken();
|
||||
|
||||
onMounted(() => {
|
||||
axios
|
||||
.get('/api/accounts/acl')
|
||||
.then(({ data }) => {
|
||||
state.setUser({
|
||||
id: data.user.id,
|
||||
username: data.user.name,
|
||||
nickname: data.user.nickname,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
quasar.notify({
|
||||
message: t('errors.statusUnauthorized'),
|
||||
type: 'negative',
|
||||
});
|
||||
logout();
|
||||
});
|
||||
});
|
||||
|
||||
function logout(): void {
|
||||
session.destroy();
|
||||
router.push('/login');
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,4 @@
|
|||
import store from '@/store';
|
||||
/* import store from '@/store';
|
||||
|
||||
export function useRole() {
|
||||
function hasAny(roles: string[]): boolean {
|
||||
|
@ -15,3 +15,4 @@ export function useRole() {
|
|||
hasAny,
|
||||
};
|
||||
}
|
||||
*/
|
|
@ -2,6 +2,31 @@
|
|||
// so you can safely delete all default props below
|
||||
|
||||
export default {
|
||||
failed: 'Action failed',
|
||||
success: 'Action was successful',
|
||||
'globals': {
|
||||
'lang': {
|
||||
'es': 'Spanish',
|
||||
'en': 'English'
|
||||
}
|
||||
},
|
||||
'errors': {
|
||||
'statusUnauthorized': 'Access denied',
|
||||
'statusInternalServerError': 'An internal server error has ocurred'
|
||||
},
|
||||
'login': {
|
||||
'title': 'Login',
|
||||
'username': 'Username',
|
||||
'password': 'Password',
|
||||
'submit': 'Log in',
|
||||
'keepLogin': 'Keep me logged in',
|
||||
'loginSuccess': 'You have successfully logged in',
|
||||
'loginError': 'Invalid username or password'
|
||||
},
|
||||
'customer': {},
|
||||
'components': {
|
||||
'topbar': {},
|
||||
'userPanel': {
|
||||
'settings': 'Settings',
|
||||
'logOut': 'Log Out'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
export default {
|
||||
'globals': {
|
||||
'lang': {
|
||||
'es': 'Español',
|
||||
'en': 'Inglés'
|
||||
}
|
||||
},
|
||||
'errors': {
|
||||
'statusUnauthorized': 'Acceso denegado',
|
||||
'statusInternalServerError': 'Ha ocurrido un error interno del servidor'
|
||||
},
|
||||
'login': {
|
||||
'title': 'Iniciar sesión',
|
||||
'username': 'Nombre de usuario',
|
||||
'password': 'Contraseña',
|
||||
'submit': 'Iniciar sesión',
|
||||
'keepLogin': 'Mantener sesión iniciada',
|
||||
'loginSuccess': 'Inicio de sesión correcto',
|
||||
'loginError': 'Nombre de usuario o contraseña incorrectos'
|
||||
},
|
||||
'customer': {},
|
||||
'components': {
|
||||
'topbar': {},
|
||||
'userPanel': {
|
||||
'settings': 'Configuración',
|
||||
'logOut': 'Cerrar sesión'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import en from './en';
|
||||
import es from './es';
|
||||
|
||||
export default {
|
||||
'en': en,
|
||||
'es': es,
|
||||
};
|
||||
|
|
|
@ -13,30 +13,40 @@
|
|||
>
|
||||
<q-scroll-area class="fit text-grey-8">
|
||||
<q-list padding>
|
||||
<q-item clickable v-ripple :to="{ path: '/dashboard' }" active-class="text-orange">
|
||||
<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 :to="{ path: '/customer' }" active-class="text-orange">
|
||||
<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-section>Customers</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple :to="{ path: '/ticket' }" active-class="text-orange">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="vn:ticket" />
|
||||
</q-item-section>
|
||||
<q-item-section> Tickets </q-item-section>
|
||||
<q-item-section>Tickets</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-icon name="receipt" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section> Invoice Out </q-item-section>
|
||||
<q-item-section>Invoice Out</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-ripple>
|
||||
|
@ -44,7 +54,7 @@
|
|||
<q-icon name="shopping_cart" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section> Catalog </q-item-section>
|
||||
<q-item-section>Catalog</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator />
|
||||
|
@ -54,7 +64,7 @@
|
|||
<q-icon name="drafts" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section> Drafts </q-item-section>
|
||||
<q-item-section>Drafts</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-scroll-area>
|
||||
|
@ -67,7 +77,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Topbar from '@/components/Topbar.vue';
|
||||
import Topbar from 'src/components/Topbar.vue';
|
||||
const drawer = ref(false);
|
||||
const miniState = ref(true);
|
||||
|
||||
|
@ -77,7 +87,4 @@ function onToggleDrawer(): void {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-darker {
|
||||
background-color: $darker;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -23,7 +23,13 @@
|
|||
<q-page>
|
||||
<div id="login">
|
||||
<q-card class="login q-pa-xl">
|
||||
<img src="@/assets/logo.svg" alt="Logo" class="logo q-mb-xl" />
|
||||
<q-img
|
||||
src="~/assets/logo.svg"
|
||||
alt="Logo"
|
||||
fit="contain"
|
||||
:ratio="16 / 9"
|
||||
class="q-mb-md"
|
||||
/>
|
||||
<q-form @submit="onSubmit" class="q-gutter-md">
|
||||
<q-input
|
||||
filled
|
||||
|
@ -70,7 +76,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRouter } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
import { useSession } from '/components/Session';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const session = useSession();
|
||||
|
@ -90,38 +96,38 @@ const darkMode = computed({
|
|||
},
|
||||
});
|
||||
|
||||
function onSubmit(): void {
|
||||
axios
|
||||
.post('/api/accounts/login', {
|
||||
async function onSubmit(): Promise<void> {
|
||||
try {
|
||||
const { data } = await axios.post('/api/accounts/login', {
|
||||
user: username.value,
|
||||
password: password.value,
|
||||
})
|
||||
.then((response) => {
|
||||
session.setToken({
|
||||
token: response.data.token,
|
||||
keepLogin: keepLogin.value,
|
||||
});
|
||||
});
|
||||
session.setToken({
|
||||
token: data.token,
|
||||
keepLogin: keepLogin.value,
|
||||
});
|
||||
|
||||
quasar.notify({
|
||||
message: t('login.loginSuccess'),
|
||||
type: 'positive',
|
||||
});
|
||||
router.push('/dashboard');
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorCode = error.response.status;
|
||||
quasar.notify({
|
||||
message: t('login.loginSuccess'),
|
||||
type: 'positive',
|
||||
});
|
||||
await router.push({ path: '/dashboard' });
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const errorCode = error.response && error.response.status;
|
||||
if (errorCode === 401) {
|
||||
quasar.notify({
|
||||
message: t('login.loginFailed'),
|
||||
type: 'negative',
|
||||
});
|
||||
} else {
|
||||
quasar.notify({
|
||||
message: t('errors.statusInternalServerError'),
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
quasar.notify({
|
||||
message: t('errors.statusInternalServerError'),
|
||||
type: 'negative',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
createWebHistory,
|
||||
} from 'vue-router';
|
||||
import routes from './routes';
|
||||
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
const session = useSession();
|
||||
/*
|
||||
* If not building with SSR mode, you can
|
||||
* directly export the Router instantiation;
|
||||
|
@ -35,5 +36,16 @@ export default route(function (/* { store, ssrContext } */) {
|
|||
),
|
||||
});
|
||||
|
||||
Router.beforeEach((to, from, next) => {
|
||||
|
||||
const { isLoggedIn } = session;
|
||||
|
||||
if (!isLoggedIn && to.name !== 'Login') {
|
||||
next({ path: '/login', query: { redirect: to.fullPath } });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
return Router;
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: '/',
|
||||
name: 'Main',
|
||||
component: () => import('../Layout/Main.vue'),
|
||||
component: () => import('../layouts/Main.vue'),
|
||||
redirect: { name: 'Dashboard' },
|
||||
children: [
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('../pages/Layout/NotFound.vue'),
|
||||
component: () => import('../pages/NotFound.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue