import { Bell, Map, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "react-router"; import RouteIcon from "~/components/RouteIcon"; import { useJourney } from "~/contexts/JourneyContext"; import { useStopArrivals } from "~/hooks/useArrivals"; /** * A sticky banner rendered at the bottom of the AppShell (above the nav bar) * while a journey is being tracked. Shows live minutes-remaining count and * lets the user cancel tracking. */ export function ActiveJourneyBanner() { const { t } = useTranslation(); const { activeJourney, stopJourney } = useJourney(); const { data } = useStopArrivals( activeJourney?.stopId ?? "", false, !!activeJourney ); const [permissionState, setPermissionState] = useState< NotificationPermission | "unsupported" >(() => { if (typeof Notification === "undefined") return "unsupported"; return Notification.permission; }); // Request notification permission the first time a journey starts const hasRequestedRef = useRef(false); useEffect(() => { if (!activeJourney || hasRequestedRef.current) return; if ( typeof Notification === "undefined" || Notification.permission !== "default" ) return; hasRequestedRef.current = true; Notification.requestPermission().then((perm) => { setPermissionState(perm); }); }, [activeJourney]); if (!activeJourney) return null; const liveArrival = data?.arrivals.find( (a) => a.tripId === activeJourney.tripId ); const minutes = liveArrival?.estimate.minutes; const precision = liveArrival?.estimate.precision; const minutesLabel = minutes == null ? "–" : minutes <= 0 ? t("journey.arriving_now", "¡Llegando!") : t("journey.minutes_away", { defaultValue: "{{minutes}} min", minutes, }); const isApproaching = minutes != null && minutes <= activeJourney.notifyAtMinutes && precision !== "past"; return (
{/* Clickable body — navigates to the stop and opens the map for the tracked trip */}

{activeJourney.headsignDestination ?? t("journey.tracking_bus", "Siguiendo autobús")}

{activeJourney.stopName} {" · "} {minutesLabel}

{permissionState === "denied" && ( )} {/* Cancel button — absolutely positioned so it doesn't nest inside the Link */}
); }