aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/components/arrivals/ArrivalCard.tsx
diff options
context:
space:
mode:
authorCopilot <198982749+Copilot@users.noreply.github.com>2026-03-24 20:32:17 +0100
committerGitHub <noreply@github.com>2026-03-24 20:32:17 +0100
commit695c7a65a1e9ab3b95beeaf02a1e3b10bb16996b (patch)
treef302b91a050e3ecfb295b5d16c6ab2962de1a713 /src/frontend/app/components/arrivals/ArrivalCard.tsx
parent757960525576038898d655b630cbaac44671f599 (diff)
feat: client-side trip tracking with browser notifications (#151)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arielcostas <94913521+arielcostas@users.noreply.github.com>
Diffstat (limited to 'src/frontend/app/components/arrivals/ArrivalCard.tsx')
-rw-r--r--src/frontend/app/components/arrivals/ArrivalCard.tsx43
1 files changed, 42 insertions, 1 deletions
diff --git a/src/frontend/app/components/arrivals/ArrivalCard.tsx b/src/frontend/app/components/arrivals/ArrivalCard.tsx
index bdd20a5..9c68a97 100644
--- a/src/frontend/app/components/arrivals/ArrivalCard.tsx
+++ b/src/frontend/app/components/arrivals/ArrivalCard.tsx
@@ -1,4 +1,4 @@
-import { AlertTriangle, BusFront, LocateIcon } from "lucide-react";
+import { AlertTriangle, BusFront, LocateIcon, Navigation } from "lucide-react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import Marquee from "react-fast-marquee";
import { useTranslation } from "react-i18next";
@@ -9,6 +9,8 @@ import "./ArrivalCard.css";
interface ArrivalCardProps {
arrival: Arrival;
onClick?: () => void;
+ onTrack?: () => void;
+ isTracked?: boolean;
}
const AutoMarquee = ({ text }: { text: string }) => {
@@ -57,6 +59,8 @@ const AutoMarquee = ({ text }: { text: string }) => {
export const ArrivalCard: React.FC<ArrivalCardProps> = ({
arrival,
onClick,
+ onTrack,
+ isTracked = false,
}) => {
const { t } = useTranslation();
const {
@@ -287,6 +291,43 @@ export const ArrivalCard: React.FC<ArrivalCardProps> = ({
</span>
);
})}
+
+ {onTrack && estimate.precision !== "past" && (
+ // Use a <span> instead of a <button> here because this element can
+ // be rendered inside a <button> (when isClickable=true), and nested
+ // <button> elements are invalid HTML.
+ <span
+ role="button"
+ tabIndex={0}
+ onClick={(e) => {
+ e.stopPropagation();
+ onTrack();
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ e.stopPropagation();
+ onTrack();
+ }
+ }}
+ aria-label={
+ isTracked
+ ? t("journey.stop_tracking", "Detener seguimiento")
+ : t("journey.track_bus", "Seguir este autobús")
+ }
+ aria-pressed={isTracked}
+ className={`ml-auto text-xs px-2.5 py-0.5 rounded-full flex items-center gap-1 shrink-0 font-medium tracking-wide transition-colors cursor-pointer select-none ${
+ isTracked
+ ? "bg-blue-600 text-white hover:bg-blue-700"
+ : "bg-black/[0.04] dark:bg-white/[0.08] text-slate-500 dark:text-slate-400 hover:bg-blue-100 dark:hover:bg-blue-900/30 hover:text-blue-600 dark:hover:text-blue-400"
+ }`}
+ >
+ <Navigation className="w-3 h-3" />
+ {isTracked
+ ? t("journey.tracking", "Siguiendo")
+ : t("journey.track", "Seguir")}
+ </span>
+ )}
</div>
</Tag>
);