add: guard in router

This commit is contained in:
Alexandro Uc Santos
2023-11-24 21:07:42 -06:00
parent afa8e1983b
commit ec02b98272
18 changed files with 914 additions and 47 deletions

View File

@@ -1,3 +1,7 @@
<script setup>
import Notification from './components/ui/Notification.vue';
</script>
<template>
<main>
<RouterView />
@@ -5,12 +9,6 @@
<Notification/>
</template>
<script setup>
import Notification from './components/ui/Notification.vue';
</script>
<style scoped>
</style>

View File

@@ -2,9 +2,6 @@ body {
margin: 0px 0px;
padding: 0px;
}
.bg-body{
background-color: red;
}
.w-lg-45{
width: 45%;

View File

@@ -0,0 +1,50 @@
<script setup>
import { useAuthStore } from '../../stores/auth';
const auth = useAuthStore();
</script>
<template>
<div class="noty-fixed" v-if="auth.checking">
<div class="content-noty">
<div class="body">
<img src="/images/logo.png" alt="logo" class="logo animate__animated animate__rubberBand">
<h2>Cargando...</h2>
</div>
</div>
</div>
</template>
<style scoped>
.noty-fixed {
position: fixed;
right: 0px;
top: 0px;
left: 0px;
bottom: 0px;
}
.content-noty {
width: 100vw;
height: 100vh;
padding: 16px 32px;
background-color: #FFF;
/* opacity: 0.9; */
}
.body {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
margin: 0 auto;
gap: 5rem;
/* align-content: center; */
justify-content: center;
}
.logo {
width: 240px;
}
</style>

View File

@@ -28,7 +28,7 @@
.content-noty {
width: 100%;
max-width: 500px;
padding: 14px 20px;
padding: 16px 32px;
background-color: white;
border-radius: 8px;

View File

@@ -8,6 +8,8 @@ export const validateEmail = (email) => {
export const messagesError = (msg) => {
switch (msg) {
case 'Invalid credentials':
return 'Email y/o password incorrectos';
case 'Email is not registered!':
return 'No se encontro una cuenta con este email';
case 'Wrong OTP':

View File

@@ -1,11 +1,305 @@
<template>
<RouterView />
</template>
<script setup>
import { RouterLink, useRoute, useRouter } from 'vue-router';
import { useAuthStore } from '../stores/auth';
import LoadingModal from '../components/ui/LoadingModal.vue';
const route = useRoute();
const router = useRouter();
const auth = useAuthStore();
$(document).ready(function() {
$('#sidebarCollapse').on('click', function () {
$('#sidebar').toggleClass('active');
});
});
const handleLogout = () => {
auth.$patch({
sesion: '',
token: '',
user: {},
});
localStorage.removeItem('session');
router.push({name: 'login'});
}
</script>
<style scoped>
<template>
<div class="wrapper">
<nav id="sidebar">
<div class="sidebar-header">
<div class="logo">
<!-- <img src="/images/logo.png" alt="Eta viaporte" width="120"> -->
<img src="/images/logo-eta.png" alt="Eta viaporte" width="120">
</div>
<h2 class="my-4">COVO</h2>
<p><i class="fa-solid fa-user"></i> <span class="ms-2">{{ auth.user?.first_name + ' ' + auth.user?.last_name }}</span></p>
<div class="divider"></div>
</div>
<div>
</div>
<ul class="list-unstyled components">
<li :class="[route.name === 'home' ? 'bg-nav-active' : '']">
<div>
<i class="fa-regular fa-building" :class="[route.name === 'home' ? 'router-link-active' : '']"></i>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'home'}">Empresa</RouterLink>
</div>
<!-- <i class="fa-solid fa-chevron-right"></i> -->
</li>
<li :class="[route.name === 'users' ? 'bg-nav-active' : '']">
<div>
<i class="fa-regular fa-user" :class="[route.name === 'users' ? 'router-link-active' : '']"></i>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'users'}">Usuarios</RouterLink>
</div>
<!-- <i class="fa-solid fa-chevron-right"></i> -->
</li>
<li :class="[route.name === 'calendar' ? 'bg-nav-active' : '']">
<div>
<i class="fa-regular fa-calendar" :class="[route.name === 'calendar' ? 'router-link-active' : '']"></i>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'calendar'}">Calendario</RouterLink>
</div>
<!-- <i class="fa-solid fa-chevron-right"></i> -->
</li>
<li :class="[route.name === 'loads' ? 'bg-nav-active' : '']">
<div>
<i class="fa-solid fa-box" :class="[route.name === 'loads' ? 'router-link-active' : '']"></i>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'loads'}">Cargas</RouterLink>
</div>
<!-- <i class="fa-solid fa-chevron-right"></i> -->
</li>
<li :class="[route.name === 'vehicles' ? 'bg-nav-active' : '']">
<div>
<i class="fa-solid fa-truck-fast" :class="[route.name === 'vehicles' ? 'router-link-active' : '']"></i>
<RouterLink
active-class=""
class="nav-link" :to="{name: 'vehicles'}">Vehiculos</RouterLink>
</div>
<!-- <i class="fa-solid fa-chevron-right"></i> -->
</li>
</ul>
<div class="eta-info">
<div class="divider"></div>
<a class="link-eta" href="">Aviso de privaciadad</a>
<a class="link-eta" href="">Terminos y condiciones</a>
<a class="link-eta" href="">FAQS</a>
<div class="d-flex align-items-center">
<i class="fa-solid fa-right-from-bracket"></i>
<a @click="handleLogout"
active-class=""
class="nav-link m-2">
Cerrar sesión
</a>
</div>
</div>
</nav>
<div id="content">
<nav class="navbar navbar-expand-lg navbar-light custom-navbar">
<div class="nav-items">
<button type="button" id="sidebarCollapse" class="btn btn-info btn-menu">
<i class="fas fa-align-left"></i>
</button>
<div class="nav-options">
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'recovery'}">Embarques</RouterLink>
<RouterLink
active-class="router-link-active"
class="nav-link" :to="{name: 'recovery'}">Cargas</RouterLink>
</div>
</div>
</nav>
<div class="view">
<RouterView />
</div>
</div>
</div>
<LoadingModal/>
</template>
<style lang="scss" scoped>
.wrapper {
display: flex;
width: 100%;
align-items: stretch;
background-color: #f6f3f3;
}
#sidebar {
/* margin: 20px; */
display: flex;
flex-direction: column;
width: 220px;
min-height: 100vh;
/* background: #FFF;
color: #323030; */
background: #323030;
color: #FFF;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.10));
transition: all 0.3s;
}
#sidebar.active {
margin-left: -250px;
}
#sidebar .sidebar-header {
padding: 20px;
/* background: #FFF;
color: #323030; */
background: #323030;
color: #FFF;
margin-bottom: 8px;
}
.logo {
display: flex;
justify-content: center;
}
.sidebar-header h2 {
margin-top: 16px;
font-size: 1.4rem;
font-weight: 900;
}
#sidebar ul li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
margin-bottom: 5px;
/* background-color: #FFF; */
background-color: #323030;
}
#sidebar ul li div{
display: flex;
align-items: center;
gap: 1rem;
}
#sidebar ul li i{
font-size: 20px;
color: #897c7c;
}
#sidebar ul li.active > a, a[aria-expanded="true"] {
color: #fff;
background: #6d7fcc;
}
a[data-toggle="collapse"] {
position: relative;
}
.custom-navbar {
display: block;
width: calc(100vw - 220px);
background-color: #FFF;
}
.btn-menu {
display: none;
}
.dropdown-toggle::after {
display: block;
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
}
.view {
margin: 24px 16px;
}
.nav-items {
display: flex;
flex-direction: row;
justify-content: end;
}
.nav-options {
margin-left: 32px;
display: flex;
}
.nav-link{
cursor: pointer;
/* color: #5f5c5c; */
color: #ebd6d6;
font-size: 1.2rem;
margin-right: 1.2rem;
font-weight: 500;
}
.nav-link:hover{
/* color: #413f3c; */
color: #FFF;
}
.nav-link:focus{
/* color: #413f3c; */
color: #FFF;
}
.router-link-active{
/* color: #413f3c !important; */
color: #FFF !important;
}
.bg-nav-active {
/* background-color: #e4eff2 !important;; */
background-color: #373738 !important;;
}
.eta-info {
// position: fixed;
display: flex;
flex-direction: column;
margin-top: 20px;
bottom: 10px;
gap: 1rem;
// background-color: red;
align-items: start;
padding: 10px 16px;
}
.link-eta {
font-size: 14px;
text-decoration: none;
font-weight: 400;
color: #FFF;
opacity: 0.6;
}
@media (max-width: 768px) {
.custom-navbar {
width: 100vw;
}
.nav-items {
justify-content: space-between;
}
.btn-menu {
display: flex;
}
#sidebar {
margin-left: -220px;
}
#sidebar.active {
margin-left: 0;
}
}
</style>

