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']
Menu: typeof import('primevue/menu')['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']
Tag: typeof import('primevue/tag')['default']
TopBar: typeof import('./src/components/layout/TopBar.vue')['default']

View File

@ -17,8 +17,8 @@ const toggleUserMenu = (event: Event) => {
};
// Función de logout
const handleLogout = () => {
logout();
const handleLogout = async () => {
await logout();
router.push('/login');
};
@ -129,10 +129,10 @@ const userMenuItems = ref([
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">
{{ user?.name.split(' ').map(n => n[0]).join('').toUpperCase() || 'U' }}
{{ user?.name?.charAt(0).toUpperCase() || 'U' }}
</div>
<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>
<i class="pi pi-angle-down text-sm text-surface-500 hidden md:block"></i>
</button>
@ -147,7 +147,7 @@ const userMenuItems = ref([
>
<template #start>
<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>
</div>
</template>

View File

@ -8,7 +8,6 @@ const { login, isLoading } = useAuth();
const email = ref('');
const password = ref('');
const remember = ref(false);
const showPassword = ref(false);
const errorMessage = ref('');
@ -92,8 +91,8 @@ const handleKeyPress = (event: KeyboardEvent) => {
<Message severity="info" :closable="false">
<div class="text-sm">
<p class="font-semibold mb-1">Credenciales de prueba:</p>
<p><strong>Email:</strong> admin@gols.com</p>
<p><strong>Password:</strong> admin123</p>
<p><strong>Email:</strong> admin@golsystems.com.mx</p>
<p><strong>Password:</strong> P8YeFQggR06r</p>
</div>
</Message>
@ -149,23 +148,6 @@ const handleKeyPress = (event: KeyboardEvent) => {
</IconField>
</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 -->
<Button
type="submit"

View File

@ -1,11 +1,46 @@
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 {
id: number;
name: string;
paternal: string;
maternal: string;
phone: string | null;
email: string;
avatar?: string;
role: string;
email_verified_at: string | null;
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 {
@ -20,63 +55,68 @@ const isLoading = ref(false);
// Inicializar desde localStorage
const initAuth = () => {
const storedToken = localStorage.getItem('auth_token');
const storedUser = localStorage.getItem('auth_user');
if (storedToken && storedUser) {
token.value = storedToken;
user.value = JSON.parse(storedUser);
try {
const storedToken = localStorage.getItem('auth_token');
const storedUser = localStorage.getItem('auth_user');
if (storedToken && storedUser && storedUser !== 'undefined') {
token.value = storedToken;
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
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 }> => {
isLoading.value = true;
try {
// Simular llamada a API
await new Promise(resolve => setTimeout(resolve, 1000));
// Llamar al servicio de autenticación
const response = await authService.login(credentials);
// Validación simple (en producción esto vendría del backend)
if (credentials.email === 'admin@gols.com' && credentials.password === 'admin123') {
const mockUser: User = {
id: 1,
name: 'John Doe',
email: credentials.email,
avatar: '',
role: 'admin'
};
const mockToken = 'mock-jwt-token-' + Date.now();
// Guardar en estado
user.value = mockUser;
token.value = mockToken;
// Persistir en localStorage
localStorage.setItem('auth_token', mockToken);
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' };
// Guardar en estado
user.value = response.user;
token.value = response.token;
// Persistir en localStorage
localStorage.setItem('auth_token', response.token);
localStorage.setItem('auth_user', JSON.stringify(response.user));
return { success: true };
} catch (error: any) {
console.error('Error en login:', error);
return {
success: false,
error: error.message || 'Error al iniciar sesión'
};
} finally {
isLoading.value = false;
}
};
// Función de logout
const logout = () => {
const logout = async () => {
// Limpiar estado local inmediatamente para reactividad
user.value = null;
token.value = null;
localStorage.removeItem('auth_token');
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

View File

@ -1,9 +1,13 @@
import api from '../../../services/api';
import type { User, LoginCredentials } from '../composables/useAuth';
import type { User, LoginCredentials, Role } from '../composables/useAuth';
export interface LoginResponse {
user: User;
token: string;
status: string;
data: {
user: User;
userRoles: Role[];
token: string;
};
}
export interface RegisterData {
@ -17,31 +21,29 @@ class AuthService {
/**
* Iniciar sesión
*/
async login(credentials: LoginCredentials): Promise<LoginResponse> {
async login(credentials: LoginCredentials): Promise<{ user: User; token: string }> {
try {
// En producción, esto haría una llamada real al backend
// const response = await api.post<LoginResponse>('/auth/login', credentials);
// 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);
const response = await api.post<LoginResponse>('/api/auth/login', {
email: credentials.email,
password: credentials.password
});
// Retornar solo user y token para mantener compatibilidad con useAuth
return {
user: response.data.data.user,
token: response.data.data.token
};
} 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');
}
}
@ -63,10 +65,21 @@ class AuthService {
*/
async logout(): Promise<void> {
try {
// Notificar al backend que se cerró sesión
await api.post('/auth/logout');
} catch (error) {
const response = await api.post<{
status: string;
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);
// No lanzar error para que el logout local continúe aunque falle el backend
}
}