54 lines
1.3 KiB
TypeScript
54 lines
1.3 KiB
TypeScript
import { useRef, useState, type PointerEvent } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { AdminTokenDialog } from "./AdminTokenDialog";
|
|
|
|
const LOGO_CLICKS = 5;
|
|
const LOGO_CLICK_WINDOW_MS = 2000;
|
|
|
|
export function BrandLogo() {
|
|
const navigate = useNavigate();
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
const clicks = useRef({ count: 0, lastAt: 0 });
|
|
|
|
const onLogoPointerDown = (e: PointerEvent<HTMLImageElement>) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const now = Date.now();
|
|
if (now - clicks.current.lastAt > LOGO_CLICK_WINDOW_MS) {
|
|
clicks.current.count = 0;
|
|
}
|
|
clicks.current.lastAt = now;
|
|
clicks.current.count += 1;
|
|
|
|
if (clicks.current.count >= LOGO_CLICKS) {
|
|
clicks.current.count = 0;
|
|
setDialogOpen(true);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<img
|
|
className="brand-logo"
|
|
src="/logo.png"
|
|
alt=""
|
|
width={40}
|
|
height={40}
|
|
decoding="async"
|
|
onPointerDown={onLogoPointerDown}
|
|
/>
|
|
{dialogOpen ? (
|
|
<AdminTokenDialog
|
|
onClose={() => setDialogOpen(false)}
|
|
onSuccess={() => {
|
|
setDialogOpen(false);
|
|
window.dispatchEvent(new Event("modelping:admin-auth"));
|
|
void navigate("/admin", { replace: true });
|
|
}}
|
|
/>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|