diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 15:04:55 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 15:05:34 +0100 |
| commit | d51169f6411b68a226d76d2d39826904de484929 (patch) | |
| tree | 4d8a403dfcc5b17671a92b8cc1e5d71d20ed9537 /src/frontend/app/routes | |
| parent | d434204860fc0409ad6343e815d0057b97ce3573 (diff) | |
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.
Diffstat (limited to 'src/frontend/app/routes')
| -rw-r--r-- | src/frontend/app/routes/about.css | 7 | ||||
| -rw-r--r-- | src/frontend/app/routes/about.tsx | 61 | ||||
| -rw-r--r-- | src/frontend/app/routes/estimates-$id.tsx | 2 | ||||
| -rw-r--r-- | src/frontend/app/routes/favourites.tsx | 13 | ||||
| -rw-r--r-- | src/frontend/app/routes/home.tsx | 18 | ||||
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 26 | ||||
| -rw-r--r-- | src/frontend/app/routes/settings.tsx | 51 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.css | 29 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.tsx | 30 | ||||
| -rw-r--r-- | src/frontend/app/routes/timetable-$id.tsx | 26 |
10 files changed, 137 insertions, 126 deletions
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 ( + <div className="page-container"> + <p className="about-description">{t("about.description")}</p> + + <h2>{t("about.credits")}</h2> + <p> + <a + href="https://github.com/arielcostas/busurbano" + className="about-link" + rel="nofollow noreferrer noopener" + > + {t("about.github")} + </a>{" "} + - {t("about.developed_by")}{" "} + <a + href="https://www.costas.dev" + className="about-link" + rel="nofollow noreferrer noopener" + > + Ariel Costas + </a> + </p> + {region === "vigo" && ( + <p> + {t("about.data_source_prefix")}{" "} + <a + href="https://datos.vigo.org" + className="about-link" + rel="nofollow noreferrer noopener" + > + datos.vigo.org + </a>{" "} + {t("about.data_source_middle")}{" "} + <a + href="https://opendefinition.org/licenses/odc-by/" + className="about-link" + rel="nofollow noreferrer noopener" + > + Open Data Commons Attribution License + </a> + . + </p> + )} + + <div className="about-version"> + <small>Version: {__COMMIT_HASH__}</small> + </div> + </div> + ); +} 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 ( + <div className="page-container"> + <p>{t("favourites.empty", "No tienes paradas favoritas.")}</p> + </div> + ); +} 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<Stop[] | null>(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 ( <div className="page-container"> - <h1 className="page-title">{t("about.title")}</h1> - <p className="about-description">{t("about.description")}</p> <section className="settings-section"> <h2>{t("about.settings")}</h2> <div className="settings-content-inline"> @@ -151,45 +151,6 @@ export default function Settings() { </dl> </details> </section> - <h2>{t("about.credits")}</h2> - <p> - <a - href="https://github.com/arielcostas/busurbano" - className="about-link" - rel="nofollow noreferrer noopener" - > - {t("about.github")} - </a>{" "} - - {t("about.developed_by")}{" "} - <a - href="https://www.costas.dev" - className="about-link" - rel="nofollow noreferrer noopener" - > - Ariel Costas - </a> - </p> - {region === "vigo" && ( - <p> - {t("about.data_source_prefix")}{" "} - <a - href="https://datos.vigo.org" - className="about-link" - rel="nofollow noreferrer noopener" - > - datos.vigo.org - </a>{" "} - {t("about.data_source_middle")}{" "} - <a - href="https://opendefinition.org/licenses/odc-by/" - className="about-link" - rel="nofollow noreferrer noopener" - > - Open Data Commons Attribution License - </a> - . - </p> - )} {showModal && ( <div className="modal-overlay" onClick={cancelRegionChange}> 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} /> </div> - <h1 className="page-title"> - {getStopDisplayName()} - </h1> <button className="manual-refresh-button" @@ -230,12 +230,6 @@ export default function Estimates() { {stopData && <StopAlert stop={stopData} />} - <div className="experimental-notice"> - <strong> - {t("estimates.experimental_feature", "Experimental feature")} - </strong> - </div> - <div className="estimates-list-container"> {dataLoading ? ( <ConsolidatedCirculationListSkeleton /> diff --git a/src/frontend/app/routes/timetable-$id.tsx b/src/frontend/app/routes/timetable-$id.tsx index 8a1cba7..c036cb3 100644 --- a/src/frontend/app/routes/timetable-$id.tsx +++ b/src/frontend/app/routes/timetable-$id.tsx @@ -1,21 +1,21 @@ -import { useEffect, useState, useRef } from "react"; -import { useParams, Link } from "react-router"; -import StopDataProvider from "../data/StopDataProvider"; import { - ArrowLeft, - Eye, - EyeOff, - ChevronUp, - ChevronDown, - Clock, + ArrowLeft, + ChevronDown, + ChevronUp, + Clock, + Eye, + EyeOff, } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Link, useParams } from "react-router"; +import { useApp } from "~/AppContext"; +import { ErrorDisplay } from "~/components/ErrorDisplay"; import { type ScheduledTable } from "~/components/SchedulesTable"; import { TimetableSkeleton } from "~/components/TimetableSkeleton"; -import { ErrorDisplay } from "~/components/ErrorDisplay"; +import { type RegionId, getRegionConfig } from "~/config/RegionConfig"; import LineIcon from "../components/LineIcon"; -import { useTranslation } from "react-i18next"; -import { type RegionId, getRegionConfig } from "~/data/RegionConfig"; -import { useApp } from "~/AppContext"; +import StopDataProvider from "../data/StopDataProvider"; import "./timetable-$id.css"; interface ErrorInfo { |
