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

@@ -2,7 +2,14 @@
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
"args": [
"-y",
"chrome-devtools-mcp@latest"
]
},
"context7": {
"type": "http",
"url": "https://mcp.context7.com/mcp"
}
}
}

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>

View File

@@ -47,27 +47,182 @@
class="gap-1"
/>
<UNavigationMenu
:collapsed="isCollapsed"
:items="navigationSecondary"
orientation="vertical"
class="mt-auto gap-1"
/>
</template>
<template #footer="{ collapsed: isCollapsed }">
<UButton
:avatar="{ src: 'https://avatars.githubusercontent.com/u/12011070?v=4' }"
:label="isCollapsed ? undefined : 'Equipo Núcleo'"
color="neutral"
variant="ghost"
class="w-full justify-start"
:block="isCollapsed"
>
<template #trailing>
<UIcon name="i-lucide-log-out" class="size-4" />
</template>
</UButton>
<div v-if="user?.authenticated && !loading" class="space-y-3">
<!-- User Profile Section -->
<div
v-if="!isCollapsed"
class="p-0 space-y-2"
>
<!-- User Info Card -->
<div class="px-3 py-2.5 bg-gradient-to-br from-primary-50/50 to-transparent dark:from-primary-950/20 dark:to-transparent rounded-lg border border-primary-100/60 dark:border-primary-900/40">
<div class="flex items-center gap-3">
<div class="relative flex-shrink-0">
<UAvatar
v-bind="userAvatar"
size="md"
:ui="{
wrapper: 'ring-2 ring-primary-400/50 dark:ring-primary-500/40 shadow-sm'
}"
/>
<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 shadow-sm"
title="En línea"
/>
</div>
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm text-gray-900 dark:text-white truncate leading-tight">
{{ user.name || user.username }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5">
{{ user.email }}
</p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="space-y-1">
<UButton
to="/profile"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-primary-50/80 dark:hover:bg-primary-950/30 transition-all duration-200"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-blue-50 dark:bg-blue-950/30 flex items-center justify-center flex-shrink-0">
<UIcon name="i-lucide-user" class="size-3.5 text-blue-600 dark:text-blue-400" />
</div>
</template>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Mi Perfil</span>
</UButton>
<UButton
to="/settings"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-primary-50/80 dark:hover:bg-primary-950/30 transition-all duration-200"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-gray-50 dark:bg-gray-800/50 flex items-center justify-center flex-shrink-0">
<UIcon name="i-lucide-settings" class="size-3.5 text-gray-600 dark:text-gray-400" />
</div>
</template>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Configuración</span>
</UButton>
<UButton
to="/notifications"
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-primary-50/80 dark:hover:bg-primary-950/30 transition-all duration-200"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center relative flex-shrink-0">
<UIcon name="i-lucide-bell" class="size-3.5 text-amber-600 dark:text-amber-400" />
<span class="absolute -top-0.5 -right-0.5 w-2 h-2 bg-red-500 rounded-full ring-1 ring-white dark:ring-gray-900" />
</div>
</template>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Notificaciones</span>
<template #trailing>
<UBadge color="red" variant="solid" size="xs" class="ml-auto">3</UBadge>
</template>
</UButton>
<div class="pt-2 mt-1.5 border-t border-gray-200/60 dark:border-gray-800/60">
<UButton
color="neutral"
variant="ghost"
size="sm"
block
class="justify-start gap-2.5 hover:bg-red-50/80 dark:hover:bg-red-950/30 transition-all duration-200 group"
:ui="{
rounded: 'rounded-lg',
padding: { sm: 'px-2.5 py-2' }
}"
@click="logout"
>
<template #leading>
<div class="w-7 h-7 rounded-md bg-red-50 dark:bg-red-950/30 flex items-center justify-center flex-shrink-0 group-hover:bg-red-100 dark:group-hover:bg-red-950/50 transition-colors">
<UIcon name="i-lucide-log-out" class="size-3.5 text-red-600 dark:text-red-400" />
</div>
</template>
<span class="text-sm font-medium text-red-600 dark:text-red-400">Cerrar sesión</span>
</UButton>
</div>
</div>
</div>
<!-- Collapsed View -->
<div v-else class="flex flex-col items-center gap-3">
<div class="relative group">
<UButton
color="neutral"
variant="ghost"
square
class="relative hover:bg-primary-50/50 dark:hover:bg-primary-950/20 transition-all duration-200"
:ui="{ rounded: 'rounded-lg' }"
>
<div class="relative">
<UAvatar
v-bind="userAvatar"
size="sm"
:ui="{
wrapper: 'ring-2 ring-primary-400/40 dark:ring-primary-500/30 group-hover:ring-primary-500/60 transition-all duration-200 shadow-sm'
}"
/>
<span
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 bg-green-500 border-2 border-white dark:border-gray-950 rounded-full shadow-sm"
/>
</div>
</UButton>
</div>
<div class="w-full h-px bg-gray-200/50 dark:bg-gray-800/50" />
<UButton
@click="logout"
color="neutral"
variant="ghost"
square
class="hover:bg-red-50 dark:hover:bg-red-950/20 transition-all duration-200"
:ui="{ rounded: 'rounded-lg' }"
>
<UIcon name="i-lucide-log-out" class="size-4 text-red-600 dark:text-red-400" />
</UButton>
</div>
</div>
<!-- Loading State -->
<div v-else-if="loading">
<div v-if="!isCollapsed" class="space-y-2">
<USkeleton class="h-14 w-full" :ui="{ rounded: 'rounded-lg', background: 'bg-gray-200/60 dark:bg-gray-800/60' }" />
<USkeleton class="h-28 w-full" :ui="{ rounded: 'rounded-lg', background: 'bg-gray-200/60 dark:bg-gray-800/60' }" />
</div>
<div v-else class="flex flex-col items-center gap-3">
<USkeleton class="h-9 w-9" :ui="{ rounded: 'rounded-lg', background: 'bg-gray-200/60 dark:bg-gray-800/60' }" />
</div>
</div>
</template>
</UDashboardSidebar>
</template>
@@ -127,18 +282,16 @@ const navigationPrimary = computed<NavigationMenuItem[]>(() => [
}
])
const navigationSecondary: NavigationMenuItem[] = [
{
label: 'Documentación',
icon: 'i-lucide-book-open',
to: 'https://ui.nuxt.com',
target: '_blank'
},
{
label: 'Repositorio',
icon: 'i-lucide-github',
to: 'https://gitea.nucleoriofrio.com/nucleo000/analiticaNucleo',
target: '_blank'
}
]
const { user, loading, fetchUser, logout } = useAuth()
// Cargar usuario al montar
onMounted(() => {
fetchUser()
})
// Computed para el avatar del usuario
const userAvatar = computed(() => ({
src: user.value?.avatar_url || `https://ui-avatars.com/api/?name=${encodeURIComponent(user.value?.name || user.value?.username || 'User')}&background=3b82f6&color=fff&bold=true&format=svg`,
alt: user.value?.name || user.value?.username || 'User'
}))
</script>

