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);
}