aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/routes/home.tsx
diff options
context:
space:
mode:
authorAriel Costas Guerrero <ariel@costas.dev>2026-03-13 17:12:12 +0100
committerAriel Costas Guerrero <ariel@costas.dev>2026-03-13 17:12:12 +0100
commitece17875d4e454423f55f0623a456c0433ecd502 (patch)
tree732c0432cbf32757344c51b8c01bb18e83e9c0c0 /src/frontend/app/routes/home.tsx
parent5c670f1b4a237b7a5197dfcf94de92095da95463 (diff)
feat: integrate geolocation functionality and enhance map interactions
- Added useGeolocation hook to manage user location and permissions. - Updated PlannerOverlay to utilize geolocation for setting origin. - Enhanced NavBar with a new planner route. - Introduced context menu for map interactions to set routes from current location. - Improved search functionality in the map with a dedicated search bar. - Updated localization files with new strings for routing and search features.
Diffstat (limited to 'src/frontend/app/routes/home.tsx')
-rw-r--r--src/frontend/app/routes/home.tsx108
1 files changed, 12 insertions, 96 deletions
diff --git a/src/frontend/app/routes/home.tsx b/src/frontend/app/routes/home.tsx
index 45d7ddf..e71c788 100644
--- a/src/frontend/app/routes/home.tsx
+++ b/src/frontend/app/routes/home.tsx
@@ -1,8 +1,7 @@
-import { Clock, History, Star } from "lucide-react";
+import { Clock, History, MapPin, Star } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
-import { PlannerOverlay } from "~/components/PlannerOverlay";
import { usePageTitle } from "~/contexts/PageTitleContext";
import { usePlanner } from "~/hooks/usePlanner";
import StopItem from "../components/StopItem";
@@ -13,16 +12,12 @@ export default function StopList() {
const { t } = useTranslation();
usePageTitle(t("navbar.stops", "Paradas"));
const navigate = useNavigate();
- const { history, searchRoute, loadRoute } = usePlanner({ autoLoad: false });
+ const { history, loadRoute } = usePlanner({ autoLoad: false });
const [data, setData] = useState<Stop[] | null>(null);
const [loading, setLoading] = useState(true);
const [searchResults, setSearchResults] = useState<Stop[] | null>(null);
const [favouriteStops, setFavouriteStops] = useState<Stop[]>([]);
const [recentStops, setRecentStops] = useState<Stop[]>([]);
- const [userLocation, setUserLocation] = useState<{
- latitude: number;
- longitude: number;
- } | null>(null);
const searchTimeout = useRef<NodeJS.Timeout | null>(null);
const randomPlaceholder = useMemo(
@@ -30,68 +25,6 @@ export default function StopList() {
[t]
);
- const requestUserLocation = useCallback(() => {
- if (typeof window === "undefined" || !("geolocation" in navigator)) {
- return;
- }
-
- navigator.geolocation.getCurrentPosition(
- (position) => {
- setUserLocation({
- latitude: position.coords.latitude,
- longitude: position.coords.longitude,
- });
- },
- (error) => {
- console.warn("Unable to obtain user location", error);
- },
- {
- enableHighAccuracy: false,
- maximumAge: Infinity,
- timeout: 10000,
- }
- );
- }, []);
-
- useEffect(() => {
- if (typeof window === "undefined" || !("geolocation" in navigator)) {
- return;
- }
-
- let permissionStatus: PermissionStatus | null = null;
-
- const handlePermissionChange = () => {
- if (permissionStatus?.state === "granted") {
- requestUserLocation();
- }
- };
-
- const checkPermission = async () => {
- try {
- if (navigator.permissions?.query) {
- permissionStatus = await navigator.permissions.query({
- name: "geolocation",
- });
- if (permissionStatus.state === "granted") {
- requestUserLocation();
- }
- permissionStatus.addEventListener("change", handlePermissionChange);
- } else {
- requestUserLocation();
- }
- } catch (error) {
- console.warn("Geolocation permission check failed", error);
- requestUserLocation();
- }
- };
-
- checkPermission();
-
- return () => {
- permissionStatus?.removeEventListener("change", handlePermissionChange);
- };
- }, [requestUserLocation]);
-
// Load stops from network
const loadStops = useCallback(async () => {
try {
@@ -164,33 +97,16 @@ export default function StopList() {
<div className="flex flex-col gap-4 py-4 pb-8">
{/* Planner Section */}
<div className="w-full px-4">
- <details className="group bg-surface border border-slate-200 dark:border-slate-700 shadow-sm">
- <summary className="list-none cursor-pointer focus:outline-none">
- <div className="flex items-center justify-between p-3 rounded-xl group-open:mb-3 transition-all">
- <div className="flex items-center gap-3">
- <History className="w-5 h-5 text-primary-600 dark:text-primary-400" />
- <span className="font-semibold text-text">
- {t("planner.where_to", "¿A dónde quieres ir?")}
- </span>
- </div>
- <div className="text-muted group-open:rotate-180 transition-transform">
- ↓
- </div>
- </div>
- </summary>
-
- <PlannerOverlay
- inline
- forceExpanded
- cardBackground="bg-transparent"
- userLocation={userLocation}
- autoLoad={false}
- onSearch={(origin, destination, time, arriveBy) => {
- searchRoute(origin, destination, time, arriveBy);
- }}
- onNavigateToPlanner={() => navigate("/planner")}
- />
- </details>
+ <button
+ type="button"
+ onClick={() => navigate("/planner")}
+ className="w-full flex items-center gap-3 p-3 rounded-xl bg-surface border border-slate-200 dark:border-slate-700 shadow-sm hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors text-left"
+ >
+ <MapPin className="w-5 h-5 text-primary-600 dark:text-primary-400 shrink-0" />
+ <span className="font-semibold text-text">
+ {t("planner.where_to", "¿A dónde quieres ir?")}
+ </span>
+ </button>
{history.length > 0 && (
<div className="mt-3 flex flex-col gap-2">