[FA-18] Frontend bootstrapped
This commit is contained in:
103
fictionarchive-web/src/components/AuthenticationDisplay.tsx
Normal file
103
fictionarchive-web/src/components/AuthenticationDisplay.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user