feat: enhance authentication flow with improved user data handling and API integration

This commit is contained in:
Edgar Mendez Mendoza 2025-11-06 12:51:09 -06:00
parent e1521ef9c7
commit 857c149b87
6 changed files with 137 additions and 91 deletions

9
.env.example Normal file
View 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
View File

@ -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']

View File

@ -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>

View File

@ -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"

View File

@ -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

View File

@ -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
} }
} }