Files
perfil/nuxt4/app/components/UserHeader.vue
josedario87 6b87902119
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 55s
Implementar avatar estilo Windows Live Messenger con sistema de presencia
- Agregar componente MsnAvatar con marco SVG de grosor variable
- Implementar degradados radiales estilo Frutiger Aero
- Agregar composable usePresence con detección de inactividad
- Incluir selector de estados: Online, Away, Busy, Offline
- Actualizar UserHeader para usar el nuevo avatar
2025-10-17 13:56:59 -06:00

395 lines
10 KiB
Vue

<template>
<div class="user-header">
<!-- Header principal -->
<div class="header-content">
<!-- Avatar estilo MSN Messenger -->
<div class="avatar-section">
<MsnAvatar
v-if="user"
:src="user.avatar"
:alt="user.name || user.username"
:presence-status="presenceStatus"
:size="140"
/>
</div>
<!-- Info del usuario -->
<div class="user-info">
<div class="user-name-row">
<h1 class="user-name">{{ user?.name || user?.username }}</h1>
<button class="edit-button" @click="$emit('edit-profile')" title="Editar perfil">
<UIcon name="i-heroicons-pencil-square" class="w-4 h-4" />
</button>
</div>
<p class="user-email">{{ user?.email }}</p>
<!-- Selector de estado de presencia -->
<div class="presence-selector">
<button
v-for="option in presenceOptions"
:key="option.value"
:class="['presence-option', { active: presenceStatus === option.value }]"
@click="setPresenceStatus(option.value as any)"
:title="option.label"
>
<UIcon :name="option.icon" class="w-3.5 h-3.5" />
<span class="presence-label">{{ option.label }}</span>
</button>
</div>
<div class="user-badges">
<span
v-for="group in user?.groups.slice(0, 3)"
:key="group"
class="user-badge"
>
{{ group }}
</span>
<span
v-if="user && user.groups.length > 3"
class="user-badge user-badge-count"
>
+{{ user.groups.length - 3 }}
</span>
</div>
</div>
</div>
<!-- Botón de tema -->
<button class="theme-toggle" @click.stop="toggleTheme" :title="isNight ? 'Cambiar a modo día' : 'Cambiar a modo noche'">
<transition name="theme-icon" mode="out-in">
<UIcon v-if="isNight" key="moon" name="i-heroicons-moon" class="w-6 h-6" />
<UIcon v-else key="sun" name="i-heroicons-sun" class="w-6 h-6" />
</transition>
</button>
</div>
</template>
<script setup lang="ts">
const { user } = useAuthentik()
const { isNight, toggleTheme } = useTheme()
const { status: presenceStatus, setStatus: setPresenceStatus, initActivityListeners } = usePresence()
// Emits
defineEmits(['edit-profile'])
// Inicializar listeners de actividad
onMounted(() => {
initActivityListeners()
})
// Estados disponibles para el selector
const presenceOptions = [
{ value: 'online', label: 'Disponible', icon: 'i-heroicons-check-circle' },
{ value: 'away', label: 'Ausente', icon: 'i-heroicons-clock' },
{ value: 'busy', label: 'Ocupado', icon: 'i-heroicons-minus-circle' },
{ value: 'offline', label: 'Desconectado', icon: 'i-heroicons-x-circle' }
]
</script>
<style scoped>
.user-header {
position: relative;
margin-bottom: 2rem;
}
.header-content {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1.5rem 2rem;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 1.5rem;
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.avatar-section {
flex-shrink: 0;
}
.user-info {
flex: 1;
min-width: 0;
}
.user-name-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.user-name {
font-size: 1.5rem;
font-weight: 700;
margin: 0;
color: var(--color-gray-900);
transition: color 0.3s ease;
}
.user-email {
font-size: 0.875rem;
color: var(--color-gray-600);
margin: 0.25rem 0 0.5rem 0;
}
.presence-selector {
display: flex;
gap: 0.375rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
}
.presence-option {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 0.5rem;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 0, 0, 0.08);
color: var(--color-gray-600);
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.presence-option:hover {
background: rgba(255, 255, 255, 0.5);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.presence-option.active {
background: rgba(var(--color-primary-500), 0.2);
border-color: rgba(var(--color-primary-500), 0.4);
color: rgb(var(--color-primary-600));
font-weight: 600;
box-shadow: 0 2px 8px rgba(var(--color-primary-500), 0.2);
}
.presence-label {
white-space: nowrap;
}
.user-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.user-badge {
display: inline-block;
padding: 0.375rem 0.875rem;
font-size: 0.8125rem;
font-weight: 600;
border-radius: 0.625rem;
background: rgba(var(--color-primary-500), 0.15);
backdrop-filter: blur(10px) saturate(150%);
border: 1px solid rgba(var(--color-primary-500), 0.3);
color: rgb(var(--color-primary-500));
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 2px 8px 0 rgba(var(--color-primary-500), 0.2),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3),
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.1);
}
.user-badge:hover {
transform: translateY(-1px);
box-shadow:
0 4px 12px 0 rgba(var(--color-primary-500), 0.3),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.4),
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.15);
}
.user-badge-count {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(0, 0, 0, 0.1);
color: var(--color-gray-700);
box-shadow:
0 2px 6px 0 rgba(31, 38, 135, 0.08),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
}
.edit-button {
display: flex;
align-items: center;
justify-content: center;
padding: 0.25rem;
background: transparent;
border: none;
color: var(--color-gray-400);
cursor: pointer;
transition: all 0.2s ease;
border-radius: 0.375rem;
}
.edit-button:hover {
color: rgb(var(--color-primary-500));
background: rgba(var(--color-primary-500), 0.1);
}
.theme-toggle {
position: absolute;
top: 1rem;
right: 1rem;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(15px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.18);
color: rgb(var(--color-primary-500));
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 4px 16px 0 rgba(31, 38, 135, 0.2),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
z-index: 10;
}
.theme-toggle:hover {
transform: scale(1.15) rotate(20deg) translateY(-2px);
box-shadow:
0 8px 24px 0 rgba(31, 38, 135, 0.3),
0 0 0 1px rgba(var(--color-primary-500), 0.5),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
}
.theme-icon-enter-active,
.theme-icon-leave-active {
transition: all 0.3s ease;
}
.theme-icon-enter-from {
opacity: 0;
transform: rotate(-180deg) scale(0);
}
.theme-icon-leave-to {
opacity: 0;
transform: rotate(180deg) scale(0);
}
/* Responsive */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
text-align: center;
padding: 1.5rem 1rem;
}
.theme-toggle {
top: 0.5rem;
right: 0.5rem;
width: 2.5rem;
height: 2.5rem;
}
.user-name {
font-size: 1.25rem;
}
.user-badges {
justify-content: center;
}
}
</style>
<style>
/* Estilos de modo oscuro (sin scoped para que .dark funcione correctamente) */
.dark .header-content {
background: rgba(0, 0, 0, 0.15) !important;
box-shadow:
0 8px 32px 0 rgba(0, 0, 0, 0.5),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.05) !important;
border: 1px solid rgba(255, 255, 255, 0.08) !important;
}
.dark .header-content:hover {
box-shadow:
0 12px 40px 0 rgba(0, 0, 0, 0.6),
0 0 0 1px rgba(var(--color-primary-500), 0.6),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.1) !important;
}
.dark .user-name {
color: var(--color-gray-100) !important;
}
.dark .user-email {
color: var(--color-gray-300) !important;
font-weight: 600;
}
.dark .theme-toggle {
background: rgba(0, 0, 0, 0.15) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
box-shadow:
0 4px 16px 0 rgba(0, 0, 0, 0.5),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.05) !important;
}
.dark .theme-toggle:hover {
box-shadow:
0 8px 24px 0 rgba(0, 0, 0, 0.7),
0 0 0 1px rgba(var(--color-primary-500), 0.7),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.1) !important;
}
.dark .user-badge {
background: rgba(var(--color-primary-500), 0.25) !important;
border-color: rgba(var(--color-primary-500), 0.5) !important;
color: rgb(var(--color-primary-400)) !important;
box-shadow:
0 2px 8px 0 rgba(var(--color-primary-500), 0.4),
0 0 0 1px rgba(var(--color-primary-500), 0.3),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.08),
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.2) !important;
}
.dark .user-badge:hover {
box-shadow:
0 4px 12px 0 rgba(var(--color-primary-500), 0.5),
0 0 0 1px rgba(var(--color-primary-500), 0.4),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.1),
inset 0 -1px 2px 0 rgba(var(--color-primary-500), 0.25) !important;
}
.dark .user-badge-count {
background: rgba(255, 255, 255, 0.05) !important;
border-color: rgba(255, 255, 255, 0.1) !important;
color: var(--color-gray-300) !important;
box-shadow:
0 2px 6px 0 rgba(0, 0, 0, 0.3),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.05) !important;
}
.dark .presence-option {
background: rgba(255, 255, 255, 0.05) !important;
border-color: rgba(255, 255, 255, 0.1) !important;
color: var(--color-gray-400) !important;
}
.dark .presence-option:hover {
background: rgba(255, 255, 255, 0.1) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
}
.dark .presence-option.active {
background: rgba(var(--color-primary-500), 0.3) !important;
border-color: rgba(var(--color-primary-500), 0.5) !important;
color: rgb(var(--color-primary-400)) !important;
box-shadow: 0 2px 8px rgba(var(--color-primary-500), 0.4) !important;
}
</style>