Files
arcadia-admin/app/lib/profile-bootstrap.tsx
jules ab116f8465 refactor: rename @crema/arcadia-client → @crema/arcadia-core-client
Disambiguates the Phoenix/auth client lib from lib-arcadia-agents-client.
Dir lib-arcadia-client → lib-arcadia-core-client; alias updated in
tsconfig paths, vite config, app.css @source, imports, CI and docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:31:56 +10:00

91 lines
3.1 KiB
TypeScript

// Fetches the arcadia profile on app boot (and after login) and caches
// the resolved avatar URL in localStorage so the appbar's <Avatar> shows
// immediately, without waiting for the user to navigate to /profile.
import { useEffect } from "react"
import { useArcadiaClient } from "@crema/arcadia-core-client"
import { fetchDigitalObjectAsBlobUrl } from "~/lib/arcadia/digital-objects"
import { getProfile, pickAvatarUrl } from "~/lib/arcadia/profiles"
import { loadProfile, saveProfile } from "~/lib/profile"
export function ProfileBootstrap() {
const arcadia = useArcadiaClient()
useEffect(() => {
let cancelled = false
const tryBootstrap = async () => {
const token =
typeof window !== "undefined"
? sessionStorage.getItem("arcadia_access_token")
: null
if (!token) return
try {
const p = await getProfile(arcadia)
if (cancelled) return
const persistentUrl = pickAvatarUrl(p)
const current = loadProfile()
const cachedIsStaleBlob = current.avatarUrl?.startsWith("blob:") ?? false
if (persistentUrl) {
if (current.avatarUrl !== persistentUrl) {
saveProfile({ ...current, avatarUrl: persistentUrl })
}
return
}
// No persistent variant yet but the user has an avatar — fetch
// the raw bytes as a blob URL. This also covers the "stale blob
// URL from previous session" case: replace it with a fresh one.
if (p.avatar_digital_object_id) {
if (cachedIsStaleBlob) {
// Clear the stale URL immediately so the appbar drops back
// to initials while we refetch (better than a broken image).
saveProfile({ ...current, avatarUrl: "" })
}
try {
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"
const blobUrl = await fetchDigitalObjectAsBlobUrl(
baseUrl,
p.avatar_digital_object_id,
token,
tenantId,
)
if (cancelled) return
const fresh = loadProfile()
saveProfile({ ...fresh, avatarUrl: blobUrl })
} catch {
// Best-effort; appbar will show initials until processing completes.
}
return
}
// No avatar at all — clear any stale URL the cache might still hold.
if (current.avatarUrl) {
saveProfile({ ...current, avatarUrl: "" })
}
} catch {
// 401 / network — silently skip; will retry on next session change.
}
}
void tryBootstrap()
const onSessionChange = () => void tryBootstrap()
window.addEventListener("crema:session-change", onSessionChange)
return () => {
cancelled = true
window.removeEventListener("crema:session-change", onSessionChange)
}
}, [arcadia])
return null
}