View File

@@ -1,6 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import AuthLayout from '../layouts/AuthLayout.vue'
import LoginView from '../views/LoginView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -25,21 +24,77 @@ const router = createRouter({
name: 'recovery',
component: () => import('../views/RecoveryPasswordView.vue'),
},
{
path: 'empresa',
name: 'company',
component: () => import('../views/CompleteRegisterView.vue')
}
]
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('../layouts/AdminLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: 'inicio',
name: 'inicio',
name: 'home',
component: () => import('../views/HomeView.vue'),
},
{
path: 'usuarios',
name: 'users',
component: () => import('../views/UsersView.vue'),
},
{
path: 'calendario',
name: 'calendar',
component: () => import('../views/CalendarView.vue'),
},
{
path: 'cargas',
name: 'loads',
component: () => import('../views/LoadsView.vue'),
},
{
path: 'vehiculos',
name: 'vehicles',
component: () => import('../views/VehiclesView.vue'),
},
]
}
]
})
});
router.beforeEach( async(to, from, next) => {
const requiresAuth = to.matched.some(url => url.meta.requiresAuth)
const session = localStorage.getItem('session');
// console.log('--------------- SE EJECUTA -----------------');
// console.log(session);
// console.log('--------------- FIN -----------------');
if(requiresAuth) {
//Comprobamos si el usuario esta authenticado
if(session) {
// try {
// const resp = await renewToken();
// if(resp.msg == 'success') {
// next();
// } else {
// next({name: 'login'})
// }
// } catch (error) {
// console.log(error);
// next({name: 'login'})
// }
next();
} else {
next({name: 'login'})
}
} else {
// No esta protegido
next();
}
});
export default router

