From afa65c7a6d12a96925b76405621d7f201f8c8d4a Mon Sep 17 00:00:00 2001 From: "Alejandro E. Rendon" Date: Fri, 24 Jul 2026 08:36:08 -0500 Subject: [PATCH] Add Schedule Room Directions Dialog component: Implemented a new dialog for displaying room directions with geolocation support, integrated into the user schedule component. Updated localization for room map features in English and Spanish. --- .../schedule-room-directions-dialog.tsx | 164 ++++++++++++++++++ .../user-schedule-02/user-schedule-02.tsx | 29 +++- src/lib/i18n/blocks-en.ts | 14 ++ src/lib/i18n/blocks-es.ts | 14 ++ src/lib/schedule-rooms.ts | 67 +++++++ 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/components/blocks/schedule/schedule-room-directions-dialog.tsx create mode 100644 src/lib/schedule-rooms.ts diff --git a/src/components/blocks/schedule/schedule-room-directions-dialog.tsx b/src/components/blocks/schedule/schedule-room-directions-dialog.tsx new file mode 100644 index 0000000..3ef7937 --- /dev/null +++ b/src/components/blocks/schedule/schedule-room-directions-dialog.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { + ExternalLinkIcon, + LoaderCircleIcon, + MapPinnedIcon, +} from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useTranslations } from "@/contexts/language-context"; +import { + getScheduleRoomDirectionsUrl, + getScheduleRoomLocation, + getScheduleRoomMapEmbedUrl, +} from "@/lib/schedule-rooms"; + +type ScheduleRoomDirectionsDialogProps = { + room: string | null; + open: boolean; + onOpenChange: (open: boolean) => void; +}; + +type GeolocationState = + | { status: "idle" } + | { status: "loading" } + | { status: "ready"; lat: number; lng: number } + | { status: "denied" | "unavailable"; message: string }; + +function ScheduleRoomDirectionsDialog({ + room, + open, + onOpenChange, +}: ScheduleRoomDirectionsDialogProps) { + const { t } = useTranslations(); + const [geo, setGeo] = useState({ status: "idle" }); + + const location = useMemo( + () => (room ? getScheduleRoomLocation(room) : undefined), + [room], + ); + + useEffect(() => { + if (!open || !location) { + setGeo({ status: "idle" }); + return; + } + + if (!navigator.geolocation) { + setGeo({ + status: "unavailable", + message: t("blocks.scheduleUi.roomMap.geoUnavailable"), + }); + return; + } + + let cancelled = false; + setGeo({ status: "loading" }); + + navigator.geolocation.getCurrentPosition( + (position) => { + if (cancelled) return; + setGeo({ + status: "ready", + lat: position.coords.latitude, + lng: position.coords.longitude, + }); + }, + (error) => { + if (cancelled) return; + setGeo({ + status: + error.code === error.PERMISSION_DENIED ? "denied" : "unavailable", + message: + error.code === error.PERMISSION_DENIED + ? t("blocks.scheduleUi.roomMap.geoDenied") + : t("blocks.scheduleUi.roomMap.geoUnavailable"), + }); + }, + { + enableHighAccuracy: true, + timeout: 12_000, + maximumAge: 60_000, + }, + ); + + return () => { + cancelled = true; + }; + }, [location, open, t]); + + if (!location) { + return null; + } + + const directionsUrl = getScheduleRoomDirectionsUrl( + location, + geo.status === "ready" ? { lat: geo.lat, lng: geo.lng } : undefined, + ); + + return ( + + +
+ + + + {location.name} + + + {t("blocks.scheduleUi.roomMap.description")} + + + + {geo.status === "loading" || geo.status === "idle" ? ( +

+ + {t("blocks.scheduleUi.roomMap.locating")} +

+ ) : geo.status === "ready" ? ( +

+ {t("blocks.scheduleUi.roomMap.located")} +

+ ) : ( +

{geo.message}

+ )} +
+ +
+