View File

@@ -25,8 +25,18 @@ export const useAuth = () => {
}
const logout = () => {
// Authentik maneja el logout, redirigir a la URL de logout
window.location.href = '/outpost.goauthentik.io/sign_out'
// Limpiar estado local
user.value = null
loading.value = false
// Obtener configuración de Authentik desde variables de entorno
const config = useRuntimeConfig()
const authentikUrl = config.public.authentikUrl || 'https://authentik.nucleoriofrio.com'
const appSlug = config.public.authentikAppSlug || 'devserver'
// Redirigir al endpoint de logout de Authentik con el slug de la aplicación
// Esto cierra la sesión completa de Authentik (OIDC end-session)
window.location.href = `${authentikUrl}/application/o/${appSlug}/end-session/`
}
return {

82
nuxt4-app/app/error.vue Normal file
View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps<{
error: NuxtError
}>()
const handleError = () => clearError({ redirect: '/' })
const errorMessage = computed(() => {
if (props.error.statusCode === 404) {
return {
title: 'Página no encontrada',
description: 'Lo sentimos, la página que buscas no existe o ha sido movida.',
icon: 'i-lucide-file-question'
}
}
return {
title: 'Error del servidor',
description: 'Ha ocurrido un error inesperado. Por favor, intenta nuevamente.',
icon: 'i-lucide-alert-circle'
}
})
</script>
<template>
<UDashboardLayout>
<UDashboardPanel grow>
<div class="flex items-center justify-center min-h-screen p-4">
<div class="text-center max-w-md mx-auto space-y-6">
<!-- Error Icon -->
<div class="flex justify-center">
<div class="w-20 h-20 rounded-full bg-red-50 dark:bg-red-950/30 flex items-center justify-center">
<UIcon :name="errorMessage.icon" class="size-10 text-red-600 dark:text-red-400" />
</div>
</div>
<!-- Error Code -->
<div>
<h1 class="text-6xl font-bold text-gray-900 dark:text-white mb-2">
{{ error.statusCode || '500' }}
</h1>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-300 mb-3">
{{ errorMessage.title }}
</h2>
<p class="text-gray-600 dark:text-gray-400">
{{ errorMessage.description }}
</p>
</div>
<!-- Error Details (dev mode) -->
<div v-if="error.message && $config.public.dev" class="mt-4 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg text-left">
<p class="text-xs font-mono text-gray-600 dark:text-gray-400 break-all">
{{ error.message }}
</p>
</div>
<!-- Actions -->
<div class="flex flex-col sm:flex-row gap-3 justify-center mt-8">
<UButton
to="/"
color="primary"
size="lg"
icon="i-lucide-home"
>
Ir al inicio
</UButton>
<UButton
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-refresh-ccw"
@click="handleError"
>
Intentar nuevamente
</UButton>
</div>
</div>
</div>
</UDashboardPanel>
</UDashboardLayout>
</template>

View File

@@ -0,0 +1,150 @@
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
// Mock notifications for preview
const mockNotifications = [
{
id: 1,
type: 'info',
title: 'Nueva actualización disponible',
message: 'Se ha lanzado una nueva versión del sistema con mejoras de rendimiento.',
time: '2 horas',
read: false,
icon: 'i-lucide-info',
color: 'blue'
},
{
id: 2,
type: 'warning',
title: 'Mantenimiento programado',
message: 'El sistema estará en mantenimiento el próximo domingo de 2:00 AM a 4:00 AM.',
time: '1 día',
read: false,
icon: 'i-lucide-alert-triangle',
color: 'amber'
},
{
id: 3,
type: 'success',
title: 'Reporte generado',
message: 'Tu reporte mensual ha sido generado exitosamente.',
time: '3 días',
read: true,
icon: 'i-lucide-check-circle',
color: 'green'
}
]
</script>
<template>
<UDashboardLayout>
<UDashboardPanel grow>
<UDashboardNavbar
title="Notificaciones"
description="Mantente al día con las últimas actualizaciones y alertas"
/>
<UDashboardPanelContent>
<div class="max-w-4xl mx-auto space-y-8">
<!-- Coming Soon Banner -->
<UCard>
<div class="text-center py-12 space-y-6">
<div class="flex justify-center">
<div class="w-24 h-24 rounded-full bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center relative">
<UIcon name="i-lucide-bell" class="size-12 text-amber-600 dark:text-amber-400" />
<span class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center ring-4 ring-white dark:ring-gray-900">
3
</span>
</div>
</div>
<div>
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Página en construcción
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">
Estamos trabajando en esta funcionalidad
</p>
<p class="text-sm text-gray-500 dark:text-gray-500 max-w-md mx-auto">
Pronto podrás gestionar tus notificaciones, configurar alertas personalizadas y mantenerte informado sobre eventos importantes del sistema.
</p>
</div>
<div class="flex justify-center gap-3">
<UButton
to="/"
color="primary"
icon="i-lucide-home"
>
Volver al inicio
</UButton>
</div>
</div>
</UCard>
<!-- Notifications Preview -->
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Vista previa de notificaciones
</h3>
<UBadge color="red" variant="solid" size="sm">
{{ mockNotifications.filter(n => !n.read).length }} nuevas
</UBadge>
</div>
</template>
<div class="space-y-3">
<div
v-for="notification in mockNotifications"
:key="notification.id"
:class="[
'p-4 rounded-lg border transition-colors',
notification.read
? 'bg-gray-50/50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700'
: 'bg-white dark:bg-gray-900 border-gray-300 dark:border-gray-600'
]"
>
<div class="flex gap-3">
<div :class="[
'w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0',
`bg-${notification.color}-50 dark:bg-${notification.color}-950/30`
]">
<UIcon
:name="notification.icon"
:class="[
'size-5',
`text-${notification.color}-600 dark:text-${notification.color}-400`
]"
/>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-start justify-between gap-2">
<h4 :class="[
'font-semibold text-sm',
notification.read
? 'text-gray-600 dark:text-gray-400'
: 'text-gray-900 dark:text-white'
]">
{{ notification.title }}
</h4>
<span class="text-xs text-gray-500 dark:text-gray-500 flex-shrink-0">
hace {{ notification.time }}
</span>
</div>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
{{ notification.message }}
</p>
</div>
</div>
</div>
</div>
</UCard>
</div>
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardLayout>
</template>

