Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
102 lines
3.0 KiB
TypeScript
102 lines
3.0 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
import Link from 'next/link'
|
|
import {
|
|
authText,
|
|
notifyText,
|
|
overviewText,
|
|
profileText,
|
|
themeText,
|
|
} from '@/lib/i18n/shell'
|
|
import { ThemeToggle } from '@/components/ThemeToggle'
|
|
|
|
/**
|
|
* Konto und Einstellungen, oben rechts.
|
|
*
|
|
* Alles, was den einzelnen Nutzer betrifft und nicht die Akten, sammelt sich
|
|
* hinter dem Profilbild: das Thema, die Benachrichtigungen, der eigene Zugang,
|
|
* das Abmelden. In der Kopfzeile standen diese Dinge vorher zwischen den
|
|
* Aktenbereichen und nahmen dort Platz weg, den die Arbeit braucht.
|
|
*
|
|
* Das Abmelden kommt als Server Action von aussen herein - dieses Fenster darf
|
|
* die Sitzung nicht selbst beenden können.
|
|
*/
|
|
export function ProfileMenu({
|
|
displayName,
|
|
username,
|
|
avatar,
|
|
signOutAction,
|
|
}: {
|
|
displayName: string
|
|
username: string
|
|
avatar: ReactNode
|
|
signOutAction: () => Promise<void>
|
|
}) {
|
|
const [offen, setOffen] = useState(false)
|
|
const huelle = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!offen) return
|
|
|
|
function daneben(event: PointerEvent) {
|
|
if (!huelle.current?.contains(event.target as Node)) setOffen(false)
|
|
}
|
|
function fluchttaste(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') setOffen(false)
|
|
}
|
|
|
|
document.addEventListener('pointerdown', daneben)
|
|
document.addEventListener('keydown', fluchttaste)
|
|
return () => {
|
|
document.removeEventListener('pointerdown', daneben)
|
|
document.removeEventListener('keydown', fluchttaste)
|
|
}
|
|
}, [offen])
|
|
|
|
return (
|
|
<div ref={huelle} className="relative">
|
|
<button
|
|
type="button"
|
|
onClick={() => setOffen((vorher) => !vorher)}
|
|
aria-expanded={offen}
|
|
aria-haspopup="menu"
|
|
aria-label={profileText.open}
|
|
className="btn btn-sm p-1"
|
|
>
|
|
{avatar}
|
|
</button>
|
|
|
|
{offen ? (
|
|
<div
|
|
role="menu"
|
|
className="absolute top-full right-0 z-40 mt-2 w-64 border border-line bg-panel p-4"
|
|
>
|
|
<p className="font-condensed text-xs text-text">{displayName}</p>
|
|
<p className="font-mono text-2xs text-text-dim">@{username}</p>
|
|
|
|
<div className="mt-4 flex items-center justify-between gap-3 border-t border-line pt-3">
|
|
<span className="font-condensed text-2xs text-text-dim">{themeText.label}</span>
|
|
<ThemeToggle />
|
|
</div>
|
|
|
|
<nav className="mt-3 flex flex-col items-start gap-2 border-t border-line pt-3">
|
|
<Link href="/einstellungen" className="btn btn-sm" onClick={() => setOffen(false)}>
|
|
{notifyText.heading}
|
|
</Link>
|
|
<Link href="/zugang" className="btn btn-sm" onClick={() => setOffen(false)}>
|
|
{overviewText.heading}
|
|
</Link>
|
|
</nav>
|
|
|
|
<form action={signOutAction} className="mt-3 border-t border-line pt-3">
|
|
<button type="submit" className="btn btn-sm btn-primary">
|
|
{authText.signOut}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|