From 9afe54d18800a68cff3998e8b902803c669db870 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Fri, 17 Oct 2025 17:56:58 -0600 Subject: [PATCH] Agregar carga de archivos y drag & drop para foto de perfil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregar zona de drag & drop con feedback visual - Agregar botón para subir foto desde dispositivo - Implementar validación de tipo de archivo (solo imágenes) - Implementar validación de tamaño (máximo 5MB) - Agregar estilos responsive y soporte para modo oscuro --- nuxt4/app/components/UserProfileForm.vue | 192 ++++++++++++++++++++++- 1 file changed, 189 insertions(+), 3 deletions(-) diff --git a/nuxt4/app/components/UserProfileForm.vue b/nuxt4/app/components/UserProfileForm.vue index 5a482e2..0290576 100644 --- a/nuxt4/app/components/UserProfileForm.vue +++ b/nuxt4/app/components/UserProfileForm.vue @@ -33,7 +33,32 @@ Foto de Perfil -
+ + +
+ +

+ Arrastra una imagen aquí +

+ o usa los botones de abajo +
+ + +
+ + - {{ currentAvatar && currentAvatar.includes('/avatars/') ? 'Cambiar foto' : 'Tomar foto' }} + Tomar foto + + + + + Subir desde dispositivo (null) // Cookie para persistir cambios del formulario const formCookie = useCookie>('profile-form-draft', { @@ -694,6 +734,74 @@ const removeAvatar = async () => { isUploading.value = false } } + +// Abrir selector de archivos +const triggerFileInput = () => { + fileInput.value?.click() +} + +// Manejar selección de archivo desde input +const handleFileSelect = (event: Event) => { + const input = event.target as HTMLInputElement + const file = input.files?.[0] + if (file) { + processImageFile(file) + } + // Limpiar input para permitir seleccionar el mismo archivo de nuevo + input.value = '' +} + +// Manejar drag over +const handleDragOver = (event: DragEvent) => { + event.preventDefault() + isDragging.value = true +} + +// Manejar drag leave +const handleDragLeave = () => { + isDragging.value = false +} + +// Manejar drop +const handleDrop = (event: DragEvent) => { + event.preventDefault() + isDragging.value = false + + const file = event.dataTransfer?.files[0] + if (file) { + processImageFile(file) + } +} + +// Procesar archivo de imagen +const processImageFile = async (file: File) => { + // Validar que sea una imagen + if (!file.type.startsWith('image/')) { + toast.add({ + title: 'Archivo inválido', + description: 'Por favor selecciona un archivo de imagen', + color: 'error', + icon: 'i-heroicons-exclamation-triangle' + }) + return + } + + // Validar tamaño (máximo 5MB) + const maxSize = 5 * 1024 * 1024 // 5MB + if (file.size > maxSize) { + toast.add({ + title: 'Archivo muy grande', + description: 'La imagen debe ser menor a 5MB', + color: 'error', + icon: 'i-heroicons-exclamation-triangle' + }) + return + } + + // Convertir a Blob y subir + const blob = new Blob([file], { type: file.type }) + await handleAvatarCapture(blob) +}