Just to be clear for anyone coming here. This is how it was achieved…
Since we cannot control the headers of the external server, we need to use a proxy to fetch the tiles. This will make the browser think that the tiles are coming from the same origin as our application.
Here’s how I’ll modify the Vite configuration to set up a proxy for the OpenStreetMap tiles:
vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['lucide-react'],
},
server: {
headers: {
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
},
proxy: {
'/osm_tiles': {
target: 'https://c.tile.openstreetmap.org',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/osm_tiles/, ''),
},
}
}
})
MapDisplay.tsx
import React from 'react';
import { MapContainer, TileLayer, Circle, Marker, useMap } from 'react-leaflet';
import { LatLngTuple, LatLng } from 'leaflet';
import { LocationPicker } from '../provider/LocationPicker';
import 'leaflet/dist/leaflet.css';
// MapUpdater component to handle center updates
function MapUpdater({ center }: { center: LatLngTuple }) {
const map = useMap();
React.useEffect(() => {
map.setView(center, map.getZoom());
}, [center, map]);
return null;
}
interface MapDisplayProps {
location: LatLngTuple;
isManualMode: boolean;
onLocationSelect: (latlng: LatLng) => void;
}
export function MapDisplay({ location, isManualMode, onLocationSelect }: MapDisplayProps) {
return (
<div className="bg-white rounded-lg overflow-hidden shadow-lg">
<div style={{ height: '400px' }}>
<MapContainer
center={location}
zoom={13}
style={{ height: '100%', width: '100%' }}
className="z-0"
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="/osm_tiles/{z}/{x}/{y}.png"
/>
<Circle
center={location}
radius={5000}
pathOptions={{
color: 'blue',
fillColor: '#30f',
fillOpacity: 0.1
}}
/>
<Marker position={location} />
{!isManualMode && <LocationPicker onLocationSelect={onLocationSelect} />}
<MapUpdater center={location} />
</MapContainer>
</div>
</div>
);
}
ServiceProviderMap.tsx
import React from 'react';
import { MapContainer, TileLayer, Circle, Marker, Popup } from 'react-leaflet';
import { LatLngTuple } from 'leaflet';
import { fetchProviders, type ServiceProvider } from '../../lib/firebase/providers';
import { toLatLngTuple } from '../../lib/location/types';
import { STANDARD_SEARCH_RADIUS_KM } from '../../constants/search';
import { MapUpdater } from './MapUpdater';
import { ProviderPopup } from './ProviderPopup';
import 'leaflet/dist/leaflet.css';
interface ServiceProviderMapProps {
selectedService: string;
userLocation: LatLngTuple;
searchRadius?: number;
isDevelopmentMode?: boolean;
onlineOnly?: boolean;
onRequestService?: (providerId: string) => void;
}
export function ServiceProviderMap({
selectedService,
userLocation,
searchRadius = STANDARD_SEARCH_RADIUS_KM,
isDevelopmentMode = false,
onlineOnly = false,
onRequestService
}: ServiceProviderMapProps) {
const [providers, setProviders] = React.useState<ServiceProvider[]>([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
const loadProviders = async () => {
try {
setLoading(true);
setError(null);
const providersData = await fetchProviders(
selectedService,
userLocation,
searchRadius,
isDevelopmentMode,
onlineOnly
);
setProviders(providersData);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load providers');
} finally {
setLoading(false);
}
};
loadProviders();
}, [selectedService, userLocation, searchRadius, isDevelopmentMode, onlineOnly]);
const handleRequestService = (providerId: string) => {
if (onRequestService) {
onRequestService(providerId);
}
};
if (loading) {
return (
<div className="h-[400px] flex items-center justify-center bg-gray-50">
<div className="text-gray-600">Loading providers...</div>
</div>
);
}
return (
<div className="space-y-4">
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">{error}</p>
</div>
)}
{!error && providers.length === 0 && (
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-yellow-800">
No service providers available within {searchRadius}km of your location.
Please try again later or contact emergency services if needed.
</p>
</div>
)}
{providers.length > 0 && (
<div className="grid gap-2 mb-4">
<h3 className="text-lg font-semibold text-gray-900">
Available Providers ({providers.length})
</h3>
<p className="text-sm text-gray-600">
Showing {onlineOnly ? 'available ' : ''}providers within {searchRadius}km of your location
</p>
</div>
)}
<div className="rounded-lg overflow-hidden shadow-lg">
<div style={{ height: '400px' }}>
<MapContainer
center={userLocation}
zoom={11}
style={{ height: '100%', width: '100%' }}
className="z-0"
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="/osm_tiles/{z}/{x}/{y}.png"
/>
<Circle
center={userLocation}
radius={searchRadius * 1000}
pathOptions={{
color: isDevelopmentMode ? 'orange' : 'blue',
fillColor: isDevelopmentMode ? '#f97316' : '#30f',
fillOpacity: 0.1
}}
/>
{providers.map((provider) => (
<Marker
key={provider.id}
position={toLatLngTuple(provider.location)}
>
<Popup>
<ProviderPopup
provider={provider}
selectedService={selectedService}
onRequestService={handleRequestService}
/>
</Popup>
</Marker>
))}
<MapUpdater center={userLocation} />
</MapContainer>
</div>
</div>
</div>
);
}
LocationSection.tsx
import React from 'react';
import { LocationPicker } from '../map/LocationPicker';
import { useLocation } from '../../hooks/useLocation';
import { RefreshCw } from 'lucide-react';
import { MapContainer, TileLayer, Circle, Marker, useMap } from 'react-leaflet';
import { LatLngTuple, LatLng } from 'leaflet';
// MapUpdater component to handle center updates
function MapUpdater({ center }: { center: LatLngTuple }) {
const map = useMap();
React.useEffect(() => {
map.setView(center, map.getZoom());
}, [center, map]);
return null;
}
export function LocationSection() {
const {
location,
isManualMode,
address,
error,
loading,
setManualLocation,
toggleMode,
updateCurrentLocation,
} = useLocation();
if (loading) {
return (
<div className="animate-pulse">
<div className="h-[400px] bg-gray-200 rounded-lg"></div>
</div>
);
}
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Current Location</h2>
<p className="text-sm text-gray-600">
This is your current location for roadside assistance. It can be different from your home address.
</p>
{error && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">{error}</p>
</div>
)}
{address && (
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg flex justify-between items-center">
<p className="text-blue-800">
<strong>Current Location:</strong> {address}
</p>
{!isManualMode && (
<button
onClick={updateCurrentLocation}
className="p-2 hover:bg-blue-100 rounded-full transition-colors"
title="Refresh current location"
>
<RefreshCw className="h-5 w-5 text-blue-600" />
</button>
)}
</div>
)}
<div className="h-[400px] rounded-lg overflow-hidden shadow-lg">
<MapContainer
center={location}
zoom={13}
style={{ height: '100%', width: '100%' }}
className="z-0"
>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="/osm_tiles/{z}/{x}/{y}.png"
/>
<Circle
center={location}
radius={5000}
pathOptions={{
color: 'blue',
fillColor: '#30f',
fillOpacity: 0.1
}}
/>
<Marker position={location} />
{!isManualMode && <LocationPicker onLocationSelect={setManualLocation} />}
<MapUpdater center={location} />
</MapContainer>
</div>
</div>
);
}
Hope that helps someone.