View File

@@ -1,6 +1,65 @@
import api from "../lib/axios";
import {messagesError} from '../helpers/validations';
export const login = async(body) => {
try {
const endpoint = "/account/authorize";
const {data} = await api.post(endpoint, body);
console.log(data);
console.log(data.accessToken);
if(data.accessToken !== null){
if(data.user.job_role !== 'driver'){
//TODO: Guardar token y datos del usuario
return {
msg: 'success',
data
};
} else {
//Remover datos de sesion
return 'Rol no autorizado';
}
} else {
return {
msg: "email y/o password incorrectos",
data: null
};
}
} catch (error) {
const errStr = error.response.data.error ?? 'Algo salio mal, intente más tarde';
return {
msg: messagesError(errStr),
data: null
};
}
}
export const renewToken = async() => {
const session = localStorage.getItem('session');
try {
const endpoint = `/account/authorize/${session}`;
const {data} = await api.get(endpoint);
console.log(data.user);
if(data.accessToken !== null){
return {
msg: 'success',
data
};
} else {
return {
msg: "Sesion expiro",
data: null
};
}
} catch (error) {
const errStr = error.response.data.error ?? 'Algo salio mal, intente más tarde';
return {
msg: 'Sesion expiro',
data: null
};
}
}
export const regiter = async(body) => {
try {
const endpoint = "/account/signup";

55
src/stores/auth.js Normal file
View File

@@ -0,0 +1,55 @@
import { defineStore } from "pinia";
import { ref, onMounted } from "vue";
import { useRouter } from 'vue-router';
import { renewToken } from '../services/auth';
import {useNotificationsStore} from './notifications';
export const useAuthStore = defineStore('auth', () => {
const router = useRouter();
const noty = useNotificationsStore();
const sesion = ref('')
const checking = ref(false);
const token = ref('')
const user = ref(null)
onMounted( async() => {
console.log('Se ejecuta onMounted auth');
checkSession();
});
const checkSession = async() => {
checking.value = true;
const resp = await renewToken();
if(resp.msg === 'success') {
localStorage.setItem('session', resp.data.session_token);
sesion.value = resp.data.session_token;
token.value = resp.data.accessToken;
user.value = resp.data.user;
checking.value = false;
} else {
noty.show = true;
noty.text = 'Sesión ha expirado, ingresa nuevamente';
noty.error = true;
checking.value = false;
router.push({name: 'login'});
}
}
const logout = () => {
localStorage.removeItem('session');
router.push({name: 'login'});
sesion.value = '';
token.value = '';
user.value = null;
}
return {
sesion,
logout,
token,
user,
checking
}
});

View File

@@ -0,0 +1,13 @@
<script setup>
</script>
<template>
<div>
<h2 class="title">Calendario</h2>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,99 @@
<script setup>
</script>
<template>
<h2 class="title">Complete su registro</h2>
<div class="card-info flex-column">
<p>¿Como te quieres registrar?</p>
<label class="container">Embarcador
<input type="radio" checked="checked" name="radio">
<span class="checkmark"></span>
</label>
<label class="container">Transportista
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
<label class="container">Broker (Embarcador)
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
<label class="container">Broker (Transportista)
<input type="radio" name="radio">
<span class="checkmark"></span>
</label>
</div>
</template>
<style lang="scss" scoped>
.container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default radio button */
.container input {
position: absolute;
opacity: 0;
cursor: pointer;
}
/* Create a custom radio button */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
border-radius: 50%;
}
/* On mouse-over, add a grey background color */
.container:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the radio button is checked, add a blue background */
.container input:checked ~ .checkmark {
background-color: #2196F3;
}
/* Create the indicator (the dot/circle - hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the indicator (dot/circle) when checked */
.container input:checked ~ .checkmark:after {
display: block;
}
/* Style the indicator (dot/circle) */
.container .checkmark:after {
top: 9px;
left: 9px;
width: 8px;
height: 8px;
border-radius: 50%;
background: white;
}
</style>
<!-- ¿Como te quieres registrar? (Opciones a elegir)
1 - Embarcador
2 - Transportista
3 - Broker (Embarcador)
4 - Broker (Transportista) -->

13
src/views/LoadsView.vue Normal file
View File

@@ -0,0 +1,13 @@
<script setup>
</script>
<template>
<div>
<h2 class="title">Cargas</h2>
</div>
</template>
<style scoped>
</style>

View File

@@ -4,14 +4,19 @@
import NotificationBadge from '../components/ui/NotificationBadge.vue';
import {validateEmail} from '../helpers/validations';
import Spiner from '../components/ui/Spiner.vue';
import { RouterLink } from 'vue-router';
import { login } from '../services/auth';
import { RouterLink, useRouter } from 'vue-router';
import { useAuthStore } from '../stores/auth';
const form = reactive({
email: '',
password: '',
email: 'alexandro.uc.santos@gmail.com',
password: 'Password0',
});
const router = useRouter();
const auth = useAuthStore();
const loading = ref(false);
const msgError = ref('');
const msgSuccess = ref('');
@@ -31,7 +36,24 @@ import { RouterLink } from 'vue-router';
email: form.email,
password: form.password
}
console.log(data);
const resp = await login(data);
if(resp.msg === 'success') {
console.log(resp.data.user);
if(resp.data.user.first_name && resp.data.user.last_name) {
localStorage.setItem('session', resp.data.session_token);
router.push({name: 'home'});
auth.$patch({
sesion: resp.data.session_token,
token: resp.data.accessToken,
user: resp.data.user,
})
} else {
router.push({name: 'company'});
}
} else {
msgError.value = resp.msg;
}
clearMessages();
loading.value = false;
}
}
@@ -59,29 +81,29 @@ import { RouterLink } from 'vue-router';
<div class="d-flex flex-column my-5 justify-content-center align-items-center">
<h2 class="title">Iniciar sesión</h2>
<p class="subtitle mt-4 mb-5">Bienvenido de vuelta! Ingresa tu email y contraseña</p>
<form @submit.prevent="handleLogin" class="form-content">
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<CustomInput
label="Ingresa tu correo electrónico"
name="email"
type="email"
v-model:field="form.email"
/>
<CustomInput
label="Contraseña"
name="password"
type="password"
v-model:field="form.password"
/>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Ingresar">
</form>
<p class="mt-3 fs-6">¿Olvidaste tu contreseña? <RouterLink class="btn-text" :to="{name: 'recovery'}">Ingresa aqui</RouterLink></p>
<p class="mt-5 fs-6">¿No tienes una cuenta? <RouterLink class="btn-text" :to="{name: 'register'}">Registrate aqui</RouterLink></p>
<form @submit.prevent="handleLogin" class="form-content">
<NotificationBadge :msg="msgError" v-if="msgError != ''"/>
<NotificationBadge :msg="msgSuccess" :is-error="false" v-if="msgSuccess != ''"/>
<CustomInput
label="Ingresa tu correo electrónico"
name="email"
type="email"
v-model:field="form.email"
/>
<CustomInput
label="Contraseña"
name="password"
type="password"
v-model:field="form.password"
/>
<Spiner v-if="loading" class="mt-5"/>
<input
v-else
type="submit"
class="btn-primary-lg btn-lg-block radius-1 mt-5" value="Ingresar">
</form>
<p class="mt-3 fs-6">¿Olvidaste tu contreseña? <RouterLink class="btn-text" :to="{name: 'recovery'}">Ingresa aqui</RouterLink></p>
<p class="mt-5 fs-6">¿No tienes una cuenta? <RouterLink class="btn-text" :to="{name: 'register'}">Registrate aqui</RouterLink></p>
</div>
</template>

13
src/views/UsersView.vue Normal file
View File

@@ -0,0 +1,13 @@
<script setup>
</script>
<template>
<div>
<h2 class="title">Usuarios</h2>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,13 @@
<script setup>
</script>
<template>
<div>
<h2 class="title">Vehiculos</h2>
</div>
</template>
<style scoped>
</style>