You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
4.3 KiB
150 lines
4.3 KiB
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { useEffect, useRef, useState, useTransition } from "react";
|
|
import type { PopiartUser } from "@/lib/popiart-api";
|
|
import type { Locale } from "@/lib/site-content";
|
|
|
|
function replaceLocale(pathname: string, locale: Locale) {
|
|
const parts = pathname.split("/");
|
|
if (parts[1] === "zh" || parts[1] === "en") {
|
|
parts[1] = locale;
|
|
return parts.join("/") || `/${locale}`;
|
|
}
|
|
return `/${locale}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
|
|
}
|
|
|
|
function userLabel(user: PopiartUser) {
|
|
return user.name || user.email || user.id;
|
|
}
|
|
|
|
function initials(user: PopiartUser) {
|
|
const source = user.name || user.email || user.id || "PA";
|
|
return source.slice(0, 2).toUpperCase();
|
|
}
|
|
|
|
export function HeaderControls({
|
|
locale,
|
|
user,
|
|
labels,
|
|
loginHref,
|
|
}: {
|
|
locale: Locale;
|
|
user: PopiartUser | null;
|
|
labels: {
|
|
zh: string;
|
|
en: string;
|
|
logout: string;
|
|
loggingOut: string;
|
|
loginHint: string;
|
|
login: string;
|
|
};
|
|
loginHref: string;
|
|
}) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
const [isPending, startTransition] = useTransition();
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
function handlePointerDown(event: MouseEvent) {
|
|
if (!menuRef.current?.contains(event.target as Node)) {
|
|
setMenuOpen(false);
|
|
}
|
|
}
|
|
|
|
function handleEscape(event: KeyboardEvent) {
|
|
if (event.key === "Escape") {
|
|
setMenuOpen(false);
|
|
}
|
|
}
|
|
|
|
window.addEventListener("mousedown", handlePointerDown);
|
|
window.addEventListener("keydown", handleEscape);
|
|
|
|
return () => {
|
|
window.removeEventListener("mousedown", handlePointerDown);
|
|
window.removeEventListener("keydown", handleEscape);
|
|
};
|
|
}, []);
|
|
|
|
function switchLocale(target: Locale) {
|
|
setMenuOpen(false);
|
|
router.push(replaceLocale(pathname, target));
|
|
}
|
|
|
|
function logout() {
|
|
startTransition(async () => {
|
|
await fetch("/api/auth/logout", {
|
|
method: "POST",
|
|
});
|
|
router.refresh();
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className="header-actions">
|
|
<div className="locale-switch" ref={menuRef}>
|
|
<button
|
|
aria-expanded={menuOpen}
|
|
aria-haspopup="menu"
|
|
className={`locale-menu-button ${menuOpen ? "locale-menu-button-open" : ""}`}
|
|
onClick={() => setMenuOpen((open) => !open)}
|
|
type="button"
|
|
>
|
|
<span>{locale === "zh" ? labels.zh : labels.en}</span>
|
|
<span aria-hidden="true" className={`locale-menu-chevron ${menuOpen ? "locale-menu-chevron-open" : ""}`}>
|
|
▾
|
|
</span>
|
|
</button>
|
|
{menuOpen ? (
|
|
<div className="locale-menu-list" role="menu">
|
|
<button
|
|
aria-pressed={locale === "zh"}
|
|
className={`locale-menu-item ${locale === "zh" ? "locale-menu-item-active" : ""}`}
|
|
onClick={() => switchLocale("zh")}
|
|
role="menuitemradio"
|
|
type="button"
|
|
>
|
|
{labels.zh}
|
|
</button>
|
|
<button
|
|
aria-pressed={locale === "en"}
|
|
className={`locale-menu-item ${locale === "en" ? "locale-menu-item-active" : ""}`}
|
|
onClick={() => switchLocale("en")}
|
|
role="menuitemradio"
|
|
type="button"
|
|
>
|
|
{labels.en}
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{user ? (
|
|
<div className="auth-cluster">
|
|
<Link className="session-pill" href={`/${locale}/console`} title={userLabel(user)}>
|
|
<span className="session-avatar">{initials(user)}</span>
|
|
<span className="session-name">{userLabel(user)}</span>
|
|
</Link>
|
|
<button
|
|
className="button button-light button-small button-auth-stable"
|
|
disabled={isPending}
|
|
onClick={logout}
|
|
type="button"
|
|
>
|
|
{isPending ? labels.loggingOut : labels.logout}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="auth-cluster">
|
|
<Link className="button button-light button-small button-auth-stable" href={loginHref}>
|
|
{labels.login}
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|