From d51169f6411b68a226d76d2d39826904de484929 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Wed, 19 Nov 2025 15:04:55 +0100 Subject: feat: Add About and Favourites pages, update routing and context management - Added new routes for About and Favourites pages. - Implemented About page with version information and credits. - Created Favourites page with a placeholder message for empty favourites. - Refactored RegionConfig import paths for consistency. - Introduced PageTitleContext to manage page titles dynamically. - Updated various components to utilize the new context for setting page titles. - Enhanced AppShell layout with a responsive Drawer for navigation. - Added CSS styles for new components and pages. - Integrated commit hash display in the About page for version tracking. --- src/frontend/app/routes/about.css | 7 ++++ src/frontend/app/routes/about.tsx | 61 +++++++++++++++++++++++++++++++ src/frontend/app/routes/estimates-$id.tsx | 2 +- src/frontend/app/routes/favourites.tsx | 13 +++++++ src/frontend/app/routes/home.tsx | 18 +++++---- src/frontend/app/routes/map.tsx | 26 +++++++------ src/frontend/app/routes/settings.tsx | 51 +++----------------------- src/frontend/app/routes/stops-$id.css | 29 --------------- src/frontend/app/routes/stops-$id.tsx | 30 ++++++--------- src/frontend/app/routes/timetable-$id.tsx | 26 ++++++------- 10 files changed, 137 insertions(+), 126 deletions(-) create mode 100644 src/frontend/app/routes/about.css create mode 100644 src/frontend/app/routes/about.tsx create mode 100644 src/frontend/app/routes/favourites.tsx (limited to 'src/frontend/app/routes') diff --git a/src/frontend/app/routes/about.css b/src/frontend/app/routes/about.css new file mode 100644 index 0000000..8f13015 --- /dev/null +++ b/src/frontend/app/routes/about.css @@ -0,0 +1,7 @@ +.about-version { + margin-top: 2rem; + text-align: center; + color: var(--subtitle-color); + border-top: 1px solid var(--border-color); + padding-top: 1rem; +} diff --git a/src/frontend/app/routes/about.tsx b/src/frontend/app/routes/about.tsx new file mode 100644 index 0000000..d41268d --- /dev/null +++ b/src/frontend/app/routes/about.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { usePageTitle } from "~/contexts/PageTitleContext"; +import { useApp } from "../AppContext"; +import "./about.css"; +import "./settings.css"; // Reusing settings CSS for now + +export default function About() { + const { t } = useTranslation(); + usePageTitle(t("about.title", "Acerca de")); + const { region } = useApp(); + + return ( +
+

{t("about.description")}

+ +

{t("about.credits")}

+

+ + {t("about.github")} + {" "} + - {t("about.developed_by")}{" "} + + Ariel Costas + +