View File

@@ -0,0 +1,93 @@
<script setup lang="ts">
const { user } = useAuth()
definePageMeta({
middleware: 'auth'
})
</script>
<template>
<UDashboardLayout>
<UDashboardPanel grow>
<UDashboardNavbar
title="Mi Perfil"
description="Gestiona tu información personal y preferencias de cuenta"
/>
<UDashboardPanelContent>
<div class="max-w-4xl mx-auto space-y-8">
<!-- Coming Soon Banner -->
<UCard>
<div class="text-center py-12 space-y-6">
<div class="flex justify-center">
<div class="w-24 h-24 rounded-full bg-blue-50 dark:bg-blue-950/30 flex items-center justify-center">
<UIcon name="i-lucide-user" class="size-12 text-blue-600 dark:text-blue-400" />
</div>
</div>
<div>
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Página en construcción
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">
Estamos trabajando en esta funcionalidad
</p>
<p class="text-sm text-gray-500 dark:text-gray-500 max-w-md mx-auto">
Pronto podrás gestionar tu perfil, actualizar tu información personal, cambiar tu foto de perfil y más.
</p>
</div>
<div class="flex justify-center gap-3">
<UButton
to="/"
color="primary"
icon="i-lucide-home"
>
Volver al inicio
</UButton>
</div>
</div>
</UCard>
<!-- Current User Info Preview -->
<UCard v-if="user">
<template #header>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
Información actual del usuario
</h3>
</template>
<div class="space-y-4">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
Nombre de usuario
</label>
<p class="mt-1 text-gray-900 dark:text-white">
{{ user.username || 'No especificado' }}
</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
Nombre completo
</label>
<p class="mt-1 text-gray-900 dark:text-white">
{{ user.name || 'No especificado' }}
</p>
</div>
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
Email
</label>
<p class="mt-1 text-gray-900 dark:text-white">
{{ user.email || 'No especificado' }}
</p>
</div>
</div>
</UCard>
</div>
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardLayout>
</template>

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
<template>
<UDashboardLayout>
<UDashboardPanel grow>
<UDashboardNavbar
title="Configuración"
description="Personaliza tu experiencia y preferencias del sistema"
/>
<UDashboardPanelContent>
<div class="max-w-4xl mx-auto space-y-8">
<!-- Coming Soon Banner -->
<UCard>
<div class="text-center py-12 space-y-6">
<div class="flex justify-center">
<div class="w-24 h-24 rounded-full bg-gray-50 dark:bg-gray-800/50 flex items-center justify-center">
<UIcon name="i-lucide-settings" class="size-12 text-gray-600 dark:text-gray-400" />
</div>
</div>
<div>
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Página en construcción
</h2>
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">
Estamos trabajando en esta funcionalidad
</p>
<p class="text-sm text-gray-500 dark:text-gray-500 max-w-md mx-auto">
Pronto podrás configurar tus preferencias de visualización, notificaciones, privacidad y más opciones del sistema.
</p>
</div>
<div class="flex justify-center gap-3">
<UButton
to="/"
color="primary"
icon="i-lucide-home"
>
Volver al inicio
</UButton>
</div>
</div>
</UCard>
<!-- Settings Preview -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-blue-50 dark:bg-blue-950/30 flex items-center justify-center">
<UIcon name="i-lucide-palette" class="size-5 text-blue-600 dark:text-blue-400" />
</div>
<h3 class="font-semibold text-gray-900 dark:text-white">
Apariencia
</h3>
</div>
</template>
<p class="text-sm text-gray-600 dark:text-gray-400">
Tema, colores y personalización visual
</p>
</UCard>
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-amber-50 dark:bg-amber-950/30 flex items-center justify-center">
<UIcon name="i-lucide-bell" class="size-5 text-amber-600 dark:text-amber-400" />
</div>
<h3 class="font-semibold text-gray-900 dark:text-white">
Notificaciones
</h3>
</div>
</template>
<p class="text-sm text-gray-600 dark:text-gray-400">
Gestión de alertas y comunicaciones
</p>
</UCard>
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-green-50 dark:bg-green-950/30 flex items-center justify-center">
<UIcon name="i-lucide-shield" class="size-5 text-green-600 dark:text-green-400" />
</div>
<h3 class="font-semibold text-gray-900 dark:text-white">
Privacidad
</h3>
</div>
</template>
<p class="text-sm text-gray-600 dark:text-gray-400">
Control de datos y seguridad
</p>
</UCard>
<UCard>
<template #header>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-lg bg-purple-50 dark:bg-purple-950/30 flex items-center justify-center">
<UIcon name="i-lucide-globe" class="size-5 text-purple-600 dark:text-purple-400" />
</div>
<h3 class="font-semibold text-gray-900 dark:text-white">
Idioma y región
</h3>
</div>
</template>
<p class="text-sm text-gray-600 dark:text-gray-400">
Preferencias de localización
</p>
</UCard>
</div>
</div>
</UDashboardPanelContent>
</UDashboardPanel>
</UDashboardLayout>
</template>

View File

@@ -164,6 +164,10 @@ export default defineNuxtConfig({
url: process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL,
serviceRoleKey:
process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
},
public: {
authentikUrl: process.env.NUXT_PUBLIC_AUTHENTIK_URL || '',
authentikAppSlug: process.env.NUXT_PUBLIC_AUTHENTIK_APP_SLUG || ''
}
}
})