Feature: Mejorar UX del tema y persistencia
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 55s

- Mover botón editar al lado del nombre (siempre visible, sutil)
- Quitar efecto hover del header
- Detectar tema del sistema operativo automáticamente
- Actualizar theme-color dinámicamente (azul cielo día / oscuro noche)
- Usar cookies para persistir tema y filtros (1 año)
- Sincronizar filtros de apps con cookies
This commit is contained in:
2025-10-16 23:03:43 -06:00
parent 8b94e81dc8
commit 9a3dc1f0e6
3 changed files with 74 additions and 68 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div class="user-header">
<!-- Header principal clickable -->
<div class="header-content" @click="openEditProfile">
<!-- Header principal -->
<div class="header-content">
<!-- Avatar -->
<div class="avatar-section">
<UAvatar
@@ -15,7 +15,12 @@
<!-- Info del usuario -->
<div class="user-info">
<h1 class="user-name">{{ user?.name || user?.username }}</h1>
<div class="user-name-row">
<h1 class="user-name">{{ user?.name || user?.username }}</h1>
<button class="edit-button" @click.stop="openEditProfile" title="Editar perfil">
<UIcon name="i-heroicons-pencil-square" class="w-4 h-4" />
</button>
</div>
<p class="user-email">{{ user?.email }}</p>
<div class="user-badges">
<UBadge
@@ -39,12 +44,6 @@
</UBadge>
</div>
</div>
<!-- Ícono de edición -->
<div class="edit-hint">
<UIcon name="i-heroicons-pencil-square" class="w-5 h-5" />
<span class="edit-text">Editar perfil</span>
</div>
</div>
<!-- Botón de tema -->
@@ -202,20 +201,10 @@ const handleSubmit = async () => {
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.header-content:hover {
transform: translateY(-4px) scale(1.01);
box-shadow:
0 12px 40px 0 rgba(31, 38, 135, 0.25),
0 0 0 1px rgba(var(--color-primary-500), 0.4),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
border-color: rgba(var(--color-primary-500), 0.5);
}
.avatar-section {
flex-shrink: 0;
}
@@ -225,15 +214,17 @@ const handleSubmit = async () => {
transition: box-shadow 0.3s ease;
}
.header-content:hover .avatar-glow {
box-shadow: 0 0 30px rgba(var(--color-primary-500), 0.6);
}
.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;
@@ -271,28 +262,22 @@ const handleSubmit = async () => {
inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
}
.edit-hint {
.edit-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
background: rgba(var(--color-primary-500), 0.1);
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));
opacity: 0;
transform: translateX(-10px);
transition: all 0.3s ease;
}
.header-content:hover .edit-hint {
opacity: 1;
transform: translateX(0);
}
.edit-text {
font-size: 0.875rem;
font-weight: 500;
white-space: nowrap;
background: rgba(var(--color-primary-500), 0.1);
}
.theme-toggle {
@@ -348,15 +333,6 @@ const handleSubmit = async () => {
padding: 1.5rem 1rem;
}
.edit-hint {
opacity: 1;
transform: translateX(0);
}
.edit-text {
display: none;
}
.theme-toggle {
top: 0.5rem;
right: 0.5rem;

View File

@@ -122,8 +122,15 @@ const { data: applications, pending, error, refresh } = await useFetch<Applicati
default: () => []
})
// Estado de filtros
const selectedGroups = ref<string[]>([])
// Cookie para persistir filtros seleccionados
const filtersCookie = useCookie<string[]>('app-filters', {
maxAge: 60 * 60 * 24 * 365, // 1 año
sameSite: 'lax',
default: () => []
})
// Estado de filtros (inicializado desde cookie)
const selectedGroups = ref<string[]>(filtersCookie.value || [])
// Estado para iconos fallidos (para no intentar cargarlos de nuevo)
const failedIcons = ref<Set<string>>(new Set())
@@ -222,8 +229,15 @@ const toggleGroup = (group: string) => {
} else {
selectedGroups.value.splice(index, 1)
}
// Guardar en cookie
filtersCookie.value = selectedGroups.value
}
// Sincronizar con cookie cuando cambian los filtros
watch(selectedGroups, (newFilters) => {
filtersCookie.value = newFilters
})
// Refrescar automáticamente cada 5 minutos
const refreshInterval = setInterval(() => {
refresh()