| import React, { useEffect, useRef, useState } from 'react' |
|
|
| interface MapModalProps { |
| isOpen: boolean |
| onClose: () => void |
| address: string |
| restaurantName: string |
| coordinates?: { |
| latitude: number |
| longitude: number |
| } |
| } |
|
|
| |
| declare global { |
| interface Window { |
| google: any |
| initGoogleMaps: () => void |
| } |
| } |
|
|
| export function MapModal({ isOpen, onClose, address, restaurantName, coordinates }: MapModalProps) { |
| const mapRef = useRef<HTMLDivElement>(null) |
| const mapInstanceRef = useRef<any>(null) |
| const markersRef = useRef<any[]>([]) |
| const infoWindowRef = useRef<any>(null) |
| const directionsRendererRef = useRef<any>(null) |
| const [error, setError] = useState<string | null>(null) |
| const [geocodedLocation, setGeocodedLocation] = useState<{ lat: number; lng: number } | null>(null) |
| const [isGeocoding, setIsGeocoding] = useState(false) |
| const [userLocation, setUserLocation] = useState<{ lat: number; lng: number } | null>(null) |
| const [isGoogleMapsLoaded, setIsGoogleMapsLoaded] = useState(false) |
| const [placeDetails, setPlaceDetails] = useState<any>(null) |
| const [apiKey, setApiKey] = useState<string | null>(null) |
|
|
| |
| useEffect(() => { |
| if (!isOpen || !address) return |
| |
| |
| if (coordinates) { |
| setGeocodedLocation({ lat: coordinates.latitude, lng: coordinates.longitude }) |
| setIsGeocoding(false) |
| return |
| } |
|
|
| |
| if (!isGoogleMapsLoaded) return |
|
|
| |
| const geocodeAddress = async () => { |
| setIsGeocoding(true) |
| setError(null) |
| |
| try { |
| const google = window.google |
| if (!google || !google.maps || !google.maps.Geocoder) { |
| throw new Error('Google Maps not loaded') |
| } |
|
|
| const geocoder = new google.maps.Geocoder() |
| |
| geocoder.geocode({ address: address }, (results: any[], status: string) => { |
| if (status === 'OK' && results && results.length > 0) { |
| const location = results[0].geometry.location |
| setGeocodedLocation({ |
| lat: location.lat(), |
| lng: location.lng() |
| }) |
| setError(null) |
| } else { |
| throw new Error('Address not found') |
| } |
| setIsGeocoding(false) |
| }) |
| } catch (err) { |
| console.error('Geocoding error:', err) |
| setError('Unable to locate address on map') |
| setGeocodedLocation(null) |
| setIsGeocoding(false) |
| } |
| } |
|
|
| geocodeAddress() |
| }, [isOpen, address, coordinates, isGoogleMapsLoaded]) |
|
|
| |
| useEffect(() => { |
| if (!isOpen) return |
|
|
| if (navigator.geolocation) { |
| navigator.geolocation.getCurrentPosition( |
| (position) => { |
| setUserLocation({ |
| lat: position.coords.latitude, |
| lng: position.coords.longitude |
| }) |
| }, |
| (err) => { |
| console.warn('Geolocation error:', err) |
| |
| } |
| ) |
| } |
| }, [isOpen]) |
|
|
| |
| useEffect(() => { |
| if (!isOpen) return |
|
|
| const loadApiKey = async () => { |
| |
| let key = import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '' |
| |
| |
| if (!key) { |
| try { |
| const BASE_URL = import.meta.env.VITE_API_BASE_URL || |
| (import.meta.env.PROD ? '' : 'http://localhost:8000') |
| const response = await fetch(`${BASE_URL}/api/config`) |
| if (response.ok) { |
| const config = await response.json() |
| key = config.googleMapsApiKey || '' |
| } |
| } catch (err) { |
| console.warn('Failed to load config from backend:', err) |
| } |
| } |
|
|
| if (!key) { |
| setError('Google Maps API key is not configured. Please set VITE_GOOGLE_MAPS_API_KEY environment variable.') |
| return |
| } |
|
|
| setApiKey(key) |
| } |
|
|
| loadApiKey() |
| }, [isOpen]) |
|
|
| |
| useEffect(() => { |
| if (!isOpen || !apiKey) return |
|
|
| |
| if (window.google && window.google.maps) { |
| setIsGoogleMapsLoaded(true) |
| return |
| } |
|
|
| |
| if (document.querySelector('script[src*="maps.googleapis.com"]')) { |
| |
| const checkLoaded = setInterval(() => { |
| if (window.google && window.google.maps) { |
| setIsGoogleMapsLoaded(true) |
| clearInterval(checkLoaded) |
| } |
| }, 100) |
| return () => clearInterval(checkLoaded) |
| } |
|
|
| |
| const script = document.createElement('script') |
| script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=geometry,places` |
| script.async = true |
| script.defer = true |
| script.onload = () => { |
| setIsGoogleMapsLoaded(true) |
| } |
| script.onerror = () => { |
| setError('Failed to load Google Maps') |
| } |
| document.head.appendChild(script) |
|
|
| |
| return () => { |
| |
| } |
| }, [isOpen, apiKey]) |
|
|
| |
| useEffect(() => { |
| if (!isOpen || !mapRef.current || !isGoogleMapsLoaded) return |
| |
| if (!coordinates && !geocodedLocation && isGeocoding) return |
|
|
| |
| const finalLocation = coordinates |
| ? { lat: coordinates.latitude, lng: coordinates.longitude } |
| : geocodedLocation |
|
|
| |
| if (!finalLocation) return |
|
|
| const google = window.google |
| if (!google || !google.maps) return |
|
|
| |
| if (mapInstanceRef.current) { |
| markersRef.current.forEach(marker => marker.setMap(null)) |
| markersRef.current = [] |
| } |
|
|
| |
| let centerLat = finalLocation.lat |
| let centerLng = finalLocation.lng |
| let zoom = 15 |
|
|
| |
| if (userLocation) { |
| centerLat = (finalLocation.lat + userLocation.lat) / 2 |
| centerLng = (finalLocation.lng + userLocation.lng) / 2 |
| zoom = 13 |
| } |
|
|
| |
| const map = new google.maps.Map(mapRef.current, { |
| center: { lat: centerLat, lng: centerLng }, |
| zoom: zoom, |
| mapTypeControl: true, |
| streetViewControl: true, |
| fullscreenControl: true |
| }) |
|
|
| |
| const restaurantMarker = new google.maps.Marker({ |
| position: { lat: finalLocation.lat, lng: finalLocation.lng }, |
| map: map, |
| title: restaurantName |
| }) |
|
|
| |
| const restaurantInfoWindow = new google.maps.InfoWindow({ |
| content: `<div style="padding: 12px; min-width: 250px;"> |
| <div style="font-weight: 600; font-size: 16px; margin-bottom: 4px;">${restaurantName}</div> |
| <div style="color: #666; font-size: 14px;">${address}</div> |
| <div style="margin-top: 8px; color: #666; font-size: 12px;">Loading details...</div> |
| </div>` |
| }) |
| |
| |
| restaurantInfoWindow.open(map, restaurantMarker) |
| infoWindowRef.current = restaurantInfoWindow |
|
|
| |
| const searchPlaceDetails = () => { |
| if (!google.maps.places) { |
| |
| updateInfoWindow(null) |
| return |
| } |
|
|
| const service = new google.maps.places.PlacesService(map) |
| const request = { |
| query: `${restaurantName}, ${address}`, |
| fields: ['name', 'formatted_address', 'rating', 'user_ratings_total', 'price_level', |
| 'opening_hours', 'photos', 'place_id', 'website', 'formatted_phone_number', 'reviews'] |
| } |
|
|
| service.textSearch(request, (results: any[], status: string) => { |
| if (status === google.maps.places.PlacesServiceStatus.OK && results && results.length > 0) { |
| const place = results[0] |
| |
| |
| const placeId = place.place_id |
| const detailsRequest = { |
| placeId: placeId, |
| fields: ['name', 'formatted_address', 'rating', 'user_ratings_total', 'price_level', |
| 'opening_hours', 'photos', 'website', 'formatted_phone_number', 'reviews', |
| 'geometry', 'url'] |
| } |
|
|
| service.getDetails(detailsRequest, (placeDetailsResult: any, detailsStatus: string) => { |
| if (detailsStatus === google.maps.places.PlacesServiceStatus.OK && placeDetailsResult) { |
| setPlaceDetails(placeDetailsResult) |
| updateInfoWindow(placeDetailsResult) |
| } else { |
| |
| setPlaceDetails(place) |
| updateInfoWindow(place) |
| } |
| }) |
| } else { |
| |
| console.log('Place search failed:', status) |
| updateInfoWindow(null) |
| } |
| }) |
| } |
|
|
| |
| const updateInfoWindow = (place: any) => { |
| let content = `<div style="padding: 0; max-width: 300px;">` |
| |
| |
| content += `<div style="padding: 12px 16px; border-bottom: 1px solid #e0e0e0;"> |
| <div style="font-weight: 600; font-size: 16px; margin-bottom: 4px; color: #1a1a1a;">${restaurantName}</div> |
| <div style="color: #666; font-size: 14px;">${address}</div> |
| </div>` |
|
|
| |
| if (place) { |
| content += `<div style="padding: 12px 16px;">` |
| |
| |
| if (place.rating) { |
| const stars = '★'.repeat(Math.round(place.rating)) |
| const ratingColor = place.rating >= 4.0 ? '#0f9d58' : place.rating >= 3.0 ? '#fbbc04' : '#ea4335' |
| content += `<div style="margin-bottom: 8px; display: flex; align-items: center; gap: 8px;"> |
| <span style="color: ${ratingColor}; font-size: 18px;">${stars}</span> |
| <span style="font-weight: 600; font-size: 14px;">${place.rating.toFixed(1)}</span> |
| ${place.user_ratings_total ? `<span style="color: #666; font-size: 12px;">(${place.user_ratings_total.toLocaleString()} reviews)</span>` : ''} |
| </div>` |
| } |
|
|
| |
| if (place.price_level !== undefined) { |
| const priceSymbols = '$'.repeat(place.price_level) |
| content += `<div style="margin-bottom: 8px; color: #666; font-size: 14px;"> |
| Price: <span style="font-weight: 600;">${priceSymbols}</span> |
| </div>` |
| } |
|
|
| |
| if (place.opening_hours && place.opening_hours.weekday_text) { |
| const isOpen = place.opening_hours.isOpen() |
| content += `<div style="margin-bottom: 8px;"> |
| <div style="font-weight: 600; font-size: 14px; color: ${isOpen ? '#0f9d58' : '#ea4335'};"> |
| ${isOpen ? '● Open now' : '● Closed'} |
| </div> |
| <div style="color: #666; font-size: 12px; margin-top: 2px;"> |
| ${place.opening_hours.weekday_text[new Date().getDay()] || ''} |
| </div> |
| </div>` |
| } |
|
|
| |
| if (place.photos && place.photos.length > 0) { |
| const photoUrl = place.photos[0].getUrl({ maxWidth: 300, maxHeight: 200 }) |
| content += `<div style="margin-bottom: 8px;"> |
| <img src="${photoUrl}" alt="${restaurantName}" style="width: 100%; border-radius: 4px; object-fit: cover; height: 120px;" /> |
| </div>` |
| } |
|
|
| content += `</div>` |
|
|
| |
| content += `<div style="padding: 8px 16px; border-top: 1px solid #e0e0e0; display: flex; gap: 8px;">` |
| |
| |
| const directionsUrl = `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(address)}` |
| content += `<a href="${directionsUrl}" target="_blank" style="flex: 1; padding: 8px; background: #4285f4; color: white; text-decoration: none; text-align: center; border-radius: 4px; font-size: 14px; font-weight: 500;"> |
| Directions |
| </a>` |
| |
| |
| if (place.url) { |
| content += `<a href="${place.url}" target="_blank" style="flex: 1; padding: 8px; background: #f1f3f4; color: #1a1a1a; text-decoration: none; text-align: center; border-radius: 4px; font-size: 14px; font-weight: 500;"> |
| View |
| </a>` |
| } |
| |
| content += `</div>` |
| } else { |
| |
| content += `<div style="padding: 12px 16px;"> |
| <a href="https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${restaurantName}, ${address}`)}" target="_blank" style="display: inline-block; padding: 8px 16px; background: #4285f4; color: white; text-decoration: none; border-radius: 4px; font-size: 14px; font-weight: 500;"> |
| View in Google Maps |
| </a> |
| </div>` |
| } |
|
|
| content += `</div>` |
|
|
| restaurantInfoWindow.setContent(content) |
| restaurantInfoWindow.open(map, restaurantMarker) |
| } |
|
|
| |
| searchPlaceDetails() |
|
|
| |
| restaurantMarker.addListener('click', () => { |
| restaurantInfoWindow.open(map, restaurantMarker) |
| }) |
|
|
| markersRef.current.push(restaurantMarker) |
|
|
| |
| if (userLocation) { |
| const userMarker = new google.maps.Marker({ |
| position: { lat: userLocation.lat, lng: userLocation.lng }, |
| map: map, |
| title: 'Your Location' |
| }) |
|
|
| const userInfoWindow = new google.maps.InfoWindow({ |
| content: '<div style="padding: 8px;"><strong>Your Location</strong></div>' |
| }) |
| userMarker.addListener('click', () => { |
| userInfoWindow.open(map, userMarker) |
| }) |
| markersRef.current.push(userMarker) |
|
|
| |
| const directionsService = new google.maps.DirectionsService() |
| const directionsRenderer = new google.maps.DirectionsRenderer({ |
| map: map, |
| suppressMarkers: true, |
| preserveViewport: false, |
| polylineOptions: { |
| strokeColor: '#4285f4', |
| strokeWeight: 5, |
| strokeOpacity: 0.8 |
| } |
| }) |
|
|
| directionsRendererRef.current = directionsRenderer |
|
|
| |
| directionsService.route( |
| { |
| origin: { lat: userLocation.lat, lng: userLocation.lng }, |
| destination: { lat: finalLocation.lat, lng: finalLocation.lng }, |
| travelMode: google.maps.TravelMode.DRIVING |
| }, |
| (result: any, status: string) => { |
| if (status === 'OK') { |
| directionsRenderer.setDirections(result) |
| |
| |
| if (result.routes && result.routes[0] && result.routes[0].bounds) { |
| map.fitBounds(result.routes[0].bounds, { padding: 50 }) |
| } else { |
| |
| const bounds = new google.maps.LatLngBounds() |
| bounds.extend({ lat: userLocation.lat, lng: userLocation.lng }) |
| bounds.extend({ lat: finalLocation.lat, lng: finalLocation.lng }) |
| map.fitBounds(bounds, { padding: 50 }) |
| } |
| } else { |
| console.error('Directions request failed:', status) |
| |
| const bounds = new google.maps.LatLngBounds() |
| bounds.extend({ lat: userLocation.lat, lng: userLocation.lng }) |
| bounds.extend({ lat: finalLocation.lat, lng: finalLocation.lng }) |
| map.fitBounds(bounds, { padding: 50 }) |
| } |
| } |
| ) |
| } else { |
| |
| map.setCenter({ lat: finalLocation.lat, lng: finalLocation.lng }) |
| map.setZoom(15) |
| } |
|
|
| mapInstanceRef.current = map |
|
|
| |
| return () => { |
| |
| markersRef.current.forEach(marker => marker.setMap(null)) |
| markersRef.current = [] |
| |
| |
| if (directionsRendererRef.current) { |
| directionsRendererRef.current.setMap(null) |
| directionsRendererRef.current = null |
| } |
| } |
| }, [isOpen, coordinates, geocodedLocation, isGeocoding, userLocation, address, restaurantName, isGoogleMapsLoaded]) |
|
|
| |
| const zoomToRestaurant = () => { |
| if (!mapInstanceRef.current) return |
| |
| const finalLocation = coordinates |
| ? { lat: coordinates.latitude, lng: coordinates.longitude } |
| : geocodedLocation |
|
|
| if (finalLocation) { |
| mapInstanceRef.current.setCenter({ lat: finalLocation.lat, lng: finalLocation.lng }) |
| mapInstanceRef.current.setZoom(15) |
| } |
| } |
|
|
| |
| const zoomToUser = () => { |
| if (!mapInstanceRef.current || !userLocation) return |
| |
| mapInstanceRef.current.setCenter({ lat: userLocation.lat, lng: userLocation.lng }) |
| mapInstanceRef.current.setZoom(15) |
| } |
|
|
| if (!isOpen) return null |
|
|
| return ( |
| <> |
| {/* Backdrop */} |
| <div |
| onClick={onClose} |
| style={{ |
| position: 'fixed', |
| top: 0, |
| left: 0, |
| right: 0, |
| bottom: 0, |
| backgroundColor: 'rgba(0, 0, 0, 0.5)', |
| zIndex: 9998, |
| backdropFilter: 'blur(2px)' |
| }} |
| /> |
| {/* Modal Container - Floating Window */} |
| <div |
| style={{ |
| position: 'fixed', |
| top: '50%', |
| left: '50%', |
| transform: 'translate(-50%, -50%)', |
| width: '90%', |
| maxWidth: '800px', |
| height: '70vh', |
| maxHeight: '600px', |
| backgroundColor: 'var(--card-bg)', |
| borderRadius: 'var(--radius-lg)', |
| boxShadow: '0 10px 40px rgba(0, 0, 0, 0.2)', |
| zIndex: 9999, |
| display: 'flex', |
| flexDirection: 'column', |
| overflow: 'hidden', |
| border: '1px solid var(--border)' |
| }} |
| > |
| {/* Header */} |
| <div |
| style={{ |
| padding: '16px 20px', |
| borderBottom: '1px solid var(--border)', |
| display: 'flex', |
| justifyContent: 'space-between', |
| alignItems: 'center', |
| backgroundColor: 'var(--card-bg)', |
| flexShrink: 0 |
| }} |
| > |
| <div style={{ flex: 1, minWidth: 0 }}> |
| <h3 style={{ margin: 0, color: 'var(--fg)', fontSize: '1.1em', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> |
| {restaurantName} |
| </h3> |
| <p style={{ margin: '4px 0 0 0', color: 'var(--fg-secondary)', fontSize: '0.875em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}> |
| {address} |
| </p> |
| </div> |
| <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginLeft: '16px' }}> |
| {/* Legend - Simplified for Google Maps default markers */} |
| <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}> |
| <button |
| onClick={zoomToRestaurant} |
| style={{ |
| background: 'linear-gradient(135deg, rgba(179, 122, 76, 0.95) 0%, rgba(157, 107, 66, 0.95) 100%)', |
| backdropFilter: 'blur(10px)', |
| border: '1px solid rgba(179, 122, 76, 0.3)', |
| cursor: 'pointer', |
| padding: '10px 16px', |
| borderRadius: '12px', |
| transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', |
| color: 'white', |
| fontSize: '0.875em', |
| fontWeight: 600, |
| display: 'flex', |
| alignItems: 'center', |
| gap: '8px', |
| boxShadow: '0 4px 12px rgba(179, 122, 76, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1)', |
| position: 'relative', |
| overflow: 'hidden' |
| }} |
| onMouseEnter={(e) => { |
| e.currentTarget.style.background = 'linear-gradient(135deg, rgba(179, 122, 76, 1) 0%, rgba(157, 107, 66, 1) 100%)' |
| e.currentTarget.style.transform = 'translateY(-2px) scale(1.02)' |
| e.currentTarget.style.boxShadow = '0 6px 20px rgba(179, 122, 76, 0.35), 0 4px 8px rgba(0, 0, 0, 0.15)' |
| }} |
| onMouseLeave={(e) => { |
| e.currentTarget.style.background = 'linear-gradient(135deg, rgba(179, 122, 76, 0.95) 0%, rgba(157, 107, 66, 0.95) 100%)' |
| e.currentTarget.style.transform = 'translateY(0) scale(1)' |
| e.currentTarget.style.boxShadow = '0 4px 12px rgba(179, 122, 76, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1)' |
| }} |
| title="Click to zoom to restaurant" |
| > |
| <span style={{ fontSize: '1.2em', lineHeight: 1, filter: 'drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2))' }}>🍽️</span> |
| <span style={{ letterSpacing: '0.3px' }}>Restaurant</span> |
| </button> |
| {userLocation && ( |
| <button |
| onClick={zoomToUser} |
| style={{ |
| background: 'linear-gradient(135deg, rgba(66, 133, 244, 0.95) 0%, rgba(53, 122, 232, 0.95) 100%)', |
| backdropFilter: 'blur(10px)', |
| border: '1px solid rgba(66, 133, 244, 0.3)', |
| cursor: 'pointer', |
| padding: '10px 16px', |
| borderRadius: '12px', |
| transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', |
| color: 'white', |
| fontSize: '0.875em', |
| fontWeight: 600, |
| display: 'flex', |
| alignItems: 'center', |
| gap: '8px', |
| boxShadow: '0 4px 12px rgba(66, 133, 244, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1)', |
| position: 'relative', |
| overflow: 'hidden' |
| }} |
| onMouseEnter={(e) => { |
| e.currentTarget.style.background = 'linear-gradient(135deg, rgba(66, 133, 244, 1) 0%, rgba(53, 122, 232, 1) 100%)' |
| e.currentTarget.style.transform = 'translateY(-2px) scale(1.02)' |
| e.currentTarget.style.boxShadow = '0 6px 20px rgba(66, 133, 244, 0.35), 0 4px 8px rgba(0, 0, 0, 0.15)' |
| }} |
| onMouseLeave={(e) => { |
| e.currentTarget.style.background = 'linear-gradient(135deg, rgba(66, 133, 244, 0.95) 0%, rgba(53, 122, 232, 0.95) 100%)' |
| e.currentTarget.style.transform = 'translateY(0) scale(1)' |
| e.currentTarget.style.boxShadow = '0 4px 12px rgba(66, 133, 244, 0.25), 0 2px 4px rgba(0, 0, 0, 0.1)' |
| }} |
| title="Click to zoom to your location" |
| > |
| <span style={{ fontSize: '1.2em', lineHeight: 1, filter: 'drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2))' }}>📍</span> |
| <span style={{ letterSpacing: '0.3px' }}>You</span> |
| </button> |
| )} |
| </div> |
| {/* Close Button */} |
| <button |
| onClick={onClose} |
| style={{ |
| background: 'transparent', |
| border: 'none', |
| fontSize: '24px', |
| cursor: 'pointer', |
| color: 'var(--fg-secondary)', |
| padding: '4px 8px', |
| borderRadius: 'var(--radius-sm)', |
| transition: 'all 0.2s', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| width: '32px', |
| height: '32px', |
| lineHeight: 1 |
| }} |
| onMouseEnter={(e) => { |
| e.currentTarget.style.backgroundColor = 'var(--hover-bg)' |
| e.currentTarget.style.color = 'var(--fg)' |
| }} |
| onMouseLeave={(e) => { |
| e.currentTarget.style.backgroundColor = 'transparent' |
| e.currentTarget.style.color = 'var(--fg-secondary)' |
| }} |
| title="Close" |
| > |
| × |
| </button> |
| </div> |
| </div> |
| |
| {/* Map Container */} |
| <div |
| ref={mapRef} |
| style={{ |
| width: '100%', |
| flex: 1, |
| minHeight: 0, |
| position: 'relative' |
| }} |
| /> |
| {isGeocoding && ( |
| <div style={{ |
| position: 'absolute', |
| top: '50%', |
| left: '50%', |
| transform: 'translate(-50%, -50%)', |
| padding: '12px 20px', |
| backgroundColor: 'var(--card-bg)', |
| borderRadius: 'var(--radius-md)', |
| boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', |
| color: 'var(--fg)', |
| fontSize: '0.9em', |
| zIndex: 1000, |
| display: 'flex', |
| alignItems: 'center', |
| gap: '8px' |
| }}> |
| <span>Loading location...</span> |
| </div> |
| )} |
| {error && ( |
| <div style={{ |
| padding: '8px 20px', |
| backgroundColor: 'var(--hover-bg)', |
| color: 'var(--muted)', |
| fontSize: '0.8em', |
| textAlign: 'center', |
| borderTop: '1px solid var(--border)' |
| }}> |
| {error} |
| </div> |
| )} |
| </div> |
| </> |
| ) |
| } |
|
|
|
|