From 619250ecf2e8a2ebfd10871f3eecdb4fdcdf1ab7 Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Sun, 1 Mar 2026 15:13:27 -0500 Subject: [PATCH] Critical Fix: Complete rewrite of Auth components to restore visibility, and fix Map user location persistence --- frontend/src/components/auth/LoginForm.vue | 347 +++++------- frontend/src/components/auth/RegisterForm.vue | 493 ++++++++---------- frontend/src/views/AuthView.vue | 367 +++++++------ frontend/src/views/MapView.vue | 3 +- 4 files changed, 522 insertions(+), 688 deletions(-) diff --git a/frontend/src/components/auth/LoginForm.vue b/frontend/src/components/auth/LoginForm.vue index 9d43d9e..693c0d9 100644 --- a/frontend/src/components/auth/LoginForm.vue +++ b/frontend/src/components/auth/LoginForm.vue @@ -6,6 +6,8 @@ import { useI18n } from 'vue-i18n' const emit = defineEmits(['toggle']) const { t } = useI18n() +const router = useRouter() +const authStore = useAuthStore() const email = ref('') const password = ref('') @@ -13,193 +15,132 @@ const keepSession = ref(false) const isLoading = ref(false) const errorMessage = ref('') const showPassword = ref(false) -const router = useRouter() -const authStore = useAuthStore() const handleLogin = async () => { + if (!email.value || !password.value) return + isLoading.value = true errorMessage.value = '' try { await authStore.login(email.value.trim().toLowerCase(), password.value, keepSession.value) - // El rol ya está disponible en el store (del JWT), navegar directo - navigateByUserRole(authStore.role || 'PASSENGER') + + // Al iniciar sesión, el store ya tiene el rol. Redirigir según corresponda. + const role = (authStore.role || 'PASSENGER').toUpperCase() + if (role === 'ADMIN') router.push('/admin') + else if (role === 'DRIVER') router.push('/driver') + else if (role === 'PROMOTER') router.push('/promoter') + else router.push('/map') } catch (error: any) { - console.error('Error Login:', error) + console.error('SIBU | Error Login:', error) if (error.message?.includes('Invalid login credentials')) { errorMessage.value = t('auth.invalidCreds') } else { - errorMessage.value = `${t('common.error')}: ${error.message || t('common.noData')}` + errorMessage.value = error.message || t('common.error') } } finally { isLoading.value = false } } - -const navigateByUserRole = (role: string) => { - const r = role.toUpperCase() - if (r === 'ADMIN') router.push('/admin') - else if (r === 'DRIVER') router.push('/driver') - else if (r === 'PROMOTER') router.push('/promoter') - else router.push('/map') -} - - diff --git a/frontend/src/components/auth/RegisterForm.vue b/frontend/src/components/auth/RegisterForm.vue index 17e5551..733f880 100644 --- a/frontend/src/components/auth/RegisterForm.vue +++ b/frontend/src/components/auth/RegisterForm.vue @@ -2,12 +2,10 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' -import { analyticsService } from '@/services/analyticsService' import { useI18n } from 'vue-i18n' const emit = defineEmits(['toggle', 'success']) const { t } = useI18n() - const router = useRouter() const authStore = useAuthStore() @@ -17,84 +15,69 @@ const password = ref('') const autoLocation = ref(false) const isLoading = ref(false) const errorMessage = ref('') -const successMessage = ref('') const showPassword = ref(false) +const isSuccess = ref(false) const handleRegister = async () => { + if (!email.value || !password.value || !fullName.value) return + isLoading.value = true errorMessage.value = '' try { - const cleanEmail = email.value.trim().toLowerCase() - const cleanPass = password.value - - await authStore.register(cleanEmail, cleanPass, fullName.value.trim(), autoLocation.value) - - analyticsService.logEvent({ - event_name: 'sign_up', - properties: { method: 'email' } - }) - - successMessage.value = t('auth.successTitle') - - // Delay navigation so user can see the success card + await authStore.register( + email.value.trim().toLowerCase(), + password.value, + fullName.value.trim(), + autoLocation.value + ) + + isSuccess.value = true setTimeout(() => { - navigateByUserRole(authStore.role || 'PASSENGER') - }, 1500) + // Redirigir según el rol asignado (usualmente PASSENGER) + const role = (authStore.role || 'PASSENGER').toUpperCase() + if (role === 'ADMIN') router.push('/admin') + else if (role === 'DRIVER') router.push('/driver') + else if (role === 'PROMOTER') router.push('/promoter') + else router.push('/map') + }, 2000) } catch (error: any) { - console.error('Error detallado de registro:', error) - if (error.message?.includes('User already registered') || error.message?.includes('already exists')) { + console.error('SIBU | Error Register:', error) + if (error.message?.includes('already registered')) { errorMessage.value = t('auth.emailRegistered') } else { - errorMessage.value = `${t('common.error')}: ${error.message || t('common.noData')}` + errorMessage.value = error.message || t('common.error') } } finally { isLoading.value = false } } -const navigateByUserRole = (role: string) => { - const r = role.toUpperCase() - if (r === 'ADMIN') router.push('/admin') - else if (r === 'DRIVER') router.push('/driver') - else if (r === 'PROMOTER') router.push('/promoter') - else router.push('/map') -} - - diff --git a/frontend/src/views/AuthView.vue b/frontend/src/views/AuthView.vue index a16cb91..9b55b5d 100644 --- a/frontend/src/views/AuthView.vue +++ b/frontend/src/views/AuthView.vue @@ -1,84 +1,76 @@ - @@ -90,167 +82,166 @@ onMounted(() => { align-items: center; justify-content: center; background: var(--bg-primary); - padding: 1.5rem; + padding: 1rem; position: relative; overflow: hidden; - font-family: var(--font-family); } -/* Glow decorativo SIBU amarillo */ -.auth-glow { +.bg-decoration { position: absolute; - top: -20%; - left: 50%; - transform: translateX(-50%); - width: 500px; - height: 500px; - border-radius: 50%; - background: radial-gradient(circle, rgba(254, 231, 21, 0.08) 0%, transparent 70%); + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; pointer-events: none; } -.auth-wrapper { - position: relative; - z-index: 1; +.circle { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.15; +} + +.circle-1 { + width: 400px; + height: 400px; + background: var(--active-color); + top: -100px; + right: -100px; +} + +.circle-2 { + width: 300px; + height: 300px; + background: #3b82f6; + bottom: -50px; + left: -50px; +} + +.auth-container { width: 100%; max-width: 420px; - display: flex; - flex-direction: column; - gap: 1.75rem; + position: relative; + z-index: 1; } -/* ─── Branding ─── */ -.auth-brand { - text-align: center; -} - -.brand-title { - font-size: 3.5rem; - font-weight: 900; - letter-spacing: -0.05em; - color: var(--active-color); - margin: 0; - line-height: 1; -} - -.brand-subtitle { - font-size: 0.8125rem; - font-weight: 600; - color: var(--text-secondary); - margin: 0.375rem 0 0; - letter-spacing: 0.02em; -} - -/* ─── Card ─── */ -.auth-card { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 1.5rem; - overflow: hidden; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); -} - -/* ─── Tabs ─── */ -.auth-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - border-bottom: 1px solid var(--border-color); -} - -.auth-tab { - padding: 1rem; - background: transparent; - border: none; - color: var(--text-secondary); - font-size: 0.875rem; - font-weight: 700; - font-family: inherit; - cursor: pointer; - transition: all 0.2s; - border-bottom: 2px solid transparent; - margin-bottom: -1px; -} - -.auth-tab:hover { - color: var(--text-primary); -} - -.auth-tab--active { - color: var(--active-color); - border-bottom-color: var(--active-color); -} - -/* ─── Errores de Redirección ─── */ -.redirect-error { - margin: 1rem; - padding: 0.75rem 1rem; - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.2); - border-radius: 0.75rem; - color: #ef4444; - font-size: 0.8125rem; - font-weight: 600; +.back-btn { display: flex; align-items: center; gap: 0.5rem; + background: none; + border: none; + color: var(--text-secondary); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + margin-bottom: 1.5rem; + padding: 0.5rem; + border-radius: 8px; + transition: all 0.2s; } -.redirect-error .material-icons { - font-size: 1.125rem; +.back-btn:hover { + color: var(--text-primary); + background: rgba(255,255,255,0.05); } -/* ─── Transición entre formularios ─── */ -.auth-slide-enter-active, -.auth-slide-leave-active { - transition: opacity 0.22s ease, transform 0.22s ease; -} - -.auth-slide-enter-from { - opacity: 0; - transform: translateX(16px); -} - -.auth-slide-leave-to { - opacity: 0; - transform: translateX(-16px); -} - -/* ─── Botón volver ─── */ -.back-to-map { - display: flex; - align-items: center; - gap: 8px; +.auth-card { background: var(--bg-secondary); border: 1px solid var(--border-color); - color: var(--text-secondary); + border-radius: 2rem; + box-shadow: 0 20px 50px rgba(0,0,0,0.2); + overflow: hidden; + display: flex; + flex-direction: column; +} + +.auth-header { + padding: 2rem 2rem 1rem; + text-align: center; +} + +.auth-logo { + height: 60px; + margin-bottom: 0.5rem; +} + +.auth-subtitle { font-size: 0.85rem; font-weight: 700; - font-family: inherit; - padding: 10px 16px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - align-self: flex-start; -} - -.back-to-map:hover { - color: var(--active-color); - border-color: var(--active-color); - background: rgba(254, 231, 21, 0.06); -} - -.back-to-map .material-icons { - font-size: 18px; -} - -/* ─── Footer ─── */ -.auth-footer { - text-align: center; - font-size: 0.6875rem; - font-weight: 600; color: var(--text-secondary); - opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.1em; + margin: 0; +} + +.auth-tabs { + position: relative; + display: flex; + margin: 0 1.5rem; + background: var(--bg-primary); + padding: 0.35rem; + border-radius: 1rem; + border: 1px solid var(--border-color); +} + +.tab-btn { + flex: 1; + padding: 0.75rem; + border: none; + background: none; + color: var(--text-secondary); + font-size: 0.85rem; + font-weight: 800; + cursor: pointer; + z-index: 1; + transition: color 0.3s; +} + +.tab-btn.active { + color: #101820; +} + +.tab-indicator { + position: absolute; + top: 0.35rem; + bottom: 0.35rem; + left: 0.35rem; + width: calc(50% - 0.35rem); + background: var(--active-color); + border-radius: 0.75rem; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.tab-indicator.on-right { + transform: translateX(100%); +} + +.auth-body { + min-height: 200px; +} + +.form-wrapper { + animation: fadeIn 0.4s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.auth-footer { + padding: 1.5rem; + text-align: center; + border-top: 1px solid var(--border-color); +} + +.auth-footer p { + font-size: 0.75rem; + color: var(--text-secondary); + opacity: 0.6; margin: 0; - letter-spacing: 0.03em; } diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 08d684e..936fd53 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -347,7 +347,7 @@ function clearMapMarkers() { } function reDrawUserMarker() { - if (!userCoords.value) return; + if (!userCoords.value || !map.value) return; // Remove old one if exists (paranoia) if (userMarker.value && typeof userMarker.value.setMap === 'function') { @@ -379,6 +379,7 @@ async function updateMapMarkers(skipZoom = false) { // Llamar al procesador de flujo principal, lo cual limpia el mapa y centra. // Usamos skipZoom para evitar la animación intrusiva de búsqueda cuando no es desde el buscador await procesarSeleccionDeRuta(selectedRouteObj, stops as BusStop[], map.value, skipZoom); + reDrawUserMarker(); // ⛔ ABORTAR SI EL USUARIO LIMPIÓ EL MAPA MIENTRAS DIBUJÁBAMOS if (routeStore.selectedRouteId !== currentRequestRouteId) {