Explorar el Código

feat: show gold ghost markers on map for unregistered users

neyrogovnarik hace 1 mes
padre
commit
72b1812fee
Se han modificado 2 ficheros con 37 adiciones y 14 borrados
  1. 8 5
      frontend/src/components/MapView.tsx
  2. 29 9
      frontend/src/lib/map.ts

+ 8 - 5
frontend/src/components/MapView.tsx

@@ -22,8 +22,6 @@ export default function MapView() {
   const [mapError, setMapError] = useState(false)
 
   const fetchPlaces = useCallback(async (bounds?: MapBounds) => {
-    if (!user) return
-
     try {
       const params = new URLSearchParams()
       if (bounds) {
@@ -36,7 +34,7 @@ export default function MapView() {
     } catch {
       // Ошибка загрузки мест — карта продолжает работать с текущими данными
     }
-  }, [user])
+  }, [])
 
   /** Реф для актуальной версии fetchPlaces — предотвращает проблему устаревшего замыкания */
   const fetchPlacesRef = useRef(fetchPlaces)
@@ -64,6 +62,9 @@ export default function MapView() {
       provider.onMove((center, zoom, bounds) => {
         fetchPlacesRef.current(bounds)
       })
+
+      const b = provider.getBounds?.()
+      if (b) fetchPlaces(b)
     }).catch(() => {
       setMapError(true)
     })
@@ -78,11 +79,13 @@ export default function MapView() {
     const provider = mapRef.current
     if (!provider || !provider.isReady()) return
 
+    const isGuest = !user
     places.forEach((place) => {
       provider.addMarker(place.id, place.lat, place.lng, {
         type: place.type as 'place' | 'studio',
-        title: place.title,
-        onClick: () => setSelectedPlace(place),
+        title: isGuest ? undefined : place.title,
+        onClick: isGuest ? undefined : () => setSelectedPlace(place),
+        ghost: isGuest,
       })
     })
 

+ 29 - 9
frontend/src/lib/map.ts

@@ -14,6 +14,7 @@ export interface MarkerOptions {
   type?: 'place' | 'studio' | 'visitor'
   title?: string
   onClick?: () => void
+  ghost?: boolean
 }
 
 const TILE_URL = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
@@ -23,13 +24,19 @@ const MARKER_COLORS: Record<string, string> = {
   visitor: '#22c55e',
 }
 
-function createIcon(type: string, size: number): L.DivIcon {
-  const color = MARKER_COLORS[type] || '#3b82f6'
+function createIcon(type: string, size: number, ghost?: boolean): L.DivIcon {
+  const color = ghost
+    ? 'radial-gradient(circle, rgba(255, 215, 0, 0.9) 0%, rgba(255, 215, 0, 0.3) 50%, rgba(255, 215, 0, 0) 70%)'
+    : MARKER_COLORS[type] || '#3b82f6'
+  const style = ghost
+    ? `width:${size * 2.5}px;height:${size * 2.5}px;background:${color};border-radius:50%;box-shadow:0 0 20px rgba(255, 215, 0, 0.4);pointer-events:none`
+    : `width:${size}px;height:${size}px;background:${color};border-radius:50%;border:${size > 20 ? '3px' : '2px'} solid white;box-shadow:0 2px 8px rgba(0,0,0,0.3)`
+
   return L.divIcon({
     className: '',
-    iconSize: [size, size],
-    iconAnchor: [size / 2, size / 2],
-    html: `<div style="width:${size}px;height:${size}px;background:${color};border-radius:50%;border:${size > 20 ? '3px' : '2px'} solid white;box-shadow:0 2px 8px rgba(0,0,0,0.3);transition:transform 0.2s"></div>`,
+    iconSize: ghost ? [size * 2.5, size * 2.5] : [size, size],
+    iconAnchor: ghost ? [size * 2.5 / 2, size * 2.5 / 2] : [size / 2, size / 2],
+    html: `<div style="${style}"></div>`,
   })
 }
 
@@ -42,6 +49,7 @@ export interface MapProvider {
   setCenter(lat: number, lng: number): void
   fitBounds(bounds: [[number, number], [number, number]]): void
   onMove(cb: (center: [number, number], zoom: number, bounds: MapBounds) => void): void
+  getBounds(): MapBounds | null
 }
 
 export function createLeafletMapProvider(): MapProvider {
@@ -94,15 +102,16 @@ export function createLeafletMapProvider(): MapProvider {
 
     addMarker(id, lat, lng, options) {
       if (!map) return
+      const ghost = options?.ghost
       const type = options?.type || 'place'
       const size = type === 'visitor' ? 12 : 32
-      const icon = createIcon(type, size)
-      const marker = L.marker([lat, lng], { icon }).addTo(map)
+      const icon = createIcon(type, size, ghost)
+      const marker = L.marker([lat, lng], { icon, interactive: !ghost }).addTo(map)
 
-      if (options?.title) {
+      if (options?.title && !ghost) {
         marker.bindTooltip(options.title, { direction: 'top' })
       }
-      if (options?.onClick) {
+      if (options?.onClick && !ghost) {
         marker.on('click', () => options.onClick?.())
       }
 
@@ -131,5 +140,16 @@ export function createLeafletMapProvider(): MapProvider {
     onMove(cb) {
       moveHandler = cb
     },
+
+    getBounds() {
+      if (!map) return null
+      const b = map.getBounds()
+      return {
+        swLat: b.getSouthWest().lat,
+        swLng: b.getSouthWest().lng,
+        neLat: b.getNorthEast().lat,
+        neLng: b.getNorthEast().lng,
+      }
+    },
   }
 }