diff --git a/neode-ui/src/views/web5/Web5Identities.vue b/neode-ui/src/views/web5/Web5Identities.vue index 30b09c74..9cc79ad6 100644 --- a/neode-ui/src/views/web5/Web5Identities.vue +++ b/neode-ui/src/views/web5/Web5Identities.vue @@ -321,12 +321,24 @@
- - + +
+ + +
- - + +
+ + +
@@ -395,6 +407,46 @@ const profileEditorIdentity = ref(null) const profileForm = ref({}) const profileSaving = ref(false) const profilePublishing = ref(false) +const avatarUploading = ref(false) +const bannerUploading = ref(false) + +// Upload to local blob store + set the corresponding profile URL so +// the kind:0 event (publish) includes a reachable picture/banner. The +// returned `self_test_url` is a capability-signed /blob/?cap=… +// path — works locally. For external nostr clients to see the image, +// swap to a public image host later. +async function uploadAsset(ev: Event, field: 'picture' | 'banner') { + const input = ev.target as HTMLInputElement + const file = input?.files?.[0] + if (!file) return + const flag = field === 'picture' ? avatarUploading : bannerUploading + flag.value = true + profileError.value = '' + try { + const buf = await file.arrayBuffer() + const resp = await fetch('/api/blob', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Blob-Mime': file.type || 'application/octet-stream', + 'X-Blob-Filename': file.name, + }, + body: buf, + }) + if (!resp.ok) throw new Error(`upload failed: HTTP ${resp.status}`) + const { self_test_url } = await resp.json() as { self_test_url?: string } + if (!self_test_url) throw new Error('blob API returned no URL') + // Assign and let the in the header preview react. + profileForm.value[field] = self_test_url + } catch (e: unknown) { + profileError.value = e instanceof Error ? e.message : `${field} upload failed` + } finally { + flag.value = false + // Clear the input so selecting the same file again re-fires change. + if (input) input.value = '' + } +} const profileError = ref('') const profileSuccess = ref('')