import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { Gift, Home, MessageCircle, MessageCircleQuestion, Star, Trophy, Users } from "lucide-react"; import { Link } from "react-router-dom"; import { readStoredVisitorContext, visitorEventUrl } from "../types/visitorContext"; type HyperNavigationFABProps = { /** Preferred; falls back to stored visitor context `eventId`. */ eventId?: string; /** When set (e.g. on session page), matches Audience Questions “View All” (`/questions?event_id&session_id`). */ sessionId?: string; /** When true, add Feedback to the menu (session page + event allows feedback + session flag). */ showFeedbackNav?: boolean; /** When true, add Ratings to the menu (session page + event ratings on + session flag). */ showRatingsNav?: boolean; /** When set with showFeedbackNav, append form_id to the feedback link. */ feedbackFormId?: string; /** When set with showRatingsNav, append form_id to the ratings link. */ ratingsFormId?: string; }; type MenuItem = { label: string; to: string; icon: ReactNode; }; /** Fixed to viewport bottom-right, aligned with `MobileShell` max width (402px). Opens actions above the FAB. */ export function HyperNavigationFAB({ eventId: eventIdProp, sessionId: sessionIdProp, showFeedbackNav = false, showRatingsNav = false, feedbackFormId, ratingsFormId, }: HyperNavigationFABProps) { const ctx = readStoredVisitorContext(); const eventId = (eventIdProp ?? ctx?.eventId ?? "").trim(); const sessionId = (sessionIdProp ?? ctx?.sessionId ?? "").trim(); const [open, setOpen] = useState(false); const wrapRef = useRef(null); const eventHome = eventId ? visitorEventUrl(eventId) : "/"; const rewards = eventId ? `/rewards?${new URLSearchParams({ event_id: eventId }).toString()}` : "/rewards"; const leaderboard = eventId ? `/leaderboard?${new URLSearchParams({ event_id: eventId }).toString()}` : "/leaderboard"; const allQuestionsQs = new URLSearchParams(); if (eventId) allQuestionsQs.set("event_id", eventId); if (sessionId) allQuestionsQs.set("session_id", sessionId); const allQuestions = eventId ? `/questions?${allQuestionsQs.toString()}` : "/questions"; const feedbackQs = new URLSearchParams(); if (eventId) feedbackQs.set("event_id", eventId); if (sessionId) feedbackQs.set("session_id", sessionId); if (feedbackFormId?.trim()) feedbackQs.set("form_id", feedbackFormId.trim()); const feedbackTo = eventId ? `/feedback?${feedbackQs.toString()}` : "/feedback"; const ratingsQs = new URLSearchParams(); if (eventId) ratingsQs.set("event_id", eventId); if (sessionId) ratingsQs.set("session_id", sessionId); if (ratingsFormId?.trim()) ratingsQs.set("form_id", ratingsFormId.trim()); const ratingsTo = eventId ? `/ratings?${ratingsQs.toString()}` : "/ratings"; const items: MenuItem[] = [ { label: "Event Home", to: eventHome, icon: }, { label: "Rewards", to: rewards, icon: }, { label: "Leaderboard", to: leaderboard, icon: }, ...(showFeedbackNav ? [{ label: "Feedback", to: feedbackTo, icon: }] : []), ...(showRatingsNav ? [{ label: "Ratings", to: ratingsTo, icon: }] : []), { label: "All Questions", to: allQuestions, icon: , }, ]; useEffect(() => { if (!open) return; const onDoc = (e: MouseEvent) => { if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDoc); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); }; }, [open]); const close = useCallback(() => setOpen(false), []); return (
{open ? (
{items.map((item) => ( {item.icon} {item.label} ))}
) : null}
); }