+ {region === "vigo" && ( +

+ {t("about.data_source_prefix")}{" "} + + datos.vigo.org + {" "} + {t("about.data_source_middle")}{" "} + + Open Data Commons Attribution License + + . +

+ )} + +
+ Version: {__COMMIT_HASH__} +
+
+ ); +} diff --git a/src/frontend/app/routes/estimates-$id.tsx b/src/frontend/app/routes/estimates-$id.tsx index 74f24e6..b92e59d 100644 --- a/src/frontend/app/routes/estimates-$id.tsx +++ b/src/frontend/app/routes/estimates-$id.tsx @@ -15,7 +15,7 @@ import { } from "~/components/SchedulesTableSkeleton"; import { StopAlert } from "~/components/StopAlert"; import { TimetableSkeleton } from "~/components/TimetableSkeleton"; -import { type RegionId, getRegionConfig } from "~/data/RegionConfig"; +import { type RegionId, getRegionConfig } from "~/config/RegionConfig"; import { useAutoRefresh } from "~/hooks/useAutoRefresh"; import { useApp } from "../AppContext"; import { GroupedTable } from "../components/GroupedTable"; diff --git a/src/frontend/app/routes/favourites.tsx b/src/frontend/app/routes/favourites.tsx new file mode 100644 index 0000000..5b74391 --- /dev/null +++ b/src/frontend/app/routes/favourites.tsx @@ -0,0 +1,13 @@ +import { useTranslation } from "react-i18next"; +import { usePageTitle } from "~/contexts/PageTitleContext"; + +export default function Favourites() { + const { t } = useTranslation(); + usePageTitle(t("navbar.favourites", "Favoritos")); + + return ( +
+

{t("favourites.empty", "No tienes paradas favoritas.")}

+
+ ); +} diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx index 2909999..8a1e3b3 100644 --- a/src/frontend/app/routes/home.tsx +++ b/src/frontend/app/routes/home.tsx @@ -1,18 +1,20 @@ "use client"; -import { useEffect, useMemo, useRef, useState, useCallback } from "react"; -import StopDataProvider, { type Stop } from "../data/StopDataProvider"; -import StopItem from "../components/StopItem"; -import StopItemSkeleton from "../components/StopItemSkeleton"; -import StopGallery from "../components/StopGallery"; -import ServiceAlerts from "../components/ServiceAlerts"; import Fuse from "fuse.js"; -import "./home.css"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { REGIONS } from "~/config/RegionConfig"; +import { usePageTitle } from "~/contexts/PageTitleContext"; import { useApp } from "../AppContext"; -import { REGIONS } from "~/data/RegionConfig"; +import ServiceAlerts from "../components/ServiceAlerts"; +import StopGallery from "../components/StopGallery"; +import StopItem from "../components/StopItem"; +import StopItemSkeleton from "../components/StopItemSkeleton"; +import StopDataProvider, { type Stop } from "../data/StopDataProvider"; +import "./home.css"; export default function StopList() { const { t } = useTranslation(); + usePageTitle(t("navbar.stops", "Paradas")); const { region } = useApp(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index b8fb881..57fb04e 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -6,17 +6,18 @@ import type { Feature as GeoJsonFeature, Point } from "geojson"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Map, { - GeolocateControl, - Layer, - NavigationControl, - Source, - type MapLayerMouseEvent, - type MapRef, - type StyleSpecification + GeolocateControl, + Layer, + NavigationControl, + Source, + type MapLayerMouseEvent, + type MapRef, + type StyleSpecification } from "react-map-gl/maplibre"; -import { useApp } from "~/AppContext"; import { StopSheet } from "~/components/StopSheet"; -import { REGIONS } from "~/data/RegionConfig"; +import { getRegionConfig } from "~/config/RegionConfig"; +import { usePageTitle } from "~/contexts/PageTitleContext"; +import { useApp } from "../AppContext"; // Default minimal fallback style before dynamic loading const defaultStyle: StyleSpecification = { @@ -30,6 +31,7 @@ const defaultStyle: StyleSpecification = { // Componente principal del mapa export default function StopMap() { const { t } = useTranslation(); + usePageTitle(t("navbar.map", "Mapa")); const [stops, setStops] = useState< GeoJsonFeature< Point, @@ -162,8 +164,8 @@ export default function StopMap() { }} attributionControl={{ compact: false }} maxBounds={ - REGIONS[region].bounds - ? [REGIONS[region].bounds!.sw, REGIONS[region].bounds!.ne] + getRegionConfig(region).bounds + ? [getRegionConfig(region).bounds!.sw, getRegionConfig(region).bounds!.ne] : undefined } > @@ -218,7 +220,7 @@ export default function StopMap() { "text-size": ["interpolate", ["linear"], ["zoom"], 11, 8, 22, 16], }} paint={{ - "text-color": `${REGIONS[region].textColour || "#000"}`, + "text-color": `${getRegionConfig(region).textColour || "#000"}`, "text-halo-color": "#FFF", "text-halo-width": 1, }} diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx index 2134b4c..351ccf0 100644 --- a/src/frontend/app/routes/settings.tsx +++ b/src/frontend/app/routes/settings.tsx @@ -1,12 +1,14 @@ -import { type Theme, useApp } from "../AppContext"; -import "./settings.css"; -import { useTranslation } from "react-i18next"; import { useState } from "react"; -import { getAvailableRegions, REGIONS } from "../data/RegionConfig"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; +import { usePageTitle } from "~/contexts/PageTitleContext"; +import { type Theme, useApp } from "../AppContext"; +import { getAvailableRegions } from "../config/RegionConfig"; +import "./settings.css"; export default function Settings() { const { t, i18n } = useTranslation(); + usePageTitle(t("navbar.settings", "Ajustes")); const navigate = useNavigate(); const { theme, @@ -46,8 +48,6 @@ export default function Settings() { return (
-

{t("about.title")}

-

{t("about.description")}

{t("about.settings")}

@@ -151,45 +151,6 @@ export default function Settings() {
-

{t("about.credits")}

-

- - {t("about.github")} - {" "} - - {t("about.developed_by")}{" "} - - Ariel Costas - -

- {region === "vigo" && ( -

- {t("about.data_source_prefix")}{" "} - - datos.vigo.org - {" "} - {t("about.data_source_middle")}{" "} - - Open Data Commons Attribution License - - . -

- )} {showModal && (
diff --git a/src/frontend/app/routes/stops-$id.css b/src/frontend/app/routes/stops-$id.css index c515435..9ecac16 100644 --- a/src/frontend/app/routes/stops-$id.css +++ b/src/frontend/app/routes/stops-$id.css @@ -1,8 +1,3 @@ -.page-title { - margin-block: 0; - font-size: 1.5rem; -} - .estimates-content-wrapper { display: flex; flex-direction: column; @@ -266,30 +261,6 @@ flex-shrink: 0; } -.experimental-notice { - background-color: #fff3cd; - border: 1px solid #ffc107; - border-radius: 8px; - padding: 0.5rem 1rem; - color: #856404; - flex-shrink: 0; -} - -.experimental-notice strong { - display: block; - color: #856404; -} - -[data-theme="dark"] .experimental-notice { - background-color: #3d3100; - border-color: #ffc107; - color: #ffd966; -} - -[data-theme="dark"] .experimental-notice strong { - color: #ffd966; -} - .refresh-status { display: flex; align-items: center; diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index 372582b..f340009 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -8,7 +8,8 @@ import { StopAlert } from "~/components/StopAlert"; import { StopMap } from "~/components/StopMapSheet"; import { ConsolidatedCirculationList } from "~/components/Stops/ConsolidatedCirculationList"; import { ConsolidatedCirculationListSkeleton } from "~/components/Stops/ConsolidatedCirculationListSkeleton"; -import { type RegionId, getRegionConfig } from "~/data/RegionConfig"; +import { type RegionId, getRegionConfig } from "~/config/RegionConfig"; +import { usePageTitle } from "~/contexts/PageTitleContext"; import { useAutoRefresh } from "~/hooks/useAutoRefresh"; import { useApp } from "../AppContext"; import StopDataProvider, { type Stop } from "../data/StopDataProvider"; @@ -79,6 +80,16 @@ export default function Estimates() { const { region } = useApp(); const regionConfig = getRegionConfig(region); + // Helper function to get the display name for the stop + const getStopDisplayName = useCallback(() => { + if (customName) return customName; + if (stopData?.name.intersect) return stopData.name.intersect; + if (stopData?.name.original) return stopData.name.original; + return `Parada ${stopIdNum}`; + }, [customName, stopData, stopIdNum]); + + usePageTitle(getStopDisplayName()); + const parseError = (error: any): ErrorInfo => { if (!navigator.onLine) { return { type: "network", message: "No internet connection" }; @@ -165,14 +176,6 @@ export default function Estimates() { } }; - // Helper function to get the display name for the stop - const getStopDisplayName = () => { - if (customName) return customName; - if (stopData?.name.intersect) return stopData.name.intersect; - if (stopData?.name.original) return stopData.name.original; - return `Parada ${stopIdNum}`; - }; - const handleRename = () => { const current = getStopDisplayName(); const input = window.prompt("Custom name for this stop:", current); @@ -202,9 +205,6 @@ export default function Estimates() { onClick={handleRename} width={20} />
-

- {getStopDisplayName()} -