Skip to content

Commit 5495159

Browse files
committed
Merge branch 'dev'
2 parents ce24f46 + f4505a9 commit 5495159

File tree

3 files changed

+183
-35
lines changed

3 files changed

+183
-35
lines changed

app/components/map/MapPicker.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from 'react';
44
import { MapPin, Crosshair, RotateCcw } from 'lucide-react';
55
import { Button } from '../ui/button';
66
import { useTheme } from 'next-themes';
7+
import { createTileLayer, getDefaultMapView, logMapError, testMapTileAccess } from '../../utils/mapConfig';
78

89
// Leaflet imports - we'll import these dynamically to avoid SSR issues
910
let L: any = null;
@@ -92,33 +93,23 @@ const MapPicker: React.FC<MapPickerProps> = ({
9293
dragging: allowPanning,
9394
});
9495

95-
// Add tile layer with theme support
96+
// Add tile layer with theme support and error handling
9697
const isDarkMode = resolvedTheme === 'dark';
97-
const tileUrl = isDarkMode
98-
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
99-
: 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png';
10098

101-
L.tileLayer(tileUrl, {
102-
attribution: '© OpenStreetMap contributors © CARTO',
103-
maxZoom: 19,
104-
}).addTo(map);
99+
// Test tile accessibility first
100+
const tilesAccessible = await testMapTileAccess(isDarkMode);
101+
if (!tilesAccessible) {
102+
console.warn('Map tiles may not be accessible, but proceeding anyway');
103+
}
105104

106-
// Simple logic: use location if available, otherwise default view
107-
const hasLocation = location && typeof location.lat === 'number' && typeof location.lng === 'number';
105+
const tileLayer = createTileLayer(L, isDarkMode);
106+
tileLayer.addTo(map);
108107

109-
let centerLat, centerLng, zoom;
108+
// Use centralized map view logic
109+
const mapView = getDefaultMapView(location);
110+
const zoom = initialZoomRef.current || mapView.zoom;
110111

111-
if (hasLocation) {
112-
centerLat = location.lat;
113-
centerLng = location.lng;
114-
zoom = initialZoomRef.current || location.zoom || 15;
115-
} else {
116-
centerLat = 0.0;
117-
centerLng = 0.0;
118-
zoom = initialZoomRef.current || 1;
119-
}
120-
121-
map.setView([centerLat, centerLng], zoom);
112+
map.setView(mapView.center, zoom);
122113

