logout e integracion con authentik completa
All checks were successful
deploy-analiticaNucleo / deploy (push) Successful in 36s

This commit is contained in:
2025-10-05 17:08:53 -06:00
parent 0380f69f1b
commit 16cef018b5
9 changed files with 836 additions and 54 deletions

View File

@@ -1,21 +1,71 @@
<script setup lang="ts">
const { user, loading, fetchUser, logout } = useAuth()
// Estado para el dropdown
const isOpen = ref(false)
// Cargar usuario al montar
onMounted(() => {
fetchUser()
})
// Computed para el avatar del usuario con gradiente dinámico
const userAvatar = computed(() => ({
src: user.value?.avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(user.value?.name || user.value?.username || 'User')}&background=random&bold=true&format=svg`,
alt: user.value?.name || user.value?.username || 'User'
}))
// Iniciales del usuario para efecto visual
const userInitials = computed(() => {
const name = user.value?.name || user.value?.username || 'U'
return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
})
const items = computed(() => [
[{
label: user.value?.email || 'Usuario',
label: user.value?.name || user.value?.username || 'Usuario',
description: user.value?.email,
avatar: userAvatar.value,
slot: 'account',
disabled: true
type: 'label'
}],
[{
label: 'Mi Perfil',
description: 'Ver y editar perfil',
icon: 'i-lucide-user',
iconClass: 'text-blue-500',
to: '/profile',
slot: 'profile'
},
{
label: 'Configuración',
description: 'Preferencias y ajustes',
icon: 'i-lucide-settings',
iconClass: 'text-gray-500',
to: '/settings',
slot: 'settings'
},
{
label: 'Notificaciones',
description: 'Gestionar notificaciones',
icon: 'i-lucide-bell',
iconClass: 'text-amber-500',
to: '/notifications',
slot: 'notifications',
badge: '3'
}],
[{
label: 'Ayuda y Soporte',
icon: 'i-lucide-help-circle',
iconClass: 'text-indigo-500',
to: '/help'
}],
[{
label: 'Cerrar sesión',
icon: 'i-heroicons-arrow-right-on-rectangle',
click: logout
icon: 'i-lucide-log-out',
iconClass: 'text-red-500',
color: 'error',
click: () => logout()
}]
])
</script>
@@ -23,26 +73,139 @@ const items = computed(() => [
<template>
<UDropdownMenu
v-if="user?.authenticated && !loading"
v-model:open="isOpen"
:items="items"
:ui="{ item: { disabled: 'cursor-text select-text' } }"
:popper="{ placement: 'bottom-start' }"
:ui="{
content: 'w-72 p-2 bg-white dark:bg-gray-900 shadow-xl border border-gray-200 dark:border-gray-800 backdrop-blur-xl',
item: {
disabled: 'cursor-default select-text opacity-100',
base: 'group gap-3 rounded-xl transition-all duration-300 hover:bg-gray-50 dark:hover:bg-gray-800/50 active:scale-[0.98]',
padding: 'px-4 py-3'
},
separator: 'my-1.5',
arrow: 'before:bg-white dark:before:bg-gray-900 before:border before:border-gray-200 dark:before:border-gray-800'
}"
:popper="{ placement: 'bottom-end', offsetDistance: 12 }"
arrow
>
<UAvatar
:alt="user.name || user.username || 'User'"
size="sm"
/>
<UButton
color="neutral"
variant="ghost"
:ui="{
rounded: 'rounded-full',
padding: { sm: 'p-1' }
}"
class="relative hover:bg-gray-100/80 dark:hover:bg-gray-800/80 transition-all duration-300 hover:scale-110 active:scale-95 group"
>
<div class="relative">
<UAvatar
v-bind="userAvatar"
size="sm"
:ui="{
wrapper: 'ring-2 ring-offset-2 ring-offset-white dark:ring-offset-gray-950 ring-primary-500/30 group-hover:ring-primary-500/60 transition-all duration-300 shadow-lg group-hover:shadow-primary-500/20'
}"
/>
<span
class="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-500 border-2 border-white dark:border-gray-950 rounded-full transition-all duration-300 group-hover:scale-110"
title="En línea"
/>
</div>
</UButton>
<template #account="{ item }">
<div class="text-left">
<p class="font-medium text-gray-900 dark:text-white">
{{ user.name || user.username }}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 truncate">
{{ item.label }}
</p>
<div class="flex items-center gap-4 py-2 px-2 border-b border-gray-100 dark:border-gray-800 mb-1">
<div class="relative">
<UAvatar
v-bind="item.avatar"
size="lg"
:ui="{
wrapper: 'ring-2 ring-primary-500/40 shadow-lg'
}"
/>
<span
class="absolute -bottom-1 -right-1 w-4 h-4 bg-green-500 border-2 border-white dark:border-gray-900 rounded-full"
title="En línea"
/>
</div>
<div class="flex-1 min-w-0">
<p class="font-bold text-base text-gray-900 dark:text-white truncate mb-0.5">
{{ item.label }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400 truncate font-medium">
{{ item.description }}
</p>
</div>
</div>
</template>
<template #profile="{ item }">
<div class="flex items-center gap-3">
<div :class="['w-9 h-9 rounded-lg bg-blue-50 dark:bg-blue-950/30 flex items-center justify-center transition-colors duration-200 group-hover:bg-blue-100 dark:group-hover:bg-blue-950/50']">
<UIcon :name="item.icon" :class="['w-4 h-4', item.iconClass]" />
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm text-gray-900 dark:text-white">
{{ item.label }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ item.description }}
</p>
</div>
<UIcon name="i-lucide-chevron-right" class="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
</div>
</template>
<template #settings="{ item }">
<div class="flex items-center gap-3">
<div :class="['w-9 h-9 rounded-lg bg-gray-50 dark:bg-gray-800/50 flex items-center justify-center transition-colors duration-200 group-hover:bg-gray-100 dark:group-hover:bg-gray-800']">
<UIcon :name="item.icon" :class="['w-4 h-4', item.iconClass]" />
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm text-gray-900 dark:text-white">
{{ item.label }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ item.description }}
</p>
</div>
<UIcon name="i-lucide-chevron-right" class="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
</div>
</template>
<template #notifications="{ item }">
<div class="flex items-center gap-3">
<div class="relative">
<div :class="['w-9 h-9 rounded-lg bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center transition-colors duration-200 group-hover:bg-amber-100 dark:group-hover:bg-amber-950/50']">
<UIcon :name="item.icon" :class="['w-4 h-4', item.iconClass]" />
</div>
<span v-if="item.badge" class="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center ring-2 ring-white dark:ring-gray-900">
{{ item.badge }}
</span>
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm text-gray-900 dark:text-white">
{{ item.label }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ item.description }}
</p>
</div>
<UIcon name="i-lucide-chevron-right" class="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
</div>
</template>
</UDropdownMenu>
<USkeleton v-else-if="loading" class="h-8 w-8" :ui="{ rounded: 'rounded-full' }" />
<div
v-else-if="loading"
class="relative"
>
<USkeleton
class="h-9 w-9"
:ui="{
rounded: 'rounded-full',
background: 'bg-gradient-to-br from-gray-200 to-gray-300 dark:from-gray-700 dark:to-gray-800'
}"
/>
<div class="absolute inset-0 rounded-full bg-gradient-to-br from-primary-500/20 to-transparent animate-pulse" />
</div>
</template>