/** * Compact account dropdown in TopBar โ€” shows the signed-in email and offers * Logout. Closes on outside click; menu state is local since nothing else * needs to pop it open. */ import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import useAppStore from '../store/appStore' import { logout as apiLogout } from '../api/auth' export default function UserMenu() { const navigate = useNavigate() const currentUser = useAppStore((s) => s.currentUser) const setCurrentUser = useAppStore((s) => s.setCurrentUser) const [open, setOpen] = useState(false) const panelRef = useRef(null) const buttonRef = useRef(null) useEffect(() => { if (!open) return function onDown(e) { if (panelRef.current?.contains(e.target)) return if (buttonRef.current?.contains(e.target)) return setOpen(false) } document.addEventListener('mousedown', onDown) return () => document.removeEventListener('mousedown', onDown) }, [open]) if (!currentUser) return null // Avatar: first letter of first name (post-Phase 4.6 we collect this on // signup) โ€” falls back to the email initial for any legacy account that // somehow doesn't have one set. const avatar = (currentUser.first_name?.[0] ?? currentUser.email[0] ?? '?').toUpperCase() const displayName = currentUser.first_name ? `${currentUser.first_name} ${currentUser.last_name ?? ''}`.trim() : currentUser.email async function onLogout() { setOpen(false) await apiLogout() // Tear down the active session/project state so the WS effect that // streams /api/projects/{id}/logs sees project.id โ†’ null and unsubscribes, // and so the next login starts on a clean slate instead of a stale tree. const { resetProject, setSessions, setActiveSessionId, setMessages } = useAppStore.getState() resetProject() setSessions([]) setActiveSessionId(null) setMessages([]) setCurrentUser(null) navigate('/', { replace: true }) } return (
{open && (
Signed in as
{displayName}
{currentUser.username && (
@{currentUser.username} ยท {currentUser.email}
)}
)}
) }