298 lines
6.4 KiB
Vue
298 lines
6.4 KiB
Vue
<template>
|
|
<button
|
|
class="auth-indicator"
|
|
:class="[authStatus, { checking: isCheckingAuth }]"
|
|
@click="handleClick"
|
|
:title="tooltipText"
|
|
:disabled="isCheckingAuth"
|
|
>
|
|
<component
|
|
:is="iconComponent"
|
|
:size="20"
|
|
class="auth-icon"
|
|
/>
|
|
<span class="auth-status-text">{{ statusText }}</span>
|
|
</button>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, onUnmounted, watch } from 'vue'
|
|
import { UserCheck, UserX, Loader, ShieldAlert } from 'lucide-vue-next'
|
|
import { useAuth } from '~/composables/useAuth'
|
|
import { useMusicStore } from '~/stores/music'
|
|
|
|
const {
|
|
isAuthenticated,
|
|
authChecked,
|
|
isCheckingAuth,
|
|
authStatus,
|
|
checkAuth,
|
|
triggerAuth,
|
|
markUnauthenticated,
|
|
setupVisibilityListener,
|
|
cleanupListeners
|
|
} = useAuth()
|
|
const musicStore = useMusicStore()
|
|
|
|
// Check auth on mount and setup listeners
|
|
onMounted(async () => {
|
|
await checkAuth(true)
|
|
setupVisibilityListener()
|
|
|
|
// Chequea auth cada 30 segundos mientras la pestaña está activa
|
|
const interval = setInterval(() => {
|
|
if (!document.hidden) {
|
|
checkAuth()
|
|
}
|
|
}, 30000)
|
|
|
|
onUnmounted(() => {
|
|
clearInterval(interval)
|
|
})
|
|
})
|
|
|
|
// Cleanup listeners on unmount
|
|
onUnmounted(() => {
|
|
cleanupListeners()
|
|
})
|
|
|
|
// Watch for failed API requests that might indicate auth loss
|
|
watch(() => musicStore.error, (error, oldError) => {
|
|
// Solo actuar si es un nuevo error (no el mismo)
|
|
if (error && error !== oldError) {
|
|
console.log('[AuthIndicator] Music store error detected:', error)
|
|
|
|
if (error.includes('401') || error.includes('403') ||
|
|
error.includes('Unauthorized') || error.includes('Forbidden')) {
|
|
// Auth error detected - mark as unauthenticated immediately
|
|
console.log('[AuthIndicator] Auth error detected, updating status')
|
|
markUnauthenticated()
|
|
}
|
|
}
|
|
}, { immediate: false })
|
|
|
|
// Watch loading state - cuando termina de cargar, verificar si hubo errores de auth
|
|
watch(() => musicStore.loading, (loading, wasLoading) => {
|
|
if (wasLoading && !loading && musicStore.error) {
|
|
// Terminó de cargar con error, verificar si es de auth
|
|
const error = musicStore.error
|
|
if (error.includes('401') || error.includes('403') ||
|
|
error.includes('Unauthorized') || error.includes('Forbidden')) {
|
|
markUnauthenticated()
|
|
}
|
|
}
|
|
})
|
|
|
|
const iconComponent = computed(() => {
|
|
if (isCheckingAuth.value) return Loader
|
|
if (!authChecked.value) return Loader
|
|
|
|
switch (authStatus.value) {
|
|
case 'authenticated':
|
|
return UserCheck
|
|
case 'unauthenticated':
|
|
return UserX
|
|
default:
|
|
return ShieldAlert
|
|
}
|
|
})
|
|
|
|
const statusText = computed(() => {
|
|
if (isCheckingAuth.value) return 'Verificando...'
|
|
|
|
switch (authStatus.value) {
|
|
case 'authenticated':
|
|
return 'Conectado'
|
|
case 'unauthenticated':
|
|
return 'Sin conexión'
|
|
default:
|
|
return 'Desconocido'
|
|
}
|
|
})
|
|
|
|
const tooltipText = computed(() => {
|
|
if (isCheckingAuth.value) return 'Verificando autenticación...'
|
|
|
|
switch (authStatus.value) {
|
|
case 'authenticated':
|
|
return 'Estás autenticado. Puedes descargar música.'
|
|
case 'unauthenticated':
|
|
return 'No autenticado. Click para iniciar sesión.'
|
|
default:
|
|
return 'Estado de autenticación desconocido'
|
|
}
|
|
})
|
|
|
|
const handleClick = () => {
|
|
if (isCheckingAuth.value) return
|
|
|
|
if (authStatus.value === 'unauthenticated') {
|
|
// Redirect to trigger Authentik
|
|
triggerAuth()
|
|
} else if (authStatus.value === 'authenticated') {
|
|
// Re-check auth status
|
|
checkAuth()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.auth-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 14px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 12px;
|
|
color: var(--text-secondary);
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
white-space: nowrap;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.auth-indicator::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 2px;
|
|
background: var(--status-color);
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.auth-indicator:hover::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.auth-indicator:not(:disabled):hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.auth-indicator:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.auth-icon {
|
|
flex-shrink: 0;
|
|
color: var(--status-color);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.auth-indicator.checking .auth-icon {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
.auth-status-text {
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
/* Status colors */
|
|
.auth-indicator.authenticated {
|
|
--status-color: #10b981;
|
|
border-color: rgba(16, 185, 129, 0.3);
|
|
}
|
|
|
|
.auth-indicator.authenticated:hover {
|
|
border-color: rgba(16, 185, 129, 0.5);
|
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
|
|
}
|
|
|
|
.auth-indicator.unauthenticated {
|
|
--status-color: #ef4444;
|
|
border-color: rgba(239, 68, 68, 0.3);
|
|
}
|
|
|
|
.auth-indicator.unauthenticated:hover {
|
|
border-color: rgba(239, 68, 68, 0.5);
|
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
|
|
}
|
|
|
|
.auth-indicator.unknown {
|
|
--status-color: #f59e0b;
|
|
border-color: rgba(245, 158, 11, 0.3);
|
|
}
|
|
|
|
.auth-indicator.checking {
|
|
--status-color: #3b82f6;
|
|
}
|
|
|
|
/* Animation for loading state */
|
|
@keyframes spin {
|
|
from {
|
|
transform: rotate(0deg);
|
|
}
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* Responsive design */
|
|
@media (max-width: 768px) {
|
|
.auth-status-text {
|
|
display: none;
|
|
}
|
|
|
|
.auth-indicator {
|
|
padding: 8px;
|
|
min-width: 40px;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
/* Pulse effect for unauthenticated state */
|
|
.auth-indicator.unauthenticated {
|
|
animation: pulse-red 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse-red {
|
|
0%, 100% {
|
|
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
|
|
}
|
|
}
|
|
|
|
/* Accessibility */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.auth-indicator {
|
|
transition: none;
|
|
animation: none;
|
|
}
|
|
|
|
.auth-indicator.checking .auth-icon {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* High contrast mode */
|
|
@media (prefers-contrast: high) {
|
|
.auth-indicator {
|
|
border-width: 2px;
|
|
}
|
|
|
|
.auth-indicator.authenticated {
|
|
background: rgba(16, 185, 129, 0.2);
|
|
}
|
|
|
|
.auth-indicator.unauthenticated {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
}
|
|
}
|
|
</style>
|