104 lines
2.8 KiB
TypeScript
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>
|
|
)
|
|
}
|