Files
FictionArchive/fictionarchive-web/src/components/AuthenticationDisplay.tsx
2025-11-24 13:25:29 -05:00

104 lines
2.8 KiB
TypeScript

import { useEffect, useMemo, useRef, useState } from 'react'
import { useAuth } from '../auth/AuthContext'
import { Button } from './ui/button'
export function AuthenticationDisplay() {
const { user, isConfigured, isLoading, login, logout } = useAuth()
const [isOpen, setIsOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
const email = useMemo(
() =>
user?.profile?.email ??
user?.profile?.preferred_username ??
user?.profile?.name ??
user?.profile?.sub ??
null,
[user],
)
useEffect(() => {
if (!isOpen) return
const handleClickOutside = (event: MouseEvent) => {
if (!menuRef.current) return
if (!menuRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') setIsOpen(false)
}
document.addEventListener('mousedown', handleClickOutside)
document.addEventListener('keydown', handleEscape)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
document.removeEventListener('keydown', handleEscape)
}
}, [isOpen])
if (isLoading) {
return (
<Button variant="outline" size="sm" disabled>
Loading...
</Button>
)
}
if (!isConfigured) {
return (
<Button
variant="secondary"
size="sm"
onClick={() =>
alert('OIDC is not configured. Set VITE_OIDC_* environment variables to enable login.')
}
>
Configure OIDC
</Button>
)
}
if (!user) {
return (
<Button variant="secondary" size="sm" onClick={login}>
Login
</Button>
)
}
return (
<div className="relative" ref={menuRef}>
<Button
variant="ghost"
size="sm"
className="flex items-center gap-2 text-foreground"
onClick={() => setIsOpen((open) => !open)}
aria-expanded={isOpen}
aria-haspopup="menu"
>
<span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary">
{(email ?? 'User').slice(0, 1).toUpperCase()}
</span>
<span className="max-w-[12ch] truncate">{email ?? 'User'}</span>
</Button>
{isOpen && (
<div className="absolute right-0 top-full mt-2 w-56 rounded-lg border bg-white p-2 shadow-lg">
<div className="px-3 py-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Signed in
</p>
<p className="truncate text-sm font-medium text-foreground">{email ?? 'User'}</p>
</div>
<Button variant="ghost" size="sm" className="w-full justify-start" onClick={logout}>
Log out
</Button>
</div>
)}
</div>
)
}