These were aspirational placeholders — stored to localStorage but never read by anything. Removed from the form, types, and persistence layer. Local profile is now just the avatar URL mirror, which the appbar reads before the server profile fetch resolves on mount. Preferences card renamed to "Avatar" since that's all that's left. Re-add server-backed if/when something actually consumes them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
2.2 KiB
TypeScript
89 lines
2.2 KiB
TypeScript
// Local mirror of the resolved avatar URL, so the appbar can render the
|
|
// avatar before the profile fetch resolves on next mount. The real
|
|
// profile (name, email, bio, phone, location, timezone, avatar) is
|
|
// server-backed — see ~/lib/arcadia/profiles.ts.
|
|
|
|
import { useEffect, useSyncExternalStore } from "react"
|
|
|
|
export type Profile = {
|
|
avatarUrl: string
|
|
}
|
|
|
|
export const DEFAULT_PROFILE: Profile = {
|
|
avatarUrl: "",
|
|
}
|
|
|
|
const STORAGE_KEY = "crema.profile"
|
|
const CHANGE_EVENT = "crema:profile-change"
|
|
|
|
function readFromStorage(): Profile {
|
|
if (typeof window === "undefined") return DEFAULT_PROFILE
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY)
|
|
if (!raw) return DEFAULT_PROFILE
|
|
const parsed = JSON.parse(raw) as Partial<Profile>
|
|
return {
|
|
avatarUrl:
|
|
typeof parsed.avatarUrl === "string"
|
|
? parsed.avatarUrl
|
|
: DEFAULT_PROFILE.avatarUrl,
|
|
}
|
|
} catch {
|
|
return DEFAULT_PROFILE
|
|
}
|
|
}
|
|
|
|
export function loadProfile(): Profile {
|
|
return readFromStorage()
|
|
}
|
|
|
|
export function saveProfile(next: Profile) {
|
|
if (typeof window === "undefined") return
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(next))
|
|
window.dispatchEvent(new CustomEvent(CHANGE_EVENT))
|
|
}
|
|
|
|
export function resetProfile() {
|
|
saveProfile(DEFAULT_PROFILE)
|
|
}
|
|
|
|
export function profileInitials(name: string): string {
|
|
const words = name.trim().split(/\s+/).filter(Boolean)
|
|
if (words.length === 0) return "?"
|
|
if (words.length === 1) return words[0].slice(0, 2).toUpperCase()
|
|
return (words[0][0] + words[words.length - 1][0]).toUpperCase()
|
|
}
|
|
|
|
let cached: Profile | null = null
|
|
|
|
function subscribe(cb: () => void): () => void {
|
|
const onChange = () => {
|
|
cached = null
|
|
cb()
|
|
}
|
|
window.addEventListener(CHANGE_EVENT, onChange)
|
|
window.addEventListener("storage", (e) => {
|
|
if (e.key === STORAGE_KEY) onChange()
|
|
})
|
|
return () => {
|
|
window.removeEventListener(CHANGE_EVENT, onChange)
|
|
}
|
|
}
|
|
|
|
function getSnapshot(): Profile {
|
|
if (!cached) cached = readFromStorage()
|
|
return cached
|
|
}
|
|
|
|
function getServerSnapshot(): Profile {
|
|
return DEFAULT_PROFILE
|
|
}
|
|
|
|
export function useProfile(): Profile {
|
|
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
useEffect(() => {
|
|
cached = null
|
|
}, [])
|
|
return value
|
|
}
|