profile: bootstrap avatar URL on app boot
The appbar's <Avatar> reads avatarUrl from the localStorage profile mirror. Without a global fetcher, that mirror only got populated when the user navigated to /profile, so a fresh browser session showed initials in the appbar until then. - ProfileBootstrap component runs in root.tsx alongside LlmConfigBootstrap. On mount and on session change, fetches the arcadia profile and caches the resolved avatar URL. - profile.tsx loadAccount now also persists the URL into localStorage on initial fetch (was in-memory only) so it survives reloads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
47
app/lib/profile-bootstrap.tsx
Normal file
47
app/lib/profile-bootstrap.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// 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-client"
|
||||||
|
|
||||||
|
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 url = pickAvatarUrl(p)
|
||||||
|
if (!url) return
|
||||||
|
const current = loadProfile()
|
||||||
|
if (current.avatarUrl === url) return
|
||||||
|
saveProfile({ ...current, avatarUrl: url })
|
||||||
|
} 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
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { ToastProvider, Toaster } from "@crema/notification-ui"
|
|||||||
import { CommandBusProvider } from "@crema/action-bus"
|
import { CommandBusProvider } from "@crema/action-bus"
|
||||||
import { ArcadiaProvider } from "@crema/arcadia-client"
|
import { ArcadiaProvider } from "@crema/arcadia-client"
|
||||||
import { LlmConfigBootstrap } from "~/lib/llm-config-bootstrap"
|
import { LlmConfigBootstrap } from "~/lib/llm-config-bootstrap"
|
||||||
|
import { ProfileBootstrap } from "~/lib/profile-bootstrap"
|
||||||
// CREMA:PROVIDERS-IMPORTS
|
// CREMA:PROVIDERS-IMPORTS
|
||||||
|
|
||||||
const ARCADIA_URL = import.meta.env.VITE_ARCADIA_URL ?? "http://localhost:4000"
|
const ARCADIA_URL = import.meta.env.VITE_ARCADIA_URL ?? "http://localhost:4000"
|
||||||
@@ -63,6 +64,7 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<CommandBusProvider>
|
<CommandBusProvider>
|
||||||
<LlmConfigBootstrap />
|
<LlmConfigBootstrap />
|
||||||
|
<ProfileBootstrap />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</CommandBusProvider>
|
</CommandBusProvider>
|
||||||
|
|||||||
@@ -115,7 +115,17 @@ export default function ProfileRoute() {
|
|||||||
timezone: p.timezone ?? "",
|
timezone: p.timezone ?? "",
|
||||||
})
|
})
|
||||||
const url = pickAvatarUrl(p)
|
const url = pickAvatarUrl(p)
|
||||||
if (url) setPrefs((d) => ({ ...d, avatarUrl: url }))
|
if (url) {
|
||||||
|
// Persist into localStorage so the appbar's useProfile() picks
|
||||||
|
// it up on next render — without this, the appbar avatar reverts
|
||||||
|
// to initials on every fresh browser session until the user
|
||||||
|
// re-uploads.
|
||||||
|
setPrefs((d) => {
|
||||||
|
const next = { ...d, avatarUrl: url }
|
||||||
|
saveProfile(next)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setAccountError(
|
setAccountError(
|
||||||
|
|||||||
Reference in New Issue
Block a user