Files
perfil/nuxt4/app/app.vue
josedario87 5bb5e5092e
Some checks failed
build-and-deploy / build-and-deploy (push) Has been cancelled
Implementar Web Share Target API para compartir fotos con la PWA
- Agregar share_target al manifest de la PWA
- Crear endpoint /api/share-target para recibir archivos compartidos
- Guardar archivos temporalmente en /public/temp-shared
- Modificar UserProfileForm para aceptar imágenes externas
- Detectar automáticamente imágenes compartidas y procesarlas
- Crear endpoint /api/share-target/cleanup para limpiar temporales
- Mostrar toast informativo al recibir imagen compartida
- Redirigir automáticamente al formulario de perfil
- Soportar compartir desde galería, otras apps, etc.
2025-10-17 18:29:00 -06:00

181 lines
4.9 KiB
Vue

<template>
<UApp>
<NuxtRouteAnnouncer />
<UNotifications />
<!-- Barra de título para Window Controls Overlay (PWA) -->
<WindowTitleBar />
<!-- Fondo animado -->
<AnimatedBackground />
<!-- Contenido principal -->
<div class="main-content">
<UContainer class="py-8">
<div v-if="isAuthenticated" class="space-y-6">
<!-- Header principal con info del usuario -->
<UserHeader @edit-profile="showProfileForm = true" />
<!-- Formulario de edición de perfil o Lista de aplicaciones -->
<UserProfileForm
v-if="showProfileForm"
:shared-image-url="sharedImageUrl"
@close="handleCloseProfileForm"
/>
<AuthApplicationsList v-else />
<!-- Acciones rápidas en footer transparente -->
<div class="quick-actions">
<AuthSessionStatusButton />
<AuthProfileButton />
<AuthLogoutButton />
</div>
</div>
<!-- Mensaje si no está autenticado -->
<div v-else class="auth-message">
<UCard class="text-center">
<div class="py-12">
<UIcon name="i-heroicons-shield-exclamation" class="w-20 h-20 mx-auto mb-6 text-gray-400" />
<h2 class="text-3xl font-bold mb-3">No autenticado</h2>
<p class="text-gray-600 dark:text-gray-400 text-lg">
Authentik Proxy Outpost debería redirigirte automáticamente.
</p>
</div>
</UCard>
</div>
</UContainer>
</div>
</UApp>
</template>
<script setup lang="ts">
const { isAuthenticated } = useAuthentik()
const { isNight } = useTheme()
const route = useRoute()
const router = useRouter()
// Estado para mostrar formulario de edición
const showProfileForm = ref(false)
// URL de imagen compartida (si existe)
const sharedImageUrl = ref<string | null>(null)
// Detectar si se compartió una imagen
onMounted(() => {
const sharedToken = route.query.shared as string
const sharedFile = route.query.file as string
if (sharedToken && sharedFile) {
// Construir URL del archivo temporal
sharedImageUrl.value = `/temp-shared/${sharedFile}`
// Abrir automáticamente el formulario de perfil
showProfileForm.value = true
// Limpiar query params de la URL sin recargar
router.replace({ query: {} })
}
})
// Manejar cierre del formulario
const handleCloseProfileForm = () => {
showProfileForm.value = false
sharedImageUrl.value = null
}
// Configurar meta tags para PWA
useHead({
htmlAttrs: {
lang: 'es'
},
link: [
{ rel: 'manifest', href: '/manifest.webmanifest' },
{ rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }
],
meta: [
{ name: 'theme-color', content: '#00DC82' },
{ name: 'mobile-web-app-capable', content: 'yes' },
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
{ name: 'apple-mobile-web-app-status-bar-style', content: 'default' },
{ name: 'language', content: 'es' },
{ property: 'og:locale', content: 'es_ES' }
]
})
</script>
<style scoped>
.main-content {
position: relative;
z-index: 1;
min-height: 100vh;
/* Ajustar padding cuando Window Controls Overlay está activo */
padding-top: max(2rem, env(titlebar-area-height, 0px));
padding-top: max(2rem, calc(env(titlebar-area-height, 0px) + 1rem));
}
.quick-actions {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.3);
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);
border: 1px solid rgba(255, 255, 255, 0.18);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.auth-message {
display: flex;
align-items: center;
justify-content: center;
min-height: 60vh;
}
.auth-message :deep(.card) {
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow:
0 8px 32px 0 rgba(31, 38, 135, 0.15),
inset 0 1px 1px 0 rgba(255, 255, 255, 0.3);
}
/* Responsive */
@media (max-width: 768px) {
.main-content {
padding-top: 1rem;
}
.quick-actions {
flex-direction: column;
align-items: stretch;
}
}
</style>
<style>
/* Estilos de modo oscuro (sin scoped para que .dark funcione correctamente) */
.dark .quick-actions {
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 .auth-message :deep(.card) {
background: rgba(0, 0, 0, 0.15) !important;
border: 1px solid rgba(255, 255, 255, 0.08) !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;
}
</style>