feat: Add OpenStreetMap display

This commit is contained in:
gpt-engineer-app[bot]
2025-09-29 13:35:33 +00:00
parent a7d18d6264
commit 90ba2c21ce
4 changed files with 111 additions and 1 deletions

49
package-lock.json generated
View File

@@ -39,18 +39,21 @@
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@supabase/supabase-js": "^2.57.4", "@supabase/supabase-js": "^2.57.4",
"@tanstack/react-query": "^5.83.0", "@tanstack/react-query": "^5.83.0",
"@types/leaflet": "^1.9.20",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"leaflet": "^1.9.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.61.1", "react-hook-form": "^7.61.1",
"react-leaflet": "^5.0.0",
"react-resizable-panels": "^2.1.9", "react-resizable-panels": "^2.1.9",
"react-router-dom": "^6.30.1", "react-router-dom": "^6.30.1",
"recharts": "^2.15.4", "recharts": "^2.15.4",
@@ -2503,6 +2506,17 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@react-leaflet/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
"license": "Hippocratic-2.1",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.23.0", "version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
@@ -3168,6 +3182,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -3175,6 +3195,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/leaflet": {
"version": "1.9.20",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
"license": "MIT",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.16.5", "version": "22.16.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz",
@@ -4942,6 +4971,12 @@
"json-buffer": "3.0.1" "json-buffer": "3.0.1"
} }
}, },
"node_modules/leaflet": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -6075,6 +6110,20 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-leaflet": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
"integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
"license": "Hippocratic-2.1",
"dependencies": {
"@react-leaflet/core": "^3.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",

View File

@@ -42,18 +42,21 @@
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@supabase/supabase-js": "^2.57.4", "@supabase/supabase-js": "^2.57.4",
"@tanstack/react-query": "^5.83.0", "@tanstack/react-query": "^5.83.0",
"@types/leaflet": "^1.9.20",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"leaflet": "^1.9.4",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.61.1", "react-hook-form": "^7.61.1",
"react-leaflet": "^5.0.0",
"react-resizable-panels": "^2.1.9", "react-resizable-panels": "^2.1.9",
"react-router-dom": "^6.30.1", "react-router-dom": "^6.30.1",
"recharts": "^2.15.4", "recharts": "^2.15.4",

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import type { LatLngExpression } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
// Fix for default markers in react-leaflet
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
interface ParkLocationMapProps {
latitude: number;
longitude: number;
parkName: string;
className?: string;
}
export function ParkLocationMap({ latitude, longitude, parkName, className }: ParkLocationMapProps) {
const position: LatLngExpression = [latitude, longitude];
return (
<div className={`w-full h-64 rounded-lg overflow-hidden border border-border ${className}`}>
<MapContainer
center={position}
zoom={15}
style={{ height: '100%', width: '100%' }}
zoomControl={true}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={position}>
<Popup>
<div className="text-center">
<strong>{parkName}</strong>
</div>
</Popup>
</Marker>
</MapContainer>
</div>
);
}

View File

@@ -10,6 +10,7 @@ import { MapPin, Star, Clock, Phone, Globe, Calendar, ArrowLeft, Users, Zap, Cam
import { ReviewsSection } from '@/components/reviews/ReviewsSection'; import { ReviewsSection } from '@/components/reviews/ReviewsSection';
import { RideCard } from '@/components/rides/RideCard'; import { RideCard } from '@/components/rides/RideCard';
import { Park, Ride } from '@/types/database'; import { Park, Ride } from '@/types/database';
import { ParkLocationMap } from '@/components/maps/ParkLocationMap';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
export default function ParkDetail() { export default function ParkDetail() {
const { const {
@@ -334,13 +335,23 @@ export default function ParkDetail() {
<Separator /> <Separator />
<div className="space-y-2"> <div className="space-y-4">
<div className="font-medium">Location</div> <div className="font-medium">Location</div>
{park.location && <div className="text-sm text-muted-foreground space-y-1"> {park.location && <div className="text-sm text-muted-foreground space-y-1">
{park.location.city && <div>{park.location.city}</div>} {park.location.city && <div>{park.location.city}</div>}
{park.location.state_province && <div>{park.location.state_province}</div>} {park.location.state_province && <div>{park.location.state_province}</div>}
<div>{park.location.country}</div> <div>{park.location.country}</div>
</div>} </div>}
{park.location?.latitude && park.location?.longitude && (
<div className="mt-4">
<ParkLocationMap
latitude={Number(park.location.latitude)}
longitude={Number(park.location.longitude)}
parkName={park.name}
/>
</div>
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>