feat: enhance authentication flow with improved user data handling and API integration
This commit is contained in:
parent
e1521ef9c7
commit
857c149b87
9
.env.example
Normal file
9
.env.example
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# API Configuration
|
||||||
|
VITE_API_URL=http://localhost:3000/api
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
VITE_APP_ENV=development
|
||||||
|
|
||||||
|
# App Configuration
|
||||||
|
VITE_APP_NAME=GOLS Control
|
||||||
|
VITE_APP_VERSION=1.0.0
|
||||||
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -28,6 +28,8 @@ declare module 'vue' {
|
|||||||
KpiCard: typeof import('./src/components/shared/KpiCard.vue')['default']
|
KpiCard: typeof import('./src/components/shared/KpiCard.vue')['default']
|
||||||
Menu: typeof import('primevue/menu')['default']
|
Menu: typeof import('primevue/menu')['default']
|
||||||
Message: typeof import('primevue/message')['default']
|
Message: typeof import('primevue/message')['default']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
|
Sidebar: typeof import('./src/components/layout/Sidebar.vue')['default']
|
||||||
Tag: typeof import('primevue/tag')['default']
|
Tag: typeof import('primevue/tag')['default']
|
||||||
TopBar: typeof import('./src/components/layout/TopBar.vue')['default']
|
TopBar: typeof import('./src/components/layout/TopBar.vue')['default']
|
||||||
|
|||||||
@ -17,8 +17,8 @@ const toggleUserMenu = (event: Event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Función de logout
|
// Función de logout
|
||||||
const handleLogout = () => {
|
const handleLogout = async () => {
|
||||||
logout();
|
await logout();
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -129,10 +129,10 @@ const userMenuItems = ref([
|
|||||||
aria-controls="user_menu"
|
aria-controls="user_menu"
|
||||||
>
|
>
|
||||||
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center text-white text-sm font-semibold">
|
<div class="w-8 h-8 rounded-full bg-primary flex items-center justify-center text-white text-sm font-semibold">
|
||||||
{{ user?.name.split(' ').map(n => n[0]).join('').toUpperCase() || 'U' }}
|
{{ user?.name?.charAt(0).toUpperCase() || 'U' }}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm font-medium text-surface-900 dark:text-surface-0 hidden md:block">
|
<span class="text-sm font-medium text-surface-900 dark:text-surface-0 hidden md:block">
|
||||||
{{ user?.name || 'Usuario' }}
|
{{ user?.full_name || user?.name || 'Usuario' }}
|
||||||
</span>
|
</span>
|
||||||
<i class="pi pi-angle-down text-sm text-surface-500 hidden md:block"></i>
|
<i class="pi pi-angle-down text-sm text-surface-500 hidden md:block"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -147,7 +147,7 @@ const userMenuItems = ref([
|
|||||||
>
|
>
|
||||||
<template #start>
|
<template #start>
|
||||||
<div class="px-4 py-3 border-b border-surface-200 dark:border-surface-700">
|
<div class="px-4 py-3 border-b border-surface-200 dark:border-surface-700">
|
||||||
<p class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ user?.name }}</p>
|
<p class="text-sm font-semibold text-surface-900 dark:text-surface-0">{{ user?.full_name || user?.name }}</p>
|
||||||
<p class="text-xs text-surface-500 dark:text-surface-400">{{ user?.email }}</p>
|
<p class="text-xs text-surface-500 dark:text-surface-400">{{ user?.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ const { login, isLoading } = useAuth();
|
|||||||
|
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const remember = ref(false);
|
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
|
|
||||||
@ -92,8 +91,8 @@ const handleKeyPress = (event: KeyboardEvent) => {
|
|||||||
<Message severity="info" :closable="false">
|
<Message severity="info" :closable="false">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p class="font-semibold mb-1">Credenciales de prueba:</p>
|
<p class="font-semibold mb-1">Credenciales de prueba:</p>
|
||||||
<p><strong>Email:</strong> admin@gols.com</p>
|
<p><strong>Email:</strong> admin@golsystems.com.mx</p>
|
||||||
<p><strong>Password:</strong> admin123</p>
|
<p><strong>Password:</strong> P8YeFQggR06r</p>
|
||||||
</div>
|
</div>
|
||||||
</Message>
|
</Message>
|
||||||
|
|
||||||
@ -149,23 +148,6 @@ const handleKeyPress = (event: KeyboardEvent) => {
|
|||||||
</IconField>
|
</IconField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Remember me y Forgot password -->
|
|
||||||
<!-- <div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
inputId="remember-me"
|
|
||||||
v-model="remember"
|
|
||||||
:binary="true"
|
|
||||||
/>
|
|
||||||
<label for="remember-me" class="text-sm text-surface-700 dark:text-surface-300 cursor-pointer">
|
|
||||||
Remember Me
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<a href="#" class="text-sm font-medium text-primary hover:text-primary/80">
|
|
||||||
Forgot Password?
|
|
||||||
</a>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- Botón de Login -->
|
<!-- Botón de Login -->
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@ -1,11 +1,46 @@
|
|||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import authService from '../services/authService';
|
||||||
|
|
||||||
|
export interface Role {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
guard_name: string;
|
||||||
|
description: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
paternal: string;
|
||||||
|
maternal: string;
|
||||||
|
phone: string | null;
|
||||||
email: string;
|
email: string;
|
||||||
avatar?: string;
|
email_verified_at: string | null;
|
||||||
role: string;
|
current_team_id: number | null;
|
||||||
|
profile_photo_path: string | null;
|
||||||
|
department_id: number | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
deleted_at: string | null;
|
||||||
|
rfc: string | null;
|
||||||
|
curp: string | null;
|
||||||
|
nss: string | null;
|
||||||
|
hire_date: string | null;
|
||||||
|
gender_ek: string | null;
|
||||||
|
birthdate: string | null;
|
||||||
|
entity_id: number | null;
|
||||||
|
municipality_id: number | null;
|
||||||
|
marital_status_ek: string | null;
|
||||||
|
children: number | null;
|
||||||
|
address: string | null;
|
||||||
|
vacation_days_available: number;
|
||||||
|
status_ek: string;
|
||||||
|
full_name: string;
|
||||||
|
last_name: string;
|
||||||
|
profile_photo_url: string;
|
||||||
|
roles?: Role[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginCredentials {
|
export interface LoginCredentials {
|
||||||
@ -20,63 +55,68 @@ const isLoading = ref(false);
|
|||||||
|
|
||||||
// Inicializar desde localStorage
|
// Inicializar desde localStorage
|
||||||
const initAuth = () => {
|
const initAuth = () => {
|
||||||
const storedToken = localStorage.getItem('auth_token');
|
try {
|
||||||
const storedUser = localStorage.getItem('auth_user');
|
const storedToken = localStorage.getItem('auth_token');
|
||||||
|
const storedUser = localStorage.getItem('auth_user');
|
||||||
|
|
||||||
if (storedToken && storedUser) {
|
if (storedToken && storedUser && storedUser !== 'undefined') {
|
||||||
token.value = storedToken;
|
token.value = storedToken;
|
||||||
user.value = JSON.parse(storedUser);
|
user.value = JSON.parse(storedUser);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al inicializar autenticación:', error);
|
||||||
|
// Limpiar localStorage si hay datos corruptos
|
||||||
|
localStorage.removeItem('auth_token');
|
||||||
|
localStorage.removeItem('auth_user');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computeds
|
// Computeds
|
||||||
const isAuthenticated = computed(() => !!user.value && !!token.value);
|
const isAuthenticated = computed(() => !!user.value && !!token.value);
|
||||||
|
|
||||||
// Función de login (simulada)
|
// Función de login usando authService
|
||||||
const login = async (credentials: LoginCredentials): Promise<{ success: boolean; error?: string }> => {
|
const login = async (credentials: LoginCredentials): Promise<{ success: boolean; error?: string }> => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Simular llamada a API
|
// Llamar al servicio de autenticación
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
const response = await authService.login(credentials);
|
||||||
|
|
||||||
// Validación simple (en producción esto vendría del backend)
|
// Guardar en estado
|
||||||
if (credentials.email === 'admin@gols.com' && credentials.password === 'admin123') {
|
user.value = response.user;
|
||||||
const mockUser: User = {
|
token.value = response.token;
|
||||||
id: 1,
|
|
||||||
name: 'John Doe',
|
|
||||||
email: credentials.email,
|
|
||||||
avatar: '',
|
|
||||||
role: 'admin'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockToken = 'mock-jwt-token-' + Date.now();
|
// Persistir en localStorage
|
||||||
|
localStorage.setItem('auth_token', response.token);
|
||||||
|
localStorage.setItem('auth_user', JSON.stringify(response.user));
|
||||||
|
|
||||||
// Guardar en estado
|
return { success: true };
|
||||||
user.value = mockUser;
|
} catch (error: any) {
|
||||||
token.value = mockToken;
|
console.error('Error en login:', error);
|
||||||
|
return {
|
||||||
// Persistir en localStorage
|
success: false,
|
||||||
localStorage.setItem('auth_token', mockToken);
|
error: error.message || 'Error al iniciar sesión'
|
||||||
localStorage.setItem('auth_user', JSON.stringify(mockUser));
|
};
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} else {
|
|
||||||
return { success: false, error: 'Credenciales inválidas' };
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: 'Error al iniciar sesión' };
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Función de logout
|
// Función de logout
|
||||||
const logout = () => {
|
const logout = async () => {
|
||||||
|
// Limpiar estado local inmediatamente para reactividad
|
||||||
user.value = null;
|
user.value = null;
|
||||||
token.value = null;
|
token.value = null;
|
||||||
localStorage.removeItem('auth_token');
|
localStorage.removeItem('auth_token');
|
||||||
localStorage.removeItem('auth_user');
|
localStorage.removeItem('auth_user');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Notificar al backend (en segundo plano)
|
||||||
|
await authService.logout();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error al cerrar sesión en backend:', error);
|
||||||
|
// El logout local ya se completó, solo loggeamos el error
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Composable para usar en componentes
|
// Composable para usar en componentes
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import api from '../../../services/api';
|
import api from '../../../services/api';
|
||||||
import type { User, LoginCredentials } from '../composables/useAuth';
|
import type { User, LoginCredentials, Role } from '../composables/useAuth';
|
||||||
|
|
||||||
export interface LoginResponse {
|
export interface LoginResponse {
|
||||||
user: User;
|
status: string;
|
||||||
token: string;
|
data: {
|
||||||
|
user: User;
|
||||||
|
userRoles: Role[];
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterData {
|
export interface RegisterData {
|
||||||
@ -17,31 +21,29 @@ class AuthService {
|
|||||||
/**
|
/**
|
||||||
* Iniciar sesión
|
* Iniciar sesión
|
||||||
*/
|
*/
|
||||||
async login(credentials: LoginCredentials): Promise<LoginResponse> {
|
async login(credentials: LoginCredentials): Promise<{ user: User; token: string }> {
|
||||||
try {
|
try {
|
||||||
// En producción, esto haría una llamada real al backend
|
const response = await api.post<LoginResponse>('/api/auth/login', {
|
||||||
// const response = await api.post<LoginResponse>('/auth/login', credentials);
|
email: credentials.email,
|
||||||
|
password: credentials.password
|
||||||
// Simulación para desarrollo
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (credentials.email === 'admin@gols.com' && credentials.password === 'admin123') {
|
|
||||||
resolve({
|
|
||||||
user: {
|
|
||||||
id: 1,
|
|
||||||
name: 'John Doe',
|
|
||||||
email: credentials.email,
|
|
||||||
avatar: '',
|
|
||||||
role: 'admin'
|
|
||||||
},
|
|
||||||
token: 'mock-jwt-token-' + Date.now()
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
reject(new Error('Credenciales inválidas'));
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Retornar solo user y token para mantener compatibilidad con useAuth
|
||||||
|
return {
|
||||||
|
user: response.data.data.user,
|
||||||
|
token: response.data.data.token
|
||||||
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error('Error en login:', error);
|
||||||
|
|
||||||
|
// Manejar errores de validación (422)
|
||||||
|
if (error.response?.status === 422 && error.response?.data?.errors) {
|
||||||
|
const errors = error.response.data.errors;
|
||||||
|
const firstError = Object.values(errors)[0] as string[];
|
||||||
|
throw new Error(firstError[0] || error.response.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manejar otros errores
|
||||||
throw new Error(error.response?.data?.message || 'Error al iniciar sesión');
|
throw new Error(error.response?.data?.message || 'Error al iniciar sesión');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,10 +65,21 @@ class AuthService {
|
|||||||
*/
|
*/
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Notificar al backend que se cerró sesión
|
const response = await api.post<{
|
||||||
await api.post('/auth/logout');
|
status: string;
|
||||||
} catch (error) {
|
data: {
|
||||||
|
is_revoked: boolean;
|
||||||
|
};
|
||||||
|
}>('/api/auth/logout');
|
||||||
|
|
||||||
|
console.log('Respuesta logout:', response.data);
|
||||||
|
|
||||||
|
if (response.data.status === 'success' && response.data.data.is_revoked) {
|
||||||
|
console.log('Token revocado exitosamente');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
console.error('Error al cerrar sesión:', error);
|
console.error('Error al cerrar sesión:', error);
|
||||||
|
// No lanzar error para que el logout local continúe aunque falle el backend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user