v1.10.0: top-bar Log out (ISS-157) + Admin button gated to admins (ISS-158)
CI / build + unit + e2e (pull_request) Successful in 6m18s
CI / build + unit + e2e (pull_request) Successful in 6m18s
This commit is contained in:
+22
-5
@@ -14,7 +14,7 @@ import { approxLocalTime } from '../lib/localTime';
|
||||
import Modal from './Modal';
|
||||
import AdminPanel from './AdminPanel';
|
||||
import Leaderboard from './Leaderboard';
|
||||
import { sessionHeaders } from '../lib/authClient';
|
||||
import { clearStoredSession, sessionHeaders } from '../lib/authClient';
|
||||
|
||||
// v0.70.0 default view: null island [0,0] at zoom 2 — the world is 2048 px wide there and the
|
||||
// Africa+Europe bounding box (lat −35..+71, lon −18..+52) spans ~800 px vertically in Web Mercator,
|
||||
@@ -74,11 +74,13 @@ interface MapProps {
|
||||
// v1.1.0 (ISS-107): the admin panel's Log out cleared the session — tell the shell to drop back
|
||||
// to the sign-in screen instead of leaving a signed-out user staring at a live dashboard.
|
||||
onLogout?: () => void;
|
||||
// v1.10.0 (ISS-158): /api/me says this session is an admin → show the top-bar Admin button.
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
// Named FlightMap (not Map): a component called `Map` would shadow the global
|
||||
// Map constructor inside its own body, breaking `new Map(...)` for the airport index.
|
||||
export default function FlightMap({ state, hovered, applySnapshot, onLogout }: MapProps) {
|
||||
export default function FlightMap({ state, hovered, applySnapshot, onLogout, isAdmin = false }: MapProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const mapRef = useRef<maplibregl.Map | null>(null);
|
||||
// /api/airports is fetched at most once per page life: every styledata event
|
||||
@@ -569,6 +571,14 @@ export default function FlightMap({ state, hovered, applySnapshot, onLogout }: M
|
||||
void localAssetsAvailable().then((available) => mapRef.current?.setStyle(styleUrlFor(id, available)));
|
||||
};
|
||||
|
||||
// v1.10.0 (ISS-157): top-bar Log out — same three steps as the admin panel's button: best-effort
|
||||
// server-side session kill, drop the stored id, tell the shell to show sign-in again.
|
||||
const onTopBarLogout = async () => {
|
||||
await fetch('/api/logout', { method: 'POST', headers: sessionHeaders() }).catch(() => undefined);
|
||||
clearStoredSession();
|
||||
onLogout?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="map-wrap">
|
||||
<header data-testid="app-header" className="app-header">
|
||||
@@ -596,9 +606,16 @@ export default function FlightMap({ state, hovered, applySnapshot, onLogout }: M
|
||||
<button type="button" data-testid="leaderboard-btn" className="pane-btn" onClick={() => setLeaderboardOpen(true)}>
|
||||
Leaderboard
|
||||
</button>
|
||||
{/* v0.71.0: admin panel entry point — right side of the top bar, opens a modal (no URL). */}
|
||||
<button type="button" data-testid="admin-btn" className="pane-btn" onClick={() => setAdminOpen(true)}>
|
||||
Admin
|
||||
{/* v0.71.0: admin panel entry point — right side of the top bar, opens a modal (no URL).
|
||||
v1.10.0 (ISS-158): only for admins (/api/me `admin` flag) — hidden entirely otherwise. */}
|
||||
{isAdmin && (
|
||||
<button type="button" data-testid="admin-btn" className="pane-btn" onClick={() => setAdminOpen(true)}>
|
||||
Admin
|
||||
</button>
|
||||
)}
|
||||
{/* v1.10.0 (ISS-157): Log out — last child, so the flex row puts it top-right of the bar. */}
|
||||
<button type="button" data-testid="logout-btn" className="pane-btn" onClick={() => void onTopBarLogout()}>
|
||||
Log out
|
||||
</button>
|
||||
</header>
|
||||
<div ref={containerRef} data-testid="map" className="map-container" />
|
||||
|
||||
Reference in New Issue
Block a user