avatar: render immediately + survive reload
The variant pipeline is async, so right after upload all four URLs in
profile.avatar_urls are still null. The first wiring attempt called
pickAvatarUrl() which returned null, and nothing visible changed even
though the upload + PATCH succeeded.
Fixes:
- pickAvatarUrl: use the actual backend keys (small/medium/large/
original — there's no "thumbnail").
- After upload, when no variant URL is ready, fetch the raw object
via /api/v1/digital_objects/:id/content as a blob URL for immediate
display. Persist that URL to localStorage so the appbar's
useProfile() picks it up via the storage event.
- ProfileBootstrap: detect stale blob: URLs cached from previous
sessions, clear them, and refetch a fresh blob URL when variants
still aren't ready. Eventually the persistent variant URLs land
and overwrite.
- Force-remount AvatarImage via key={src} in the profile page and
appbar — base-ui's Avatar.Image keeps internal load state that
doesn't always reset on src change.
- Diagnostic logs in fetchDigitalObjectAsBlobUrl + the upload flow
to make next debug round one step easier (kept; cheap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,10 @@ import {
|
||||
type Profile,
|
||||
} from "~/lib/profile"
|
||||
import { getUser, updateUser, type User } from "~/lib/arcadia/users"
|
||||
import { uploadFile } from "~/lib/arcadia/digital-objects"
|
||||
import {
|
||||
fetchDigitalObjectAsBlobUrl,
|
||||
uploadFile,
|
||||
} from "~/lib/arcadia/digital-objects"
|
||||
import {
|
||||
getProfile,
|
||||
updateProfile as updateArcadiaProfile,
|
||||
@@ -246,11 +249,49 @@ export default function ProfileRoute() {
|
||||
avatar_digital_object_id: obj.id,
|
||||
})
|
||||
setArcadiaProfile(updated)
|
||||
const url = pickAvatarUrl(updated)
|
||||
if (url) {
|
||||
const next = { ...prefs, avatarUrl: url }
|
||||
const persistentUrl = pickAvatarUrl(updated)
|
||||
if (persistentUrl) {
|
||||
// Variant pipeline already finished — persist to localStorage.
|
||||
const next = { ...prefs, avatarUrl: persistentUrl }
|
||||
setPrefs(next)
|
||||
savePrefsLocal(next)
|
||||
} else {
|
||||
// Variants aren't ready yet (image-processing is async). Fetch
|
||||
// the raw object as a blob URL for immediate in-memory render.
|
||||
// Don't persist to localStorage — blob URLs don't survive a
|
||||
// reload, and ProfileBootstrap will pick up the persistent URL
|
||||
// on next mount once processing completes.
|
||||
const token =
|
||||
typeof window !== "undefined"
|
||||
? sessionStorage.getItem("arcadia_access_token")
|
||||
: null
|
||||
if (token) {
|
||||
const baseUrl =
|
||||
(import.meta.env.VITE_ARCADIA_URL as string | undefined) ??
|
||||
"http://localhost:4000"
|
||||
const tenantId =
|
||||
(import.meta.env.VITE_ARCADIA_TENANT as string | undefined) ??
|
||||
"default"
|
||||
try {
|
||||
const blobUrl = await fetchDigitalObjectAsBlobUrl(
|
||||
baseUrl,
|
||||
obj.id,
|
||||
token,
|
||||
tenantId,
|
||||
)
|
||||
// eslint-disable-next-line no-console
|
||||
console.info("[avatar] blob URL ready:", blobUrl)
|
||||
// Persist the blob URL so the appbar's useProfile() picks it
|
||||
// up via the storage event. Blob URLs don't survive a reload,
|
||||
// but ProfileBootstrap will refresh on next mount.
|
||||
const next = { ...prefs, avatarUrl: blobUrl }
|
||||
setPrefs(next)
|
||||
savePrefsLocal(next)
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("[avatar] blob fetch failed:", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setAvatarError(
|
||||
@@ -300,7 +341,11 @@ export default function ProfileRoute() {
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<Avatar className="size-20 ring-2 ring-primary/30">
|
||||
{prefs.avatarUrl ? (
|
||||
<AvatarImage src={prefs.avatarUrl} alt={accountDraft.email} />
|
||||
<AvatarImage
|
||||
key={prefs.avatarUrl}
|
||||
src={prefs.avatarUrl}
|
||||
alt={accountDraft.email}
|
||||
/>
|
||||
) : null}
|
||||
<AvatarFallback className="bg-primary text-lg font-semibold text-primary-foreground">
|
||||
{initials}
|
||||
|
||||
Reference in New Issue
Block a user