diff --git a/nuxt4/app/components/UserProfileForm.vue b/nuxt4/app/components/UserProfileForm.vue index a54ea3a..b2d296d 100644 --- a/nuxt4/app/components/UserProfileForm.vue +++ b/nuxt4/app/components/UserProfileForm.vue @@ -49,6 +49,33 @@ o usa los botones de abajo + +
+
+ + O pega una URL de imagen +
+
+ + + + Cargar + +
+ {{ urlError }} +
+
(null) +// URL del avatar +const avatarUrl = ref('') +const urlError = ref('') + // Cookie para persistir cambios del formulario const formCookie = useCookie>('profile-form-draft', { maxAge: 60 * 60 * 24 * 7, // 7 días @@ -815,6 +847,91 @@ const processImageFile = async (file: File) => { await handleAvatarCapture(blob) } +// Cargar avatar desde URL +const loadAvatarFromUrl = async () => { + urlError.value = '' + + // Validar que la URL no esté vacía + if (!avatarUrl.value.trim()) { + urlError.value = 'Por favor ingresa una URL' + return + } + + // Validar formato de URL + try { + new URL(avatarUrl.value) + } catch (error) { + urlError.value = 'URL inválida. Por favor ingresa una URL válida (ej: https://ejemplo.com/imagen.jpg)' + return + } + + // Validar que la URL sea https + if (!avatarUrl.value.startsWith('https://') && !avatarUrl.value.startsWith('http://')) { + urlError.value = 'La URL debe comenzar con http:// o https://' + return + } + + isLoadingUrl.value = true + isUploading.value = true + + try { + // Descargar la imagen + const response = await fetch(avatarUrl.value, { + mode: 'cors' + }) + + if (!response.ok) { + throw new Error(`Error al descargar la imagen: ${response.statusText}`) + } + + const blob = await response.blob() + + // Validar que sea una imagen + if (!blob.type.startsWith('image/')) { + throw new Error('El archivo en la URL no es una imagen válida') + } + + // Validar tamaño (máximo 5MB) + const maxSize = 5 * 1024 * 1024 // 5MB + if (blob.size > maxSize) { + throw new Error('La imagen debe ser menor a 5MB') + } + + // Procesar la imagen + await handleAvatarCapture(blob) + + // Limpiar el campo de URL y errores + avatarUrl.value = '' + urlError.value = '' + + toast.add({ + title: 'Imagen cargada', + description: 'La imagen se descargó correctamente desde la URL', + color: 'success', + icon: 'i-heroicons-check-circle' + }) + } catch (error: any) { + console.error('Error loading image from URL:', error) + + // Manejar errores de CORS + if (error.message.includes('CORS') || error.message.includes('NetworkError')) { + urlError.value = 'No se pudo acceder a la imagen. Puede que el servidor no permita descargas externas (CORS). Intenta descargar la imagen manualmente y subirla desde tu dispositivo.' + } else { + urlError.value = error.message || 'No se pudo cargar la imagen desde la URL' + } + + toast.add({ + title: 'Error', + description: error.message || 'No se pudo cargar la imagen desde la URL', + color: 'error', + icon: 'i-heroicons-exclamation-triangle' + }) + } finally { + isLoadingUrl.value = false + isUploading.value = false + } +} + // Procesar imagen compartida desde Web Share Target const processSharedImage = async (imageUrl: string) => { try { @@ -1045,10 +1162,19 @@ const processSharedImage = async (imageUrl: string) => { font-style: italic; } +.avatar-url-section { + margin-top: 1rem; + padding: 1rem; + background: rgba(var(--color-gray-100), 0.5); + border-radius: 0.75rem; + border: 1px solid rgba(var(--color-gray-300), 0.5); +} + .avatar-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; + margin-top: 1rem; } .camera-modal { @@ -1301,6 +1427,11 @@ const processSharedImage = async (imageUrl: string) => { color: var(--color-gray-400) !important; } +.dark .avatar-url-section { + background: rgba(255, 255, 255, 0.03) !important; + border-color: rgba(255, 255, 255, 0.1) !important; +} + .dark .form-actions { border-top-color: rgba(255, 255, 255, 0.1); }