123114
// Add marker if location exists
124115
if (hasLocation) {
@@ -197,7 +188,12 @@ const MapPicker: React.FC<MapPickerProps> = ({
197188
setIsLoading(false);
198189

199190
} catch (err: any) {
200-
console.error('Error initializing map:', err);
191+
logMapError('MapPicker initialization', err, {
192+
hasLocation: !!location,
193+
theme: resolvedTheme,
194+
readOnly,
195+
height
196+
});
201197
setError('Failed to initialize map. Please try refreshing the page.');
202198
setIsLoading(false);
203199
}

app/components/utils/UserMapTab.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Button } from '../ui/button';
66
import { PillLink } from './PillLink';
77
import { useRouter } from 'next/navigation';
88
import { useTheme } from 'next-themes';
9+
import { createTileLayer, getDefaultMapView, logMapError, testMapTileAccess } from '../../utils/mapConfig';
910

1011
interface Location {
1112
lat: number;
@@ -83,22 +84,27 @@ function MultiLocationMap({ pages, center, zoom, onPageClick }: MultiLocationMap
8384
touchZoom: true
8485
}).setView([center.lat, center.lng], zoom);
8586

86-
// Add tile layer with theme support
87-
const tileUrl = resolvedTheme === 'dark'
88-
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
89-
: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
87+
// Add tile layer with theme support and error handling
88+
const isDarkMode = resolvedTheme === 'dark';
9089

91-
L.tileLayer(tileUrl, {
92-
attribution: resolvedTheme === 'dark'
93-
? '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
94-
: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
95-
maxZoom: 19
96-
}).addTo(map);
90+
// Test tile accessibility first
91+
const tilesAccessible = await testMapTileAccess(isDarkMode);
92+
if (!tilesAccessible) {
93+
console.warn('Map tiles may not be accessible, but proceeding anyway');
94+
}
95+
96+
const tileLayer = createTileLayer(L, isDarkMode);
97+
tileLayer.addTo(map);
9798

9899
mapInstanceRef.current = map;
99100
setIsLoading(false);
100101
} catch (err) {
101-
console.error('Error initializing map:', err);
102+
logMapError('MultiLocationMap initialization', err, {
103+
pagesCount: pages.length,
104+
center,
105+
zoom,
106+
theme: resolvedTheme
107+
});
102108
setError('Failed to load map');
103109
setIsLoading(false);
104110
}
@@ -281,7 +287,8 @@ export default function UserMapTab({ userId, username }: UserMapTabProps) {
281287
// Calculate center point for map view
282288
const mapCenter = React.useMemo(() => {
283289
if (pages.length === 0) {
284-
return { lat: 40.7128, lng: -74.0060 }; // Default to NYC
290+
const defaultView = getDefaultMapView();
291+
return { lat: defaultView.center[0], lng: defaultView.center[1] };
285292
}
286293

287294
if (pages.length === 1) {

app/utils/mapConfig.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Centralized map configuration for consistent tile providers and error handling
3+
*/
4+
5+
export interface MapTileConfig {
6+
url: string;
7+
attribution: string;
8+
maxZoom: number;
9+
errorTileUrl?: string;
10+
}
11+
12+
/**
13+
* Get the appropriate tile configuration based on theme and environment
14+
*/
15+
export function getMapTileConfig(isDarkMode: boolean = false): MapTileConfig {
16+
// Check if we have a Mapbox token for premium tiles
17+
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN;
18+
19+
if (mapboxToken && mapboxToken !== 'your-mapbox-token') {
20+
// Use Mapbox tiles (more reliable for production)
21+
const styleId = isDarkMode ? 'dark-v11' : 'streets-v12';
22+
return {
23+
url: `https://api.mapbox.com/styles/v1/mapbox/${styleId}/tiles/{z}/{x}/{y}?access_token=${mapboxToken}`,
24+
attribution: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
25+
maxZoom: 22,
26+
errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' // 1x1 transparent PNG
27+
};
28+
}
29+
30+
// Fallback to OpenStreetMap-based tiles
31+
if (isDarkMode) {
32+
// Use CartoDB dark tiles for dark mode
33+
return {
34+
url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
35+
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
36+
maxZoom: 19,
37+
errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='
38+
};
39+
} else {
40+
// Use OpenStreetMap tiles for light mode with fallback
41+
return {
42+
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
43+
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
44+
maxZoom: 19,
45+
errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='
46+
};
47+
}
48+
}
49+
50+
/**
51+
* Create a tile layer with error handling and fallback
52+
*/
53+
export function createTileLayer(L: any, isDarkMode: boolean = false) {
54+
const config = getMapTileConfig(isDarkMode);
55+
56+
const tileLayer = L.tileLayer(config.url, {
57+
attribution: config.attribution,
58+
maxZoom: config.maxZoom,
59+
errorTileUrl: config.errorTileUrl,
60+
// Add retry logic for failed tiles
61+
retryDelay: 1000,
62+
retryLimit: 3,
63+
});
64+
65+
// Add error handling
66+
tileLayer.on('tileerror', function(error: any) {
67+
console.warn('Map tile failed to load:', {
68+
url: error.tile.src,
69+
coords: error.coords,
70+
error: error.error
71+
});
72+
73+
// Try to reload the tile after a delay
74+
setTimeout(() => {
75+
if (error.tile && error.tile.src) {
76+
error.tile.src = error.tile.src + '?retry=' + Date.now();
77+
}
78+
}, 2000);
79+
});
80+
81+
tileLayer.on('tileload', function() {
82+
// Tiles are loading successfully
83+
console.debug('Map tiles loading successfully');
84+
});
85+
86+
return tileLayer;
87+
}
88+
89+
/**
90+
* Get default map center and zoom based on location availability
91+
*/
92+
export function getDefaultMapView(location?: { lat: number; lng: number; zoom?: number }) {
93+
if (location && typeof location.lat === 'number' && typeof location.lng === 'number') {
94+
return {
95+
center: [location.lat, location.lng] as [number, number],
96+
zoom: location.zoom || 15
97+
};
98+
}
99+
100+
// Default to world view
101+
return {
102+
center: [20, 0] as [number, number], // Slightly north to show more land
103+
zoom: 2
104+
};
105+
}
106+
107+
/**
108+
* Enhanced error logging for map issues
109+
*/
110+
export function logMapError(context: string, error: any, additionalInfo?: any) {
111+
console.error(`Map Error [${context}]:`, {
112+
error: error.message || error,
113+
stack: error.stack,
114+
timestamp: new Date().toISOString(),
115+
userAgent: typeof window !== 'undefined' ? window.navigator.userAgent : 'server',
116+
url: typeof window !== 'undefined' ? window.location.href : 'server',
117+
additionalInfo
118+
});
119+
}
120+
121+
/**
122+
* Check if map tiles are accessible
123+
*/
124+
export async function testMapTileAccess(isDarkMode: boolean = false): Promise<boolean> {
125+
if (typeof window === 'undefined') return true; // Skip on server
126+
127+
const config = getMapTileConfig(isDarkMode);
128+
const testUrl = config.url
129+
.replace('{s}', 'a')
130+
.replace('{z}', '1')
131+
.replace('{x}', '0')
132+
.replace('{y}', '0')
133+
.replace('{r}', '');
134+
135+
try {
136+
const response = await fetch(testUrl, {
137+
method: 'HEAD',
138+
mode: 'no-cors' // Avoid CORS issues for testing
139+
});
140+
return true; // If we get here, the request didn't fail immediately
141+
} catch (error) {
142+
console.warn('Map tile accessibility test failed:', error);
143+
return false;
144+
}
145+
}

0 commit comments

Comments
 (0)