import { useCallback, useEffect, useState } from "react" import { Check, RefreshCw, Trash2 } from "lucide-react" import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client" import { AlertBanner } from "@crema/feedback-ui" import { AppShell } from "~/components/layout/app-shell" import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar" import { Badge } from "~/components/ui/badge" import { Button } from "~/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu" import { Input } from "~/components/ui/input" import { Textarea } from "~/components/ui/textarea" import { useAgents } from "~/lib/agents" import { pageTitle } from "~/lib/page-meta" import { profileInitials, resetProfile, saveProfile, useProfile, type Profile, } from "~/lib/profile" import { getUser, updateUser, type User } from "~/lib/arcadia/users" import { updateSessionUser, useSession } from "~/lib/session" export const meta = () => pageTitle("Profile") interface AccountDraft { first_name: string last_name: string email: string } export default function ProfileRoute() { const session = useSession() const arcadia = useArcadiaClient() const profile = useProfile() const agents = useAgents() // Local preferences (avatar, title, bio, signature, default agent). const [prefs, setPrefs] = useState(profile) const [prefsSavedAt, setPrefsSavedAt] = useState(null) useEffect(() => { setPrefs(profile) }, [profile]) const prefsDirty = JSON.stringify(prefs) !== JSON.stringify(profile) // Arcadia account. const [account, setAccount] = useState(null) const [accountDraft, setAccountDraft] = useState({ first_name: "", last_name: "", email: "", }) const [accountLoading, setAccountLoading] = useState(true) const [accountSaving, setAccountSaving] = useState(false) const [accountSavedAt, setAccountSavedAt] = useState(null) const [accountError, setAccountError] = useState(null) const loadAccount = useCallback(async () => { if (!session) return setAccountLoading(true) setAccountError(null) try { const u = await getUser(arcadia, session.userId) setAccount(u) setAccountDraft({ first_name: u.first_name ?? "", last_name: u.last_name ?? "", email: u.email, }) } catch (err) { setAccountError( err instanceof ArcadiaError ? err.message : "Failed to load account.", ) } finally { setAccountLoading(false) } }, [arcadia, session]) useEffect(() => { loadAccount() }, [loadAccount]) const accountDirty = !!account && (accountDraft.first_name !== (account.first_name ?? "") || accountDraft.last_name !== (account.last_name ?? "") || accountDraft.email !== account.email) const saveAccount = async () => { if (!account) return setAccountSaving(true) setAccountError(null) try { const updated = await updateUser(arcadia, account.id, { first_name: accountDraft.first_name || null, last_name: accountDraft.last_name || null, email: accountDraft.email, }) setAccount(updated) updateSessionUser({ name: updated.full_name, email: updated.email }) setAccountSavedAt(Date.now()) } catch (err) { setAccountError( err instanceof ArcadiaError ? err.message : "Save failed.", ) } finally { setAccountSaving(false) } } // Local prefs handlers. const initials = profileInitials( [accountDraft.first_name, accountDraft.last_name].filter(Boolean).join(" ") || account?.full_name || session?.name || "", ) const onPickAvatar = (file: File | null) => { if (!file) { setPrefs((d) => ({ ...d, avatarUrl: "" })) return } const reader = new FileReader() reader.onload = () => { const result = reader.result if (typeof result === "string") setPrefs((d) => ({ ...d, avatarUrl: result })) } reader.readAsDataURL(file) } const savePrefs = () => { saveProfile(prefs) setPrefsSavedAt(Date.now()) } const defaultAgent = agents.find((a) => a.id === prefs.defaultAgentId) ?? null return ( Account {account?.email_verified ? ( Verified ) : account ? ( Unverified ) : null} {account?.status && account.status !== "active" ? ( {account.status} ) : null} Your arcadia identity. Changes are saved to the platform and reflected anywhere your name or email appears. {accountError ? ( setAccountError(null)} > {accountError} ) : null}
{prefs.avatarUrl ? ( ) : null} {initials}
{account?.full_name || accountDraft.email || "—"} {account ? ( <> Tenant {account.tenant_id} · ID {account.id} Last sign-in{" "} {account.last_sign_in_at ? new Date(account.last_sign_in_at).toLocaleString() : "—"} ) : null}
setAccountDraft((d) => ({ ...d, first_name: e.target.value })) } autoComplete="given-name" disabled={accountLoading || accountSaving} /> setAccountDraft((d) => ({ ...d, last_name: e.target.value })) } autoComplete="family-name" disabled={accountLoading || accountSaving} /> setAccountDraft((d) => ({ ...d, email: e.target.value })) } autoComplete="email" disabled={accountLoading || accountSaving} />
{accountSavedAt && !accountDirty && ( Saved. )}
Preferences Local-only settings stored in this browser — avatar, bio, signature, and the assistant's default persona.
Avatar
{prefs.avatarUrl && ( )}
PNG, JPG, or SVG. Stored locally as a data URL.
setPrefs((d) => ({ ...d, title: e.target.value })) } placeholder="e.g. Platform admin" /> {defaultAgent ? ( <> {defaultAgent.name} {" "} — {defaultAgent.role} ) : ( "Use first available" )} setPrefs((d) => ({ ...d, defaultAgentId: "" })) } data-state={!prefs.defaultAgentId ? "checked" : undefined} > First available {agents.map((a) => ( setPrefs((d) => ({ ...d, defaultAgentId: a.id })) } data-state={ prefs.defaultAgentId === a.id ? "checked" : undefined } className="flex flex-col items-start" > {a.name} {a.role} ))}